github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/shims_test.go (about)

     1  package schema
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strconv"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/google/go-cmp/cmp/cmpopts"
    14  	"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
    15  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema"
    16  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    17  	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
    18  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
    19  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    20  	"github.com/zclconf/go-cty/cty"
    21  )
    22  
    23  var (
    24  	typeComparer  = cmp.Comparer(cty.Type.Equals)
    25  	valueComparer = cmp.Comparer(cty.Value.RawEquals)
    26  	equateEmpty   = cmpopts.EquateEmpty()
    27  )
    28  
    29  func testApplyDiff(t *testing.T,
    30  	resource *Resource,
    31  	state, expected *terraform.InstanceState,
    32  	diff *terraform.InstanceDiff) {
    33  
    34  	testSchema := providers.Schema{
    35  		Version: int64(resource.SchemaVersion),
    36  		Block:   resourceSchemaToBlock(resource.Schema),
    37  	}
    38  
    39  	stateVal, err := StateValueFromInstanceState(state, testSchema.Block.ImpliedType())
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	newState, err := ApplyDiff(stateVal, diff, testSchema.Block)
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  
    49  	// verify that "id" is correct
    50  	id := newState.AsValueMap()["id"]
    51  
    52  	switch {
    53  	case diff.Destroy || diff.DestroyDeposed || diff.DestroyTainted:
    54  		// there should be no id
    55  		if !id.IsNull() {
    56  			t.Fatalf("destroyed instance should have no id: %#v", id)
    57  		}
    58  	default:
    59  		// the "id" field always exists and is computed, so it must have a
    60  		// valid value or be unknown.
    61  		if id.IsNull() {
    62  			t.Fatal("new instance state cannot have a null id")
    63  		}
    64  
    65  		if id.IsKnown() && id.AsString() == "" {
    66  			t.Fatal("new instance id cannot be an empty string")
    67  		}
    68  	}
    69  
    70  	// Resource.Meta will be hanlded separately, so it's OK that we lose the
    71  	// timeout values here.
    72  	expectedState, err := StateValueFromInstanceState(expected, testSchema.Block.ImpliedType())
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  
    77  	if !cmp.Equal(expectedState, newState, equateEmpty, typeComparer, valueComparer) {
    78  		t.Fatalf(cmp.Diff(expectedState, newState, equateEmpty, typeComparer, valueComparer))
    79  	}
    80  }
    81  
    82  func TestShimResourcePlan_destroyCreate(t *testing.T) {
    83  	r := &Resource{
    84  		SchemaVersion: 2,
    85  		Schema: map[string]*Schema{
    86  			"foo": {
    87  				Type:     TypeInt,
    88  				Optional: true,
    89  				ForceNew: true,
    90  			},
    91  		},
    92  	}
    93  
    94  	d := &terraform.InstanceDiff{
    95  		Attributes: map[string]*terraform.ResourceAttrDiff{
    96  			"foo": {
    97  				RequiresNew: true,
    98  				Old:         "3",
    99  				New:         "42",
   100  			},
   101  		},
   102  	}
   103  
   104  	state := &terraform.InstanceState{
   105  		Attributes: map[string]string{"foo": "3"},
   106  	}
   107  
   108  	expected := &terraform.InstanceState{
   109  		ID: hcl2shim.UnknownVariableValue,
   110  		Attributes: map[string]string{
   111  			"id":  hcl2shim.UnknownVariableValue,
   112  			"foo": "42",
   113  		},
   114  		Meta: map[string]interface{}{
   115  			"schema_version": "2",
   116  		},
   117  	}
   118  
   119  	testApplyDiff(t, r, state, expected, d)
   120  }
   121  
   122  func TestShimResourceApply_create(t *testing.T) {
   123  	r := &Resource{
   124  		SchemaVersion: 2,
   125  		Schema: map[string]*Schema{
   126  			"foo": {
   127  				Type:     TypeInt,
   128  				Optional: true,
   129  			},
   130  		},
   131  	}
   132  
   133  	called := false
   134  	r.Create = func(d *ResourceData, m interface{}) error {
   135  		called = true
   136  		d.SetId("foo")
   137  		return nil
   138  	}
   139  
   140  	var s *terraform.InstanceState = nil
   141  
   142  	d := &terraform.InstanceDiff{
   143  		Attributes: map[string]*terraform.ResourceAttrDiff{
   144  			"foo": {
   145  				New: "42",
   146  			},
   147  		},
   148  	}
   149  
   150  	actual, err := r.Apply(s, d, nil)
   151  	if err != nil {
   152  		t.Fatalf("err: %s", err)
   153  	}
   154  
   155  	if !called {
   156  		t.Fatal("not called")
   157  	}
   158  
   159  	expected := &terraform.InstanceState{
   160  		ID: "foo",
   161  		Attributes: map[string]string{
   162  			"id":  "foo",
   163  			"foo": "42",
   164  		},
   165  		Meta: map[string]interface{}{
   166  			"schema_version": "2",
   167  		},
   168  	}
   169  
   170  	if !reflect.DeepEqual(actual, expected) {
   171  		t.Fatalf("bad: %#v", actual)
   172  	}
   173  
   174  	// Shim
   175  	// now that we have our diff and desired state, see if we can reproduce
   176  	// that with the shim
   177  	// we're not testing Resource.Create, so we need to start with the "created" state
   178  	createdState := &terraform.InstanceState{
   179  		ID:         "foo",
   180  		Attributes: map[string]string{"id": "foo"},
   181  	}
   182  
   183  	testApplyDiff(t, r, createdState, expected, d)
   184  }
   185  
   186  func TestShimResourceApply_Timeout_state(t *testing.T) {
   187  	r := &Resource{
   188  		SchemaVersion: 2,
   189  		Schema: map[string]*Schema{
   190  			"foo": {
   191  				Type:     TypeInt,
   192  				Optional: true,
   193  			},
   194  		},
   195  		Timeouts: &ResourceTimeout{
   196  			Create: DefaultTimeout(40 * time.Minute),
   197  			Update: DefaultTimeout(80 * time.Minute),
   198  			Delete: DefaultTimeout(40 * time.Minute),
   199  		},
   200  	}
   201  
   202  	called := false
   203  	r.Create = func(d *ResourceData, m interface{}) error {
   204  		called = true
   205  		d.SetId("foo")
   206  		return nil
   207  	}
   208  
   209  	var s *terraform.InstanceState = nil
   210  
   211  	d := &terraform.InstanceDiff{
   212  		Attributes: map[string]*terraform.ResourceAttrDiff{
   213  			"foo": {
   214  				New: "42",
   215  			},
   216  		},
   217  	}
   218  
   219  	diffTimeout := &ResourceTimeout{
   220  		Create: DefaultTimeout(40 * time.Minute),
   221  		Update: DefaultTimeout(80 * time.Minute),
   222  		Delete: DefaultTimeout(40 * time.Minute),
   223  	}
   224  
   225  	if err := diffTimeout.DiffEncode(d); err != nil {
   226  		t.Fatalf("Error encoding timeout to diff: %s", err)
   227  	}
   228  
   229  	actual, err := r.Apply(s, d, nil)
   230  	if err != nil {
   231  		t.Fatalf("err: %s", err)
   232  	}
   233  
   234  	if !called {
   235  		t.Fatal("not called")
   236  	}
   237  
   238  	expected := &terraform.InstanceState{
   239  		ID: "foo",
   240  		Attributes: map[string]string{
   241  			"id":  "foo",
   242  			"foo": "42",
   243  		},
   244  		Meta: map[string]interface{}{
   245  			"schema_version": "2",
   246  			TimeoutKey:       expectedForValues(40, 0, 80, 40, 0),
   247  		},
   248  	}
   249  
   250  	if !reflect.DeepEqual(actual, expected) {
   251  		t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   252  	}
   253  
   254  	// Shim
   255  	// we're not testing Resource.Create, so we need to start with the "created" state
   256  	createdState := &terraform.InstanceState{
   257  		ID:         "foo",
   258  		Attributes: map[string]string{"id": "foo"},
   259  	}
   260  
   261  	testApplyDiff(t, r, createdState, expected, d)
   262  }
   263  
   264  func TestShimResourceDiff_Timeout_diff(t *testing.T) {
   265  	r := &Resource{
   266  		Schema: map[string]*Schema{
   267  			"foo": {
   268  				Type:     TypeInt,
   269  				Optional: true,
   270  			},
   271  		},
   272  		Timeouts: &ResourceTimeout{
   273  			Create: DefaultTimeout(40 * time.Minute),
   274  			Update: DefaultTimeout(80 * time.Minute),
   275  			Delete: DefaultTimeout(40 * time.Minute),
   276  		},
   277  	}
   278  
   279  	r.Create = func(d *ResourceData, m interface{}) error {
   280  		d.SetId("foo")
   281  		return nil
   282  	}
   283  
   284  	conf := terraform.NewResourceConfigRaw(map[string]interface{}{
   285  		"foo": 42,
   286  		TimeoutsConfigKey: map[string]interface{}{
   287  			"create": "2h",
   288  		},
   289  	})
   290  	var s *terraform.InstanceState
   291  
   292  	actual, err := r.Diff(s, conf, nil)
   293  	if err != nil {
   294  		t.Fatalf("err: %s", err)
   295  	}
   296  
   297  	expected := &terraform.InstanceDiff{
   298  		Attributes: map[string]*terraform.ResourceAttrDiff{
   299  			"foo": {
   300  				New: "42",
   301  			},
   302  		},
   303  	}
   304  
   305  	diffTimeout := &ResourceTimeout{
   306  		Create: DefaultTimeout(120 * time.Minute),
   307  		Update: DefaultTimeout(80 * time.Minute),
   308  		Delete: DefaultTimeout(40 * time.Minute),
   309  	}
   310  
   311  	if err := diffTimeout.DiffEncode(expected); err != nil {
   312  		t.Fatalf("Error encoding timeout to diff: %s", err)
   313  	}
   314  
   315  	if !reflect.DeepEqual(actual, expected) {
   316  		t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta)
   317  	}
   318  
   319  	// Shim
   320  	// apply this diff, so we have a state to compare
   321  	applied, err := r.Apply(s, actual, nil)
   322  	if err != nil {
   323  		t.Fatal(err)
   324  	}
   325  
   326  	// we're not testing Resource.Create, so we need to start with the "created" state
   327  	createdState := &terraform.InstanceState{
   328  		ID:         "foo",
   329  		Attributes: map[string]string{"id": "foo"},
   330  	}
   331  
   332  	testSchema := providers.Schema{
   333  		Version: int64(r.SchemaVersion),
   334  		Block:   resourceSchemaToBlock(r.Schema),
   335  	}
   336  
   337  	initialVal, err := StateValueFromInstanceState(createdState, testSchema.Block.ImpliedType())
   338  	if err != nil {
   339  		t.Fatal(err)
   340  	}
   341  
   342  	appliedVal, err := StateValueFromInstanceState(applied, testSchema.Block.ImpliedType())
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  
   347  	d, err := DiffFromValues(initialVal, appliedVal, r)
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  	if eq, _ := d.Same(expected); !eq {
   352  		t.Fatal(cmp.Diff(d, expected))
   353  	}
   354  }
   355  
   356  func TestShimResourceApply_destroy(t *testing.T) {
   357  	r := &Resource{
   358  		Schema: map[string]*Schema{
   359  			"foo": {
   360  				Type:     TypeInt,
   361  				Optional: true,
   362  			},
   363  		},
   364  	}
   365  
   366  	called := false
   367  	r.Delete = func(d *ResourceData, m interface{}) error {
   368  		called = true
   369  		return nil
   370  	}
   371  
   372  	s := &terraform.InstanceState{
   373  		ID: "bar",
   374  	}
   375  
   376  	d := &terraform.InstanceDiff{
   377  		Destroy: true,
   378  	}
   379  
   380  	actual, err := r.Apply(s, d, nil)
   381  	if err != nil {
   382  		t.Fatalf("err: %s", err)
   383  	}
   384  
   385  	if !called {
   386  		t.Fatal("delete not called")
   387  	}
   388  
   389  	if actual != nil {
   390  		t.Fatalf("bad: %#v", actual)
   391  	}
   392  
   393  	// Shim
   394  	// now that we have our diff and desired state, see if we can reproduce
   395  	// that with the shim
   396  	testApplyDiff(t, r, s, actual, d)
   397  }
   398  
   399  func TestShimResourceApply_destroyCreate(t *testing.T) {
   400  	r := &Resource{
   401  		Schema: map[string]*Schema{
   402  			"foo": {
   403  				Type:     TypeInt,
   404  				Optional: true,
   405  				ForceNew: true,
   406  			},
   407  
   408  			"tags": {
   409  				Type:     TypeMap,
   410  				Optional: true,
   411  				Computed: true,
   412  			},
   413  		},
   414  	}
   415  
   416  	change := false
   417  	r.Create = func(d *ResourceData, m interface{}) error {
   418  		change = d.HasChange("tags")
   419  		d.SetId("foo")
   420  		return nil
   421  	}
   422  	r.Delete = func(d *ResourceData, m interface{}) error {
   423  		return nil
   424  	}
   425  
   426  	var s *terraform.InstanceState = &terraform.InstanceState{
   427  		ID: "bar",
   428  		Attributes: map[string]string{
   429  			"foo":       "7",
   430  			"tags.Name": "foo",
   431  		},
   432  	}
   433  
   434  	d := &terraform.InstanceDiff{
   435  		Attributes: map[string]*terraform.ResourceAttrDiff{
   436  			"id": {
   437  				New: "foo",
   438  			},
   439  			"foo": {
   440  				Old:         "7",
   441  				New:         "42",
   442  				RequiresNew: true,
   443  			},
   444  			"tags.Name": {
   445  				Old:         "foo",
   446  				New:         "foo",
   447  				RequiresNew: true,
   448  			},
   449  		},
   450  	}
   451  
   452  	actual, err := r.Apply(s, d, nil)
   453  	if err != nil {
   454  		t.Fatalf("err: %s", err)
   455  	}
   456  
   457  	if !change {
   458  		t.Fatal("should have change")
   459  	}
   460  
   461  	expected := &terraform.InstanceState{
   462  		ID: "foo",
   463  		Attributes: map[string]string{
   464  			"id":        "foo",
   465  			"foo":       "42",
   466  			"tags.%":    "1",
   467  			"tags.Name": "foo",
   468  		},
   469  	}
   470  
   471  	if !reflect.DeepEqual(actual, expected) {
   472  		cmp.Diff(actual, expected)
   473  	}
   474  
   475  	// Shim
   476  	// now that we have our diff and desired state, see if we can reproduce
   477  	// that with the shim
   478  	// we're not testing Resource.Create, so we need to start with the "created" state
   479  	createdState := &terraform.InstanceState{
   480  		ID: "foo",
   481  		Attributes: map[string]string{
   482  			"id":        "foo",
   483  			"foo":       "7",
   484  			"tags.%":    "1",
   485  			"tags.Name": "foo",
   486  		},
   487  	}
   488  
   489  	testApplyDiff(t, r, createdState, expected, d)
   490  }
   491  
   492  func TestShimSchemaMap_Diff(t *testing.T) {
   493  	cases := []struct {
   494  		Name          string
   495  		Schema        map[string]*Schema
   496  		State         *terraform.InstanceState
   497  		Config        map[string]interface{}
   498  		CustomizeDiff CustomizeDiffFunc
   499  		Diff          *terraform.InstanceDiff
   500  		Err           bool
   501  	}{
   502  		{
   503  			Name: "diff-1",
   504  			Schema: map[string]*Schema{
   505  				"availability_zone": {
   506  					Type:     TypeString,
   507  					Optional: true,
   508  					Computed: true,
   509  					ForceNew: true,
   510  				},
   511  			},
   512  
   513  			State: nil,
   514  
   515  			Config: map[string]interface{}{
   516  				"availability_zone": "foo",
   517  			},
   518  
   519  			Diff: &terraform.InstanceDiff{
   520  				Attributes: map[string]*terraform.ResourceAttrDiff{
   521  					"availability_zone": {
   522  						Old:         "",
   523  						New:         "foo",
   524  						RequiresNew: true,
   525  					},
   526  				},
   527  			},
   528  
   529  			Err: false,
   530  		},
   531  
   532  		{
   533  			Name: "diff-2",
   534  			Schema: map[string]*Schema{
   535  				"availability_zone": {
   536  					Type:     TypeString,
   537  					Optional: true,
   538  					Computed: true,
   539  					ForceNew: true,
   540  				},
   541  			},
   542  
   543  			State: nil,
   544  
   545  			Config: map[string]interface{}{},
   546  
   547  			Diff: &terraform.InstanceDiff{
   548  				Attributes: map[string]*terraform.ResourceAttrDiff{
   549  					"availability_zone": {
   550  						Old:         "",
   551  						NewComputed: true,
   552  						RequiresNew: true,
   553  					},
   554  				},
   555  			},
   556  
   557  			Err: false,
   558  		},
   559  
   560  		{
   561  			Name: "diff-3",
   562  			Schema: map[string]*Schema{
   563  				"availability_zone": {
   564  					Type:     TypeString,
   565  					Optional: true,
   566  					Computed: true,
   567  					ForceNew: true,
   568  				},
   569  			},
   570  
   571  			State: &terraform.InstanceState{
   572  				ID: "foo",
   573  			},
   574  
   575  			Config: map[string]interface{}{},
   576  
   577  			Diff: nil,
   578  
   579  			Err: false,
   580  		},
   581  
   582  		{
   583  			Name: "Computed, but set in config",
   584  			Schema: map[string]*Schema{
   585  				"availability_zone": {
   586  					Type:     TypeString,
   587  					Optional: true,
   588  					Computed: true,
   589  				},
   590  			},
   591  
   592  			State: &terraform.InstanceState{
   593  				ID: "id",
   594  				Attributes: map[string]string{
   595  					"availability_zone": "foo",
   596  				},
   597  			},
   598  
   599  			Config: map[string]interface{}{
   600  				"availability_zone": "bar",
   601  			},
   602  
   603  			Diff: &terraform.InstanceDiff{
   604  				Attributes: map[string]*terraform.ResourceAttrDiff{
   605  					"availability_zone": {
   606  						Old: "foo",
   607  						New: "bar",
   608  					},
   609  				},
   610  			},
   611  
   612  			Err: false,
   613  		},
   614  
   615  		{
   616  			Name: "Default",
   617  			Schema: map[string]*Schema{
   618  				"availability_zone": {
   619  					Type:     TypeString,
   620  					Optional: true,
   621  					Default:  "foo",
   622  				},
   623  			},
   624  
   625  			State: nil,
   626  
   627  			Config: nil,
   628  
   629  			Diff: &terraform.InstanceDiff{
   630  				Attributes: map[string]*terraform.ResourceAttrDiff{
   631  					"availability_zone": {
   632  						Old: "",
   633  						New: "foo",
   634  					},
   635  				},
   636  			},
   637  
   638  			Err: false,
   639  		},
   640  
   641  		{
   642  			Name: "DefaultFunc, value",
   643  			Schema: map[string]*Schema{
   644  				"availability_zone": {
   645  					Type:     TypeString,
   646  					Optional: true,
   647  					DefaultFunc: func() (interface{}, error) {
   648  						return "foo", nil
   649  					},
   650  				},
   651  			},
   652  
   653  			State: nil,
   654  
   655  			Config: nil,
   656  
   657  			Diff: &terraform.InstanceDiff{
   658  				Attributes: map[string]*terraform.ResourceAttrDiff{
   659  					"availability_zone": {
   660  						Old: "",
   661  						New: "foo",
   662  					},
   663  				},
   664  			},
   665  
   666  			Err: false,
   667  		},
   668  
   669  		{
   670  			Name: "DefaultFunc, configuration set",
   671  			Schema: map[string]*Schema{
   672  				"availability_zone": {
   673  					Type:     TypeString,
   674  					Optional: true,
   675  					DefaultFunc: func() (interface{}, error) {
   676  						return "foo", nil
   677  					},
   678  				},
   679  			},
   680  
   681  			State: nil,
   682  
   683  			Config: map[string]interface{}{
   684  				"availability_zone": "bar",
   685  			},
   686  
   687  			Diff: &terraform.InstanceDiff{
   688  				Attributes: map[string]*terraform.ResourceAttrDiff{
   689  					"availability_zone": {
   690  						Old: "",
   691  						New: "bar",
   692  					},
   693  				},
   694  			},
   695  
   696  			Err: false,
   697  		},
   698  
   699  		{
   700  			Name: "String with StateFunc",
   701  			Schema: map[string]*Schema{
   702  				"availability_zone": {
   703  					Type:     TypeString,
   704  					Optional: true,
   705  					Computed: true,
   706  					StateFunc: func(a interface{}) string {
   707  						return a.(string) + "!"
   708  					},
   709  				},
   710  			},
   711  
   712  			State: nil,
   713  
   714  			Config: map[string]interface{}{
   715  				"availability_zone": "foo",
   716  			},
   717  
   718  			Diff: &terraform.InstanceDiff{
   719  				Attributes: map[string]*terraform.ResourceAttrDiff{
   720  					"availability_zone": {
   721  						Old:      "",
   722  						New:      "foo!",
   723  						NewExtra: "foo",
   724  					},
   725  				},
   726  			},
   727  
   728  			Err: false,
   729  		},
   730  
   731  		{
   732  			Name: "StateFunc not called with nil value",
   733  			Schema: map[string]*Schema{
   734  				"availability_zone": {
   735  					Type:     TypeString,
   736  					Optional: true,
   737  					Computed: true,
   738  					StateFunc: func(a interface{}) string {
   739  						t.Error("should not get here!")
   740  						return ""
   741  					},
   742  				},
   743  			},
   744  
   745  			State: nil,
   746  
   747  			Config: map[string]interface{}{},
   748  
   749  			Diff: &terraform.InstanceDiff{
   750  				Attributes: map[string]*terraform.ResourceAttrDiff{
   751  					"availability_zone": {
   752  						Old:         "",
   753  						New:         "",
   754  						NewComputed: true,
   755  					},
   756  				},
   757  			},
   758  
   759  			Err: false,
   760  		},
   761  
   762  		{
   763  			Name: "Variable computed",
   764  			Schema: map[string]*Schema{
   765  				"availability_zone": {
   766  					Type:     TypeString,
   767  					Optional: true,
   768  				},
   769  			},
   770  
   771  			State: nil,
   772  
   773  			Config: map[string]interface{}{
   774  				"availability_zone": hcl2shim.UnknownVariableValue,
   775  			},
   776  
   777  			Diff: &terraform.InstanceDiff{
   778  				Attributes: map[string]*terraform.ResourceAttrDiff{
   779  					"availability_zone": {
   780  						Old:         "",
   781  						New:         hcl2shim.UnknownVariableValue,
   782  						NewComputed: true,
   783  					},
   784  				},
   785  			},
   786  
   787  			Err: false,
   788  		},
   789  
   790  		{
   791  			Name: "Int decode",
   792  			Schema: map[string]*Schema{
   793  				"port": {
   794  					Type:     TypeInt,
   795  					Optional: true,
   796  					Computed: true,
   797  					ForceNew: true,
   798  				},
   799  			},
   800  
   801  			State: nil,
   802  
   803  			Config: map[string]interface{}{
   804  				"port": 27,
   805  			},
   806  
   807  			Diff: &terraform.InstanceDiff{
   808  				Attributes: map[string]*terraform.ResourceAttrDiff{
   809  					"port": {
   810  						Old:         "",
   811  						New:         "27",
   812  						RequiresNew: true,
   813  					},
   814  				},
   815  			},
   816  
   817  			Err: false,
   818  		},
   819  
   820  		{
   821  			Name: "bool decode",
   822  			Schema: map[string]*Schema{
   823  				"port": {
   824  					Type:     TypeBool,
   825  					Optional: true,
   826  					Computed: true,
   827  					ForceNew: true,
   828  				},
   829  			},
   830  
   831  			State: nil,
   832  
   833  			Config: map[string]interface{}{
   834  				"port": false,
   835  			},
   836  
   837  			Diff: &terraform.InstanceDiff{
   838  				Attributes: map[string]*terraform.ResourceAttrDiff{
   839  					"port": {
   840  						Old:         "",
   841  						New:         "false",
   842  						RequiresNew: true,
   843  					},
   844  				},
   845  			},
   846  
   847  			Err: false,
   848  		},
   849  
   850  		{
   851  			Name: "Bool",
   852  			Schema: map[string]*Schema{
   853  				"delete": {
   854  					Type:     TypeBool,
   855  					Optional: true,
   856  					Default:  false,
   857  				},
   858  			},
   859  
   860  			State: &terraform.InstanceState{
   861  				ID: "id",
   862  				Attributes: map[string]string{
   863  					"delete": "false",
   864  				},
   865  			},
   866  
   867  			Config: nil,
   868  
   869  			Diff: nil,
   870  
   871  			Err: false,
   872  		},
   873  
   874  		{
   875  			Name: "List decode",
   876  			Schema: map[string]*Schema{
   877  				"ports": {
   878  					Type:     TypeList,
   879  					Required: true,
   880  					Elem:     &Schema{Type: TypeInt},
   881  				},
   882  			},
   883  
   884  			State: nil,
   885  
   886  			Config: map[string]interface{}{
   887  				"ports": []interface{}{1, 2, 5},
   888  			},
   889  
   890  			Diff: &terraform.InstanceDiff{
   891  				Attributes: map[string]*terraform.ResourceAttrDiff{
   892  					"ports.#": {
   893  						Old: "0",
   894  						New: "3",
   895  					},
   896  					"ports.0": {
   897  						Old: "",
   898  						New: "1",
   899  					},
   900  					"ports.1": {
   901  						Old: "",
   902  						New: "2",
   903  					},
   904  					"ports.2": {
   905  						Old: "",
   906  						New: "5",
   907  					},
   908  				},
   909  			},
   910  
   911  			Err: false,
   912  		},
   913  
   914  		{
   915  			Name: "List decode with promotion with list",
   916  			Schema: map[string]*Schema{
   917  				"ports": {
   918  					Type:          TypeList,
   919  					Required:      true,
   920  					Elem:          &Schema{Type: TypeInt},
   921  					PromoteSingle: true,
   922  				},
   923  			},
   924  
   925  			State: nil,
   926  
   927  			Config: map[string]interface{}{
   928  				"ports": []interface{}{"5"},
   929  			},
   930  
   931  			Diff: &terraform.InstanceDiff{
   932  				Attributes: map[string]*terraform.ResourceAttrDiff{
   933  					"ports.#": {
   934  						Old: "0",
   935  						New: "1",
   936  					},
   937  					"ports.0": {
   938  						Old: "",
   939  						New: "5",
   940  					},
   941  				},
   942  			},
   943  
   944  			Err: false,
   945  		},
   946  
   947  		{
   948  			Schema: map[string]*Schema{
   949  				"ports": {
   950  					Type:     TypeList,
   951  					Required: true,
   952  					Elem:     &Schema{Type: TypeInt},
   953  				},
   954  			},
   955  
   956  			State: nil,
   957  
   958  			Config: map[string]interface{}{
   959  				"ports": []interface{}{1, 2, 5},
   960  			},
   961  
   962  			Diff: &terraform.InstanceDiff{
   963  				Attributes: map[string]*terraform.ResourceAttrDiff{
   964  					"ports.#": {
   965  						Old: "0",
   966  						New: "3",
   967  					},
   968  					"ports.0": {
   969  						Old: "",
   970  						New: "1",
   971  					},
   972  					"ports.1": {
   973  						Old: "",
   974  						New: "2",
   975  					},
   976  					"ports.2": {
   977  						Old: "",
   978  						New: "5",
   979  					},
   980  				},
   981  			},
   982  
   983  			Err: false,
   984  		},
   985  
   986  		{
   987  			Schema: map[string]*Schema{
   988  				"ports": {
   989  					Type:     TypeList,
   990  					Required: true,
   991  					Elem:     &Schema{Type: TypeInt},
   992  				},
   993  			},
   994  
   995  			State: nil,
   996  
   997  			Config: map[string]interface{}{
   998  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"},
   999  			},
  1000  
  1001  			Diff: &terraform.InstanceDiff{
  1002  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1003  					"ports.#": {
  1004  						Old:         "0",
  1005  						New:         "",
  1006  						NewComputed: true,
  1007  					},
  1008  				},
  1009  			},
  1010  
  1011  			Err: false,
  1012  		},
  1013  
  1014  		{
  1015  			Schema: map[string]*Schema{
  1016  				"ports": {
  1017  					Type:     TypeList,
  1018  					Required: true,
  1019  					Elem:     &Schema{Type: TypeInt},
  1020  				},
  1021  			},
  1022  
  1023  			State: &terraform.InstanceState{
  1024  				ID: "id",
  1025  				Attributes: map[string]string{
  1026  					"ports.#": "3",
  1027  					"ports.0": "1",
  1028  					"ports.1": "2",
  1029  					"ports.2": "5",
  1030  				},
  1031  			},
  1032  
  1033  			Config: map[string]interface{}{
  1034  				"ports": []interface{}{1, 2, 5},
  1035  			},
  1036  
  1037  			Diff: nil,
  1038  
  1039  			Err: false,
  1040  		},
  1041  
  1042  		{
  1043  			Name: "",
  1044  			Schema: map[string]*Schema{
  1045  				"ports": {
  1046  					Type:     TypeList,
  1047  					Required: true,
  1048  					Elem:     &Schema{Type: TypeInt},
  1049  				},
  1050  			},
  1051  
  1052  			State: &terraform.InstanceState{
  1053  				ID: "id",
  1054  				Attributes: map[string]string{
  1055  					"ports.#": "2",
  1056  					"ports.0": "1",
  1057  					"ports.1": "2",
  1058  				},
  1059  			},
  1060  
  1061  			Config: map[string]interface{}{
  1062  				"ports": []interface{}{1, 2, 5},
  1063  			},
  1064  
  1065  			Diff: &terraform.InstanceDiff{
  1066  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1067  					"ports.#": {
  1068  						Old: "2",
  1069  						New: "3",
  1070  					},
  1071  					"ports.2": {
  1072  						Old: "",
  1073  						New: "5",
  1074  					},
  1075  				},
  1076  			},
  1077  
  1078  			Err: false,
  1079  		},
  1080  
  1081  		{
  1082  			Name: "",
  1083  			Schema: map[string]*Schema{
  1084  				"ports": {
  1085  					Type:     TypeList,
  1086  					Required: true,
  1087  					Elem:     &Schema{Type: TypeInt},
  1088  					ForceNew: true,
  1089  				},
  1090  			},
  1091  
  1092  			State: nil,
  1093  
  1094  			Config: map[string]interface{}{
  1095  				"ports": []interface{}{1, 2, 5},
  1096  			},
  1097  
  1098  			Diff: &terraform.InstanceDiff{
  1099  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1100  					"ports.#": {
  1101  						Old:         "0",
  1102  						New:         "3",
  1103  						RequiresNew: true,
  1104  					},
  1105  					"ports.0": {
  1106  						Old:         "",
  1107  						New:         "1",
  1108  						RequiresNew: true,
  1109  					},
  1110  					"ports.1": {
  1111  						Old:         "",
  1112  						New:         "2",
  1113  						RequiresNew: true,
  1114  					},
  1115  					"ports.2": {
  1116  						Old:         "",
  1117  						New:         "5",
  1118  						RequiresNew: true,
  1119  					},
  1120  				},
  1121  			},
  1122  
  1123  			Err: false,
  1124  		},
  1125  
  1126  		{
  1127  			Name: "",
  1128  			Schema: map[string]*Schema{
  1129  				"ports": {
  1130  					Type:     TypeList,
  1131  					Optional: true,
  1132  					Computed: true,
  1133  					Elem:     &Schema{Type: TypeInt},
  1134  				},
  1135  			},
  1136  
  1137  			State: nil,
  1138  
  1139  			Config: map[string]interface{}{},
  1140  
  1141  			Diff: &terraform.InstanceDiff{
  1142  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1143  					"ports.#": {
  1144  						Old:         "",
  1145  						NewComputed: true,
  1146  					},
  1147  				},
  1148  			},
  1149  
  1150  			Err: false,
  1151  		},
  1152  
  1153  		{
  1154  			Name: "List with computed set",
  1155  			Schema: map[string]*Schema{
  1156  				"config": {
  1157  					Type:     TypeList,
  1158  					Optional: true,
  1159  					ForceNew: true,
  1160  					MinItems: 1,
  1161  					Elem: &Resource{
  1162  						Schema: map[string]*Schema{
  1163  							"name": {
  1164  								Type:     TypeString,
  1165  								Required: true,
  1166  							},
  1167  
  1168  							"rules": {
  1169  								Type:     TypeSet,
  1170  								Computed: true,
  1171  								Elem:     &Schema{Type: TypeString},
  1172  								Set:      HashString,
  1173  							},
  1174  						},
  1175  					},
  1176  				},
  1177  			},
  1178  
  1179  			State: nil,
  1180  
  1181  			Config: map[string]interface{}{
  1182  				"config": []interface{}{
  1183  					map[string]interface{}{
  1184  						"name": "hello",
  1185  					},
  1186  				},
  1187  			},
  1188  
  1189  			Diff: &terraform.InstanceDiff{
  1190  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1191  					"config.#": {
  1192  						Old:         "0",
  1193  						New:         "1",
  1194  						RequiresNew: true,
  1195  					},
  1196  
  1197  					"config.0.name": {
  1198  						Old: "",
  1199  						New: "hello",
  1200  					},
  1201  
  1202  					"config.0.rules.#": {
  1203  						Old:         "",
  1204  						NewComputed: true,
  1205  					},
  1206  				},
  1207  			},
  1208  
  1209  			Err: false,
  1210  		},
  1211  
  1212  		{
  1213  			Name: "Set-1",
  1214  			Schema: map[string]*Schema{
  1215  				"ports": {
  1216  					Type:     TypeSet,
  1217  					Required: true,
  1218  					Elem:     &Schema{Type: TypeInt},
  1219  					Set: func(a interface{}) int {
  1220  						return a.(int)
  1221  					},
  1222  				},
  1223  			},
  1224  
  1225  			State: nil,
  1226  
  1227  			Config: map[string]interface{}{
  1228  				"ports": []interface{}{5, 2, 1},
  1229  			},
  1230  
  1231  			Diff: &terraform.InstanceDiff{
  1232  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1233  					"ports.#": {
  1234  						Old: "0",
  1235  						New: "3",
  1236  					},
  1237  					"ports.1": {
  1238  						Old: "",
  1239  						New: "1",
  1240  					},
  1241  					"ports.2": {
  1242  						Old: "",
  1243  						New: "2",
  1244  					},
  1245  					"ports.5": {
  1246  						Old: "",
  1247  						New: "5",
  1248  					},
  1249  				},
  1250  			},
  1251  
  1252  			Err: false,
  1253  		},
  1254  
  1255  		{
  1256  			Name: "Set-2",
  1257  			Schema: map[string]*Schema{
  1258  				"ports": {
  1259  					Type:     TypeSet,
  1260  					Computed: true,
  1261  					Required: true,
  1262  					Elem:     &Schema{Type: TypeInt},
  1263  					Set: func(a interface{}) int {
  1264  						return a.(int)
  1265  					},
  1266  				},
  1267  			},
  1268  
  1269  			State: &terraform.InstanceState{
  1270  				ID: "id",
  1271  				Attributes: map[string]string{
  1272  					"ports.#": "0",
  1273  				},
  1274  			},
  1275  
  1276  			Config: nil,
  1277  
  1278  			Diff: nil,
  1279  
  1280  			Err: false,
  1281  		},
  1282  
  1283  		{
  1284  			Name: "Set-3",
  1285  			Schema: map[string]*Schema{
  1286  				"ports": {
  1287  					Type:     TypeSet,
  1288  					Optional: true,
  1289  					Computed: true,
  1290  					Elem:     &Schema{Type: TypeInt},
  1291  					Set: func(a interface{}) int {
  1292  						return a.(int)
  1293  					},
  1294  				},
  1295  			},
  1296  
  1297  			State: nil,
  1298  
  1299  			Config: nil,
  1300  
  1301  			Diff: &terraform.InstanceDiff{
  1302  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1303  					"ports.#": {
  1304  						Old:         "",
  1305  						NewComputed: true,
  1306  					},
  1307  				},
  1308  			},
  1309  
  1310  			Err: false,
  1311  		},
  1312  
  1313  		{
  1314  			Name: "Set-4",
  1315  			Schema: map[string]*Schema{
  1316  				"ports": {
  1317  					Type:     TypeSet,
  1318  					Required: true,
  1319  					Elem:     &Schema{Type: TypeInt},
  1320  					Set: func(a interface{}) int {
  1321  						return a.(int)
  1322  					},
  1323  				},
  1324  			},
  1325  
  1326  			State: nil,
  1327  
  1328  			Config: map[string]interface{}{
  1329  				"ports": []interface{}{"2", "5", 1},
  1330  			},
  1331  
  1332  			Diff: &terraform.InstanceDiff{
  1333  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1334  					"ports.#": {
  1335  						Old: "0",
  1336  						New: "3",
  1337  					},
  1338  					"ports.1": {
  1339  						Old: "",
  1340  						New: "1",
  1341  					},
  1342  					"ports.2": {
  1343  						Old: "",
  1344  						New: "2",
  1345  					},
  1346  					"ports.5": {
  1347  						Old: "",
  1348  						New: "5",
  1349  					},
  1350  				},
  1351  			},
  1352  
  1353  			Err: false,
  1354  		},
  1355  
  1356  		{
  1357  			Name: "Set-5",
  1358  			Schema: map[string]*Schema{
  1359  				"ports": {
  1360  					Type:     TypeSet,
  1361  					Required: true,
  1362  					Elem:     &Schema{Type: TypeInt},
  1363  					Set: func(a interface{}) int {
  1364  						return a.(int)
  1365  					},
  1366  				},
  1367  			},
  1368  
  1369  			State: nil,
  1370  
  1371  			Config: map[string]interface{}{
  1372  				"ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5},
  1373  			},
  1374  
  1375  			Diff: &terraform.InstanceDiff{
  1376  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1377  					"ports.#": {
  1378  						Old:         "",
  1379  						New:         "",
  1380  						NewComputed: true,
  1381  					},
  1382  				},
  1383  			},
  1384  
  1385  			Err: false,
  1386  		},
  1387  
  1388  		{
  1389  			Name: "Set-6",
  1390  			Schema: map[string]*Schema{
  1391  				"ports": {
  1392  					Type:     TypeSet,
  1393  					Required: true,
  1394  					Elem:     &Schema{Type: TypeInt},
  1395  					Set: func(a interface{}) int {
  1396  						return a.(int)
  1397  					},
  1398  				},
  1399  			},
  1400  
  1401  			State: &terraform.InstanceState{
  1402  				ID: "id",
  1403  				Attributes: map[string]string{
  1404  					"ports.#": "2",
  1405  					"ports.1": "1",
  1406  					"ports.2": "2",
  1407  				},
  1408  			},
  1409  
  1410  			Config: map[string]interface{}{
  1411  				"ports": []interface{}{5, 2, 1},
  1412  			},
  1413  
  1414  			Diff: &terraform.InstanceDiff{
  1415  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1416  					"ports.#": {
  1417  						Old: "2",
  1418  						New: "3",
  1419  					},
  1420  					"ports.1": {
  1421  						Old: "1",
  1422  						New: "1",
  1423  					},
  1424  					"ports.2": {
  1425  						Old: "2",
  1426  						New: "2",
  1427  					},
  1428  					"ports.5": {
  1429  						Old: "",
  1430  						New: "5",
  1431  					},
  1432  				},
  1433  			},
  1434  
  1435  			Err: false,
  1436  		},
  1437  
  1438  		{
  1439  			Name: "Set-8",
  1440  			Schema: map[string]*Schema{
  1441  				"ports": {
  1442  					Type:     TypeSet,
  1443  					Optional: true,
  1444  					Computed: true,
  1445  					Elem:     &Schema{Type: TypeInt},
  1446  					Set: func(a interface{}) int {
  1447  						return a.(int)
  1448  					},
  1449  				},
  1450  			},
  1451  
  1452  			State: &terraform.InstanceState{
  1453  				ID: "id",
  1454  				Attributes: map[string]string{
  1455  					"availability_zone": "bar",
  1456  					"ports.#":           "1",
  1457  					"ports.80":          "80",
  1458  				},
  1459  			},
  1460  
  1461  			Config: map[string]interface{}{},
  1462  
  1463  			Diff: nil,
  1464  
  1465  			Err: false,
  1466  		},
  1467  
  1468  		{
  1469  			Name: "Set-9",
  1470  			Schema: map[string]*Schema{
  1471  				"ingress": {
  1472  					Type:     TypeSet,
  1473  					Required: true,
  1474  					Elem: &Resource{
  1475  						Schema: map[string]*Schema{
  1476  							"ports": {
  1477  								Type:     TypeList,
  1478  								Optional: true,
  1479  								Elem:     &Schema{Type: TypeInt},
  1480  							},
  1481  						},
  1482  					},
  1483  					Set: func(v interface{}) int {
  1484  						m := v.(map[string]interface{})
  1485  						ps := m["ports"].([]interface{})
  1486  						result := 0
  1487  						for _, p := range ps {
  1488  							result += p.(int)
  1489  						}
  1490  						return result
  1491  					},
  1492  				},
  1493  			},
  1494  
  1495  			State: &terraform.InstanceState{
  1496  				ID: "id",
  1497  				Attributes: map[string]string{
  1498  					"ingress.#":           "2",
  1499  					"ingress.80.ports.#":  "1",
  1500  					"ingress.80.ports.0":  "80",
  1501  					"ingress.443.ports.#": "1",
  1502  					"ingress.443.ports.0": "443",
  1503  				},
  1504  			},
  1505  
  1506  			Config: map[string]interface{}{
  1507  				"ingress": []interface{}{
  1508  					map[string]interface{}{
  1509  						"ports": []interface{}{443},
  1510  					},
  1511  					map[string]interface{}{
  1512  						"ports": []interface{}{80},
  1513  					},
  1514  				},
  1515  			},
  1516  
  1517  			Diff: nil,
  1518  
  1519  			Err: false,
  1520  		},
  1521  
  1522  		{
  1523  			Name: "List of structure decode",
  1524  			Schema: map[string]*Schema{
  1525  				"ingress": {
  1526  					Type:     TypeList,
  1527  					Required: true,
  1528  					Elem: &Resource{
  1529  						Schema: map[string]*Schema{
  1530  							"from": {
  1531  								Type:     TypeInt,
  1532  								Required: true,
  1533  							},
  1534  						},
  1535  					},
  1536  				},
  1537  			},
  1538  
  1539  			State: nil,
  1540  
  1541  			Config: map[string]interface{}{
  1542  				"ingress": []interface{}{
  1543  					map[string]interface{}{
  1544  						"from": 8080,
  1545  					},
  1546  				},
  1547  			},
  1548  
  1549  			Diff: &terraform.InstanceDiff{
  1550  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1551  					"ingress.#": {
  1552  						Old: "0",
  1553  						New: "1",
  1554  					},
  1555  					"ingress.0.from": {
  1556  						Old: "",
  1557  						New: "8080",
  1558  					},
  1559  				},
  1560  			},
  1561  
  1562  			Err: false,
  1563  		},
  1564  
  1565  		{
  1566  			Name: "ComputedWhen",
  1567  			Schema: map[string]*Schema{
  1568  				"availability_zone": {
  1569  					Type:         TypeString,
  1570  					Computed:     true,
  1571  					ComputedWhen: []string{"port"},
  1572  				},
  1573  
  1574  				"port": {
  1575  					Type:     TypeInt,
  1576  					Optional: true,
  1577  				},
  1578  			},
  1579  
  1580  			State: &terraform.InstanceState{
  1581  				ID: "id",
  1582  				Attributes: map[string]string{
  1583  					"availability_zone": "foo",
  1584  					"port":              "80",
  1585  				},
  1586  			},
  1587  
  1588  			Config: map[string]interface{}{
  1589  				"port": 80,
  1590  			},
  1591  
  1592  			Diff: nil,
  1593  
  1594  			Err: false,
  1595  		},
  1596  
  1597  		{
  1598  			Name: "computed",
  1599  			Schema: map[string]*Schema{
  1600  				"availability_zone": {
  1601  					Type:         TypeString,
  1602  					Computed:     true,
  1603  					ComputedWhen: []string{"port"},
  1604  				},
  1605  
  1606  				"port": {
  1607  					Type:     TypeInt,
  1608  					Optional: true,
  1609  				},
  1610  			},
  1611  
  1612  			State: nil,
  1613  
  1614  			Config: map[string]interface{}{
  1615  				"port": 80,
  1616  			},
  1617  
  1618  			Diff: &terraform.InstanceDiff{
  1619  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1620  					"availability_zone": {
  1621  						NewComputed: true,
  1622  					},
  1623  					"port": {
  1624  						New: "80",
  1625  					},
  1626  				},
  1627  			},
  1628  
  1629  			Err: false,
  1630  		},
  1631  
  1632  		{
  1633  			Name: "computed, exists",
  1634  			Schema: map[string]*Schema{
  1635  				"availability_zone": {
  1636  					Type:         TypeString,
  1637  					Computed:     true,
  1638  					ComputedWhen: []string{"port"},
  1639  				},
  1640  
  1641  				"port": {
  1642  					Type:     TypeInt,
  1643  					Optional: true,
  1644  				},
  1645  			},
  1646  
  1647  			State: &terraform.InstanceState{
  1648  				ID: "id",
  1649  				Attributes: map[string]string{
  1650  					"port": "80",
  1651  				},
  1652  			},
  1653  
  1654  			Config: map[string]interface{}{
  1655  				"port": 80,
  1656  			},
  1657  
  1658  			// there is no computed diff when the instance exists already
  1659  			Diff: nil,
  1660  
  1661  			Err: false,
  1662  		},
  1663  
  1664  		{
  1665  			Name: "Maps-1",
  1666  			Schema: map[string]*Schema{
  1667  				"config_vars": {
  1668  					Type: TypeMap,
  1669  				},
  1670  			},
  1671  
  1672  			State: nil,
  1673  
  1674  			Config: map[string]interface{}{
  1675  				"config_vars": map[string]interface{}{
  1676  					"bar": "baz",
  1677  				},
  1678  			},
  1679  
  1680  			Diff: &terraform.InstanceDiff{
  1681  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1682  					"config_vars.%": {
  1683  						Old: "0",
  1684  						New: "1",
  1685  					},
  1686  
  1687  					"config_vars.bar": {
  1688  						Old: "",
  1689  						New: "baz",
  1690  					},
  1691  				},
  1692  			},
  1693  
  1694  			Err: false,
  1695  		},
  1696  
  1697  		{
  1698  			Name: "Maps-2",
  1699  			Schema: map[string]*Schema{
  1700  				"config_vars": {
  1701  					Type: TypeMap,
  1702  				},
  1703  			},
  1704  
  1705  			State: &terraform.InstanceState{
  1706  				ID: "id",
  1707  				Attributes: map[string]string{
  1708  					"config_vars.%":   "1",
  1709  					"config_vars.foo": "bar",
  1710  				},
  1711  			},
  1712  
  1713  			Config: map[string]interface{}{
  1714  				"config_vars": map[string]interface{}{
  1715  					"bar": "baz",
  1716  				},
  1717  			},
  1718  
  1719  			Diff: &terraform.InstanceDiff{
  1720  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1721  					"config_vars.foo": {
  1722  						Old:        "bar",
  1723  						NewRemoved: true,
  1724  					},
  1725  					"config_vars.bar": {
  1726  						Old: "",
  1727  						New: "baz",
  1728  					},
  1729  				},
  1730  			},
  1731  
  1732  			Err: false,
  1733  		},
  1734  
  1735  		{
  1736  			Name: "Maps-3",
  1737  			Schema: map[string]*Schema{
  1738  				"vars": {
  1739  					Type:     TypeMap,
  1740  					Optional: true,
  1741  					Computed: true,
  1742  				},
  1743  			},
  1744  
  1745  			State: &terraform.InstanceState{
  1746  				ID: "id",
  1747  				Attributes: map[string]string{
  1748  					"vars.%":   "1",
  1749  					"vars.foo": "bar",
  1750  				},
  1751  			},
  1752  
  1753  			Config: map[string]interface{}{
  1754  				"vars": map[string]interface{}{
  1755  					"bar": "baz",
  1756  				},
  1757  			},
  1758  
  1759  			Diff: &terraform.InstanceDiff{
  1760  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1761  					"vars.foo": {
  1762  						Old:        "bar",
  1763  						New:        "",
  1764  						NewRemoved: true,
  1765  					},
  1766  					"vars.bar": {
  1767  						Old: "",
  1768  						New: "baz",
  1769  					},
  1770  				},
  1771  			},
  1772  
  1773  			Err: false,
  1774  		},
  1775  
  1776  		{
  1777  			Name: "Maps-4",
  1778  			Schema: map[string]*Schema{
  1779  				"vars": {
  1780  					Type:     TypeMap,
  1781  					Computed: true,
  1782  				},
  1783  			},
  1784  
  1785  			State: &terraform.InstanceState{
  1786  				ID: "id",
  1787  				Attributes: map[string]string{
  1788  					"vars.%":   "1",
  1789  					"vars.foo": "bar",
  1790  				},
  1791  			},
  1792  
  1793  			Config: nil,
  1794  
  1795  			Diff: nil,
  1796  
  1797  			Err: false,
  1798  		},
  1799  
  1800  		{
  1801  			Name: "Maps-5",
  1802  			Schema: map[string]*Schema{
  1803  				"config_vars": {
  1804  					Type: TypeList,
  1805  					Elem: &Schema{Type: TypeMap},
  1806  				},
  1807  			},
  1808  
  1809  			State: &terraform.InstanceState{
  1810  				ID: "id",
  1811  				Attributes: map[string]string{
  1812  					"config_vars.#":     "1",
  1813  					"config_vars.0.%":   "1",
  1814  					"config_vars.0.foo": "bar",
  1815  				},
  1816  			},
  1817  
  1818  			Config: map[string]interface{}{
  1819  				"config_vars": []interface{}{
  1820  					map[string]interface{}{
  1821  						"bar": "baz",
  1822  					},
  1823  				},
  1824  			},
  1825  
  1826  			Diff: &terraform.InstanceDiff{
  1827  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1828  					"config_vars.0.foo": {
  1829  						Old:        "bar",
  1830  						NewRemoved: true,
  1831  					},
  1832  					"config_vars.0.bar": {
  1833  						Old: "",
  1834  						New: "baz",
  1835  					},
  1836  				},
  1837  			},
  1838  
  1839  			Err: false,
  1840  		},
  1841  
  1842  		{
  1843  			Name: "Maps-6",
  1844  			Schema: map[string]*Schema{
  1845  				"config_vars": {
  1846  					Type:     TypeList,
  1847  					Elem:     &Schema{Type: TypeMap},
  1848  					Optional: true,
  1849  				},
  1850  			},
  1851  
  1852  			State: &terraform.InstanceState{
  1853  				ID: "id",
  1854  				Attributes: map[string]string{
  1855  					"config_vars.#":     "1",
  1856  					"config_vars.0.%":   "2",
  1857  					"config_vars.0.foo": "bar",
  1858  					"config_vars.0.bar": "baz",
  1859  				},
  1860  			},
  1861  
  1862  			Config: map[string]interface{}{},
  1863  
  1864  			Diff: &terraform.InstanceDiff{
  1865  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1866  					"config_vars.#": {
  1867  						Old: "1",
  1868  						New: "0",
  1869  					},
  1870  					"config_vars.0.%": {
  1871  						Old: "2",
  1872  						New: "0",
  1873  					},
  1874  					"config_vars.0.foo": {
  1875  						Old:        "bar",
  1876  						NewRemoved: true,
  1877  					},
  1878  					"config_vars.0.bar": {
  1879  						Old:        "baz",
  1880  						NewRemoved: true,
  1881  					},
  1882  				},
  1883  			},
  1884  
  1885  			Err: false,
  1886  		},
  1887  
  1888  		{
  1889  			Name: "ForceNews",
  1890  			Schema: map[string]*Schema{
  1891  				"availability_zone": {
  1892  					Type:     TypeString,
  1893  					Optional: true,
  1894  					ForceNew: true,
  1895  				},
  1896  
  1897  				"address": {
  1898  					Type:     TypeString,
  1899  					Optional: true,
  1900  					Computed: true,
  1901  				},
  1902  			},
  1903  
  1904  			State: &terraform.InstanceState{
  1905  				ID: "id",
  1906  				Attributes: map[string]string{
  1907  					"availability_zone": "bar",
  1908  					"address":           "foo",
  1909  				},
  1910  			},
  1911  
  1912  			Config: map[string]interface{}{
  1913  				"availability_zone": "foo",
  1914  			},
  1915  
  1916  			Diff: &terraform.InstanceDiff{
  1917  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1918  					"availability_zone": {
  1919  						Old:         "bar",
  1920  						New:         "foo",
  1921  						RequiresNew: true,
  1922  					},
  1923  				},
  1924  			},
  1925  
  1926  			Err: false,
  1927  		},
  1928  
  1929  		{
  1930  			Name: "Set-10",
  1931  			Schema: map[string]*Schema{
  1932  				"availability_zone": {
  1933  					Type:     TypeString,
  1934  					Optional: true,
  1935  					ForceNew: true,
  1936  				},
  1937  
  1938  				"ports": {
  1939  					Type:     TypeSet,
  1940  					Optional: true,
  1941  					Computed: true,
  1942  					Elem:     &Schema{Type: TypeInt},
  1943  					Set: func(a interface{}) int {
  1944  						return a.(int)
  1945  					},
  1946  				},
  1947  			},
  1948  
  1949  			State: &terraform.InstanceState{
  1950  				ID: "id",
  1951  				Attributes: map[string]string{
  1952  					"availability_zone": "bar",
  1953  					"ports.#":           "1",
  1954  					"ports.80":          "80",
  1955  				},
  1956  			},
  1957  
  1958  			Config: map[string]interface{}{
  1959  				"availability_zone": "foo",
  1960  			},
  1961  
  1962  			Diff: &terraform.InstanceDiff{
  1963  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1964  					"availability_zone": {
  1965  						Old:         "bar",
  1966  						New:         "foo",
  1967  						RequiresNew: true,
  1968  					},
  1969  				},
  1970  			},
  1971  
  1972  			Err: false,
  1973  		},
  1974  
  1975  		{
  1976  			Name: "Set-11",
  1977  			Schema: map[string]*Schema{
  1978  				"instances": {
  1979  					Type:     TypeSet,
  1980  					Elem:     &Schema{Type: TypeString},
  1981  					Optional: true,
  1982  					Computed: true,
  1983  					Set: func(v interface{}) int {
  1984  						return len(v.(string))
  1985  					},
  1986  				},
  1987  			},
  1988  
  1989  			State: &terraform.InstanceState{
  1990  				ID: "id",
  1991  				Attributes: map[string]string{
  1992  					"instances.#": "0",
  1993  				},
  1994  			},
  1995  
  1996  			Config: map[string]interface{}{
  1997  				"instances": []interface{}{hcl2shim.UnknownVariableValue},
  1998  			},
  1999  
  2000  			Diff: &terraform.InstanceDiff{
  2001  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2002  					"instances.#": {
  2003  						NewComputed: true,
  2004  					},
  2005  				},
  2006  			},
  2007  
  2008  			Err: false,
  2009  		},
  2010  
  2011  		{
  2012  			Name: "Set-12",
  2013  			Schema: map[string]*Schema{
  2014  				"route": {
  2015  					Type:     TypeSet,
  2016  					Optional: true,
  2017  					Elem: &Resource{
  2018  						Schema: map[string]*Schema{
  2019  							"index": {
  2020  								Type:     TypeInt,
  2021  								Required: true,
  2022  							},
  2023  
  2024  							"gateway": {
  2025  								Type:     TypeString,
  2026  								Optional: true,
  2027  							},
  2028  						},
  2029  					},
  2030  					Set: func(v interface{}) int {
  2031  						m := v.(map[string]interface{})
  2032  						return m["index"].(int)
  2033  					},
  2034  				},
  2035  			},
  2036  
  2037  			State: nil,
  2038  
  2039  			Config: map[string]interface{}{
  2040  				"route": []interface{}{
  2041  					map[string]interface{}{
  2042  						"index":   "1",
  2043  						"gateway": hcl2shim.UnknownVariableValue,
  2044  					},
  2045  				},
  2046  			},
  2047  
  2048  			Diff: &terraform.InstanceDiff{
  2049  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2050  					"route.#": {
  2051  						Old: "0",
  2052  						New: "1",
  2053  					},
  2054  					"route.~1.index": {
  2055  						Old: "",
  2056  						New: "1",
  2057  					},
  2058  					"route.~1.gateway": {
  2059  						Old:         "",
  2060  						New:         hcl2shim.UnknownVariableValue,
  2061  						NewComputed: true,
  2062  					},
  2063  				},
  2064  			},
  2065  
  2066  			Err: false,
  2067  		},
  2068  
  2069  		{
  2070  			Name: "Set-13",
  2071  			Schema: map[string]*Schema{
  2072  				"route": {
  2073  					Type:     TypeSet,
  2074  					Optional: true,
  2075  					Elem: &Resource{
  2076  						Schema: map[string]*Schema{
  2077  							"index": {
  2078  								Type:     TypeInt,
  2079  								Required: true,
  2080  							},
  2081  
  2082  							"gateway": {
  2083  								Type:     TypeSet,
  2084  								Optional: true,
  2085  								Elem:     &Schema{Type: TypeInt},
  2086  								Set: func(a interface{}) int {
  2087  									return a.(int)
  2088  								},
  2089  							},
  2090  						},
  2091  					},
  2092  					Set: func(v interface{}) int {
  2093  						m := v.(map[string]interface{})
  2094  						return m["index"].(int)
  2095  					},
  2096  				},
  2097  			},
  2098  
  2099  			State: nil,
  2100  
  2101  			Config: map[string]interface{}{
  2102  				"route": []interface{}{
  2103  					map[string]interface{}{
  2104  						"index": "1",
  2105  						"gateway": []interface{}{
  2106  							hcl2shim.UnknownVariableValue,
  2107  						},
  2108  					},
  2109  				},
  2110  			},
  2111  
  2112  			Diff: &terraform.InstanceDiff{
  2113  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2114  					"route.#": {
  2115  						Old: "0",
  2116  						New: "1",
  2117  					},
  2118  					"route.~1.index": {
  2119  						Old: "",
  2120  						New: "1",
  2121  					},
  2122  					"route.~1.gateway.#": {
  2123  						NewComputed: true,
  2124  					},
  2125  				},
  2126  			},
  2127  
  2128  			Err: false,
  2129  		},
  2130  
  2131  		{
  2132  			Name: "Computed maps",
  2133  			Schema: map[string]*Schema{
  2134  				"vars": {
  2135  					Type:     TypeMap,
  2136  					Computed: true,
  2137  				},
  2138  			},
  2139  
  2140  			State: nil,
  2141  
  2142  			Config: nil,
  2143  
  2144  			Diff: &terraform.InstanceDiff{
  2145  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2146  					"vars.%": {
  2147  						Old:         "",
  2148  						NewComputed: true,
  2149  					},
  2150  				},
  2151  			},
  2152  
  2153  			Err: false,
  2154  		},
  2155  
  2156  		{
  2157  			Name: "Computed maps",
  2158  			Schema: map[string]*Schema{
  2159  				"vars": {
  2160  					Type:     TypeMap,
  2161  					Computed: true,
  2162  				},
  2163  			},
  2164  
  2165  			State: &terraform.InstanceState{
  2166  				ID: "id",
  2167  				Attributes: map[string]string{
  2168  					"vars.%": "0",
  2169  				},
  2170  			},
  2171  
  2172  			Config: map[string]interface{}{
  2173  				"vars": map[string]interface{}{
  2174  					"bar": hcl2shim.UnknownVariableValue,
  2175  				},
  2176  			},
  2177  
  2178  			Diff: &terraform.InstanceDiff{
  2179  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2180  					"vars.%": {
  2181  						Old:         "",
  2182  						NewComputed: true,
  2183  					},
  2184  				},
  2185  			},
  2186  
  2187  			Err: false,
  2188  		},
  2189  
  2190  		{
  2191  			Name:   "Empty",
  2192  			Schema: map[string]*Schema{},
  2193  
  2194  			State: &terraform.InstanceState{},
  2195  
  2196  			Config: map[string]interface{}{},
  2197  
  2198  			Diff: nil,
  2199  
  2200  			Err: false,
  2201  		},
  2202  
  2203  		{
  2204  			Name: "Float",
  2205  			Schema: map[string]*Schema{
  2206  				"some_threshold": {
  2207  					Type: TypeFloat,
  2208  				},
  2209  			},
  2210  
  2211  			State: &terraform.InstanceState{
  2212  				ID: "id",
  2213  				Attributes: map[string]string{
  2214  					"some_threshold": "567.8",
  2215  				},
  2216  			},
  2217  
  2218  			Config: map[string]interface{}{
  2219  				"some_threshold": 12.34,
  2220  			},
  2221  
  2222  			Diff: &terraform.InstanceDiff{
  2223  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2224  					"some_threshold": {
  2225  						Old: "567.8",
  2226  						New: "12.34",
  2227  					},
  2228  				},
  2229  			},
  2230  
  2231  			Err: false,
  2232  		},
  2233  
  2234  		{
  2235  			Name: "https://github.com/hashicorp/terraform-plugin-sdk/issues/824",
  2236  			Schema: map[string]*Schema{
  2237  				"block_device": {
  2238  					Type:     TypeSet,
  2239  					Optional: true,
  2240  					Computed: true,
  2241  					Elem: &Resource{
  2242  						Schema: map[string]*Schema{
  2243  							"device_name": {
  2244  								Type:     TypeString,
  2245  								Required: true,
  2246  							},
  2247  							"delete_on_termination": {
  2248  								Type:     TypeBool,
  2249  								Optional: true,
  2250  								Default:  true,
  2251  							},
  2252  						},
  2253  					},
  2254  					Set: func(v interface{}) int {
  2255  						var buf bytes.Buffer
  2256  						m := v.(map[string]interface{})
  2257  						buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string)))
  2258  						buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool)))
  2259  						return hashcode.String(buf.String())
  2260  					},
  2261  				},
  2262  			},
  2263  
  2264  			State: &terraform.InstanceState{
  2265  				ID: "id",
  2266  				Attributes: map[string]string{
  2267  					"block_device.#": "2",
  2268  					"block_device.616397234.delete_on_termination":  "true",
  2269  					"block_device.616397234.device_name":            "/dev/sda1",
  2270  					"block_device.2801811477.delete_on_termination": "true",
  2271  					"block_device.2801811477.device_name":           "/dev/sdx",
  2272  				},
  2273  			},
  2274  
  2275  			Config: map[string]interface{}{
  2276  				"block_device": []interface{}{
  2277  					map[string]interface{}{
  2278  						"device_name": "/dev/sda1",
  2279  					},
  2280  					map[string]interface{}{
  2281  						"device_name": "/dev/sdx",
  2282  					},
  2283  				},
  2284  			},
  2285  			Diff: nil,
  2286  			Err:  false,
  2287  		},
  2288  
  2289  		{
  2290  			Name: "Zero value in state shouldn't result in diff",
  2291  			Schema: map[string]*Schema{
  2292  				"port": {
  2293  					Type:     TypeBool,
  2294  					Optional: true,
  2295  					ForceNew: true,
  2296  				},
  2297  			},
  2298  
  2299  			State: &terraform.InstanceState{
  2300  				ID: "id",
  2301  				Attributes: map[string]string{
  2302  					"port": "false",
  2303  				},
  2304  			},
  2305  
  2306  			Config: map[string]interface{}{},
  2307  
  2308  			Diff: nil,
  2309  
  2310  			Err: false,
  2311  		},
  2312  
  2313  		{
  2314  			Name: "Same as prev, but for sets",
  2315  			Schema: map[string]*Schema{
  2316  				"route": {
  2317  					Type:     TypeSet,
  2318  					Optional: true,
  2319  					Elem: &Resource{
  2320  						Schema: map[string]*Schema{
  2321  							"index": {
  2322  								Type:     TypeInt,
  2323  								Required: true,
  2324  							},
  2325  
  2326  							"gateway": {
  2327  								Type:     TypeSet,
  2328  								Optional: true,
  2329  								Elem:     &Schema{Type: TypeInt},
  2330  								Set: func(a interface{}) int {
  2331  									return a.(int)
  2332  								},
  2333  							},
  2334  						},
  2335  					},
  2336  					Set: func(v interface{}) int {
  2337  						m := v.(map[string]interface{})
  2338  						return m["index"].(int)
  2339  					},
  2340  				},
  2341  			},
  2342  
  2343  			State: &terraform.InstanceState{
  2344  				ID: "id",
  2345  				Attributes: map[string]string{
  2346  					"route.#": "0",
  2347  				},
  2348  			},
  2349  
  2350  			Config: map[string]interface{}{},
  2351  
  2352  			Diff: nil,
  2353  
  2354  			Err: false,
  2355  		},
  2356  
  2357  		{
  2358  			Name: "A set computed element shouldn't cause a diff",
  2359  			Schema: map[string]*Schema{
  2360  				"active": {
  2361  					Type:     TypeBool,
  2362  					Computed: true,
  2363  					ForceNew: true,
  2364  				},
  2365  			},
  2366  
  2367  			State: &terraform.InstanceState{
  2368  				ID: "id",
  2369  				Attributes: map[string]string{
  2370  					"active": "true",
  2371  				},
  2372  			},
  2373  
  2374  			Config: map[string]interface{}{},
  2375  
  2376  			Diff: nil,
  2377  
  2378  			Err: false,
  2379  		},
  2380  
  2381  		{
  2382  			Name: "An empty set should show up in the diff",
  2383  			Schema: map[string]*Schema{
  2384  				"instances": {
  2385  					Type:     TypeSet,
  2386  					Elem:     &Schema{Type: TypeString},
  2387  					Optional: true,
  2388  					ForceNew: true,
  2389  					Set: func(v interface{}) int {
  2390  						return len(v.(string))
  2391  					},
  2392  				},
  2393  			},
  2394  
  2395  			State: &terraform.InstanceState{
  2396  				ID: "id",
  2397  				Attributes: map[string]string{
  2398  					"instances.#": "1",
  2399  					"instances.3": "foo",
  2400  				},
  2401  			},
  2402  
  2403  			Config: map[string]interface{}{},
  2404  
  2405  			Diff: &terraform.InstanceDiff{
  2406  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2407  					"instances.#": {
  2408  						Old:         "1",
  2409  						New:         "0",
  2410  						RequiresNew: true,
  2411  					},
  2412  					"instances.3": {
  2413  						Old:         "foo",
  2414  						New:         "",
  2415  						NewRemoved:  true,
  2416  						RequiresNew: true,
  2417  					},
  2418  				},
  2419  			},
  2420  
  2421  			Err: false,
  2422  		},
  2423  
  2424  		{
  2425  			Name: "Map with empty value",
  2426  			Schema: map[string]*Schema{
  2427  				"vars": {
  2428  					Type: TypeMap,
  2429  				},
  2430  			},
  2431  
  2432  			State: nil,
  2433  
  2434  			Config: map[string]interface{}{
  2435  				"vars": map[string]interface{}{
  2436  					"foo": "",
  2437  				},
  2438  			},
  2439  
  2440  			Diff: &terraform.InstanceDiff{
  2441  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2442  					"vars.%": {
  2443  						Old: "0",
  2444  						New: "1",
  2445  					},
  2446  					"vars.foo": {
  2447  						Old: "",
  2448  						New: "",
  2449  					},
  2450  				},
  2451  			},
  2452  
  2453  			Err: false,
  2454  		},
  2455  
  2456  		{
  2457  			Name: "Unset bool, not in state",
  2458  			Schema: map[string]*Schema{
  2459  				"force": {
  2460  					Type:     TypeBool,
  2461  					Optional: true,
  2462  					ForceNew: true,
  2463  				},
  2464  			},
  2465  
  2466  			State: nil,
  2467  
  2468  			Config: map[string]interface{}{},
  2469  
  2470  			Diff: nil,
  2471  
  2472  			Err: false,
  2473  		},
  2474  
  2475  		{
  2476  			Name: "Unset set, not in state",
  2477  			Schema: map[string]*Schema{
  2478  				"metadata_keys": {
  2479  					Type:     TypeSet,
  2480  					Optional: true,
  2481  					ForceNew: true,
  2482  					Elem:     &Schema{Type: TypeInt},
  2483  					Set:      func(interface{}) int { return 0 },
  2484  				},
  2485  			},
  2486  
  2487  			State: nil,
  2488  
  2489  			Config: map[string]interface{}{},
  2490  
  2491  			Diff: nil,
  2492  
  2493  			Err: false,
  2494  		},
  2495  
  2496  		{
  2497  			Name: "Unset list in state, should not show up computed",
  2498  			Schema: map[string]*Schema{
  2499  				"metadata_keys": {
  2500  					Type:     TypeList,
  2501  					Optional: true,
  2502  					Computed: true,
  2503  					ForceNew: true,
  2504  					Elem:     &Schema{Type: TypeInt},
  2505  				},
  2506  			},
  2507  
  2508  			State: &terraform.InstanceState{
  2509  				ID: "id",
  2510  				Attributes: map[string]string{
  2511  					"metadata_keys.#": "0",
  2512  				},
  2513  			},
  2514  
  2515  			Config: map[string]interface{}{},
  2516  
  2517  			Diff: nil,
  2518  
  2519  			Err: false,
  2520  		},
  2521  
  2522  		{
  2523  			Name: "Computed map without config that's known to be empty does not generate diff",
  2524  			Schema: map[string]*Schema{
  2525  				"tags": {
  2526  					Type:     TypeMap,
  2527  					Computed: true,
  2528  				},
  2529  			},
  2530  
  2531  			Config: nil,
  2532  
  2533  			State: &terraform.InstanceState{
  2534  				ID: "id",
  2535  				Attributes: map[string]string{
  2536  					"tags.%": "0",
  2537  				},
  2538  			},
  2539  
  2540  			Diff: nil,
  2541  
  2542  			Err: false,
  2543  		},
  2544  
  2545  		{
  2546  			Name: "Set with hyphen keys",
  2547  			Schema: map[string]*Schema{
  2548  				"route": {
  2549  					Type:     TypeSet,
  2550  					Optional: true,
  2551  					Elem: &Resource{
  2552  						Schema: map[string]*Schema{
  2553  							"index": {
  2554  								Type:     TypeInt,
  2555  								Required: true,
  2556  							},
  2557  
  2558  							"gateway-name": {
  2559  								Type:     TypeString,
  2560  								Optional: true,
  2561  							},
  2562  						},
  2563  					},
  2564  					Set: func(v interface{}) int {
  2565  						m := v.(map[string]interface{})
  2566  						return m["index"].(int)
  2567  					},
  2568  				},
  2569  			},
  2570  
  2571  			State: nil,
  2572  
  2573  			Config: map[string]interface{}{
  2574  				"route": []interface{}{
  2575  					map[string]interface{}{
  2576  						"index":        "1",
  2577  						"gateway-name": "hello",
  2578  					},
  2579  				},
  2580  			},
  2581  
  2582  			Diff: &terraform.InstanceDiff{
  2583  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2584  					"route.#": {
  2585  						Old: "0",
  2586  						New: "1",
  2587  					},
  2588  					"route.1.index": {
  2589  						Old: "",
  2590  						New: "1",
  2591  					},
  2592  					"route.1.gateway-name": {
  2593  						Old: "",
  2594  						New: "hello",
  2595  					},
  2596  				},
  2597  			},
  2598  
  2599  			Err: false,
  2600  		},
  2601  
  2602  		{
  2603  			Name: "StateFunc in nested set (#1759)",
  2604  			Schema: map[string]*Schema{
  2605  				"service_account": {
  2606  					Type:     TypeList,
  2607  					Optional: true,
  2608  					ForceNew: true,
  2609  					Elem: &Resource{
  2610  						Schema: map[string]*Schema{
  2611  							"scopes": {
  2612  								Type:     TypeSet,
  2613  								Required: true,
  2614  								ForceNew: true,
  2615  								Elem: &Schema{
  2616  									Type: TypeString,
  2617  									StateFunc: func(v interface{}) string {
  2618  										return v.(string) + "!"
  2619  									},
  2620  								},
  2621  								Set: func(v interface{}) int {
  2622  									i, err := strconv.Atoi(v.(string))
  2623  									if err != nil {
  2624  										t.Fatalf("err: %s", err)
  2625  									}
  2626  									return i
  2627  								},
  2628  							},
  2629  						},
  2630  					},
  2631  				},
  2632  			},
  2633  
  2634  			State: nil,
  2635  
  2636  			Config: map[string]interface{}{
  2637  				"service_account": []interface{}{
  2638  					map[string]interface{}{
  2639  						"scopes": []interface{}{"123"},
  2640  					},
  2641  				},
  2642  			},
  2643  
  2644  			Diff: &terraform.InstanceDiff{
  2645  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2646  					"service_account.#": {
  2647  						Old:         "0",
  2648  						New:         "1",
  2649  						RequiresNew: true,
  2650  					},
  2651  					"service_account.0.scopes.#": {
  2652  						Old:         "0",
  2653  						New:         "1",
  2654  						RequiresNew: true,
  2655  					},
  2656  					"service_account.0.scopes.123": {
  2657  						Old:         "",
  2658  						New:         "123!",
  2659  						NewExtra:    "123",
  2660  						RequiresNew: true,
  2661  					},
  2662  				},
  2663  			},
  2664  
  2665  			Err: false,
  2666  		},
  2667  
  2668  		{
  2669  			Name: "Removing set elements",
  2670  			Schema: map[string]*Schema{
  2671  				"instances": {
  2672  					Type:     TypeSet,
  2673  					Elem:     &Schema{Type: TypeString},
  2674  					Optional: true,
  2675  					ForceNew: true,
  2676  					Set: func(v interface{}) int {
  2677  						return len(v.(string))
  2678  					},
  2679  				},
  2680  			},
  2681  
  2682  			State: &terraform.InstanceState{
  2683  				ID: "id",
  2684  				Attributes: map[string]string{
  2685  					"instances.#": "2",
  2686  					"instances.3": "333",
  2687  					"instances.2": "22",
  2688  				},
  2689  			},
  2690  
  2691  			Config: map[string]interface{}{
  2692  				"instances": []interface{}{"333", "4444"},
  2693  			},
  2694  
  2695  			Diff: &terraform.InstanceDiff{
  2696  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2697  					"instances.2": {
  2698  						Old:         "22",
  2699  						New:         "",
  2700  						NewRemoved:  true,
  2701  						RequiresNew: true,
  2702  					},
  2703  					"instances.3": {
  2704  						Old: "333",
  2705  						New: "333",
  2706  					},
  2707  					"instances.4": {
  2708  						Old:         "",
  2709  						New:         "4444",
  2710  						RequiresNew: true,
  2711  					},
  2712  				},
  2713  			},
  2714  
  2715  			Err: false,
  2716  		},
  2717  
  2718  		{
  2719  			Name: "Bools can be set with 0/1 in config, still get true/false",
  2720  			Schema: map[string]*Schema{
  2721  				"one": {
  2722  					Type:     TypeBool,
  2723  					Optional: true,
  2724  				},
  2725  				"two": {
  2726  					Type:     TypeBool,
  2727  					Optional: true,
  2728  				},
  2729  				"three": {
  2730  					Type:     TypeBool,
  2731  					Optional: true,
  2732  				},
  2733  			},
  2734  
  2735  			State: &terraform.InstanceState{
  2736  				ID: "id",
  2737  				Attributes: map[string]string{
  2738  					"one":   "false",
  2739  					"two":   "true",
  2740  					"three": "true",
  2741  				},
  2742  			},
  2743  
  2744  			Config: map[string]interface{}{
  2745  				"one": "1",
  2746  				"two": "0",
  2747  			},
  2748  
  2749  			Diff: &terraform.InstanceDiff{
  2750  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2751  					"one": {
  2752  						Old: "false",
  2753  						New: "true",
  2754  					},
  2755  					"two": {
  2756  						Old: "true",
  2757  						New: "false",
  2758  					},
  2759  					"three": {
  2760  						Old:        "true",
  2761  						New:        "false",
  2762  						NewRemoved: true,
  2763  					},
  2764  				},
  2765  			},
  2766  
  2767  			Err: false,
  2768  		},
  2769  
  2770  		{
  2771  			Name:   "tainted in state w/ no attr changes is still a replacement",
  2772  			Schema: map[string]*Schema{},
  2773  
  2774  			State: &terraform.InstanceState{
  2775  				ID: "id",
  2776  				Attributes: map[string]string{
  2777  					"id": "someid",
  2778  				},
  2779  				Tainted: true,
  2780  			},
  2781  
  2782  			Config: map[string]interface{}{},
  2783  
  2784  			Diff: &terraform.InstanceDiff{
  2785  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  2786  				DestroyTainted: true,
  2787  			},
  2788  		},
  2789  
  2790  		{
  2791  			Name: "Set ForceNew only marks the changing element as ForceNew",
  2792  			Schema: map[string]*Schema{
  2793  				"ports": {
  2794  					Type:     TypeSet,
  2795  					Required: true,
  2796  					ForceNew: true,
  2797  					Elem:     &Schema{Type: TypeInt},
  2798  					Set: func(a interface{}) int {
  2799  						return a.(int)
  2800  					},
  2801  				},
  2802  			},
  2803  
  2804  			State: &terraform.InstanceState{
  2805  				ID: "id",
  2806  				Attributes: map[string]string{
  2807  					"ports.#": "3",
  2808  					"ports.1": "1",
  2809  					"ports.2": "2",
  2810  					"ports.4": "4",
  2811  				},
  2812  			},
  2813  
  2814  			Config: map[string]interface{}{
  2815  				"ports": []interface{}{5, 2, 1},
  2816  			},
  2817  
  2818  			Diff: &terraform.InstanceDiff{
  2819  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2820  					"ports.1": {
  2821  						Old: "1",
  2822  						New: "1",
  2823  					},
  2824  					"ports.2": {
  2825  						Old: "2",
  2826  						New: "2",
  2827  					},
  2828  					"ports.5": {
  2829  						Old:         "",
  2830  						New:         "5",
  2831  						RequiresNew: true,
  2832  					},
  2833  					"ports.4": {
  2834  						Old:         "4",
  2835  						New:         "0",
  2836  						NewRemoved:  true,
  2837  						RequiresNew: true,
  2838  					},
  2839  				},
  2840  			},
  2841  		},
  2842  
  2843  		{
  2844  			Name: "removed optional items should trigger ForceNew",
  2845  			Schema: map[string]*Schema{
  2846  				"description": {
  2847  					Type:     TypeString,
  2848  					ForceNew: true,
  2849  					Optional: true,
  2850  				},
  2851  			},
  2852  
  2853  			State: &terraform.InstanceState{
  2854  				ID: "id",
  2855  				Attributes: map[string]string{
  2856  					"description": "foo",
  2857  				},
  2858  			},
  2859  
  2860  			Config: map[string]interface{}{},
  2861  
  2862  			Diff: &terraform.InstanceDiff{
  2863  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2864  					"description": {
  2865  						Old:         "foo",
  2866  						New:         "",
  2867  						RequiresNew: true,
  2868  						NewRemoved:  true,
  2869  					},
  2870  				},
  2871  			},
  2872  
  2873  			Err: false,
  2874  		},
  2875  
  2876  		// GH-7715
  2877  		{
  2878  			Name: "computed value for boolean field",
  2879  			Schema: map[string]*Schema{
  2880  				"foo": {
  2881  					Type:     TypeBool,
  2882  					ForceNew: true,
  2883  					Computed: true,
  2884  					Optional: true,
  2885  				},
  2886  			},
  2887  
  2888  			State: &terraform.InstanceState{
  2889  				ID: "id",
  2890  			},
  2891  
  2892  			Config: map[string]interface{}{
  2893  				"foo": hcl2shim.UnknownVariableValue,
  2894  			},
  2895  
  2896  			Diff: &terraform.InstanceDiff{
  2897  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2898  					"foo": {
  2899  						Old:         "",
  2900  						New:         "false",
  2901  						NewComputed: true,
  2902  						RequiresNew: true,
  2903  					},
  2904  				},
  2905  			},
  2906  
  2907  			Err: false,
  2908  		},
  2909  
  2910  		{
  2911  			Name: "Set ForceNew marks count as ForceNew if computed",
  2912  			Schema: map[string]*Schema{
  2913  				"ports": {
  2914  					Type:     TypeSet,
  2915  					Required: true,
  2916  					ForceNew: true,
  2917  					Elem:     &Schema{Type: TypeInt},
  2918  					Set: func(a interface{}) int {
  2919  						return a.(int)
  2920  					},
  2921  				},
  2922  			},
  2923  
  2924  			State: &terraform.InstanceState{
  2925  				ID: "id",
  2926  				Attributes: map[string]string{
  2927  					"ports.#": "3",
  2928  					"ports.1": "1",
  2929  					"ports.2": "2",
  2930  					"ports.4": "4",
  2931  				},
  2932  			},
  2933  
  2934  			Config: map[string]interface{}{
  2935  				"ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1},
  2936  			},
  2937  
  2938  			Diff: &terraform.InstanceDiff{
  2939  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2940  					"ports.#": {
  2941  						NewComputed: true,
  2942  						RequiresNew: true,
  2943  					},
  2944  				},
  2945  			},
  2946  		},
  2947  
  2948  		{
  2949  			Name: "List with computed schema and ForceNew",
  2950  			Schema: map[string]*Schema{
  2951  				"config": {
  2952  					Type:     TypeList,
  2953  					Optional: true,
  2954  					ForceNew: true,
  2955  					Elem: &Schema{
  2956  						Type: TypeString,
  2957  					},
  2958  				},
  2959  			},
  2960  
  2961  			State: &terraform.InstanceState{
  2962  				ID: "id",
  2963  				Attributes: map[string]string{
  2964  					"config.#": "2",
  2965  					"config.0": "a",
  2966  					"config.1": "b",
  2967  				},
  2968  			},
  2969  
  2970  			Config: map[string]interface{}{
  2971  				"config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue},
  2972  			},
  2973  
  2974  			Diff: &terraform.InstanceDiff{
  2975  				Attributes: map[string]*terraform.ResourceAttrDiff{
  2976  					"config.#": {
  2977  						Old:         "2",
  2978  						New:         "",
  2979  						RequiresNew: true,
  2980  						NewComputed: true,
  2981  					},
  2982  				},
  2983  			},
  2984  
  2985  			Err: false,
  2986  		},
  2987  
  2988  		{
  2989  			Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema",
  2990  			Schema: map[string]*Schema{
  2991  				"availability_zone": {
  2992  					Type:     TypeString,
  2993  					Optional: true,
  2994  					Computed: true,
  2995  				},
  2996  			},
  2997  
  2998  			State: nil,
  2999  
  3000  			Config: map[string]interface{}{
  3001  				"availability_zone": "foo",
  3002  			},
  3003  
  3004  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3005  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  3006  					return err
  3007  				}
  3008  				if err := d.ForceNew("availability_zone"); err != nil {
  3009  					return err
  3010  				}
  3011  				return nil
  3012  			},
  3013  
  3014  			Diff: &terraform.InstanceDiff{
  3015  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3016  					"availability_zone": {
  3017  						Old:         "",
  3018  						New:         "bar",
  3019  						RequiresNew: true,
  3020  					},
  3021  				},
  3022  			},
  3023  
  3024  			Err: false,
  3025  		},
  3026  
  3027  		{
  3028  			// NOTE: This case is technically impossible in the current
  3029  			// implementation, because optional+computed values never show up in the
  3030  			// diff. In the event behavior changes this test should ensure that the
  3031  			// intended diff still shows up.
  3032  			Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema",
  3033  			Schema: map[string]*Schema{
  3034  				"availability_zone": {
  3035  					Type:     TypeString,
  3036  					Optional: true,
  3037  					Computed: true,
  3038  				},
  3039  			},
  3040  
  3041  			State: nil,
  3042  
  3043  			Config: map[string]interface{}{},
  3044  
  3045  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3046  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  3047  					return err
  3048  				}
  3049  				if err := d.ForceNew("availability_zone"); err != nil {
  3050  					return err
  3051  				}
  3052  				return nil
  3053  			},
  3054  
  3055  			Diff: &terraform.InstanceDiff{
  3056  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3057  					"availability_zone": {
  3058  						Old:         "",
  3059  						New:         "bar",
  3060  						RequiresNew: true,
  3061  					},
  3062  				},
  3063  			},
  3064  
  3065  			Err: false,
  3066  		},
  3067  
  3068  		{
  3069  
  3070  			Name: "overridden diff with a CustomizeDiff function, ForceNew in schema",
  3071  			Schema: map[string]*Schema{
  3072  				"availability_zone": {
  3073  					Type:     TypeString,
  3074  					Optional: true,
  3075  					Computed: true,
  3076  					ForceNew: true,
  3077  				},
  3078  			},
  3079  
  3080  			State: nil,
  3081  
  3082  			Config: map[string]interface{}{
  3083  				"availability_zone": "foo",
  3084  			},
  3085  
  3086  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3087  				if err := d.SetNew("availability_zone", "bar"); err != nil {
  3088  					return err
  3089  				}
  3090  				return nil
  3091  			},
  3092  
  3093  			Diff: &terraform.InstanceDiff{
  3094  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3095  					"availability_zone": {
  3096  						Old:         "",
  3097  						New:         "bar",
  3098  						RequiresNew: true,
  3099  					},
  3100  				},
  3101  			},
  3102  
  3103  			Err: false,
  3104  		},
  3105  
  3106  		{
  3107  			Name: "required field with computed diff added with CustomizeDiff function",
  3108  			Schema: map[string]*Schema{
  3109  				"ami_id": {
  3110  					Type:     TypeString,
  3111  					Required: true,
  3112  				},
  3113  				"instance_id": {
  3114  					Type:     TypeString,
  3115  					Computed: true,
  3116  				},
  3117  			},
  3118  
  3119  			State: nil,
  3120  
  3121  			Config: map[string]interface{}{
  3122  				"ami_id": "foo",
  3123  			},
  3124  
  3125  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3126  				if err := d.SetNew("instance_id", "bar"); err != nil {
  3127  					return err
  3128  				}
  3129  				return nil
  3130  			},
  3131  
  3132  			Diff: &terraform.InstanceDiff{
  3133  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3134  					"ami_id": {
  3135  						Old: "",
  3136  						New: "foo",
  3137  					},
  3138  					"instance_id": {
  3139  						Old: "",
  3140  						New: "bar",
  3141  					},
  3142  				},
  3143  			},
  3144  
  3145  			Err: false,
  3146  		},
  3147  
  3148  		{
  3149  			Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition",
  3150  			Schema: map[string]*Schema{
  3151  				"ports": {
  3152  					Type:     TypeSet,
  3153  					Optional: true,
  3154  					Computed: true,
  3155  					Elem:     &Schema{Type: TypeInt},
  3156  					Set: func(a interface{}) int {
  3157  						return a.(int)
  3158  					},
  3159  				},
  3160  			},
  3161  
  3162  			State: &terraform.InstanceState{
  3163  				ID: "id",
  3164  				Attributes: map[string]string{
  3165  					"ports.#": "3",
  3166  					"ports.1": "1",
  3167  					"ports.2": "2",
  3168  					"ports.4": "4",
  3169  				},
  3170  			},
  3171  
  3172  			Config: map[string]interface{}{
  3173  				"ports": []interface{}{5, 2, 6},
  3174  			},
  3175  
  3176  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3177  				if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil {
  3178  					return err
  3179  				}
  3180  				if err := d.ForceNew("ports"); err != nil {
  3181  					return err
  3182  				}
  3183  				return nil
  3184  			},
  3185  
  3186  			Diff: &terraform.InstanceDiff{
  3187  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3188  					"ports.1": {
  3189  						Old: "1",
  3190  						New: "1",
  3191  					},
  3192  					"ports.2": {
  3193  						Old: "2",
  3194  						New: "2",
  3195  					},
  3196  					"ports.5": {
  3197  						Old:         "",
  3198  						New:         "5",
  3199  						RequiresNew: true,
  3200  					},
  3201  					"ports.4": {
  3202  						Old:         "4",
  3203  						New:         "0",
  3204  						NewRemoved:  true,
  3205  						RequiresNew: true,
  3206  					},
  3207  				},
  3208  			},
  3209  		},
  3210  
  3211  		{
  3212  			Name:   "tainted resource does not run CustomizeDiffFunc",
  3213  			Schema: map[string]*Schema{},
  3214  
  3215  			State: &terraform.InstanceState{
  3216  				ID: "someid",
  3217  				Attributes: map[string]string{
  3218  					"id": "someid",
  3219  				},
  3220  				Tainted: true,
  3221  			},
  3222  
  3223  			Config: map[string]interface{}{},
  3224  
  3225  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3226  				return errors.New("diff customization should not have run")
  3227  			},
  3228  
  3229  			Diff: &terraform.InstanceDiff{
  3230  				Attributes:     map[string]*terraform.ResourceAttrDiff{},
  3231  				DestroyTainted: true,
  3232  			},
  3233  
  3234  			Err: false,
  3235  		},
  3236  
  3237  		{
  3238  			Name: "NewComputed based on a conditional with CustomizeDiffFunc",
  3239  			Schema: map[string]*Schema{
  3240  				"etag": {
  3241  					Type:     TypeString,
  3242  					Optional: true,
  3243  					Computed: true,
  3244  				},
  3245  				"version_id": {
  3246  					Type:     TypeString,
  3247  					Computed: true,
  3248  				},
  3249  			},
  3250  
  3251  			State: &terraform.InstanceState{
  3252  				ID: "id",
  3253  				Attributes: map[string]string{
  3254  					"etag":       "foo",
  3255  					"version_id": "1",
  3256  				},
  3257  			},
  3258  
  3259  			Config: map[string]interface{}{
  3260  				"etag": "bar",
  3261  			},
  3262  
  3263  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3264  				if d.HasChange("etag") {
  3265  					d.SetNewComputed("version_id")
  3266  				}
  3267  				return nil
  3268  			},
  3269  
  3270  			Diff: &terraform.InstanceDiff{
  3271  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3272  					"etag": {
  3273  						Old: "foo",
  3274  						New: "bar",
  3275  					},
  3276  					"version_id": {
  3277  						Old:         "1",
  3278  						New:         "",
  3279  						NewComputed: true,
  3280  					},
  3281  				},
  3282  			},
  3283  
  3284  			Err: false,
  3285  		},
  3286  
  3287  		{
  3288  			Name: "vetoing a diff",
  3289  			Schema: map[string]*Schema{
  3290  				"foo": {
  3291  					Type:     TypeString,
  3292  					Optional: true,
  3293  					Computed: true,
  3294  				},
  3295  			},
  3296  
  3297  			State: &terraform.InstanceState{
  3298  				ID: "id",
  3299  				Attributes: map[string]string{
  3300  					"foo": "bar",
  3301  				},
  3302  			},
  3303  
  3304  			Config: map[string]interface{}{
  3305  				"foo": "baz",
  3306  			},
  3307  
  3308  			CustomizeDiff: func(d *ResourceDiff, meta interface{}) error {
  3309  				return fmt.Errorf("diff vetoed")
  3310  			},
  3311  
  3312  			Err: true,
  3313  		},
  3314  
  3315  		// A lot of resources currently depended on using the empty string as a
  3316  		// nil/unset value.
  3317  		{
  3318  			Name: "optional, computed, empty string",
  3319  			Schema: map[string]*Schema{
  3320  				"attr": {
  3321  					Type:     TypeString,
  3322  					Optional: true,
  3323  					Computed: true,
  3324  				},
  3325  			},
  3326  
  3327  			State: &terraform.InstanceState{
  3328  				ID: "id",
  3329  				Attributes: map[string]string{
  3330  					"attr": "bar",
  3331  				},
  3332  			},
  3333  
  3334  			Config: map[string]interface{}{
  3335  				"attr": "",
  3336  			},
  3337  		},
  3338  
  3339  		{
  3340  			Name: "optional, computed, empty string should not crash in CustomizeDiff",
  3341  			Schema: map[string]*Schema{
  3342  				"unrelated_set": {
  3343  					Type:     TypeSet,
  3344  					Optional: true,
  3345  					Elem:     &Schema{Type: TypeString},
  3346  				},
  3347  				"stream_enabled": {
  3348  					Type:     TypeBool,
  3349  					Optional: true,
  3350  				},
  3351  				"stream_view_type": {
  3352  					Type:     TypeString,
  3353  					Optional: true,
  3354  					Computed: true,
  3355  				},
  3356  			},
  3357  
  3358  			State: &terraform.InstanceState{
  3359  				ID: "id",
  3360  				Attributes: map[string]string{
  3361  					"unrelated_set.#":  "0",
  3362  					"stream_enabled":   "true",
  3363  					"stream_view_type": "KEYS_ONLY",
  3364  				},
  3365  			},
  3366  			Config: map[string]interface{}{
  3367  				"stream_enabled":   false,
  3368  				"stream_view_type": "",
  3369  			},
  3370  			CustomizeDiff: func(diff *ResourceDiff, v interface{}) error {
  3371  				v, ok := diff.GetOk("unrelated_set")
  3372  				if ok {
  3373  					return fmt.Errorf("Didn't expect unrelated_set: %#v", v)
  3374  				}
  3375  				return nil
  3376  			},
  3377  			Diff: &terraform.InstanceDiff{
  3378  				Attributes: map[string]*terraform.ResourceAttrDiff{
  3379  					"stream_enabled": {
  3380  						Old: "true",
  3381  						New: "false",
  3382  					},
  3383  				},
  3384  			},
  3385  		},
  3386  	}
  3387  
  3388  	for i, tc := range cases {
  3389  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  3390  			c := terraform.NewResourceConfigRaw(tc.Config)
  3391  
  3392  			{
  3393  				d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, false)
  3394  				if err != nil != tc.Err {
  3395  					t.Fatalf("err: %s", err)
  3396  				}
  3397  				if !cmp.Equal(d, tc.Diff, equateEmpty) {
  3398  					t.Fatal(cmp.Diff(d, tc.Diff, equateEmpty))
  3399  				}
  3400  			}
  3401  			// up to here is already tested in helper/schema; we're just
  3402  			// verify that we haven't broken any tests in transition.
  3403  
  3404  			// create a schema from the schemaMap
  3405  			testSchema := resourceSchemaToBlock(tc.Schema)
  3406  
  3407  			// get our initial state cty.Value
  3408  			stateVal, err := StateValueFromInstanceState(tc.State, testSchema.ImpliedType())
  3409  			if err != nil {
  3410  				t.Fatal(err)
  3411  			}
  3412  
  3413  			// this is the desired cty.Value from the configuration
  3414  			configVal := hcl2shim.HCL2ValueFromConfigValue(c.Config)
  3415  
  3416  			// verify that we can round-trip the config
  3417  			origConfig := hcl2shim.ConfigValueFromHCL2(configVal)
  3418  			if !cmp.Equal(c.Config, origConfig, equateEmpty) {
  3419  				t.Fatal(cmp.Diff(c.Config, origConfig, equateEmpty))
  3420  			}
  3421  
  3422  			// make sure our config conforms precisely to the schema
  3423  			configVal, err = testSchema.CoerceValue(configVal)
  3424  			if err != nil {
  3425  				t.Fatal(tfdiags.FormatError(err))
  3426  			}
  3427  
  3428  			// The new API requires returning the desired state rather than a
  3429  			// diff, so we need to verify that we can combine the state and
  3430  			// diff and recreate a new state.
  3431  
  3432  			// now verify that we can create diff, using the new config and state values
  3433  			// customize isn't run on tainted resources
  3434  			tainted := tc.State != nil && tc.State.Tainted
  3435  			if tainted {
  3436  				tc.CustomizeDiff = nil
  3437  			}
  3438  
  3439  			res := &Resource{Schema: tc.Schema}
  3440  
  3441  			d, err := diffFromValues(stateVal, configVal, res, tc.CustomizeDiff)
  3442  			if err != nil {
  3443  				if !tc.Err {
  3444  					t.Fatal(err)
  3445  				}
  3446  			}
  3447  
  3448  			// In a real "apply" operation there would be no unknown values,
  3449  			// so for tests containing unknowns we'll stop here: the steps
  3450  			// after this point apply only to the apply phase.
  3451  			if !configVal.IsWhollyKnown() {
  3452  				return
  3453  			}
  3454  
  3455  			// our diff function can't set DestroyTainted, but match the
  3456  			// expected value here for the test fixtures
  3457  			if tainted {
  3458  				if d == nil {
  3459  					d = &terraform.InstanceDiff{}
  3460  				}
  3461  				d.DestroyTainted = true
  3462  			}
  3463  
  3464  			if eq, _ := d.Same(tc.Diff); !eq {
  3465  				t.Fatal(cmp.Diff(d, tc.Diff))
  3466  			}
  3467  
  3468  		})
  3469  	}
  3470  }
  3471  
  3472  func resourceSchemaToBlock(s map[string]*Schema) *configschema.Block {
  3473  	return (&Resource{Schema: s}).CoreConfigSchema()
  3474  }
  3475  
  3476  func TestRemoveConfigUnknowns(t *testing.T) {
  3477  	cfg := map[string]interface{}{
  3478  		"id": "74D93920-ED26-11E3-AC10-0800200C9A66",
  3479  		"route_rules": []interface{}{
  3480  			map[string]interface{}{
  3481  				"cidr_block":        "74D93920-ED26-11E3-AC10-0800200C9A66",
  3482  				"destination":       "0.0.0.0/0",
  3483  				"destination_type":  "CIDR_BLOCK",
  3484  				"network_entity_id": "1",
  3485  			},
  3486  			map[string]interface{}{
  3487  				"cidr_block":       "74D93920-ED26-11E3-AC10-0800200C9A66",
  3488  				"destination":      "0.0.0.0/0",
  3489  				"destination_type": "CIDR_BLOCK",
  3490  				"sub_block": []interface{}{
  3491  					map[string]interface{}{
  3492  						"computed": "74D93920-ED26-11E3-AC10-0800200C9A66",
  3493  					},
  3494  				},
  3495  			},
  3496  		},
  3497  	}
  3498  
  3499  	expect := map[string]interface{}{
  3500  		"route_rules": []interface{}{
  3501  			map[string]interface{}{
  3502  				"destination":       "0.0.0.0/0",
  3503  				"destination_type":  "CIDR_BLOCK",
  3504  				"network_entity_id": "1",
  3505  			},
  3506  			map[string]interface{}{
  3507  				"destination":      "0.0.0.0/0",
  3508  				"destination_type": "CIDR_BLOCK",
  3509  				"sub_block": []interface{}{
  3510  					map[string]interface{}{},
  3511  				},
  3512  			},
  3513  		},
  3514  	}
  3515  
  3516  	removeConfigUnknowns(cfg)
  3517  
  3518  	if !reflect.DeepEqual(cfg, expect) {
  3519  		t.Fatalf("\nexpected: %#v\ngot:      %#v", expect, cfg)
  3520  	}
  3521  }