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

     1  package schema
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"testing"
     8  
     9  	"github.com/davecgh/go-spew/spew"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
    11  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
    12  )
    13  
    14  // testSetFunc is a very simple function we use to test a foo/bar complex set.
    15  // Both "foo" and "bar" are int values.
    16  //
    17  // This is not foolproof as since it performs sums, you can run into
    18  // collisions. Spec tests accordingly. :P
    19  func testSetFunc(v interface{}) int {
    20  	m := v.(map[string]interface{})
    21  	return m["foo"].(int) + m["bar"].(int)
    22  }
    23  
    24  // resourceDiffTestCase provides a test case struct for SetNew and SetDiff.
    25  type resourceDiffTestCase struct {
    26  	Name          string
    27  	Schema        map[string]*Schema
    28  	State         *terraform.InstanceState
    29  	Config        *terraform.ResourceConfig
    30  	Diff          *terraform.InstanceDiff
    31  	Key           string
    32  	OldValue      interface{}
    33  	NewValue      interface{}
    34  	Expected      *terraform.InstanceDiff
    35  	ExpectedKeys  []string
    36  	ExpectedError bool
    37  }
    38  
    39  // testDiffCases produces a list of test cases for use with SetNew and SetDiff.
    40  func testDiffCases(t *testing.T, oldPrefix string, oldOffset int, computed bool) []resourceDiffTestCase {
    41  	return []resourceDiffTestCase{
    42  		{
    43  			Name: "basic primitive diff",
    44  			Schema: map[string]*Schema{
    45  				"foo": {
    46  					Type:     TypeString,
    47  					Optional: true,
    48  					Computed: true,
    49  				},
    50  			},
    51  			State: &terraform.InstanceState{
    52  				Attributes: map[string]string{
    53  					"foo": "bar",
    54  				},
    55  			},
    56  			Config: testConfig(t, map[string]interface{}{
    57  				"foo": "baz",
    58  			}),
    59  			Diff: &terraform.InstanceDiff{
    60  				Attributes: map[string]*terraform.ResourceAttrDiff{
    61  					"foo": {
    62  						Old: "bar",
    63  						New: "baz",
    64  					},
    65  				},
    66  			},
    67  			Key:      "foo",
    68  			NewValue: "qux",
    69  			Expected: &terraform.InstanceDiff{
    70  				Attributes: map[string]*terraform.ResourceAttrDiff{
    71  					"foo": {
    72  						Old: "bar",
    73  						New: func() string {
    74  							if computed {
    75  								return ""
    76  							}
    77  							return "qux"
    78  						}(),
    79  						NewComputed: computed,
    80  					},
    81  				},
    82  			},
    83  		},
    84  		{
    85  			Name: "basic set diff",
    86  			Schema: map[string]*Schema{
    87  				"foo": {
    88  					Type:     TypeSet,
    89  					Optional: true,
    90  					Computed: true,
    91  					Elem:     &Schema{Type: TypeString},
    92  					Set:      HashString,
    93  				},
    94  			},
    95  			State: &terraform.InstanceState{
    96  				Attributes: map[string]string{
    97  					"foo.#":          "1",
    98  					"foo.1996459178": "bar",
    99  				},
   100  			},
   101  			Config: testConfig(t, map[string]interface{}{
   102  				"foo": []interface{}{"baz"},
   103  			}),
   104  			Diff: &terraform.InstanceDiff{
   105  				Attributes: map[string]*terraform.ResourceAttrDiff{
   106  					"foo.1996459178": {
   107  						Old:        "bar",
   108  						New:        "",
   109  						NewRemoved: true,
   110  					},
   111  					"foo.2015626392": {
   112  						Old: "",
   113  						New: "baz",
   114  					},
   115  				},
   116  			},
   117  			Key:      "foo",
   118  			NewValue: []interface{}{"qux"},
   119  			Expected: &terraform.InstanceDiff{
   120  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   121  					result := map[string]*terraform.ResourceAttrDiff{}
   122  					if computed {
   123  						result["foo.#"] = &terraform.ResourceAttrDiff{
   124  							Old:         "1",
   125  							New:         "",
   126  							NewComputed: true,
   127  						}
   128  					} else {
   129  						result["foo.2800005064"] = &terraform.ResourceAttrDiff{
   130  							Old: "",
   131  							New: "qux",
   132  						}
   133  						result["foo.1996459178"] = &terraform.ResourceAttrDiff{
   134  							Old:        "bar",
   135  							New:        "",
   136  							NewRemoved: true,
   137  						}
   138  					}
   139  					return result
   140  				}(),
   141  			},
   142  		},
   143  		{
   144  			Name: "basic list diff",
   145  			Schema: map[string]*Schema{
   146  				"foo": {
   147  					Type:     TypeList,
   148  					Optional: true,
   149  					Computed: true,
   150  					Elem:     &Schema{Type: TypeString},
   151  				},
   152  			},
   153  			State: &terraform.InstanceState{
   154  				Attributes: map[string]string{
   155  					"foo.#": "1",
   156  					"foo.0": "bar",
   157  				},
   158  			},
   159  			Config: testConfig(t, map[string]interface{}{
   160  				"foo": []interface{}{"baz"},
   161  			}),
   162  			Diff: &terraform.InstanceDiff{
   163  				Attributes: map[string]*terraform.ResourceAttrDiff{
   164  					"foo.0": {
   165  						Old: "bar",
   166  						New: "baz",
   167  					},
   168  				},
   169  			},
   170  			Key:      "foo",
   171  			NewValue: []interface{}{"qux"},
   172  			Expected: &terraform.InstanceDiff{
   173  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   174  					result := make(map[string]*terraform.ResourceAttrDiff)
   175  					if computed {
   176  						result["foo.#"] = &terraform.ResourceAttrDiff{
   177  							Old:         "1",
   178  							New:         "",
   179  							NewComputed: true,
   180  						}
   181  					} else {
   182  						result["foo.0"] = &terraform.ResourceAttrDiff{
   183  							Old: "bar",
   184  							New: "qux",
   185  						}
   186  					}
   187  					return result
   188  				}(),
   189  			},
   190  		},
   191  		{
   192  			Name: "basic map diff",
   193  			Schema: map[string]*Schema{
   194  				"foo": {
   195  					Type:     TypeMap,
   196  					Optional: true,
   197  					Computed: true,
   198  				},
   199  			},
   200  			State: &terraform.InstanceState{
   201  				Attributes: map[string]string{
   202  					"foo.%":   "1",
   203  					"foo.bar": "baz",
   204  				},
   205  			},
   206  			Config: testConfig(t, map[string]interface{}{
   207  				"foo": map[string]interface{}{"bar": "qux"},
   208  			}),
   209  			Diff: &terraform.InstanceDiff{
   210  				Attributes: map[string]*terraform.ResourceAttrDiff{
   211  					"foo.bar": {
   212  						Old: "baz",
   213  						New: "qux",
   214  					},
   215  				},
   216  			},
   217  			Key:      "foo",
   218  			NewValue: map[string]interface{}{"bar": "quux"},
   219  			Expected: &terraform.InstanceDiff{
   220  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   221  					result := make(map[string]*terraform.ResourceAttrDiff)
   222  					if computed {
   223  						result["foo.%"] = &terraform.ResourceAttrDiff{
   224  							Old:         "",
   225  							New:         "",
   226  							NewComputed: true,
   227  						}
   228  						result["foo.bar"] = &terraform.ResourceAttrDiff{
   229  							Old:        "baz",
   230  							New:        "",
   231  							NewRemoved: true,
   232  						}
   233  					} else {
   234  						result["foo.bar"] = &terraform.ResourceAttrDiff{
   235  							Old: "baz",
   236  							New: "quux",
   237  						}
   238  					}
   239  					return result
   240  				}(),
   241  			},
   242  		},
   243  		{
   244  			Name: "additional diff with primitive",
   245  			Schema: map[string]*Schema{
   246  				"foo": {
   247  					Type:     TypeString,
   248  					Optional: true,
   249  				},
   250  				"one": {
   251  					Type:     TypeString,
   252  					Optional: true,
   253  					Computed: true,
   254  				},
   255  			},
   256  			State: &terraform.InstanceState{
   257  				Attributes: map[string]string{
   258  					"foo": "bar",
   259  					"one": "two",
   260  				},
   261  			},
   262  			Config: testConfig(t, map[string]interface{}{
   263  				"foo": "baz",
   264  			}),
   265  			Diff: &terraform.InstanceDiff{
   266  				Attributes: map[string]*terraform.ResourceAttrDiff{
   267  					"foo": {
   268  						Old: "bar",
   269  						New: "baz",
   270  					},
   271  				},
   272  			},
   273  			Key:      "one",
   274  			NewValue: "four",
   275  			Expected: &terraform.InstanceDiff{
   276  				Attributes: map[string]*terraform.ResourceAttrDiff{
   277  					"foo": {
   278  						Old: "bar",
   279  						New: "baz",
   280  					},
   281  					"one": {
   282  						Old: "two",
   283  						New: func() string {
   284  							if computed {
   285  								return ""
   286  							}
   287  							return "four"
   288  						}(),
   289  						NewComputed: computed,
   290  					},
   291  				},
   292  			},
   293  		},
   294  		{
   295  			Name: "additional diff with primitive computed only",
   296  			Schema: map[string]*Schema{
   297  				"foo": {
   298  					Type:     TypeString,
   299  					Optional: true,
   300  				},
   301  				"one": {
   302  					Type:     TypeString,
   303  					Computed: true,
   304  				},
   305  			},
   306  			State: &terraform.InstanceState{
   307  				Attributes: map[string]string{
   308  					"foo": "bar",
   309  					"one": "two",
   310  				},
   311  			},
   312  			Config: testConfig(t, map[string]interface{}{
   313  				"foo": "baz",
   314  			}),
   315  			Diff: &terraform.InstanceDiff{
   316  				Attributes: map[string]*terraform.ResourceAttrDiff{
   317  					"foo": {
   318  						Old: "bar",
   319  						New: "baz",
   320  					},
   321  				},
   322  			},
   323  			Key:      "one",
   324  			NewValue: "three",
   325  			Expected: &terraform.InstanceDiff{
   326  				Attributes: map[string]*terraform.ResourceAttrDiff{
   327  					"foo": {
   328  						Old: "bar",
   329  						New: "baz",
   330  					},
   331  					"one": {
   332  						Old: "two",
   333  						New: func() string {
   334  							if computed {
   335  								return ""
   336  							}
   337  							return "three"
   338  						}(),
   339  						NewComputed: computed,
   340  					},
   341  				},
   342  			},
   343  		},
   344  		{
   345  			Name: "complex-ish set diff",
   346  			Schema: map[string]*Schema{
   347  				"top": {
   348  					Type:     TypeSet,
   349  					Optional: true,
   350  					Computed: true,
   351  					Elem: &Resource{
   352  						Schema: map[string]*Schema{
   353  							"foo": {
   354  								Type:     TypeInt,
   355  								Optional: true,
   356  								Computed: true,
   357  							},
   358  							"bar": {
   359  								Type:     TypeInt,
   360  								Optional: true,
   361  								Computed: true,
   362  							},
   363  						},
   364  					},
   365  					Set: testSetFunc,
   366  				},
   367  			},
   368  			State: &terraform.InstanceState{
   369  				Attributes: map[string]string{
   370  					"top.#":      "2",
   371  					"top.3.foo":  "1",
   372  					"top.3.bar":  "2",
   373  					"top.23.foo": "11",
   374  					"top.23.bar": "12",
   375  				},
   376  			},
   377  			Config: testConfig(t, map[string]interface{}{
   378  				"top": []interface{}{
   379  					map[string]interface{}{
   380  						"foo": 1,
   381  						"bar": 3,
   382  					},
   383  					map[string]interface{}{
   384  						"foo": 12,
   385  						"bar": 12,
   386  					},
   387  				},
   388  			}),
   389  			Diff: &terraform.InstanceDiff{
   390  				Attributes: map[string]*terraform.ResourceAttrDiff{
   391  					"top.4.foo": {
   392  						Old: "",
   393  						New: "1",
   394  					},
   395  					"top.4.bar": {
   396  						Old: "",
   397  						New: "3",
   398  					},
   399  					"top.24.foo": {
   400  						Old: "",
   401  						New: "12",
   402  					},
   403  					"top.24.bar": {
   404  						Old: "",
   405  						New: "12",
   406  					},
   407  				},
   408  			},
   409  			Key: "top",
   410  			NewValue: NewSet(testSetFunc, []interface{}{
   411  				map[string]interface{}{
   412  					"foo": 1,
   413  					"bar": 4,
   414  				},
   415  				map[string]interface{}{
   416  					"foo": 13,
   417  					"bar": 12,
   418  				},
   419  				map[string]interface{}{
   420  					"foo": 21,
   421  					"bar": 22,
   422  				},
   423  			}),
   424  			Expected: &terraform.InstanceDiff{
   425  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   426  					result := make(map[string]*terraform.ResourceAttrDiff)
   427  					if computed {
   428  						result["top.#"] = &terraform.ResourceAttrDiff{
   429  							Old:         "2",
   430  							New:         "",
   431  							NewComputed: true,
   432  						}
   433  					} else {
   434  						result["top.#"] = &terraform.ResourceAttrDiff{
   435  							Old: "2",
   436  							New: "3",
   437  						}
   438  						result["top.5.foo"] = &terraform.ResourceAttrDiff{
   439  							Old: "",
   440  							New: "1",
   441  						}
   442  						result["top.5.bar"] = &terraform.ResourceAttrDiff{
   443  							Old: "",
   444  							New: "4",
   445  						}
   446  						result["top.25.foo"] = &terraform.ResourceAttrDiff{
   447  							Old: "",
   448  							New: "13",
   449  						}
   450  						result["top.25.bar"] = &terraform.ResourceAttrDiff{
   451  							Old: "",
   452  							New: "12",
   453  						}
   454  						result["top.43.foo"] = &terraform.ResourceAttrDiff{
   455  							Old: "",
   456  							New: "21",
   457  						}
   458  						result["top.43.bar"] = &terraform.ResourceAttrDiff{
   459  							Old: "",
   460  							New: "22",
   461  						}
   462  					}
   463  					return result
   464  				}(),
   465  			},
   466  		},
   467  		{
   468  			Name: "primitive, no diff, no refresh",
   469  			Schema: map[string]*Schema{
   470  				"foo": {
   471  					Type:     TypeString,
   472  					Computed: true,
   473  				},
   474  			},
   475  			State: &terraform.InstanceState{
   476  				Attributes: map[string]string{
   477  					"foo": "bar",
   478  				},
   479  			},
   480  			Config:   testConfig(t, map[string]interface{}{}),
   481  			Diff:     &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   482  			Key:      "foo",
   483  			NewValue: "baz",
   484  			Expected: &terraform.InstanceDiff{
   485  				Attributes: map[string]*terraform.ResourceAttrDiff{
   486  					"foo": {
   487  						Old: "bar",
   488  						New: func() string {
   489  							if computed {
   490  								return ""
   491  							}
   492  							return "baz"
   493  						}(),
   494  						NewComputed: computed,
   495  					},
   496  				},
   497  			},
   498  		},
   499  		{
   500  			Name: "non-computed key, should error",
   501  			Schema: map[string]*Schema{
   502  				"foo": {
   503  					Type:     TypeString,
   504  					Required: true,
   505  				},
   506  			},
   507  			State: &terraform.InstanceState{
   508  				Attributes: map[string]string{
   509  					"foo": "bar",
   510  				},
   511  			},
   512  			Config: testConfig(t, map[string]interface{}{
   513  				"foo": "baz",
   514  			}),
   515  			Diff: &terraform.InstanceDiff{
   516  				Attributes: map[string]*terraform.ResourceAttrDiff{
   517  					"foo": {
   518  						Old: "bar",
   519  						New: "baz",
   520  					},
   521  				},
   522  			},
   523  			Key:           "foo",
   524  			NewValue:      "qux",
   525  			ExpectedError: true,
   526  		},
   527  		{
   528  			Name: "bad key, should error",
   529  			Schema: map[string]*Schema{
   530  				"foo": {
   531  					Type:     TypeString,
   532  					Required: true,
   533  				},
   534  			},
   535  			State: &terraform.InstanceState{
   536  				Attributes: map[string]string{
   537  					"foo": "bar",
   538  				},
   539  			},
   540  			Config: testConfig(t, map[string]interface{}{
   541  				"foo": "baz",
   542  			}),
   543  			Diff: &terraform.InstanceDiff{
   544  				Attributes: map[string]*terraform.ResourceAttrDiff{
   545  					"foo": {
   546  						Old: "bar",
   547  						New: "baz",
   548  					},
   549  				},
   550  			},
   551  			Key:           "bad",
   552  			NewValue:      "qux",
   553  			ExpectedError: true,
   554  		},
   555  		{
   556  			// NOTE: This case is technically impossible in the current
   557  			// implementation, because optional+computed values never show up in the
   558  			// diff, and we actually clear existing diffs when SetNew or
   559  			// SetNewComputed is run.  This test is here to ensure that if either of
   560  			// these behaviors change that we don't introduce regressions.
   561  			Name: "NewRemoved in diff for Optional and Computed, should be fully overridden",
   562  			Schema: map[string]*Schema{
   563  				"foo": {
   564  					Type:     TypeString,
   565  					Optional: true,
   566  					Computed: true,
   567  				},
   568  			},
   569  			State: &terraform.InstanceState{
   570  				Attributes: map[string]string{
   571  					"foo": "bar",
   572  				},
   573  			},
   574  			Config: testConfig(t, map[string]interface{}{}),
   575  			Diff: &terraform.InstanceDiff{
   576  				Attributes: map[string]*terraform.ResourceAttrDiff{
   577  					"foo": {
   578  						Old:        "bar",
   579  						New:        "",
   580  						NewRemoved: true,
   581  					},
   582  				},
   583  			},
   584  			Key:      "foo",
   585  			NewValue: "qux",
   586  			Expected: &terraform.InstanceDiff{
   587  				Attributes: map[string]*terraform.ResourceAttrDiff{
   588  					"foo": {
   589  						Old: "bar",
   590  						New: func() string {
   591  							if computed {
   592  								return ""
   593  							}
   594  							return "qux"
   595  						}(),
   596  						NewComputed: computed,
   597  					},
   598  				},
   599  			},
   600  		},
   601  		{
   602  			Name: "NewComputed should always propagate",
   603  			Schema: map[string]*Schema{
   604  				"foo": {
   605  					Type:     TypeString,
   606  					Computed: true,
   607  				},
   608  			},
   609  			State: &terraform.InstanceState{
   610  				Attributes: map[string]string{
   611  					"foo": "",
   612  				},
   613  				ID: "pre-existing",
   614  			},
   615  			Config:   testConfig(t, map[string]interface{}{}),
   616  			Diff:     &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   617  			Key:      "foo",
   618  			NewValue: "",
   619  			Expected: &terraform.InstanceDiff{
   620  				Attributes: func() map[string]*terraform.ResourceAttrDiff {
   621  					if computed {
   622  						return map[string]*terraform.ResourceAttrDiff{
   623  							"foo": {
   624  								NewComputed: computed,
   625  							},
   626  						}
   627  					}
   628  					return map[string]*terraform.ResourceAttrDiff{}
   629  				}(),
   630  			},
   631  		},
   632  	}
   633  }
   634  
   635  func TestSetNew(t *testing.T) {
   636  	testCases := testDiffCases(t, "", 0, false)
   637  	for _, tc := range testCases {
   638  		t.Run(tc.Name, func(t *testing.T) {
   639  			m := schemaMap(tc.Schema)
   640  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
   641  			err := d.SetNew(tc.Key, tc.NewValue)
   642  			switch {
   643  			case err != nil && !tc.ExpectedError:
   644  				t.Fatalf("bad: %s", err)
   645  			case err == nil && tc.ExpectedError:
   646  				t.Fatalf("Expected error, got none")
   647  			case err != nil && tc.ExpectedError:
   648  				return
   649  			}
   650  			for _, k := range d.UpdatedKeys() {
   651  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   652  					t.Fatalf("bad: %s", err)
   653  				}
   654  			}
   655  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   656  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   657  			}
   658  		})
   659  	}
   660  }
   661  
   662  func TestSetNewComputed(t *testing.T) {
   663  	testCases := testDiffCases(t, "", 0, true)
   664  	for _, tc := range testCases {
   665  		t.Run(tc.Name, func(t *testing.T) {
   666  			m := schemaMap(tc.Schema)
   667  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
   668  			err := d.SetNewComputed(tc.Key)
   669  			switch {
   670  			case err != nil && !tc.ExpectedError:
   671  				t.Fatalf("bad: %s", err)
   672  			case err == nil && tc.ExpectedError:
   673  				t.Fatalf("Expected error, got none")
   674  			case err != nil && tc.ExpectedError:
   675  				return
   676  			}
   677  			for _, k := range d.UpdatedKeys() {
   678  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   679  					t.Fatalf("bad: %s", err)
   680  				}
   681  			}
   682  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   683  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   684  			}
   685  		})
   686  	}
   687  }
   688  
   689  func TestForceNew(t *testing.T) {
   690  	cases := []resourceDiffTestCase{
   691  		{
   692  			Name: "basic primitive diff",
   693  			Schema: map[string]*Schema{
   694  				"foo": {
   695  					Type:     TypeString,
   696  					Optional: true,
   697  					Computed: true,
   698  				},
   699  			},
   700  			State: &terraform.InstanceState{
   701  				Attributes: map[string]string{
   702  					"foo": "bar",
   703  				},
   704  			},
   705  			Config: testConfig(t, map[string]interface{}{
   706  				"foo": "baz",
   707  			}),
   708  			Diff: &terraform.InstanceDiff{
   709  				Attributes: map[string]*terraform.ResourceAttrDiff{
   710  					"foo": {
   711  						Old: "bar",
   712  						New: "baz",
   713  					},
   714  				},
   715  			},
   716  			Key: "foo",
   717  			Expected: &terraform.InstanceDiff{
   718  				Attributes: map[string]*terraform.ResourceAttrDiff{
   719  					"foo": {
   720  						Old:         "bar",
   721  						New:         "baz",
   722  						RequiresNew: true,
   723  					},
   724  				},
   725  			},
   726  		},
   727  		{
   728  			Name: "no change, should error",
   729  			Schema: map[string]*Schema{
   730  				"foo": {
   731  					Type:     TypeString,
   732  					Optional: true,
   733  					Computed: true,
   734  				},
   735  			},
   736  			State: &terraform.InstanceState{
   737  				Attributes: map[string]string{
   738  					"foo": "bar",
   739  				},
   740  			},
   741  			Config: testConfig(t, map[string]interface{}{
   742  				"foo": "bar",
   743  			}),
   744  			ExpectedError: true,
   745  		},
   746  		{
   747  			Name: "basic primitive, non-computed key",
   748  			Schema: map[string]*Schema{
   749  				"foo": {
   750  					Type:     TypeString,
   751  					Required: true,
   752  				},
   753  			},
   754  			State: &terraform.InstanceState{
   755  				Attributes: map[string]string{
   756  					"foo": "bar",
   757  				},
   758  			},
   759  			Config: testConfig(t, map[string]interface{}{
   760  				"foo": "baz",
   761  			}),
   762  			Diff: &terraform.InstanceDiff{
   763  				Attributes: map[string]*terraform.ResourceAttrDiff{
   764  					"foo": {
   765  						Old: "bar",
   766  						New: "baz",
   767  					},
   768  				},
   769  			},
   770  			Key: "foo",
   771  			Expected: &terraform.InstanceDiff{
   772  				Attributes: map[string]*terraform.ResourceAttrDiff{
   773  					"foo": {
   774  						Old:         "bar",
   775  						New:         "baz",
   776  						RequiresNew: true,
   777  					},
   778  				},
   779  			},
   780  		},
   781  		{
   782  			Name: "nested field",
   783  			Schema: map[string]*Schema{
   784  				"foo": {
   785  					Type:     TypeList,
   786  					Required: true,
   787  					MaxItems: 1,
   788  					Elem: &Resource{
   789  						Schema: map[string]*Schema{
   790  							"bar": {
   791  								Type:     TypeString,
   792  								Optional: true,
   793  							},
   794  							"baz": {
   795  								Type:     TypeString,
   796  								Optional: true,
   797  							},
   798  						},
   799  					},
   800  				},
   801  			},
   802  			State: &terraform.InstanceState{
   803  				Attributes: map[string]string{
   804  					"foo.#":     "1",
   805  					"foo.0.bar": "abc",
   806  					"foo.0.baz": "xyz",
   807  				},
   808  			},
   809  			Config: testConfig(t, map[string]interface{}{
   810  				"foo": []interface{}{
   811  					map[string]interface{}{
   812  						"bar": "abcdefg",
   813  						"baz": "changed",
   814  					},
   815  				},
   816  			}),
   817  			Diff: &terraform.InstanceDiff{
   818  				Attributes: map[string]*terraform.ResourceAttrDiff{
   819  					"foo.0.bar": {
   820  						Old: "abc",
   821  						New: "abcdefg",
   822  					},
   823  					"foo.0.baz": {
   824  						Old: "xyz",
   825  						New: "changed",
   826  					},
   827  				},
   828  			},
   829  			Key: "foo.0.baz",
   830  			Expected: &terraform.InstanceDiff{
   831  				Attributes: map[string]*terraform.ResourceAttrDiff{
   832  					"foo.0.bar": {
   833  						Old: "abc",
   834  						New: "abcdefg",
   835  					},
   836  					"foo.0.baz": {
   837  						Old:         "xyz",
   838  						New:         "changed",
   839  						RequiresNew: true,
   840  					},
   841  				},
   842  			},
   843  		},
   844  		{
   845  			Name: "preserve NewRemoved on existing diff",
   846  			Schema: map[string]*Schema{
   847  				"foo": {
   848  					Type:     TypeString,
   849  					Optional: true,
   850  				},
   851  			},
   852  			State: &terraform.InstanceState{
   853  				Attributes: map[string]string{
   854  					"foo": "bar",
   855  				},
   856  			},
   857  			Config: testConfig(t, map[string]interface{}{}),
   858  			Diff: &terraform.InstanceDiff{
   859  				Attributes: map[string]*terraform.ResourceAttrDiff{
   860  					"foo": {
   861  						Old:        "bar",
   862  						New:        "",
   863  						NewRemoved: true,
   864  					},
   865  				},
   866  			},
   867  			Key: "foo",
   868  			Expected: &terraform.InstanceDiff{
   869  				Attributes: map[string]*terraform.ResourceAttrDiff{
   870  					"foo": {
   871  						Old:         "bar",
   872  						New:         "",
   873  						RequiresNew: true,
   874  						NewRemoved:  true,
   875  					},
   876  				},
   877  			},
   878  		},
   879  		{
   880  			Name: "nested field, preserve original diff without zero values",
   881  			Schema: map[string]*Schema{
   882  				"foo": {
   883  					Type:     TypeList,
   884  					Required: true,
   885  					MaxItems: 1,
   886  					Elem: &Resource{
   887  						Schema: map[string]*Schema{
   888  							"bar": {
   889  								Type:     TypeString,
   890  								Optional: true,
   891  							},
   892  							"baz": {
   893  								Type:     TypeInt,
   894  								Optional: true,
   895  							},
   896  						},
   897  					},
   898  				},
   899  			},
   900  			State: &terraform.InstanceState{
   901  				Attributes: map[string]string{
   902  					"foo.#":     "1",
   903  					"foo.0.bar": "abc",
   904  				},
   905  			},
   906  			Config: testConfig(t, map[string]interface{}{
   907  				"foo": []interface{}{
   908  					map[string]interface{}{
   909  						"bar": "abcdefg",
   910  					},
   911  				},
   912  			}),
   913  			Diff: &terraform.InstanceDiff{
   914  				Attributes: map[string]*terraform.ResourceAttrDiff{
   915  					"foo.0.bar": {
   916  						Old: "abc",
   917  						New: "abcdefg",
   918  					},
   919  				},
   920  			},
   921  			Key: "foo.0.bar",
   922  			Expected: &terraform.InstanceDiff{
   923  				Attributes: map[string]*terraform.ResourceAttrDiff{
   924  					"foo.0.bar": {
   925  						Old:         "abc",
   926  						New:         "abcdefg",
   927  						RequiresNew: true,
   928  					},
   929  				},
   930  			},
   931  		},
   932  	}
   933  	for _, tc := range cases {
   934  		t.Run(tc.Name, func(t *testing.T) {
   935  			m := schemaMap(tc.Schema)
   936  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
   937  			err := d.ForceNew(tc.Key)
   938  			switch {
   939  			case err != nil && !tc.ExpectedError:
   940  				t.Fatalf("bad: %s", err)
   941  			case err == nil && tc.ExpectedError:
   942  				t.Fatalf("Expected error, got none")
   943  			case err != nil && tc.ExpectedError:
   944  				return
   945  			}
   946  			for _, k := range d.UpdatedKeys() {
   947  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
   948  					t.Fatalf("bad: %s", err)
   949  				}
   950  			}
   951  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
   952  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
   953  			}
   954  		})
   955  	}
   956  }
   957  
   958  func TestClear(t *testing.T) {
   959  	cases := []resourceDiffTestCase{
   960  		{
   961  			Name: "basic primitive diff",
   962  			Schema: map[string]*Schema{
   963  				"foo": {
   964  					Type:     TypeString,
   965  					Optional: true,
   966  					Computed: true,
   967  				},
   968  			},
   969  			State: &terraform.InstanceState{
   970  				Attributes: map[string]string{
   971  					"foo": "bar",
   972  				},
   973  			},
   974  			Config: testConfig(t, map[string]interface{}{
   975  				"foo": "baz",
   976  			}),
   977  			Diff: &terraform.InstanceDiff{
   978  				Attributes: map[string]*terraform.ResourceAttrDiff{
   979  					"foo": {
   980  						Old: "bar",
   981  						New: "baz",
   982  					},
   983  				},
   984  			},
   985  			Key:      "foo",
   986  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
   987  		},
   988  		{
   989  			Name: "non-computed key, should error",
   990  			Schema: map[string]*Schema{
   991  				"foo": {
   992  					Type:     TypeString,
   993  					Required: true,
   994  				},
   995  			},
   996  			State: &terraform.InstanceState{
   997  				Attributes: map[string]string{
   998  					"foo": "bar",
   999  				},
  1000  			},
  1001  			Config: testConfig(t, map[string]interface{}{
  1002  				"foo": "baz",
  1003  			}),
  1004  			Diff: &terraform.InstanceDiff{
  1005  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1006  					"foo": {
  1007  						Old: "bar",
  1008  						New: "baz",
  1009  					},
  1010  				},
  1011  			},
  1012  			Key:           "foo",
  1013  			ExpectedError: true,
  1014  		},
  1015  		{
  1016  			Name: "multi-value, one removed",
  1017  			Schema: map[string]*Schema{
  1018  				"foo": {
  1019  					Type:     TypeString,
  1020  					Optional: true,
  1021  					Computed: true,
  1022  				},
  1023  				"one": {
  1024  					Type:     TypeString,
  1025  					Optional: true,
  1026  					Computed: true,
  1027  				},
  1028  			},
  1029  			State: &terraform.InstanceState{
  1030  				Attributes: map[string]string{
  1031  					"foo": "bar",
  1032  					"one": "two",
  1033  				},
  1034  			},
  1035  			Config: testConfig(t, map[string]interface{}{
  1036  				"foo": "baz",
  1037  				"one": "three",
  1038  			}),
  1039  			Diff: &terraform.InstanceDiff{
  1040  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1041  					"foo": {
  1042  						Old: "bar",
  1043  						New: "baz",
  1044  					},
  1045  					"one": {
  1046  						Old: "two",
  1047  						New: "three",
  1048  					},
  1049  				},
  1050  			},
  1051  			Key: "one",
  1052  			Expected: &terraform.InstanceDiff{
  1053  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1054  					"foo": {
  1055  						Old: "bar",
  1056  						New: "baz",
  1057  					},
  1058  				},
  1059  			},
  1060  		},
  1061  		{
  1062  			Name: "basic sub-block diff",
  1063  			Schema: map[string]*Schema{
  1064  				"foo": {
  1065  					Type:     TypeList,
  1066  					Optional: true,
  1067  					Computed: true,
  1068  					Elem: &Resource{
  1069  						Schema: map[string]*Schema{
  1070  							"bar": {
  1071  								Type:     TypeString,
  1072  								Optional: true,
  1073  								Computed: true,
  1074  							},
  1075  							"baz": {
  1076  								Type:     TypeString,
  1077  								Optional: true,
  1078  								Computed: true,
  1079  							},
  1080  						},
  1081  					},
  1082  				},
  1083  			},
  1084  			State: &terraform.InstanceState{
  1085  				Attributes: map[string]string{
  1086  					"foo.0.bar": "bar1",
  1087  					"foo.0.baz": "baz1",
  1088  				},
  1089  			},
  1090  			Config: testConfig(t, map[string]interface{}{
  1091  				"foo": []interface{}{
  1092  					map[string]interface{}{
  1093  						"bar": "bar2",
  1094  						"baz": "baz1",
  1095  					},
  1096  				},
  1097  			}),
  1098  			Diff: &terraform.InstanceDiff{
  1099  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1100  					"foo.0.bar": {
  1101  						Old: "bar1",
  1102  						New: "bar2",
  1103  					},
  1104  				},
  1105  			},
  1106  			Key:      "foo.0.bar",
  1107  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{}},
  1108  		},
  1109  		{
  1110  			Name: "sub-block diff only partial clear",
  1111  			Schema: map[string]*Schema{
  1112  				"foo": {
  1113  					Type:     TypeList,
  1114  					Optional: true,
  1115  					Computed: true,
  1116  					Elem: &Resource{
  1117  						Schema: map[string]*Schema{
  1118  							"bar": {
  1119  								Type:     TypeString,
  1120  								Optional: true,
  1121  								Computed: true,
  1122  							},
  1123  							"baz": {
  1124  								Type:     TypeString,
  1125  								Optional: true,
  1126  								Computed: true,
  1127  							},
  1128  						},
  1129  					},
  1130  				},
  1131  			},
  1132  			State: &terraform.InstanceState{
  1133  				Attributes: map[string]string{
  1134  					"foo.0.bar": "bar1",
  1135  					"foo.0.baz": "baz1",
  1136  				},
  1137  			},
  1138  			Config: testConfig(t, map[string]interface{}{
  1139  				"foo": []interface{}{
  1140  					map[string]interface{}{
  1141  						"bar": "bar2",
  1142  						"baz": "baz2",
  1143  					},
  1144  				},
  1145  			}),
  1146  			Diff: &terraform.InstanceDiff{
  1147  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1148  					"foo.0.bar": {
  1149  						Old: "bar1",
  1150  						New: "bar2",
  1151  					},
  1152  					"foo.0.baz": {
  1153  						Old: "baz1",
  1154  						New: "baz2",
  1155  					},
  1156  				},
  1157  			},
  1158  			Key: "foo.0.bar",
  1159  			Expected: &terraform.InstanceDiff{Attributes: map[string]*terraform.ResourceAttrDiff{
  1160  				"foo.0.baz": {
  1161  					Old: "baz1",
  1162  					New: "baz2",
  1163  				},
  1164  			}},
  1165  		},
  1166  	}
  1167  	for _, tc := range cases {
  1168  		t.Run(tc.Name, func(t *testing.T) {
  1169  			m := schemaMap(tc.Schema)
  1170  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
  1171  			err := d.Clear(tc.Key)
  1172  			switch {
  1173  			case err != nil && !tc.ExpectedError:
  1174  				t.Fatalf("bad: %s", err)
  1175  			case err == nil && tc.ExpectedError:
  1176  				t.Fatalf("Expected error, got none")
  1177  			case err != nil && tc.ExpectedError:
  1178  				return
  1179  			}
  1180  			for _, k := range d.UpdatedKeys() {
  1181  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
  1182  					t.Fatalf("bad: %s", err)
  1183  				}
  1184  			}
  1185  			if !reflect.DeepEqual(tc.Expected, tc.Diff) {
  1186  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.Expected), spew.Sdump(tc.Diff))
  1187  			}
  1188  		})
  1189  	}
  1190  }
  1191  
  1192  func TestGetChangedKeysPrefix(t *testing.T) {
  1193  	cases := []resourceDiffTestCase{
  1194  		{
  1195  			Name: "basic primitive diff",
  1196  			Schema: map[string]*Schema{
  1197  				"foo": {
  1198  					Type:     TypeString,
  1199  					Optional: true,
  1200  					Computed: true,
  1201  				},
  1202  			},
  1203  			State: &terraform.InstanceState{
  1204  				Attributes: map[string]string{
  1205  					"foo": "bar",
  1206  				},
  1207  			},
  1208  			Config: testConfig(t, map[string]interface{}{
  1209  				"foo": "baz",
  1210  			}),
  1211  			Diff: &terraform.InstanceDiff{
  1212  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1213  					"foo": {
  1214  						Old: "bar",
  1215  						New: "baz",
  1216  					},
  1217  				},
  1218  			},
  1219  			Key: "foo",
  1220  			ExpectedKeys: []string{
  1221  				"foo",
  1222  			},
  1223  		},
  1224  		{
  1225  			Name: "nested field filtering",
  1226  			Schema: map[string]*Schema{
  1227  				"testfield": {
  1228  					Type:     TypeString,
  1229  					Required: true,
  1230  				},
  1231  				"foo": {
  1232  					Type:     TypeList,
  1233  					Required: true,
  1234  					MaxItems: 1,
  1235  					Elem: &Resource{
  1236  						Schema: map[string]*Schema{
  1237  							"bar": {
  1238  								Type:     TypeString,
  1239  								Optional: true,
  1240  							},
  1241  							"baz": {
  1242  								Type:     TypeString,
  1243  								Optional: true,
  1244  							},
  1245  						},
  1246  					},
  1247  				},
  1248  			},
  1249  			State: &terraform.InstanceState{
  1250  				Attributes: map[string]string{
  1251  					"testfield": "blablah",
  1252  					"foo.#":     "1",
  1253  					"foo.0.bar": "abc",
  1254  					"foo.0.baz": "xyz",
  1255  				},
  1256  			},
  1257  			Config: testConfig(t, map[string]interface{}{
  1258  				"testfield": "modified",
  1259  				"foo": []interface{}{
  1260  					map[string]interface{}{
  1261  						"bar": "abcdefg",
  1262  						"baz": "changed",
  1263  					},
  1264  				},
  1265  			}),
  1266  			Diff: &terraform.InstanceDiff{
  1267  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1268  					"testfield": {
  1269  						Old: "blablah",
  1270  						New: "modified",
  1271  					},
  1272  					"foo.0.bar": {
  1273  						Old: "abc",
  1274  						New: "abcdefg",
  1275  					},
  1276  					"foo.0.baz": {
  1277  						Old: "xyz",
  1278  						New: "changed",
  1279  					},
  1280  				},
  1281  			},
  1282  			Key: "foo",
  1283  			ExpectedKeys: []string{
  1284  				"foo.0.bar",
  1285  				"foo.0.baz",
  1286  			},
  1287  		},
  1288  	}
  1289  	for _, tc := range cases {
  1290  		t.Run(tc.Name, func(t *testing.T) {
  1291  			m := schemaMap(tc.Schema)
  1292  			d := newResourceDiff(m, tc.Config, tc.State, tc.Diff)
  1293  			keys := d.GetChangedKeysPrefix(tc.Key)
  1294  
  1295  			for _, k := range d.UpdatedKeys() {
  1296  				if err := m.diff(k, m[k], tc.Diff, d, false); err != nil {
  1297  					t.Fatalf("bad: %s", err)
  1298  				}
  1299  			}
  1300  
  1301  			sort.Strings(keys)
  1302  
  1303  			if !reflect.DeepEqual(tc.ExpectedKeys, keys) {
  1304  				t.Fatalf("Expected %s, got %s", spew.Sdump(tc.ExpectedKeys), spew.Sdump(keys))
  1305  			}
  1306  		})
  1307  	}
  1308  }
  1309  
  1310  func TestResourceDiffGetOkExists(t *testing.T) {
  1311  	cases := []struct {
  1312  		Name   string
  1313  		Schema map[string]*Schema
  1314  		State  *terraform.InstanceState
  1315  		Config *terraform.ResourceConfig
  1316  		Diff   *terraform.InstanceDiff
  1317  		Key    string
  1318  		Value  interface{}
  1319  		Ok     bool
  1320  	}{
  1321  		/*
  1322  		 * Primitives
  1323  		 */
  1324  		{
  1325  			Name: "string-literal-empty",
  1326  			Schema: map[string]*Schema{
  1327  				"availability_zone": {
  1328  					Type:     TypeString,
  1329  					Optional: true,
  1330  					Computed: true,
  1331  					ForceNew: true,
  1332  				},
  1333  			},
  1334  
  1335  			State:  nil,
  1336  			Config: nil,
  1337  
  1338  			Diff: &terraform.InstanceDiff{
  1339  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1340  					"availability_zone": {
  1341  						Old: "",
  1342  						New: "",
  1343  					},
  1344  				},
  1345  			},
  1346  
  1347  			Key:   "availability_zone",
  1348  			Value: "",
  1349  			Ok:    true,
  1350  		},
  1351  
  1352  		{
  1353  			Name: "string-computed-empty",
  1354  			Schema: map[string]*Schema{
  1355  				"availability_zone": {
  1356  					Type:     TypeString,
  1357  					Optional: true,
  1358  					Computed: true,
  1359  					ForceNew: true,
  1360  				},
  1361  			},
  1362  
  1363  			State:  nil,
  1364  			Config: nil,
  1365  
  1366  			Diff: &terraform.InstanceDiff{
  1367  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1368  					"availability_zone": {
  1369  						Old:         "",
  1370  						New:         "",
  1371  						NewComputed: true,
  1372  					},
  1373  				},
  1374  			},
  1375  
  1376  			Key:   "availability_zone",
  1377  			Value: "",
  1378  			Ok:    false,
  1379  		},
  1380  
  1381  		{
  1382  			Name: "string-optional-computed-nil-diff",
  1383  			Schema: map[string]*Schema{
  1384  				"availability_zone": {
  1385  					Type:     TypeString,
  1386  					Optional: true,
  1387  					Computed: true,
  1388  					ForceNew: true,
  1389  				},
  1390  			},
  1391  
  1392  			State:  nil,
  1393  			Config: nil,
  1394  
  1395  			Diff: nil,
  1396  
  1397  			Key:   "availability_zone",
  1398  			Value: "",
  1399  			Ok:    false,
  1400  		},
  1401  
  1402  		/*
  1403  		 * Lists
  1404  		 */
  1405  
  1406  		{
  1407  			Name: "list-optional",
  1408  			Schema: map[string]*Schema{
  1409  				"ports": {
  1410  					Type:     TypeList,
  1411  					Optional: true,
  1412  					Elem:     &Schema{Type: TypeInt},
  1413  				},
  1414  			},
  1415  
  1416  			State:  nil,
  1417  			Config: nil,
  1418  
  1419  			Diff: nil,
  1420  
  1421  			Key:   "ports",
  1422  			Value: []interface{}{},
  1423  			Ok:    false,
  1424  		},
  1425  
  1426  		/*
  1427  		 * Map
  1428  		 */
  1429  
  1430  		{
  1431  			Name: "map-optional",
  1432  			Schema: map[string]*Schema{
  1433  				"ports": {
  1434  					Type:     TypeMap,
  1435  					Optional: true,
  1436  				},
  1437  			},
  1438  
  1439  			State:  nil,
  1440  			Config: nil,
  1441  
  1442  			Diff: nil,
  1443  
  1444  			Key:   "ports",
  1445  			Value: map[string]interface{}{},
  1446  			Ok:    false,
  1447  		},
  1448  
  1449  		/*
  1450  		 * Set
  1451  		 */
  1452  
  1453  		{
  1454  			Name: "set-optional",
  1455  			Schema: map[string]*Schema{
  1456  				"ports": {
  1457  					Type:     TypeSet,
  1458  					Optional: true,
  1459  					Elem:     &Schema{Type: TypeInt},
  1460  					Set:      func(a interface{}) int { return a.(int) },
  1461  				},
  1462  			},
  1463  
  1464  			State:  nil,
  1465  			Config: nil,
  1466  
  1467  			Diff: nil,
  1468  
  1469  			Key:   "ports",
  1470  			Value: []interface{}{},
  1471  			Ok:    false,
  1472  		},
  1473  
  1474  		{
  1475  			Name: "set-optional-key",
  1476  			Schema: map[string]*Schema{
  1477  				"ports": {
  1478  					Type:     TypeSet,
  1479  					Optional: true,
  1480  					Elem:     &Schema{Type: TypeInt},
  1481  					Set:      func(a interface{}) int { return a.(int) },
  1482  				},
  1483  			},
  1484  
  1485  			State:  nil,
  1486  			Config: nil,
  1487  
  1488  			Diff: nil,
  1489  
  1490  			Key:   "ports.0",
  1491  			Value: 0,
  1492  			Ok:    false,
  1493  		},
  1494  
  1495  		{
  1496  			Name: "bool-literal-empty",
  1497  			Schema: map[string]*Schema{
  1498  				"availability_zone": {
  1499  					Type:     TypeBool,
  1500  					Optional: true,
  1501  					Computed: true,
  1502  					ForceNew: true,
  1503  				},
  1504  			},
  1505  
  1506  			State:  nil,
  1507  			Config: nil,
  1508  			Diff: &terraform.InstanceDiff{
  1509  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1510  					"availability_zone": {
  1511  						Old: "",
  1512  						New: "",
  1513  					},
  1514  				},
  1515  			},
  1516  
  1517  			Key:   "availability_zone",
  1518  			Value: false,
  1519  			Ok:    true,
  1520  		},
  1521  
  1522  		{
  1523  			Name: "bool-literal-set",
  1524  			Schema: map[string]*Schema{
  1525  				"availability_zone": {
  1526  					Type:     TypeBool,
  1527  					Optional: true,
  1528  					Computed: true,
  1529  					ForceNew: true,
  1530  				},
  1531  			},
  1532  
  1533  			State:  nil,
  1534  			Config: nil,
  1535  
  1536  			Diff: &terraform.InstanceDiff{
  1537  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1538  					"availability_zone": {
  1539  						New: "true",
  1540  					},
  1541  				},
  1542  			},
  1543  
  1544  			Key:   "availability_zone",
  1545  			Value: true,
  1546  			Ok:    true,
  1547  		},
  1548  		{
  1549  			Name: "value-in-config",
  1550  			Schema: map[string]*Schema{
  1551  				"availability_zone": {
  1552  					Type:     TypeString,
  1553  					Optional: true,
  1554  				},
  1555  			},
  1556  
  1557  			State: &terraform.InstanceState{
  1558  				Attributes: map[string]string{
  1559  					"availability_zone": "foo",
  1560  				},
  1561  			},
  1562  			Config: testConfig(t, map[string]interface{}{
  1563  				"availability_zone": "foo",
  1564  			}),
  1565  
  1566  			Diff: &terraform.InstanceDiff{
  1567  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1568  			},
  1569  
  1570  			Key:   "availability_zone",
  1571  			Value: "foo",
  1572  			Ok:    true,
  1573  		},
  1574  		{
  1575  			Name: "new-value-in-config",
  1576  			Schema: map[string]*Schema{
  1577  				"availability_zone": {
  1578  					Type:     TypeString,
  1579  					Optional: true,
  1580  				},
  1581  			},
  1582  
  1583  			State: nil,
  1584  			Config: testConfig(t, map[string]interface{}{
  1585  				"availability_zone": "foo",
  1586  			}),
  1587  
  1588  			Diff: &terraform.InstanceDiff{
  1589  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1590  					"availability_zone": {
  1591  						Old: "",
  1592  						New: "foo",
  1593  					},
  1594  				},
  1595  			},
  1596  
  1597  			Key:   "availability_zone",
  1598  			Value: "foo",
  1599  			Ok:    true,
  1600  		},
  1601  		{
  1602  			Name: "optional-computed-value-in-config",
  1603  			Schema: map[string]*Schema{
  1604  				"availability_zone": {
  1605  					Type:     TypeString,
  1606  					Optional: true,
  1607  					Computed: true,
  1608  				},
  1609  			},
  1610  
  1611  			State: &terraform.InstanceState{
  1612  				Attributes: map[string]string{
  1613  					"availability_zone": "foo",
  1614  				},
  1615  			},
  1616  			Config: testConfig(t, map[string]interface{}{
  1617  				"availability_zone": "bar",
  1618  			}),
  1619  
  1620  			Diff: &terraform.InstanceDiff{
  1621  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1622  					"availability_zone": {
  1623  						Old: "foo",
  1624  						New: "bar",
  1625  					},
  1626  				},
  1627  			},
  1628  
  1629  			Key:   "availability_zone",
  1630  			Value: "bar",
  1631  			Ok:    true,
  1632  		},
  1633  		{
  1634  			Name: "removed-value",
  1635  			Schema: map[string]*Schema{
  1636  				"availability_zone": {
  1637  					Type:     TypeString,
  1638  					Optional: true,
  1639  				},
  1640  			},
  1641  
  1642  			State: &terraform.InstanceState{
  1643  				Attributes: map[string]string{
  1644  					"availability_zone": "foo",
  1645  				},
  1646  			},
  1647  			Config: testConfig(t, map[string]interface{}{}),
  1648  
  1649  			Diff: &terraform.InstanceDiff{
  1650  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1651  					"availability_zone": {
  1652  						Old:        "foo",
  1653  						New:        "",
  1654  						NewRemoved: true,
  1655  					},
  1656  				},
  1657  			},
  1658  
  1659  			Key:   "availability_zone",
  1660  			Value: "",
  1661  			Ok:    true,
  1662  		},
  1663  	}
  1664  
  1665  	for i, tc := range cases {
  1666  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  1667  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  1668  
  1669  			v, ok := d.GetOkExists(tc.Key)
  1670  			if s, ok := v.(*Set); ok {
  1671  				v = s.List()
  1672  			}
  1673  
  1674  			if !reflect.DeepEqual(v, tc.Value) {
  1675  				t.Fatalf("Bad %s: \n%#v", tc.Name, v)
  1676  			}
  1677  			if ok != tc.Ok {
  1678  				t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Ok, ok)
  1679  			}
  1680  		})
  1681  	}
  1682  }
  1683  
  1684  func TestResourceDiffGetOkExistsSetNew(t *testing.T) {
  1685  	tc := struct {
  1686  		Schema map[string]*Schema
  1687  		State  *terraform.InstanceState
  1688  		Diff   *terraform.InstanceDiff
  1689  		Key    string
  1690  		Value  interface{}
  1691  		Ok     bool
  1692  	}{
  1693  		Schema: map[string]*Schema{
  1694  			"availability_zone": {
  1695  				Type:     TypeString,
  1696  				Optional: true,
  1697  				Computed: true,
  1698  			},
  1699  		},
  1700  
  1701  		State: nil,
  1702  
  1703  		Diff: &terraform.InstanceDiff{
  1704  			Attributes: map[string]*terraform.ResourceAttrDiff{},
  1705  		},
  1706  
  1707  		Key:   "availability_zone",
  1708  		Value: "foobar",
  1709  		Ok:    true,
  1710  	}
  1711  
  1712  	d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff)
  1713  	d.SetNew(tc.Key, tc.Value)
  1714  
  1715  	v, ok := d.GetOkExists(tc.Key)
  1716  	if s, ok := v.(*Set); ok {
  1717  		v = s.List()
  1718  	}
  1719  
  1720  	if !reflect.DeepEqual(v, tc.Value) {
  1721  		t.Fatalf("Bad: \n%#v", v)
  1722  	}
  1723  	if ok != tc.Ok {
  1724  		t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok)
  1725  	}
  1726  }
  1727  
  1728  func TestResourceDiffGetOkExistsSetNewComputed(t *testing.T) {
  1729  	tc := struct {
  1730  		Schema map[string]*Schema
  1731  		State  *terraform.InstanceState
  1732  		Diff   *terraform.InstanceDiff
  1733  		Key    string
  1734  		Value  interface{}
  1735  		Ok     bool
  1736  	}{
  1737  		Schema: map[string]*Schema{
  1738  			"availability_zone": {
  1739  				Type:     TypeString,
  1740  				Optional: true,
  1741  				Computed: true,
  1742  			},
  1743  		},
  1744  
  1745  		State: &terraform.InstanceState{
  1746  			Attributes: map[string]string{
  1747  				"availability_zone": "foo",
  1748  			},
  1749  		},
  1750  
  1751  		Diff: &terraform.InstanceDiff{
  1752  			Attributes: map[string]*terraform.ResourceAttrDiff{},
  1753  		},
  1754  
  1755  		Key:   "availability_zone",
  1756  		Value: "foobar",
  1757  		Ok:    false,
  1758  	}
  1759  
  1760  	d := newResourceDiff(tc.Schema, testConfig(t, map[string]interface{}{}), tc.State, tc.Diff)
  1761  	d.SetNewComputed(tc.Key)
  1762  
  1763  	_, ok := d.GetOkExists(tc.Key)
  1764  
  1765  	if ok != tc.Ok {
  1766  		t.Fatalf("expected ok: %t, got: %t", tc.Ok, ok)
  1767  	}
  1768  }
  1769  
  1770  func TestResourceDiffNewValueKnown(t *testing.T) {
  1771  	cases := []struct {
  1772  		Name     string
  1773  		Schema   map[string]*Schema
  1774  		State    *terraform.InstanceState
  1775  		Config   *terraform.ResourceConfig
  1776  		Diff     *terraform.InstanceDiff
  1777  		Key      string
  1778  		Expected bool
  1779  	}{
  1780  		{
  1781  			Name: "in config, no state",
  1782  			Schema: map[string]*Schema{
  1783  				"availability_zone": {
  1784  					Type:     TypeString,
  1785  					Optional: true,
  1786  				},
  1787  			},
  1788  			State: nil,
  1789  			Config: testConfig(t, map[string]interface{}{
  1790  				"availability_zone": "foo",
  1791  			}),
  1792  			Diff: &terraform.InstanceDiff{
  1793  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1794  					"availability_zone": {
  1795  						Old: "",
  1796  						New: "foo",
  1797  					},
  1798  				},
  1799  			},
  1800  			Key:      "availability_zone",
  1801  			Expected: true,
  1802  		},
  1803  		{
  1804  			Name: "in config, has state, no diff",
  1805  			Schema: map[string]*Schema{
  1806  				"availability_zone": {
  1807  					Type:     TypeString,
  1808  					Optional: true,
  1809  				},
  1810  			},
  1811  			State: &terraform.InstanceState{
  1812  				Attributes: map[string]string{
  1813  					"availability_zone": "foo",
  1814  				},
  1815  			},
  1816  			Config: testConfig(t, map[string]interface{}{
  1817  				"availability_zone": "foo",
  1818  			}),
  1819  			Diff: &terraform.InstanceDiff{
  1820  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1821  			},
  1822  			Key:      "availability_zone",
  1823  			Expected: true,
  1824  		},
  1825  		{
  1826  			Name: "computed attribute, in state, no diff",
  1827  			Schema: map[string]*Schema{
  1828  				"availability_zone": {
  1829  					Type:     TypeString,
  1830  					Computed: true,
  1831  				},
  1832  			},
  1833  			State: &terraform.InstanceState{
  1834  				Attributes: map[string]string{
  1835  					"availability_zone": "foo",
  1836  				},
  1837  			},
  1838  			Config: testConfig(t, map[string]interface{}{}),
  1839  			Diff: &terraform.InstanceDiff{
  1840  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1841  			},
  1842  			Key:      "availability_zone",
  1843  			Expected: true,
  1844  		},
  1845  		{
  1846  			Name: "optional and computed attribute, in state, no config",
  1847  			Schema: map[string]*Schema{
  1848  				"availability_zone": {
  1849  					Type:     TypeString,
  1850  					Optional: true,
  1851  					Computed: true,
  1852  				},
  1853  			},
  1854  			State: &terraform.InstanceState{
  1855  				Attributes: map[string]string{
  1856  					"availability_zone": "foo",
  1857  				},
  1858  			},
  1859  			Config: testConfig(t, map[string]interface{}{}),
  1860  			Diff: &terraform.InstanceDiff{
  1861  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1862  			},
  1863  			Key:      "availability_zone",
  1864  			Expected: true,
  1865  		},
  1866  		{
  1867  			Name: "optional and computed attribute, in state, with config",
  1868  			Schema: map[string]*Schema{
  1869  				"availability_zone": {
  1870  					Type:     TypeString,
  1871  					Optional: true,
  1872  					Computed: true,
  1873  				},
  1874  			},
  1875  			State: &terraform.InstanceState{
  1876  				Attributes: map[string]string{
  1877  					"availability_zone": "foo",
  1878  				},
  1879  			},
  1880  			Config: testConfig(t, map[string]interface{}{
  1881  				"availability_zone": "foo",
  1882  			}),
  1883  			Diff: &terraform.InstanceDiff{
  1884  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1885  			},
  1886  			Key:      "availability_zone",
  1887  			Expected: true,
  1888  		},
  1889  		{
  1890  			Name: "computed value, through config reader",
  1891  			Schema: map[string]*Schema{
  1892  				"availability_zone": {
  1893  					Type:     TypeString,
  1894  					Optional: true,
  1895  				},
  1896  			},
  1897  			State: &terraform.InstanceState{
  1898  				Attributes: map[string]string{
  1899  					"availability_zone": "foo",
  1900  				},
  1901  			},
  1902  			Config: testConfig(
  1903  				t,
  1904  				map[string]interface{}{
  1905  					"availability_zone": hcl2shim.UnknownVariableValue,
  1906  				},
  1907  			),
  1908  			Diff: &terraform.InstanceDiff{
  1909  				Attributes: map[string]*terraform.ResourceAttrDiff{},
  1910  			},
  1911  			Key:      "availability_zone",
  1912  			Expected: false,
  1913  		},
  1914  		{
  1915  			Name: "computed value, through diff reader",
  1916  			Schema: map[string]*Schema{
  1917  				"availability_zone": {
  1918  					Type:     TypeString,
  1919  					Optional: true,
  1920  				},
  1921  			},
  1922  			State: &terraform.InstanceState{
  1923  				Attributes: map[string]string{
  1924  					"availability_zone": "foo",
  1925  				},
  1926  			},
  1927  			Config: testConfig(
  1928  				t,
  1929  				map[string]interface{}{
  1930  					"availability_zone": hcl2shim.UnknownVariableValue,
  1931  				},
  1932  			),
  1933  			Diff: &terraform.InstanceDiff{
  1934  				Attributes: map[string]*terraform.ResourceAttrDiff{
  1935  					"availability_zone": {
  1936  						Old:         "foo",
  1937  						New:         "",
  1938  						NewComputed: true,
  1939  					},
  1940  				},
  1941  			},
  1942  			Key:      "availability_zone",
  1943  			Expected: false,
  1944  		},
  1945  	}
  1946  
  1947  	for i, tc := range cases {
  1948  		t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) {
  1949  			d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  1950  
  1951  			actual := d.NewValueKnown(tc.Key)
  1952  			if tc.Expected != actual {
  1953  				t.Fatalf("%s: expected ok: %t, got: %t", tc.Name, tc.Expected, actual)
  1954  			}
  1955  		})
  1956  	}
  1957  }
  1958  
  1959  func TestResourceDiffNewValueKnownSetNew(t *testing.T) {
  1960  	tc := struct {
  1961  		Schema   map[string]*Schema
  1962  		State    *terraform.InstanceState
  1963  		Config   *terraform.ResourceConfig
  1964  		Diff     *terraform.InstanceDiff
  1965  		Key      string
  1966  		Value    interface{}
  1967  		Expected bool
  1968  	}{
  1969  		Schema: map[string]*Schema{
  1970  			"availability_zone": {
  1971  				Type:     TypeString,
  1972  				Optional: true,
  1973  				Computed: true,
  1974  			},
  1975  		},
  1976  		State: &terraform.InstanceState{
  1977  			Attributes: map[string]string{
  1978  				"availability_zone": "foo",
  1979  			},
  1980  		},
  1981  		Config: testConfig(
  1982  			t,
  1983  			map[string]interface{}{
  1984  				"availability_zone": hcl2shim.UnknownVariableValue,
  1985  			},
  1986  		),
  1987  		Diff: &terraform.InstanceDiff{
  1988  			Attributes: map[string]*terraform.ResourceAttrDiff{
  1989  				"availability_zone": {
  1990  					Old:         "foo",
  1991  					New:         "",
  1992  					NewComputed: true,
  1993  				},
  1994  			},
  1995  		},
  1996  		Key:      "availability_zone",
  1997  		Value:    "bar",
  1998  		Expected: true,
  1999  	}
  2000  
  2001  	d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  2002  	d.SetNew(tc.Key, tc.Value)
  2003  
  2004  	actual := d.NewValueKnown(tc.Key)
  2005  	if tc.Expected != actual {
  2006  		t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual)
  2007  	}
  2008  }
  2009  
  2010  func TestResourceDiffNewValueKnownSetNewComputed(t *testing.T) {
  2011  	tc := struct {
  2012  		Schema   map[string]*Schema
  2013  		State    *terraform.InstanceState
  2014  		Config   *terraform.ResourceConfig
  2015  		Diff     *terraform.InstanceDiff
  2016  		Key      string
  2017  		Expected bool
  2018  	}{
  2019  		Schema: map[string]*Schema{
  2020  			"availability_zone": {
  2021  				Type:     TypeString,
  2022  				Computed: true,
  2023  			},
  2024  		},
  2025  		State: &terraform.InstanceState{
  2026  			Attributes: map[string]string{
  2027  				"availability_zone": "foo",
  2028  			},
  2029  		},
  2030  		Config: testConfig(t, map[string]interface{}{}),
  2031  		Diff: &terraform.InstanceDiff{
  2032  			Attributes: map[string]*terraform.ResourceAttrDiff{},
  2033  		},
  2034  		Key:      "availability_zone",
  2035  		Expected: false,
  2036  	}
  2037  
  2038  	d := newResourceDiff(tc.Schema, tc.Config, tc.State, tc.Diff)
  2039  	d.SetNewComputed(tc.Key)
  2040  
  2041  	actual := d.NewValueKnown(tc.Key)
  2042  	if tc.Expected != actual {
  2043  		t.Fatalf("expected ok: %t, got: %t", tc.Expected, actual)
  2044  	}
  2045  }