github.com/opentofu/opentofu@v1.7.1/internal/legacy/helper/schema/schema_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 schema
     7  
     8  import (
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"reflect"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/opentofu/opentofu/internal/configs/hcl2shim"
    19  	"github.com/opentofu/opentofu/internal/legacy/helper/hashcode"
    20  	"github.com/opentofu/opentofu/internal/legacy/tofu"
    21  )
    22  
    23  func TestEnvDefaultFunc(t *testing.T) {
    24  	key := "TF_TEST_ENV_DEFAULT_FUNC"
    25  
    26  	f := EnvDefaultFunc(key, "42")
    27  
    28  	actual, err := f()
    29  	if err != nil {
    30  		t.Fatalf("err: %s", err)
    31  	}
    32  	if actual != "42" {
    33  		t.Fatalf("bad: %#v", actual)
    34  	}
    35  
    36  	t.Setenv(key, "foo")
    37  
    38  	actual, err = f()
    39  	if err != nil {
    40  		t.Fatalf("err: %s", err)
    41  	}
    42  	if actual != "foo" {
    43  		t.Fatalf("bad: %#v", actual)
    44  	}
    45  }
    46  
    47  func TestMultiEnvDefaultFunc(t *testing.T) {
    48  	keys := []string{
    49  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC1",
    50  		"TF_TEST_MULTI_ENV_DEFAULT_FUNC2",
    51  	}
    52  
    53  	const dv = "42"
    54  
    55  	t.Run("shall return the default value", func(t *testing.T) {
    56  		f := MultiEnvDefaultFunc(keys, dv)
    57  
    58  		actual, err := f()
    59  		if err != nil {
    60  			t.Fatalf("err: %s", err)
    61  		}
    62  		if actual != dv {
    63  			t.Fatalf("bad: %#v", actual)
    64  		}
    65  	})
    66  
    67  	t.Run("shall return the value of the first key", func(t *testing.T) {
    68  		f := MultiEnvDefaultFunc(keys, dv)
    69  		t.Setenv(keys[0], "foo")
    70  
    71  		actual, err := f()
    72  		if err != nil {
    73  			t.Fatalf("err: %s", err)
    74  		}
    75  		if actual != "foo" {
    76  			t.Fatalf("bad: %#v", actual)
    77  		}
    78  	})
    79  
    80  	t.Run("shall return the value of the second key", func(t *testing.T) {
    81  		f := MultiEnvDefaultFunc(keys, dv)
    82  		t.Setenv(keys[1], "bar")
    83  
    84  		actual, err := f()
    85  		if err != nil {
    86  			t.Fatalf("err: %s", err)
    87  		}
    88  		if actual != "bar" {
    89  			t.Fatalf("bad: %#v", actual)
    90  		}
    91  	})
    92  }
    93  
    94  func TestValueType_Zero(t *testing.T) {
    95  	cases := []struct {
    96  		Type  ValueType
    97  		Value interface{}
    98  	}{
    99  		{TypeBool, false},
   100  		{TypeInt, 0},
   101  		{TypeFloat, 0.0},
   102  		{TypeString, ""},
   103  		{TypeList, []interface{}{}},
   104  		{TypeMap, map[string]interface{}{}},
   105  		{TypeSet, new(Set)},
   106  	}
   107  
   108  	for i, tc := range cases {
   109  		actual := tc.Type.Zero()
   110  		if !reflect.DeepEqual(actual, tc.Value) {
   111  			t.Fatalf("%d: %#v != %#v", i, actual, tc.Value)
   112  		}
   113  	}
   114  }
   115  
   116  func TestSchemaMap_Diff(t *testing.T) {
   117  	cases := []struct {
   118  		Name          string
   119  		Schema        map[string]*Schema
   120  		State         *tofu.InstanceState
   121  		Config        map[string]interface{}
   122  		CustomizeDiff CustomizeDiffFunc
   123  		Diff          *tofu.InstanceDiff
   124  		Err           bool
   125  	}{
   126  		{
   127  			Schema: map[string]*Schema{
   128  				"availability_zone": &Schema{
   129  					Type:     TypeString,
   130  					Optional: true,
   131  					Computed: true,
   132  					ForceNew: true,
   133  				},
   134  			},
   135  
   136  			State: nil,
   137  
   138  			Config: map[string]interface{}{
   139  				"availability_zone": "foo",
   140  			},
   141  
   142  			Diff: &tofu.InstanceDiff{
   143  				Attributes: map[string]*tofu.ResourceAttrDiff{
   144  					"availability_zone": &tofu.ResourceAttrDiff{
   145  						Old:         "",
   146  						New:         "foo",
   147  						RequiresNew: true,
   148  					},
   149  				},
   150  			},
   151  
   152  			Err: false,
   153  		},
   154  
   155  		{
   156  			Schema: map[string]*Schema{
   157  				"availability_zone": &Schema{
   158  					Type:     TypeString,
   159  					Optional: true,
   160  					Computed: true,
   161  					ForceNew: true,
   162  				},
   163  			},
   164  
   165  			State: nil,
   166  
   167  			Config: map[string]interface{}{},
   168  
   169  			Diff: &tofu.InstanceDiff{
   170  				Attributes: map[string]*tofu.ResourceAttrDiff{
   171  					"availability_zone": &tofu.ResourceAttrDiff{
   172  						Old:         "",
   173  						NewComputed: true,
   174  						RequiresNew: true,
   175  					},
   176  				},
   177  			},
   178  
   179  			Err: false,
   180  		},
   181  
   182  		{
   183  			Schema: map[string]*Schema{
   184  				"availability_zone": &Schema{
   185  					Type:     TypeString,
   186  					Optional: true,
   187  					Computed: true,
   188  					ForceNew: true,
   189  				},
   190  			},
   191  
   192  			State: &tofu.InstanceState{
   193  				ID: "foo",
   194  			},
   195  
   196  			Config: map[string]interface{}{},
   197  
   198  			Diff: nil,
   199  
   200  			Err: false,
   201  		},
   202  
   203  		{
   204  			Name: "Computed, but set in config",
   205  			Schema: map[string]*Schema{
   206  				"availability_zone": &Schema{
   207  					Type:     TypeString,
   208  					Optional: true,
   209  					Computed: true,
   210  				},
   211  			},
   212  
   213  			State: &tofu.InstanceState{
   214  				Attributes: map[string]string{
   215  					"availability_zone": "foo",
   216  				},
   217  			},
   218  
   219  			Config: map[string]interface{}{
   220  				"availability_zone": "bar",
   221  			},
   222  
   223  			Diff: &tofu.InstanceDiff{
   224  				Attributes: map[string]*tofu.ResourceAttrDiff{
   225  					"availability_zone": &tofu.ResourceAttrDiff{
   226  						Old: "foo",
   227  						New: "bar",
   228  					},
   229  				},
   230  			},
   231  
   232  			Err: false,
   233  		},
   234  
   235  		{
   236  			Name: "Default",
   237  			Schema: map[string]*Schema{
   238  				"availability_zone": &Schema{
   239  					Type:     TypeString,
   240  					Optional: true,
   241  					Default:  "foo",
   242  				},
   243  			},
   244  
   245  			State: nil,
   246  
   247  			Config: nil,
   248  
   249  			Diff: &tofu.InstanceDiff{
   250  				Attributes: map[string]*tofu.ResourceAttrDiff{
   251  					"availability_zone": &tofu.ResourceAttrDiff{
   252  						Old: "",
   253  						New: "foo",
   254  					},
   255  				},
   256  			},
   257  
   258  			Err: false,
   259  		},
   260  
   261  		{
   262  			Name: "DefaultFunc, value",
   263  			Schema: map[string]*Schema{
   264  				"availability_zone": &Schema{
   265  					Type:     TypeString,
   266  					Optional: true,
   267  					DefaultFunc: func() (interface{}, error) {
   268  						return "foo", nil
   269  					},
   270  				},
   271  			},
   272  
   273  			State: nil,
   274  
   275  			Config: nil,
   276  
   277  			Diff: &tofu.InstanceDiff{
   278  				Attributes: map[string]*tofu.ResourceAttrDiff{
   279  					"availability_zone": &tofu.ResourceAttrDiff{
   280  						Old: "",
   281  						New: "foo",
   282  					},
   283  				},
   284  			},
   285  
   286  			Err: false,
   287  		},
   288  
   289  		{
   290  			Name: "DefaultFunc, configuration set",
   291  			Schema: map[string]*Schema{
   292  				"availability_zone": &Schema{
   293  					Type:     TypeString,
   294  					Optional: true,
   295  					DefaultFunc: func() (interface{}, error) {
   296  						return "foo", nil
   297  					},
   298  				},
   299  			},
   300  
   301  			State: nil,
   302  
   303  			Config: map[string]interface{}{
   304  				"availability_zone": "bar",
   305  			},
   306  
   307  			Diff: &tofu.InstanceDiff{
   308  				Attributes: map[string]*tofu.ResourceAttrDiff{
   309  					"availability_zone": &tofu.ResourceAttrDiff{
   310  						Old: "",
   311  						New: "bar",
   312  					},
   313  				},
   314  			},
   315  
   316  			Err: false,
   317  		},
   318  
   319  		{
   320  			Name: "String with StateFunc",
   321  			Schema: map[string]*Schema{
   322  				"availability_zone": &Schema{
   323  					Type:     TypeString,
   324  					Optional: true,
   325  					Computed: true,
   326  					StateFunc: func(a interface{}) string {
   327  						return a.(string) + "!"
   328  					},
   329  				},
   330  			},
   331  
   332  			State: nil,
   333  
   334  			Config: map[string]interface{}{
   335  				"availability_zone": "foo",
   336  			},
   337  
   338  			Diff: &tofu.InstanceDiff{
   339  				Attributes: map[string]*tofu.ResourceAttrDiff{
   340  					"availability_zone": &tofu.ResourceAttrDiff{
   341  						Old:      "",
   342  						New:      "foo!",
   343  						NewExtra: "foo",
   344  					},
   345  				},
   346  			},
   347  
   348  			Err: false,
   349  		},
   350  
   351  		{
   352  			Name: "StateFunc not called with nil value",
   353  			Schema: map[string]*Schema{
   354  				"availability_zone": &Schema{
   355  					Type:     TypeString,
   356  					Optional: true,
   357  					Computed: true,
   358  					StateFunc: func(a interface{}) string {
   359  						t.Fatalf("should not get here!")
   360  						return ""
   361  					},
   362  				},
   363  			},
   364  
   365  			State: nil,
   366  
   367  			Config: map[string]interface{}{},
   368  
   369  			Diff: &tofu.InstanceDiff{
   370  				Attributes: map[string]*tofu.ResourceAttrDiff{
   371  					"availability_zone": &tofu.ResourceAttrDiff{
   372  						Old:         "",
   373  						New:         "",
   374  						NewComputed: true,
   375  					},
   376  				},
   377  			},
   378  
   379  			Err: false,
   380  		},
   381  
   382  		{
   383  			Name: "Variable computed",
   384  			Schema: map[string]*Schema{
   385  				"availability_zone": &Schema{
   386  					Type:     TypeString,
   387  					Optional: true,
   388  				},
   389  			},
   390  
   391  			State: nil,
   392  
   393  			Config: map[string]interface{}{
   394  				"availability_zone": hcl2shim.UnknownVariableValue,
   395  			},
   396  
   397  			Diff: &tofu.InstanceDiff{
   398  				Attributes: map[string]*tofu.ResourceAttrDiff{
   399  					"availability_zone": &tofu.ResourceAttrDiff{
   400  						Old:         "",
   401  						New:         hcl2shim.UnknownVariableValue,
   402  						NewComputed: true,
   403  					},
   404  				},
   405  			},
   406  
   407  			Err: false,
   408  		},
   409  
   410  		{
   411  			Name: "Int decode",
   412  			Schema: map[string]*Schema{
   413  				"port": &Schema{
   414  					Type:     TypeInt,
   415  					Optional: true,
   416  					Computed: true,
   417  					ForceNew: true,
   418  				},
   419  			},
   420  
   421  			State: nil,
   422  
   423  			Config: map[string]interface{}{
   424  				"port": 27,
   425  			},
   426  
   427  			Diff: &tofu.InstanceDiff{
   428  				Attributes: map[string]*tofu.ResourceAttrDiff{
   429  					"port": &tofu.ResourceAttrDiff{
   430  						Old:         "",
   431  						New:         "27",
   432  						RequiresNew: true,
   433  					},
   434  				},
   435  			},
   436  
   437  			Err: false,
   438  		},
   439  
   440  		{
   441  			Name: "bool decode",
   442  			Schema: map[string]*Schema{
   443  				"port": &Schema{
   444  					Type:     TypeBool,
   445  					Optional: true,
   446  					Computed: true,
   447  					ForceNew: true,
   448  				},
   449  			},
   450  
   451  			State: nil,
   452  
   453  			Config: map[string]interface{}{
   454  				"port": false,
   455  			},
   456  
   457  			Diff: &tofu.InstanceDiff{
   458  				Attributes: map[string]*tofu.ResourceAttrDiff{
   459  					"port": &tofu.ResourceAttrDiff{
   460  						Old:         "",
   461  						New:         "false",
   462  						RequiresNew: true,
   463  					},
   464  				},
   465  			},
   466  
   467  			Err: false,
   468  		},
   469  
   470  		{
   471  			Name: "Bool",
   472  			Schema: map[string]*Schema{
   473  				"delete": &Schema{
   474  					Type:     TypeBool,
   475  					Optional: true,
   476  					Default:  false,
   477  				},
   478  			},
   479  
   480  			State: &tofu.InstanceState{
   481  				Attributes: map[string]string{
   482  					"delete": "false",
   483  				},
   484  			},
   485  
   486  			Config: nil,
   487  
   488  			Diff: nil,
   489  
   490  			Err: false,
   491  		},
   492  
   493  		{
   494  			Name: "List decode",
   495  			Schema: map[string]*Schema{
   496  				"ports": &Schema{
   497  					Type:     TypeList,
   498  					Required: true,
   499  					Elem:     &Schema{Type: TypeInt},
   500  				},
   501  			},
   502  
   503  			State: nil,
   504  
   505  			Config: map[string]interface{}{
   506  				"ports": []interface{}{1, 2, 5},
   507  			},
   508  
   509  			Diff: &tofu.InstanceDiff{
   510  				Attributes: map[string]*tofu.ResourceAttrDiff{
   511  					"ports.#": &tofu.ResourceAttrDiff{
   512  						Old: "0",
   513  						New: "3",
   514  					},
   515  					"ports.0": &tofu.ResourceAttrDiff{
   516  						Old: "",
   517  						New: "1",
   518  					},
   519  					"ports.1": &tofu.ResourceAttrDiff{
   520  						Old: "",
   521  						New: "2",
   522  					},
   523  					"ports.2": &tofu.ResourceAttrDiff{
   524  						Old: "",
   525  						New: "5",
   526  					},
   527  				},
   528  			},
   529  
   530  			Err: false,
   531  		},
   532  
   533  		{
   534  			Name: "List decode with promotion",
   535  			Schema: map[string]*Schema{
   536  				"ports": &Schema{
   537  					Type:          TypeList,
   538  					Required:      true,
   539  					Elem:          &Schema{Type: TypeInt},
   540  					PromoteSingle: true,
   541  				},
   542  			},
   543  
   544  			State: nil,
   545  
   546  			Config: map[string]interface{}{
   547  				"ports": "5",
   548  			},
   549  
   550  			Diff: &tofu.InstanceDiff{
   551  				Attributes: map[string]*tofu.ResourceAttrDiff{
   552  					"ports.#": &tofu.ResourceAttrDiff{
   553  						Old: "0",
   554  						New: "1",
   555  					},
   556  					"ports.0": &tofu.ResourceAttrDiff{
   557  						Old: "",
   558  						New: "5",
   559  					},
   560  				},
   561  			},
   562  
   563  			Err: false,
   564  		},
   565  
   566  		{
   567  			Name: "List decode with promotion with list",
   568  			Schema: map[string]*Schema{
   569  				"ports": &Schema{
   570  					Type:          TypeList,
   571  					Required:      true,
   572  					Elem:          &Schema{Type: TypeInt},
   573  					PromoteSingle: true,
   574  				},
   575  			},
   576  
   577  			State: nil,
   578  
   579  			Config: map[string]interface{}{
   580  				"ports": []interface{}{"5"},
   581  			},
   582  
   583  			Diff: &tofu.InstanceDiff{
   584  				Attributes: map[string]*tofu.ResourceAttrDiff{
   585  					"ports.#": &tofu.ResourceAttrDiff{
   586  						Old: "0",
   587  						New: "1",
   588  					},
   589  					"ports.0": &tofu.ResourceAttrDiff{
   590  						Old: "",
   591  						New: "5",
   592  					},
   593  				},
   594  			},
   595  
   596  			Err: false,
   597  		},
   598  
   599  		{
   600  			Schema: map[string]*Schema{
   601  				"ports": &Schema{
   602  					Type:     TypeList,
   603  					Required: true,
   604  					Elem:     &Schema{Type: TypeInt},
   605  				},
   606  			},
   607  
   608  			State: nil,
   609  
   610  			Config: map[string]interface{}{
   611  				"ports": []interface{}{1, 2, 5},
   612  			},
   613  
   614  			Diff: &tofu.InstanceDiff{
   615  				Attributes: map[string]*tofu.ResourceAttrDiff{
   616  					"ports.#": &tofu.ResourceAttrDiff{
   617  						Old: "0",
   618  						New: "3",
   619  					},
   620  					"ports.0": &tofu.ResourceAttrDiff{
   621  						Old: "",
   622  						New: "1",
   623  					},
   624  					"ports.1": &tofu.ResourceAttrDiff{
   625  						Old: "",
   626  						New: "2",
   627  					},
   628  					"ports.2": &tofu.ResourceAttrDiff{
   629  						Old: "",
   630  						New: "5",
   631  					},
   632  				},
   633  			},
   634  
   635  			Err: false,
   636  		},
   637  
   638  		{
   639  			Schema: map[string]*Schema{
   640  				"ports": &Schema{
   641  					Type:     TypeList,
   642  					Required: true,
   643  					Elem:     &Schema{Type: TypeInt},
   644  				},
   645  			},
   646  
   647  			State: nil,
   648  
   649  			Config: map[string]interface{}{
   650  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5},
   651  			},
   652  
   653  			Diff: &tofu.InstanceDiff{
   654  				Attributes: map[string]*tofu.ResourceAttrDiff{
   655  					"ports.#": &tofu.ResourceAttrDiff{
   656  						Old:         "0",
   657  						New:         "",
   658  						NewComputed: true,
   659  					},
   660  				},
   661  			},
   662  
   663  			Err: false,
   664  		},
   665  
   666  		{
   667  			Schema: map[string]*Schema{
   668  				"ports": &Schema{
   669  					Type:     TypeList,
   670  					Required: true,
   671  					Elem:     &Schema{Type: TypeInt},
   672  				},
   673  			},
   674  
   675  			State: &tofu.InstanceState{
   676  				Attributes: map[string]string{
   677  					"ports.#": "3",
   678  					"ports.0": "1",
   679  					"ports.1": "2",
   680  					"ports.2": "5",
   681  				},
   682  			},
   683  
   684  			Config: map[string]interface{}{
   685  				"ports": []interface{}{1, 2, 5},
   686  			},
   687  
   688  			Diff: nil,
   689  
   690  			Err: false,
   691  		},
   692  
   693  		{
   694  			Name: "",
   695  			Schema: map[string]*Schema{
   696  				"ports": &Schema{
   697  					Type:     TypeList,
   698  					Required: true,
   699  					Elem:     &Schema{Type: TypeInt},
   700  				},
   701  			},
   702  
   703  			State: &tofu.InstanceState{
   704  				Attributes: map[string]string{
   705  					"ports.#": "2",
   706  					"ports.0": "1",
   707  					"ports.1": "2",
   708  				},
   709  			},
   710  
   711  			Config: map[string]interface{}{
   712  				"ports": []interface{}{1, 2, 5},
   713  			},
   714  
   715  			Diff: &tofu.InstanceDiff{
   716  				Attributes: map[string]*tofu.ResourceAttrDiff{
   717  					"ports.#": &tofu.ResourceAttrDiff{
   718  						Old: "2",
   719  						New: "3",
   720  					},
   721  					"ports.2": &tofu.ResourceAttrDiff{
   722  						Old: "",
   723  						New: "5",
   724  					},
   725  				},
   726  			},
   727  
   728  			Err: false,
   729  		},
   730  
   731  		{
   732  			Name: "",
   733  			Schema: map[string]*Schema{
   734  				"ports": &Schema{
   735  					Type:     TypeList,
   736  					Required: true,
   737  					Elem:     &Schema{Type: TypeInt},
   738  					ForceNew: true,
   739  				},
   740  			},
   741  
   742  			State: nil,
   743  
   744  			Config: map[string]interface{}{
   745  				"ports": []interface{}{1, 2, 5},
   746  			},
   747  
   748  			Diff: &tofu.InstanceDiff{
   749  				Attributes: map[string]*tofu.ResourceAttrDiff{
   750  					"ports.#": &tofu.ResourceAttrDiff{
   751  						Old:         "0",
   752  						New:         "3",
   753  						RequiresNew: true,
   754  					},
   755  					"ports.0": &tofu.ResourceAttrDiff{
   756  						Old:         "",
   757  						New:         "1",
   758  						RequiresNew: true,
   759  					},
   760  					"ports.1": &tofu.ResourceAttrDiff{
   761  						Old:         "",
   762  						New:         "2",
   763  						RequiresNew: true,
   764  					},
   765  					"ports.2": &tofu.ResourceAttrDiff{
   766  						Old:         "",
   767  						New:         "5",
   768  						RequiresNew: true,
   769  					},
   770  				},
   771  			},
   772  
   773  			Err: false,
   774  		},
   775  
   776  		{
   777  			Name: "",
   778  			Schema: map[string]*Schema{
   779  				"ports": &Schema{
   780  					Type:     TypeList,
   781  					Optional: true,
   782  					Computed: true,
   783  					Elem:     &Schema{Type: TypeInt},
   784  				},
   785  			},
   786  
   787  			State: nil,
   788  
   789  			Config: map[string]interface{}{},
   790  
   791  			Diff: &tofu.InstanceDiff{
   792  				Attributes: map[string]*tofu.ResourceAttrDiff{
   793  					"ports.#": &tofu.ResourceAttrDiff{
   794  						Old:         "",
   795  						NewComputed: true,
   796  					},
   797  				},
   798  			},
   799  
   800  			Err: false,
   801  		},
   802  
   803  		{
   804  			Name: "List with computed set",
   805  			Schema: map[string]*Schema{
   806  				"config": &Schema{
   807  					Type:     TypeList,
   808  					Optional: true,
   809  					ForceNew: true,
   810  					MinItems: 1,
   811  					Elem: &Resource{
   812  						Schema: map[string]*Schema{
   813  							"name": {
   814  								Type:     TypeString,
   815  								Required: true,
   816  							},
   817  
   818  							"rules": {
   819  								Type:     TypeSet,
   820  								Computed: true,
   821  								Elem:     &Schema{Type: TypeString},
   822  								Set:      HashString,
   823  							},
   824  						},
   825  					},
   826  				},
   827  			},
   828  
   829  			State: nil,
   830  
   831  			Config: map[string]interface{}{
   832  				"config": []interface{}{
   833  					map[string]interface{}{
   834  						"name": "hello",
   835  					},
   836  				},
   837  			},
   838  
   839  			Diff: &tofu.InstanceDiff{
   840  				Attributes: map[string]*tofu.ResourceAttrDiff{
   841  					"config.#": &tofu.ResourceAttrDiff{
   842  						Old:         "0",
   843  						New:         "1",
   844  						RequiresNew: true,
   845  					},
   846  
   847  					"config.0.name": &tofu.ResourceAttrDiff{
   848  						Old: "",
   849  						New: "hello",
   850  					},
   851  
   852  					"config.0.rules.#": &tofu.ResourceAttrDiff{
   853  						Old:         "",
   854  						NewComputed: true,
   855  					},
   856  				},
   857  			},
   858  
   859  			Err: false,
   860  		},
   861  
   862  		{
   863  			Name: "Set",
   864  			Schema: map[string]*Schema{
   865  				"ports": &Schema{
   866  					Type:     TypeSet,
   867  					Required: true,
   868  					Elem:     &Schema{Type: TypeInt},
   869  					Set: func(a interface{}) int {
   870  						return a.(int)
   871  					},
   872  				},
   873  			},
   874  
   875  			State: nil,
   876  
   877  			Config: map[string]interface{}{
   878  				"ports": []interface{}{5, 2, 1},
   879  			},
   880  
   881  			Diff: &tofu.InstanceDiff{
   882  				Attributes: map[string]*tofu.ResourceAttrDiff{
   883  					"ports.#": &tofu.ResourceAttrDiff{
   884  						Old: "0",
   885  						New: "3",
   886  					},
   887  					"ports.1": &tofu.ResourceAttrDiff{
   888  						Old: "",
   889  						New: "1",
   890  					},
   891  					"ports.2": &tofu.ResourceAttrDiff{
   892  						Old: "",
   893  						New: "2",
   894  					},
   895  					"ports.5": &tofu.ResourceAttrDiff{
   896  						Old: "",
   897  						New: "5",
   898  					},
   899  				},
   900  			},
   901  
   902  			Err: false,
   903  		},
   904  
   905  		{
   906  			Name: "Set",
   907  			Schema: map[string]*Schema{
   908  				"ports": &Schema{
   909  					Type:     TypeSet,
   910  					Computed: true,
   911  					Required: true,
   912  					Elem:     &Schema{Type: TypeInt},
   913  					Set: func(a interface{}) int {
   914  						return a.(int)
   915  					},
   916  				},
   917  			},
   918  
   919  			State: &tofu.InstanceState{
   920  				Attributes: map[string]string{
   921  					"ports.#": "0",
   922  				},
   923  			},
   924  
   925  			Config: nil,
   926  
   927  			Diff: nil,
   928  
   929  			Err: false,
   930  		},
   931  
   932  		{
   933  			Name: "Set",
   934  			Schema: map[string]*Schema{
   935  				"ports": &Schema{
   936  					Type:     TypeSet,
   937  					Optional: true,
   938  					Computed: true,
   939  					Elem:     &Schema{Type: TypeInt},
   940  					Set: func(a interface{}) int {
   941  						return a.(int)
   942  					},
   943  				},
   944  			},
   945  
   946  			State: nil,
   947  
   948  			Config: nil,
   949  
   950  			Diff: &tofu.InstanceDiff{
   951  				Attributes: map[string]*tofu.ResourceAttrDiff{
   952  					"ports.#": &tofu.ResourceAttrDiff{
   953  						Old:         "",
   954  						NewComputed: true,
   955  					},
   956  				},
   957  			},
   958  
   959  			Err: false,
   960  		},
   961  
   962  		{
   963  			Name: "Set",
   964  			Schema: map[string]*Schema{
   965  				"ports": &Schema{
   966  					Type:     TypeSet,
   967  					Required: true,
   968  					Elem:     &Schema{Type: TypeInt},
   969  					Set: func(a interface{}) int {
   970  						return a.(int)
   971  					},
   972  				},
   973  			},
   974  
   975  			State: nil,
   976  
   977  			Config: map[string]interface{}{
   978  				"ports": []interface{}{"2", "5", 1},
   979  			},
   980  
   981  			Diff: &tofu.InstanceDiff{
   982  				Attributes: map[string]*tofu.ResourceAttrDiff{
   983  					"ports.#": &tofu.ResourceAttrDiff{
   984  						Old: "0",
   985  						New: "3",
   986  					},
   987  					"ports.1": &tofu.ResourceAttrDiff{
   988  						Old: "",
   989  						New: "1",
   990  					},
   991  					"ports.2": &tofu.ResourceAttrDiff{
   992  						Old: "",
   993  						New: "2",
   994  					},
   995  					"ports.5": &tofu.ResourceAttrDiff{
   996  						Old: "",
   997  						New: "5",
   998  					},
   999  				},
  1000  			},
  1001  
  1002  			Err: false,
  1003  		},
  1004  
  1005  		{
  1006  			Name: "Set",
  1007  			Schema: map[string]*Schema{
  1008  				"ports": &Schema{
  1009  					Type:     TypeSet,
  1010  					Required: true,
  1011  					Elem:     &Schema{Type: TypeInt},
  1012  					Set: func(a interface{}) int {
  1013  						return a.(int)
  1014  					},
  1015  				},
  1016  			},
  1017  
  1018  			State: nil,
  1019  
  1020  			Config: map[string]interface{}{
  1021  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"},
  1022  			},
  1023  
  1024  			Diff: &tofu.InstanceDiff{
  1025  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1026  					"ports.#": &tofu.ResourceAttrDiff{
  1027  						Old:         "",
  1028  						New:         "",
  1029  						NewComputed: true,
  1030  					},
  1031  				},
  1032  			},
  1033  
  1034  			Err: false,
  1035  		},
  1036  
  1037  		{
  1038  			Name: "Set",
  1039  			Schema: map[string]*Schema{
  1040  				"ports": &Schema{
  1041  					Type:     TypeSet,
  1042  					Required: true,
  1043  					Elem:     &Schema{Type: TypeInt},
  1044  					Set: func(a interface{}) int {
  1045  						return a.(int)
  1046  					},
  1047  				},
  1048  			},
  1049  
  1050  			State: &tofu.InstanceState{
  1051  				Attributes: map[string]string{
  1052  					"ports.#": "2",
  1053  					"ports.1": "1",
  1054  					"ports.2": "2",
  1055  				},
  1056  			},
  1057  
  1058  			Config: map[string]interface{}{
  1059  				"ports": []interface{}{5, 2, 1},
  1060  			},
  1061  
  1062  			Diff: &tofu.InstanceDiff{
  1063  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1064  					"ports.#": &tofu.ResourceAttrDiff{
  1065  						Old: "2",
  1066  						New: "3",
  1067  					},
  1068  					"ports.1": &tofu.ResourceAttrDiff{
  1069  						Old: "1",
  1070  						New: "1",
  1071  					},
  1072  					"ports.2": &tofu.ResourceAttrDiff{
  1073  						Old: "2",
  1074  						New: "2",
  1075  					},
  1076  					"ports.5": &tofu.ResourceAttrDiff{
  1077  						Old: "",
  1078  						New: "5",
  1079  					},
  1080  				},
  1081  			},
  1082  
  1083  			Err: false,
  1084  		},
  1085  
  1086  		{
  1087  			Name: "Set",
  1088  			Schema: map[string]*Schema{
  1089  				"ports": &Schema{
  1090  					Type:     TypeSet,
  1091  					Required: true,
  1092  					Elem:     &Schema{Type: TypeInt},
  1093  					Set: func(a interface{}) int {
  1094  						return a.(int)
  1095  					},
  1096  				},
  1097  			},
  1098  
  1099  			State: &tofu.InstanceState{
  1100  				Attributes: map[string]string{
  1101  					"ports.#": "2",
  1102  					"ports.1": "1",
  1103  					"ports.2": "2",
  1104  				},
  1105  			},
  1106  
  1107  			Config: map[string]interface{}{},
  1108  
  1109  			Diff: &tofu.InstanceDiff{
  1110  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1111  					"ports.#": &tofu.ResourceAttrDiff{
  1112  						Old: "2",
  1113  						New: "0",
  1114  					},
  1115  					"ports.1": &tofu.ResourceAttrDiff{
  1116  						Old:        "1",
  1117  						New:        "0",
  1118  						NewRemoved: true,
  1119  					},
  1120  					"ports.2": &tofu.ResourceAttrDiff{
  1121  						Old:        "2",
  1122  						New:        "0",
  1123  						NewRemoved: true,
  1124  					},
  1125  				},
  1126  			},
  1127  
  1128  			Err: false,
  1129  		},
  1130  
  1131  		{
  1132  			Name: "Set",
  1133  			Schema: map[string]*Schema{
  1134  				"ports": &Schema{
  1135  					Type:     TypeSet,
  1136  					Optional: true,
  1137  					Computed: true,
  1138  					Elem:     &Schema{Type: TypeInt},
  1139  					Set: func(a interface{}) int {
  1140  						return a.(int)
  1141  					},
  1142  				},
  1143  			},
  1144  
  1145  			State: &tofu.InstanceState{
  1146  				Attributes: map[string]string{
  1147  					"availability_zone": "bar",
  1148  					"ports.#":           "1",
  1149  					"ports.80":          "80",
  1150  				},
  1151  			},
  1152  
  1153  			Config: map[string]interface{}{},
  1154  
  1155  			Diff: nil,
  1156  
  1157  			Err: false,
  1158  		},
  1159  
  1160  		{
  1161  			Name: "Set",
  1162  			Schema: map[string]*Schema{
  1163  				"ingress": &Schema{
  1164  					Type:     TypeSet,
  1165  					Required: true,
  1166  					Elem: &Resource{
  1167  						Schema: map[string]*Schema{
  1168  							"ports": &Schema{
  1169  								Type:     TypeList,
  1170  								Optional: true,
  1171  								Elem:     &Schema{Type: TypeInt},
  1172  							},
  1173  						},
  1174  					},
  1175  					Set: func(v interface{}) int {
  1176  						m := v.(map[string]interface{})
  1177  						ps := m["ports"].([]interface{})
  1178  						result := 0
  1179  						for _, p := range ps {
  1180  							result += p.(int)
  1181  						}
  1182  						return result
  1183  					},
  1184  				},
  1185  			},
  1186  
  1187  			State: &tofu.InstanceState{
  1188  				Attributes: map[string]string{
  1189  					"ingress.#":           "2",
  1190  					"ingress.80.ports.#":  "1",
  1191  					"ingress.80.ports.0":  "80",
  1192  					"ingress.443.ports.#": "1",
  1193  					"ingress.443.ports.0": "443",
  1194  				},
  1195  			},
  1196  
  1197  			Config: map[string]interface{}{
  1198  				"ingress": []interface{}{
  1199  					map[string]interface{}{
  1200  						"ports": []interface{}{443},
  1201  					},
  1202  					map[string]interface{}{
  1203  						"ports": []interface{}{80},
  1204  					},
  1205  				},
  1206  			},
  1207  
  1208  			Diff: nil,
  1209  
  1210  			Err: false,
  1211  		},
  1212  
  1213  		{
  1214  			Name: "List of structure decode",
  1215  			Schema: map[string]*Schema{
  1216  				"ingress": &Schema{
  1217  					Type:     TypeList,
  1218  					Required: true,
  1219  					Elem: &Resource{
  1220  						Schema: map[string]*Schema{
  1221  							"from": &Schema{
  1222  								Type:     TypeInt,
  1223  								Required: true,
  1224  							},
  1225  						},
  1226  					},
  1227  				},
  1228  			},
  1229  
  1230  			State: nil,
  1231  
  1232  			Config: map[string]interface{}{
  1233  				"ingress": []interface{}{
  1234  					map[string]interface{}{
  1235  						"from": 8080,
  1236  					},
  1237  				},
  1238  			},
  1239  
  1240  			Diff: &tofu.InstanceDiff{
  1241  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1242  					"ingress.#": &tofu.ResourceAttrDiff{
  1243  						Old: "0",
  1244  						New: "1",
  1245  					},
  1246  					"ingress.0.from": &tofu.ResourceAttrDiff{
  1247  						Old: "",
  1248  						New: "8080",
  1249  					},
  1250  				},
  1251  			},
  1252  
  1253  			Err: false,
  1254  		},
  1255  
  1256  		{
  1257  			Name: "ComputedWhen",
  1258  			Schema: map[string]*Schema{
  1259  				"availability_zone": &Schema{
  1260  					Type:         TypeString,
  1261  					Computed:     true,
  1262  					ComputedWhen: []string{"port"},
  1263  				},
  1264  
  1265  				"port": &Schema{
  1266  					Type:     TypeInt,
  1267  					Optional: true,
  1268  				},
  1269  			},
  1270  
  1271  			State: &tofu.InstanceState{
  1272  				Attributes: map[string]string{
  1273  					"availability_zone": "foo",
  1274  					"port":              "80",
  1275  				},
  1276  			},
  1277  
  1278  			Config: map[string]interface{}{
  1279  				"port": 80,
  1280  			},
  1281  
  1282  			Diff: nil,
  1283  
  1284  			Err: false,
  1285  		},
  1286  
  1287  		{
  1288  			Name: "",
  1289  			Schema: map[string]*Schema{
  1290  				"availability_zone": &Schema{
  1291  					Type:         TypeString,
  1292  					Computed:     true,
  1293  					ComputedWhen: []string{"port"},
  1294  				},
  1295  
  1296  				"port": &Schema{
  1297  					Type:     TypeInt,
  1298  					Optional: true,
  1299  				},
  1300  			},
  1301  
  1302  			State: &tofu.InstanceState{
  1303  				Attributes: map[string]string{
  1304  					"port": "80",
  1305  				},
  1306  			},
  1307  
  1308  			Config: map[string]interface{}{
  1309  				"port": 80,
  1310  			},
  1311  
  1312  			Diff: &tofu.InstanceDiff{
  1313  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1314  					"availability_zone": &tofu.ResourceAttrDiff{
  1315  						NewComputed: true,
  1316  					},
  1317  				},
  1318  			},
  1319  
  1320  			Err: false,
  1321  		},
  1322  
  1323  		/* TODO
  1324  		{
  1325  			Schema: map[string]*Schema{
  1326  				"availability_zone": &Schema{
  1327  					Type:         TypeString,
  1328  					Computed:     true,
  1329  					ComputedWhen: []string{"port"},
  1330  				},
  1331  
  1332  				"port": &Schema{
  1333  					Type:     TypeInt,
  1334  					Optional: true,
  1335  				},
  1336  			},
  1337  
  1338  			State: &terraform.InstanceState{
  1339  				Attributes: map[string]string{
  1340  					"availability_zone": "foo",
  1341  					"port":              "80",
  1342  				},
  1343  			},
  1344  
  1345  			Config: map[string]interface{}{
  1346  				"port": 8080,
  1347  			},
  1348  
  1349  			Diff: &terraform.ResourceDiff{
  1350  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1351  					"availability_zone": &terraform.ResourceAttrDiff{
  1352  						Old:         "foo",
  1353  						NewComputed: true,
  1354  					},
  1355  					"port": &terraform.ResourceAttrDiff{
  1356  						Old: "80",
  1357  						New: "8080",
  1358  					},
  1359  				},
  1360  			},
  1361  
  1362  			Err: false,
  1363  		},
  1364  		*/
  1365  
  1366  		{
  1367  			Name: "Maps",
  1368  			Schema: map[string]*Schema{
  1369  				"config_vars": &Schema{
  1370  					Type: TypeMap,
  1371  				},
  1372  			},
  1373  
  1374  			State: nil,
  1375  
  1376  			Config: map[string]interface{}{
  1377  				"config_vars": []interface{}{
  1378  					map[string]interface{}{
  1379  						"bar": "baz",
  1380  					},
  1381  				},
  1382  			},
  1383  
  1384  			Diff: &tofu.InstanceDiff{
  1385  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1386  					"config_vars.%": &tofu.ResourceAttrDiff{
  1387  						Old: "0",
  1388  						New: "1",
  1389  					},
  1390  
  1391  					"config_vars.bar": &tofu.ResourceAttrDiff{
  1392  						Old: "",
  1393  						New: "baz",
  1394  					},
  1395  				},
  1396  			},
  1397  
  1398  			Err: false,
  1399  		},
  1400  
  1401  		{
  1402  			Name: "Maps",
  1403  			Schema: map[string]*Schema{
  1404  				"config_vars": &Schema{
  1405  					Type: TypeMap,
  1406  				},
  1407  			},
  1408  
  1409  			State: &tofu.InstanceState{
  1410  				Attributes: map[string]string{
  1411  					"config_vars.foo": "bar",
  1412  				},
  1413  			},
  1414  
  1415  			Config: map[string]interface{}{
  1416  				"config_vars": []interface{}{
  1417  					map[string]interface{}{
  1418  						"bar": "baz",
  1419  					},
  1420  				},
  1421  			},
  1422  
  1423  			Diff: &tofu.InstanceDiff{
  1424  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1425  					"config_vars.foo": &tofu.ResourceAttrDiff{
  1426  						Old:        "bar",
  1427  						NewRemoved: true,
  1428  					},
  1429  					"config_vars.bar": &tofu.ResourceAttrDiff{
  1430  						Old: "",
  1431  						New: "baz",
  1432  					},
  1433  				},
  1434  			},
  1435  
  1436  			Err: false,
  1437  		},
  1438  
  1439  		{
  1440  			Name: "Maps",
  1441  			Schema: map[string]*Schema{
  1442  				"vars": &Schema{
  1443  					Type:     TypeMap,
  1444  					Optional: true,
  1445  					Computed: true,
  1446  				},
  1447  			},
  1448  
  1449  			State: &tofu.InstanceState{
  1450  				Attributes: map[string]string{
  1451  					"vars.foo": "bar",
  1452  				},
  1453  			},
  1454  
  1455  			Config: map[string]interface{}{
  1456  				"vars": []interface{}{
  1457  					map[string]interface{}{
  1458  						"bar": "baz",
  1459  					},
  1460  				},
  1461  			},
  1462  
  1463  			Diff: &tofu.InstanceDiff{
  1464  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1465  					"vars.foo": &tofu.ResourceAttrDiff{
  1466  						Old:        "bar",
  1467  						New:        "",
  1468  						NewRemoved: true,
  1469  					},
  1470  					"vars.bar": &tofu.ResourceAttrDiff{
  1471  						Old: "",
  1472  						New: "baz",
  1473  					},
  1474  				},
  1475  			},
  1476  
  1477  			Err: false,
  1478  		},
  1479  
  1480  		{
  1481  			Name: "Maps",
  1482  			Schema: map[string]*Schema{
  1483  				"vars": &Schema{
  1484  					Type:     TypeMap,
  1485  					Computed: true,
  1486  				},
  1487  			},
  1488  
  1489  			State: &tofu.InstanceState{
  1490  				Attributes: map[string]string{
  1491  					"vars.foo": "bar",
  1492  				},
  1493  			},
  1494  
  1495  			Config: nil,
  1496  
  1497  			Diff: nil,
  1498  
  1499  			Err: false,
  1500  		},
  1501  
  1502  		{
  1503  			Name: "Maps",
  1504  			Schema: map[string]*Schema{
  1505  				"config_vars": &Schema{
  1506  					Type: TypeList,
  1507  					Elem: &Schema{Type: TypeMap},
  1508  				},
  1509  			},
  1510  
  1511  			State: &tofu.InstanceState{
  1512  				Attributes: map[string]string{
  1513  					"config_vars.#":     "1",
  1514  					"config_vars.0.foo": "bar",
  1515  				},
  1516  			},
  1517  
  1518  			Config: map[string]interface{}{
  1519  				"config_vars": []interface{}{
  1520  					map[string]interface{}{
  1521  						"bar": "baz",
  1522  					},
  1523  				},
  1524  			},
  1525  
  1526  			Diff: &tofu.InstanceDiff{
  1527  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1528  					"config_vars.0.foo": &tofu.ResourceAttrDiff{
  1529  						Old:        "bar",
  1530  						NewRemoved: true,
  1531  					},
  1532  					"config_vars.0.bar": &tofu.ResourceAttrDiff{
  1533  						Old: "",
  1534  						New: "baz",
  1535  					},
  1536  				},
  1537  			},
  1538  
  1539  			Err: false,
  1540  		},
  1541  
  1542  		{
  1543  			Name: "Maps",
  1544  			Schema: map[string]*Schema{
  1545  				"config_vars": &Schema{
  1546  					Type: TypeList,
  1547  					Elem: &Schema{Type: TypeMap},
  1548  				},
  1549  			},
  1550  
  1551  			State: &tofu.InstanceState{
  1552  				Attributes: map[string]string{
  1553  					"config_vars.#":     "1",
  1554  					"config_vars.0.foo": "bar",
  1555  					"config_vars.0.bar": "baz",
  1556  				},
  1557  			},
  1558  
  1559  			Config: map[string]interface{}{},
  1560  
  1561  			Diff: &tofu.InstanceDiff{
  1562  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1563  					"config_vars.#": &tofu.ResourceAttrDiff{
  1564  						Old: "1",
  1565  						New: "0",
  1566  					},
  1567  					"config_vars.0.%": &tofu.ResourceAttrDiff{
  1568  						Old: "2",
  1569  						New: "0",
  1570  					},
  1571  					"config_vars.0.foo": &tofu.ResourceAttrDiff{
  1572  						Old:        "bar",
  1573  						NewRemoved: true,
  1574  					},
  1575  					"config_vars.0.bar": &tofu.ResourceAttrDiff{
  1576  						Old:        "baz",
  1577  						NewRemoved: true,
  1578  					},
  1579  				},
  1580  			},
  1581  
  1582  			Err: false,
  1583  		},
  1584  
  1585  		{
  1586  			Name: "ForceNews",
  1587  			Schema: map[string]*Schema{
  1588  				"availability_zone": &Schema{
  1589  					Type:     TypeString,
  1590  					Optional: true,
  1591  					ForceNew: true,
  1592  				},
  1593  
  1594  				"address": &Schema{
  1595  					Type:     TypeString,
  1596  					Optional: true,
  1597  					Computed: true,
  1598  				},
  1599  			},
  1600  
  1601  			State: &tofu.InstanceState{
  1602  				Attributes: map[string]string{
  1603  					"availability_zone": "bar",
  1604  					"address":           "foo",
  1605  				},
  1606  			},
  1607  
  1608  			Config: map[string]interface{}{
  1609  				"availability_zone": "foo",
  1610  			},
  1611  
  1612  			Diff: &tofu.InstanceDiff{
  1613  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1614  					"availability_zone": &tofu.ResourceAttrDiff{
  1615  						Old:         "bar",
  1616  						New:         "foo",
  1617  						RequiresNew: true,
  1618  					},
  1619  
  1620  					"address": &tofu.ResourceAttrDiff{
  1621  						Old:         "foo",
  1622  						New:         "",
  1623  						NewComputed: true,
  1624  					},
  1625  				},
  1626  			},
  1627  
  1628  			Err: false,
  1629  		},
  1630  
  1631  		{
  1632  			Name: "Set",
  1633  			Schema: map[string]*Schema{
  1634  				"availability_zone": &Schema{
  1635  					Type:     TypeString,
  1636  					Optional: true,
  1637  					ForceNew: true,
  1638  				},
  1639  
  1640  				"ports": &Schema{
  1641  					Type:     TypeSet,
  1642  					Optional: true,
  1643  					Computed: true,
  1644  					Elem:     &Schema{Type: TypeInt},
  1645  					Set: func(a interface{}) int {
  1646  						return a.(int)
  1647  					},
  1648  				},
  1649  			},
  1650  
  1651  			State: &tofu.InstanceState{
  1652  				Attributes: map[string]string{
  1653  					"availability_zone": "bar",
  1654  					"ports.#":           "1",
  1655  					"ports.80":          "80",
  1656  				},
  1657  			},
  1658  
  1659  			Config: map[string]interface{}{
  1660  				"availability_zone": "foo",
  1661  			},
  1662  
  1663  			Diff: &tofu.InstanceDiff{
  1664  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1665  					"availability_zone": &tofu.ResourceAttrDiff{
  1666  						Old:         "bar",
  1667  						New:         "foo",
  1668  						RequiresNew: true,
  1669  					},
  1670  
  1671  					"ports.#": &tofu.ResourceAttrDiff{
  1672  						Old:         "1",
  1673  						New:         "",
  1674  						NewComputed: true,
  1675  					},
  1676  				},
  1677  			},
  1678  
  1679  			Err: false,
  1680  		},
  1681  
  1682  		{
  1683  			Name: "Set",
  1684  			Schema: map[string]*Schema{
  1685  				"instances": &Schema{
  1686  					Type:     TypeSet,
  1687  					Elem:     &Schema{Type: TypeString},
  1688  					Optional: true,
  1689  					Computed: true,
  1690  					Set: func(v interface{}) int {
  1691  						return len(v.(string))
  1692  					},
  1693  				},
  1694  			},
  1695  
  1696  			State: &tofu.InstanceState{
  1697  				Attributes: map[string]string{
  1698  					"instances.#": "0",
  1699  				},
  1700  			},
  1701  
  1702  			Config: map[string]interface{}{
  1703  				"instances": []interface{}{hcl2shim.UnknownVariableValue},
  1704  			},
  1705  
  1706  			Diff: &tofu.InstanceDiff{
  1707  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1708  					"instances.#": &tofu.ResourceAttrDiff{
  1709  						NewComputed: true,
  1710  					},
  1711  				},
  1712  			},
  1713  
  1714  			Err: false,
  1715  		},
  1716  
  1717  		{
  1718  			Name: "Set",
  1719  			Schema: map[string]*Schema{
  1720  				"route": &Schema{
  1721  					Type:     TypeSet,
  1722  					Optional: true,
  1723  					Elem: &Resource{
  1724  						Schema: map[string]*Schema{
  1725  							"index": &Schema{
  1726  								Type:     TypeInt,
  1727  								Required: true,
  1728  							},
  1729  
  1730  							"gateway": &Schema{
  1731  								Type:     TypeString,
  1732  								Optional: true,
  1733  							},
  1734  						},
  1735  					},
  1736  					Set: func(v interface{}) int {
  1737  						m := v.(map[string]interface{})
  1738  						return m["index"].(int)
  1739  					},
  1740  				},
  1741  			},
  1742  
  1743  			State: nil,
  1744  
  1745  			Config: map[string]interface{}{
  1746  				"route": []interface{}{
  1747  					map[string]interface{}{
  1748  						"index":   "1",
  1749  						"gateway": hcl2shim.UnknownVariableValue,
  1750  					},
  1751  				},
  1752  			},
  1753  
  1754  			Diff: &tofu.InstanceDiff{
  1755  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1756  					"route.#": &tofu.ResourceAttrDiff{
  1757  						Old: "0",
  1758  						New: "1",
  1759  					},
  1760  					"route.~1.index": &tofu.ResourceAttrDiff{
  1761  						Old: "",
  1762  						New: "1",
  1763  					},
  1764  					"route.~1.gateway": &tofu.ResourceAttrDiff{
  1765  						Old:         "",
  1766  						New:         hcl2shim.UnknownVariableValue,
  1767  						NewComputed: true,
  1768  					},
  1769  				},
  1770  			},
  1771  
  1772  			Err: false,
  1773  		},
  1774  
  1775  		{
  1776  			Name: "Set",
  1777  			Schema: map[string]*Schema{
  1778  				"route": &Schema{
  1779  					Type:     TypeSet,
  1780  					Optional: true,
  1781  					Elem: &Resource{
  1782  						Schema: map[string]*Schema{
  1783  							"index": &Schema{
  1784  								Type:     TypeInt,
  1785  								Required: true,
  1786  							},
  1787  
  1788  							"gateway": &Schema{
  1789  								Type:     TypeSet,
  1790  								Optional: true,
  1791  								Elem:     &Schema{Type: TypeInt},
  1792  								Set: func(a interface{}) int {
  1793  									return a.(int)
  1794  								},
  1795  							},
  1796  						},
  1797  					},
  1798  					Set: func(v interface{}) int {
  1799  						m := v.(map[string]interface{})
  1800  						return m["index"].(int)
  1801  					},
  1802  				},
  1803  			},
  1804  
  1805  			State: nil,
  1806  
  1807  			Config: map[string]interface{}{
  1808  				"route": []interface{}{
  1809  					map[string]interface{}{
  1810  						"index": "1",
  1811  						"gateway": []interface{}{
  1812  							hcl2shim.UnknownVariableValue,
  1813  						},
  1814  					},
  1815  				},
  1816  			},
  1817  
  1818  			Diff: &tofu.InstanceDiff{
  1819  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1820  					"route.#": &tofu.ResourceAttrDiff{
  1821  						Old: "0",
  1822  						New: "1",
  1823  					},
  1824  					"route.~1.index": &tofu.ResourceAttrDiff{
  1825  						Old: "",
  1826  						New: "1",
  1827  					},
  1828  					"route.~1.gateway.#": &tofu.ResourceAttrDiff{
  1829  						NewComputed: true,
  1830  					},
  1831  				},
  1832  			},
  1833  
  1834  			Err: false,
  1835  		},
  1836  
  1837  		{
  1838  			Name: "Computed maps",
  1839  			Schema: map[string]*Schema{
  1840  				"vars": &Schema{
  1841  					Type:     TypeMap,
  1842  					Computed: true,
  1843  				},
  1844  			},
  1845  
  1846  			State: nil,
  1847  
  1848  			Config: nil,
  1849  
  1850  			Diff: &tofu.InstanceDiff{
  1851  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1852  					"vars.%": &tofu.ResourceAttrDiff{
  1853  						Old:         "",
  1854  						NewComputed: true,
  1855  					},
  1856  				},
  1857  			},
  1858  
  1859  			Err: false,
  1860  		},
  1861  
  1862  		{
  1863  			Name: "Computed maps",
  1864  			Schema: map[string]*Schema{
  1865  				"vars": &Schema{
  1866  					Type:     TypeMap,
  1867  					Computed: true,
  1868  				},
  1869  			},
  1870  
  1871  			State: &tofu.InstanceState{
  1872  				Attributes: map[string]string{
  1873  					"vars.%": "0",
  1874  				},
  1875  			},
  1876  
  1877  			Config: map[string]interface{}{
  1878  				"vars": map[string]interface{}{
  1879  					"bar": hcl2shim.UnknownVariableValue,
  1880  				},
  1881  			},
  1882  
  1883  			Diff: &tofu.InstanceDiff{
  1884  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1885  					"vars.%": &tofu.ResourceAttrDiff{
  1886  						Old:         "",
  1887  						NewComputed: true,
  1888  					},
  1889  				},
  1890  			},
  1891  
  1892  			Err: false,
  1893  		},
  1894  
  1895  		{
  1896  			Name:   " - Empty",
  1897  			Schema: map[string]*Schema{},
  1898  
  1899  			State: &tofu.InstanceState{},
  1900  
  1901  			Config: map[string]interface{}{},
  1902  
  1903  			Diff: nil,
  1904  
  1905  			Err: false,
  1906  		},
  1907  
  1908  		{
  1909  			Name: "Float",
  1910  			Schema: map[string]*Schema{
  1911  				"some_threshold": &Schema{
  1912  					Type: TypeFloat,
  1913  				},
  1914  			},
  1915  
  1916  			State: &tofu.InstanceState{
  1917  				Attributes: map[string]string{
  1918  					"some_threshold": "567.8",
  1919  				},
  1920  			},
  1921  
  1922  			Config: map[string]interface{}{
  1923  				"some_threshold": 12.34,
  1924  			},
  1925  
  1926  			Diff: &tofu.InstanceDiff{
  1927  				Attributes: map[string]*tofu.ResourceAttrDiff{
  1928  					"some_threshold": &tofu.ResourceAttrDiff{
  1929  						Old: "567.8",
  1930  						New: "12.34",
  1931  					},
  1932  				},
  1933  			},
  1934  
  1935  			Err: false,
  1936  		},
  1937  
  1938  		{
  1939  			Name: "https://github.com/hashicorp/terraform/issues/824",
  1940  			Schema: map[string]*Schema{
  1941  				"block_device": &Schema{
  1942  					Type:     TypeSet,
  1943  					Optional: true,
  1944  					Computed: true,
  1945  					Elem: &Resource{
  1946  						Schema: map[string]*Schema{
  1947  							"device_name": &Schema{
  1948  								Type:     TypeString,
  1949  								Required: true,
  1950  							},
  1951  							"delete_on_termination": &Schema{
  1952  								Type:     TypeBool,
  1953  								Optional: true,
  1954  								Default:  true,
  1955  							},
  1956  						},
  1957  					},
  1958  					Set: func(v interface{}) int {
  1959  						var buf bytes.Buffer
  1960  						m := v.(map[string]interface{})
  1961  						buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
  1962  						buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
  1963  						return hashcode.String(buf.String())
  1964  					},
  1965  				},
  1966  			},
  1967  
  1968  			State: &tofu.InstanceState{
  1969  				Attributes: map[string]string{
  1970  					"block_device.#": "2",
  1971  					"block_device.616397234.delete_on_termination":  "true",
  1972  					"block_device.616397234.device_name":            "/dev/sda1",
  1973  					"block_device.2801811477.delete_on_termination": "true",
  1974  					"block_device.2801811477.device_name":           "/dev/sdx",
  1975  				},
  1976  			},
  1977  
  1978  			Config: map[string]interface{}{
  1979  				"block_device": []interface{}{
  1980  					map[string]interface{}{
  1981  						"device_name": "/dev/sda1",
  1982  					},
  1983  					map[string]interface{}{
  1984  						"device_name": "/dev/sdx",
  1985  					},
  1986  				},
  1987  			},
  1988  			Diff: nil,
  1989  			Err:  false,
  1990  		},
  1991  
  1992  		{
  1993  			Name: "Zero value in state shouldn't result in diff",
  1994  			Schema: map[string]*Schema{
  1995  				"port": &Schema{
  1996  					Type:     TypeBool,
  1997  					Optional: true,
  1998  					ForceNew: true,
  1999  				},
  2000  			},
  2001  
  2002  			State: &tofu.InstanceState{
  2003  				Attributes: map[string]string{
  2004  					"port": "false",
  2005  				},
  2006  			},
  2007  
  2008  			Config: map[string]interface{}{},
  2009  
  2010  			Diff: nil,
  2011  
  2012  			Err: false,
  2013  		},
  2014  
  2015  		{
  2016  			Name: "Same as prev, but for sets",
  2017  			Schema: map[string]*Schema{
  2018  				"route": &Schema{
  2019  					Type:     TypeSet,
  2020  					Optional: true,
  2021  					Elem: &Resource{
  2022  						Schema: map[string]*Schema{
  2023  							"index": &Schema{
  2024  								Type:     TypeInt,
  2025  								Required: true,
  2026  							},
  2027  
  2028  							"gateway": &Schema{
  2029  								Type:     TypeSet,
  2030  								Optional: true,
  2031  								Elem:     &Schema{Type: TypeInt},
  2032  								Set: func(a interface{}) int {
  2033  									return a.(int)
  2034  								},
  2035  							},
  2036  						},
  2037  					},
  2038  					Set: func(v interface{}) int {
  2039  						m := v.(map[string]interface{})
  2040  						return m["index"].(int)
  2041  					},
  2042  				},
  2043  			},
  2044  
  2045  			State: &tofu.InstanceState{
  2046  				Attributes: map[string]string{
  2047  					"route.#": "0",
  2048  				},
  2049  			},
  2050  
  2051  			Config: map[string]interface{}{},
  2052  
  2053  			Diff: nil,
  2054  
  2055  			Err: false,
  2056  		},
  2057  
  2058  		{
  2059  			Name: "A set computed element shouldn't cause a diff",
  2060  			Schema: map[string]*Schema{
  2061  				"active": &Schema{
  2062  					Type:     TypeBool,
  2063  					Computed: true,
  2064  					ForceNew: true,
  2065  				},
  2066  			},
  2067  
  2068  			State: &tofu.InstanceState{
  2069  				Attributes: map[string]string{
  2070  					"active": "true",
  2071  				},
  2072  			},
  2073  
  2074  			Config: map[string]interface{}{},
  2075  
  2076  			Diff: nil,
  2077  
  2078  			Err: false,
  2079  		},
  2080  
  2081  		{
  2082  			Name: "An empty set should show up in the diff",
  2083  			Schema: map[string]*Schema{
  2084  				"instances": &Schema{
  2085  					Type:     TypeSet,
  2086  					Elem:     &Schema{Type: TypeString},
  2087  					Optional: true,
  2088  					ForceNew: true,
  2089  					Set: func(v interface{}) int {
  2090  						return len(v.(string))
  2091  					},
  2092  				},
  2093  			},
  2094  
  2095  			State: &tofu.InstanceState{
  2096  				Attributes: map[string]string{
  2097  					"instances.#": "1",
  2098  					"instances.3": "foo",
  2099  				},
  2100  			},
  2101  
  2102  			Config: map[string]interface{}{},
  2103  
  2104  			Diff: &tofu.InstanceDiff{
  2105  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2106  					"instances.#": &tofu.ResourceAttrDiff{
  2107  						Old:         "1",
  2108  						New:         "0",
  2109  						RequiresNew: true,
  2110  					},
  2111  					"instances.3": &tofu.ResourceAttrDiff{
  2112  						Old:         "foo",
  2113  						New:         "",
  2114  						NewRemoved:  true,
  2115  						RequiresNew: true,
  2116  					},
  2117  				},
  2118  			},
  2119  
  2120  			Err: false,
  2121  		},
  2122  
  2123  		{
  2124  			Name: "Map with empty value",
  2125  			Schema: map[string]*Schema{
  2126  				"vars": &Schema{
  2127  					Type: TypeMap,
  2128  				},
  2129  			},
  2130  
  2131  			State: nil,
  2132  
  2133  			Config: map[string]interface{}{
  2134  				"vars": map[string]interface{}{
  2135  					"foo": "",
  2136  				},
  2137  			},
  2138  
  2139  			Diff: &tofu.InstanceDiff{
  2140  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2141  					"vars.%": &tofu.ResourceAttrDiff{
  2142  						Old: "0",
  2143  						New: "1",
  2144  					},
  2145  					"vars.foo": &tofu.ResourceAttrDiff{
  2146  						Old: "",
  2147  						New: "",
  2148  					},
  2149  				},
  2150  			},
  2151  
  2152  			Err: false,
  2153  		},
  2154  
  2155  		{
  2156  			Name: "Unset bool, not in state",
  2157  			Schema: map[string]*Schema{
  2158  				"force": &Schema{
  2159  					Type:     TypeBool,
  2160  					Optional: true,
  2161  					ForceNew: true,
  2162  				},
  2163  			},
  2164  
  2165  			State: nil,
  2166  
  2167  			Config: map[string]interface{}{},
  2168  
  2169  			Diff: nil,
  2170  
  2171  			Err: false,
  2172  		},
  2173  
  2174  		{
  2175  			Name: "Unset set, not in state",
  2176  			Schema: map[string]*Schema{
  2177  				"metadata_keys": &Schema{
  2178  					Type:     TypeSet,
  2179  					Optional: true,
  2180  					ForceNew: true,
  2181  					Elem:     &Schema{Type: TypeInt},
  2182  					Set:      func(interface{}) int { return 0 },
  2183  				},
  2184  			},
  2185  
  2186  			State: nil,
  2187  
  2188  			Config: map[string]interface{}{},
  2189  
  2190  			Diff: nil,
  2191  
  2192  			Err: false,
  2193  		},
  2194  
  2195  		{
  2196  			Name: "Unset list in state, should not show up computed",
  2197  			Schema: map[string]*Schema{
  2198  				"metadata_keys": &Schema{
  2199  					Type:     TypeList,
  2200  					Optional: true,
  2201  					Computed: true,
  2202  					ForceNew: true,
  2203  					Elem:     &Schema{Type: TypeInt},
  2204  				},
  2205  			},
  2206  
  2207  			State: &tofu.InstanceState{
  2208  				Attributes: map[string]string{
  2209  					"metadata_keys.#": "0",
  2210  				},
  2211  			},
  2212  
  2213  			Config: map[string]interface{}{},
  2214  
  2215  			Diff: nil,
  2216  
  2217  			Err: false,
  2218  		},
  2219  
  2220  		{
  2221  			Name: "Set element computed element",
  2222  			Schema: map[string]*Schema{
  2223  				"ports": &Schema{
  2224  					Type:     TypeSet,
  2225  					Required: true,
  2226  					Elem:     &Schema{Type: TypeInt},
  2227  					Set: func(a interface{}) int {
  2228  						return a.(int)
  2229  					},
  2230  				},
  2231  			},
  2232  
  2233  			State: nil,
  2234  
  2235  			Config: map[string]interface{}{
  2236  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue},
  2237  			},
  2238  
  2239  			Diff: &tofu.InstanceDiff{
  2240  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2241  					"ports.#": &tofu.ResourceAttrDiff{
  2242  						Old:         "",
  2243  						New:         "",
  2244  						NewComputed: true,
  2245  					},
  2246  				},
  2247  			},
  2248  
  2249  			Err: false,
  2250  		},
  2251  
  2252  		{
  2253  			Name: "Computed map without config that's known to be empty does not generate diff",
  2254  			Schema: map[string]*Schema{
  2255  				"tags": &Schema{
  2256  					Type:     TypeMap,
  2257  					Computed: true,
  2258  				},
  2259  			},
  2260  
  2261  			Config: nil,
  2262  
  2263  			State: &tofu.InstanceState{
  2264  				Attributes: map[string]string{
  2265  					"tags.%": "0",
  2266  				},
  2267  			},
  2268  
  2269  			Diff: nil,
  2270  
  2271  			Err: false,
  2272  		},
  2273  
  2274  		{
  2275  			Name: "Set with hyphen keys",
  2276  			Schema: map[string]*Schema{
  2277  				"route": &Schema{
  2278  					Type:     TypeSet,
  2279  					Optional: true,
  2280  					Elem: &Resource{
  2281  						Schema: map[string]*Schema{
  2282  							"index": &Schema{
  2283  								Type:     TypeInt,
  2284  								Required: true,
  2285  							},
  2286  
  2287  							"gateway-name": &Schema{
  2288  								Type:     TypeString,
  2289  								Optional: true,
  2290  							},
  2291  						},
  2292  					},
  2293  					Set: func(v interface{}) int {
  2294  						m := v.(map[string]interface{})
  2295  						return m["index"].(int)
  2296  					},
  2297  				},
  2298  			},
  2299  
  2300  			State: nil,
  2301  
  2302  			Config: map[string]interface{}{
  2303  				"route": []interface{}{
  2304  					map[string]interface{}{
  2305  						"index":        "1",
  2306  						"gateway-name": "hello",
  2307  					},
  2308  				},
  2309  			},
  2310  
  2311  			Diff: &tofu.InstanceDiff{
  2312  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2313  					"route.#": &tofu.ResourceAttrDiff{
  2314  						Old: "0",
  2315  						New: "1",
  2316  					},
  2317  					"route.1.index": &tofu.ResourceAttrDiff{
  2318  						Old: "",
  2319  						New: "1",
  2320  					},
  2321  					"route.1.gateway-name": &tofu.ResourceAttrDiff{
  2322  						Old: "",
  2323  						New: "hello",
  2324  					},
  2325  				},
  2326  			},
  2327  
  2328  			Err: false,
  2329  		},
  2330  
  2331  		{
  2332  			Name: ": StateFunc in nested set (#1759)",
  2333  			Schema: map[string]*Schema{
  2334  				"service_account": &Schema{
  2335  					Type:     TypeList,
  2336  					Optional: true,
  2337  					ForceNew: true,
  2338  					Elem: &Resource{
  2339  						Schema: map[string]*Schema{
  2340  							"scopes": &Schema{
  2341  								Type:     TypeSet,
  2342  								Required: true,
  2343  								ForceNew: true,
  2344  								Elem: &Schema{
  2345  									Type: TypeString,
  2346  									StateFunc: func(v interface{}) string {
  2347  										return v.(string) + "!"
  2348  									},
  2349  								},
  2350  								Set: func(v interface{}) int {
  2351  									i, err := strconv.Atoi(v.(string))
  2352  									if err != nil {
  2353  										t.Fatalf("err: %s", err)
  2354  									}
  2355  									return i
  2356  								},
  2357  							},
  2358  						},
  2359  					},
  2360  				},
  2361  			},
  2362  
  2363  			State: nil,
  2364  
  2365  			Config: map[string]interface{}{
  2366  				"service_account": []interface{}{
  2367  					map[string]interface{}{
  2368  						"scopes": []interface{}{"123"},
  2369  					},
  2370  				},
  2371  			},
  2372  
  2373  			Diff: &tofu.InstanceDiff{
  2374  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2375  					"service_account.#": &tofu.ResourceAttrDiff{
  2376  						Old:         "0",
  2377  						New:         "1",
  2378  						RequiresNew: true,
  2379  					},
  2380  					"service_account.0.scopes.#": &tofu.ResourceAttrDiff{
  2381  						Old:         "0",
  2382  						New:         "1",
  2383  						RequiresNew: true,
  2384  					},
  2385  					"service_account.0.scopes.123": &tofu.ResourceAttrDiff{
  2386  						Old:         "",
  2387  						New:         "123!",
  2388  						NewExtra:    "123",
  2389  						RequiresNew: true,
  2390  					},
  2391  				},
  2392  			},
  2393  
  2394  			Err: false,
  2395  		},
  2396  
  2397  		{
  2398  			Name: "Removing set elements",
  2399  			Schema: map[string]*Schema{
  2400  				"instances": &Schema{
  2401  					Type:     TypeSet,
  2402  					Elem:     &Schema{Type: TypeString},
  2403  					Optional: true,
  2404  					ForceNew: true,
  2405  					Set: func(v interface{}) int {
  2406  						return len(v.(string))
  2407  					},
  2408  				},
  2409  			},
  2410  
  2411  			State: &tofu.InstanceState{
  2412  				Attributes: map[string]string{
  2413  					"instances.#": "2",
  2414  					"instances.3": "333",
  2415  					"instances.2": "22",
  2416  				},
  2417  			},
  2418  
  2419  			Config: map[string]interface{}{
  2420  				"instances": []interface{}{"333", "4444"},
  2421  			},
  2422  
  2423  			Diff: &tofu.InstanceDiff{
  2424  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2425  					"instances.#": &tofu.ResourceAttrDiff{
  2426  						Old: "2",
  2427  						New: "2",
  2428  					},
  2429  					"instances.2": &tofu.ResourceAttrDiff{
  2430  						Old:         "22",
  2431  						New:         "",
  2432  						NewRemoved:  true,
  2433  						RequiresNew: true,
  2434  					},
  2435  					"instances.3": &tofu.ResourceAttrDiff{
  2436  						Old: "333",
  2437  						New: "333",
  2438  					},
  2439  					"instances.4": &tofu.ResourceAttrDiff{
  2440  						Old:         "",
  2441  						New:         "4444",
  2442  						RequiresNew: true,
  2443  					},
  2444  				},
  2445  			},
  2446  
  2447  			Err: false,
  2448  		},
  2449  
  2450  		{
  2451  			Name: "Bools can be set with 0/1 in config, still get true/false",
  2452  			Schema: map[string]*Schema{
  2453  				"one": &Schema{
  2454  					Type:     TypeBool,
  2455  					Optional: true,
  2456  				},
  2457  				"two": &Schema{
  2458  					Type:     TypeBool,
  2459  					Optional: true,
  2460  				},
  2461  				"three": &Schema{
  2462  					Type:     TypeBool,
  2463  					Optional: true,
  2464  				},
  2465  			},
  2466  
  2467  			State: &tofu.InstanceState{
  2468  				Attributes: map[string]string{
  2469  					"one":   "false",
  2470  					"two":   "true",
  2471  					"three": "true",
  2472  				},
  2473  			},
  2474  
  2475  			Config: map[string]interface{}{
  2476  				"one": "1",
  2477  				"two": "0",
  2478  			},
  2479  
  2480  			Diff: &tofu.InstanceDiff{
  2481  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2482  					"one": &tofu.ResourceAttrDiff{
  2483  						Old: "false",
  2484  						New: "true",
  2485  					},
  2486  					"two": &tofu.ResourceAttrDiff{
  2487  						Old: "true",
  2488  						New: "false",
  2489  					},
  2490  					"three": &tofu.ResourceAttrDiff{
  2491  						Old:        "true",
  2492  						New:        "false",
  2493  						NewRemoved: true,
  2494  					},
  2495  				},
  2496  			},
  2497  
  2498  			Err: false,
  2499  		},
  2500  
  2501  		{
  2502  			Name:   "tainted in state w/ no attr changes is still a replacement",
  2503  			Schema: map[string]*Schema{},
  2504  
  2505  			State: &tofu.InstanceState{
  2506  				Attributes: map[string]string{
  2507  					"id": "someid",
  2508  				},
  2509  				Tainted: true,
  2510  			},
  2511  
  2512  			Config: map[string]interface{}{},
  2513  
  2514  			Diff: &tofu.InstanceDiff{
  2515  				Attributes:     map[string]*tofu.ResourceAttrDiff{},
  2516  				DestroyTainted: true,
  2517  			},
  2518  
  2519  			Err: false,
  2520  		},
  2521  
  2522  		{
  2523  			Name: "Set ForceNew only marks the changing element as ForceNew",
  2524  			Schema: map[string]*Schema{
  2525  				"ports": &Schema{
  2526  					Type:     TypeSet,
  2527  					Required: true,
  2528  					ForceNew: true,
  2529  					Elem:     &Schema{Type: TypeInt},
  2530  					Set: func(a interface{}) int {
  2531  						return a.(int)
  2532  					},
  2533  				},
  2534  			},
  2535  
  2536  			State: &tofu.InstanceState{
  2537  				Attributes: map[string]string{
  2538  					"ports.#": "3",
  2539  					"ports.1": "1",
  2540  					"ports.2": "2",
  2541  					"ports.4": "4",
  2542  				},
  2543  			},
  2544  
  2545  			Config: map[string]interface{}{
  2546  				"ports": []interface{}{5, 2, 1},
  2547  			},
  2548  
  2549  			Diff: &tofu.InstanceDiff{
  2550  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2551  					"ports.#": &tofu.ResourceAttrDiff{
  2552  						Old: "3",
  2553  						New: "3",
  2554  					},
  2555  					"ports.1": &tofu.ResourceAttrDiff{
  2556  						Old: "1",
  2557  						New: "1",
  2558  					},
  2559  					"ports.2": &tofu.ResourceAttrDiff{
  2560  						Old: "2",
  2561  						New: "2",
  2562  					},
  2563  					"ports.5": &tofu.ResourceAttrDiff{
  2564  						Old:         "",
  2565  						New:         "5",
  2566  						RequiresNew: true,
  2567  					},
  2568  					"ports.4": &tofu.ResourceAttrDiff{
  2569  						Old:         "4",
  2570  						New:         "0",
  2571  						NewRemoved:  true,
  2572  						RequiresNew: true,
  2573  					},
  2574  				},
  2575  			},
  2576  		},
  2577  
  2578  		{
  2579  			Name: "removed optional items should trigger ForceNew",
  2580  			Schema: map[string]*Schema{
  2581  				"description": &Schema{
  2582  					Type:     TypeString,
  2583  					ForceNew: true,
  2584  					Optional: true,
  2585  				},
  2586  			},
  2587  
  2588  			State: &tofu.InstanceState{
  2589  				Attributes: map[string]string{
  2590  					"description": "foo",
  2591  				},
  2592  			},
  2593  
  2594  			Config: map[string]interface{}{},
  2595  
  2596  			Diff: &tofu.InstanceDiff{
  2597  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2598  					"description": &tofu.ResourceAttrDiff{
  2599  						Old:         "foo",
  2600  						New:         "",
  2601  						RequiresNew: true,
  2602  						NewRemoved:  true,
  2603  					},
  2604  				},
  2605  			},
  2606  
  2607  			Err: false,
  2608  		},
  2609  
  2610  		// GH-7715
  2611  		{
  2612  			Name: "computed value for boolean field",
  2613  			Schema: map[string]*Schema{
  2614  				"foo": &Schema{
  2615  					Type:     TypeBool,
  2616  					ForceNew: true,
  2617  					Computed: true,
  2618  					Optional: true,
  2619  				},
  2620  			},
  2621  
  2622  			State: &tofu.InstanceState{},
  2623  
  2624  			Config: map[string]interface{}{
  2625  				"foo": hcl2shim.UnknownVariableValue,
  2626  			},
  2627  
  2628  			Diff: &tofu.InstanceDiff{
  2629  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2630  					"foo": &tofu.ResourceAttrDiff{
  2631  						Old:         "",
  2632  						New:         "false",
  2633  						NewComputed: true,
  2634  						RequiresNew: true,
  2635  					},
  2636  				},
  2637  			},
  2638  
  2639  			Err: false,
  2640  		},
  2641  
  2642  		{
  2643  			Name: "Set ForceNew marks count as ForceNew if computed",
  2644  			Schema: map[string]*Schema{
  2645  				"ports": &Schema{
  2646  					Type:     TypeSet,
  2647  					Required: true,
  2648  					ForceNew: true,
  2649  					Elem:     &Schema{Type: TypeInt},
  2650  					Set: func(a interface{}) int {
  2651  						return a.(int)
  2652  					},
  2653  				},
  2654  			},
  2655  
  2656  			State: &tofu.InstanceState{
  2657  				Attributes: map[string]string{
  2658  					"ports.#": "3",
  2659  					"ports.1": "1",
  2660  					"ports.2": "2",
  2661  					"ports.4": "4",
  2662  				},
  2663  			},
  2664  
  2665  			Config: map[string]interface{}{
  2666  				"ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1},
  2667  			},
  2668  
  2669  			Diff: &tofu.InstanceDiff{
  2670  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2671  					"ports.#": &tofu.ResourceAttrDiff{
  2672  						Old:         "3",
  2673  						New:         "",
  2674  						NewComputed: true,
  2675  						RequiresNew: true,
  2676  					},
  2677  				},
  2678  			},
  2679  		},
  2680  
  2681  		{
  2682  			Name: "List with computed schema and ForceNew",
  2683  			Schema: map[string]*Schema{
  2684  				"config": &Schema{
  2685  					Type:     TypeList,
  2686  					Optional: true,
  2687  					ForceNew: true,
  2688  					Elem: &Schema{
  2689  						Type: TypeString,
  2690  					},
  2691  				},
  2692  			},
  2693  
  2694  			State: &tofu.InstanceState{
  2695  				Attributes: map[string]string{
  2696  					"config.#": "2",
  2697  					"config.0": "a",
  2698  					"config.1": "b",
  2699  				},
  2700  			},
  2701  
  2702  			Config: map[string]interface{}{
  2703  				"config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue},
  2704  			},
  2705  
  2706  			Diff: &tofu.InstanceDiff{
  2707  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2708  					"config.#": &tofu.ResourceAttrDiff{
  2709  						Old:         "2",
  2710  						New:         "",
  2711  						RequiresNew: true,
  2712  						NewComputed: true,
  2713  					},
  2714  				},
  2715  			},
  2716  
  2717  			Err: false,
  2718  		},
  2719  
  2720  		{
  2721  			Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema",
  2722  			Schema: map[string]*Schema{
  2723  				"availability_zone": &Schema{
  2724  					Type:     TypeString,
  2725  					Optional: true,
  2726  					Computed: true,
  2727  				},
  2728  			},
  2729  
  2730  			State: nil,
  2731  
  2732  			Config: map[string]interface{}{
  2733  				"availability_zone": "foo",
  2734  			},
  2735  
  2736  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2737  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2738  					return err
  2739  				}
  2740  				if err := d.ForceNew("availability_zone"); err != nil {
  2741  					return err
  2742  				}
  2743  				return nil
  2744  			},
  2745  
  2746  			Diff: &tofu.InstanceDiff{
  2747  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2748  					"availability_zone": &tofu.ResourceAttrDiff{
  2749  						Old:         "",
  2750  						New:         "bar",
  2751  						RequiresNew: true,
  2752  					},
  2753  				},
  2754  			},
  2755  
  2756  			Err: false,
  2757  		},
  2758  
  2759  		{
  2760  			// NOTE: This case is technically impossible in the current
  2761  			// implementation, because optional+computed values never show up in the
  2762  			// diff. In the event behavior changes this test should ensure that the
  2763  			// intended diff still shows up.
  2764  			Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema",
  2765  			Schema: map[string]*Schema{
  2766  				"availability_zone": &Schema{
  2767  					Type:     TypeString,
  2768  					Optional: true,
  2769  					Computed: true,
  2770  				},
  2771  			},
  2772  
  2773  			State: nil,
  2774  
  2775  			Config: map[string]interface{}{},
  2776  
  2777  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2778  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2779  					return err
  2780  				}
  2781  				if err := d.ForceNew("availability_zone"); err != nil {
  2782  					return err
  2783  				}
  2784  				return nil
  2785  			},
  2786  
  2787  			Diff: &tofu.InstanceDiff{
  2788  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2789  					"availability_zone": &tofu.ResourceAttrDiff{
  2790  						Old:         "",
  2791  						New:         "bar",
  2792  						RequiresNew: true,
  2793  					},
  2794  				},
  2795  			},
  2796  
  2797  			Err: false,
  2798  		},
  2799  
  2800  		{
  2801  
  2802  			Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
  2803  			Schema: map[string]*Schema{
  2804  				"availability_zone": &Schema{
  2805  					Type:     TypeString,
  2806  					Optional: true,
  2807  					Computed: true,
  2808  					ForceNew: true,
  2809  				},
  2810  			},
  2811  
  2812  			State: nil,
  2813  
  2814  			Config: map[string]interface{}{
  2815  				"availability_zone": "foo",
  2816  			},
  2817  
  2818  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2819  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  2820  					return err
  2821  				}
  2822  				return nil
  2823  			},
  2824  
  2825  			Diff: &tofu.InstanceDiff{
  2826  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2827  					"availability_zone": &tofu.ResourceAttrDiff{
  2828  						Old:         "",
  2829  						New:         "bar",
  2830  						RequiresNew: true,
  2831  					},
  2832  				},
  2833  			},
  2834  
  2835  			Err: false,
  2836  		},
  2837  
  2838  		{
  2839  			Name: "required field with computed diff added with CustomizeDiff function",
  2840  			Schema: map[string]*Schema{
  2841  				"ami_id": &Schema{
  2842  					Type:     TypeString,
  2843  					Required: true,
  2844  				},
  2845  				"instance_id": &Schema{
  2846  					Type:     TypeString,
  2847  					Computed: true,
  2848  				},
  2849  			},
  2850  
  2851  			State: nil,
  2852  
  2853  			Config: map[string]interface{}{
  2854  				"ami_id": "foo",
  2855  			},
  2856  
  2857  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2858  				if err := d.SetNew("instance_id", "bar"); err != nil {
  2859  					return err
  2860  				}
  2861  				return nil
  2862  			},
  2863  
  2864  			Diff: &tofu.InstanceDiff{
  2865  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2866  					"ami_id": &tofu.ResourceAttrDiff{
  2867  						Old: "",
  2868  						New: "foo",
  2869  					},
  2870  					"instance_id": &tofu.ResourceAttrDiff{
  2871  						Old: "",
  2872  						New: "bar",
  2873  					},
  2874  				},
  2875  			},
  2876  
  2877  			Err: false,
  2878  		},
  2879  
  2880  		{
  2881  			Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
  2882  			Schema: map[string]*Schema{
  2883  				"ports": &Schema{
  2884  					Type:     TypeSet,
  2885  					Optional: true,
  2886  					Computed: true,
  2887  					Elem:     &Schema{Type: TypeInt},
  2888  					Set: func(a interface{}) int {
  2889  						return a.(int)
  2890  					},
  2891  				},
  2892  			},
  2893  
  2894  			State: &tofu.InstanceState{
  2895  				Attributes: map[string]string{
  2896  					"ports.#": "3",
  2897  					"ports.1": "1",
  2898  					"ports.2": "2",
  2899  					"ports.4": "4",
  2900  				},
  2901  			},
  2902  
  2903  			Config: map[string]interface{}{
  2904  				"ports": []interface{}{5, 2, 6},
  2905  			},
  2906  
  2907  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2908  				if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
  2909  					return err
  2910  				}
  2911  				if err := d.ForceNew("ports"); err != nil {
  2912  					return err
  2913  				}
  2914  				return nil
  2915  			},
  2916  
  2917  			Diff: &tofu.InstanceDiff{
  2918  				Attributes: map[string]*tofu.ResourceAttrDiff{
  2919  					"ports.#": &tofu.ResourceAttrDiff{
  2920  						Old: "3",
  2921  						New: "3",
  2922  					},
  2923  					"ports.1": &tofu.ResourceAttrDiff{
  2924  						Old: "1",
  2925  						New: "1",
  2926  					},
  2927  					"ports.2": &tofu.ResourceAttrDiff{
  2928  						Old: "2",
  2929  						New: "2",
  2930  					},
  2931  					"ports.5": &tofu.ResourceAttrDiff{
  2932  						Old:         "",
  2933  						New:         "5",
  2934  						RequiresNew: true,
  2935  					},
  2936  					"ports.4": &tofu.ResourceAttrDiff{
  2937  						Old:         "4",
  2938  						New:         "0",
  2939  						NewRemoved:  true,
  2940  						RequiresNew: true,
  2941  					},
  2942  				},
  2943  			},
  2944  		},
  2945  
  2946  		{
  2947  			Name:   "tainted resource does not run CustomizeDiffFunc",
  2948  			Schema: map[string]*Schema{},
  2949  
  2950  			State: &tofu.InstanceState{
  2951  				Attributes: map[string]string{
  2952  					"id": "someid",
  2953  				},
  2954  				Tainted: true,
  2955  			},
  2956  
  2957  			Config: map[string]interface{}{},
  2958  
  2959  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2960  				return errors.New("diff customization should not have run")
  2961  			},
  2962  
  2963  			Diff: &tofu.InstanceDiff{
  2964  				Attributes:     map[string]*tofu.ResourceAttrDiff{},
  2965  				DestroyTainted: true,
  2966  			},
  2967  
  2968  			Err: false,
  2969  		},
  2970  
  2971  		{
  2972  			Name: "NewComputed based on a conditional with CustomizeDiffFunc",
  2973  			Schema: map[string]*Schema{
  2974  				"etag": &Schema{
  2975  					Type:     TypeString,
  2976  					Optional: true,
  2977  					Computed: true,
  2978  				},
  2979  				"version_id": &Schema{
  2980  					Type:     TypeString,
  2981  					Computed: true,
  2982  				},
  2983  			},
  2984  
  2985  			State: &tofu.InstanceState{
  2986  				Attributes: map[string]string{
  2987  					"etag":       "foo",
  2988  					"version_id": "1",
  2989  				},
  2990  			},
  2991  
  2992  			Config: map[string]interface{}{
  2993  				"etag": "bar",
  2994  			},
  2995  
  2996  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  2997  				if d.HasChange("etag") {
  2998  					d.SetNewComputed("version_id")
  2999  				}
  3000  				return nil
  3001  			},
  3002  
  3003  			Diff: &tofu.InstanceDiff{
  3004  				Attributes: map[string]*tofu.ResourceAttrDiff{
  3005  					"etag": &tofu.ResourceAttrDiff{
  3006  						Old: "foo",
  3007  						New: "bar",
  3008  					},
  3009  					"version_id": &tofu.ResourceAttrDiff{
  3010  						Old:         "1",
  3011  						New:         "",
  3012  						NewComputed: true,
  3013  					},
  3014  				},
  3015  			},
  3016  
  3017  			Err: false,
  3018  		},
  3019  
  3020  		{
  3021  			Name: "NewComputed should always propagate with CustomizeDiff",
  3022  			Schema: map[string]*Schema{
  3023  				"foo": &Schema{
  3024  					Type:     TypeString,
  3025  					Computed: true,
  3026  				},
  3027  			},
  3028  
  3029  			State: &tofu.InstanceState{
  3030  				Attributes: map[string]string{
  3031  					"foo": "",
  3032  				},
  3033  				ID: "pre-existing",
  3034  			},
  3035  
  3036  			Config: map[string]interface{}{},
  3037  
  3038  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3039  				d.SetNewComputed("foo")
  3040  				return nil
  3041  			},
  3042  
  3043  			Diff: &tofu.InstanceDiff{
  3044  				Attributes: map[string]*tofu.ResourceAttrDiff{
  3045  					"foo": &tofu.ResourceAttrDiff{
  3046  						NewComputed: true,
  3047  					},
  3048  				},
  3049  			},
  3050  
  3051  			Err: false,
  3052  		},
  3053  
  3054  		{
  3055  			Name: "vetoing a diff",
  3056  			Schema: map[string]*Schema{
  3057  				"foo": &Schema{
  3058  					Type:     TypeString,
  3059  					Optional: true,
  3060  					Computed: true,
  3061  				},
  3062  			},
  3063  
  3064  			State: &tofu.InstanceState{
  3065  				Attributes: map[string]string{
  3066  					"foo": "bar",
  3067  				},
  3068  			},
  3069  
  3070  			Config: map[string]interface{}{
  3071  				"foo": "baz",
  3072  			},
  3073  
  3074  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3075  				return fmt.Errorf("diff vetoed")
  3076  			},
  3077  
  3078  			Err: true,
  3079  		},
  3080  
  3081  		// A lot of resources currently depended on using the empty string as a
  3082  		// nil/unset value.
  3083  		// FIXME: We want this to eventually produce a diff, since there
  3084  		// technically is a new value in the config.
  3085  		{
  3086  			Name: "optional, computed, empty string",
  3087  			Schema: map[string]*Schema{
  3088  				"attr": &Schema{
  3089  					Type:     TypeString,
  3090  					Optional: true,
  3091  					Computed: true,
  3092  				},
  3093  			},
  3094  
  3095  			State: &tofu.InstanceState{
  3096  				Attributes: map[string]string{
  3097  					"attr": "bar",
  3098  				},
  3099  			},
  3100  
  3101  			Config: map[string]interface{}{
  3102  				"attr": "",
  3103  			},
  3104  		},
  3105  
  3106  		{
  3107  			Name: "optional, computed, empty string should not crash in CustomizeDiff",
  3108  			Schema: map[string]*Schema{
  3109  				"unrelated_set": {
  3110  					Type:     TypeSet,
  3111  					Optional: true,
  3112  					Elem:     &Schema{Type: TypeString},
  3113  				},
  3114  				"stream_enabled": {
  3115  					Type:     TypeBool,
  3116  					Optional: true,
  3117  				},
  3118  				"stream_view_type": {
  3119  					Type:     TypeString,
  3120  					Optional: true,
  3121  					Computed: true,
  3122  				},
  3123  			},
  3124  
  3125  			State: &tofu.InstanceState{
  3126  				Attributes: map[string]string{
  3127  					"unrelated_set.#":  "0",
  3128  					"stream_enabled":   "true",
  3129  					"stream_view_type": "KEYS_ONLY",
  3130  				},
  3131  			},
  3132  			Config: map[string]interface{}{
  3133  				"stream_enabled":   false,
  3134  				"stream_view_type": "",
  3135  			},
  3136  			CustomizeDiff: func(diff *ResourceDiff, v interface{}) error {
  3137  				v, ok := diff.GetOk("unrelated_set")
  3138  				if ok {
  3139  					return fmt.Errorf("Didn't expect unrelated_set: %#v", v)
  3140  				}
  3141  				return nil
  3142  			},
  3143  			Diff: &tofu.InstanceDiff{
  3144  				Attributes: map[string]*tofu.ResourceAttrDiff{
  3145  					"stream_enabled": {
  3146  						Old: "true",
  3147  						New: "false",
  3148  					},
  3149  				},
  3150  			},
  3151  		},
  3152  	}
  3153  
  3154  	for i, tc := range cases {
  3155  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  3156  			c := tofu.NewResourceConfigRaw(tc.Config)
  3157  
  3158  			d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, true)
  3159  			if err != nil != tc.Err {
  3160  				t.Fatalf("err: %s", err)
  3161  			}
  3162  
  3163  			if !reflect.DeepEqual(tc.Diff, d) {
  3164  				t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d)
  3165  			}
  3166  		})
  3167  	}
  3168  }
  3169  
  3170  func TestSchemaMap_Input(t *testing.T) {
  3171  	cases := map[string]struct {
  3172  		Schema map[string]*Schema
  3173  		Config map[string]interface{}
  3174  		Input  map[string]string
  3175  		Result map[string]interface{}
  3176  		Err    bool
  3177  	}{
  3178  		/*
  3179  		 * String decode
  3180  		 */
  3181  
  3182  		"no input on optional field with no config": {
  3183  			Schema: map[string]*Schema{
  3184  				"availability_zone": &Schema{
  3185  					Type:     TypeString,
  3186  					Optional: true,
  3187  				},
  3188  			},
  3189  
  3190  			Input:  map[string]string{},
  3191  			Result: map[string]interface{}{},
  3192  			Err:    false,
  3193  		},
  3194  
  3195  		"input ignored when config has a value": {
  3196  			Schema: map[string]*Schema{
  3197  				"availability_zone": &Schema{
  3198  					Type:     TypeString,
  3199  					Optional: true,
  3200  				},
  3201  			},
  3202  
  3203  			Config: map[string]interface{}{
  3204  				"availability_zone": "bar",
  3205  			},
  3206  
  3207  			Input: map[string]string{
  3208  				"availability_zone": "foo",
  3209  			},
  3210  
  3211  			Result: map[string]interface{}{},
  3212  
  3213  			Err: false,
  3214  		},
  3215  
  3216  		"input ignored when schema has a default": {
  3217  			Schema: map[string]*Schema{
  3218  				"availability_zone": &Schema{
  3219  					Type:     TypeString,
  3220  					Default:  "foo",
  3221  					Optional: true,
  3222  				},
  3223  			},
  3224  
  3225  			Input: map[string]string{
  3226  				"availability_zone": "bar",
  3227  			},
  3228  
  3229  			Result: map[string]interface{}{},
  3230  
  3231  			Err: false,
  3232  		},
  3233  
  3234  		"input ignored when default function returns a value": {
  3235  			Schema: map[string]*Schema{
  3236  				"availability_zone": &Schema{
  3237  					Type: TypeString,
  3238  					DefaultFunc: func() (interface{}, error) {
  3239  						return "foo", nil
  3240  					},
  3241  					Optional: true,
  3242  				},
  3243  			},
  3244  
  3245  			Input: map[string]string{
  3246  				"availability_zone": "bar",
  3247  			},
  3248  
  3249  			Result: map[string]interface{}{},
  3250  
  3251  			Err: false,
  3252  		},
  3253  
  3254  		"input ignored when default function returns an empty string": {
  3255  			Schema: map[string]*Schema{
  3256  				"availability_zone": &Schema{
  3257  					Type:     TypeString,
  3258  					Default:  "",
  3259  					Optional: true,
  3260  				},
  3261  			},
  3262  
  3263  			Input: map[string]string{
  3264  				"availability_zone": "bar",
  3265  			},
  3266  
  3267  			Result: map[string]interface{}{},
  3268  
  3269  			Err: false,
  3270  		},
  3271  
  3272  		"input used when default function returns nil": {
  3273  			Schema: map[string]*Schema{
  3274  				"availability_zone": &Schema{
  3275  					Type: TypeString,
  3276  					DefaultFunc: func() (interface{}, error) {
  3277  						return nil, nil
  3278  					},
  3279  					Required: true,
  3280  				},
  3281  			},
  3282  
  3283  			Input: map[string]string{
  3284  				"availability_zone": "bar",
  3285  			},
  3286  
  3287  			Result: map[string]interface{}{
  3288  				"availability_zone": "bar",
  3289  			},
  3290  
  3291  			Err: false,
  3292  		},
  3293  
  3294  		"input not used when optional default function returns nil": {
  3295  			Schema: map[string]*Schema{
  3296  				"availability_zone": &Schema{
  3297  					Type: TypeString,
  3298  					DefaultFunc: func() (interface{}, error) {
  3299  						return nil, nil
  3300  					},
  3301  					Optional: true,
  3302  				},
  3303  			},
  3304  
  3305  			Input:  map[string]string{},
  3306  			Result: map[string]interface{}{},
  3307  			Err:    false,
  3308  		},
  3309  	}
  3310  
  3311  	for i, tc := range cases {
  3312  		if tc.Config == nil {
  3313  			tc.Config = make(map[string]interface{})
  3314  		}
  3315  
  3316  		input := new(tofu.MockUIInput)
  3317  		input.InputReturnMap = tc.Input
  3318  
  3319  		rc := tofu.NewResourceConfigRaw(tc.Config)
  3320  		rc.Config = make(map[string]interface{})
  3321  
  3322  		actual, err := schemaMap(tc.Schema).Input(input, rc)
  3323  		if err != nil != tc.Err {
  3324  			t.Fatalf("#%v err: %s", i, err)
  3325  		}
  3326  
  3327  		if !reflect.DeepEqual(tc.Result, actual.Config) {
  3328  			t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result)
  3329  		}
  3330  	}
  3331  }
  3332  
  3333  func TestSchemaMap_InputDefault(t *testing.T) {
  3334  	emptyConfig := make(map[string]interface{})
  3335  	rc := tofu.NewResourceConfigRaw(emptyConfig)
  3336  	rc.Config = make(map[string]interface{})
  3337  
  3338  	input := new(tofu.MockUIInput)
  3339  	input.InputFn = func(opts *tofu.InputOpts) (string, error) {
  3340  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3341  		return "", nil
  3342  	}
  3343  
  3344  	schema := map[string]*Schema{
  3345  		"availability_zone": &Schema{
  3346  			Type:     TypeString,
  3347  			Default:  "foo",
  3348  			Optional: true,
  3349  		},
  3350  	}
  3351  	actual, err := schemaMap(schema).Input(input, rc)
  3352  	if err != nil {
  3353  		t.Fatalf("err: %s", err)
  3354  	}
  3355  
  3356  	expected := map[string]interface{}{}
  3357  
  3358  	if !reflect.DeepEqual(expected, actual.Config) {
  3359  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3360  	}
  3361  }
  3362  
  3363  func TestSchemaMap_InputDeprecated(t *testing.T) {
  3364  	emptyConfig := make(map[string]interface{})
  3365  	rc := tofu.NewResourceConfigRaw(emptyConfig)
  3366  	rc.Config = make(map[string]interface{})
  3367  
  3368  	input := new(tofu.MockUIInput)
  3369  	input.InputFn = func(opts *tofu.InputOpts) (string, error) {
  3370  		t.Fatalf("InputFn should not be called on: %#v", opts)
  3371  		return "", nil
  3372  	}
  3373  
  3374  	schema := map[string]*Schema{
  3375  		"availability_zone": &Schema{
  3376  			Type:       TypeString,
  3377  			Deprecated: "long gone",
  3378  			Optional:   true,
  3379  		},
  3380  	}
  3381  	actual, err := schemaMap(schema).Input(input, rc)
  3382  	if err != nil {
  3383  		t.Fatalf("err: %s", err)
  3384  	}
  3385  
  3386  	expected := map[string]interface{}{}
  3387  
  3388  	if !reflect.DeepEqual(expected, actual.Config) {
  3389  		t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected)
  3390  	}
  3391  }
  3392  
  3393  func TestSchemaMap_InternalValidate(t *testing.T) {
  3394  	cases := map[string]struct {
  3395  		In  map[string]*Schema
  3396  		Err bool
  3397  	}{
  3398  		"nothing": {
  3399  			nil,
  3400  			false,
  3401  		},
  3402  
  3403  		"Both optional and required": {
  3404  			map[string]*Schema{
  3405  				"foo": &Schema{
  3406  					Type:     TypeInt,
  3407  					Optional: true,
  3408  					Required: true,
  3409  				},
  3410  			},
  3411  			true,
  3412  		},
  3413  
  3414  		"No optional and no required": {
  3415  			map[string]*Schema{
  3416  				"foo": &Schema{
  3417  					Type: TypeInt,
  3418  				},
  3419  			},
  3420  			true,
  3421  		},
  3422  
  3423  		"Missing Type": {
  3424  			map[string]*Schema{
  3425  				"foo": &Schema{
  3426  					Required: true,
  3427  				},
  3428  			},
  3429  			true,
  3430  		},
  3431  
  3432  		"Required but computed": {
  3433  			map[string]*Schema{
  3434  				"foo": &Schema{
  3435  					Type:     TypeInt,
  3436  					Required: true,
  3437  					Computed: true,
  3438  				},
  3439  			},
  3440  			true,
  3441  		},
  3442  
  3443  		"Looks good": {
  3444  			map[string]*Schema{
  3445  				"foo": &Schema{
  3446  					Type:     TypeString,
  3447  					Required: true,
  3448  				},
  3449  			},
  3450  			false,
  3451  		},
  3452  
  3453  		"Computed but has default": {
  3454  			map[string]*Schema{
  3455  				"foo": &Schema{
  3456  					Type:     TypeInt,
  3457  					Optional: true,
  3458  					Computed: true,
  3459  					Default:  "foo",
  3460  				},
  3461  			},
  3462  			true,
  3463  		},
  3464  
  3465  		"Required but has default": {
  3466  			map[string]*Schema{
  3467  				"foo": &Schema{
  3468  					Type:     TypeInt,
  3469  					Optional: true,
  3470  					Required: true,
  3471  					Default:  "foo",
  3472  				},
  3473  			},
  3474  			true,
  3475  		},
  3476  
  3477  		"List element not set": {
  3478  			map[string]*Schema{
  3479  				"foo": &Schema{
  3480  					Type: TypeList,
  3481  				},
  3482  			},
  3483  			true,
  3484  		},
  3485  
  3486  		"List default": {
  3487  			map[string]*Schema{
  3488  				"foo": &Schema{
  3489  					Type:    TypeList,
  3490  					Elem:    &Schema{Type: TypeInt},
  3491  					Default: "foo",
  3492  				},
  3493  			},
  3494  			true,
  3495  		},
  3496  
  3497  		"List element computed": {
  3498  			map[string]*Schema{
  3499  				"foo": &Schema{
  3500  					Type:     TypeList,
  3501  					Optional: true,
  3502  					Elem: &Schema{
  3503  						Type:     TypeInt,
  3504  						Computed: true,
  3505  					},
  3506  				},
  3507  			},
  3508  			true,
  3509  		},
  3510  
  3511  		"List element with Set set": {
  3512  			map[string]*Schema{
  3513  				"foo": &Schema{
  3514  					Type:     TypeList,
  3515  					Elem:     &Schema{Type: TypeInt},
  3516  					Set:      func(interface{}) int { return 0 },
  3517  					Optional: true,
  3518  				},
  3519  			},
  3520  			true,
  3521  		},
  3522  
  3523  		"Set element with no Set set": {
  3524  			map[string]*Schema{
  3525  				"foo": &Schema{
  3526  					Type:     TypeSet,
  3527  					Elem:     &Schema{Type: TypeInt},
  3528  					Optional: true,
  3529  				},
  3530  			},
  3531  			false,
  3532  		},
  3533  
  3534  		"Required but computedWhen": {
  3535  			map[string]*Schema{
  3536  				"foo": &Schema{
  3537  					Type:         TypeInt,
  3538  					Required:     true,
  3539  					ComputedWhen: []string{"foo"},
  3540  				},
  3541  			},
  3542  			true,
  3543  		},
  3544  
  3545  		"Conflicting attributes cannot be required": {
  3546  			map[string]*Schema{
  3547  				"a": &Schema{
  3548  					Type:     TypeBool,
  3549  					Required: true,
  3550  				},
  3551  				"b": &Schema{
  3552  					Type:          TypeBool,
  3553  					Optional:      true,
  3554  					ConflictsWith: []string{"a"},
  3555  				},
  3556  			},
  3557  			true,
  3558  		},
  3559  
  3560  		"Attribute with conflicts cannot be required": {
  3561  			map[string]*Schema{
  3562  				"b": &Schema{
  3563  					Type:          TypeBool,
  3564  					Required:      true,
  3565  					ConflictsWith: []string{"a"},
  3566  				},
  3567  			},
  3568  			true,
  3569  		},
  3570  
  3571  		"ConflictsWith cannot be used w/ ComputedWhen": {
  3572  			map[string]*Schema{
  3573  				"a": &Schema{
  3574  					Type:         TypeBool,
  3575  					ComputedWhen: []string{"foor"},
  3576  				},
  3577  				"b": &Schema{
  3578  					Type:          TypeBool,
  3579  					Required:      true,
  3580  					ConflictsWith: []string{"a"},
  3581  				},
  3582  			},
  3583  			true,
  3584  		},
  3585  
  3586  		"Sub-resource invalid": {
  3587  			map[string]*Schema{
  3588  				"foo": &Schema{
  3589  					Type:     TypeList,
  3590  					Optional: true,
  3591  					Elem: &Resource{
  3592  						Schema: map[string]*Schema{
  3593  							"foo": new(Schema),
  3594  						},
  3595  					},
  3596  				},
  3597  			},
  3598  			true,
  3599  		},
  3600  
  3601  		"Sub-resource valid": {
  3602  			map[string]*Schema{
  3603  				"foo": &Schema{
  3604  					Type:     TypeList,
  3605  					Optional: true,
  3606  					Elem: &Resource{
  3607  						Schema: map[string]*Schema{
  3608  							"foo": &Schema{
  3609  								Type:     TypeInt,
  3610  								Optional: true,
  3611  							},
  3612  						},
  3613  					},
  3614  				},
  3615  			},
  3616  			false,
  3617  		},
  3618  
  3619  		"ValidateFunc on non-primitive": {
  3620  			map[string]*Schema{
  3621  				"foo": &Schema{
  3622  					Type:     TypeSet,
  3623  					Required: true,
  3624  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3625  						return
  3626  					},
  3627  				},
  3628  			},
  3629  			true,
  3630  		},
  3631  
  3632  		"computed-only field with validateFunc": {
  3633  			map[string]*Schema{
  3634  				"string": &Schema{
  3635  					Type:     TypeString,
  3636  					Computed: true,
  3637  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  3638  						es = append(es, fmt.Errorf("this is not fine"))
  3639  						return
  3640  					},
  3641  				},
  3642  			},
  3643  			true,
  3644  		},
  3645  
  3646  		"computed-only field with diffSuppressFunc": {
  3647  			map[string]*Schema{
  3648  				"string": &Schema{
  3649  					Type:     TypeString,
  3650  					Computed: true,
  3651  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3652  						// Always suppress any diff
  3653  						return false
  3654  					},
  3655  				},
  3656  			},
  3657  			true,
  3658  		},
  3659  
  3660  		"invalid field name format #1": {
  3661  			map[string]*Schema{
  3662  				"with space": &Schema{
  3663  					Type:     TypeString,
  3664  					Optional: true,
  3665  				},
  3666  			},
  3667  			true,
  3668  		},
  3669  
  3670  		"invalid field name format #2": {
  3671  			map[string]*Schema{
  3672  				"WithCapitals": &Schema{
  3673  					Type:     TypeString,
  3674  					Optional: true,
  3675  				},
  3676  			},
  3677  			true,
  3678  		},
  3679  
  3680  		"invalid field name format of a Deprecated field": {
  3681  			map[string]*Schema{
  3682  				"WithCapitals": &Schema{
  3683  					Type:       TypeString,
  3684  					Optional:   true,
  3685  					Deprecated: "Use with_underscores instead",
  3686  				},
  3687  			},
  3688  			false,
  3689  		},
  3690  
  3691  		"invalid field name format of a Removed field": {
  3692  			map[string]*Schema{
  3693  				"WithCapitals": &Schema{
  3694  					Type:     TypeString,
  3695  					Optional: true,
  3696  					Removed:  "Use with_underscores instead",
  3697  				},
  3698  			},
  3699  			false,
  3700  		},
  3701  
  3702  		"ConfigModeBlock with Elem *Resource": {
  3703  			map[string]*Schema{
  3704  				"block": &Schema{
  3705  					Type:       TypeList,
  3706  					ConfigMode: SchemaConfigModeBlock,
  3707  					Optional:   true,
  3708  					Elem:       &Resource{},
  3709  				},
  3710  			},
  3711  			false,
  3712  		},
  3713  
  3714  		"ConfigModeBlock Computed with Elem *Resource": {
  3715  			map[string]*Schema{
  3716  				"block": &Schema{
  3717  					Type:       TypeList,
  3718  					ConfigMode: SchemaConfigModeBlock,
  3719  					Computed:   true,
  3720  					Elem:       &Resource{},
  3721  				},
  3722  			},
  3723  			true, // ConfigMode of block cannot be used for computed schema
  3724  		},
  3725  
  3726  		"ConfigModeBlock with Elem *Schema": {
  3727  			map[string]*Schema{
  3728  				"block": &Schema{
  3729  					Type:       TypeList,
  3730  					ConfigMode: SchemaConfigModeBlock,
  3731  					Optional:   true,
  3732  					Elem: &Schema{
  3733  						Type: TypeString,
  3734  					},
  3735  				},
  3736  			},
  3737  			true,
  3738  		},
  3739  
  3740  		"ConfigModeBlock with no Elem": {
  3741  			map[string]*Schema{
  3742  				"block": &Schema{
  3743  					Type:       TypeString,
  3744  					ConfigMode: SchemaConfigModeBlock,
  3745  					Optional:   true,
  3746  				},
  3747  			},
  3748  			true,
  3749  		},
  3750  
  3751  		"ConfigModeBlock inside ConfigModeAttr": {
  3752  			map[string]*Schema{
  3753  				"block": &Schema{
  3754  					Type:       TypeList,
  3755  					ConfigMode: SchemaConfigModeAttr,
  3756  					Optional:   true,
  3757  					Elem: &Resource{
  3758  						Schema: map[string]*Schema{
  3759  							"sub": &Schema{
  3760  								Type:       TypeList,
  3761  								ConfigMode: SchemaConfigModeBlock,
  3762  								Elem:       &Resource{},
  3763  							},
  3764  						},
  3765  					},
  3766  				},
  3767  			},
  3768  			true, // ConfigMode of block cannot be used in child of schema with ConfigMode of attribute
  3769  		},
  3770  
  3771  		"ConfigModeAuto with *Resource inside ConfigModeAttr": {
  3772  			map[string]*Schema{
  3773  				"block": &Schema{
  3774  					Type:       TypeList,
  3775  					ConfigMode: SchemaConfigModeAttr,
  3776  					Optional:   true,
  3777  					Elem: &Resource{
  3778  						Schema: map[string]*Schema{
  3779  							"sub": &Schema{
  3780  								Type: TypeList,
  3781  								Elem: &Resource{},
  3782  							},
  3783  						},
  3784  					},
  3785  				},
  3786  			},
  3787  			true, // in *schema.Resource with ConfigMode of attribute, so must also have ConfigMode of attribute
  3788  		},
  3789  	}
  3790  
  3791  	for tn, tc := range cases {
  3792  		t.Run(tn, func(t *testing.T) {
  3793  			err := schemaMap(tc.In).InternalValidate(nil)
  3794  			if err != nil != tc.Err {
  3795  				if tc.Err {
  3796  					t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In)
  3797  				}
  3798  				t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In)
  3799  			}
  3800  		})
  3801  	}
  3802  
  3803  }
  3804  
  3805  func TestSchemaMap_DiffSuppress(t *testing.T) {
  3806  	cases := map[string]struct {
  3807  		Schema       map[string]*Schema
  3808  		State        *tofu.InstanceState
  3809  		Config       map[string]interface{}
  3810  		ExpectedDiff *tofu.InstanceDiff
  3811  		Err          bool
  3812  	}{
  3813  		"#0 - Suppress otherwise valid diff by returning true": {
  3814  			Schema: map[string]*Schema{
  3815  				"availability_zone": {
  3816  					Type:     TypeString,
  3817  					Optional: true,
  3818  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3819  						// Always suppress any diff
  3820  						return true
  3821  					},
  3822  				},
  3823  			},
  3824  
  3825  			State: nil,
  3826  
  3827  			Config: map[string]interface{}{
  3828  				"availability_zone": "foo",
  3829  			},
  3830  
  3831  			ExpectedDiff: nil,
  3832  
  3833  			Err: false,
  3834  		},
  3835  
  3836  		"#1 - Don't suppress diff by returning false": {
  3837  			Schema: map[string]*Schema{
  3838  				"availability_zone": {
  3839  					Type:     TypeString,
  3840  					Optional: true,
  3841  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3842  						// Always suppress any diff
  3843  						return false
  3844  					},
  3845  				},
  3846  			},
  3847  
  3848  			State: nil,
  3849  
  3850  			Config: map[string]interface{}{
  3851  				"availability_zone": "foo",
  3852  			},
  3853  
  3854  			ExpectedDiff: &tofu.InstanceDiff{
  3855  				Attributes: map[string]*tofu.ResourceAttrDiff{
  3856  					"availability_zone": {
  3857  						Old: "",
  3858  						New: "foo",
  3859  					},
  3860  				},
  3861  			},
  3862  
  3863  			Err: false,
  3864  		},
  3865  
  3866  		"Default with suppress makes no diff": {
  3867  			Schema: map[string]*Schema{
  3868  				"availability_zone": {
  3869  					Type:     TypeString,
  3870  					Optional: true,
  3871  					Default:  "foo",
  3872  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3873  						return true
  3874  					},
  3875  				},
  3876  			},
  3877  
  3878  			State: nil,
  3879  
  3880  			Config: map[string]interface{}{},
  3881  
  3882  			ExpectedDiff: nil,
  3883  
  3884  			Err: false,
  3885  		},
  3886  
  3887  		"Default with false suppress makes diff": {
  3888  			Schema: map[string]*Schema{
  3889  				"availability_zone": {
  3890  					Type:     TypeString,
  3891  					Optional: true,
  3892  					Default:  "foo",
  3893  					DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool {
  3894  						return false
  3895  					},
  3896  				},
  3897  			},
  3898  
  3899  			State: nil,
  3900  
  3901  			Config: map[string]interface{}{},
  3902  
  3903  			ExpectedDiff: &tofu.InstanceDiff{
  3904  				Attributes: map[string]*tofu.ResourceAttrDiff{
  3905  					"availability_zone": {
  3906  						Old: "",
  3907  						New: "foo",
  3908  					},
  3909  				},
  3910  			},
  3911  
  3912  			Err: false,
  3913  		},
  3914  
  3915  		"Complex structure with set of computed string should mark root set as computed": {
  3916  			Schema: map[string]*Schema{
  3917  				"outer": &Schema{
  3918  					Type:     TypeSet,
  3919  					Optional: true,
  3920  					Elem: &Resource{
  3921  						Schema: map[string]*Schema{
  3922  							"outer_str": &Schema{
  3923  								Type:     TypeString,
  3924  								Optional: true,
  3925  							},
  3926  							"inner": &Schema{
  3927  								Type:     TypeSet,
  3928  								Optional: true,
  3929  								Elem: &Resource{
  3930  									Schema: map[string]*Schema{
  3931  										"inner_str": &Schema{
  3932  											Type:     TypeString,
  3933  											Optional: true,
  3934  										},
  3935  									},
  3936  								},
  3937  								Set: func(v interface{}) int {
  3938  									return 2
  3939  								},
  3940  							},
  3941  						},
  3942  					},
  3943  					Set: func(v interface{}) int {
  3944  						return 1
  3945  					},
  3946  				},
  3947  			},
  3948  
  3949  			State: nil,
  3950  
  3951  			Config: map[string]interface{}{
  3952  				"outer": []interface{}{
  3953  					map[string]interface{}{
  3954  						"outer_str": "foo",
  3955  						"inner": []interface{}{
  3956  							map[string]interface{}{
  3957  								"inner_str": hcl2shim.UnknownVariableValue,
  3958  							},
  3959  						},
  3960  					},
  3961  				},
  3962  			},
  3963  
  3964  			ExpectedDiff: &tofu.InstanceDiff{
  3965  				Attributes: map[string]*tofu.ResourceAttrDiff{
  3966  					"outer.#": &tofu.ResourceAttrDiff{
  3967  						Old: "0",
  3968  						New: "1",
  3969  					},
  3970  					"outer.~1.outer_str": &tofu.ResourceAttrDiff{
  3971  						Old: "",
  3972  						New: "foo",
  3973  					},
  3974  					"outer.~1.inner.#": &tofu.ResourceAttrDiff{
  3975  						Old: "0",
  3976  						New: "1",
  3977  					},
  3978  					"outer.~1.inner.~2.inner_str": &tofu.ResourceAttrDiff{
  3979  						Old:         "",
  3980  						New:         hcl2shim.UnknownVariableValue,
  3981  						NewComputed: true,
  3982  					},
  3983  				},
  3984  			},
  3985  
  3986  			Err: false,
  3987  		},
  3988  
  3989  		"Complex structure with complex list of computed string should mark root set as computed": {
  3990  			Schema: map[string]*Schema{
  3991  				"outer": &Schema{
  3992  					Type:     TypeSet,
  3993  					Optional: true,
  3994  					Elem: &Resource{
  3995  						Schema: map[string]*Schema{
  3996  							"outer_str": &Schema{
  3997  								Type:     TypeString,
  3998  								Optional: true,
  3999  							},
  4000  							"inner": &Schema{
  4001  								Type:     TypeList,
  4002  								Optional: true,
  4003  								Elem: &Resource{
  4004  									Schema: map[string]*Schema{
  4005  										"inner_str": &Schema{
  4006  											Type:     TypeString,
  4007  											Optional: true,
  4008  										},
  4009  									},
  4010  								},
  4011  							},
  4012  						},
  4013  					},
  4014  					Set: func(v interface{}) int {
  4015  						return 1
  4016  					},
  4017  				},
  4018  			},
  4019  
  4020  			State: nil,
  4021  
  4022  			Config: map[string]interface{}{
  4023  				"outer": []interface{}{
  4024  					map[string]interface{}{
  4025  						"outer_str": "foo",
  4026  						"inner": []interface{}{
  4027  							map[string]interface{}{
  4028  								"inner_str": hcl2shim.UnknownVariableValue,
  4029  							},
  4030  						},
  4031  					},
  4032  				},
  4033  			},
  4034  
  4035  			ExpectedDiff: &tofu.InstanceDiff{
  4036  				Attributes: map[string]*tofu.ResourceAttrDiff{
  4037  					"outer.#": &tofu.ResourceAttrDiff{
  4038  						Old: "0",
  4039  						New: "1",
  4040  					},
  4041  					"outer.~1.outer_str": &tofu.ResourceAttrDiff{
  4042  						Old: "",
  4043  						New: "foo",
  4044  					},
  4045  					"outer.~1.inner.#": &tofu.ResourceAttrDiff{
  4046  						Old: "0",
  4047  						New: "1",
  4048  					},
  4049  					"outer.~1.inner.0.inner_str": &tofu.ResourceAttrDiff{
  4050  						Old:         "",
  4051  						New:         hcl2shim.UnknownVariableValue,
  4052  						NewComputed: true,
  4053  					},
  4054  				},
  4055  			},
  4056  
  4057  			Err: false,
  4058  		},
  4059  	}
  4060  
  4061  	for tn, tc := range cases {
  4062  		t.Run(tn, func(t *testing.T) {
  4063  			c := tofu.NewResourceConfigRaw(tc.Config)
  4064  
  4065  			d, err := schemaMap(tc.Schema).Diff(tc.State, c, nil, nil, true)
  4066  			if err != nil != tc.Err {
  4067  				t.Fatalf("#%q err: %s", tn, err)
  4068  			}
  4069  
  4070  			if !reflect.DeepEqual(tc.ExpectedDiff, d) {
  4071  				t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d)
  4072  			}
  4073  		})
  4074  	}
  4075  }
  4076  
  4077  func TestSchemaMap_Validate(t *testing.T) {
  4078  	cases := map[string]struct {
  4079  		Schema   map[string]*Schema
  4080  		Config   map[string]interface{}
  4081  		Err      bool
  4082  		Errors   []error
  4083  		Warnings []string
  4084  	}{
  4085  		"Good": {
  4086  			Schema: map[string]*Schema{
  4087  				"availability_zone": &Schema{
  4088  					Type:     TypeString,
  4089  					Optional: true,
  4090  					Computed: true,
  4091  					ForceNew: true,
  4092  				},
  4093  			},
  4094  
  4095  			Config: map[string]interface{}{
  4096  				"availability_zone": "foo",
  4097  			},
  4098  		},
  4099  
  4100  		"Good, because the var is not set and that error will come elsewhere": {
  4101  			Schema: map[string]*Schema{
  4102  				"size": &Schema{
  4103  					Type:     TypeInt,
  4104  					Required: true,
  4105  				},
  4106  			},
  4107  
  4108  			Config: map[string]interface{}{
  4109  				"size": hcl2shim.UnknownVariableValue,
  4110  			},
  4111  		},
  4112  
  4113  		"Required field not set": {
  4114  			Schema: map[string]*Schema{
  4115  				"availability_zone": &Schema{
  4116  					Type:     TypeString,
  4117  					Required: true,
  4118  				},
  4119  			},
  4120  
  4121  			Config: map[string]interface{}{},
  4122  
  4123  			Err: true,
  4124  		},
  4125  
  4126  		"Invalid basic type": {
  4127  			Schema: map[string]*Schema{
  4128  				"port": &Schema{
  4129  					Type:     TypeInt,
  4130  					Required: true,
  4131  				},
  4132  			},
  4133  
  4134  			Config: map[string]interface{}{
  4135  				"port": "I am invalid",
  4136  			},
  4137  
  4138  			Err: true,
  4139  		},
  4140  
  4141  		"Invalid complex type": {
  4142  			Schema: map[string]*Schema{
  4143  				"user_data": &Schema{
  4144  					Type:     TypeString,
  4145  					Optional: true,
  4146  				},
  4147  			},
  4148  
  4149  			Config: map[string]interface{}{
  4150  				"user_data": []interface{}{
  4151  					map[string]interface{}{
  4152  						"foo": "bar",
  4153  					},
  4154  				},
  4155  			},
  4156  
  4157  			Err: true,
  4158  		},
  4159  
  4160  		"Bad type": {
  4161  			Schema: map[string]*Schema{
  4162  				"size": &Schema{
  4163  					Type:     TypeInt,
  4164  					Required: true,
  4165  				},
  4166  			},
  4167  
  4168  			Config: map[string]interface{}{
  4169  				"size": "nope",
  4170  			},
  4171  
  4172  			Err: true,
  4173  		},
  4174  
  4175  		"Required but has DefaultFunc": {
  4176  			Schema: map[string]*Schema{
  4177  				"availability_zone": &Schema{
  4178  					Type:     TypeString,
  4179  					Required: true,
  4180  					DefaultFunc: func() (interface{}, error) {
  4181  						return "foo", nil
  4182  					},
  4183  				},
  4184  			},
  4185  
  4186  			Config: nil,
  4187  		},
  4188  
  4189  		"Required but has DefaultFunc return nil": {
  4190  			Schema: map[string]*Schema{
  4191  				"availability_zone": &Schema{
  4192  					Type:     TypeString,
  4193  					Required: true,
  4194  					DefaultFunc: func() (interface{}, error) {
  4195  						return nil, nil
  4196  					},
  4197  				},
  4198  			},
  4199  
  4200  			Config: nil,
  4201  
  4202  			Err: true,
  4203  		},
  4204  
  4205  		"List with promotion": {
  4206  			Schema: map[string]*Schema{
  4207  				"ingress": &Schema{
  4208  					Type:          TypeList,
  4209  					Elem:          &Schema{Type: TypeInt},
  4210  					PromoteSingle: true,
  4211  					Optional:      true,
  4212  				},
  4213  			},
  4214  
  4215  			Config: map[string]interface{}{
  4216  				"ingress": "5",
  4217  			},
  4218  
  4219  			Err: false,
  4220  		},
  4221  
  4222  		"List with promotion set as list": {
  4223  			Schema: map[string]*Schema{
  4224  				"ingress": &Schema{
  4225  					Type:          TypeList,
  4226  					Elem:          &Schema{Type: TypeInt},
  4227  					PromoteSingle: true,
  4228  					Optional:      true,
  4229  				},
  4230  			},
  4231  
  4232  			Config: map[string]interface{}{
  4233  				"ingress": []interface{}{"5"},
  4234  			},
  4235  
  4236  			Err: false,
  4237  		},
  4238  
  4239  		"Optional sub-resource": {
  4240  			Schema: map[string]*Schema{
  4241  				"ingress": &Schema{
  4242  					Type: TypeList,
  4243  					Elem: &Resource{
  4244  						Schema: map[string]*Schema{
  4245  							"from": &Schema{
  4246  								Type:     TypeInt,
  4247  								Required: true,
  4248  							},
  4249  						},
  4250  					},
  4251  				},
  4252  			},
  4253  
  4254  			Config: map[string]interface{}{},
  4255  
  4256  			Err: false,
  4257  		},
  4258  
  4259  		"Sub-resource is the wrong type": {
  4260  			Schema: map[string]*Schema{
  4261  				"ingress": &Schema{
  4262  					Type:     TypeList,
  4263  					Required: true,
  4264  					Elem: &Resource{
  4265  						Schema: map[string]*Schema{
  4266  							"from": &Schema{
  4267  								Type:     TypeInt,
  4268  								Required: true,
  4269  							},
  4270  						},
  4271  					},
  4272  				},
  4273  			},
  4274  
  4275  			Config: map[string]interface{}{
  4276  				"ingress": []interface{}{"foo"},
  4277  			},
  4278  
  4279  			Err: true,
  4280  		},
  4281  
  4282  		"Not a list nested block": {
  4283  			Schema: map[string]*Schema{
  4284  				"ingress": &Schema{
  4285  					Type:     TypeList,
  4286  					Optional: true,
  4287  					Elem: &Resource{
  4288  						Schema: map[string]*Schema{
  4289  							"from": &Schema{
  4290  								Type:     TypeInt,
  4291  								Required: true,
  4292  							},
  4293  						},
  4294  					},
  4295  				},
  4296  			},
  4297  
  4298  			Config: map[string]interface{}{
  4299  				"ingress": "foo",
  4300  			},
  4301  
  4302  			Err: true,
  4303  			Errors: []error{
  4304  				fmt.Errorf(`ingress: should be a list`),
  4305  			},
  4306  		},
  4307  
  4308  		"Not a list primitive": {
  4309  			Schema: map[string]*Schema{
  4310  				"strings": &Schema{
  4311  					Type:     TypeList,
  4312  					Optional: true,
  4313  					Elem: &Schema{
  4314  						Type: TypeString,
  4315  					},
  4316  				},
  4317  			},
  4318  
  4319  			Config: map[string]interface{}{
  4320  				"strings": "foo",
  4321  			},
  4322  
  4323  			Err: true,
  4324  			Errors: []error{
  4325  				fmt.Errorf(`strings: should be a list`),
  4326  			},
  4327  		},
  4328  
  4329  		"Unknown list": {
  4330  			Schema: map[string]*Schema{
  4331  				"strings": &Schema{
  4332  					Type:     TypeList,
  4333  					Optional: true,
  4334  					Elem: &Schema{
  4335  						Type: TypeString,
  4336  					},
  4337  				},
  4338  			},
  4339  
  4340  			Config: map[string]interface{}{
  4341  				"strings": hcl2shim.UnknownVariableValue,
  4342  			},
  4343  
  4344  			Err: false,
  4345  		},
  4346  
  4347  		"Unknown + Deprecation": {
  4348  			Schema: map[string]*Schema{
  4349  				"old_news": &Schema{
  4350  					Type:       TypeString,
  4351  					Optional:   true,
  4352  					Deprecated: "please use 'new_news' instead",
  4353  				},
  4354  			},
  4355  
  4356  			Config: map[string]interface{}{
  4357  				"old_news": hcl2shim.UnknownVariableValue,
  4358  			},
  4359  
  4360  			Warnings: []string{
  4361  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  4362  			},
  4363  		},
  4364  
  4365  		"Required sub-resource field": {
  4366  			Schema: map[string]*Schema{
  4367  				"ingress": &Schema{
  4368  					Type: TypeList,
  4369  					Elem: &Resource{
  4370  						Schema: map[string]*Schema{
  4371  							"from": &Schema{
  4372  								Type:     TypeInt,
  4373  								Required: true,
  4374  							},
  4375  						},
  4376  					},
  4377  				},
  4378  			},
  4379  
  4380  			Config: map[string]interface{}{
  4381  				"ingress": []interface{}{
  4382  					map[string]interface{}{},
  4383  				},
  4384  			},
  4385  
  4386  			Err: true,
  4387  		},
  4388  
  4389  		"Good sub-resource": {
  4390  			Schema: map[string]*Schema{
  4391  				"ingress": &Schema{
  4392  					Type:     TypeList,
  4393  					Optional: true,
  4394  					Elem: &Resource{
  4395  						Schema: map[string]*Schema{
  4396  							"from": &Schema{
  4397  								Type:     TypeInt,
  4398  								Required: true,
  4399  							},
  4400  						},
  4401  					},
  4402  				},
  4403  			},
  4404  
  4405  			Config: map[string]interface{}{
  4406  				"ingress": []interface{}{
  4407  					map[string]interface{}{
  4408  						"from": 80,
  4409  					},
  4410  				},
  4411  			},
  4412  
  4413  			Err: false,
  4414  		},
  4415  
  4416  		"Good sub-resource, computed value": {
  4417  			Schema: map[string]*Schema{
  4418  				"ingress": &Schema{
  4419  					Type:     TypeList,
  4420  					Optional: true,
  4421  					Elem: &Resource{
  4422  						Schema: map[string]*Schema{
  4423  							"from": &Schema{
  4424  								Type:     TypeInt,
  4425  								Optional: true,
  4426  							},
  4427  						},
  4428  					},
  4429  				},
  4430  			},
  4431  
  4432  			Config: map[string]interface{}{
  4433  				"ingress": []interface{}{
  4434  					map[string]interface{}{
  4435  						"from": hcl2shim.UnknownVariableValue,
  4436  					},
  4437  				},
  4438  			},
  4439  
  4440  			Err: false,
  4441  		},
  4442  
  4443  		"Invalid/unknown field": {
  4444  			Schema: map[string]*Schema{
  4445  				"availability_zone": &Schema{
  4446  					Type:     TypeString,
  4447  					Optional: true,
  4448  					Computed: true,
  4449  					ForceNew: true,
  4450  				},
  4451  			},
  4452  
  4453  			Config: map[string]interface{}{
  4454  				"foo": "bar",
  4455  			},
  4456  
  4457  			Err: true,
  4458  		},
  4459  
  4460  		"Invalid/unknown field with computed value": {
  4461  			Schema: map[string]*Schema{
  4462  				"availability_zone": &Schema{
  4463  					Type:     TypeString,
  4464  					Optional: true,
  4465  					Computed: true,
  4466  					ForceNew: true,
  4467  				},
  4468  			},
  4469  
  4470  			Config: map[string]interface{}{
  4471  				"foo": hcl2shim.UnknownVariableValue,
  4472  			},
  4473  
  4474  			Err: true,
  4475  		},
  4476  
  4477  		"Computed field set": {
  4478  			Schema: map[string]*Schema{
  4479  				"availability_zone": &Schema{
  4480  					Type:     TypeString,
  4481  					Computed: true,
  4482  				},
  4483  			},
  4484  
  4485  			Config: map[string]interface{}{
  4486  				"availability_zone": "bar",
  4487  			},
  4488  
  4489  			Err: true,
  4490  		},
  4491  
  4492  		"Not a set": {
  4493  			Schema: map[string]*Schema{
  4494  				"ports": &Schema{
  4495  					Type:     TypeSet,
  4496  					Required: true,
  4497  					Elem:     &Schema{Type: TypeInt},
  4498  					Set: func(a interface{}) int {
  4499  						return a.(int)
  4500  					},
  4501  				},
  4502  			},
  4503  
  4504  			Config: map[string]interface{}{
  4505  				"ports": "foo",
  4506  			},
  4507  
  4508  			Err: true,
  4509  		},
  4510  
  4511  		"Maps": {
  4512  			Schema: map[string]*Schema{
  4513  				"user_data": &Schema{
  4514  					Type:     TypeMap,
  4515  					Optional: true,
  4516  				},
  4517  			},
  4518  
  4519  			Config: map[string]interface{}{
  4520  				"user_data": "foo",
  4521  			},
  4522  
  4523  			Err: true,
  4524  		},
  4525  
  4526  		"Good map: data surrounded by extra slice": {
  4527  			Schema: map[string]*Schema{
  4528  				"user_data": &Schema{
  4529  					Type:     TypeMap,
  4530  					Optional: true,
  4531  				},
  4532  			},
  4533  
  4534  			Config: map[string]interface{}{
  4535  				"user_data": []interface{}{
  4536  					map[string]interface{}{
  4537  						"foo": "bar",
  4538  					},
  4539  				},
  4540  			},
  4541  		},
  4542  
  4543  		"Good map": {
  4544  			Schema: map[string]*Schema{
  4545  				"user_data": &Schema{
  4546  					Type:     TypeMap,
  4547  					Optional: true,
  4548  				},
  4549  			},
  4550  
  4551  			Config: map[string]interface{}{
  4552  				"user_data": map[string]interface{}{
  4553  					"foo": "bar",
  4554  				},
  4555  			},
  4556  		},
  4557  
  4558  		"Map with type specified as value type": {
  4559  			Schema: map[string]*Schema{
  4560  				"user_data": &Schema{
  4561  					Type:     TypeMap,
  4562  					Optional: true,
  4563  					Elem:     TypeBool,
  4564  				},
  4565  			},
  4566  
  4567  			Config: map[string]interface{}{
  4568  				"user_data": map[string]interface{}{
  4569  					"foo": "not_a_bool",
  4570  				},
  4571  			},
  4572  
  4573  			Err: true,
  4574  		},
  4575  
  4576  		"Map with type specified as nested Schema": {
  4577  			Schema: map[string]*Schema{
  4578  				"user_data": &Schema{
  4579  					Type:     TypeMap,
  4580  					Optional: true,
  4581  					Elem:     &Schema{Type: TypeBool},
  4582  				},
  4583  			},
  4584  
  4585  			Config: map[string]interface{}{
  4586  				"user_data": map[string]interface{}{
  4587  					"foo": "not_a_bool",
  4588  				},
  4589  			},
  4590  
  4591  			Err: true,
  4592  		},
  4593  
  4594  		"Bad map: just a slice": {
  4595  			Schema: map[string]*Schema{
  4596  				"user_data": &Schema{
  4597  					Type:     TypeMap,
  4598  					Optional: true,
  4599  				},
  4600  			},
  4601  
  4602  			Config: map[string]interface{}{
  4603  				"user_data": []interface{}{
  4604  					"foo",
  4605  				},
  4606  			},
  4607  
  4608  			Err: true,
  4609  		},
  4610  
  4611  		"Good set: config has slice with single interpolated value": {
  4612  			Schema: map[string]*Schema{
  4613  				"security_groups": &Schema{
  4614  					Type:     TypeSet,
  4615  					Optional: true,
  4616  					Computed: true,
  4617  					ForceNew: true,
  4618  					Elem:     &Schema{Type: TypeString},
  4619  					Set: func(v interface{}) int {
  4620  						return len(v.(string))
  4621  					},
  4622  				},
  4623  			},
  4624  
  4625  			Config: map[string]interface{}{
  4626  				"security_groups": []interface{}{"${var.foo}"},
  4627  			},
  4628  
  4629  			Err: false,
  4630  		},
  4631  
  4632  		"Bad set: config has single interpolated value": {
  4633  			Schema: map[string]*Schema{
  4634  				"security_groups": &Schema{
  4635  					Type:     TypeSet,
  4636  					Optional: true,
  4637  					Computed: true,
  4638  					ForceNew: true,
  4639  					Elem:     &Schema{Type: TypeString},
  4640  				},
  4641  			},
  4642  
  4643  			Config: map[string]interface{}{
  4644  				"security_groups": "${var.foo}",
  4645  			},
  4646  
  4647  			Err: true,
  4648  		},
  4649  
  4650  		"Bad, subresource should not allow unknown elements": {
  4651  			Schema: map[string]*Schema{
  4652  				"ingress": &Schema{
  4653  					Type:     TypeList,
  4654  					Optional: true,
  4655  					Elem: &Resource{
  4656  						Schema: map[string]*Schema{
  4657  							"port": &Schema{
  4658  								Type:     TypeInt,
  4659  								Required: true,
  4660  							},
  4661  						},
  4662  					},
  4663  				},
  4664  			},
  4665  
  4666  			Config: map[string]interface{}{
  4667  				"ingress": []interface{}{
  4668  					map[string]interface{}{
  4669  						"port":  80,
  4670  						"other": "yes",
  4671  					},
  4672  				},
  4673  			},
  4674  
  4675  			Err: true,
  4676  		},
  4677  
  4678  		"Bad, subresource should not allow invalid types": {
  4679  			Schema: map[string]*Schema{
  4680  				"ingress": &Schema{
  4681  					Type:     TypeList,
  4682  					Optional: true,
  4683  					Elem: &Resource{
  4684  						Schema: map[string]*Schema{
  4685  							"port": &Schema{
  4686  								Type:     TypeInt,
  4687  								Required: true,
  4688  							},
  4689  						},
  4690  					},
  4691  				},
  4692  			},
  4693  
  4694  			Config: map[string]interface{}{
  4695  				"ingress": []interface{}{
  4696  					map[string]interface{}{
  4697  						"port": "bad",
  4698  					},
  4699  				},
  4700  			},
  4701  
  4702  			Err: true,
  4703  		},
  4704  
  4705  		"Bad, should not allow lists to be assigned to string attributes": {
  4706  			Schema: map[string]*Schema{
  4707  				"availability_zone": &Schema{
  4708  					Type:     TypeString,
  4709  					Required: true,
  4710  				},
  4711  			},
  4712  
  4713  			Config: map[string]interface{}{
  4714  				"availability_zone": []interface{}{"foo", "bar", "baz"},
  4715  			},
  4716  
  4717  			Err: true,
  4718  		},
  4719  
  4720  		"Bad, should not allow maps to be assigned to string attributes": {
  4721  			Schema: map[string]*Schema{
  4722  				"availability_zone": &Schema{
  4723  					Type:     TypeString,
  4724  					Required: true,
  4725  				},
  4726  			},
  4727  
  4728  			Config: map[string]interface{}{
  4729  				"availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"},
  4730  			},
  4731  
  4732  			Err: true,
  4733  		},
  4734  
  4735  		"Deprecated attribute usage generates warning, but not error": {
  4736  			Schema: map[string]*Schema{
  4737  				"old_news": &Schema{
  4738  					Type:       TypeString,
  4739  					Optional:   true,
  4740  					Deprecated: "please use 'new_news' instead",
  4741  				},
  4742  			},
  4743  
  4744  			Config: map[string]interface{}{
  4745  				"old_news": "extra extra!",
  4746  			},
  4747  
  4748  			Err: false,
  4749  
  4750  			Warnings: []string{
  4751  				"\"old_news\": [DEPRECATED] please use 'new_news' instead",
  4752  			},
  4753  		},
  4754  
  4755  		"Deprecated generates no warnings if attr not used": {
  4756  			Schema: map[string]*Schema{
  4757  				"old_news": &Schema{
  4758  					Type:       TypeString,
  4759  					Optional:   true,
  4760  					Deprecated: "please use 'new_news' instead",
  4761  				},
  4762  			},
  4763  
  4764  			Err: false,
  4765  
  4766  			Warnings: nil,
  4767  		},
  4768  
  4769  		"Removed attribute usage generates error": {
  4770  			Schema: map[string]*Schema{
  4771  				"long_gone": &Schema{
  4772  					Type:     TypeString,
  4773  					Optional: true,
  4774  					Removed:  "no longer supported by Cloud API",
  4775  				},
  4776  			},
  4777  
  4778  			Config: map[string]interface{}{
  4779  				"long_gone": "still here!",
  4780  			},
  4781  
  4782  			Err: true,
  4783  			Errors: []error{
  4784  				fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"),
  4785  			},
  4786  		},
  4787  
  4788  		"Removed generates no errors if attr not used": {
  4789  			Schema: map[string]*Schema{
  4790  				"long_gone": &Schema{
  4791  					Type:     TypeString,
  4792  					Optional: true,
  4793  					Removed:  "no longer supported by Cloud API",
  4794  				},
  4795  			},
  4796  
  4797  			Err: false,
  4798  		},
  4799  
  4800  		"Conflicting attributes generate error": {
  4801  			Schema: map[string]*Schema{
  4802  				"b": &Schema{
  4803  					Type:     TypeString,
  4804  					Optional: true,
  4805  				},
  4806  				"a": &Schema{
  4807  					Type:          TypeString,
  4808  					Optional:      true,
  4809  					ConflictsWith: []string{"b"},
  4810  				},
  4811  			},
  4812  
  4813  			Config: map[string]interface{}{
  4814  				"b": "b-val",
  4815  				"a": "a-val",
  4816  			},
  4817  
  4818  			Err: true,
  4819  			Errors: []error{
  4820  				fmt.Errorf("\"a\": conflicts with b"),
  4821  			},
  4822  		},
  4823  
  4824  		"Conflicting attributes okay when unknown 1": {
  4825  			Schema: map[string]*Schema{
  4826  				"b": &Schema{
  4827  					Type:     TypeString,
  4828  					Optional: true,
  4829  				},
  4830  				"a": &Schema{
  4831  					Type:          TypeString,
  4832  					Optional:      true,
  4833  					ConflictsWith: []string{"b"},
  4834  				},
  4835  			},
  4836  
  4837  			Config: map[string]interface{}{
  4838  				"b": "b-val",
  4839  				"a": hcl2shim.UnknownVariableValue,
  4840  			},
  4841  
  4842  			Err: false,
  4843  		},
  4844  
  4845  		"Conflicting attributes okay when unknown 2": {
  4846  			Schema: map[string]*Schema{
  4847  				"b": &Schema{
  4848  					Type:     TypeString,
  4849  					Optional: true,
  4850  				},
  4851  				"a": &Schema{
  4852  					Type:          TypeString,
  4853  					Optional:      true,
  4854  					ConflictsWith: []string{"b"},
  4855  				},
  4856  			},
  4857  
  4858  			Config: map[string]interface{}{
  4859  				"b": hcl2shim.UnknownVariableValue,
  4860  				"a": "a-val",
  4861  			},
  4862  
  4863  			Err: false,
  4864  		},
  4865  
  4866  		"Conflicting attributes generate error even if one is unknown": {
  4867  			Schema: map[string]*Schema{
  4868  				"b": &Schema{
  4869  					Type:          TypeString,
  4870  					Optional:      true,
  4871  					ConflictsWith: []string{"a", "c"},
  4872  				},
  4873  				"a": &Schema{
  4874  					Type:          TypeString,
  4875  					Optional:      true,
  4876  					ConflictsWith: []string{"b", "c"},
  4877  				},
  4878  				"c": &Schema{
  4879  					Type:          TypeString,
  4880  					Optional:      true,
  4881  					ConflictsWith: []string{"b", "a"},
  4882  				},
  4883  			},
  4884  
  4885  			Config: map[string]interface{}{
  4886  				"b": hcl2shim.UnknownVariableValue,
  4887  				"a": "a-val",
  4888  				"c": "c-val",
  4889  			},
  4890  
  4891  			Err: true,
  4892  			Errors: []error{
  4893  				fmt.Errorf("\"a\": conflicts with c"),
  4894  				fmt.Errorf("\"c\": conflicts with a"),
  4895  			},
  4896  		},
  4897  
  4898  		"Required attribute & undefined conflicting optional are good": {
  4899  			Schema: map[string]*Schema{
  4900  				"required_att": &Schema{
  4901  					Type:     TypeString,
  4902  					Required: true,
  4903  				},
  4904  				"optional_att": &Schema{
  4905  					Type:          TypeString,
  4906  					Optional:      true,
  4907  					ConflictsWith: []string{"required_att"},
  4908  				},
  4909  			},
  4910  
  4911  			Config: map[string]interface{}{
  4912  				"required_att": "required-val",
  4913  			},
  4914  
  4915  			Err: false,
  4916  		},
  4917  
  4918  		"Required conflicting attribute & defined optional generate error": {
  4919  			Schema: map[string]*Schema{
  4920  				"required_att": &Schema{
  4921  					Type:     TypeString,
  4922  					Required: true,
  4923  				},
  4924  				"optional_att": &Schema{
  4925  					Type:          TypeString,
  4926  					Optional:      true,
  4927  					ConflictsWith: []string{"required_att"},
  4928  				},
  4929  			},
  4930  
  4931  			Config: map[string]interface{}{
  4932  				"required_att": "required-val",
  4933  				"optional_att": "optional-val",
  4934  			},
  4935  
  4936  			Err: true,
  4937  			Errors: []error{
  4938  				fmt.Errorf(`"optional_att": conflicts with required_att`),
  4939  			},
  4940  		},
  4941  
  4942  		"Computed + Optional fields conflicting with each other": {
  4943  			Schema: map[string]*Schema{
  4944  				"foo_att": &Schema{
  4945  					Type:          TypeString,
  4946  					Optional:      true,
  4947  					Computed:      true,
  4948  					ConflictsWith: []string{"bar_att"},
  4949  				},
  4950  				"bar_att": &Schema{
  4951  					Type:          TypeString,
  4952  					Optional:      true,
  4953  					Computed:      true,
  4954  					ConflictsWith: []string{"foo_att"},
  4955  				},
  4956  			},
  4957  
  4958  			Config: map[string]interface{}{
  4959  				"foo_att": "foo-val",
  4960  				"bar_att": "bar-val",
  4961  			},
  4962  
  4963  			Err: true,
  4964  			Errors: []error{
  4965  				fmt.Errorf(`"foo_att": conflicts with bar_att`),
  4966  				fmt.Errorf(`"bar_att": conflicts with foo_att`),
  4967  			},
  4968  		},
  4969  
  4970  		"Computed + Optional fields NOT conflicting with each other": {
  4971  			Schema: map[string]*Schema{
  4972  				"foo_att": &Schema{
  4973  					Type:          TypeString,
  4974  					Optional:      true,
  4975  					Computed:      true,
  4976  					ConflictsWith: []string{"bar_att"},
  4977  				},
  4978  				"bar_att": &Schema{
  4979  					Type:          TypeString,
  4980  					Optional:      true,
  4981  					Computed:      true,
  4982  					ConflictsWith: []string{"foo_att"},
  4983  				},
  4984  			},
  4985  
  4986  			Config: map[string]interface{}{
  4987  				"foo_att": "foo-val",
  4988  			},
  4989  
  4990  			Err: false,
  4991  		},
  4992  
  4993  		"Computed + Optional fields that conflict with none set": {
  4994  			Schema: map[string]*Schema{
  4995  				"foo_att": &Schema{
  4996  					Type:          TypeString,
  4997  					Optional:      true,
  4998  					Computed:      true,
  4999  					ConflictsWith: []string{"bar_att"},
  5000  				},
  5001  				"bar_att": &Schema{
  5002  					Type:          TypeString,
  5003  					Optional:      true,
  5004  					Computed:      true,
  5005  					ConflictsWith: []string{"foo_att"},
  5006  				},
  5007  			},
  5008  
  5009  			Config: map[string]interface{}{},
  5010  
  5011  			Err: false,
  5012  		},
  5013  
  5014  		"Good with ValidateFunc": {
  5015  			Schema: map[string]*Schema{
  5016  				"validate_me": &Schema{
  5017  					Type:     TypeString,
  5018  					Required: true,
  5019  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5020  						return
  5021  					},
  5022  				},
  5023  			},
  5024  			Config: map[string]interface{}{
  5025  				"validate_me": "valid",
  5026  			},
  5027  			Err: false,
  5028  		},
  5029  
  5030  		"Bad with ValidateFunc": {
  5031  			Schema: map[string]*Schema{
  5032  				"validate_me": &Schema{
  5033  					Type:     TypeString,
  5034  					Required: true,
  5035  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5036  						es = append(es, fmt.Errorf("something is not right here"))
  5037  						return
  5038  					},
  5039  				},
  5040  			},
  5041  			Config: map[string]interface{}{
  5042  				"validate_me": "invalid",
  5043  			},
  5044  			Err: true,
  5045  			Errors: []error{
  5046  				fmt.Errorf(`something is not right here`),
  5047  			},
  5048  		},
  5049  
  5050  		"ValidateFunc not called when type does not match": {
  5051  			Schema: map[string]*Schema{
  5052  				"number": &Schema{
  5053  					Type:     TypeInt,
  5054  					Required: true,
  5055  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5056  						t.Fatalf("Should not have gotten validate call")
  5057  						return
  5058  					},
  5059  				},
  5060  			},
  5061  			Config: map[string]interface{}{
  5062  				"number": "NaN",
  5063  			},
  5064  			Err: true,
  5065  		},
  5066  
  5067  		"ValidateFunc gets decoded type": {
  5068  			Schema: map[string]*Schema{
  5069  				"maybe": &Schema{
  5070  					Type:     TypeBool,
  5071  					Required: true,
  5072  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5073  						if _, ok := v.(bool); !ok {
  5074  							t.Fatalf("Expected bool, got: %#v", v)
  5075  						}
  5076  						return
  5077  					},
  5078  				},
  5079  			},
  5080  			Config: map[string]interface{}{
  5081  				"maybe": "true",
  5082  			},
  5083  		},
  5084  
  5085  		"ValidateFunc is not called with a computed value": {
  5086  			Schema: map[string]*Schema{
  5087  				"validate_me": &Schema{
  5088  					Type:     TypeString,
  5089  					Required: true,
  5090  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5091  						es = append(es, fmt.Errorf("something is not right here"))
  5092  						return
  5093  					},
  5094  				},
  5095  			},
  5096  			Config: map[string]interface{}{
  5097  				"validate_me": hcl2shim.UnknownVariableValue,
  5098  			},
  5099  
  5100  			Err: false,
  5101  		},
  5102  
  5103  		"special timeouts field": {
  5104  			Schema: map[string]*Schema{
  5105  				"availability_zone": &Schema{
  5106  					Type:     TypeString,
  5107  					Optional: true,
  5108  					Computed: true,
  5109  					ForceNew: true,
  5110  				},
  5111  			},
  5112  
  5113  			Config: map[string]interface{}{
  5114  				TimeoutsConfigKey: "bar",
  5115  			},
  5116  
  5117  			Err: false,
  5118  		},
  5119  
  5120  		"invalid bool field": {
  5121  			Schema: map[string]*Schema{
  5122  				"bool_field": {
  5123  					Type:     TypeBool,
  5124  					Optional: true,
  5125  				},
  5126  			},
  5127  			Config: map[string]interface{}{
  5128  				"bool_field": "abcdef",
  5129  			},
  5130  			Err: true,
  5131  		},
  5132  		"invalid integer field": {
  5133  			Schema: map[string]*Schema{
  5134  				"integer_field": {
  5135  					Type:     TypeInt,
  5136  					Optional: true,
  5137  				},
  5138  			},
  5139  			Config: map[string]interface{}{
  5140  				"integer_field": "abcdef",
  5141  			},
  5142  			Err: true,
  5143  		},
  5144  		"invalid float field": {
  5145  			Schema: map[string]*Schema{
  5146  				"float_field": {
  5147  					Type:     TypeFloat,
  5148  					Optional: true,
  5149  				},
  5150  			},
  5151  			Config: map[string]interface{}{
  5152  				"float_field": "abcdef",
  5153  			},
  5154  			Err: true,
  5155  		},
  5156  
  5157  		// Invalid map values
  5158  		"invalid bool map value": {
  5159  			Schema: map[string]*Schema{
  5160  				"boolMap": &Schema{
  5161  					Type:     TypeMap,
  5162  					Elem:     TypeBool,
  5163  					Optional: true,
  5164  				},
  5165  			},
  5166  			Config: map[string]interface{}{
  5167  				"boolMap": map[string]interface{}{
  5168  					"boolField": "notbool",
  5169  				},
  5170  			},
  5171  			Err: true,
  5172  		},
  5173  		"invalid int map value": {
  5174  			Schema: map[string]*Schema{
  5175  				"intMap": &Schema{
  5176  					Type:     TypeMap,
  5177  					Elem:     TypeInt,
  5178  					Optional: true,
  5179  				},
  5180  			},
  5181  			Config: map[string]interface{}{
  5182  				"intMap": map[string]interface{}{
  5183  					"intField": "notInt",
  5184  				},
  5185  			},
  5186  			Err: true,
  5187  		},
  5188  		"invalid float map value": {
  5189  			Schema: map[string]*Schema{
  5190  				"floatMap": &Schema{
  5191  					Type:     TypeMap,
  5192  					Elem:     TypeFloat,
  5193  					Optional: true,
  5194  				},
  5195  			},
  5196  			Config: map[string]interface{}{
  5197  				"floatMap": map[string]interface{}{
  5198  					"floatField": "notFloat",
  5199  				},
  5200  			},
  5201  			Err: true,
  5202  		},
  5203  
  5204  		"map with positive validate function": {
  5205  			Schema: map[string]*Schema{
  5206  				"floatInt": &Schema{
  5207  					Type:     TypeMap,
  5208  					Elem:     TypeInt,
  5209  					Optional: true,
  5210  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5211  						return
  5212  					},
  5213  				},
  5214  			},
  5215  			Config: map[string]interface{}{
  5216  				"floatInt": map[string]interface{}{
  5217  					"rightAnswer": "42",
  5218  					"tooMuch":     "43",
  5219  				},
  5220  			},
  5221  			Err: false,
  5222  		},
  5223  		"map with negative validate function": {
  5224  			Schema: map[string]*Schema{
  5225  				"floatInt": &Schema{
  5226  					Type:     TypeMap,
  5227  					Elem:     TypeInt,
  5228  					Optional: true,
  5229  					ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5230  						es = append(es, fmt.Errorf("this is not fine"))
  5231  						return
  5232  					},
  5233  				},
  5234  			},
  5235  			Config: map[string]interface{}{
  5236  				"floatInt": map[string]interface{}{
  5237  					"rightAnswer": "42",
  5238  					"tooMuch":     "43",
  5239  				},
  5240  			},
  5241  			Err: true,
  5242  		},
  5243  
  5244  		// The Validation function should not see interpolation strings from
  5245  		// non-computed values.
  5246  		"set with partially computed list and map": {
  5247  			Schema: map[string]*Schema{
  5248  				"outer": &Schema{
  5249  					Type:     TypeSet,
  5250  					Optional: true,
  5251  					Computed: true,
  5252  					Elem: &Resource{
  5253  						Schema: map[string]*Schema{
  5254  							"list": &Schema{
  5255  								Type:     TypeList,
  5256  								Optional: true,
  5257  								Elem: &Schema{
  5258  									Type: TypeString,
  5259  									ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
  5260  										if strings.HasPrefix(v.(string), "${") {
  5261  											es = append(es, fmt.Errorf("should not have interpolations"))
  5262  										}
  5263  										return
  5264  									},
  5265  								},
  5266  							},
  5267  						},
  5268  					},
  5269  				},
  5270  			},
  5271  			Config: map[string]interface{}{
  5272  				"outer": []interface{}{
  5273  					map[string]interface{}{
  5274  						"list": []interface{}{"A", hcl2shim.UnknownVariableValue, "c"},
  5275  					},
  5276  				},
  5277  			},
  5278  			Err: false,
  5279  		},
  5280  		"unexpected nils values": {
  5281  			Schema: map[string]*Schema{
  5282  				"strings": &Schema{
  5283  					Type:     TypeList,
  5284  					Optional: true,
  5285  					Elem: &Schema{
  5286  						Type: TypeString,
  5287  					},
  5288  				},
  5289  				"block": &Schema{
  5290  					Type:     TypeList,
  5291  					Optional: true,
  5292  					Elem: &Resource{
  5293  						Schema: map[string]*Schema{
  5294  							"int": &Schema{
  5295  								Type:     TypeInt,
  5296  								Required: true,
  5297  							},
  5298  						},
  5299  					},
  5300  				},
  5301  			},
  5302  
  5303  			Config: map[string]interface{}{
  5304  				"strings": []interface{}{"1", nil},
  5305  				"block": []interface{}{map[string]interface{}{
  5306  					"int": nil,
  5307  				},
  5308  					nil,
  5309  				},
  5310  			},
  5311  			Err: true,
  5312  		},
  5313  	}
  5314  
  5315  	for tn, tc := range cases {
  5316  		t.Run(tn, func(t *testing.T) {
  5317  			c := tofu.NewResourceConfigRaw(tc.Config)
  5318  
  5319  			ws, es := schemaMap(tc.Schema).Validate(c)
  5320  			if len(es) > 0 != tc.Err {
  5321  				if len(es) == 0 {
  5322  					t.Errorf("%q: no errors", tn)
  5323  				}
  5324  
  5325  				for _, e := range es {
  5326  					t.Errorf("%q: err: %s", tn, e)
  5327  				}
  5328  
  5329  				t.FailNow()
  5330  			}
  5331  
  5332  			if !reflect.DeepEqual(ws, tc.Warnings) {
  5333  				t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws)
  5334  			}
  5335  
  5336  			if tc.Errors != nil {
  5337  				sort.Sort(errorSort(es))
  5338  				sort.Sort(errorSort(tc.Errors))
  5339  
  5340  				if !reflect.DeepEqual(es, tc.Errors) {
  5341  					t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es)
  5342  				}
  5343  			}
  5344  		})
  5345  
  5346  	}
  5347  }
  5348  
  5349  func TestSchemaSet_ValidateMaxItems(t *testing.T) {
  5350  	cases := map[string]struct {
  5351  		Schema          map[string]*Schema
  5352  		State           *tofu.InstanceState
  5353  		Config          map[string]interface{}
  5354  		ConfigVariables map[string]string
  5355  		Diff            *tofu.InstanceDiff
  5356  		Err             bool
  5357  		Errors          []error
  5358  	}{
  5359  		"#0": {
  5360  			Schema: map[string]*Schema{
  5361  				"aliases": &Schema{
  5362  					Type:     TypeSet,
  5363  					Optional: true,
  5364  					MaxItems: 1,
  5365  					Elem:     &Schema{Type: TypeString},
  5366  				},
  5367  			},
  5368  			State: nil,
  5369  			Config: map[string]interface{}{
  5370  				"aliases": []interface{}{"foo", "bar"},
  5371  			},
  5372  			Diff: nil,
  5373  			Err:  true,
  5374  			Errors: []error{
  5375  				fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"),
  5376  			},
  5377  		},
  5378  		"#1": {
  5379  			Schema: map[string]*Schema{
  5380  				"aliases": &Schema{
  5381  					Type:     TypeSet,
  5382  					Optional: true,
  5383  					Elem:     &Schema{Type: TypeString},
  5384  				},
  5385  			},
  5386  			State: nil,
  5387  			Config: map[string]interface{}{
  5388  				"aliases": []interface{}{"foo", "bar"},
  5389  			},
  5390  			Diff:   nil,
  5391  			Err:    false,
  5392  			Errors: nil,
  5393  		},
  5394  		"#2": {
  5395  			Schema: map[string]*Schema{
  5396  				"aliases": &Schema{
  5397  					Type:     TypeSet,
  5398  					Optional: true,
  5399  					MaxItems: 1,
  5400  					Elem:     &Schema{Type: TypeString},
  5401  				},
  5402  			},
  5403  			State: nil,
  5404  			Config: map[string]interface{}{
  5405  				"aliases": []interface{}{"foo"},
  5406  			},
  5407  			Diff:   nil,
  5408  			Err:    false,
  5409  			Errors: nil,
  5410  		},
  5411  	}
  5412  
  5413  	for tn, tc := range cases {
  5414  		c := tofu.NewResourceConfigRaw(tc.Config)
  5415  		_, es := schemaMap(tc.Schema).Validate(c)
  5416  
  5417  		if len(es) > 0 != tc.Err {
  5418  			if len(es) == 0 {
  5419  				t.Errorf("%q: no errors", tn)
  5420  			}
  5421  
  5422  			for _, e := range es {
  5423  				t.Errorf("%q: err: %s", tn, e)
  5424  			}
  5425  
  5426  			t.FailNow()
  5427  		}
  5428  
  5429  		if tc.Errors != nil {
  5430  			if !reflect.DeepEqual(es, tc.Errors) {
  5431  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5432  			}
  5433  		}
  5434  	}
  5435  }
  5436  
  5437  func TestSchemaSet_ValidateMinItems(t *testing.T) {
  5438  	cases := map[string]struct {
  5439  		Schema          map[string]*Schema
  5440  		State           *tofu.InstanceState
  5441  		Config          map[string]interface{}
  5442  		ConfigVariables map[string]string
  5443  		Diff            *tofu.InstanceDiff
  5444  		Err             bool
  5445  		Errors          []error
  5446  	}{
  5447  		"#0": {
  5448  			Schema: map[string]*Schema{
  5449  				"aliases": &Schema{
  5450  					Type:     TypeSet,
  5451  					Optional: true,
  5452  					MinItems: 2,
  5453  					Elem:     &Schema{Type: TypeString},
  5454  				},
  5455  			},
  5456  			State: nil,
  5457  			Config: map[string]interface{}{
  5458  				"aliases": []interface{}{"foo", "bar"},
  5459  			},
  5460  			Diff:   nil,
  5461  			Err:    false,
  5462  			Errors: nil,
  5463  		},
  5464  		"#1": {
  5465  			Schema: map[string]*Schema{
  5466  				"aliases": &Schema{
  5467  					Type:     TypeSet,
  5468  					Optional: true,
  5469  					Elem:     &Schema{Type: TypeString},
  5470  				},
  5471  			},
  5472  			State: nil,
  5473  			Config: map[string]interface{}{
  5474  				"aliases": []interface{}{"foo", "bar"},
  5475  			},
  5476  			Diff:   nil,
  5477  			Err:    false,
  5478  			Errors: nil,
  5479  		},
  5480  		"#2": {
  5481  			Schema: map[string]*Schema{
  5482  				"aliases": &Schema{
  5483  					Type:     TypeSet,
  5484  					Optional: true,
  5485  					MinItems: 2,
  5486  					Elem:     &Schema{Type: TypeString},
  5487  				},
  5488  			},
  5489  			State: nil,
  5490  			Config: map[string]interface{}{
  5491  				"aliases": []interface{}{"foo"},
  5492  			},
  5493  			Diff: nil,
  5494  			Err:  true,
  5495  			Errors: []error{
  5496  				fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"),
  5497  			},
  5498  		},
  5499  	}
  5500  
  5501  	for tn, tc := range cases {
  5502  		c := tofu.NewResourceConfigRaw(tc.Config)
  5503  		_, es := schemaMap(tc.Schema).Validate(c)
  5504  
  5505  		if len(es) > 0 != tc.Err {
  5506  			if len(es) == 0 {
  5507  				t.Errorf("%q: no errors", tn)
  5508  			}
  5509  
  5510  			for _, e := range es {
  5511  				t.Errorf("%q: err: %s", tn, e)
  5512  			}
  5513  
  5514  			t.FailNow()
  5515  		}
  5516  
  5517  		if tc.Errors != nil {
  5518  			if !reflect.DeepEqual(es, tc.Errors) {
  5519  				t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es)
  5520  			}
  5521  		}
  5522  	}
  5523  }
  5524  
  5525  // errorSort implements sort.Interface to sort errors by their error message
  5526  type errorSort []error
  5527  
  5528  func (e errorSort) Len() int      { return len(e) }
  5529  func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  5530  func (e errorSort) Less(i, j int) bool {
  5531  	return e[i].Error() < e[j].Error()
  5532  }
  5533  
  5534  func TestSchemaMapDeepCopy(t *testing.T) {
  5535  	schema := map[string]*Schema{
  5536  		"foo": &Schema{
  5537  			Type: TypeString,
  5538  		},
  5539  	}
  5540  	source := schemaMap(schema)
  5541  	dest := source.DeepCopy()
  5542  	dest["foo"].ForceNew = true
  5543  	if reflect.DeepEqual(source, dest) {
  5544  		t.Fatalf("source and dest should not match")
  5545  	}
  5546  }