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 }