github.com/hugorut/terraform@v1.1.3/src/lang/eval_test.go (about)

     1  package lang
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"testing"
     7  
     8  	"github.com/hugorut/terraform/src/addrs"
     9  	"github.com/hugorut/terraform/src/configs/configschema"
    10  	"github.com/hugorut/terraform/src/instances"
    11  
    12  	"github.com/hashicorp/hcl/v2"
    13  	"github.com/hashicorp/hcl/v2/hclsyntax"
    14  
    15  	"github.com/zclconf/go-cty/cty"
    16  	ctyjson "github.com/zclconf/go-cty/cty/json"
    17  )
    18  
    19  func TestScopeEvalContext(t *testing.T) {
    20  	data := &dataForTests{
    21  		CountAttrs: map[string]cty.Value{
    22  			"index": cty.NumberIntVal(0),
    23  		},
    24  		ForEachAttrs: map[string]cty.Value{
    25  			"key":   cty.StringVal("a"),
    26  			"value": cty.NumberIntVal(1),
    27  		},
    28  		Resources: map[string]cty.Value{
    29  			"null_resource.foo": cty.ObjectVal(map[string]cty.Value{
    30  				"attr": cty.StringVal("bar"),
    31  			}),
    32  			"data.null_data_source.foo": cty.ObjectVal(map[string]cty.Value{
    33  				"attr": cty.StringVal("bar"),
    34  			}),
    35  			"null_resource.multi": cty.TupleVal([]cty.Value{
    36  				cty.ObjectVal(map[string]cty.Value{
    37  					"attr": cty.StringVal("multi0"),
    38  				}),
    39  				cty.ObjectVal(map[string]cty.Value{
    40  					"attr": cty.StringVal("multi1"),
    41  				}),
    42  			}),
    43  			"null_resource.each": cty.ObjectVal(map[string]cty.Value{
    44  				"each0": cty.ObjectVal(map[string]cty.Value{
    45  					"attr": cty.StringVal("each0"),
    46  				}),
    47  				"each1": cty.ObjectVal(map[string]cty.Value{
    48  					"attr": cty.StringVal("each1"),
    49  				}),
    50  			}),
    51  			"null_resource.multi[1]": cty.ObjectVal(map[string]cty.Value{
    52  				"attr": cty.StringVal("multi1"),
    53  			}),
    54  		},
    55  		LocalValues: map[string]cty.Value{
    56  			"foo": cty.StringVal("bar"),
    57  		},
    58  		Modules: map[string]cty.Value{
    59  			"module.foo": cty.ObjectVal(map[string]cty.Value{
    60  				"output0": cty.StringVal("bar0"),
    61  				"output1": cty.StringVal("bar1"),
    62  			}),
    63  		},
    64  		PathAttrs: map[string]cty.Value{
    65  			"module": cty.StringVal("foo/bar"),
    66  		},
    67  		TerraformAttrs: map[string]cty.Value{
    68  			"workspace": cty.StringVal("default"),
    69  		},
    70  		InputVariables: map[string]cty.Value{
    71  			"baz": cty.StringVal("boop"),
    72  		},
    73  	}
    74  
    75  	tests := []struct {
    76  		Expr string
    77  		Want map[string]cty.Value
    78  	}{
    79  		{
    80  			`12`,
    81  			map[string]cty.Value{},
    82  		},
    83  		{
    84  			`count.index`,
    85  			map[string]cty.Value{
    86  				"count": cty.ObjectVal(map[string]cty.Value{
    87  					"index": cty.NumberIntVal(0),
    88  				}),
    89  			},
    90  		},
    91  		{
    92  			`each.key`,
    93  			map[string]cty.Value{
    94  				"each": cty.ObjectVal(map[string]cty.Value{
    95  					"key": cty.StringVal("a"),
    96  				}),
    97  			},
    98  		},
    99  		{
   100  			`each.value`,
   101  			map[string]cty.Value{
   102  				"each": cty.ObjectVal(map[string]cty.Value{
   103  					"value": cty.NumberIntVal(1),
   104  				}),
   105  			},
   106  		},
   107  		{
   108  			`local.foo`,
   109  			map[string]cty.Value{
   110  				"local": cty.ObjectVal(map[string]cty.Value{
   111  					"foo": cty.StringVal("bar"),
   112  				}),
   113  			},
   114  		},
   115  		{
   116  			`null_resource.foo`,
   117  			map[string]cty.Value{
   118  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   119  					"foo": cty.ObjectVal(map[string]cty.Value{
   120  						"attr": cty.StringVal("bar"),
   121  					}),
   122  				}),
   123  				"resource": cty.ObjectVal(map[string]cty.Value{
   124  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   125  						"foo": cty.ObjectVal(map[string]cty.Value{
   126  							"attr": cty.StringVal("bar"),
   127  						}),
   128  					}),
   129  				}),
   130  			},
   131  		},
   132  		{
   133  			`null_resource.foo.attr`,
   134  			map[string]cty.Value{
   135  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   136  					"foo": cty.ObjectVal(map[string]cty.Value{
   137  						"attr": cty.StringVal("bar"),
   138  					}),
   139  				}),
   140  				"resource": cty.ObjectVal(map[string]cty.Value{
   141  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   142  						"foo": cty.ObjectVal(map[string]cty.Value{
   143  							"attr": cty.StringVal("bar"),
   144  						}),
   145  					}),
   146  				}),
   147  			},
   148  		},
   149  		{
   150  			`null_resource.multi`,
   151  			map[string]cty.Value{
   152  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   153  					"multi": cty.TupleVal([]cty.Value{
   154  						cty.ObjectVal(map[string]cty.Value{
   155  							"attr": cty.StringVal("multi0"),
   156  						}),
   157  						cty.ObjectVal(map[string]cty.Value{
   158  							"attr": cty.StringVal("multi1"),
   159  						}),
   160  					}),
   161  				}),
   162  				"resource": cty.ObjectVal(map[string]cty.Value{
   163  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   164  						"multi": cty.TupleVal([]cty.Value{
   165  							cty.ObjectVal(map[string]cty.Value{
   166  								"attr": cty.StringVal("multi0"),
   167  							}),
   168  							cty.ObjectVal(map[string]cty.Value{
   169  								"attr": cty.StringVal("multi1"),
   170  							}),
   171  						}),
   172  					}),
   173  				}),
   174  			},
   175  		},
   176  		{
   177  			// at this level, all instance references return the entire resource
   178  			`null_resource.multi[1]`,
   179  			map[string]cty.Value{
   180  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   181  					"multi": cty.TupleVal([]cty.Value{
   182  						cty.ObjectVal(map[string]cty.Value{
   183  							"attr": cty.StringVal("multi0"),
   184  						}),
   185  						cty.ObjectVal(map[string]cty.Value{
   186  							"attr": cty.StringVal("multi1"),
   187  						}),
   188  					}),
   189  				}),
   190  				"resource": cty.ObjectVal(map[string]cty.Value{
   191  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   192  						"multi": cty.TupleVal([]cty.Value{
   193  							cty.ObjectVal(map[string]cty.Value{
   194  								"attr": cty.StringVal("multi0"),
   195  							}),
   196  							cty.ObjectVal(map[string]cty.Value{
   197  								"attr": cty.StringVal("multi1"),
   198  							}),
   199  						}),
   200  					}),
   201  				}),
   202  			},
   203  		},
   204  		{
   205  			// at this level, all instance references return the entire resource
   206  			`null_resource.each["each1"]`,
   207  			map[string]cty.Value{
   208  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   209  					"each": cty.ObjectVal(map[string]cty.Value{
   210  						"each0": cty.ObjectVal(map[string]cty.Value{
   211  							"attr": cty.StringVal("each0"),
   212  						}),
   213  						"each1": cty.ObjectVal(map[string]cty.Value{
   214  							"attr": cty.StringVal("each1"),
   215  						}),
   216  					}),
   217  				}),
   218  				"resource": cty.ObjectVal(map[string]cty.Value{
   219  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   220  						"each": cty.ObjectVal(map[string]cty.Value{
   221  							"each0": cty.ObjectVal(map[string]cty.Value{
   222  								"attr": cty.StringVal("each0"),
   223  							}),
   224  							"each1": cty.ObjectVal(map[string]cty.Value{
   225  								"attr": cty.StringVal("each1"),
   226  							}),
   227  						}),
   228  					}),
   229  				}),
   230  			},
   231  		},
   232  		{
   233  			// at this level, all instance references return the entire resource
   234  			`null_resource.each["each1"].attr`,
   235  			map[string]cty.Value{
   236  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   237  					"each": cty.ObjectVal(map[string]cty.Value{
   238  						"each0": cty.ObjectVal(map[string]cty.Value{
   239  							"attr": cty.StringVal("each0"),
   240  						}),
   241  						"each1": cty.ObjectVal(map[string]cty.Value{
   242  							"attr": cty.StringVal("each1"),
   243  						}),
   244  					}),
   245  				}),
   246  				"resource": cty.ObjectVal(map[string]cty.Value{
   247  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   248  						"each": cty.ObjectVal(map[string]cty.Value{
   249  							"each0": cty.ObjectVal(map[string]cty.Value{
   250  								"attr": cty.StringVal("each0"),
   251  							}),
   252  							"each1": cty.ObjectVal(map[string]cty.Value{
   253  								"attr": cty.StringVal("each1"),
   254  							}),
   255  						}),
   256  					}),
   257  				}),
   258  			},
   259  		},
   260  		{
   261  			`foo(null_resource.multi, null_resource.multi[1])`,
   262  			map[string]cty.Value{
   263  				"null_resource": cty.ObjectVal(map[string]cty.Value{
   264  					"multi": cty.TupleVal([]cty.Value{
   265  						cty.ObjectVal(map[string]cty.Value{
   266  							"attr": cty.StringVal("multi0"),
   267  						}),
   268  						cty.ObjectVal(map[string]cty.Value{
   269  							"attr": cty.StringVal("multi1"),
   270  						}),
   271  					}),
   272  				}),
   273  				"resource": cty.ObjectVal(map[string]cty.Value{
   274  					"null_resource": cty.ObjectVal(map[string]cty.Value{
   275  						"multi": cty.TupleVal([]cty.Value{
   276  							cty.ObjectVal(map[string]cty.Value{
   277  								"attr": cty.StringVal("multi0"),
   278  							}),
   279  							cty.ObjectVal(map[string]cty.Value{
   280  								"attr": cty.StringVal("multi1"),
   281  							}),
   282  						}),
   283  					}),
   284  				}),
   285  			},
   286  		},
   287  		{
   288  			`data.null_data_source.foo`,
   289  			map[string]cty.Value{
   290  				"data": cty.ObjectVal(map[string]cty.Value{
   291  					"null_data_source": cty.ObjectVal(map[string]cty.Value{
   292  						"foo": cty.ObjectVal(map[string]cty.Value{
   293  							"attr": cty.StringVal("bar"),
   294  						}),
   295  					}),
   296  				}),
   297  			},
   298  		},
   299  		{
   300  			`module.foo`,
   301  			map[string]cty.Value{
   302  				"module": cty.ObjectVal(map[string]cty.Value{
   303  					"foo": cty.ObjectVal(map[string]cty.Value{
   304  						"output0": cty.StringVal("bar0"),
   305  						"output1": cty.StringVal("bar1"),
   306  					}),
   307  				}),
   308  			},
   309  		},
   310  		// any module reference returns the entire module
   311  		{
   312  			`module.foo.output1`,
   313  			map[string]cty.Value{
   314  				"module": cty.ObjectVal(map[string]cty.Value{
   315  					"foo": cty.ObjectVal(map[string]cty.Value{
   316  						"output0": cty.StringVal("bar0"),
   317  						"output1": cty.StringVal("bar1"),
   318  					}),
   319  				}),
   320  			},
   321  		},
   322  		{
   323  			`path.module`,
   324  			map[string]cty.Value{
   325  				"path": cty.ObjectVal(map[string]cty.Value{
   326  					"module": cty.StringVal("foo/bar"),
   327  				}),
   328  			},
   329  		},
   330  		{
   331  			`self.baz`,
   332  			map[string]cty.Value{
   333  				"self": cty.ObjectVal(map[string]cty.Value{
   334  					"attr": cty.StringVal("multi1"),
   335  				}),
   336  			},
   337  		},
   338  		{
   339  			`terraform.workspace`,
   340  			map[string]cty.Value{
   341  				"terraform": cty.ObjectVal(map[string]cty.Value{
   342  					"workspace": cty.StringVal("default"),
   343  				}),
   344  			},
   345  		},
   346  		{
   347  			`var.baz`,
   348  			map[string]cty.Value{
   349  				"var": cty.ObjectVal(map[string]cty.Value{
   350  					"baz": cty.StringVal("boop"),
   351  				}),
   352  			},
   353  		},
   354  	}
   355  
   356  	for _, test := range tests {
   357  		t.Run(test.Expr, func(t *testing.T) {
   358  			expr, parseDiags := hclsyntax.ParseExpression([]byte(test.Expr), "", hcl.Pos{Line: 1, Column: 1})
   359  			if len(parseDiags) != 0 {
   360  				t.Errorf("unexpected diagnostics during parse")
   361  				for _, diag := range parseDiags {
   362  					t.Errorf("- %s", diag)
   363  				}
   364  				return
   365  			}
   366  
   367  			refs, refsDiags := ReferencesInExpr(expr)
   368  			if refsDiags.HasErrors() {
   369  				t.Fatal(refsDiags.Err())
   370  			}
   371  
   372  			scope := &Scope{
   373  				Data: data,
   374  
   375  				// "self" will just be an arbitrary one of the several resource
   376  				// instances we have in our test dataset.
   377  				SelfAddr: addrs.ResourceInstance{
   378  					Resource: addrs.Resource{
   379  						Mode: addrs.ManagedResourceMode,
   380  						Type: "null_resource",
   381  						Name: "multi",
   382  					},
   383  					Key: addrs.IntKey(1),
   384  				},
   385  			}
   386  			ctx, ctxDiags := scope.EvalContext(refs)
   387  			if ctxDiags.HasErrors() {
   388  				t.Fatal(ctxDiags.Err())
   389  			}
   390  
   391  			// For easier test assertions we'll just remove any top-level
   392  			// empty objects from our variables map.
   393  			for k, v := range ctx.Variables {
   394  				if v.RawEquals(cty.EmptyObjectVal) {
   395  					delete(ctx.Variables, k)
   396  				}
   397  			}
   398  
   399  			gotVal := cty.ObjectVal(ctx.Variables)
   400  			wantVal := cty.ObjectVal(test.Want)
   401  
   402  			if !gotVal.RawEquals(wantVal) {
   403  				// We'll JSON-ize our values here just so it's easier to
   404  				// read them in the assertion output.
   405  				gotJSON := formattedJSONValue(gotVal)
   406  				wantJSON := formattedJSONValue(wantVal)
   407  
   408  				t.Errorf(
   409  					"wrong result\nexpr: %s\ngot:  %s\nwant: %s",
   410  					test.Expr, gotJSON, wantJSON,
   411  				)
   412  			}
   413  		})
   414  	}
   415  }
   416  
   417  func TestScopeExpandEvalBlock(t *testing.T) {
   418  	nestedObjTy := cty.Object(map[string]cty.Type{
   419  		"boop": cty.String,
   420  	})
   421  	schema := &configschema.Block{
   422  		Attributes: map[string]*configschema.Attribute{
   423  			"foo":         {Type: cty.String, Optional: true},
   424  			"list_of_obj": {Type: cty.List(nestedObjTy), Optional: true},
   425  		},
   426  		BlockTypes: map[string]*configschema.NestedBlock{
   427  			"bar": {
   428  				Nesting: configschema.NestingMap,
   429  				Block: configschema.Block{
   430  					Attributes: map[string]*configschema.Attribute{
   431  						"baz": {Type: cty.String, Optional: true},
   432  					},
   433  				},
   434  			},
   435  		},
   436  	}
   437  	data := &dataForTests{
   438  		LocalValues: map[string]cty.Value{
   439  			"greeting": cty.StringVal("howdy"),
   440  			"list": cty.ListVal([]cty.Value{
   441  				cty.StringVal("elem0"),
   442  				cty.StringVal("elem1"),
   443  			}),
   444  			"map": cty.MapVal(map[string]cty.Value{
   445  				"key1": cty.StringVal("val1"),
   446  				"key2": cty.StringVal("val2"),
   447  			}),
   448  		},
   449  	}
   450  
   451  	tests := map[string]struct {
   452  		Config string
   453  		Want   cty.Value
   454  	}{
   455  		"empty": {
   456  			`
   457  			`,
   458  			cty.ObjectVal(map[string]cty.Value{
   459  				"foo":         cty.NullVal(cty.String),
   460  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   461  				"bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   462  					"baz": cty.String,
   463  				})),
   464  			}),
   465  		},
   466  		"literal attribute": {
   467  			`
   468  			foo = "hello"
   469  			`,
   470  			cty.ObjectVal(map[string]cty.Value{
   471  				"foo":         cty.StringVal("hello"),
   472  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   473  				"bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   474  					"baz": cty.String,
   475  				})),
   476  			}),
   477  		},
   478  		"variable attribute": {
   479  			`
   480  			foo = local.greeting
   481  			`,
   482  			cty.ObjectVal(map[string]cty.Value{
   483  				"foo":         cty.StringVal("howdy"),
   484  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   485  				"bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   486  					"baz": cty.String,
   487  				})),
   488  			}),
   489  		},
   490  		"one static block": {
   491  			`
   492  			bar "static" {}
   493  			`,
   494  			cty.ObjectVal(map[string]cty.Value{
   495  				"foo":         cty.NullVal(cty.String),
   496  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   497  				"bar": cty.MapVal(map[string]cty.Value{
   498  					"static": cty.ObjectVal(map[string]cty.Value{
   499  						"baz": cty.NullVal(cty.String),
   500  					}),
   501  				}),
   502  			}),
   503  		},
   504  		"two static blocks": {
   505  			`
   506  			bar "static0" {
   507  				baz = 0
   508  			}
   509  			bar "static1" {
   510  				baz = 1
   511  			}
   512  			`,
   513  			cty.ObjectVal(map[string]cty.Value{
   514  				"foo":         cty.NullVal(cty.String),
   515  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   516  				"bar": cty.MapVal(map[string]cty.Value{
   517  					"static0": cty.ObjectVal(map[string]cty.Value{
   518  						"baz": cty.StringVal("0"),
   519  					}),
   520  					"static1": cty.ObjectVal(map[string]cty.Value{
   521  						"baz": cty.StringVal("1"),
   522  					}),
   523  				}),
   524  			}),
   525  		},
   526  		"dynamic blocks from list": {
   527  			`
   528  			dynamic "bar" {
   529  				for_each = local.list
   530  				labels = [bar.value]
   531  				content {
   532  					baz = bar.key
   533  				}
   534  			}
   535  			`,
   536  			cty.ObjectVal(map[string]cty.Value{
   537  				"foo":         cty.NullVal(cty.String),
   538  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   539  				"bar": cty.MapVal(map[string]cty.Value{
   540  					"elem0": cty.ObjectVal(map[string]cty.Value{
   541  						"baz": cty.StringVal("0"),
   542  					}),
   543  					"elem1": cty.ObjectVal(map[string]cty.Value{
   544  						"baz": cty.StringVal("1"),
   545  					}),
   546  				}),
   547  			}),
   548  		},
   549  		"dynamic blocks from map": {
   550  			`
   551  			dynamic "bar" {
   552  				for_each = local.map
   553  				labels = [bar.key]
   554  				content {
   555  					baz = bar.value
   556  				}
   557  			}
   558  			`,
   559  			cty.ObjectVal(map[string]cty.Value{
   560  				"foo":         cty.NullVal(cty.String),
   561  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   562  				"bar": cty.MapVal(map[string]cty.Value{
   563  					"key1": cty.ObjectVal(map[string]cty.Value{
   564  						"baz": cty.StringVal("val1"),
   565  					}),
   566  					"key2": cty.ObjectVal(map[string]cty.Value{
   567  						"baz": cty.StringVal("val2"),
   568  					}),
   569  				}),
   570  			}),
   571  		},
   572  		"list-of-object attribute": {
   573  			`
   574  			list_of_obj = [
   575  				{
   576  					boop = local.greeting
   577  				},
   578  			]
   579  			`,
   580  			cty.ObjectVal(map[string]cty.Value{
   581  				"foo": cty.NullVal(cty.String),
   582  				"list_of_obj": cty.ListVal([]cty.Value{
   583  					cty.ObjectVal(map[string]cty.Value{
   584  						"boop": cty.StringVal("howdy"),
   585  					}),
   586  				}),
   587  				"bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   588  					"baz": cty.String,
   589  				})),
   590  			}),
   591  		},
   592  		"list-of-object attribute as blocks": {
   593  			`
   594  			list_of_obj {
   595  				boop = local.greeting
   596  			}
   597  			`,
   598  			cty.ObjectVal(map[string]cty.Value{
   599  				"foo": cty.NullVal(cty.String),
   600  				"list_of_obj": cty.ListVal([]cty.Value{
   601  					cty.ObjectVal(map[string]cty.Value{
   602  						"boop": cty.StringVal("howdy"),
   603  					}),
   604  				}),
   605  				"bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{
   606  					"baz": cty.String,
   607  				})),
   608  			}),
   609  		},
   610  		"lots of things at once": {
   611  			`
   612  			foo = "whoop"
   613  			bar "static0" {
   614  				baz = "s0"
   615  			}
   616  			dynamic "bar" {
   617  				for_each = local.list
   618  				labels = [bar.value]
   619  				content {
   620  					baz = bar.key
   621  				}
   622  			}
   623  			bar "static1" {
   624  				baz = "s1"
   625  			}
   626  			dynamic "bar" {
   627  				for_each = local.map
   628  				labels = [bar.key]
   629  				content {
   630  					baz = bar.value
   631  				}
   632  			}
   633  			bar "static2" {
   634  				baz = "s2"
   635  			}
   636  			`,
   637  			cty.ObjectVal(map[string]cty.Value{
   638  				"foo":         cty.StringVal("whoop"),
   639  				"list_of_obj": cty.NullVal(cty.List(nestedObjTy)),
   640  				"bar": cty.MapVal(map[string]cty.Value{
   641  					"key1": cty.ObjectVal(map[string]cty.Value{
   642  						"baz": cty.StringVal("val1"),
   643  					}),
   644  					"key2": cty.ObjectVal(map[string]cty.Value{
   645  						"baz": cty.StringVal("val2"),
   646  					}),
   647  					"elem0": cty.ObjectVal(map[string]cty.Value{
   648  						"baz": cty.StringVal("0"),
   649  					}),
   650  					"elem1": cty.ObjectVal(map[string]cty.Value{
   651  						"baz": cty.StringVal("1"),
   652  					}),
   653  					"static0": cty.ObjectVal(map[string]cty.Value{
   654  						"baz": cty.StringVal("s0"),
   655  					}),
   656  					"static1": cty.ObjectVal(map[string]cty.Value{
   657  						"baz": cty.StringVal("s1"),
   658  					}),
   659  					"static2": cty.ObjectVal(map[string]cty.Value{
   660  						"baz": cty.StringVal("s2"),
   661  					}),
   662  				}),
   663  			}),
   664  		},
   665  	}
   666  
   667  	for name, test := range tests {
   668  		t.Run(name, func(t *testing.T) {
   669  			file, parseDiags := hclsyntax.ParseConfig([]byte(test.Config), "", hcl.Pos{Line: 1, Column: 1})
   670  			if len(parseDiags) != 0 {
   671  				t.Errorf("unexpected diagnostics during parse")
   672  				for _, diag := range parseDiags {
   673  					t.Errorf("- %s", diag)
   674  				}
   675  				return
   676  			}
   677  
   678  			body := file.Body
   679  			scope := &Scope{
   680  				Data: data,
   681  			}
   682  
   683  			body, expandDiags := scope.ExpandBlock(body, schema)
   684  			if expandDiags.HasErrors() {
   685  				t.Fatal(expandDiags.Err())
   686  			}
   687  
   688  			got, valDiags := scope.EvalBlock(body, schema)
   689  			if valDiags.HasErrors() {
   690  				t.Fatal(valDiags.Err())
   691  			}
   692  
   693  			if !got.RawEquals(test.Want) {
   694  				// We'll JSON-ize our values here just so it's easier to
   695  				// read them in the assertion output.
   696  				gotJSON := formattedJSONValue(got)
   697  				wantJSON := formattedJSONValue(test.Want)
   698  
   699  				t.Errorf(
   700  					"wrong result\nconfig: %s\ngot:   %s\nwant:  %s",
   701  					test.Config, gotJSON, wantJSON,
   702  				)
   703  			}
   704  
   705  		})
   706  	}
   707  
   708  }
   709  
   710  func formattedJSONValue(val cty.Value) string {
   711  	val = cty.UnknownAsNull(val) // since JSON can't represent unknowns
   712  	j, err := ctyjson.Marshal(val, val.Type())
   713  	if err != nil {
   714  		panic(err)
   715  	}
   716  	var buf bytes.Buffer
   717  	json.Indent(&buf, j, "", "  ")
   718  	return buf.String()
   719  }
   720  
   721  func TestScopeEvalSelfBlock(t *testing.T) {
   722  	data := &dataForTests{
   723  		PathAttrs: map[string]cty.Value{
   724  			"module": cty.StringVal("foo/bar"),
   725  			"cwd":    cty.StringVal("/home/foo/bar"),
   726  			"root":   cty.StringVal("/home/foo"),
   727  		},
   728  		TerraformAttrs: map[string]cty.Value{
   729  			"workspace": cty.StringVal("default"),
   730  		},
   731  	}
   732  	schema := &configschema.Block{
   733  		Attributes: map[string]*configschema.Attribute{
   734  			"attr": {
   735  				Type: cty.String,
   736  			},
   737  			"num": {
   738  				Type: cty.Number,
   739  			},
   740  		},
   741  	}
   742  
   743  	tests := []struct {
   744  		Config  string
   745  		Self    cty.Value
   746  		KeyData instances.RepetitionData
   747  		Want    map[string]cty.Value
   748  	}{
   749  		{
   750  			Config: `attr = self.foo`,
   751  			Self: cty.ObjectVal(map[string]cty.Value{
   752  				"foo": cty.StringVal("bar"),
   753  			}),
   754  			KeyData: instances.RepetitionData{
   755  				CountIndex: cty.NumberIntVal(0),
   756  			},
   757  			Want: map[string]cty.Value{
   758  				"attr": cty.StringVal("bar"),
   759  				"num":  cty.NullVal(cty.Number),
   760  			},
   761  		},
   762  		{
   763  			Config: `num = count.index`,
   764  			KeyData: instances.RepetitionData{
   765  				CountIndex: cty.NumberIntVal(0),
   766  			},
   767  			Want: map[string]cty.Value{
   768  				"attr": cty.NullVal(cty.String),
   769  				"num":  cty.NumberIntVal(0),
   770  			},
   771  		},
   772  		{
   773  			Config: `attr = each.key`,
   774  			KeyData: instances.RepetitionData{
   775  				EachKey: cty.StringVal("a"),
   776  			},
   777  			Want: map[string]cty.Value{
   778  				"attr": cty.StringVal("a"),
   779  				"num":  cty.NullVal(cty.Number),
   780  			},
   781  		},
   782  		{
   783  			Config: `attr = path.cwd`,
   784  			Want: map[string]cty.Value{
   785  				"attr": cty.StringVal("/home/foo/bar"),
   786  				"num":  cty.NullVal(cty.Number),
   787  			},
   788  		},
   789  		{
   790  			Config: `attr = path.module`,
   791  			Want: map[string]cty.Value{
   792  				"attr": cty.StringVal("foo/bar"),
   793  				"num":  cty.NullVal(cty.Number),
   794  			},
   795  		},
   796  		{
   797  			Config: `attr = path.root`,
   798  			Want: map[string]cty.Value{
   799  				"attr": cty.StringVal("/home/foo"),
   800  				"num":  cty.NullVal(cty.Number),
   801  			},
   802  		},
   803  		{
   804  			Config: `attr = terraform.workspace`,
   805  			Want: map[string]cty.Value{
   806  				"attr": cty.StringVal("default"),
   807  				"num":  cty.NullVal(cty.Number),
   808  			},
   809  		},
   810  	}
   811  
   812  	for _, test := range tests {
   813  		t.Run(test.Config, func(t *testing.T) {
   814  			file, parseDiags := hclsyntax.ParseConfig([]byte(test.Config), "", hcl.Pos{Line: 1, Column: 1})
   815  			if len(parseDiags) != 0 {
   816  				t.Errorf("unexpected diagnostics during parse")
   817  				for _, diag := range parseDiags {
   818  					t.Errorf("- %s", diag)
   819  				}
   820  				return
   821  			}
   822  
   823  			body := file.Body
   824  
   825  			scope := &Scope{
   826  				Data: data,
   827  			}
   828  
   829  			gotVal, ctxDiags := scope.EvalSelfBlock(body, test.Self, schema, test.KeyData)
   830  			if ctxDiags.HasErrors() {
   831  				t.Fatal(ctxDiags.Err())
   832  			}
   833  
   834  			wantVal := cty.ObjectVal(test.Want)
   835  
   836  			if !gotVal.RawEquals(wantVal) {
   837  				t.Errorf(
   838  					"wrong result\nexpr: %s\ngot:  %#v\nwant: %#v",
   839  					test.Config, gotVal, wantVal,
   840  				)
   841  			}
   842  		})
   843  	}
   844  }