github.com/terraform-linters/tflint@v0.51.2-0.20240520175844-3750771571b6/terraform/lang/eval_test.go (about)

     1  package lang
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hclsyntax"
    10  
    11  	"github.com/zclconf/go-cty/cty"
    12  	ctyjson "github.com/zclconf/go-cty/cty/json"
    13  )
    14  
    15  func TestScopeEvalContext(t *testing.T) {
    16  	data := &dataForTests{
    17  		CountAttrs: map[string]cty.Value{
    18  			"index": cty.NumberIntVal(0),
    19  		},
    20  		ForEachAttrs: map[string]cty.Value{
    21  			"key":   cty.StringVal("a"),
    22  			"value": cty.NumberIntVal(1),
    23  		},
    24  		LocalValues: map[string]cty.Value{
    25  			"foo": cty.StringVal("bar"),
    26  		},
    27  		PathAttrs: map[string]cty.Value{
    28  			"module": cty.StringVal("foo/bar"),
    29  		},
    30  		TerraformAttrs: map[string]cty.Value{
    31  			"workspace": cty.StringVal("default"),
    32  		},
    33  		InputVariables: map[string]cty.Value{
    34  			"baz": cty.StringVal("boop"),
    35  		},
    36  	}
    37  
    38  	tests := []struct {
    39  		Expr string
    40  		Want map[string]cty.Value
    41  	}{
    42  		{
    43  			`12`,
    44  			map[string]cty.Value{},
    45  		},
    46  		{
    47  			`count.index`,
    48  			map[string]cty.Value{
    49  				"count": cty.ObjectVal(map[string]cty.Value{
    50  					"index": cty.NumberIntVal(0),
    51  				}),
    52  				"resource": cty.DynamicVal,
    53  				"data":     cty.DynamicVal,
    54  				"module":   cty.DynamicVal,
    55  				"self":     cty.DynamicVal,
    56  			},
    57  		},
    58  		{
    59  			`each.key`,
    60  			map[string]cty.Value{
    61  				"each": cty.ObjectVal(map[string]cty.Value{
    62  					"key": cty.StringVal("a"),
    63  				}),
    64  				"resource": cty.DynamicVal,
    65  				"data":     cty.DynamicVal,
    66  				"module":   cty.DynamicVal,
    67  				"self":     cty.DynamicVal,
    68  			},
    69  		},
    70  		{
    71  			`each.value`,
    72  			map[string]cty.Value{
    73  				"each": cty.ObjectVal(map[string]cty.Value{
    74  					"value": cty.NumberIntVal(1),
    75  				}),
    76  				"resource": cty.DynamicVal,
    77  				"data":     cty.DynamicVal,
    78  				"module":   cty.DynamicVal,
    79  				"self":     cty.DynamicVal,
    80  			},
    81  		},
    82  		{
    83  			`local.foo`,
    84  			map[string]cty.Value{
    85  				"local": cty.ObjectVal(map[string]cty.Value{
    86  					"foo": cty.StringVal("bar"),
    87  				}),
    88  				"resource": cty.DynamicVal,
    89  				"data":     cty.DynamicVal,
    90  				"module":   cty.DynamicVal,
    91  				"self":     cty.DynamicVal,
    92  			},
    93  		},
    94  		{
    95  			`null_resource.foo`,
    96  			map[string]cty.Value{
    97  				"null_resource": cty.DynamicVal,
    98  				"resource":      cty.DynamicVal,
    99  				"data":          cty.DynamicVal,
   100  				"module":        cty.DynamicVal,
   101  				"self":          cty.DynamicVal,
   102  			},
   103  		},
   104  		{
   105  			`null_resource.foo.attr`,
   106  			map[string]cty.Value{
   107  				"null_resource": cty.DynamicVal,
   108  				"resource":      cty.DynamicVal,
   109  				"data":          cty.DynamicVal,
   110  				"module":        cty.DynamicVal,
   111  				"self":          cty.DynamicVal,
   112  			},
   113  		},
   114  		{
   115  			`null_resource.multi`,
   116  			map[string]cty.Value{
   117  				"null_resource": cty.DynamicVal,
   118  				"resource":      cty.DynamicVal,
   119  				"data":          cty.DynamicVal,
   120  				"module":        cty.DynamicVal,
   121  				"self":          cty.DynamicVal,
   122  			},
   123  		},
   124  		{
   125  			`null_resource.multi[1]`,
   126  			map[string]cty.Value{
   127  				"null_resource": cty.DynamicVal,
   128  				"resource":      cty.DynamicVal,
   129  				"data":          cty.DynamicVal,
   130  				"module":        cty.DynamicVal,
   131  				"self":          cty.DynamicVal,
   132  			},
   133  		},
   134  		{
   135  			`null_resource.each["each1"]`,
   136  			map[string]cty.Value{
   137  				"null_resource": cty.DynamicVal,
   138  				"resource":      cty.DynamicVal,
   139  				"data":          cty.DynamicVal,
   140  				"module":        cty.DynamicVal,
   141  				"self":          cty.DynamicVal,
   142  			},
   143  		},
   144  		{
   145  			`null_resource.each["each1"].attr`,
   146  			map[string]cty.Value{
   147  				"null_resource": cty.DynamicVal,
   148  				"resource":      cty.DynamicVal,
   149  				"data":          cty.DynamicVal,
   150  				"module":        cty.DynamicVal,
   151  				"self":          cty.DynamicVal,
   152  			},
   153  		},
   154  		{
   155  			`foo(null_resource.multi, null_resource.multi[1])`,
   156  			map[string]cty.Value{
   157  				"null_resource": cty.DynamicVal,
   158  				"resource":      cty.DynamicVal,
   159  				"data":          cty.DynamicVal,
   160  				"module":        cty.DynamicVal,
   161  				"self":          cty.DynamicVal,
   162  			},
   163  		},
   164  		{
   165  			`path.module`,
   166  			map[string]cty.Value{
   167  				"path": cty.ObjectVal(map[string]cty.Value{
   168  					"module": cty.StringVal("foo/bar"),
   169  				}),
   170  				"resource": cty.DynamicVal,
   171  				"data":     cty.DynamicVal,
   172  				"module":   cty.DynamicVal,
   173  				"self":     cty.DynamicVal,
   174  			},
   175  		},
   176  		{
   177  			`terraform.workspace`,
   178  			map[string]cty.Value{
   179  				"terraform": cty.ObjectVal(map[string]cty.Value{
   180  					"workspace": cty.StringVal("default"),
   181  				}),
   182  				"resource": cty.DynamicVal,
   183  				"data":     cty.DynamicVal,
   184  				"module":   cty.DynamicVal,
   185  				"self":     cty.DynamicVal,
   186  			},
   187  		},
   188  		{
   189  			`var.baz`,
   190  			map[string]cty.Value{
   191  				"var": cty.ObjectVal(map[string]cty.Value{
   192  					"baz": cty.StringVal("boop"),
   193  				}),
   194  				"resource": cty.DynamicVal,
   195  				"data":     cty.DynamicVal,
   196  				"module":   cty.DynamicVal,
   197  				"self":     cty.DynamicVal,
   198  			},
   199  		},
   200  	}
   201  
   202  	for _, test := range tests {
   203  		t.Run(test.Expr, func(t *testing.T) {
   204  			expr, parseDiags := hclsyntax.ParseExpression([]byte(test.Expr), "", hcl.Pos{Line: 1, Column: 1})
   205  			if len(parseDiags) != 0 {
   206  				t.Errorf("unexpected diagnostics during parse")
   207  				for _, diag := range parseDiags {
   208  					t.Errorf("- %s", diag)
   209  				}
   210  				return
   211  			}
   212  
   213  			refs, refsDiags := ReferencesInExpr(expr)
   214  			if refsDiags.HasErrors() {
   215  				t.Fatal(refsDiags)
   216  			}
   217  
   218  			funcCalls, funcDiags := FunctionCallsInExpr(expr)
   219  			if funcDiags.HasErrors() {
   220  				t.Fatal(funcDiags)
   221  			}
   222  
   223  			scope := &Scope{
   224  				Data: data,
   225  			}
   226  			ctx, ctxDiags := scope.EvalContext(refs, funcCalls)
   227  			if ctxDiags.HasErrors() {
   228  				t.Fatal(ctxDiags)
   229  			}
   230  
   231  			// For easier test assertions we'll just remove any top-level
   232  			// empty objects from our variables map.
   233  			for k, v := range ctx.Variables {
   234  				if v.RawEquals(cty.EmptyObjectVal) {
   235  					delete(ctx.Variables, k)
   236  				}
   237  			}
   238  
   239  			gotVal := cty.ObjectVal(ctx.Variables)
   240  			wantVal := cty.ObjectVal(test.Want)
   241  
   242  			if !gotVal.RawEquals(wantVal) {
   243  				// We'll JSON-ize our values here just so it's easier to
   244  				// read them in the assertion output.
   245  				gotJSON := formattedJSONValue(gotVal)
   246  				wantJSON := formattedJSONValue(wantVal)
   247  
   248  				t.Errorf(
   249  					"wrong result\nexpr: %s\ngot:  %s\nwant: %s",
   250  					test.Expr, gotJSON, wantJSON,
   251  				)
   252  			}
   253  		})
   254  	}
   255  }
   256  
   257  func formattedJSONValue(val cty.Value) string {
   258  	val = cty.UnknownAsNull(val) // since JSON can't represent unknowns
   259  	j, err := ctyjson.Marshal(val, val.Type())
   260  	if err != nil {
   261  		panic(err)
   262  	}
   263  	var buf bytes.Buffer
   264  	json.Indent(&buf, j, "", "  ")
   265  	return buf.String()
   266  }