github.com/opentofu/opentofu@v1.7.1/internal/configs/hcl2shim/flatmap_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package hcl2shim
     7  
     8  import (
     9  	"fmt"
    10  	"testing"
    11  
    12  	"github.com/go-test/deep"
    13  
    14  	"github.com/zclconf/go-cty/cty"
    15  )
    16  
    17  func TestFlatmapValueFromHCL2(t *testing.T) {
    18  	tests := []struct {
    19  		Value cty.Value
    20  		Want  map[string]string
    21  	}{
    22  		{
    23  			cty.EmptyObjectVal,
    24  			map[string]string{},
    25  		},
    26  		{
    27  			cty.ObjectVal(map[string]cty.Value{
    28  				"foo": cty.StringVal("hello"),
    29  			}),
    30  			map[string]string{
    31  				"foo": "hello",
    32  			},
    33  		},
    34  		{
    35  			cty.ObjectVal(map[string]cty.Value{
    36  				"foo": cty.UnknownVal(cty.Bool),
    37  			}),
    38  			map[string]string{
    39  				"foo": UnknownVariableValue,
    40  			},
    41  		},
    42  		{
    43  			cty.ObjectVal(map[string]cty.Value{
    44  				"foo": cty.NumberIntVal(12),
    45  			}),
    46  			map[string]string{
    47  				"foo": "12",
    48  			},
    49  		},
    50  		{
    51  			cty.ObjectVal(map[string]cty.Value{
    52  				"foo": cty.True,
    53  				"bar": cty.False,
    54  			}),
    55  			map[string]string{
    56  				"foo": "true",
    57  				"bar": "false",
    58  			},
    59  		},
    60  		{
    61  			cty.ObjectVal(map[string]cty.Value{
    62  				"foo": cty.StringVal("hello"),
    63  				"bar": cty.StringVal("world"),
    64  				"baz": cty.StringVal("whelp"),
    65  			}),
    66  			map[string]string{
    67  				"foo": "hello",
    68  				"bar": "world",
    69  				"baz": "whelp",
    70  			},
    71  		},
    72  		{
    73  			cty.ObjectVal(map[string]cty.Value{
    74  				"foo": cty.ListValEmpty(cty.String),
    75  			}),
    76  			map[string]string{
    77  				"foo.#": "0",
    78  			},
    79  		},
    80  		{
    81  			cty.ObjectVal(map[string]cty.Value{
    82  				"foo": cty.UnknownVal(cty.List(cty.String)),
    83  			}),
    84  			map[string]string{
    85  				"foo.#": UnknownVariableValue,
    86  			},
    87  		},
    88  		{
    89  			cty.ObjectVal(map[string]cty.Value{
    90  				"foo": cty.ListVal([]cty.Value{
    91  					cty.StringVal("hello"),
    92  				}),
    93  			}),
    94  			map[string]string{
    95  				"foo.#": "1",
    96  				"foo.0": "hello",
    97  			},
    98  		},
    99  		{
   100  			cty.ObjectVal(map[string]cty.Value{
   101  				"foo": cty.ListVal([]cty.Value{
   102  					cty.StringVal("hello"),
   103  					cty.StringVal("world"),
   104  				}),
   105  			}),
   106  			map[string]string{
   107  				"foo.#": "2",
   108  				"foo.0": "hello",
   109  				"foo.1": "world",
   110  			},
   111  		},
   112  		{
   113  			cty.ObjectVal(map[string]cty.Value{
   114  				"foo": cty.MapVal(map[string]cty.Value{
   115  					"hello":       cty.NumberIntVal(12),
   116  					"hello.world": cty.NumberIntVal(10),
   117  				}),
   118  			}),
   119  			map[string]string{
   120  				"foo.%":           "2",
   121  				"foo.hello":       "12",
   122  				"foo.hello.world": "10",
   123  			},
   124  		},
   125  		{
   126  			cty.ObjectVal(map[string]cty.Value{
   127  				"foo": cty.UnknownVal(cty.Map(cty.String)),
   128  			}),
   129  			map[string]string{
   130  				"foo.%": UnknownVariableValue,
   131  			},
   132  		},
   133  		{
   134  			cty.ObjectVal(map[string]cty.Value{
   135  				"foo": cty.MapVal(map[string]cty.Value{
   136  					"hello":       cty.NumberIntVal(12),
   137  					"hello.world": cty.NumberIntVal(10),
   138  				}),
   139  			}),
   140  			map[string]string{
   141  				"foo.%":           "2",
   142  				"foo.hello":       "12",
   143  				"foo.hello.world": "10",
   144  			},
   145  		},
   146  		{
   147  			cty.ObjectVal(map[string]cty.Value{
   148  				"foo": cty.SetVal([]cty.Value{
   149  					cty.StringVal("hello"),
   150  					cty.StringVal("world"),
   151  				}),
   152  			}),
   153  			map[string]string{
   154  				"foo.#": "2",
   155  				"foo.0": "hello",
   156  				"foo.1": "world",
   157  			},
   158  		},
   159  		{
   160  			cty.ObjectVal(map[string]cty.Value{
   161  				"foo": cty.UnknownVal(cty.Set(cty.Number)),
   162  			}),
   163  			map[string]string{
   164  				"foo.#": UnknownVariableValue,
   165  			},
   166  		},
   167  		{
   168  			cty.ObjectVal(map[string]cty.Value{
   169  				"foo": cty.ListVal([]cty.Value{
   170  					cty.ObjectVal(map[string]cty.Value{
   171  						"bar": cty.StringVal("hello"),
   172  						"baz": cty.StringVal("world"),
   173  					}),
   174  					cty.ObjectVal(map[string]cty.Value{
   175  						"bar": cty.StringVal("bloo"),
   176  						"baz": cty.StringVal("blaa"),
   177  					}),
   178  				}),
   179  			}),
   180  			map[string]string{
   181  				"foo.#":     "2",
   182  				"foo.0.bar": "hello",
   183  				"foo.0.baz": "world",
   184  				"foo.1.bar": "bloo",
   185  				"foo.1.baz": "blaa",
   186  			},
   187  		},
   188  		{
   189  			cty.ObjectVal(map[string]cty.Value{
   190  				"foo": cty.ListVal([]cty.Value{
   191  					cty.ObjectVal(map[string]cty.Value{
   192  						"bar": cty.StringVal("hello"),
   193  						"baz": cty.ListVal([]cty.Value{
   194  							cty.True,
   195  							cty.True,
   196  						}),
   197  					}),
   198  					cty.ObjectVal(map[string]cty.Value{
   199  						"bar": cty.StringVal("bloo"),
   200  						"baz": cty.ListVal([]cty.Value{
   201  							cty.False,
   202  							cty.True,
   203  						}),
   204  					}),
   205  				}),
   206  			}),
   207  			map[string]string{
   208  				"foo.#":       "2",
   209  				"foo.0.bar":   "hello",
   210  				"foo.0.baz.#": "2",
   211  				"foo.0.baz.0": "true",
   212  				"foo.0.baz.1": "true",
   213  				"foo.1.bar":   "bloo",
   214  				"foo.1.baz.#": "2",
   215  				"foo.1.baz.0": "false",
   216  				"foo.1.baz.1": "true",
   217  			},
   218  		},
   219  		{
   220  			cty.ObjectVal(map[string]cty.Value{
   221  				"foo": cty.ListVal([]cty.Value{
   222  					cty.UnknownVal(cty.Object(map[string]cty.Type{
   223  						"bar": cty.String,
   224  						"baz": cty.List(cty.Bool),
   225  						"bap": cty.Map(cty.Number),
   226  					})),
   227  				}),
   228  			}),
   229  			map[string]string{
   230  				"foo.#":       "1",
   231  				"foo.0.bar":   UnknownVariableValue,
   232  				"foo.0.baz.#": UnknownVariableValue,
   233  				"foo.0.bap.%": UnknownVariableValue,
   234  			},
   235  		},
   236  		{
   237  			cty.NullVal(cty.Object(map[string]cty.Type{
   238  				"foo": cty.Set(cty.Object(map[string]cty.Type{
   239  					"bar": cty.String,
   240  				})),
   241  			})),
   242  			nil,
   243  		},
   244  	}
   245  
   246  	for _, test := range tests {
   247  		t.Run(test.Value.GoString(), func(t *testing.T) {
   248  			got := FlatmapValueFromHCL2(test.Value)
   249  
   250  			for _, problem := range deep.Equal(got, test.Want) {
   251  				t.Error(problem)
   252  			}
   253  		})
   254  	}
   255  }
   256  
   257  func TestFlatmapValueFromHCL2FromFlatmap(t *testing.T) {
   258  	tests := []struct {
   259  		Name string
   260  		Map  map[string]string
   261  		Type cty.Type
   262  	}{
   263  		{
   264  			"empty flatmap with collections",
   265  			map[string]string{},
   266  			cty.Object(map[string]cty.Type{
   267  				"foo": cty.Map(cty.String),
   268  				"bar": cty.Set(cty.String),
   269  			}),
   270  		},
   271  		{
   272  			"nil flatmap with collections",
   273  			nil,
   274  			cty.Object(map[string]cty.Type{
   275  				"foo": cty.Map(cty.String),
   276  				"bar": cty.Set(cty.String),
   277  			}),
   278  		},
   279  		{
   280  			"empty flatmap with nested collections",
   281  			map[string]string{},
   282  			cty.Object(map[string]cty.Type{
   283  				"foo": cty.Object(
   284  					map[string]cty.Type{
   285  						"baz": cty.Map(cty.String),
   286  					},
   287  				),
   288  				"bar": cty.Set(cty.String),
   289  			}),
   290  		},
   291  		{
   292  			"partial flatmap with nested collections",
   293  			map[string]string{
   294  				"foo.baz.%":   "1",
   295  				"foo.baz.key": "val",
   296  			},
   297  			cty.Object(map[string]cty.Type{
   298  				"foo": cty.Object(
   299  					map[string]cty.Type{
   300  						"baz": cty.Map(cty.String),
   301  						"biz": cty.Map(cty.String),
   302  					},
   303  				),
   304  				"bar": cty.Set(cty.String),
   305  			}),
   306  		},
   307  	}
   308  
   309  	for _, test := range tests {
   310  		t.Run(test.Name, func(t *testing.T) {
   311  			val, err := HCL2ValueFromFlatmap(test.Map, test.Type)
   312  			if err != nil {
   313  				t.Fatal(err)
   314  			}
   315  
   316  			got := FlatmapValueFromHCL2(val)
   317  
   318  			for _, problem := range deep.Equal(got, test.Map) {
   319  				t.Error(problem)
   320  			}
   321  		})
   322  	}
   323  }
   324  func TestHCL2ValueFromFlatmap(t *testing.T) {
   325  	tests := []struct {
   326  		Flatmap map[string]string
   327  		Type    cty.Type
   328  		Want    cty.Value
   329  		WantErr string
   330  	}{
   331  		{
   332  			Flatmap: map[string]string{},
   333  			Type:    cty.EmptyObject,
   334  			Want:    cty.EmptyObjectVal,
   335  		},
   336  		{
   337  			Flatmap: map[string]string{
   338  				"ignored": "foo",
   339  			},
   340  			Type: cty.EmptyObject,
   341  			Want: cty.EmptyObjectVal,
   342  		},
   343  		{
   344  			Flatmap: map[string]string{
   345  				"foo": "blah",
   346  				"bar": "true",
   347  				"baz": "12.5",
   348  				"unk": UnknownVariableValue,
   349  			},
   350  			Type: cty.Object(map[string]cty.Type{
   351  				"foo": cty.String,
   352  				"bar": cty.Bool,
   353  				"baz": cty.Number,
   354  				"unk": cty.Bool,
   355  			}),
   356  			Want: cty.ObjectVal(map[string]cty.Value{
   357  				"foo": cty.StringVal("blah"),
   358  				"bar": cty.True,
   359  				"baz": cty.NumberFloatVal(12.5),
   360  				"unk": cty.UnknownVal(cty.Bool),
   361  			}),
   362  		},
   363  		{
   364  			Flatmap: map[string]string{
   365  				"foo.#": "0",
   366  			},
   367  			Type: cty.Object(map[string]cty.Type{
   368  				"foo": cty.List(cty.String),
   369  			}),
   370  			Want: cty.ObjectVal(map[string]cty.Value{
   371  				"foo": cty.ListValEmpty(cty.String),
   372  			}),
   373  		},
   374  		{
   375  			Flatmap: map[string]string{
   376  				"foo.#": UnknownVariableValue,
   377  			},
   378  			Type: cty.Object(map[string]cty.Type{
   379  				"foo": cty.List(cty.String),
   380  			}),
   381  			Want: cty.ObjectVal(map[string]cty.Value{
   382  				"foo": cty.UnknownVal(cty.List(cty.String)),
   383  			}),
   384  		},
   385  		{
   386  			Flatmap: map[string]string{
   387  				"foo.#": "1",
   388  				"foo.0": "hello",
   389  			},
   390  			Type: cty.Object(map[string]cty.Type{
   391  				"foo": cty.List(cty.String),
   392  			}),
   393  			Want: cty.ObjectVal(map[string]cty.Value{
   394  				"foo": cty.ListVal([]cty.Value{
   395  					cty.StringVal("hello"),
   396  				}),
   397  			}),
   398  		},
   399  		{
   400  			Flatmap: map[string]string{
   401  				"foo.#": "2",
   402  				"foo.0": "true",
   403  				"foo.1": "false",
   404  				"foo.2": "ignored", // (because the count is 2, so this is out of range)
   405  			},
   406  			Type: cty.Object(map[string]cty.Type{
   407  				"foo": cty.List(cty.Bool),
   408  			}),
   409  			Want: cty.ObjectVal(map[string]cty.Value{
   410  				"foo": cty.ListVal([]cty.Value{
   411  					cty.True,
   412  					cty.False,
   413  				}),
   414  			}),
   415  		},
   416  		{
   417  			Flatmap: map[string]string{
   418  				"foo.#": "2",
   419  				"foo.0": "hello",
   420  			},
   421  			Type: cty.Object(map[string]cty.Type{
   422  				"foo": cty.Tuple([]cty.Type{
   423  					cty.String,
   424  					cty.Bool,
   425  				}),
   426  			}),
   427  			Want: cty.ObjectVal(map[string]cty.Value{
   428  				"foo": cty.TupleVal([]cty.Value{
   429  					cty.StringVal("hello"),
   430  					cty.NullVal(cty.Bool),
   431  				}),
   432  			}),
   433  		},
   434  		{
   435  			Flatmap: map[string]string{
   436  				"foo.#": UnknownVariableValue,
   437  			},
   438  			Type: cty.Object(map[string]cty.Type{
   439  				"foo": cty.Tuple([]cty.Type{
   440  					cty.String,
   441  					cty.Bool,
   442  				}),
   443  			}),
   444  			Want: cty.ObjectVal(map[string]cty.Value{
   445  				"foo": cty.UnknownVal(cty.Tuple([]cty.Type{
   446  					cty.String,
   447  					cty.Bool,
   448  				})),
   449  			}),
   450  		},
   451  		{
   452  			Flatmap: map[string]string{
   453  				"foo.#": "0",
   454  			},
   455  			Type: cty.Object(map[string]cty.Type{
   456  				"foo": cty.Set(cty.String),
   457  			}),
   458  			Want: cty.ObjectVal(map[string]cty.Value{
   459  				"foo": cty.SetValEmpty(cty.String),
   460  			}),
   461  		},
   462  		{
   463  			Flatmap: map[string]string{
   464  				"foo.#": UnknownVariableValue,
   465  			},
   466  			Type: cty.Object(map[string]cty.Type{
   467  				"foo": cty.Set(cty.String),
   468  			}),
   469  			Want: cty.ObjectVal(map[string]cty.Value{
   470  				"foo": cty.UnknownVal(cty.Set(cty.String)),
   471  			}),
   472  		},
   473  		{
   474  			Flatmap: map[string]string{
   475  				"foo.#":        "1",
   476  				"foo.24534534": "hello",
   477  			},
   478  			Type: cty.Object(map[string]cty.Type{
   479  				"foo": cty.Set(cty.String),
   480  			}),
   481  			Want: cty.ObjectVal(map[string]cty.Value{
   482  				"foo": cty.SetVal([]cty.Value{
   483  					cty.StringVal("hello"),
   484  				}),
   485  			}),
   486  		},
   487  		{
   488  			Flatmap: map[string]string{
   489  				"foo.#":        "1",
   490  				"foo.24534534": "true",
   491  				"foo.95645644": "true",
   492  				"foo.34533452": "false",
   493  			},
   494  			Type: cty.Object(map[string]cty.Type{
   495  				"foo": cty.Set(cty.Bool),
   496  			}),
   497  			Want: cty.ObjectVal(map[string]cty.Value{
   498  				"foo": cty.SetVal([]cty.Value{
   499  					cty.True,
   500  					cty.False,
   501  				}),
   502  			}),
   503  		},
   504  		{
   505  			Flatmap: map[string]string{
   506  				"foo.%": "0",
   507  			},
   508  			Type: cty.Object(map[string]cty.Type{
   509  				"foo": cty.Map(cty.String),
   510  			}),
   511  			Want: cty.ObjectVal(map[string]cty.Value{
   512  				"foo": cty.MapValEmpty(cty.String),
   513  			}),
   514  		},
   515  		{
   516  			Flatmap: map[string]string{
   517  				"foo.%":       "2",
   518  				"foo.baz":     "true",
   519  				"foo.bar.baz": "false",
   520  			},
   521  			Type: cty.Object(map[string]cty.Type{
   522  				"foo": cty.Map(cty.Bool),
   523  			}),
   524  			Want: cty.ObjectVal(map[string]cty.Value{
   525  				"foo": cty.MapVal(map[string]cty.Value{
   526  					"baz":     cty.True,
   527  					"bar.baz": cty.False,
   528  				}),
   529  			}),
   530  		},
   531  		{
   532  			Flatmap: map[string]string{
   533  				"foo.%": UnknownVariableValue,
   534  			},
   535  			Type: cty.Object(map[string]cty.Type{
   536  				"foo": cty.Map(cty.Bool),
   537  			}),
   538  			Want: cty.ObjectVal(map[string]cty.Value{
   539  				"foo": cty.UnknownVal(cty.Map(cty.Bool)),
   540  			}),
   541  		},
   542  		{
   543  			Flatmap: map[string]string{
   544  				"foo.#":     "2",
   545  				"foo.0.bar": "hello",
   546  				"foo.0.baz": "1",
   547  				"foo.1.bar": "world",
   548  				"foo.1.baz": "false",
   549  			},
   550  			Type: cty.Object(map[string]cty.Type{
   551  				"foo": cty.List(cty.Object(map[string]cty.Type{
   552  					"bar": cty.String,
   553  					"baz": cty.Bool,
   554  				})),
   555  			}),
   556  			Want: cty.ObjectVal(map[string]cty.Value{
   557  				"foo": cty.ListVal([]cty.Value{
   558  					cty.ObjectVal(map[string]cty.Value{
   559  						"bar": cty.StringVal("hello"),
   560  						"baz": cty.True,
   561  					}),
   562  					cty.ObjectVal(map[string]cty.Value{
   563  						"bar": cty.StringVal("world"),
   564  						"baz": cty.False,
   565  					}),
   566  				}),
   567  			}),
   568  		},
   569  		{
   570  			Flatmap: map[string]string{
   571  				"foo.#":            "2",
   572  				"foo.34534534.bar": "hello",
   573  				"foo.34534534.baz": "1",
   574  				"foo.93453345.bar": "world",
   575  				"foo.93453345.baz": "false",
   576  			},
   577  			Type: cty.Object(map[string]cty.Type{
   578  				"foo": cty.Set(cty.Object(map[string]cty.Type{
   579  					"bar": cty.String,
   580  					"baz": cty.Bool,
   581  				})),
   582  			}),
   583  			Want: cty.ObjectVal(map[string]cty.Value{
   584  				"foo": cty.SetVal([]cty.Value{
   585  					cty.ObjectVal(map[string]cty.Value{
   586  						"bar": cty.StringVal("hello"),
   587  						"baz": cty.True,
   588  					}),
   589  					cty.ObjectVal(map[string]cty.Value{
   590  						"bar": cty.StringVal("world"),
   591  						"baz": cty.False,
   592  					}),
   593  				}),
   594  			}),
   595  		},
   596  		{
   597  			Flatmap: map[string]string{
   598  				"foo.#": "not-valid",
   599  			},
   600  			Type: cty.Object(map[string]cty.Type{
   601  				"foo": cty.List(cty.String),
   602  			}),
   603  			WantErr: `invalid count value for "foo." in state: strconv.Atoi: parsing "not-valid": invalid syntax`,
   604  		},
   605  		{
   606  			Flatmap: nil,
   607  			Type: cty.Object(map[string]cty.Type{
   608  				"foo": cty.Set(cty.Object(map[string]cty.Type{
   609  					"bar": cty.String,
   610  				})),
   611  			}),
   612  			Want: cty.NullVal(cty.Object(map[string]cty.Type{
   613  				"foo": cty.Set(cty.Object(map[string]cty.Type{
   614  					"bar": cty.String,
   615  				})),
   616  			})),
   617  		},
   618  		{
   619  			Flatmap: map[string]string{
   620  				"foo.#":   "2",
   621  				"foo.0.%": "2",
   622  				"foo.0.a": "a",
   623  				"foo.0.b": "b",
   624  				"foo.1.%": "1",
   625  				"foo.1.a": "a",
   626  			},
   627  			Type: cty.Object(map[string]cty.Type{
   628  				"foo": cty.List(cty.Map(cty.String)),
   629  			}),
   630  
   631  			Want: cty.ObjectVal(map[string]cty.Value{
   632  				"foo": cty.ListVal([]cty.Value{
   633  					cty.MapVal(map[string]cty.Value{
   634  						"a": cty.StringVal("a"),
   635  						"b": cty.StringVal("b"),
   636  					}),
   637  					cty.MapVal(map[string]cty.Value{
   638  						"a": cty.StringVal("a"),
   639  					}),
   640  				}),
   641  			}),
   642  		},
   643  		{
   644  			Flatmap: map[string]string{
   645  				"single.#":                 "1",
   646  				"single.~1.value":          "a",
   647  				"single.~1.optional":       UnknownVariableValue,
   648  				"two.#":                    "2",
   649  				"two.~2381914684.value":    "a",
   650  				"two.~2381914684.optional": UnknownVariableValue,
   651  				"two.~2798940671.value":    "b",
   652  				"two.~2798940671.optional": UnknownVariableValue,
   653  			},
   654  			Type: cty.Object(map[string]cty.Type{
   655  				"single": cty.Set(
   656  					cty.Object(map[string]cty.Type{
   657  						"value":    cty.String,
   658  						"optional": cty.String,
   659  					}),
   660  				),
   661  				"two": cty.Set(
   662  					cty.Object(map[string]cty.Type{
   663  						"optional": cty.String,
   664  						"value":    cty.String,
   665  					}),
   666  				),
   667  			}),
   668  			Want: cty.ObjectVal(map[string]cty.Value{
   669  				"single": cty.SetVal([]cty.Value{
   670  					cty.ObjectVal(map[string]cty.Value{
   671  						"value":    cty.StringVal("a"),
   672  						"optional": cty.UnknownVal(cty.String),
   673  					}),
   674  				}),
   675  				"two": cty.SetVal([]cty.Value{
   676  					cty.ObjectVal(map[string]cty.Value{
   677  						"value":    cty.StringVal("a"),
   678  						"optional": cty.UnknownVal(cty.String),
   679  					}),
   680  					cty.ObjectVal(map[string]cty.Value{
   681  						"value":    cty.StringVal("b"),
   682  						"optional": cty.UnknownVal(cty.String),
   683  					}),
   684  				}),
   685  			}),
   686  		},
   687  		{
   688  			Flatmap: map[string]string{
   689  				"foo.#": "1",
   690  			},
   691  			Type: cty.Object(map[string]cty.Type{
   692  				"foo": cty.Set(cty.Object(map[string]cty.Type{
   693  					"bar": cty.String,
   694  				})),
   695  			}),
   696  			Want: cty.ObjectVal(map[string]cty.Value{
   697  				"foo": cty.SetVal([]cty.Value{
   698  					cty.ObjectVal(map[string]cty.Value{
   699  						"bar": cty.NullVal(cty.String),
   700  					}),
   701  				}),
   702  			}),
   703  		},
   704  		{
   705  			Flatmap: map[string]string{
   706  				"multi.#":                "1",
   707  				"multi.2.set.#":          "1",
   708  				"multi.2.set.3.required": "val",
   709  			},
   710  			Type: cty.Object(map[string]cty.Type{
   711  				"multi": cty.Set(cty.Object(map[string]cty.Type{
   712  					"set": cty.Set(cty.Object(map[string]cty.Type{
   713  						"required": cty.String,
   714  					})),
   715  				})),
   716  			}),
   717  			Want: cty.ObjectVal(map[string]cty.Value{
   718  				"multi": cty.SetVal([]cty.Value{
   719  					cty.ObjectVal(map[string]cty.Value{
   720  						"set": cty.SetVal([]cty.Value{
   721  							cty.ObjectVal(map[string]cty.Value{
   722  								"required": cty.StringVal("val"),
   723  							}),
   724  						}),
   725  					}),
   726  				}),
   727  			}),
   728  		},
   729  	}
   730  
   731  	for i, test := range tests {
   732  		t.Run(fmt.Sprintf("%d %#v as %#v", i, test.Flatmap, test.Type), func(t *testing.T) {
   733  			got, err := HCL2ValueFromFlatmap(test.Flatmap, test.Type)
   734  
   735  			if test.WantErr != "" {
   736  				if err == nil {
   737  					t.Fatalf("succeeded; want error: %s", test.WantErr)
   738  				}
   739  				if got, want := err.Error(), test.WantErr; got != want {
   740  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   741  				}
   742  				if got == cty.NilVal {
   743  					t.Fatalf("result is cty.NilVal; want valid placeholder value")
   744  				}
   745  				return
   746  			} else {
   747  				if err != nil {
   748  					t.Fatalf("unexpected error: %s", err.Error())
   749  				}
   750  			}
   751  
   752  			if !got.RawEquals(test.Want) {
   753  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   754  			}
   755  		})
   756  	}
   757  }