github.com/opentofu/opentofu@v1.7.1/internal/plans/objchange/compatible_test.go (about)

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