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

     1  package funcs
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/zclconf/go-cty/cty"
     8  )
     9  
    10  func TestDefaults(t *testing.T) {
    11  	tests := []struct {
    12  		Input, Defaults cty.Value
    13  		Want            cty.Value
    14  		WantErr         string
    15  	}{
    16  		{ // When *either* input or default are unknown, an unknown is returned.
    17  			Input: cty.ObjectVal(map[string]cty.Value{
    18  				"a": cty.UnknownVal(cty.String),
    19  			}),
    20  			Defaults: cty.ObjectVal(map[string]cty.Value{
    21  				"a": cty.StringVal("hello"),
    22  			}),
    23  			Want: cty.ObjectVal(map[string]cty.Value{
    24  				"a": cty.UnknownVal(cty.String),
    25  			}),
    26  		},
    27  		{
    28  			//  When *either* input or default are unknown, an unknown is
    29  			//  returned with marks from both input and defaults.
    30  			Input: cty.ObjectVal(map[string]cty.Value{
    31  				"a": cty.UnknownVal(cty.String),
    32  			}),
    33  			Defaults: cty.ObjectVal(map[string]cty.Value{
    34  				"a": cty.StringVal("hello").Mark("marked"),
    35  			}),
    36  			Want: cty.ObjectVal(map[string]cty.Value{
    37  				"a": cty.UnknownVal(cty.String).Mark("marked"),
    38  			}),
    39  		},
    40  		{
    41  			Input: cty.ObjectVal(map[string]cty.Value{
    42  				"a": cty.NullVal(cty.String),
    43  			}),
    44  			Defaults: cty.ObjectVal(map[string]cty.Value{
    45  				"a": cty.StringVal("hello"),
    46  			}),
    47  			Want: cty.ObjectVal(map[string]cty.Value{
    48  				"a": cty.StringVal("hello"),
    49  			}),
    50  		},
    51  		{
    52  			Input: cty.ObjectVal(map[string]cty.Value{
    53  				"a": cty.StringVal("hey"),
    54  			}),
    55  			Defaults: cty.ObjectVal(map[string]cty.Value{
    56  				"a": cty.StringVal("hello"),
    57  			}),
    58  			Want: cty.ObjectVal(map[string]cty.Value{
    59  				"a": cty.StringVal("hey"),
    60  			}),
    61  		},
    62  		{
    63  			Input: cty.ObjectVal(map[string]cty.Value{
    64  				"a": cty.NullVal(cty.String),
    65  			}),
    66  			Defaults: cty.ObjectVal(map[string]cty.Value{
    67  				"a": cty.NullVal(cty.String),
    68  			}),
    69  			Want: cty.ObjectVal(map[string]cty.Value{
    70  				"a": cty.NullVal(cty.String),
    71  			}),
    72  		},
    73  		{
    74  			Input: cty.ObjectVal(map[string]cty.Value{
    75  				"a": cty.NullVal(cty.String),
    76  			}),
    77  			Defaults: cty.ObjectVal(map[string]cty.Value{}),
    78  			Want: cty.ObjectVal(map[string]cty.Value{
    79  				"a": cty.NullVal(cty.String),
    80  			}),
    81  		},
    82  		{
    83  			Input: cty.ObjectVal(map[string]cty.Value{}),
    84  			Defaults: cty.ObjectVal(map[string]cty.Value{
    85  				"a": cty.NullVal(cty.String),
    86  			}),
    87  			WantErr: `.a: target type does not expect an attribute named "a"`,
    88  		},
    89  
    90  		{
    91  			Input: cty.ObjectVal(map[string]cty.Value{
    92  				"a": cty.ListVal([]cty.Value{
    93  					cty.NullVal(cty.String),
    94  				}),
    95  			}),
    96  			Defaults: cty.ObjectVal(map[string]cty.Value{
    97  				"a": cty.StringVal("hello"),
    98  			}),
    99  			Want: cty.ObjectVal(map[string]cty.Value{
   100  				"a": cty.ListVal([]cty.Value{
   101  					cty.StringVal("hello"),
   102  				}),
   103  			}),
   104  		},
   105  		{
   106  			Input: cty.ObjectVal(map[string]cty.Value{
   107  				"a": cty.ListVal([]cty.Value{
   108  					cty.NullVal(cty.String),
   109  					cty.StringVal("hey"),
   110  					cty.NullVal(cty.String),
   111  				}),
   112  			}),
   113  			Defaults: cty.ObjectVal(map[string]cty.Value{
   114  				"a": cty.StringVal("hello"),
   115  			}),
   116  			Want: cty.ObjectVal(map[string]cty.Value{
   117  				"a": cty.ListVal([]cty.Value{
   118  					cty.StringVal("hello"),
   119  					cty.StringVal("hey"),
   120  					cty.StringVal("hello"),
   121  				}),
   122  			}),
   123  		},
   124  		{
   125  			// Using defaults with single set elements is a pretty
   126  			// odd thing to do, but this behavior is just here because
   127  			// it generalizes from how we handle collections. It's
   128  			// tested only to ensure it doesn't change accidentally
   129  			// in future.
   130  			Input: cty.ObjectVal(map[string]cty.Value{
   131  				"a": cty.SetVal([]cty.Value{
   132  					cty.NullVal(cty.String),
   133  					cty.StringVal("hey"),
   134  				}),
   135  			}),
   136  			Defaults: cty.ObjectVal(map[string]cty.Value{
   137  				"a": cty.StringVal("hello"),
   138  			}),
   139  			Want: cty.ObjectVal(map[string]cty.Value{
   140  				"a": cty.SetVal([]cty.Value{
   141  					cty.StringVal("hey"),
   142  					cty.StringVal("hello"),
   143  				}),
   144  			}),
   145  		},
   146  		{
   147  			Input: cty.ObjectVal(map[string]cty.Value{
   148  				"a": cty.MapVal(map[string]cty.Value{
   149  					"x": cty.NullVal(cty.String),
   150  					"y": cty.StringVal("hey"),
   151  					"z": cty.NullVal(cty.String),
   152  				}),
   153  			}),
   154  			Defaults: cty.ObjectVal(map[string]cty.Value{
   155  				"a": cty.StringVal("hello"),
   156  			}),
   157  			Want: cty.ObjectVal(map[string]cty.Value{
   158  				"a": cty.MapVal(map[string]cty.Value{
   159  					"x": cty.StringVal("hello"),
   160  					"y": cty.StringVal("hey"),
   161  					"z": cty.StringVal("hello"),
   162  				}),
   163  			}),
   164  		},
   165  		{
   166  			Input: cty.ObjectVal(map[string]cty.Value{
   167  				"a": cty.ListVal([]cty.Value{
   168  					cty.ObjectVal(map[string]cty.Value{
   169  						"b": cty.StringVal("hey"),
   170  					}),
   171  					cty.ObjectVal(map[string]cty.Value{
   172  						"b": cty.NullVal(cty.String),
   173  					}),
   174  					cty.ObjectVal(map[string]cty.Value{
   175  						"b": cty.StringVal("hey"),
   176  					}),
   177  				}),
   178  			}),
   179  			Defaults: cty.ObjectVal(map[string]cty.Value{
   180  				"a": cty.ObjectVal(map[string]cty.Value{
   181  					"b": cty.StringVal("hello"),
   182  				}),
   183  			}),
   184  			Want: cty.ObjectVal(map[string]cty.Value{
   185  				"a": cty.ListVal([]cty.Value{
   186  					cty.ObjectVal(map[string]cty.Value{
   187  						"b": cty.StringVal("hey"),
   188  					}),
   189  					cty.ObjectVal(map[string]cty.Value{
   190  						"b": cty.StringVal("hello"),
   191  					}),
   192  					cty.ObjectVal(map[string]cty.Value{
   193  						"b": cty.StringVal("hey"),
   194  					}),
   195  				}),
   196  			}),
   197  		},
   198  		{
   199  			Input: cty.ListVal([]cty.Value{
   200  				cty.ObjectVal(map[string]cty.Value{
   201  					"b": cty.StringVal("hey"),
   202  				}),
   203  				cty.ObjectVal(map[string]cty.Value{
   204  					"b": cty.NullVal(cty.String),
   205  				}),
   206  				cty.ObjectVal(map[string]cty.Value{
   207  					"b": cty.StringVal("hey"),
   208  				}),
   209  			}),
   210  			Defaults: cty.ObjectVal(map[string]cty.Value{
   211  				"b": cty.StringVal("hello"),
   212  			}),
   213  			Want: cty.ListVal([]cty.Value{
   214  				cty.ObjectVal(map[string]cty.Value{
   215  					"b": cty.StringVal("hey"),
   216  				}),
   217  				cty.ObjectVal(map[string]cty.Value{
   218  					"b": cty.StringVal("hello"),
   219  				}),
   220  				cty.ObjectVal(map[string]cty.Value{
   221  					"b": cty.StringVal("hey"),
   222  				}),
   223  			}),
   224  		},
   225  		{
   226  			Input: cty.ObjectVal(map[string]cty.Value{
   227  				"a": cty.SetVal([]cty.Value{
   228  					cty.ObjectVal(map[string]cty.Value{
   229  						"b": cty.StringVal("boop"),
   230  					}),
   231  					cty.ObjectVal(map[string]cty.Value{
   232  						"b": cty.NullVal(cty.String),
   233  					}),
   234  					cty.ObjectVal(map[string]cty.Value{
   235  						"b": cty.StringVal("hey"),
   236  					}),
   237  				}),
   238  			}),
   239  			Defaults: cty.ObjectVal(map[string]cty.Value{
   240  				"a": cty.ObjectVal(map[string]cty.Value{
   241  					"b": cty.StringVal("hello"),
   242  				}),
   243  			}),
   244  			Want: cty.ObjectVal(map[string]cty.Value{
   245  				"a": cty.SetVal([]cty.Value{
   246  					cty.ObjectVal(map[string]cty.Value{
   247  						"b": cty.StringVal("boop"),
   248  					}),
   249  					cty.ObjectVal(map[string]cty.Value{
   250  						"b": cty.StringVal("hello"),
   251  					}),
   252  					cty.ObjectVal(map[string]cty.Value{
   253  						"b": cty.StringVal("hey"),
   254  					}),
   255  				}),
   256  			}),
   257  		},
   258  		{
   259  			Input: cty.ObjectVal(map[string]cty.Value{
   260  				"a": cty.SetVal([]cty.Value{
   261  					cty.ObjectVal(map[string]cty.Value{
   262  						"b": cty.StringVal("hello"),
   263  					}),
   264  					cty.ObjectVal(map[string]cty.Value{
   265  						"b": cty.NullVal(cty.String),
   266  					}),
   267  				}),
   268  			}),
   269  			Defaults: cty.ObjectVal(map[string]cty.Value{
   270  				"a": cty.ObjectVal(map[string]cty.Value{
   271  					"b": cty.StringVal("hello"),
   272  				}),
   273  			}),
   274  			Want: cty.ObjectVal(map[string]cty.Value{
   275  				"a": cty.SetVal([]cty.Value{
   276  					// After applying defaults, the one with a null value
   277  					// coalesced with the one with a non-null value,
   278  					// and so there's only one left.
   279  					cty.ObjectVal(map[string]cty.Value{
   280  						"b": cty.StringVal("hello"),
   281  					}),
   282  				}),
   283  			}),
   284  		},
   285  		{
   286  			Input: cty.ObjectVal(map[string]cty.Value{
   287  				"a": cty.MapVal(map[string]cty.Value{
   288  					"boop": cty.ObjectVal(map[string]cty.Value{
   289  						"b": cty.StringVal("hey"),
   290  					}),
   291  					"beep": cty.ObjectVal(map[string]cty.Value{
   292  						"b": cty.NullVal(cty.String),
   293  					}),
   294  				}),
   295  			}),
   296  			Defaults: cty.ObjectVal(map[string]cty.Value{
   297  				"a": cty.ObjectVal(map[string]cty.Value{
   298  					"b": cty.StringVal("hello"),
   299  				}),
   300  			}),
   301  			Want: cty.ObjectVal(map[string]cty.Value{
   302  				"a": cty.MapVal(map[string]cty.Value{
   303  					"boop": cty.ObjectVal(map[string]cty.Value{
   304  						"b": cty.StringVal("hey"),
   305  					}),
   306  					"beep": cty.ObjectVal(map[string]cty.Value{
   307  						"b": cty.StringVal("hello"),
   308  					}),
   309  				}),
   310  			}),
   311  		},
   312  		{
   313  			Input: cty.ObjectVal(map[string]cty.Value{
   314  				"a": cty.ListVal([]cty.Value{
   315  					cty.ObjectVal(map[string]cty.Value{
   316  						"b": cty.StringVal("hey"),
   317  					}),
   318  					cty.ObjectVal(map[string]cty.Value{
   319  						"b": cty.NullVal(cty.String),
   320  					}),
   321  					cty.ObjectVal(map[string]cty.Value{
   322  						"b": cty.StringVal("hey"),
   323  					}),
   324  				}),
   325  			}),
   326  			Defaults: cty.ObjectVal(map[string]cty.Value{
   327  				"a": cty.StringVal("hello"),
   328  			}),
   329  			WantErr: `.a: the default value for a collection of an object type must itself be an object type, not string`,
   330  		},
   331  		{
   332  			Input: cty.ObjectVal(map[string]cty.Value{
   333  				"a": cty.ListVal([]cty.Value{
   334  					cty.NullVal(cty.String),
   335  					cty.StringVal("hey"),
   336  					cty.NullVal(cty.String),
   337  				}),
   338  			}),
   339  			Defaults: cty.ObjectVal(map[string]cty.Value{
   340  				// The default value for a list must be a single value
   341  				// of the list's element type which provides defaults
   342  				// for each element separately, so the default for a
   343  				// list of string should be just a single string, not
   344  				// a list of string.
   345  				"a": cty.ListVal([]cty.Value{
   346  					cty.StringVal("hello"),
   347  				}),
   348  			}),
   349  			WantErr: `.a: invalid default value for string: string required`,
   350  		},
   351  		{
   352  			Input: cty.ObjectVal(map[string]cty.Value{
   353  				"a": cty.TupleVal([]cty.Value{
   354  					cty.NullVal(cty.String),
   355  					cty.StringVal("hey"),
   356  					cty.NullVal(cty.String),
   357  				}),
   358  			}),
   359  			Defaults: cty.ObjectVal(map[string]cty.Value{
   360  				"a": cty.StringVal("hello"),
   361  			}),
   362  			WantErr: `.a: the default value for a tuple type must itself be a tuple type, not string`,
   363  		},
   364  		{
   365  			Input: cty.ObjectVal(map[string]cty.Value{
   366  				"a": cty.TupleVal([]cty.Value{
   367  					cty.NullVal(cty.String),
   368  					cty.StringVal("hey"),
   369  					cty.NullVal(cty.String),
   370  				}),
   371  			}),
   372  			Defaults: cty.ObjectVal(map[string]cty.Value{
   373  				"a": cty.TupleVal([]cty.Value{
   374  					cty.StringVal("hello 0"),
   375  					cty.StringVal("hello 1"),
   376  					cty.StringVal("hello 2"),
   377  				}),
   378  			}),
   379  			Want: cty.ObjectVal(map[string]cty.Value{
   380  				"a": cty.TupleVal([]cty.Value{
   381  					cty.StringVal("hello 0"),
   382  					cty.StringVal("hey"),
   383  					cty.StringVal("hello 2"),
   384  				}),
   385  			}),
   386  		},
   387  		{
   388  			// There's no reason to use this function for plain primitive
   389  			// types, because the "default" argument in a variable definition
   390  			// already has the equivalent behavior. This function is only
   391  			// to deal with the situation of a complex-typed variable where
   392  			// only parts of the data structure are optional.
   393  			Input:    cty.NullVal(cty.String),
   394  			Defaults: cty.StringVal("hello"),
   395  			WantErr:  `only object types and collections of object types can have defaults applied`,
   396  		},
   397  		// When applying default values to structural types, null objects or
   398  		// tuples in the input should be passed through.
   399  		{
   400  			Input: cty.ObjectVal(map[string]cty.Value{
   401  				"a": cty.NullVal(cty.Object(map[string]cty.Type{
   402  					"x": cty.String,
   403  					"y": cty.String,
   404  				})),
   405  				"b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
   406  			}),
   407  			Defaults: cty.ObjectVal(map[string]cty.Value{
   408  				"a": cty.ObjectVal(map[string]cty.Value{
   409  					"x": cty.StringVal("hello"),
   410  					"y": cty.StringVal("there"),
   411  				}),
   412  				"b": cty.TupleVal([]cty.Value{
   413  					cty.StringVal("how are"),
   414  					cty.StringVal("you?"),
   415  				}),
   416  			}),
   417  			Want: cty.ObjectVal(map[string]cty.Value{
   418  				"a": cty.NullVal(cty.Object(map[string]cty.Type{
   419  					"x": cty.String,
   420  					"y": cty.String,
   421  				})),
   422  				"b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})),
   423  			}),
   424  		},
   425  		// When applying default values to structural types, we permit null
   426  		// values in the defaults, and just pass through the input value.
   427  		{
   428  			Input: cty.ObjectVal(map[string]cty.Value{
   429  				"a": cty.ListVal([]cty.Value{
   430  					cty.ObjectVal(map[string]cty.Value{
   431  						"p": cty.StringVal("xyz"),
   432  						"q": cty.StringVal("xyz"),
   433  					}),
   434  				}),
   435  				"b": cty.SetVal([]cty.Value{
   436  					cty.TupleVal([]cty.Value{
   437  						cty.NumberIntVal(0),
   438  						cty.NumberIntVal(2),
   439  					}),
   440  					cty.TupleVal([]cty.Value{
   441  						cty.NumberIntVal(1),
   442  						cty.NumberIntVal(3),
   443  					}),
   444  				}),
   445  				"c": cty.NullVal(cty.String),
   446  			}),
   447  			Defaults: cty.ObjectVal(map[string]cty.Value{
   448  				"c": cty.StringVal("tada"),
   449  			}),
   450  			Want: cty.ObjectVal(map[string]cty.Value{
   451  				"a": cty.ListVal([]cty.Value{
   452  					cty.ObjectVal(map[string]cty.Value{
   453  						"p": cty.StringVal("xyz"),
   454  						"q": cty.StringVal("xyz"),
   455  					}),
   456  				}),
   457  				"b": cty.SetVal([]cty.Value{
   458  					cty.TupleVal([]cty.Value{
   459  						cty.NumberIntVal(0),
   460  						cty.NumberIntVal(2),
   461  					}),
   462  					cty.TupleVal([]cty.Value{
   463  						cty.NumberIntVal(1),
   464  						cty.NumberIntVal(3),
   465  					}),
   466  				}),
   467  				"c": cty.StringVal("tada"),
   468  			}),
   469  		},
   470  		// When applying default values to collection types, null collections in the
   471  		// input should result in empty collections in the output.
   472  		{
   473  			Input: cty.ObjectVal(map[string]cty.Value{
   474  				"a": cty.NullVal(cty.List(cty.String)),
   475  				"b": cty.NullVal(cty.Map(cty.String)),
   476  				"c": cty.NullVal(cty.Set(cty.String)),
   477  			}),
   478  			Defaults: cty.ObjectVal(map[string]cty.Value{
   479  				"a": cty.StringVal("hello"),
   480  				"b": cty.StringVal("hi"),
   481  				"c": cty.StringVal("greetings"),
   482  			}),
   483  			Want: cty.ObjectVal(map[string]cty.Value{
   484  				"a": cty.ListValEmpty(cty.String),
   485  				"b": cty.MapValEmpty(cty.String),
   486  				"c": cty.SetValEmpty(cty.String),
   487  			}),
   488  		},
   489  		// When specifying fallbacks, we allow mismatched primitive attribute
   490  		// types so long as a safe conversion is possible. This means that we
   491  		// can accept number or boolean values for string attributes.
   492  		{
   493  			Input: cty.ObjectVal(map[string]cty.Value{
   494  				"a": cty.NullVal(cty.String),
   495  				"b": cty.NullVal(cty.String),
   496  				"c": cty.NullVal(cty.String),
   497  			}),
   498  			Defaults: cty.ObjectVal(map[string]cty.Value{
   499  				"a": cty.NumberIntVal(5),
   500  				"b": cty.True,
   501  				"c": cty.StringVal("greetings"),
   502  			}),
   503  			Want: cty.ObjectVal(map[string]cty.Value{
   504  				"a": cty.StringVal("5"),
   505  				"b": cty.StringVal("true"),
   506  				"c": cty.StringVal("greetings"),
   507  			}),
   508  		},
   509  		// Fallbacks with mismatched primitive attribute types which do not
   510  		// have safe conversions must not pass the suitable fallback check,
   511  		// even if unsafe conversion would be possible.
   512  		{
   513  			Input: cty.ObjectVal(map[string]cty.Value{
   514  				"a": cty.NullVal(cty.Bool),
   515  			}),
   516  			Defaults: cty.ObjectVal(map[string]cty.Value{
   517  				"a": cty.StringVal("5"),
   518  			}),
   519  			WantErr: ".a: invalid default value for bool: bool required",
   520  		},
   521  		// marks: we should preserve marks from both input value and defaults as leafily as possible
   522  		{
   523  			Input: cty.ObjectVal(map[string]cty.Value{
   524  				"a": cty.NullVal(cty.String),
   525  			}),
   526  			Defaults: cty.ObjectVal(map[string]cty.Value{
   527  				"a": cty.StringVal("hello").Mark("world"),
   528  			}),
   529  			Want: cty.ObjectVal(map[string]cty.Value{
   530  				"a": cty.StringVal("hello").Mark("world"),
   531  			}),
   532  		},
   533  		{ // "unused" marks don't carry over
   534  			Input: cty.ObjectVal(map[string]cty.Value{
   535  				"a": cty.NullVal(cty.String).Mark("a"),
   536  			}),
   537  			Defaults: cty.ObjectVal(map[string]cty.Value{
   538  				"a": cty.StringVal("hello"),
   539  			}),
   540  			Want: cty.ObjectVal(map[string]cty.Value{
   541  				"a": cty.StringVal("hello"),
   542  			}),
   543  		},
   544  		{ // Marks on tuples remain attached to individual elements
   545  			Input: cty.ObjectVal(map[string]cty.Value{
   546  				"a": cty.TupleVal([]cty.Value{
   547  					cty.NullVal(cty.String),
   548  					cty.StringVal("hey").Mark("input"),
   549  					cty.NullVal(cty.String),
   550  				}),
   551  			}),
   552  			Defaults: cty.ObjectVal(map[string]cty.Value{
   553  				"a": cty.TupleVal([]cty.Value{
   554  					cty.StringVal("hello 0").Mark("fallback"),
   555  					cty.StringVal("hello 1"),
   556  					cty.StringVal("hello 2"),
   557  				}),
   558  			}),
   559  			Want: cty.ObjectVal(map[string]cty.Value{
   560  				"a": cty.TupleVal([]cty.Value{
   561  					cty.StringVal("hello 0").Mark("fallback"),
   562  					cty.StringVal("hey").Mark("input"),
   563  					cty.StringVal("hello 2"),
   564  				}),
   565  			}),
   566  		},
   567  		{ // Marks from list elements
   568  			Input: cty.ObjectVal(map[string]cty.Value{
   569  				"a": cty.ListVal([]cty.Value{
   570  					cty.NullVal(cty.String),
   571  					cty.StringVal("hey").Mark("input"),
   572  					cty.NullVal(cty.String),
   573  				}),
   574  			}),
   575  			Defaults: cty.ObjectVal(map[string]cty.Value{
   576  				"a": cty.StringVal("hello 0").Mark("fallback"),
   577  			}),
   578  			Want: cty.ObjectVal(map[string]cty.Value{
   579  				"a": cty.ListVal([]cty.Value{
   580  					cty.StringVal("hello 0").Mark("fallback"),
   581  					cty.StringVal("hey").Mark("input"),
   582  					cty.StringVal("hello 0").Mark("fallback"),
   583  				}),
   584  			}),
   585  		},
   586  		{
   587  			// Sets don't allow individually-marked elements, so the marks
   588  			// end up aggregating on the set itself anyway in this case.
   589  			Input: cty.ObjectVal(map[string]cty.Value{
   590  				"a": cty.SetVal([]cty.Value{
   591  					cty.NullVal(cty.String),
   592  					cty.NullVal(cty.String),
   593  					cty.StringVal("hey").Mark("input"),
   594  				}),
   595  			}),
   596  			Defaults: cty.ObjectVal(map[string]cty.Value{
   597  				"a": cty.StringVal("hello 0").Mark("fallback"),
   598  			}),
   599  			Want: cty.ObjectVal(map[string]cty.Value{
   600  				"a": cty.SetVal([]cty.Value{
   601  					cty.StringVal("hello 0"),
   602  					cty.StringVal("hey"),
   603  					cty.StringVal("hello 0"),
   604  				}).WithMarks(cty.NewValueMarks("fallback", "input")),
   605  			}),
   606  		},
   607  		{
   608  			Input: cty.ObjectVal(map[string]cty.Value{
   609  				"a": cty.ListVal([]cty.Value{
   610  					cty.NullVal(cty.String),
   611  				}),
   612  			}),
   613  			Defaults: cty.ObjectVal(map[string]cty.Value{
   614  				"a": cty.StringVal("hello").Mark("beep"),
   615  			}).Mark("boop"),
   616  			// This is the least-intuitive case. The mark "boop" is attached to
   617  			// the default object, not it's elements, but both marks end up
   618  			// aggregated on the list element.
   619  			Want: cty.ObjectVal(map[string]cty.Value{
   620  				"a": cty.ListVal([]cty.Value{
   621  					cty.StringVal("hello").WithMarks(cty.NewValueMarks("beep", "boop")),
   622  				}),
   623  			}),
   624  		},
   625  	}
   626  
   627  	for _, test := range tests {
   628  		t.Run(fmt.Sprintf("defaults(%#v, %#v)", test.Input, test.Defaults), func(t *testing.T) {
   629  			got, gotErr := Defaults(test.Input, test.Defaults)
   630  
   631  			if test.WantErr != "" {
   632  				if gotErr == nil {
   633  					t.Fatalf("unexpected success\nwant error: %s", test.WantErr)
   634  				}
   635  				if got, want := gotErr.Error(), test.WantErr; got != want {
   636  					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
   637  				}
   638  				return
   639  			} else if gotErr != nil {
   640  				t.Fatalf("unexpected error\ngot:  %s", gotErr.Error())
   641  			}
   642  
   643  			if !test.Want.RawEquals(got) {
   644  				t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
   645  			}
   646  		})
   647  	}
   648  }