github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plans/objchange/compatible_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package objchange
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/apparentlymart/go-dump/dump"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	"github.com/terramate-io/tf/configs/configschema"
    14  	"github.com/terramate-io/tf/lang/marks"
    15  	"github.com/terramate-io/tf/tfdiags"
    16  )
    17  
    18  func TestAssertObjectCompatible(t *testing.T) {
    19  	schemaWithFoo := configschema.Block{
    20  		Attributes: map[string]*configschema.Attribute{
    21  			"foo": {Type: cty.String, Optional: true},
    22  		},
    23  	}
    24  	fooBlockValue := cty.ObjectVal(map[string]cty.Value{
    25  		"foo": cty.StringVal("bar"),
    26  	})
    27  	schemaWithFooBar := configschema.Block{
    28  		Attributes: map[string]*configschema.Attribute{
    29  			"foo": {Type: cty.String, Optional: true},
    30  			"bar": {Type: cty.String, Optional: true},
    31  		},
    32  	}
    33  	fooBarBlockValue := cty.ObjectVal(map[string]cty.Value{
    34  		"foo": cty.StringVal("bar"),
    35  		"bar": cty.NullVal(cty.String), // simulating the situation where bar isn't set in the config at all
    36  	})
    37  
    38  	tests := []struct {
    39  		Schema   *configschema.Block
    40  		Planned  cty.Value
    41  		Actual   cty.Value
    42  		WantErrs []string
    43  	}{
    44  		{
    45  			&configschema.Block{},
    46  			cty.EmptyObjectVal,
    47  			cty.EmptyObjectVal,
    48  			nil,
    49  		},
    50  		{
    51  			&configschema.Block{
    52  				Attributes: map[string]*configschema.Attribute{
    53  					"id": {
    54  						Type:     cty.String,
    55  						Computed: true,
    56  					},
    57  					"name": {
    58  						Type:     cty.String,
    59  						Required: true,
    60  					},
    61  				},
    62  			},
    63  			cty.ObjectVal(map[string]cty.Value{
    64  				"id":   cty.UnknownVal(cty.String),
    65  				"name": cty.StringVal("thingy"),
    66  			}),
    67  			cty.ObjectVal(map[string]cty.Value{
    68  				"id":   cty.UnknownVal(cty.String),
    69  				"name": cty.StringVal("thingy"),
    70  			}),
    71  			nil,
    72  		},
    73  		{
    74  			&configschema.Block{
    75  				Attributes: map[string]*configschema.Attribute{
    76  					"id": {
    77  						Type:     cty.String,
    78  						Computed: true,
    79  					},
    80  					"name": {
    81  						Type:     cty.String,
    82  						Required: true,
    83  					},
    84  				},
    85  			},
    86  			cty.ObjectVal(map[string]cty.Value{
    87  				"id":   cty.UnknownVal(cty.String),
    88  				"name": cty.UnknownVal(cty.String),
    89  			}),
    90  			cty.ObjectVal(map[string]cty.Value{
    91  				"id":   cty.UnknownVal(cty.String),
    92  				"name": cty.StringVal("thingy"),
    93  			}),
    94  			nil,
    95  		},
    96  		{
    97  			&configschema.Block{
    98  				Attributes: map[string]*configschema.Attribute{
    99  					"id": {
   100  						Type:     cty.String,
   101  						Computed: true,
   102  					},
   103  					"name": {
   104  						Type:     cty.String,
   105  						Required: true,
   106  					},
   107  				},
   108  			},
   109  			cty.ObjectVal(map[string]cty.Value{
   110  				"id":   cty.UnknownVal(cty.String),
   111  				"name": cty.StringVal("wotsit"),
   112  			}),
   113  			cty.ObjectVal(map[string]cty.Value{
   114  				"id":   cty.UnknownVal(cty.String),
   115  				"name": cty.StringVal("thingy"),
   116  			}),
   117  			[]string{
   118  				`.name: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
   119  			},
   120  		},
   121  		{
   122  			&configschema.Block{
   123  				Attributes: map[string]*configschema.Attribute{
   124  					"name": {
   125  						Type:     cty.String,
   126  						Required: true,
   127  					},
   128  				},
   129  			},
   130  			cty.ObjectVal(map[string]cty.Value{
   131  				"name": cty.UnknownVal(cty.String),
   132  			}),
   133  			cty.ObjectVal(map[string]cty.Value{
   134  				"name": cty.Zero,
   135  			}),
   136  			[]string{
   137  				`.name: wrong final value type: string required`,
   138  			},
   139  		},
   140  		{
   141  			&configschema.Block{
   142  				Attributes: map[string]*configschema.Attribute{
   143  					"name": {
   144  						Type:     cty.String,
   145  						Required: true,
   146  					},
   147  				},
   148  			},
   149  			cty.ObjectVal(map[string]cty.Value{
   150  				"name": cty.UnknownVal(cty.String).RefineNotNull(),
   151  			}),
   152  			cty.ObjectVal(map[string]cty.Value{
   153  				"name": cty.NullVal(cty.String),
   154  			}),
   155  			[]string{
   156  				`.name: final value cty.NullVal(cty.String) does not conform to planning placeholder cty.UnknownVal(cty.String).RefineNotNull()`,
   157  			},
   158  		},
   159  		{
   160  			&configschema.Block{
   161  				Attributes: map[string]*configschema.Attribute{
   162  					"name": {
   163  						Type:     cty.String,
   164  						Required: true,
   165  					},
   166  				},
   167  			},
   168  			cty.ObjectVal(map[string]cty.Value{
   169  				"name": cty.UnknownVal(cty.String).Refine().
   170  					StringPrefix("boop:").
   171  					NewValue(),
   172  			}),
   173  			cty.ObjectVal(map[string]cty.Value{
   174  				"name": cty.StringVal("thingy"),
   175  			}),
   176  			[]string{
   177  				`.name: final value cty.StringVal("thingy") does not conform to planning placeholder cty.UnknownVal(cty.String).Refine().StringPrefixFull("boop:").NewValue()`,
   178  			},
   179  		},
   180  		{
   181  			&configschema.Block{
   182  				Attributes: map[string]*configschema.Attribute{
   183  					"id": {
   184  						Type:     cty.String,
   185  						Computed: true,
   186  					},
   187  					"name": {
   188  						Type:      cty.String,
   189  						Required:  true,
   190  						Sensitive: true,
   191  					},
   192  				},
   193  			},
   194  			cty.ObjectVal(map[string]cty.Value{
   195  				"id":   cty.UnknownVal(cty.String),
   196  				"name": cty.StringVal("wotsit"),
   197  			}),
   198  			cty.ObjectVal(map[string]cty.Value{
   199  				"id":   cty.UnknownVal(cty.String),
   200  				"name": cty.StringVal("thingy"),
   201  			}),
   202  			[]string{
   203  				`.name: inconsistent values for sensitive attribute`,
   204  			},
   205  		},
   206  		{
   207  			&configschema.Block{
   208  				Attributes: map[string]*configschema.Attribute{
   209  					"id": {
   210  						Type:     cty.String,
   211  						Computed: true,
   212  					},
   213  					"name": {
   214  						Type:     cty.String,
   215  						Required: true,
   216  					},
   217  				},
   218  			},
   219  			cty.ObjectVal(map[string]cty.Value{
   220  				"id":   cty.UnknownVal(cty.String),
   221  				"name": cty.StringVal("wotsit").Mark(marks.Sensitive),
   222  			}),
   223  			cty.ObjectVal(map[string]cty.Value{
   224  				"id":   cty.UnknownVal(cty.String),
   225  				"name": cty.StringVal("thingy"),
   226  			}),
   227  			[]string{
   228  				`.name: inconsistent values for sensitive attribute`,
   229  			},
   230  		},
   231  		{
   232  			&configschema.Block{
   233  				Attributes: map[string]*configschema.Attribute{
   234  					"id": {
   235  						Type:     cty.String,
   236  						Computed: true,
   237  					},
   238  					"name": {
   239  						Type:     cty.String,
   240  						Required: true,
   241  					},
   242  				},
   243  			},
   244  			cty.ObjectVal(map[string]cty.Value{
   245  				"id":   cty.UnknownVal(cty.String),
   246  				"name": cty.StringVal("wotsit"),
   247  			}),
   248  			cty.ObjectVal(map[string]cty.Value{
   249  				"id":   cty.UnknownVal(cty.String),
   250  				"name": cty.StringVal("thingy").Mark(marks.Sensitive),
   251  			}),
   252  			[]string{
   253  				`.name: inconsistent values for sensitive attribute`,
   254  			},
   255  		},
   256  		{
   257  			// This tests the codepath that leads to couldHaveUnknownBlockPlaceholder,
   258  			// where a set may be sensitive and need to be unmarked before it
   259  			// is iterated upon
   260  			&configschema.Block{
   261  				BlockTypes: map[string]*configschema.NestedBlock{
   262  					"configuration": {
   263  						Nesting: configschema.NestingList,
   264  						Block: configschema.Block{
   265  							BlockTypes: map[string]*configschema.NestedBlock{
   266  								"sensitive_fields": {
   267  									Nesting: configschema.NestingSet,
   268  									Block:   schemaWithFoo,
   269  								},
   270  							},
   271  						},
   272  					},
   273  				},
   274  			},
   275  			cty.ObjectVal(map[string]cty.Value{
   276  				"configuration": cty.TupleVal([]cty.Value{
   277  					cty.ObjectVal(map[string]cty.Value{
   278  						"sensitive_fields": cty.SetVal([]cty.Value{
   279  							cty.ObjectVal(map[string]cty.Value{
   280  								"foo": cty.StringVal("secret"),
   281  							}),
   282  						}).Mark(marks.Sensitive),
   283  					}),
   284  				}),
   285  			}),
   286  			cty.ObjectVal(map[string]cty.Value{
   287  				"configuration": cty.TupleVal([]cty.Value{
   288  					cty.ObjectVal(map[string]cty.Value{
   289  						"sensitive_fields": cty.SetVal([]cty.Value{
   290  							cty.ObjectVal(map[string]cty.Value{
   291  								"foo": cty.StringVal("secret"),
   292  							}),
   293  						}).Mark(marks.Sensitive),
   294  					}),
   295  				}),
   296  			}),
   297  			nil,
   298  		},
   299  		{
   300  			&configschema.Block{
   301  				Attributes: map[string]*configschema.Attribute{
   302  					"id": {
   303  						Type:     cty.String,
   304  						Computed: true,
   305  					},
   306  					"stuff": {
   307  						Type:     cty.DynamicPseudoType,
   308  						Required: true,
   309  					},
   310  				},
   311  			},
   312  			cty.ObjectVal(map[string]cty.Value{
   313  				"id":    cty.UnknownVal(cty.String),
   314  				"stuff": cty.DynamicVal,
   315  			}),
   316  			cty.ObjectVal(map[string]cty.Value{
   317  				"id":    cty.UnknownVal(cty.String),
   318  				"stuff": cty.StringVal("thingy"),
   319  			}),
   320  			[]string{},
   321  		},
   322  		{
   323  			&configschema.Block{
   324  				Attributes: map[string]*configschema.Attribute{
   325  					"obj": {
   326  						Type: cty.Object(map[string]cty.Type{
   327  							"stuff": cty.DynamicPseudoType,
   328  						}),
   329  					},
   330  				},
   331  			},
   332  			cty.ObjectVal(map[string]cty.Value{
   333  				"obj": cty.ObjectVal(map[string]cty.Value{
   334  					"stuff": cty.DynamicVal,
   335  				}),
   336  			}),
   337  			cty.ObjectVal(map[string]cty.Value{
   338  				"obj": cty.ObjectVal(map[string]cty.Value{
   339  					"stuff": cty.NumberIntVal(3),
   340  				}),
   341  			}),
   342  			[]string{},
   343  		},
   344  		{
   345  			&configschema.Block{
   346  				Attributes: map[string]*configschema.Attribute{
   347  					"id": {
   348  						Type:     cty.String,
   349  						Computed: true,
   350  					},
   351  					"stuff": {
   352  						Type:     cty.DynamicPseudoType,
   353  						Required: true,
   354  					},
   355  				},
   356  			},
   357  			cty.ObjectVal(map[string]cty.Value{
   358  				"id":    cty.UnknownVal(cty.String),
   359  				"stuff": cty.StringVal("wotsit"),
   360  			}),
   361  			cty.ObjectVal(map[string]cty.Value{
   362  				"id":    cty.UnknownVal(cty.String),
   363  				"stuff": cty.StringVal("thingy"),
   364  			}),
   365  			[]string{
   366  				`.stuff: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
   367  			},
   368  		},
   369  		{
   370  			&configschema.Block{
   371  				Attributes: map[string]*configschema.Attribute{
   372  					"id": {
   373  						Type:     cty.String,
   374  						Computed: true,
   375  					},
   376  					"stuff": {
   377  						Type:     cty.DynamicPseudoType,
   378  						Required: true,
   379  					},
   380  				},
   381  			},
   382  			cty.ObjectVal(map[string]cty.Value{
   383  				"id":    cty.UnknownVal(cty.String),
   384  				"stuff": cty.StringVal("true"),
   385  			}),
   386  			cty.ObjectVal(map[string]cty.Value{
   387  				"id":    cty.UnknownVal(cty.String),
   388  				"stuff": cty.True,
   389  			}),
   390  			[]string{
   391  				`.stuff: wrong final value type: string required`,
   392  			},
   393  		},
   394  		{
   395  			&configschema.Block{
   396  				Attributes: map[string]*configschema.Attribute{
   397  					"id": {
   398  						Type:     cty.String,
   399  						Computed: true,
   400  					},
   401  					"stuff": {
   402  						Type:     cty.DynamicPseudoType,
   403  						Required: true,
   404  					},
   405  				},
   406  			},
   407  			cty.ObjectVal(map[string]cty.Value{
   408  				"id":    cty.UnknownVal(cty.String),
   409  				"stuff": cty.DynamicVal,
   410  			}),
   411  			cty.ObjectVal(map[string]cty.Value{
   412  				"id":    cty.UnknownVal(cty.String),
   413  				"stuff": cty.EmptyObjectVal,
   414  			}),
   415  			nil,
   416  		},
   417  		{
   418  			&configschema.Block{
   419  				Attributes: map[string]*configschema.Attribute{
   420  					"id": {
   421  						Type:     cty.String,
   422  						Computed: true,
   423  					},
   424  					"stuff": {
   425  						Type:     cty.DynamicPseudoType,
   426  						Required: true,
   427  					},
   428  				},
   429  			},
   430  			cty.ObjectVal(map[string]cty.Value{
   431  				"id": cty.UnknownVal(cty.String),
   432  				"stuff": cty.ObjectVal(map[string]cty.Value{
   433  					"nonsense": cty.StringVal("yup"),
   434  				}),
   435  			}),
   436  			cty.ObjectVal(map[string]cty.Value{
   437  				"id":    cty.UnknownVal(cty.String),
   438  				"stuff": cty.EmptyObjectVal,
   439  			}),
   440  			[]string{
   441  				`.stuff: wrong final value type: attribute "nonsense" is required`,
   442  			},
   443  		},
   444  		{
   445  			&configschema.Block{
   446  				Attributes: map[string]*configschema.Attribute{
   447  					"id": {
   448  						Type:     cty.String,
   449  						Computed: true,
   450  					},
   451  					"tags": {
   452  						Type:     cty.Map(cty.String),
   453  						Optional: true,
   454  					},
   455  				},
   456  			},
   457  			cty.ObjectVal(map[string]cty.Value{
   458  				"id": cty.UnknownVal(cty.String),
   459  				"tags": cty.MapVal(map[string]cty.Value{
   460  					"Name": cty.StringVal("thingy"),
   461  				}),
   462  			}),
   463  			cty.ObjectVal(map[string]cty.Value{
   464  				"id": cty.UnknownVal(cty.String),
   465  				"tags": cty.MapVal(map[string]cty.Value{
   466  					"Name": cty.StringVal("thingy"),
   467  				}),
   468  			}),
   469  			nil,
   470  		},
   471  		{
   472  			&configschema.Block{
   473  				Attributes: map[string]*configschema.Attribute{
   474  					"id": {
   475  						Type:     cty.String,
   476  						Computed: true,
   477  					},
   478  					"tags": {
   479  						Type:     cty.Map(cty.String),
   480  						Optional: true,
   481  					},
   482  				},
   483  			},
   484  			cty.ObjectVal(map[string]cty.Value{
   485  				"id": cty.UnknownVal(cty.String),
   486  				"tags": cty.MapVal(map[string]cty.Value{
   487  					"Name": cty.UnknownVal(cty.String),
   488  				}),
   489  			}),
   490  			cty.ObjectVal(map[string]cty.Value{
   491  				"id": cty.UnknownVal(cty.String),
   492  				"tags": cty.MapVal(map[string]cty.Value{
   493  					"Name": cty.StringVal("thingy"),
   494  				}),
   495  			}),
   496  			nil,
   497  		},
   498  		{
   499  			&configschema.Block{
   500  				Attributes: map[string]*configschema.Attribute{
   501  					"id": {
   502  						Type:     cty.String,
   503  						Computed: true,
   504  					},
   505  					"tags": {
   506  						Type:     cty.Map(cty.String),
   507  						Optional: true,
   508  					},
   509  				},
   510  			},
   511  			cty.ObjectVal(map[string]cty.Value{
   512  				"id": cty.UnknownVal(cty.String),
   513  				"tags": cty.MapVal(map[string]cty.Value{
   514  					"Name": cty.StringVal("wotsit"),
   515  				}),
   516  			}),
   517  			cty.ObjectVal(map[string]cty.Value{
   518  				"id": cty.UnknownVal(cty.String),
   519  				"tags": cty.MapVal(map[string]cty.Value{
   520  					"Name": cty.StringVal("thingy"),
   521  				}),
   522  			}),
   523  			[]string{
   524  				`.tags["Name"]: was cty.StringVal("wotsit"), but now cty.StringVal("thingy")`,
   525  			},
   526  		},
   527  		{
   528  			&configschema.Block{
   529  				Attributes: map[string]*configschema.Attribute{
   530  					"id": {
   531  						Type:     cty.String,
   532  						Computed: true,
   533  					},
   534  					"tags": {
   535  						Type:     cty.Map(cty.String),
   536  						Optional: true,
   537  					},
   538  				},
   539  			},
   540  			cty.ObjectVal(map[string]cty.Value{
   541  				"id": cty.UnknownVal(cty.String),
   542  				"tags": cty.MapVal(map[string]cty.Value{
   543  					"Name": cty.StringVal("thingy"),
   544  				}),
   545  			}),
   546  			cty.ObjectVal(map[string]cty.Value{
   547  				"id": cty.UnknownVal(cty.String),
   548  				"tags": cty.MapVal(map[string]cty.Value{
   549  					"Name": cty.StringVal("thingy"),
   550  					"Env":  cty.StringVal("production"),
   551  				}),
   552  			}),
   553  			[]string{
   554  				`.tags: new element "Env" has appeared`,
   555  			},
   556  		},
   557  		{
   558  			&configschema.Block{
   559  				Attributes: map[string]*configschema.Attribute{
   560  					"id": {
   561  						Type:     cty.String,
   562  						Computed: true,
   563  					},
   564  					"tags": {
   565  						Type:     cty.Map(cty.String),
   566  						Optional: true,
   567  					},
   568  				},
   569  			},
   570  			cty.ObjectVal(map[string]cty.Value{
   571  				"id": cty.UnknownVal(cty.String),
   572  				"tags": cty.MapVal(map[string]cty.Value{
   573  					"Name": cty.StringVal("thingy"),
   574  				}),
   575  			}),
   576  			cty.ObjectVal(map[string]cty.Value{
   577  				"id":   cty.UnknownVal(cty.String),
   578  				"tags": cty.MapValEmpty(cty.String),
   579  			}),
   580  			[]string{
   581  				`.tags: element "Name" has vanished`,
   582  			},
   583  		},
   584  		{
   585  			&configschema.Block{
   586  				Attributes: map[string]*configschema.Attribute{
   587  					"id": {
   588  						Type:     cty.String,
   589  						Computed: true,
   590  					},
   591  					"tags": {
   592  						Type:     cty.Map(cty.String),
   593  						Optional: true,
   594  					},
   595  				},
   596  			},
   597  			cty.ObjectVal(map[string]cty.Value{
   598  				"id": cty.UnknownVal(cty.String),
   599  				"tags": cty.MapVal(map[string]cty.Value{
   600  					"Name": cty.UnknownVal(cty.String),
   601  				}),
   602  			}),
   603  			cty.ObjectVal(map[string]cty.Value{
   604  				"id": cty.UnknownVal(cty.String),
   605  				"tags": cty.MapVal(map[string]cty.Value{
   606  					"Name": cty.NullVal(cty.String),
   607  				}),
   608  			}),
   609  			nil,
   610  		},
   611  		{
   612  			&configschema.Block{
   613  				Attributes: map[string]*configschema.Attribute{
   614  					"id": {
   615  						Type:     cty.String,
   616  						Computed: true,
   617  					},
   618  					"zones": {
   619  						Type:     cty.Set(cty.String),
   620  						Optional: true,
   621  					},
   622  				},
   623  			},
   624  			cty.ObjectVal(map[string]cty.Value{
   625  				"id": cty.UnknownVal(cty.String),
   626  				"zones": cty.SetVal([]cty.Value{
   627  					cty.StringVal("thingy"),
   628  				}),
   629  			}),
   630  			cty.ObjectVal(map[string]cty.Value{
   631  				"id": cty.UnknownVal(cty.String),
   632  				"zones": cty.SetVal([]cty.Value{
   633  					cty.StringVal("thingy"),
   634  				}),
   635  			}),
   636  			nil,
   637  		},
   638  		{
   639  			&configschema.Block{
   640  				Attributes: map[string]*configschema.Attribute{
   641  					"id": {
   642  						Type:     cty.String,
   643  						Computed: true,
   644  					},
   645  					"zones": {
   646  						Type:     cty.Set(cty.String),
   647  						Optional: true,
   648  					},
   649  				},
   650  			},
   651  			cty.ObjectVal(map[string]cty.Value{
   652  				"id": cty.UnknownVal(cty.String),
   653  				"zones": cty.SetVal([]cty.Value{
   654  					cty.StringVal("thingy"),
   655  				}),
   656  			}),
   657  			cty.ObjectVal(map[string]cty.Value{
   658  				"id": cty.UnknownVal(cty.String),
   659  				"zones": cty.SetVal([]cty.Value{
   660  					cty.StringVal("thingy"),
   661  					cty.StringVal("wotsit"),
   662  				}),
   663  			}),
   664  			[]string{
   665  				`.zones: actual set element cty.StringVal("wotsit") does not correlate with any element in plan`,
   666  				`.zones: length changed from 1 to 2`,
   667  			},
   668  		},
   669  		{
   670  			&configschema.Block{
   671  				Attributes: map[string]*configschema.Attribute{
   672  					"id": {
   673  						Type:     cty.String,
   674  						Computed: true,
   675  					},
   676  					"zones": {
   677  						Type:     cty.Set(cty.String),
   678  						Optional: true,
   679  					},
   680  				},
   681  			},
   682  			cty.ObjectVal(map[string]cty.Value{
   683  				"id": cty.UnknownVal(cty.String),
   684  				"zones": cty.SetVal([]cty.Value{
   685  					cty.UnknownVal(cty.String),
   686  					cty.UnknownVal(cty.String),
   687  				}),
   688  			}),
   689  			cty.ObjectVal(map[string]cty.Value{
   690  				"id": cty.UnknownVal(cty.String),
   691  				"zones": cty.SetVal([]cty.Value{
   692  					// Imagine that both of our unknown values ultimately resolved to "thingy",
   693  					// causing them to collapse into a single element. That's valid,
   694  					// even though it's also a little confusing and counter-intuitive.
   695  					cty.StringVal("thingy"),
   696  				}),
   697  			}),
   698  			nil,
   699  		},
   700  		{
   701  			&configschema.Block{
   702  				Attributes: map[string]*configschema.Attribute{
   703  					"id": {
   704  						Type:     cty.String,
   705  						Computed: true,
   706  					},
   707  					"names": {
   708  						Type:     cty.List(cty.String),
   709  						Optional: true,
   710  					},
   711  				},
   712  			},
   713  			cty.ObjectVal(map[string]cty.Value{
   714  				"id": cty.UnknownVal(cty.String),
   715  				"names": cty.ListVal([]cty.Value{
   716  					cty.StringVal("thingy"),
   717  				}),
   718  			}),
   719  			cty.ObjectVal(map[string]cty.Value{
   720  				"id": cty.UnknownVal(cty.String),
   721  				"names": cty.ListVal([]cty.Value{
   722  					cty.StringVal("thingy"),
   723  				}),
   724  			}),
   725  			nil,
   726  		},
   727  		{
   728  			&configschema.Block{
   729  				Attributes: map[string]*configschema.Attribute{
   730  					"id": {
   731  						Type:     cty.String,
   732  						Computed: true,
   733  					},
   734  					"names": {
   735  						Type:     cty.List(cty.String),
   736  						Optional: true,
   737  					},
   738  				},
   739  			},
   740  			cty.ObjectVal(map[string]cty.Value{
   741  				"id":    cty.UnknownVal(cty.String),
   742  				"names": cty.UnknownVal(cty.List(cty.String)),
   743  			}),
   744  			cty.ObjectVal(map[string]cty.Value{
   745  				"id": cty.UnknownVal(cty.String),
   746  				"names": cty.ListVal([]cty.Value{
   747  					cty.StringVal("thingy"),
   748  				}),
   749  			}),
   750  			nil,
   751  		},
   752  		{
   753  			&configschema.Block{
   754  				Attributes: map[string]*configschema.Attribute{
   755  					"id": {
   756  						Type:     cty.String,
   757  						Computed: true,
   758  					},
   759  					"names": {
   760  						Type:     cty.List(cty.String),
   761  						Optional: true,
   762  					},
   763  				},
   764  			},
   765  			cty.ObjectVal(map[string]cty.Value{
   766  				"id": cty.UnknownVal(cty.String),
   767  				"names": cty.ListVal([]cty.Value{
   768  					cty.UnknownVal(cty.String),
   769  				}),
   770  			}),
   771  			cty.ObjectVal(map[string]cty.Value{
   772  				"id": cty.UnknownVal(cty.String),
   773  				"names": cty.ListVal([]cty.Value{
   774  					cty.StringVal("thingy"),
   775  				}),
   776  			}),
   777  			nil,
   778  		},
   779  		{
   780  			&configschema.Block{
   781  				Attributes: map[string]*configschema.Attribute{
   782  					"id": {
   783  						Type:     cty.String,
   784  						Computed: true,
   785  					},
   786  					"names": {
   787  						Type:     cty.List(cty.String),
   788  						Optional: true,
   789  					},
   790  				},
   791  			},
   792  			cty.ObjectVal(map[string]cty.Value{
   793  				"id": cty.UnknownVal(cty.String),
   794  				"names": cty.ListVal([]cty.Value{
   795  					cty.StringVal("thingy"),
   796  					cty.UnknownVal(cty.String),
   797  				}),
   798  			}),
   799  			cty.ObjectVal(map[string]cty.Value{
   800  				"id": cty.UnknownVal(cty.String),
   801  				"names": cty.ListVal([]cty.Value{
   802  					cty.StringVal("thingy"),
   803  					cty.StringVal("wotsit"),
   804  				}),
   805  			}),
   806  			nil,
   807  		},
   808  		{
   809  			&configschema.Block{
   810  				Attributes: map[string]*configschema.Attribute{
   811  					"id": {
   812  						Type:     cty.String,
   813  						Computed: true,
   814  					},
   815  					"names": {
   816  						Type:     cty.List(cty.String),
   817  						Optional: true,
   818  					},
   819  				},
   820  			},
   821  			cty.ObjectVal(map[string]cty.Value{
   822  				"id": cty.UnknownVal(cty.String),
   823  				"names": cty.ListVal([]cty.Value{
   824  					cty.UnknownVal(cty.String),
   825  					cty.StringVal("thingy"),
   826  				}),
   827  			}),
   828  			cty.ObjectVal(map[string]cty.Value{
   829  				"id": cty.UnknownVal(cty.String),
   830  				"names": cty.ListVal([]cty.Value{
   831  					cty.StringVal("thingy"),
   832  					cty.StringVal("wotsit"),
   833  				}),
   834  			}),
   835  			[]string{
   836  				`.names[1]: was cty.StringVal("thingy"), but now cty.StringVal("wotsit")`,
   837  			},
   838  		},
   839  		{
   840  			&configschema.Block{
   841  				Attributes: map[string]*configschema.Attribute{
   842  					"id": {
   843  						Type:     cty.String,
   844  						Computed: true,
   845  					},
   846  					"names": {
   847  						Type:     cty.List(cty.String),
   848  						Optional: true,
   849  					},
   850  				},
   851  			},
   852  			cty.ObjectVal(map[string]cty.Value{
   853  				"id": cty.UnknownVal(cty.String),
   854  				"names": cty.ListVal([]cty.Value{
   855  					cty.UnknownVal(cty.String),
   856  				}),
   857  			}),
   858  			cty.ObjectVal(map[string]cty.Value{
   859  				"id": cty.UnknownVal(cty.String),
   860  				"names": cty.ListVal([]cty.Value{
   861  					cty.StringVal("thingy"),
   862  					cty.StringVal("wotsit"),
   863  				}),
   864  			}),
   865  			[]string{
   866  				`.names: new element 1 has appeared`,
   867  			},
   868  		},
   869  
   870  		// NestingSingle blocks
   871  		{
   872  			&configschema.Block{
   873  				BlockTypes: map[string]*configschema.NestedBlock{
   874  					"key": {
   875  						Nesting: configschema.NestingSingle,
   876  						Block:   configschema.Block{},
   877  					},
   878  				},
   879  			},
   880  			cty.ObjectVal(map[string]cty.Value{
   881  				"key": cty.EmptyObjectVal,
   882  			}),
   883  			cty.ObjectVal(map[string]cty.Value{
   884  				"key": cty.EmptyObjectVal,
   885  			}),
   886  			nil,
   887  		},
   888  		{
   889  			&configschema.Block{
   890  				BlockTypes: map[string]*configschema.NestedBlock{
   891  					"key": {
   892  						Nesting: configschema.NestingSingle,
   893  						Block:   configschema.Block{},
   894  					},
   895  				},
   896  			},
   897  			cty.ObjectVal(map[string]cty.Value{
   898  				"key": cty.UnknownVal(cty.EmptyObject),
   899  			}),
   900  			cty.ObjectVal(map[string]cty.Value{
   901  				"key": cty.EmptyObjectVal,
   902  			}),
   903  			nil,
   904  		},
   905  		{
   906  			&configschema.Block{
   907  				BlockTypes: map[string]*configschema.NestedBlock{
   908  					"key": {
   909  						Nesting: configschema.NestingSingle,
   910  						Block: configschema.Block{
   911  							Attributes: map[string]*configschema.Attribute{
   912  								"foo": {
   913  									Type:     cty.String,
   914  									Optional: true,
   915  								},
   916  							},
   917  						},
   918  					},
   919  				},
   920  			},
   921  			cty.ObjectVal(map[string]cty.Value{
   922  				"key": cty.NullVal(cty.Object(map[string]cty.Type{
   923  					"foo": cty.String,
   924  				})),
   925  			}),
   926  			cty.ObjectVal(map[string]cty.Value{
   927  				"key": cty.ObjectVal(map[string]cty.Value{
   928  					"foo": cty.StringVal("hello"),
   929  				}),
   930  			}),
   931  			[]string{
   932  				`.key: was absent, but now present`,
   933  			},
   934  		},
   935  		{
   936  			&configschema.Block{
   937  				BlockTypes: map[string]*configschema.NestedBlock{
   938  					"key": {
   939  						Nesting: configschema.NestingSingle,
   940  						Block: configschema.Block{
   941  							Attributes: map[string]*configschema.Attribute{
   942  								"foo": {
   943  									Type:     cty.String,
   944  									Optional: true,
   945  								},
   946  							},
   947  						},
   948  					},
   949  				},
   950  			},
   951  			cty.ObjectVal(map[string]cty.Value{
   952  				"key": cty.ObjectVal(map[string]cty.Value{
   953  					"foo": cty.StringVal("hello"),
   954  				}),
   955  			}),
   956  			cty.ObjectVal(map[string]cty.Value{
   957  				"key": cty.NullVal(cty.Object(map[string]cty.Type{
   958  					"foo": cty.String,
   959  				})),
   960  			}),
   961  			[]string{
   962  				`.key: was present, but now absent`,
   963  			},
   964  		},
   965  		{
   966  			&configschema.Block{
   967  				BlockTypes: map[string]*configschema.NestedBlock{
   968  					"key": {
   969  						Nesting: configschema.NestingSingle,
   970  						Block: configschema.Block{
   971  							Attributes: map[string]*configschema.Attribute{
   972  								"foo": {
   973  									Type:     cty.String,
   974  									Optional: true,
   975  								},
   976  							},
   977  						},
   978  					},
   979  				},
   980  			},
   981  			cty.UnknownVal(cty.Object(map[string]cty.Type{
   982  				"key": cty.Object(map[string]cty.Type{
   983  					"foo": cty.String,
   984  				}),
   985  			})),
   986  			cty.ObjectVal(map[string]cty.Value{
   987  				"key": cty.NullVal(cty.Object(map[string]cty.Type{
   988  					"foo": cty.String,
   989  				})),
   990  			}),
   991  			nil,
   992  		},
   993  
   994  		// NestingList blocks
   995  		{
   996  			&configschema.Block{
   997  				BlockTypes: map[string]*configschema.NestedBlock{
   998  					"key": {
   999  						Nesting: configschema.NestingList,
  1000  						Block:   schemaWithFoo,
  1001  					},
  1002  				},
  1003  			},
  1004  			cty.ObjectVal(map[string]cty.Value{
  1005  				"key": cty.ListVal([]cty.Value{
  1006  					fooBlockValue,
  1007  				}),
  1008  			}),
  1009  			cty.ObjectVal(map[string]cty.Value{
  1010  				"key": cty.ListVal([]cty.Value{
  1011  					fooBlockValue,
  1012  				}),
  1013  			}),
  1014  			nil,
  1015  		},
  1016  		{
  1017  			&configschema.Block{
  1018  				BlockTypes: map[string]*configschema.NestedBlock{
  1019  					"key": {
  1020  						Nesting: configschema.NestingList,
  1021  						Block:   schemaWithFoo,
  1022  					},
  1023  				},
  1024  			},
  1025  			cty.ObjectVal(map[string]cty.Value{
  1026  				"key": cty.TupleVal([]cty.Value{
  1027  					fooBlockValue,
  1028  					fooBlockValue,
  1029  				}),
  1030  			}),
  1031  			cty.ObjectVal(map[string]cty.Value{
  1032  				"key": cty.TupleVal([]cty.Value{
  1033  					fooBlockValue,
  1034  				}),
  1035  			}),
  1036  			[]string{
  1037  				`.key: block count changed from 2 to 1`,
  1038  			},
  1039  		},
  1040  		{
  1041  			&configschema.Block{
  1042  				BlockTypes: map[string]*configschema.NestedBlock{
  1043  					"key": {
  1044  						Nesting: configschema.NestingList,
  1045  						Block:   schemaWithFoo,
  1046  					},
  1047  				},
  1048  			},
  1049  			cty.ObjectVal(map[string]cty.Value{
  1050  				"key": cty.TupleVal([]cty.Value{}),
  1051  			}),
  1052  			cty.ObjectVal(map[string]cty.Value{
  1053  				"key": cty.TupleVal([]cty.Value{
  1054  					fooBlockValue,
  1055  					fooBlockValue,
  1056  				}),
  1057  			}),
  1058  			[]string{
  1059  				`.key: block count changed from 0 to 2`,
  1060  			},
  1061  		},
  1062  		{
  1063  			&configschema.Block{
  1064  				BlockTypes: map[string]*configschema.NestedBlock{
  1065  					"key": {
  1066  						Nesting: configschema.NestingList,
  1067  						Block:   schemaWithFooBar,
  1068  					},
  1069  				},
  1070  			},
  1071  			cty.UnknownVal(cty.Object(map[string]cty.Type{
  1072  				"key": cty.List(fooBarBlockValue.Type()),
  1073  			})),
  1074  			cty.ObjectVal(map[string]cty.Value{
  1075  				"key": cty.ListVal([]cty.Value{
  1076  					cty.ObjectVal(map[string]cty.Value{
  1077  						"foo": cty.StringVal("hello"),
  1078  					}),
  1079  					cty.ObjectVal(map[string]cty.Value{
  1080  						"foo": cty.StringVal("world"),
  1081  					}),
  1082  				}),
  1083  			}),
  1084  			nil, // an unknown block is allowed to expand into multiple, because that's how dynamic blocks behave when for_each is unknown
  1085  		},
  1086  		{
  1087  			&configschema.Block{
  1088  				BlockTypes: map[string]*configschema.NestedBlock{
  1089  					"key": {
  1090  						Nesting: configschema.NestingList,
  1091  						Block:   schemaWithFooBar,
  1092  					},
  1093  				},
  1094  			},
  1095  			// While we must make an exception for empty strings in sets due to
  1096  			// the legacy SDK, lists should be compared more strictly.
  1097  			// This does not count as a dynamic block placeholder
  1098  			cty.ObjectVal(map[string]cty.Value{
  1099  				"key": cty.ListVal([]cty.Value{
  1100  					fooBarBlockValue,
  1101  					cty.ObjectVal(map[string]cty.Value{
  1102  						"foo": cty.UnknownVal(cty.String),
  1103  						"bar": cty.StringVal(""),
  1104  					}),
  1105  				}),
  1106  			}),
  1107  			cty.ObjectVal(map[string]cty.Value{
  1108  				"key": cty.ListVal([]cty.Value{
  1109  					fooBlockValue,
  1110  					cty.ObjectVal(map[string]cty.Value{
  1111  						"foo": cty.StringVal("hello"),
  1112  					}),
  1113  					cty.ObjectVal(map[string]cty.Value{
  1114  						"foo": cty.StringVal("world"),
  1115  					}),
  1116  				}),
  1117  			}),
  1118  			[]string{".key: block count changed from 2 to 3"},
  1119  		},
  1120  
  1121  		// NestingSet blocks
  1122  		{
  1123  			&configschema.Block{
  1124  				BlockTypes: map[string]*configschema.NestedBlock{
  1125  					"block": {
  1126  						Nesting: configschema.NestingSet,
  1127  						Block:   schemaWithFoo,
  1128  					},
  1129  				},
  1130  			},
  1131  			cty.ObjectVal(map[string]cty.Value{
  1132  				"block": cty.SetVal([]cty.Value{
  1133  					cty.ObjectVal(map[string]cty.Value{
  1134  						"foo": cty.StringVal("hello"),
  1135  					}),
  1136  					cty.ObjectVal(map[string]cty.Value{
  1137  						"foo": cty.StringVal("world"),
  1138  					}),
  1139  				}),
  1140  			}),
  1141  			cty.ObjectVal(map[string]cty.Value{
  1142  				"block": cty.SetVal([]cty.Value{
  1143  					cty.ObjectVal(map[string]cty.Value{
  1144  						"foo": cty.StringVal("hello"),
  1145  					}),
  1146  					cty.ObjectVal(map[string]cty.Value{
  1147  						"foo": cty.StringVal("world"),
  1148  					}),
  1149  				}),
  1150  			}),
  1151  			nil,
  1152  		},
  1153  		{
  1154  			&configschema.Block{
  1155  				BlockTypes: map[string]*configschema.NestedBlock{
  1156  					"block": {
  1157  						Nesting: configschema.NestingSet,
  1158  						Block:   schemaWithFoo,
  1159  					},
  1160  				},
  1161  			},
  1162  			cty.ObjectVal(map[string]cty.Value{
  1163  				"block": cty.SetVal([]cty.Value{
  1164  					cty.ObjectVal(map[string]cty.Value{
  1165  						"foo": cty.UnknownVal(cty.String),
  1166  					}),
  1167  					cty.ObjectVal(map[string]cty.Value{
  1168  						"foo": cty.UnknownVal(cty.String),
  1169  					}),
  1170  				}),
  1171  			}),
  1172  			cty.ObjectVal(map[string]cty.Value{
  1173  				"block": cty.SetVal([]cty.Value{
  1174  					// This is testing the scenario where the two unknown values
  1175  					// turned out to be equal after we learned their values,
  1176  					// and so they coalesced together into a single element.
  1177  					cty.ObjectVal(map[string]cty.Value{
  1178  						"foo": cty.StringVal("hello"),
  1179  					}),
  1180  				}),
  1181  			}),
  1182  			nil,
  1183  		},
  1184  		{
  1185  			&configschema.Block{
  1186  				BlockTypes: map[string]*configschema.NestedBlock{
  1187  					"block": {
  1188  						Nesting: configschema.NestingSet,
  1189  						Block:   schemaWithFoo,
  1190  					},
  1191  				},
  1192  			},
  1193  			cty.ObjectVal(map[string]cty.Value{
  1194  				"block": cty.SetVal([]cty.Value{
  1195  					cty.ObjectVal(map[string]cty.Value{
  1196  						"foo": cty.UnknownVal(cty.String),
  1197  					}),
  1198  					cty.ObjectVal(map[string]cty.Value{
  1199  						"foo": cty.UnknownVal(cty.String),
  1200  					}),
  1201  				}),
  1202  			}),
  1203  			cty.ObjectVal(map[string]cty.Value{
  1204  				"block": cty.SetVal([]cty.Value{
  1205  					cty.ObjectVal(map[string]cty.Value{
  1206  						"foo": cty.StringVal("hello"),
  1207  					}),
  1208  					cty.ObjectVal(map[string]cty.Value{
  1209  						"foo": cty.StringVal("world"),
  1210  					}),
  1211  				}),
  1212  			}),
  1213  			nil,
  1214  		},
  1215  		{
  1216  			&configschema.Block{
  1217  				BlockTypes: map[string]*configschema.NestedBlock{
  1218  					"block": {
  1219  						Nesting: configschema.NestingSet,
  1220  						Block:   schemaWithFoo,
  1221  					},
  1222  				},
  1223  			},
  1224  			cty.ObjectVal(map[string]cty.Value{
  1225  				"block": cty.UnknownVal(cty.Set(
  1226  					cty.Object(map[string]cty.Type{
  1227  						"foo": cty.String,
  1228  					}),
  1229  				)),
  1230  			}),
  1231  			cty.ObjectVal(map[string]cty.Value{
  1232  				"block": cty.SetVal([]cty.Value{
  1233  					cty.ObjectVal(map[string]cty.Value{
  1234  						"foo": cty.StringVal("hello"),
  1235  					}),
  1236  					cty.ObjectVal(map[string]cty.Value{
  1237  						"foo": cty.StringVal("world"),
  1238  					}),
  1239  					cty.ObjectVal(map[string]cty.Value{
  1240  						"foo": cty.StringVal("nope"),
  1241  					}),
  1242  				}),
  1243  			}),
  1244  			// there is no error here, because the presence of unknowns
  1245  			// indicates this may be a dynamic block, and the length is unknown
  1246  			nil,
  1247  		},
  1248  		{
  1249  			&configschema.Block{
  1250  				BlockTypes: map[string]*configschema.NestedBlock{
  1251  					"block": {
  1252  						Nesting: configschema.NestingSet,
  1253  						Block:   schemaWithFoo,
  1254  					},
  1255  				},
  1256  			},
  1257  			cty.ObjectVal(map[string]cty.Value{
  1258  				"block": cty.SetVal([]cty.Value{
  1259  					cty.ObjectVal(map[string]cty.Value{
  1260  						"foo": cty.StringVal("hello"),
  1261  					}),
  1262  					cty.ObjectVal(map[string]cty.Value{
  1263  						"foo": cty.StringVal("world"),
  1264  					}),
  1265  				}),
  1266  			}),
  1267  			cty.ObjectVal(map[string]cty.Value{
  1268  				"block": cty.SetVal([]cty.Value{
  1269  					cty.ObjectVal(map[string]cty.Value{
  1270  						"foo": cty.StringVal("howdy"),
  1271  					}),
  1272  					cty.ObjectVal(map[string]cty.Value{
  1273  						"foo": cty.StringVal("world"),
  1274  					}),
  1275  				}),
  1276  			}),
  1277  			[]string{
  1278  				`.block: planned set element cty.ObjectVal(map[string]cty.Value{"foo":cty.StringVal("hello")}) does not correlate with any element in actual`,
  1279  			},
  1280  		},
  1281  		{
  1282  			// This one is an odd situation where the value representing the
  1283  			// block itself is unknown. This is never supposed to be true,
  1284  			// but in legacy SDK mode we allow such things to pass through as
  1285  			// a warning, and so we must tolerate them for matching purposes.
  1286  			&configschema.Block{
  1287  				BlockTypes: map[string]*configschema.NestedBlock{
  1288  					"block": {
  1289  						Nesting: configschema.NestingSet,
  1290  						Block:   schemaWithFoo,
  1291  					},
  1292  				},
  1293  			},
  1294  			cty.ObjectVal(map[string]cty.Value{
  1295  				"block": cty.SetVal([]cty.Value{
  1296  					cty.ObjectVal(map[string]cty.Value{
  1297  						"foo": cty.UnknownVal(cty.String),
  1298  					}),
  1299  					cty.ObjectVal(map[string]cty.Value{
  1300  						"foo": cty.UnknownVal(cty.String),
  1301  					}),
  1302  				}),
  1303  			}),
  1304  			cty.ObjectVal(map[string]cty.Value{
  1305  				"block": cty.UnknownVal(cty.Set(cty.Object(map[string]cty.Type{
  1306  					"foo": cty.String,
  1307  				}))),
  1308  			}),
  1309  			nil,
  1310  		},
  1311  		{
  1312  			&configschema.Block{
  1313  				BlockTypes: map[string]*configschema.NestedBlock{
  1314  					"block": {
  1315  						Nesting: configschema.NestingSet,
  1316  						Block:   schemaWithFoo,
  1317  					},
  1318  				},
  1319  			},
  1320  			cty.ObjectVal(map[string]cty.Value{
  1321  				"block": cty.UnknownVal(cty.Set(fooBlockValue.Type())),
  1322  			}),
  1323  			cty.ObjectVal(map[string]cty.Value{
  1324  				"block": cty.SetVal([]cty.Value{
  1325  					cty.ObjectVal(map[string]cty.Value{
  1326  						"foo": cty.StringVal("a"),
  1327  					}),
  1328  					cty.ObjectVal(map[string]cty.Value{
  1329  						"foo": cty.StringVal("b"),
  1330  					}),
  1331  				}),
  1332  			}),
  1333  			nil,
  1334  		},
  1335  		// test a set with an unknown dynamic count going to 0 values
  1336  		{
  1337  			&configschema.Block{
  1338  				BlockTypes: map[string]*configschema.NestedBlock{
  1339  					"block2": {
  1340  						Nesting: configschema.NestingSet,
  1341  						Block:   schemaWithFoo,
  1342  					},
  1343  				},
  1344  			},
  1345  			cty.ObjectVal(map[string]cty.Value{
  1346  				"block2": cty.UnknownVal(cty.Set(fooBlockValue.Type())),
  1347  			}),
  1348  			cty.ObjectVal(map[string]cty.Value{
  1349  				"block2": cty.SetValEmpty(cty.Object(map[string]cty.Type{
  1350  					"foo": cty.String,
  1351  				})),
  1352  			}),
  1353  			nil,
  1354  		},
  1355  		// test a set with a patially known dynamic count reducing it's values
  1356  		{
  1357  			&configschema.Block{
  1358  				BlockTypes: map[string]*configschema.NestedBlock{
  1359  					"block3": {
  1360  						Nesting: configschema.NestingSet,
  1361  						Block:   schemaWithFoo,
  1362  					},
  1363  				},
  1364  			},
  1365  			cty.ObjectVal(map[string]cty.Value{
  1366  				"block3": cty.SetVal([]cty.Value{
  1367  					cty.ObjectVal(map[string]cty.Value{
  1368  						"foo": cty.StringVal("a"),
  1369  					}),
  1370  					cty.ObjectVal(map[string]cty.Value{
  1371  						"foo": cty.UnknownVal(cty.String),
  1372  					}),
  1373  				}),
  1374  			}),
  1375  			cty.ObjectVal(map[string]cty.Value{
  1376  				"block3": cty.SetVal([]cty.Value{
  1377  					cty.ObjectVal(map[string]cty.Value{
  1378  						"foo": cty.StringVal("a"),
  1379  					}),
  1380  				}),
  1381  			}),
  1382  			nil,
  1383  		},
  1384  		{
  1385  			&configschema.Block{
  1386  				BlockTypes: map[string]*configschema.NestedBlock{
  1387  					"block": {
  1388  						Nesting: configschema.NestingList,
  1389  						Block: configschema.Block{
  1390  							Attributes: map[string]*configschema.Attribute{
  1391  								"foo": {
  1392  									Type:     cty.String,
  1393  									Required: true,
  1394  								},
  1395  							},
  1396  						},
  1397  					},
  1398  				},
  1399  			},
  1400  			cty.ObjectVal(map[string]cty.Value{
  1401  				"block": cty.EmptyObjectVal,
  1402  			}),
  1403  			cty.ObjectVal(map[string]cty.Value{
  1404  				"block": cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{
  1405  					"foo": cty.String,
  1406  				}))),
  1407  			}),
  1408  			nil,
  1409  		},
  1410  	}
  1411  
  1412  	for i, test := range tests {
  1413  		t.Run(fmt.Sprintf("%02d: %#v and %#v", i, test.Planned, test.Actual), func(t *testing.T) {
  1414  			errs := AssertObjectCompatible(test.Schema, test.Planned, test.Actual)
  1415  
  1416  			wantErrs := make(map[string]struct{})
  1417  			gotErrs := make(map[string]struct{})
  1418  			for _, err := range errs {
  1419  				gotErrs[tfdiags.FormatError(err)] = struct{}{}
  1420  			}
  1421  			for _, msg := range test.WantErrs {
  1422  				wantErrs[msg] = struct{}{}
  1423  			}
  1424  
  1425  			t.Logf("\nplanned: %sactual:  %s", dump.Value(test.Planned), dump.Value(test.Actual))
  1426  			for msg := range wantErrs {
  1427  				if _, ok := gotErrs[msg]; !ok {
  1428  					t.Errorf("missing expected error: %s", msg)
  1429  				}
  1430  			}
  1431  			for msg := range gotErrs {
  1432  				if _, ok := wantErrs[msg]; !ok {
  1433  					t.Errorf("unexpected extra error: %s", msg)
  1434  				}
  1435  			}
  1436  		})
  1437  	}
  1438  }