github.com/kevinklinger/open_terraform@v1.3.6/noninternal/plans/objchange/objchange_test.go (about)

     1  package objchange
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/apparentlymart/go-dump/dump"
     7  	"github.com/zclconf/go-cty/cty"
     8  
     9  	"github.com/kevinklinger/open_terraform/noninternal/configs/configschema"
    10  )
    11  
    12  func TestProposedNew(t *testing.T) {
    13  	tests := map[string]struct {
    14  		Schema *configschema.Block
    15  		Prior  cty.Value
    16  		Config cty.Value
    17  		Want   cty.Value
    18  	}{
    19  		"empty": {
    20  			&configschema.Block{},
    21  			cty.EmptyObjectVal,
    22  			cty.EmptyObjectVal,
    23  			cty.EmptyObjectVal,
    24  		},
    25  		"no prior": {
    26  			&configschema.Block{
    27  				Attributes: map[string]*configschema.Attribute{
    28  					"foo": {
    29  						Type:     cty.String,
    30  						Optional: true,
    31  					},
    32  					"bar": {
    33  						Type:     cty.String,
    34  						Computed: true,
    35  					},
    36  					"bloop": {
    37  						NestedType: &configschema.Object{
    38  							Nesting: configschema.NestingSingle,
    39  							Attributes: map[string]*configschema.Attribute{
    40  								"blop": {
    41  									Type:     cty.String,
    42  									Required: true,
    43  								},
    44  							},
    45  						},
    46  						Computed: true,
    47  					},
    48  				},
    49  				BlockTypes: map[string]*configschema.NestedBlock{
    50  					"baz": {
    51  						Nesting: configschema.NestingSingle,
    52  						Block: configschema.Block{
    53  							Attributes: map[string]*configschema.Attribute{
    54  								"boz": {
    55  									Type:     cty.String,
    56  									Optional: true,
    57  									Computed: true,
    58  								},
    59  								"biz": {
    60  									Type:     cty.String,
    61  									Optional: true,
    62  									Computed: true,
    63  								},
    64  							},
    65  						},
    66  					},
    67  				},
    68  			},
    69  			cty.NullVal(cty.DynamicPseudoType),
    70  			cty.ObjectVal(map[string]cty.Value{
    71  				"foo": cty.StringVal("hello"),
    72  				"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
    73  					"blop": cty.String,
    74  				})),
    75  				"bar": cty.NullVal(cty.String),
    76  				"baz": cty.ObjectVal(map[string]cty.Value{
    77  					"boz": cty.StringVal("world"),
    78  
    79  					// An unknown in the config represents a situation where
    80  					// an argument is explicitly set to an expression result
    81  					// that is derived from an unknown value. This is distinct
    82  					// from leaving it null, which allows the provider itself
    83  					// to decide the value during PlanResourceChange.
    84  					"biz": cty.UnknownVal(cty.String),
    85  				}),
    86  			}),
    87  			cty.ObjectVal(map[string]cty.Value{
    88  				"foo": cty.StringVal("hello"),
    89  
    90  				// unset computed attributes are null in the proposal; provider
    91  				// usually changes them to "unknown" during PlanResourceChange,
    92  				// to indicate that the value will be decided during apply.
    93  				"bar": cty.NullVal(cty.String),
    94  				"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
    95  					"blop": cty.String,
    96  				})),
    97  
    98  				"baz": cty.ObjectVal(map[string]cty.Value{
    99  					"boz": cty.StringVal("world"),
   100  					"biz": cty.UnknownVal(cty.String), // explicit unknown preserved from config
   101  				}),
   102  			}),
   103  		},
   104  		"null block remains null": {
   105  			&configschema.Block{
   106  				Attributes: map[string]*configschema.Attribute{
   107  					"foo": {
   108  						Type:     cty.String,
   109  						Optional: true,
   110  					},
   111  					"bloop": {
   112  						NestedType: &configschema.Object{
   113  							Nesting: configschema.NestingSingle,
   114  							Attributes: map[string]*configschema.Attribute{
   115  								"blop": {
   116  									Type:     cty.String,
   117  									Required: true,
   118  								},
   119  							},
   120  						},
   121  						Computed: true,
   122  					},
   123  				},
   124  				BlockTypes: map[string]*configschema.NestedBlock{
   125  					"baz": {
   126  						Nesting: configschema.NestingSingle,
   127  						Block: configschema.Block{
   128  							Attributes: map[string]*configschema.Attribute{
   129  								"boz": {
   130  									Type:     cty.String,
   131  									Optional: true,
   132  									Computed: true,
   133  								},
   134  							},
   135  						},
   136  					},
   137  				},
   138  			},
   139  			cty.NullVal(cty.DynamicPseudoType),
   140  			cty.ObjectVal(map[string]cty.Value{
   141  				"foo": cty.StringVal("bar"),
   142  				"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
   143  					"blop": cty.String,
   144  				})),
   145  				"baz": cty.NullVal(cty.Object(map[string]cty.Type{
   146  					"boz": cty.String,
   147  				})),
   148  			}),
   149  			// The bloop attribue and baz block does not exist in the config,
   150  			// and therefore shouldn't be planned.
   151  			cty.ObjectVal(map[string]cty.Value{
   152  				"foo": cty.StringVal("bar"),
   153  				"bloop": cty.NullVal(cty.Object(map[string]cty.Type{
   154  					"blop": cty.String,
   155  				})),
   156  				"baz": cty.NullVal(cty.Object(map[string]cty.Type{
   157  					"boz": cty.String,
   158  				})),
   159  			}),
   160  		},
   161  		"no prior with set": {
   162  			// This one is here because our handling of sets is more complex
   163  			// than others (due to the fuzzy correlation heuristic) and
   164  			// historically that caused us some panic-related grief.
   165  			&configschema.Block{
   166  				BlockTypes: map[string]*configschema.NestedBlock{
   167  					"baz": {
   168  						Nesting: configschema.NestingSet,
   169  						Block: configschema.Block{
   170  							Attributes: map[string]*configschema.Attribute{
   171  								"boz": {
   172  									Type:     cty.String,
   173  									Optional: true,
   174  									Computed: true,
   175  								},
   176  							},
   177  						},
   178  					},
   179  				},
   180  				Attributes: map[string]*configschema.Attribute{
   181  					"bloop": {
   182  						NestedType: &configschema.Object{
   183  							Nesting: configschema.NestingSet,
   184  							Attributes: map[string]*configschema.Attribute{
   185  								"blop": {
   186  									Type:     cty.String,
   187  									Required: true,
   188  								},
   189  							},
   190  						},
   191  						Computed: true,
   192  						Optional: true,
   193  					},
   194  				},
   195  			},
   196  			cty.NullVal(cty.DynamicPseudoType),
   197  			cty.ObjectVal(map[string]cty.Value{
   198  				"baz": cty.SetVal([]cty.Value{
   199  					cty.ObjectVal(map[string]cty.Value{
   200  						"boz": cty.StringVal("world"),
   201  					}),
   202  				}),
   203  				"bloop": cty.SetVal([]cty.Value{
   204  					cty.ObjectVal(map[string]cty.Value{
   205  						"blop": cty.StringVal("blub"),
   206  					}),
   207  				}),
   208  			}),
   209  			cty.ObjectVal(map[string]cty.Value{
   210  				"baz": cty.SetVal([]cty.Value{
   211  					cty.ObjectVal(map[string]cty.Value{
   212  						"boz": cty.StringVal("world"),
   213  					}),
   214  				}),
   215  				"bloop": cty.SetVal([]cty.Value{
   216  					cty.ObjectVal(map[string]cty.Value{
   217  						"blop": cty.StringVal("blub"),
   218  					}),
   219  				}),
   220  			}),
   221  		},
   222  		"prior attributes": {
   223  			&configschema.Block{
   224  				Attributes: map[string]*configschema.Attribute{
   225  					"foo": {
   226  						Type:     cty.String,
   227  						Optional: true,
   228  					},
   229  					"bar": {
   230  						Type:     cty.String,
   231  						Computed: true,
   232  					},
   233  					"baz": {
   234  						Type:     cty.String,
   235  						Optional: true,
   236  						Computed: true,
   237  					},
   238  					"boz": {
   239  						Type:     cty.String,
   240  						Optional: true,
   241  						Computed: true,
   242  					},
   243  					"bloop": {
   244  						NestedType: &configschema.Object{
   245  							Nesting: configschema.NestingSingle,
   246  							Attributes: map[string]*configschema.Attribute{
   247  								"blop": {
   248  									Type:     cty.String,
   249  									Required: true,
   250  								},
   251  							},
   252  						},
   253  						Optional: true,
   254  					},
   255  				},
   256  			},
   257  			cty.ObjectVal(map[string]cty.Value{
   258  				"foo": cty.StringVal("bonjour"),
   259  				"bar": cty.StringVal("petit dejeuner"),
   260  				"baz": cty.StringVal("grande dejeuner"),
   261  				"boz": cty.StringVal("a la monde"),
   262  				"bloop": cty.ObjectVal(map[string]cty.Value{
   263  					"blop": cty.StringVal("glub"),
   264  				}),
   265  			}),
   266  			cty.ObjectVal(map[string]cty.Value{
   267  				"foo": cty.StringVal("hello"),
   268  				"bar": cty.NullVal(cty.String),
   269  				"baz": cty.NullVal(cty.String),
   270  				"boz": cty.StringVal("world"),
   271  				"bloop": cty.ObjectVal(map[string]cty.Value{
   272  					"blop": cty.StringVal("bleep"),
   273  				}),
   274  			}),
   275  			cty.ObjectVal(map[string]cty.Value{
   276  				"foo": cty.StringVal("hello"),
   277  				"bar": cty.StringVal("petit dejeuner"),
   278  				"baz": cty.StringVal("grande dejeuner"),
   279  				"boz": cty.StringVal("world"),
   280  				"bloop": cty.ObjectVal(map[string]cty.Value{
   281  					"blop": cty.StringVal("bleep"),
   282  				}),
   283  			}),
   284  		},
   285  		"prior nested single": {
   286  			&configschema.Block{
   287  				BlockTypes: map[string]*configschema.NestedBlock{
   288  					"foo": {
   289  						Nesting: configschema.NestingSingle,
   290  						Block: configschema.Block{
   291  							Attributes: map[string]*configschema.Attribute{
   292  								"bar": {
   293  									Type:     cty.String,
   294  									Optional: true,
   295  									Computed: true,
   296  								},
   297  								"baz": {
   298  									Type:     cty.String,
   299  									Optional: true,
   300  									Computed: true,
   301  								},
   302  							},
   303  						},
   304  					},
   305  				},
   306  				Attributes: map[string]*configschema.Attribute{
   307  					"bloop": {
   308  						NestedType: &configschema.Object{
   309  							Nesting: configschema.NestingSingle,
   310  							Attributes: map[string]*configschema.Attribute{
   311  								"blop": {
   312  									Type:     cty.String,
   313  									Required: true,
   314  								},
   315  								"bleep": {
   316  									Type:     cty.String,
   317  									Optional: true,
   318  								},
   319  							},
   320  						},
   321  						Optional: true,
   322  					},
   323  				},
   324  			},
   325  			cty.ObjectVal(map[string]cty.Value{
   326  				"foo": cty.ObjectVal(map[string]cty.Value{
   327  					"bar": cty.StringVal("beep"),
   328  					"baz": cty.StringVal("boop"),
   329  				}),
   330  				"bloop": cty.ObjectVal(map[string]cty.Value{
   331  					"blop":  cty.StringVal("glub"),
   332  					"bleep": cty.NullVal(cty.String),
   333  				}),
   334  			}),
   335  			cty.ObjectVal(map[string]cty.Value{
   336  				"foo": cty.ObjectVal(map[string]cty.Value{
   337  					"bar": cty.StringVal("bap"),
   338  					"baz": cty.NullVal(cty.String),
   339  				}),
   340  				"bloop": cty.ObjectVal(map[string]cty.Value{
   341  					"blop":  cty.StringVal("glub"),
   342  					"bleep": cty.StringVal("beep"),
   343  				}),
   344  			}),
   345  			cty.ObjectVal(map[string]cty.Value{
   346  				"foo": cty.ObjectVal(map[string]cty.Value{
   347  					"bar": cty.StringVal("bap"),
   348  					"baz": cty.StringVal("boop"),
   349  				}),
   350  				"bloop": cty.ObjectVal(map[string]cty.Value{
   351  					"blop":  cty.StringVal("glub"),
   352  					"bleep": cty.StringVal("beep"),
   353  				}),
   354  			}),
   355  		},
   356  		"prior nested list": {
   357  			&configschema.Block{
   358  				BlockTypes: map[string]*configschema.NestedBlock{
   359  					"foo": {
   360  						Nesting: configschema.NestingList,
   361  						Block: configschema.Block{
   362  							Attributes: map[string]*configschema.Attribute{
   363  								"bar": {
   364  									Type:     cty.String,
   365  									Optional: true,
   366  									Computed: true,
   367  								},
   368  								"baz": {
   369  									Type:     cty.String,
   370  									Optional: true,
   371  									Computed: true,
   372  								},
   373  							},
   374  						},
   375  					},
   376  				},
   377  				Attributes: map[string]*configschema.Attribute{
   378  					"bloop": {
   379  						NestedType: &configschema.Object{
   380  							Nesting: configschema.NestingList,
   381  							Attributes: map[string]*configschema.Attribute{
   382  								"blop": {
   383  									Type:     cty.String,
   384  									Required: true,
   385  								},
   386  							},
   387  						},
   388  						Optional: true,
   389  					},
   390  				},
   391  			},
   392  			cty.ObjectVal(map[string]cty.Value{
   393  				"foo": cty.ListVal([]cty.Value{
   394  					cty.ObjectVal(map[string]cty.Value{
   395  						"bar": cty.StringVal("beep"),
   396  						"baz": cty.StringVal("boop"),
   397  					}),
   398  				}),
   399  				"bloop": cty.ListVal([]cty.Value{
   400  					cty.ObjectVal(map[string]cty.Value{
   401  						"blop": cty.StringVal("bar"),
   402  					}),
   403  					cty.ObjectVal(map[string]cty.Value{
   404  						"blop": cty.StringVal("baz"),
   405  					}),
   406  				}),
   407  			}),
   408  			cty.ObjectVal(map[string]cty.Value{
   409  				"foo": cty.ListVal([]cty.Value{
   410  					cty.ObjectVal(map[string]cty.Value{
   411  						"bar": cty.StringVal("bap"),
   412  						"baz": cty.NullVal(cty.String),
   413  					}),
   414  					cty.ObjectVal(map[string]cty.Value{
   415  						"bar": cty.StringVal("blep"),
   416  						"baz": cty.NullVal(cty.String),
   417  					}),
   418  				}),
   419  				"bloop": cty.ListVal([]cty.Value{
   420  					cty.ObjectVal(map[string]cty.Value{
   421  						"blop": cty.StringVal("bar"),
   422  					}),
   423  					cty.ObjectVal(map[string]cty.Value{
   424  						"blop": cty.StringVal("baz"),
   425  					}),
   426  				}),
   427  			}),
   428  			cty.ObjectVal(map[string]cty.Value{
   429  				"foo": cty.ListVal([]cty.Value{
   430  					cty.ObjectVal(map[string]cty.Value{
   431  						"bar": cty.StringVal("bap"),
   432  						"baz": cty.StringVal("boop"),
   433  					}),
   434  					cty.ObjectVal(map[string]cty.Value{
   435  						"bar": cty.StringVal("blep"),
   436  						"baz": cty.NullVal(cty.String),
   437  					}),
   438  				}),
   439  				"bloop": cty.ListVal([]cty.Value{
   440  					cty.ObjectVal(map[string]cty.Value{
   441  						"blop": cty.StringVal("bar"),
   442  					}),
   443  					cty.ObjectVal(map[string]cty.Value{
   444  						"blop": cty.StringVal("baz"),
   445  					}),
   446  				}),
   447  			}),
   448  		},
   449  		"prior nested list with dynamic": {
   450  			&configschema.Block{
   451  				BlockTypes: map[string]*configschema.NestedBlock{
   452  					"foo": {
   453  						Nesting: configschema.NestingList,
   454  						Block: configschema.Block{
   455  							Attributes: map[string]*configschema.Attribute{
   456  								"bar": {
   457  									Type:     cty.String,
   458  									Optional: true,
   459  									Computed: true,
   460  								},
   461  								"baz": {
   462  									Type:     cty.DynamicPseudoType,
   463  									Optional: true,
   464  									Computed: true,
   465  								},
   466  							},
   467  						},
   468  					},
   469  				},
   470  				Attributes: map[string]*configschema.Attribute{
   471  					"bloop": {
   472  						NestedType: &configschema.Object{
   473  							Nesting: configschema.NestingList,
   474  							Attributes: map[string]*configschema.Attribute{
   475  								"blop": {
   476  									Type:     cty.DynamicPseudoType,
   477  									Required: true,
   478  								},
   479  								"blub": {
   480  									Type:     cty.DynamicPseudoType,
   481  									Optional: true,
   482  								},
   483  							},
   484  						},
   485  						Optional: true,
   486  					},
   487  				},
   488  			},
   489  			cty.ObjectVal(map[string]cty.Value{
   490  				"foo": cty.TupleVal([]cty.Value{
   491  					cty.ObjectVal(map[string]cty.Value{
   492  						"bar": cty.StringVal("beep"),
   493  						"baz": cty.StringVal("boop"),
   494  					}),
   495  				}),
   496  				"bloop": cty.ListVal([]cty.Value{
   497  					cty.ObjectVal(map[string]cty.Value{
   498  						"blop": cty.StringVal("bar"),
   499  						"blub": cty.StringVal("glub"),
   500  					}),
   501  					cty.ObjectVal(map[string]cty.Value{
   502  						"blop": cty.StringVal("baz"),
   503  						"blub": cty.NullVal(cty.String),
   504  					}),
   505  				}),
   506  			}),
   507  			cty.ObjectVal(map[string]cty.Value{
   508  				"foo": cty.TupleVal([]cty.Value{
   509  					cty.ObjectVal(map[string]cty.Value{
   510  						"bar": cty.StringVal("bap"),
   511  						"baz": cty.NullVal(cty.String),
   512  					}),
   513  					cty.ObjectVal(map[string]cty.Value{
   514  						"bar": cty.StringVal("blep"),
   515  						"baz": cty.NullVal(cty.String),
   516  					}),
   517  				}),
   518  				"bloop": cty.ListVal([]cty.Value{
   519  					cty.ObjectVal(map[string]cty.Value{
   520  						"blop": cty.StringVal("bar"),
   521  						"blub": cty.NullVal(cty.String),
   522  					}),
   523  				}),
   524  			}),
   525  			cty.ObjectVal(map[string]cty.Value{
   526  				"foo": cty.TupleVal([]cty.Value{
   527  					cty.ObjectVal(map[string]cty.Value{
   528  						"bar": cty.StringVal("bap"),
   529  						"baz": cty.StringVal("boop"),
   530  					}),
   531  					cty.ObjectVal(map[string]cty.Value{
   532  						"bar": cty.StringVal("blep"),
   533  						"baz": cty.NullVal(cty.String),
   534  					}),
   535  				}),
   536  				"bloop": cty.ListVal([]cty.Value{
   537  					cty.ObjectVal(map[string]cty.Value{
   538  						"blop": cty.StringVal("bar"),
   539  						"blub": cty.NullVal(cty.String),
   540  					}),
   541  				}),
   542  			}),
   543  		},
   544  		"prior nested map": {
   545  			&configschema.Block{
   546  				BlockTypes: map[string]*configschema.NestedBlock{
   547  					"foo": {
   548  						Nesting: configschema.NestingMap,
   549  						Block: configschema.Block{
   550  							Attributes: map[string]*configschema.Attribute{
   551  								"bar": {
   552  									Type:     cty.String,
   553  									Optional: true,
   554  									Computed: true,
   555  								},
   556  								"baz": {
   557  									Type:     cty.String,
   558  									Optional: true,
   559  									Computed: true,
   560  								},
   561  							},
   562  						},
   563  					},
   564  				},
   565  				Attributes: map[string]*configschema.Attribute{
   566  					"bloop": {
   567  						NestedType: &configschema.Object{
   568  							Nesting: configschema.NestingMap,
   569  							Attributes: map[string]*configschema.Attribute{
   570  								"blop": {
   571  									Type:     cty.String,
   572  									Required: true,
   573  								},
   574  							},
   575  						},
   576  						Optional: true,
   577  					},
   578  				},
   579  			},
   580  			cty.ObjectVal(map[string]cty.Value{
   581  				"foo": cty.MapVal(map[string]cty.Value{
   582  					"a": cty.ObjectVal(map[string]cty.Value{
   583  						"bar": cty.StringVal("beep"),
   584  						"baz": cty.StringVal("boop"),
   585  					}),
   586  					"b": cty.ObjectVal(map[string]cty.Value{
   587  						"bar": cty.StringVal("blep"),
   588  						"baz": cty.StringVal("boot"),
   589  					}),
   590  				}),
   591  				"bloop": cty.MapVal(map[string]cty.Value{
   592  					"a": cty.ObjectVal(map[string]cty.Value{
   593  						"blop": cty.StringVal("glub"),
   594  					}),
   595  					"b": cty.ObjectVal(map[string]cty.Value{
   596  						"blop": cty.StringVal("blub"),
   597  					}),
   598  				}),
   599  			}),
   600  			cty.ObjectVal(map[string]cty.Value{
   601  				"foo": cty.MapVal(map[string]cty.Value{
   602  					"a": cty.ObjectVal(map[string]cty.Value{
   603  						"bar": cty.StringVal("bap"),
   604  						"baz": cty.NullVal(cty.String),
   605  					}),
   606  					"c": cty.ObjectVal(map[string]cty.Value{
   607  						"bar": cty.StringVal("bosh"),
   608  						"baz": cty.NullVal(cty.String),
   609  					}),
   610  				}),
   611  				"bloop": cty.MapVal(map[string]cty.Value{
   612  					"a": cty.ObjectVal(map[string]cty.Value{
   613  						"blop": cty.StringVal("glub"),
   614  					}),
   615  					"c": cty.ObjectVal(map[string]cty.Value{
   616  						"blop": cty.StringVal("blub"),
   617  					}),
   618  				}),
   619  			}),
   620  			cty.ObjectVal(map[string]cty.Value{
   621  				"foo": cty.MapVal(map[string]cty.Value{
   622  					"a": cty.ObjectVal(map[string]cty.Value{
   623  						"bar": cty.StringVal("bap"),
   624  						"baz": cty.StringVal("boop"),
   625  					}),
   626  					"c": cty.ObjectVal(map[string]cty.Value{
   627  						"bar": cty.StringVal("bosh"),
   628  						"baz": cty.NullVal(cty.String),
   629  					}),
   630  				}),
   631  				"bloop": cty.MapVal(map[string]cty.Value{
   632  					"a": cty.ObjectVal(map[string]cty.Value{
   633  						"blop": cty.StringVal("glub"),
   634  					}),
   635  					"c": cty.ObjectVal(map[string]cty.Value{
   636  						"blop": cty.StringVal("blub"),
   637  					}),
   638  				}),
   639  			}),
   640  		},
   641  		"prior nested map with dynamic": {
   642  			&configschema.Block{
   643  				BlockTypes: map[string]*configschema.NestedBlock{
   644  					"foo": {
   645  						Nesting: configschema.NestingMap,
   646  						Block: configschema.Block{
   647  							Attributes: map[string]*configschema.Attribute{
   648  								"bar": {
   649  									Type:     cty.String,
   650  									Optional: true,
   651  									Computed: true,
   652  								},
   653  								"baz": {
   654  									Type:     cty.DynamicPseudoType,
   655  									Optional: true,
   656  									Computed: true,
   657  								},
   658  							},
   659  						},
   660  					},
   661  				},
   662  				Attributes: map[string]*configschema.Attribute{
   663  					"bloop": {
   664  						NestedType: &configschema.Object{
   665  							Nesting: configschema.NestingMap,
   666  							Attributes: map[string]*configschema.Attribute{
   667  								"blop": {
   668  									Type:     cty.DynamicPseudoType,
   669  									Required: true,
   670  								},
   671  							},
   672  						},
   673  						Optional: true,
   674  					},
   675  				},
   676  			},
   677  			cty.ObjectVal(map[string]cty.Value{
   678  				"foo": cty.ObjectVal(map[string]cty.Value{
   679  					"a": cty.ObjectVal(map[string]cty.Value{
   680  						"bar": cty.StringVal("beep"),
   681  						"baz": cty.StringVal("boop"),
   682  					}),
   683  					"b": cty.ObjectVal(map[string]cty.Value{
   684  						"bar": cty.StringVal("blep"),
   685  						"baz": cty.ListVal([]cty.Value{cty.StringVal("boot")}),
   686  					}),
   687  				}),
   688  				"bloop": cty.ObjectVal(map[string]cty.Value{
   689  					"a": cty.ObjectVal(map[string]cty.Value{
   690  						"blop": cty.StringVal("glub"),
   691  					}),
   692  					"b": cty.ObjectVal(map[string]cty.Value{
   693  						"blop": cty.NumberIntVal(13),
   694  					}),
   695  				}),
   696  			}),
   697  			cty.ObjectVal(map[string]cty.Value{
   698  				"foo": cty.ObjectVal(map[string]cty.Value{
   699  					"a": cty.ObjectVal(map[string]cty.Value{
   700  						"bar": cty.StringVal("bap"),
   701  						"baz": cty.NullVal(cty.String),
   702  					}),
   703  					"c": cty.ObjectVal(map[string]cty.Value{
   704  						"bar": cty.StringVal("bosh"),
   705  						"baz": cty.NullVal(cty.List(cty.String)),
   706  					}),
   707  				}),
   708  				"bloop": cty.ObjectVal(map[string]cty.Value{
   709  					"a": cty.ObjectVal(map[string]cty.Value{
   710  						"blop": cty.StringVal("blep"),
   711  					}),
   712  					"c": cty.ObjectVal(map[string]cty.Value{
   713  						"blop": cty.NumberIntVal(13),
   714  					}),
   715  				}),
   716  			}),
   717  			cty.ObjectVal(map[string]cty.Value{
   718  				"foo": cty.ObjectVal(map[string]cty.Value{
   719  					"a": cty.ObjectVal(map[string]cty.Value{
   720  						"bar": cty.StringVal("bap"),
   721  						"baz": cty.StringVal("boop"),
   722  					}),
   723  					"c": cty.ObjectVal(map[string]cty.Value{
   724  						"bar": cty.StringVal("bosh"),
   725  						"baz": cty.NullVal(cty.List(cty.String)),
   726  					}),
   727  				}),
   728  				"bloop": cty.ObjectVal(map[string]cty.Value{
   729  					"a": cty.ObjectVal(map[string]cty.Value{
   730  						"blop": cty.StringVal("blep"),
   731  					}),
   732  					"c": cty.ObjectVal(map[string]cty.Value{
   733  						"blop": cty.NumberIntVal(13),
   734  					}),
   735  				}),
   736  			}),
   737  		},
   738  		"prior nested set": {
   739  			&configschema.Block{
   740  				BlockTypes: map[string]*configschema.NestedBlock{
   741  					"foo": {
   742  						Nesting: configschema.NestingSet,
   743  						Block: configschema.Block{
   744  							Attributes: map[string]*configschema.Attribute{
   745  								"bar": {
   746  									// This non-computed attribute will serve
   747  									// as our matching key for propagating
   748  									// "baz" from elements in the prior value.
   749  									Type:     cty.String,
   750  									Optional: true,
   751  								},
   752  								"baz": {
   753  									Type:     cty.String,
   754  									Optional: true,
   755  									Computed: true,
   756  								},
   757  							},
   758  						},
   759  					},
   760  				},
   761  				Attributes: map[string]*configschema.Attribute{
   762  					"bloop": {
   763  						NestedType: &configschema.Object{
   764  							Nesting: configschema.NestingSet,
   765  							Attributes: map[string]*configschema.Attribute{
   766  								"blop": {
   767  									Type:     cty.String,
   768  									Required: true,
   769  								},
   770  								"bleep": {
   771  									Type:     cty.String,
   772  									Optional: true,
   773  								},
   774  							},
   775  						},
   776  						Optional: true,
   777  					},
   778  				},
   779  			},
   780  			cty.ObjectVal(map[string]cty.Value{
   781  				"foo": cty.SetVal([]cty.Value{
   782  					cty.ObjectVal(map[string]cty.Value{
   783  						"bar": cty.StringVal("beep"),
   784  						"baz": cty.StringVal("boop"),
   785  					}),
   786  					cty.ObjectVal(map[string]cty.Value{
   787  						"bar": cty.StringVal("blep"),
   788  						"baz": cty.StringVal("boot"),
   789  					}),
   790  				}),
   791  				"bloop": cty.SetVal([]cty.Value{
   792  					cty.ObjectVal(map[string]cty.Value{
   793  						"blop":  cty.StringVal("glubglub"),
   794  						"bleep": cty.NullVal(cty.String),
   795  					}),
   796  					cty.ObjectVal(map[string]cty.Value{
   797  						"blop":  cty.StringVal("glubglub"),
   798  						"bleep": cty.StringVal("beep"),
   799  					}),
   800  				}),
   801  			}),
   802  			cty.ObjectVal(map[string]cty.Value{
   803  				"foo": cty.SetVal([]cty.Value{
   804  					cty.ObjectVal(map[string]cty.Value{
   805  						"bar": cty.StringVal("beep"),
   806  						"baz": cty.NullVal(cty.String),
   807  					}),
   808  					cty.ObjectVal(map[string]cty.Value{
   809  						"bar": cty.StringVal("bosh"),
   810  						"baz": cty.NullVal(cty.String),
   811  					}),
   812  				}),
   813  				"bloop": cty.SetVal([]cty.Value{
   814  					cty.ObjectVal(map[string]cty.Value{
   815  						"blop":  cty.StringVal("glubglub"),
   816  						"bleep": cty.NullVal(cty.String),
   817  					}),
   818  					cty.ObjectVal(map[string]cty.Value{
   819  						"blop":  cty.StringVal("glub"),
   820  						"bleep": cty.NullVal(cty.String),
   821  					}),
   822  				}),
   823  			}),
   824  			cty.ObjectVal(map[string]cty.Value{
   825  				"foo": cty.SetVal([]cty.Value{
   826  					cty.ObjectVal(map[string]cty.Value{
   827  						"bar": cty.StringVal("beep"),
   828  						"baz": cty.StringVal("boop"),
   829  					}),
   830  					cty.ObjectVal(map[string]cty.Value{
   831  						"bar": cty.StringVal("bosh"),
   832  						"baz": cty.NullVal(cty.String),
   833  					}),
   834  				}),
   835  				"bloop": cty.SetVal([]cty.Value{
   836  					cty.ObjectVal(map[string]cty.Value{
   837  						"blop":  cty.StringVal("glubglub"),
   838  						"bleep": cty.NullVal(cty.String),
   839  					}),
   840  					cty.ObjectVal(map[string]cty.Value{
   841  						"blop":  cty.StringVal("glub"),
   842  						"bleep": cty.NullVal(cty.String),
   843  					}),
   844  				}),
   845  			}),
   846  		},
   847  		"sets differing only by unknown": {
   848  			&configschema.Block{
   849  				BlockTypes: map[string]*configschema.NestedBlock{
   850  					"multi": {
   851  						Nesting: configschema.NestingSet,
   852  						Block: configschema.Block{
   853  							Attributes: map[string]*configschema.Attribute{
   854  								"optional": {
   855  									Type:     cty.String,
   856  									Optional: true,
   857  									Computed: true,
   858  								},
   859  							},
   860  						},
   861  					},
   862  				},
   863  				Attributes: map[string]*configschema.Attribute{
   864  					"bloop": {
   865  						NestedType: &configschema.Object{
   866  							Nesting: configschema.NestingSet,
   867  							Attributes: map[string]*configschema.Attribute{
   868  								"blop": {
   869  									Type:     cty.String,
   870  									Required: true,
   871  								},
   872  							},
   873  						},
   874  						Optional: true,
   875  					},
   876  				},
   877  			},
   878  			cty.NullVal(cty.DynamicPseudoType),
   879  			cty.ObjectVal(map[string]cty.Value{
   880  				"multi": cty.SetVal([]cty.Value{
   881  					cty.ObjectVal(map[string]cty.Value{
   882  						"optional": cty.UnknownVal(cty.String),
   883  					}),
   884  					cty.ObjectVal(map[string]cty.Value{
   885  						"optional": cty.UnknownVal(cty.String),
   886  					}),
   887  				}),
   888  				"bloop": cty.SetVal([]cty.Value{
   889  					cty.ObjectVal(map[string]cty.Value{
   890  						"blop": cty.UnknownVal(cty.String),
   891  					}),
   892  					cty.ObjectVal(map[string]cty.Value{
   893  						"blop": cty.UnknownVal(cty.String),
   894  					}),
   895  				}),
   896  			}),
   897  			cty.ObjectVal(map[string]cty.Value{
   898  				"multi": cty.SetVal([]cty.Value{
   899  					// These remain distinct because unknown values never
   900  					// compare equal. They may be consolidated together once
   901  					// the values become known, though.
   902  					cty.ObjectVal(map[string]cty.Value{
   903  						"optional": cty.UnknownVal(cty.String),
   904  					}),
   905  					cty.ObjectVal(map[string]cty.Value{
   906  						"optional": cty.UnknownVal(cty.String),
   907  					}),
   908  				}),
   909  				"bloop": cty.SetVal([]cty.Value{
   910  					cty.ObjectVal(map[string]cty.Value{
   911  						"blop": cty.UnknownVal(cty.String),
   912  					}),
   913  					cty.ObjectVal(map[string]cty.Value{
   914  						"blop": cty.UnknownVal(cty.String),
   915  					}),
   916  				}),
   917  			}),
   918  		},
   919  		"nested list in set": {
   920  			&configschema.Block{
   921  				BlockTypes: map[string]*configschema.NestedBlock{
   922  					"foo": {
   923  						Nesting: configschema.NestingSet,
   924  						Block: configschema.Block{
   925  							BlockTypes: map[string]*configschema.NestedBlock{
   926  								"bar": {
   927  									Nesting: configschema.NestingList,
   928  									Block: configschema.Block{
   929  										Attributes: map[string]*configschema.Attribute{
   930  											"baz": {
   931  												Type: cty.String,
   932  											},
   933  											"qux": {
   934  												Type:     cty.String,
   935  												Computed: true,
   936  												Optional: true,
   937  											},
   938  										},
   939  									},
   940  								},
   941  							},
   942  						},
   943  					},
   944  				},
   945  			},
   946  			cty.ObjectVal(map[string]cty.Value{
   947  				"foo": cty.SetVal([]cty.Value{
   948  					cty.ObjectVal(map[string]cty.Value{
   949  						"bar": cty.ListVal([]cty.Value{
   950  							cty.ObjectVal(map[string]cty.Value{
   951  								"baz": cty.StringVal("beep"),
   952  								"qux": cty.StringVal("boop"),
   953  							}),
   954  						}),
   955  					}),
   956  				}),
   957  			}),
   958  			cty.ObjectVal(map[string]cty.Value{
   959  				"foo": cty.SetVal([]cty.Value{
   960  					cty.ObjectVal(map[string]cty.Value{
   961  						"bar": cty.ListVal([]cty.Value{
   962  							cty.ObjectVal(map[string]cty.Value{
   963  								"baz": cty.StringVal("beep"),
   964  								"qux": cty.NullVal(cty.String),
   965  							}),
   966  						}),
   967  					}),
   968  				}),
   969  			}),
   970  			cty.ObjectVal(map[string]cty.Value{
   971  				"foo": cty.SetVal([]cty.Value{
   972  					cty.ObjectVal(map[string]cty.Value{
   973  						"bar": cty.ListVal([]cty.Value{
   974  							cty.ObjectVal(map[string]cty.Value{
   975  								"baz": cty.StringVal("beep"),
   976  								"qux": cty.StringVal("boop"),
   977  							}),
   978  						}),
   979  					}),
   980  				}),
   981  			}),
   982  		},
   983  		"empty nested list in set": {
   984  			&configschema.Block{
   985  				BlockTypes: map[string]*configschema.NestedBlock{
   986  					"foo": {
   987  						Nesting: configschema.NestingSet,
   988  						Block: configschema.Block{
   989  							BlockTypes: map[string]*configschema.NestedBlock{
   990  								"bar": {
   991  									Nesting: configschema.NestingList,
   992  									Block:   configschema.Block{},
   993  								},
   994  							},
   995  						},
   996  					},
   997  				},
   998  			},
   999  			cty.ObjectVal(map[string]cty.Value{
  1000  				"foo": cty.SetVal([]cty.Value{
  1001  					cty.ObjectVal(map[string]cty.Value{
  1002  						"bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()),
  1003  					}),
  1004  				}),
  1005  			}),
  1006  			cty.ObjectVal(map[string]cty.Value{
  1007  				"foo": cty.SetVal([]cty.Value{
  1008  					cty.ObjectVal(map[string]cty.Value{
  1009  						"bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()),
  1010  					}),
  1011  				}),
  1012  			}),
  1013  			cty.ObjectVal(map[string]cty.Value{
  1014  				"foo": cty.SetVal([]cty.Value{
  1015  					cty.ObjectVal(map[string]cty.Value{
  1016  						"bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()),
  1017  					}),
  1018  				}),
  1019  			}),
  1020  		},
  1021  		"nested list with dynamic in set": {
  1022  			&configschema.Block{
  1023  				BlockTypes: map[string]*configschema.NestedBlock{
  1024  					"foo": {
  1025  						Nesting: configschema.NestingSet,
  1026  						Block: configschema.Block{
  1027  							BlockTypes: map[string]*configschema.NestedBlock{
  1028  								"bar": {
  1029  									Nesting: configschema.NestingList,
  1030  									Block: configschema.Block{
  1031  										Attributes: map[string]*configschema.Attribute{
  1032  											"baz": {
  1033  												Type: cty.DynamicPseudoType,
  1034  											},
  1035  										},
  1036  									},
  1037  								},
  1038  							},
  1039  						},
  1040  					},
  1041  				},
  1042  			},
  1043  			cty.ObjectVal(map[string]cty.Value{
  1044  				"foo": cty.SetVal([]cty.Value{
  1045  					cty.ObjectVal(map[string]cty.Value{
  1046  						"bar": cty.TupleVal([]cty.Value{
  1047  							cty.ObjectVal(map[string]cty.Value{
  1048  								"baz": cty.StringVal("true"),
  1049  							}),
  1050  							cty.ObjectVal(map[string]cty.Value{
  1051  								"baz": cty.ListVal([]cty.Value{cty.StringVal("true")}),
  1052  							}),
  1053  						}),
  1054  					}),
  1055  				}),
  1056  			}),
  1057  			cty.ObjectVal(map[string]cty.Value{
  1058  				"foo": cty.SetVal([]cty.Value{
  1059  					cty.ObjectVal(map[string]cty.Value{
  1060  						"bar": cty.TupleVal([]cty.Value{
  1061  							cty.ObjectVal(map[string]cty.Value{
  1062  								"baz": cty.StringVal("true"),
  1063  							}),
  1064  							cty.ObjectVal(map[string]cty.Value{
  1065  								"baz": cty.ListVal([]cty.Value{cty.StringVal("true")}),
  1066  							}),
  1067  						}),
  1068  					}),
  1069  				}),
  1070  			}),
  1071  			cty.ObjectVal(map[string]cty.Value{
  1072  				"foo": cty.SetVal([]cty.Value{
  1073  					cty.ObjectVal(map[string]cty.Value{
  1074  						"bar": cty.TupleVal([]cty.Value{
  1075  							cty.ObjectVal(map[string]cty.Value{
  1076  								"baz": cty.StringVal("true"),
  1077  							}),
  1078  							cty.ObjectVal(map[string]cty.Value{
  1079  								"baz": cty.ListVal([]cty.Value{cty.StringVal("true")}),
  1080  							}),
  1081  						}),
  1082  					}),
  1083  				}),
  1084  			}),
  1085  		},
  1086  		"nested map with dynamic in set": {
  1087  			&configschema.Block{
  1088  				BlockTypes: map[string]*configschema.NestedBlock{
  1089  					"foo": {
  1090  						Nesting: configschema.NestingSet,
  1091  						Block: configschema.Block{
  1092  							BlockTypes: map[string]*configschema.NestedBlock{
  1093  								"bar": {
  1094  									Nesting: configschema.NestingMap,
  1095  									Block: configschema.Block{
  1096  										Attributes: map[string]*configschema.Attribute{
  1097  											"baz": {
  1098  												Type:     cty.DynamicPseudoType,
  1099  												Optional: true,
  1100  											},
  1101  										},
  1102  									},
  1103  								},
  1104  							},
  1105  						},
  1106  					},
  1107  				},
  1108  			},
  1109  			cty.ObjectVal(map[string]cty.Value{
  1110  				"foo": cty.SetVal([]cty.Value{
  1111  					cty.ObjectVal(map[string]cty.Value{
  1112  						"bar": cty.ObjectVal(map[string]cty.Value{
  1113  							"bing": cty.ObjectVal(map[string]cty.Value{
  1114  								"baz": cty.StringVal("true"),
  1115  							}),
  1116  							"bang": cty.ObjectVal(map[string]cty.Value{
  1117  								"baz": cty.ListVal([]cty.Value{cty.StringVal("true")}),
  1118  							}),
  1119  						}),
  1120  					}),
  1121  				}),
  1122  			}),
  1123  			cty.ObjectVal(map[string]cty.Value{
  1124  				"foo": cty.SetVal([]cty.Value{
  1125  					cty.ObjectVal(map[string]cty.Value{
  1126  						"bar": cty.ObjectVal(map[string]cty.Value{
  1127  							"bing": cty.ObjectVal(map[string]cty.Value{
  1128  								"baz": cty.ListVal([]cty.Value{cty.StringVal("true")}),
  1129  							}),
  1130  						}),
  1131  					}),
  1132  				}),
  1133  			}),
  1134  			cty.ObjectVal(map[string]cty.Value{
  1135  				"foo": cty.SetVal([]cty.Value{
  1136  					cty.ObjectVal(map[string]cty.Value{
  1137  						"bar": cty.ObjectVal(map[string]cty.Value{
  1138  							"bing": cty.ObjectVal(map[string]cty.Value{
  1139  								"baz": cty.ListVal([]cty.Value{cty.StringVal("true")}),
  1140  							}),
  1141  						}),
  1142  					}),
  1143  				}),
  1144  			}),
  1145  		},
  1146  		"empty nested map in set": {
  1147  			&configschema.Block{
  1148  				BlockTypes: map[string]*configschema.NestedBlock{
  1149  					"foo": {
  1150  						Nesting: configschema.NestingSet,
  1151  						Block: configschema.Block{
  1152  							BlockTypes: map[string]*configschema.NestedBlock{
  1153  								"bar": {
  1154  									Nesting: configschema.NestingMap,
  1155  									Block: configschema.Block{
  1156  										Attributes: map[string]*configschema.Attribute{
  1157  											"baz": {
  1158  												Type:     cty.String,
  1159  												Optional: true,
  1160  											},
  1161  										},
  1162  									},
  1163  								},
  1164  							},
  1165  						},
  1166  					},
  1167  				},
  1168  			},
  1169  			cty.ObjectVal(map[string]cty.Value{
  1170  				"foo": cty.SetVal([]cty.Value{
  1171  					cty.ObjectVal(map[string]cty.Value{
  1172  						"bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{
  1173  							"baz": cty.String,
  1174  						})),
  1175  					}),
  1176  				}),
  1177  			}),
  1178  			cty.ObjectVal(map[string]cty.Value{
  1179  				"foo": cty.SetVal([]cty.Value{
  1180  					cty.ObjectVal(map[string]cty.Value{
  1181  						"bar": cty.MapVal(map[string]cty.Value{
  1182  							"bing": cty.ObjectVal(map[string]cty.Value{
  1183  								"baz": cty.StringVal("true"),
  1184  							}),
  1185  						}),
  1186  					}),
  1187  				}),
  1188  			}),
  1189  			cty.ObjectVal(map[string]cty.Value{
  1190  				"foo": cty.SetVal([]cty.Value{
  1191  					cty.ObjectVal(map[string]cty.Value{
  1192  						"bar": cty.MapVal(map[string]cty.Value{
  1193  							"bing": cty.ObjectVal(map[string]cty.Value{
  1194  								"baz": cty.StringVal("true"),
  1195  							}),
  1196  						}),
  1197  					}),
  1198  				}),
  1199  			}),
  1200  		},
  1201  		// This example has a mixture of optional, computed and required in a deeply-nested NestedType attribute
  1202  		"deeply NestedType": {
  1203  			&configschema.Block{
  1204  				Attributes: map[string]*configschema.Attribute{
  1205  					"foo": {
  1206  						NestedType: &configschema.Object{
  1207  							Nesting: configschema.NestingSingle,
  1208  							Attributes: map[string]*configschema.Attribute{
  1209  								"bar": {
  1210  									NestedType: &configschema.Object{
  1211  										Nesting:    configschema.NestingSingle,
  1212  										Attributes: testAttributes,
  1213  									},
  1214  									Required: true,
  1215  								},
  1216  								"baz": {
  1217  									NestedType: &configschema.Object{
  1218  										Nesting:    configschema.NestingSingle,
  1219  										Attributes: testAttributes,
  1220  									},
  1221  									Optional: true,
  1222  								},
  1223  							},
  1224  						},
  1225  						Optional: true,
  1226  					},
  1227  				},
  1228  			},
  1229  			// prior
  1230  			cty.ObjectVal(map[string]cty.Value{
  1231  				"foo": cty.ObjectVal(map[string]cty.Value{
  1232  					"bar": cty.NullVal(cty.DynamicPseudoType),
  1233  					"baz": cty.ObjectVal(map[string]cty.Value{
  1234  						"optional":          cty.NullVal(cty.String),
  1235  						"computed":          cty.StringVal("hello"),
  1236  						"optional_computed": cty.StringVal("prior"),
  1237  						"required":          cty.StringVal("present"),
  1238  					}),
  1239  				}),
  1240  			}),
  1241  			// config
  1242  			cty.ObjectVal(map[string]cty.Value{
  1243  				"foo": cty.ObjectVal(map[string]cty.Value{
  1244  					"bar": cty.UnknownVal(cty.Object(map[string]cty.Type{ // explicit unknown from the config
  1245  						"optional":          cty.String,
  1246  						"computed":          cty.String,
  1247  						"optional_computed": cty.String,
  1248  						"required":          cty.String,
  1249  					})),
  1250  					"baz": cty.ObjectVal(map[string]cty.Value{
  1251  						"optional":          cty.NullVal(cty.String),
  1252  						"computed":          cty.NullVal(cty.String),
  1253  						"optional_computed": cty.StringVal("hello"),
  1254  						"required":          cty.StringVal("present"),
  1255  					}),
  1256  				}),
  1257  			}),
  1258  			// want
  1259  			cty.ObjectVal(map[string]cty.Value{
  1260  				"foo": cty.ObjectVal(map[string]cty.Value{
  1261  					"bar": cty.UnknownVal(cty.Object(map[string]cty.Type{ // explicit unknown preserved from the config
  1262  						"optional":          cty.String,
  1263  						"computed":          cty.String,
  1264  						"optional_computed": cty.String,
  1265  						"required":          cty.String,
  1266  					})),
  1267  					"baz": cty.ObjectVal(map[string]cty.Value{
  1268  						"optional":          cty.NullVal(cty.String),  // config is null
  1269  						"computed":          cty.StringVal("hello"),   // computed values come from prior
  1270  						"optional_computed": cty.StringVal("hello"),   // config takes precedent over prior in opt+computed
  1271  						"required":          cty.StringVal("present"), // value from config
  1272  					}),
  1273  				}),
  1274  			}),
  1275  		},
  1276  		"deeply nested set": {
  1277  			&configschema.Block{
  1278  				Attributes: map[string]*configschema.Attribute{
  1279  					"foo": {
  1280  						NestedType: &configschema.Object{
  1281  							Nesting: configschema.NestingSet,
  1282  							Attributes: map[string]*configschema.Attribute{
  1283  								"bar": {
  1284  									NestedType: &configschema.Object{
  1285  										Nesting:    configschema.NestingSet,
  1286  										Attributes: testAttributes,
  1287  									},
  1288  									Required: true,
  1289  								},
  1290  							},
  1291  						},
  1292  						Optional: true,
  1293  					},
  1294  				},
  1295  			},
  1296  			// prior values
  1297  			cty.ObjectVal(map[string]cty.Value{
  1298  				"foo": cty.SetVal([]cty.Value{
  1299  					cty.ObjectVal(map[string]cty.Value{
  1300  						"bar": cty.SetVal([]cty.Value{
  1301  							cty.ObjectVal(map[string]cty.Value{
  1302  								"optional":          cty.StringVal("prior"),
  1303  								"computed":          cty.StringVal("prior"),
  1304  								"optional_computed": cty.StringVal("prior"),
  1305  								"required":          cty.StringVal("prior"),
  1306  							}),
  1307  						}),
  1308  					}),
  1309  					cty.ObjectVal(map[string]cty.Value{
  1310  						"bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
  1311  							"optional":          cty.StringVal("other_prior"),
  1312  							"computed":          cty.StringVal("other_prior"),
  1313  							"optional_computed": cty.StringVal("other_prior"),
  1314  							"required":          cty.StringVal("other_prior"),
  1315  						})}),
  1316  					}),
  1317  				}),
  1318  			}),
  1319  			// config differs from prior
  1320  			cty.ObjectVal(map[string]cty.Value{
  1321  				"foo": cty.SetVal([]cty.Value{
  1322  					cty.ObjectVal(map[string]cty.Value{
  1323  						"bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
  1324  							"optional":          cty.StringVal("configured"),
  1325  							"computed":          cty.NullVal(cty.String), // computed attrs are null in config
  1326  							"optional_computed": cty.StringVal("configured"),
  1327  							"required":          cty.StringVal("configured"),
  1328  						})}),
  1329  					}),
  1330  					cty.ObjectVal(map[string]cty.Value{
  1331  						"bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
  1332  							"optional":          cty.NullVal(cty.String), // explicit null in config
  1333  							"computed":          cty.NullVal(cty.String), // computed attrs are null in config
  1334  							"optional_computed": cty.StringVal("other_configured"),
  1335  							"required":          cty.StringVal("other_configured"),
  1336  						})}),
  1337  					}),
  1338  				}),
  1339  			}),
  1340  			// want:
  1341  			cty.ObjectVal(map[string]cty.Value{
  1342  				"foo": cty.SetVal([]cty.Value{
  1343  					cty.ObjectVal(map[string]cty.Value{
  1344  						"bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
  1345  							"optional":          cty.StringVal("configured"),
  1346  							"computed":          cty.NullVal(cty.String),
  1347  							"optional_computed": cty.StringVal("configured"),
  1348  							"required":          cty.StringVal("configured"),
  1349  						})}),
  1350  					}),
  1351  					cty.ObjectVal(map[string]cty.Value{
  1352  						"bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{
  1353  							"optional":          cty.NullVal(cty.String), // explicit null in config is preserved
  1354  							"computed":          cty.NullVal(cty.String),
  1355  							"optional_computed": cty.StringVal("other_configured"),
  1356  							"required":          cty.StringVal("other_configured"),
  1357  						})}),
  1358  					}),
  1359  				}),
  1360  			}),
  1361  		},
  1362  		"expected null NestedTypes": {
  1363  			&configschema.Block{
  1364  				Attributes: map[string]*configschema.Attribute{
  1365  					"single": {
  1366  						NestedType: &configschema.Object{
  1367  							Nesting: configschema.NestingSingle,
  1368  							Attributes: map[string]*configschema.Attribute{
  1369  								"bar": {Type: cty.String},
  1370  							},
  1371  						},
  1372  						Optional: true,
  1373  					},
  1374  					"list": {
  1375  						NestedType: &configschema.Object{
  1376  							Nesting: configschema.NestingList,
  1377  							Attributes: map[string]*configschema.Attribute{
  1378  								"bar": {Type: cty.String},
  1379  							},
  1380  						},
  1381  						Optional: true,
  1382  					},
  1383  					"set": {
  1384  						NestedType: &configschema.Object{
  1385  							Nesting: configschema.NestingSet,
  1386  							Attributes: map[string]*configschema.Attribute{
  1387  								"bar": {Type: cty.String},
  1388  							},
  1389  						},
  1390  						Optional: true,
  1391  					},
  1392  					"map": {
  1393  						NestedType: &configschema.Object{
  1394  							Nesting: configschema.NestingMap,
  1395  							Attributes: map[string]*configschema.Attribute{
  1396  								"bar": {Type: cty.String},
  1397  							},
  1398  						},
  1399  						Optional: true,
  1400  					},
  1401  					"nested_map": {
  1402  						NestedType: &configschema.Object{
  1403  							Nesting: configschema.NestingMap,
  1404  							Attributes: map[string]*configschema.Attribute{
  1405  								"inner": {
  1406  									NestedType: &configschema.Object{
  1407  										Nesting:    configschema.NestingSingle,
  1408  										Attributes: testAttributes,
  1409  									},
  1410  								},
  1411  							},
  1412  						},
  1413  						Optional: true,
  1414  					},
  1415  				},
  1416  			},
  1417  			cty.ObjectVal(map[string]cty.Value{
  1418  				"single": cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")}),
  1419  				"list":   cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}),
  1420  				"map": cty.MapVal(map[string]cty.Value{
  1421  					"map_entry": cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")}),
  1422  				}),
  1423  				"set": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}),
  1424  				"nested_map": cty.MapVal(map[string]cty.Value{
  1425  					"a": cty.ObjectVal(map[string]cty.Value{
  1426  						"inner": cty.ObjectVal(map[string]cty.Value{
  1427  							"optional":          cty.StringVal("foo"),
  1428  							"computed":          cty.StringVal("foo"),
  1429  							"optional_computed": cty.StringVal("foo"),
  1430  							"required":          cty.StringVal("foo"),
  1431  						}),
  1432  					}),
  1433  				}),
  1434  			}),
  1435  			cty.ObjectVal(map[string]cty.Value{
  1436  				"single": cty.NullVal(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1437  				"list":   cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))),
  1438  				"map":    cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))),
  1439  				"set":    cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))),
  1440  				"nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  1441  					"inner": cty.Object(map[string]cty.Type{
  1442  						"optional":          cty.String,
  1443  						"computed":          cty.String,
  1444  						"optional_computed": cty.String,
  1445  						"required":          cty.String,
  1446  					}),
  1447  				}))),
  1448  			}),
  1449  			cty.ObjectVal(map[string]cty.Value{
  1450  				"single": cty.NullVal(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1451  				"list":   cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))),
  1452  				"map":    cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))),
  1453  				"set":    cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))),
  1454  				"nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
  1455  					"inner": cty.Object(map[string]cty.Type{
  1456  						"optional":          cty.String,
  1457  						"computed":          cty.String,
  1458  						"optional_computed": cty.String,
  1459  						"required":          cty.String,
  1460  					}),
  1461  				}))),
  1462  			}),
  1463  		},
  1464  		"expected empty NestedTypes": {
  1465  			&configschema.Block{
  1466  				Attributes: map[string]*configschema.Attribute{
  1467  					"set": {
  1468  						NestedType: &configschema.Object{
  1469  							Nesting: configschema.NestingSet,
  1470  							Attributes: map[string]*configschema.Attribute{
  1471  								"bar": {Type: cty.String},
  1472  							},
  1473  						},
  1474  						Optional: true,
  1475  					},
  1476  					"map": {
  1477  						NestedType: &configschema.Object{
  1478  							Nesting: configschema.NestingMap,
  1479  							Attributes: map[string]*configschema.Attribute{
  1480  								"bar": {Type: cty.String},
  1481  							},
  1482  						},
  1483  						Optional: true,
  1484  					},
  1485  				},
  1486  			},
  1487  			cty.ObjectVal(map[string]cty.Value{
  1488  				"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1489  				"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1490  			}),
  1491  			cty.ObjectVal(map[string]cty.Value{
  1492  				"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1493  				"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1494  			}),
  1495  			cty.ObjectVal(map[string]cty.Value{
  1496  				"map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1497  				"set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})),
  1498  			}),
  1499  		},
  1500  		"optional types set replacement": {
  1501  			&configschema.Block{
  1502  				Attributes: map[string]*configschema.Attribute{
  1503  					"set": {
  1504  						NestedType: &configschema.Object{
  1505  							Nesting: configschema.NestingSet,
  1506  							Attributes: map[string]*configschema.Attribute{
  1507  								"bar": {
  1508  									Type:     cty.String,
  1509  									Required: true,
  1510  								},
  1511  							},
  1512  						},
  1513  						Optional: true,
  1514  					},
  1515  				},
  1516  			},
  1517  			cty.ObjectVal(map[string]cty.Value{
  1518  				"set": cty.SetVal([]cty.Value{
  1519  					cty.ObjectVal(map[string]cty.Value{
  1520  						"bar": cty.StringVal("old"),
  1521  					}),
  1522  				}),
  1523  			}),
  1524  			cty.ObjectVal(map[string]cty.Value{
  1525  				"set": cty.SetVal([]cty.Value{
  1526  					cty.ObjectVal(map[string]cty.Value{
  1527  						"bar": cty.StringVal("new"),
  1528  					}),
  1529  				}),
  1530  			}),
  1531  			cty.ObjectVal(map[string]cty.Value{
  1532  				"set": cty.SetVal([]cty.Value{
  1533  					cty.ObjectVal(map[string]cty.Value{
  1534  						"bar": cty.StringVal("new"),
  1535  					}),
  1536  				}),
  1537  			}),
  1538  		},
  1539  		"prior null nested objects": {
  1540  			&configschema.Block{
  1541  				Attributes: map[string]*configschema.Attribute{
  1542  					"single": {
  1543  						NestedType: &configschema.Object{
  1544  							Nesting: configschema.NestingSingle,
  1545  							Attributes: map[string]*configschema.Attribute{
  1546  								"list": {
  1547  									NestedType: &configschema.Object{
  1548  										Nesting: configschema.NestingList,
  1549  										Attributes: map[string]*configschema.Attribute{
  1550  											"foo": {
  1551  												Type: cty.String,
  1552  											},
  1553  										},
  1554  									},
  1555  									Optional: true,
  1556  								},
  1557  							},
  1558  						},
  1559  						Optional: true,
  1560  					},
  1561  					"map": {
  1562  						NestedType: &configschema.Object{
  1563  							Nesting: configschema.NestingMap,
  1564  							Attributes: map[string]*configschema.Attribute{
  1565  								"map": {
  1566  									NestedType: &configschema.Object{
  1567  										Nesting: configschema.NestingList,
  1568  										Attributes: map[string]*configschema.Attribute{
  1569  											"foo": {
  1570  												Type: cty.String,
  1571  											},
  1572  										},
  1573  									},
  1574  									Optional: true,
  1575  								},
  1576  							},
  1577  						},
  1578  						Optional: true,
  1579  					},
  1580  				},
  1581  			},
  1582  			cty.NullVal(cty.Object(map[string]cty.Type{
  1583  				"single": cty.Object(map[string]cty.Type{
  1584  					"list": cty.List(cty.Object(map[string]cty.Type{
  1585  						"foo": cty.String,
  1586  					})),
  1587  				}),
  1588  				"map": cty.Map(cty.Object(map[string]cty.Type{
  1589  					"list": cty.List(cty.Object(map[string]cty.Type{
  1590  						"foo": cty.String,
  1591  					})),
  1592  				})),
  1593  			})),
  1594  			cty.ObjectVal(map[string]cty.Value{
  1595  				"single": cty.ObjectVal(map[string]cty.Value{
  1596  					"list": cty.ListVal([]cty.Value{
  1597  						cty.ObjectVal(map[string]cty.Value{
  1598  							"foo": cty.StringVal("a"),
  1599  						}),
  1600  						cty.ObjectVal(map[string]cty.Value{
  1601  							"foo": cty.StringVal("b"),
  1602  						}),
  1603  					}),
  1604  				}),
  1605  				"map": cty.MapVal(map[string]cty.Value{
  1606  					"one": cty.ObjectVal(map[string]cty.Value{
  1607  						"list": cty.ListVal([]cty.Value{
  1608  							cty.ObjectVal(map[string]cty.Value{
  1609  								"foo": cty.StringVal("a"),
  1610  							}),
  1611  							cty.ObjectVal(map[string]cty.Value{
  1612  								"foo": cty.StringVal("b"),
  1613  							}),
  1614  						}),
  1615  					}),
  1616  				}),
  1617  			}),
  1618  			cty.ObjectVal(map[string]cty.Value{
  1619  				"single": cty.ObjectVal(map[string]cty.Value{
  1620  					"list": cty.ListVal([]cty.Value{
  1621  						cty.ObjectVal(map[string]cty.Value{
  1622  							"foo": cty.StringVal("a"),
  1623  						}),
  1624  						cty.ObjectVal(map[string]cty.Value{
  1625  							"foo": cty.StringVal("b"),
  1626  						}),
  1627  					}),
  1628  				}),
  1629  				"map": cty.MapVal(map[string]cty.Value{
  1630  					"one": cty.ObjectVal(map[string]cty.Value{
  1631  						"list": cty.ListVal([]cty.Value{
  1632  							cty.ObjectVal(map[string]cty.Value{
  1633  								"foo": cty.StringVal("a"),
  1634  							}),
  1635  							cty.ObjectVal(map[string]cty.Value{
  1636  								"foo": cty.StringVal("b"),
  1637  							}),
  1638  						}),
  1639  					}),
  1640  				}),
  1641  			}),
  1642  		},
  1643  
  1644  		// data sources are planned with an unknown value
  1645  		"unknown prior nested objects": {
  1646  			&configschema.Block{
  1647  				Attributes: map[string]*configschema.Attribute{
  1648  					"list": {
  1649  						NestedType: &configschema.Object{
  1650  							Nesting: configschema.NestingList,
  1651  							Attributes: map[string]*configschema.Attribute{
  1652  								"list": {
  1653  									NestedType: &configschema.Object{
  1654  										Nesting: configschema.NestingList,
  1655  										Attributes: map[string]*configschema.Attribute{
  1656  											"foo": {
  1657  												Type: cty.String,
  1658  											},
  1659  										},
  1660  									},
  1661  									Computed: true,
  1662  								},
  1663  							},
  1664  						},
  1665  						Computed: true,
  1666  					},
  1667  				},
  1668  			},
  1669  			cty.UnknownVal(cty.Object(map[string]cty.Type{
  1670  				"List": cty.List(cty.Object(map[string]cty.Type{
  1671  					"list": cty.List(cty.Object(map[string]cty.Type{
  1672  						"foo": cty.String,
  1673  					})),
  1674  				})),
  1675  			})),
  1676  			cty.NullVal(cty.Object(map[string]cty.Type{
  1677  				"List": cty.List(cty.Object(map[string]cty.Type{
  1678  					"list": cty.List(cty.Object(map[string]cty.Type{
  1679  						"foo": cty.String,
  1680  					})),
  1681  				})),
  1682  			})),
  1683  			cty.UnknownVal(cty.Object(map[string]cty.Type{
  1684  				"List": cty.List(cty.Object(map[string]cty.Type{
  1685  					"list": cty.List(cty.Object(map[string]cty.Type{
  1686  						"foo": cty.String,
  1687  					})),
  1688  				})),
  1689  			})),
  1690  		},
  1691  	}
  1692  
  1693  	for name, test := range tests {
  1694  		t.Run(name, func(t *testing.T) {
  1695  			got := ProposedNew(test.Schema, test.Prior, test.Config)
  1696  			if !got.RawEquals(test.Want) {
  1697  				t.Errorf("wrong result\ngot:  %swant: %s", dump.Value(got), dump.Value(test.Want))
  1698  			}
  1699  		})
  1700  	}
  1701  }
  1702  
  1703  var testAttributes = map[string]*configschema.Attribute{
  1704  	"optional": {
  1705  		Type:     cty.String,
  1706  		Optional: true,
  1707  	},
  1708  	"computed": {
  1709  		Type:     cty.String,
  1710  		Computed: true,
  1711  	},
  1712  	"optional_computed": {
  1713  		Type:     cty.String,
  1714  		Computed: true,
  1715  		Optional: true,
  1716  	},
  1717  	"required": {
  1718  		Type:     cty.String,
  1719  		Required: true,
  1720  	},
  1721  }