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

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    11  )
    12  
    13  func TestDiffEmpty(t *testing.T) {
    14  	var diff *Diff
    15  	if !diff.Empty() {
    16  		t.Fatal("should be empty")
    17  	}
    18  
    19  	diff = new(Diff)
    20  	if !diff.Empty() {
    21  		t.Fatal("should be empty")
    22  	}
    23  
    24  	mod := diff.AddModule(addrs.RootModuleInstance)
    25  	mod.Resources["nodeA"] = &InstanceDiff{
    26  		Attributes: map[string]*ResourceAttrDiff{
    27  			"foo": {
    28  				Old: "foo",
    29  				New: "bar",
    30  			},
    31  		},
    32  	}
    33  
    34  	if diff.Empty() {
    35  		t.Fatal("should not be empty")
    36  	}
    37  }
    38  
    39  func TestDiffEmpty_taintedIsNotEmpty(t *testing.T) {
    40  	diff := new(Diff)
    41  
    42  	mod := diff.AddModule(addrs.RootModuleInstance)
    43  	mod.Resources["nodeA"] = &InstanceDiff{
    44  		DestroyTainted: true,
    45  	}
    46  
    47  	if diff.Empty() {
    48  		t.Fatal("should not be empty, since DestroyTainted was set")
    49  	}
    50  }
    51  
    52  func TestDiffEqual(t *testing.T) {
    53  	cases := map[string]struct {
    54  		D1, D2 *Diff
    55  		Equal  bool
    56  	}{
    57  		"nil": {
    58  			nil,
    59  			new(Diff),
    60  			false,
    61  		},
    62  
    63  		"empty": {
    64  			new(Diff),
    65  			new(Diff),
    66  			true,
    67  		},
    68  
    69  		"different module order": {
    70  			&Diff{
    71  				Modules: []*ModuleDiff{
    72  					{Path: []string{"root", "foo"}},
    73  					{Path: []string{"root", "bar"}},
    74  				},
    75  			},
    76  			&Diff{
    77  				Modules: []*ModuleDiff{
    78  					{Path: []string{"root", "bar"}},
    79  					{Path: []string{"root", "foo"}},
    80  				},
    81  			},
    82  			true,
    83  		},
    84  
    85  		"different module diff destroys": {
    86  			&Diff{
    87  				Modules: []*ModuleDiff{
    88  					{Path: []string{"root", "foo"}, Destroy: true},
    89  				},
    90  			},
    91  			&Diff{
    92  				Modules: []*ModuleDiff{
    93  					{Path: []string{"root", "foo"}, Destroy: false},
    94  				},
    95  			},
    96  			true,
    97  		},
    98  	}
    99  
   100  	for name, tc := range cases {
   101  		t.Run(name, func(t *testing.T) {
   102  			actual := tc.D1.Equal(tc.D2)
   103  			if actual != tc.Equal {
   104  				t.Fatalf("expected: %v\n\n%#v\n\n%#v", tc.Equal, tc.D1, tc.D2)
   105  			}
   106  		})
   107  	}
   108  }
   109  
   110  func TestDiffPrune(t *testing.T) {
   111  	cases := map[string]struct {
   112  		D1, D2 *Diff
   113  	}{
   114  		"nil": {
   115  			nil,
   116  			nil,
   117  		},
   118  
   119  		"empty": {
   120  			new(Diff),
   121  			new(Diff),
   122  		},
   123  
   124  		"empty module": {
   125  			&Diff{
   126  				Modules: []*ModuleDiff{
   127  					{Path: []string{"root", "foo"}},
   128  				},
   129  			},
   130  			&Diff{},
   131  		},
   132  
   133  		"destroy module": {
   134  			&Diff{
   135  				Modules: []*ModuleDiff{
   136  					{Path: []string{"root", "foo"}, Destroy: true},
   137  				},
   138  			},
   139  			&Diff{
   140  				Modules: []*ModuleDiff{
   141  					{Path: []string{"root", "foo"}, Destroy: true},
   142  				},
   143  			},
   144  		},
   145  	}
   146  
   147  	for name, tc := range cases {
   148  		t.Run(name, func(t *testing.T) {
   149  			tc.D1.Prune()
   150  			if !tc.D1.Equal(tc.D2) {
   151  				t.Fatalf("bad:\n\n%#v\n\n%#v", tc.D1, tc.D2)
   152  			}
   153  		})
   154  	}
   155  }
   156  
   157  func TestModuleDiff_ChangeType(t *testing.T) {
   158  	cases := []struct {
   159  		Diff   *ModuleDiff
   160  		Result DiffChangeType
   161  	}{
   162  		{
   163  			&ModuleDiff{},
   164  			DiffNone,
   165  		},
   166  		{
   167  			&ModuleDiff{
   168  				Resources: map[string]*InstanceDiff{
   169  					"foo": {Destroy: true},
   170  				},
   171  			},
   172  			DiffDestroy,
   173  		},
   174  		{
   175  			&ModuleDiff{
   176  				Resources: map[string]*InstanceDiff{
   177  					"foo": {
   178  						Attributes: map[string]*ResourceAttrDiff{
   179  							"foo": {
   180  								Old: "",
   181  								New: "bar",
   182  							},
   183  						},
   184  					},
   185  				},
   186  			},
   187  			DiffUpdate,
   188  		},
   189  		{
   190  			&ModuleDiff{
   191  				Resources: map[string]*InstanceDiff{
   192  					"foo": {
   193  						Attributes: map[string]*ResourceAttrDiff{
   194  							"foo": {
   195  								Old:         "",
   196  								New:         "bar",
   197  								RequiresNew: true,
   198  							},
   199  						},
   200  					},
   201  				},
   202  			},
   203  			DiffCreate,
   204  		},
   205  		{
   206  			&ModuleDiff{
   207  				Resources: map[string]*InstanceDiff{
   208  					"foo": {
   209  						Destroy: true,
   210  						Attributes: map[string]*ResourceAttrDiff{
   211  							"foo": {
   212  								Old:         "",
   213  								New:         "bar",
   214  								RequiresNew: true,
   215  							},
   216  						},
   217  					},
   218  				},
   219  			},
   220  			DiffUpdate,
   221  		},
   222  	}
   223  
   224  	for i, tc := range cases {
   225  		actual := tc.Diff.ChangeType()
   226  		if actual != tc.Result {
   227  			t.Fatalf("%d: %#v", i, actual)
   228  		}
   229  	}
   230  }
   231  
   232  func TestDiff_DeepCopy(t *testing.T) {
   233  	cases := map[string]*Diff{
   234  		"empty": {},
   235  
   236  		"basic diff": {
   237  			Modules: []*ModuleDiff{
   238  				{
   239  					Path: []string{"root"},
   240  					Resources: map[string]*InstanceDiff{
   241  						"aws_instance.foo": {
   242  							Attributes: map[string]*ResourceAttrDiff{
   243  								"num": {
   244  									Old: "0",
   245  									New: "2",
   246  								},
   247  							},
   248  						},
   249  					},
   250  				},
   251  			},
   252  		},
   253  	}
   254  
   255  	for name, tc := range cases {
   256  		t.Run(name, func(t *testing.T) {
   257  			dup := tc.DeepCopy()
   258  			if !reflect.DeepEqual(dup, tc) {
   259  				t.Fatalf("\n%#v\n\n%#v", dup, tc)
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  func TestModuleDiff_Empty(t *testing.T) {
   266  	diff := new(ModuleDiff)
   267  	if !diff.Empty() {
   268  		t.Fatal("should be empty")
   269  	}
   270  
   271  	diff.Resources = map[string]*InstanceDiff{
   272  		"nodeA": {},
   273  	}
   274  
   275  	if !diff.Empty() {
   276  		t.Fatal("should be empty")
   277  	}
   278  
   279  	diff.Resources["nodeA"].Attributes = map[string]*ResourceAttrDiff{
   280  		"foo": {
   281  			Old: "foo",
   282  			New: "bar",
   283  		},
   284  	}
   285  
   286  	if diff.Empty() {
   287  		t.Fatal("should not be empty")
   288  	}
   289  
   290  	diff.Resources["nodeA"].Attributes = nil
   291  	diff.Resources["nodeA"].Destroy = true
   292  
   293  	if diff.Empty() {
   294  		t.Fatal("should not be empty")
   295  	}
   296  }
   297  
   298  func TestModuleDiff_String(t *testing.T) {
   299  	diff := &ModuleDiff{
   300  		Resources: map[string]*InstanceDiff{
   301  			"nodeA": {
   302  				Attributes: map[string]*ResourceAttrDiff{
   303  					"foo": {
   304  						Old: "foo",
   305  						New: "bar",
   306  					},
   307  					"bar": {
   308  						Old:         "foo",
   309  						NewComputed: true,
   310  					},
   311  					"longfoo": {
   312  						Old:         "foo",
   313  						New:         "bar",
   314  						RequiresNew: true,
   315  					},
   316  					"secretfoo": {
   317  						Old:       "foo",
   318  						New:       "bar",
   319  						Sensitive: true,
   320  					},
   321  				},
   322  			},
   323  		},
   324  	}
   325  
   326  	actual := strings.TrimSpace(diff.String())
   327  	expected := strings.TrimSpace(moduleDiffStrBasic)
   328  	if actual != expected {
   329  		t.Fatalf("bad:\n%s", actual)
   330  	}
   331  }
   332  
   333  func TestInstanceDiff_ChangeType(t *testing.T) {
   334  	cases := []struct {
   335  		Diff   *InstanceDiff
   336  		Result DiffChangeType
   337  	}{
   338  		{
   339  			&InstanceDiff{},
   340  			DiffNone,
   341  		},
   342  		{
   343  			&InstanceDiff{Destroy: true},
   344  			DiffDestroy,
   345  		},
   346  		{
   347  			&InstanceDiff{
   348  				Attributes: map[string]*ResourceAttrDiff{
   349  					"foo": {
   350  						Old: "",
   351  						New: "bar",
   352  					},
   353  				},
   354  			},
   355  			DiffUpdate,
   356  		},
   357  		{
   358  			&InstanceDiff{
   359  				Attributes: map[string]*ResourceAttrDiff{
   360  					"foo": {
   361  						Old:         "",
   362  						New:         "bar",
   363  						RequiresNew: true,
   364  					},
   365  				},
   366  			},
   367  			DiffCreate,
   368  		},
   369  		{
   370  			&InstanceDiff{
   371  				Destroy: true,
   372  				Attributes: map[string]*ResourceAttrDiff{
   373  					"foo": {
   374  						Old:         "",
   375  						New:         "bar",
   376  						RequiresNew: true,
   377  					},
   378  				},
   379  			},
   380  			DiffDestroyCreate,
   381  		},
   382  		{
   383  			&InstanceDiff{
   384  				DestroyTainted: true,
   385  				Attributes: map[string]*ResourceAttrDiff{
   386  					"foo": {
   387  						Old:         "",
   388  						New:         "bar",
   389  						RequiresNew: true,
   390  					},
   391  				},
   392  			},
   393  			DiffDestroyCreate,
   394  		},
   395  	}
   396  
   397  	for i, tc := range cases {
   398  		actual := tc.Diff.ChangeType()
   399  		if actual != tc.Result {
   400  			t.Fatalf("%d: %#v", i, actual)
   401  		}
   402  	}
   403  }
   404  
   405  func TestInstanceDiff_Empty(t *testing.T) {
   406  	var rd *InstanceDiff
   407  
   408  	if !rd.Empty() {
   409  		t.Fatal("should be empty")
   410  	}
   411  
   412  	rd = new(InstanceDiff)
   413  
   414  	if !rd.Empty() {
   415  		t.Fatal("should be empty")
   416  	}
   417  
   418  	rd = &InstanceDiff{Destroy: true}
   419  
   420  	if rd.Empty() {
   421  		t.Fatal("should not be empty")
   422  	}
   423  
   424  	rd = &InstanceDiff{
   425  		Attributes: map[string]*ResourceAttrDiff{
   426  			"foo": {
   427  				New: "bar",
   428  			},
   429  		},
   430  	}
   431  
   432  	if rd.Empty() {
   433  		t.Fatal("should not be empty")
   434  	}
   435  }
   436  
   437  func TestModuleDiff_Instances(t *testing.T) {
   438  	yesDiff := &InstanceDiff{Destroy: true}
   439  	noDiff := &InstanceDiff{Destroy: true, DestroyTainted: true}
   440  
   441  	cases := []struct {
   442  		Diff   *ModuleDiff
   443  		Id     string
   444  		Result []*InstanceDiff
   445  	}{
   446  		{
   447  			&ModuleDiff{
   448  				Resources: map[string]*InstanceDiff{
   449  					"foo": yesDiff,
   450  					"bar": noDiff,
   451  				},
   452  			},
   453  			"foo",
   454  			[]*InstanceDiff{
   455  				yesDiff,
   456  			},
   457  		},
   458  
   459  		{
   460  			&ModuleDiff{
   461  				Resources: map[string]*InstanceDiff{
   462  					"foo":   yesDiff,
   463  					"foo.0": yesDiff,
   464  					"bar":   noDiff,
   465  				},
   466  			},
   467  			"foo",
   468  			[]*InstanceDiff{
   469  				yesDiff,
   470  				yesDiff,
   471  			},
   472  		},
   473  
   474  		{
   475  			&ModuleDiff{
   476  				Resources: map[string]*InstanceDiff{
   477  					"foo":     yesDiff,
   478  					"foo.0":   yesDiff,
   479  					"foo_bar": noDiff,
   480  					"bar":     noDiff,
   481  				},
   482  			},
   483  			"foo",
   484  			[]*InstanceDiff{
   485  				yesDiff,
   486  				yesDiff,
   487  			},
   488  		},
   489  	}
   490  
   491  	for i, tc := range cases {
   492  		actual := tc.Diff.Instances(tc.Id)
   493  		if !reflect.DeepEqual(actual, tc.Result) {
   494  			t.Fatalf("%d: %#v", i, actual)
   495  		}
   496  	}
   497  }
   498  
   499  func TestInstanceDiff_RequiresNew(t *testing.T) {
   500  	rd := &InstanceDiff{
   501  		Attributes: map[string]*ResourceAttrDiff{
   502  			"foo": {},
   503  		},
   504  	}
   505  
   506  	if rd.RequiresNew() {
   507  		t.Fatal("should not require new")
   508  	}
   509  
   510  	rd.Attributes["foo"].RequiresNew = true
   511  
   512  	if !rd.RequiresNew() {
   513  		t.Fatal("should require new")
   514  	}
   515  }
   516  
   517  func TestInstanceDiff_RequiresNew_nil(t *testing.T) {
   518  	var rd *InstanceDiff
   519  
   520  	if rd.RequiresNew() {
   521  		t.Fatal("should not require new")
   522  	}
   523  }
   524  
   525  func TestInstanceDiffSame(t *testing.T) {
   526  	cases := []struct {
   527  		One, Two *InstanceDiff
   528  		Same     bool
   529  		Reason   string
   530  	}{
   531  		{
   532  			&InstanceDiff{},
   533  			&InstanceDiff{},
   534  			true,
   535  			"",
   536  		},
   537  
   538  		{
   539  			nil,
   540  			nil,
   541  			true,
   542  			"",
   543  		},
   544  
   545  		{
   546  			&InstanceDiff{Destroy: false},
   547  			&InstanceDiff{Destroy: true},
   548  			false,
   549  			"diff: Destroy; old: false, new: true",
   550  		},
   551  
   552  		{
   553  			&InstanceDiff{Destroy: true},
   554  			&InstanceDiff{Destroy: true},
   555  			true,
   556  			"",
   557  		},
   558  
   559  		{
   560  			&InstanceDiff{
   561  				Attributes: map[string]*ResourceAttrDiff{
   562  					"foo": {},
   563  				},
   564  			},
   565  			&InstanceDiff{
   566  				Attributes: map[string]*ResourceAttrDiff{
   567  					"foo": {},
   568  				},
   569  			},
   570  			true,
   571  			"",
   572  		},
   573  
   574  		{
   575  			&InstanceDiff{
   576  				Attributes: map[string]*ResourceAttrDiff{
   577  					"bar": {},
   578  				},
   579  			},
   580  			&InstanceDiff{
   581  				Attributes: map[string]*ResourceAttrDiff{
   582  					"foo": {},
   583  				},
   584  			},
   585  			false,
   586  			"attribute mismatch: bar",
   587  		},
   588  
   589  		// Extra attributes
   590  		{
   591  			&InstanceDiff{
   592  				Attributes: map[string]*ResourceAttrDiff{
   593  					"foo": {},
   594  				},
   595  			},
   596  			&InstanceDiff{
   597  				Attributes: map[string]*ResourceAttrDiff{
   598  					"foo": {},
   599  					"bar": {},
   600  				},
   601  			},
   602  			false,
   603  			"extra attributes: bar",
   604  		},
   605  
   606  		{
   607  			&InstanceDiff{
   608  				Attributes: map[string]*ResourceAttrDiff{
   609  					"foo": {RequiresNew: true},
   610  				},
   611  			},
   612  			&InstanceDiff{
   613  				Attributes: map[string]*ResourceAttrDiff{
   614  					"foo": {RequiresNew: false},
   615  				},
   616  			},
   617  			false,
   618  			"diff RequiresNew; old: true, new: false",
   619  		},
   620  
   621  		// NewComputed on primitive
   622  		{
   623  			&InstanceDiff{
   624  				Attributes: map[string]*ResourceAttrDiff{
   625  					"foo": {
   626  						Old:         "",
   627  						New:         "${var.foo}",
   628  						NewComputed: true,
   629  					},
   630  				},
   631  			},
   632  			&InstanceDiff{
   633  				Attributes: map[string]*ResourceAttrDiff{
   634  					"foo": {
   635  						Old: "0",
   636  						New: "1",
   637  					},
   638  				},
   639  			},
   640  			true,
   641  			"",
   642  		},
   643  
   644  		// NewComputed on primitive, removed
   645  		{
   646  			&InstanceDiff{
   647  				Attributes: map[string]*ResourceAttrDiff{
   648  					"foo": {
   649  						Old:         "",
   650  						New:         "${var.foo}",
   651  						NewComputed: true,
   652  					},
   653  				},
   654  			},
   655  			&InstanceDiff{
   656  				Attributes: map[string]*ResourceAttrDiff{},
   657  			},
   658  			true,
   659  			"",
   660  		},
   661  
   662  		// NewComputed on set, removed
   663  		{
   664  			&InstanceDiff{
   665  				Attributes: map[string]*ResourceAttrDiff{
   666  					"foo.#": {
   667  						Old:         "",
   668  						New:         "",
   669  						NewComputed: true,
   670  					},
   671  				},
   672  			},
   673  			&InstanceDiff{
   674  				Attributes: map[string]*ResourceAttrDiff{
   675  					"foo.1": {
   676  						Old:        "foo",
   677  						New:        "",
   678  						NewRemoved: true,
   679  					},
   680  					"foo.2": {
   681  						Old: "",
   682  						New: "bar",
   683  					},
   684  				},
   685  			},
   686  			true,
   687  			"",
   688  		},
   689  
   690  		{
   691  			&InstanceDiff{
   692  				Attributes: map[string]*ResourceAttrDiff{
   693  					"foo.#": {NewComputed: true},
   694  				},
   695  			},
   696  			&InstanceDiff{
   697  				Attributes: map[string]*ResourceAttrDiff{
   698  					"foo.#": {
   699  						Old: "0",
   700  						New: "1",
   701  					},
   702  					"foo.0": {
   703  						Old: "",
   704  						New: "12",
   705  					},
   706  				},
   707  			},
   708  			true,
   709  			"",
   710  		},
   711  
   712  		{
   713  			&InstanceDiff{
   714  				Attributes: map[string]*ResourceAttrDiff{
   715  					"foo.#": {
   716  						Old: "0",
   717  						New: "1",
   718  					},
   719  					"foo.~35964334.bar": {
   720  						Old: "",
   721  						New: "${var.foo}",
   722  					},
   723  				},
   724  			},
   725  			&InstanceDiff{
   726  				Attributes: map[string]*ResourceAttrDiff{
   727  					"foo.#": {
   728  						Old: "0",
   729  						New: "1",
   730  					},
   731  					"foo.87654323.bar": {
   732  						Old: "",
   733  						New: "12",
   734  					},
   735  				},
   736  			},
   737  			true,
   738  			"",
   739  		},
   740  
   741  		{
   742  			&InstanceDiff{
   743  				Attributes: map[string]*ResourceAttrDiff{
   744  					"foo.#": {
   745  						Old:         "0",
   746  						NewComputed: true,
   747  					},
   748  				},
   749  			},
   750  			&InstanceDiff{
   751  				Attributes: map[string]*ResourceAttrDiff{},
   752  			},
   753  			true,
   754  			"",
   755  		},
   756  
   757  		// Computed can change RequiresNew by removal, and that's okay
   758  		{
   759  			&InstanceDiff{
   760  				Attributes: map[string]*ResourceAttrDiff{
   761  					"foo.#": {
   762  						Old:         "0",
   763  						NewComputed: true,
   764  						RequiresNew: true,
   765  					},
   766  				},
   767  			},
   768  			&InstanceDiff{
   769  				Attributes: map[string]*ResourceAttrDiff{},
   770  			},
   771  			true,
   772  			"",
   773  		},
   774  
   775  		// Computed can change Destroy by removal, and that's okay
   776  		{
   777  			&InstanceDiff{
   778  				Attributes: map[string]*ResourceAttrDiff{
   779  					"foo.#": {
   780  						Old:         "0",
   781  						NewComputed: true,
   782  						RequiresNew: true,
   783  					},
   784  				},
   785  
   786  				Destroy: true,
   787  			},
   788  			&InstanceDiff{
   789  				Attributes: map[string]*ResourceAttrDiff{},
   790  			},
   791  			true,
   792  			"",
   793  		},
   794  
   795  		// Computed can change Destroy by elements
   796  		{
   797  			&InstanceDiff{
   798  				Attributes: map[string]*ResourceAttrDiff{
   799  					"foo.#": {
   800  						Old:         "0",
   801  						NewComputed: true,
   802  						RequiresNew: true,
   803  					},
   804  				},
   805  
   806  				Destroy: true,
   807  			},
   808  			&InstanceDiff{
   809  				Attributes: map[string]*ResourceAttrDiff{
   810  					"foo.#": {
   811  						Old: "1",
   812  						New: "1",
   813  					},
   814  					"foo.12": {
   815  						Old:         "4",
   816  						New:         "12",
   817  						RequiresNew: true,
   818  					},
   819  				},
   820  
   821  				Destroy: true,
   822  			},
   823  			true,
   824  			"",
   825  		},
   826  
   827  		// Computed sets may not contain all fields in the original diff, and
   828  		// because multiple entries for the same set can compute to the same
   829  		// hash before the values are computed or interpolated, the overall
   830  		// count can change as well.
   831  		{
   832  			&InstanceDiff{
   833  				Attributes: map[string]*ResourceAttrDiff{
   834  					"foo.#": {
   835  						Old: "0",
   836  						New: "1",
   837  					},
   838  					"foo.~35964334.bar": {
   839  						Old: "",
   840  						New: "${var.foo}",
   841  					},
   842  				},
   843  			},
   844  			&InstanceDiff{
   845  				Attributes: map[string]*ResourceAttrDiff{
   846  					"foo.#": {
   847  						Old: "0",
   848  						New: "2",
   849  					},
   850  					"foo.87654323.bar": {
   851  						Old: "",
   852  						New: "12",
   853  					},
   854  					"foo.87654325.bar": {
   855  						Old: "",
   856  						New: "12",
   857  					},
   858  					"foo.87654325.baz": {
   859  						Old: "",
   860  						New: "12",
   861  					},
   862  				},
   863  			},
   864  			true,
   865  			"",
   866  		},
   867  
   868  		// Computed values in maps will fail the "Same" check as well
   869  		{
   870  			&InstanceDiff{
   871  				Attributes: map[string]*ResourceAttrDiff{
   872  					"foo.%": {
   873  						Old:         "",
   874  						New:         "",
   875  						NewComputed: true,
   876  					},
   877  				},
   878  			},
   879  			&InstanceDiff{
   880  				Attributes: map[string]*ResourceAttrDiff{
   881  					"foo.%": {
   882  						Old:         "0",
   883  						New:         "1",
   884  						NewComputed: false,
   885  					},
   886  					"foo.val": {
   887  						Old: "",
   888  						New: "something",
   889  					},
   890  				},
   891  			},
   892  			true,
   893  			"",
   894  		},
   895  
   896  		// In a DESTROY/CREATE scenario, the plan diff will be run against the
   897  		// state of the old instance, while the apply diff will be run against an
   898  		// empty state (because the state is cleared when the destroy runs.)
   899  		// For complex attributes, this can result in keys that seem to disappear
   900  		// between the two diffs, when in reality everything is working just fine.
   901  		//
   902  		// Same() needs to take into account this scenario by analyzing NewRemoved
   903  		// and treating as "Same" a diff that does indeed have that key removed.
   904  		{
   905  			&InstanceDiff{
   906  				Attributes: map[string]*ResourceAttrDiff{
   907  					"somemap.oldkey": {
   908  						Old:        "long ago",
   909  						New:        "",
   910  						NewRemoved: true,
   911  					},
   912  					"somemap.newkey": {
   913  						Old: "",
   914  						New: "brave new world",
   915  					},
   916  				},
   917  			},
   918  			&InstanceDiff{
   919  				Attributes: map[string]*ResourceAttrDiff{
   920  					"somemap.newkey": {
   921  						Old: "",
   922  						New: "brave new world",
   923  					},
   924  				},
   925  			},
   926  			true,
   927  			"",
   928  		},
   929  
   930  		// Another thing that can occur in DESTROY/CREATE scenarios is that list
   931  		// values that are going to zero have diffs that show up at plan time but
   932  		// are gone at apply time. The NewRemoved handling catches the fields and
   933  		// treats them as OK, but it also needs to treat the .# field itself as
   934  		// okay to be present in the old diff but not in the new one.
   935  		{
   936  			&InstanceDiff{
   937  				Attributes: map[string]*ResourceAttrDiff{
   938  					"reqnew": {
   939  						Old:         "old",
   940  						New:         "new",
   941  						RequiresNew: true,
   942  					},
   943  					"somemap.#": {
   944  						Old: "1",
   945  						New: "0",
   946  					},
   947  					"somemap.oldkey": {
   948  						Old:        "long ago",
   949  						New:        "",
   950  						NewRemoved: true,
   951  					},
   952  				},
   953  			},
   954  			&InstanceDiff{
   955  				Attributes: map[string]*ResourceAttrDiff{
   956  					"reqnew": {
   957  						Old:         "",
   958  						New:         "new",
   959  						RequiresNew: true,
   960  					},
   961  				},
   962  			},
   963  			true,
   964  			"",
   965  		},
   966  
   967  		{
   968  			&InstanceDiff{
   969  				Attributes: map[string]*ResourceAttrDiff{
   970  					"reqnew": {
   971  						Old:         "old",
   972  						New:         "new",
   973  						RequiresNew: true,
   974  					},
   975  					"somemap.%": {
   976  						Old: "1",
   977  						New: "0",
   978  					},
   979  					"somemap.oldkey": {
   980  						Old:        "long ago",
   981  						New:        "",
   982  						NewRemoved: true,
   983  					},
   984  				},
   985  			},
   986  			&InstanceDiff{
   987  				Attributes: map[string]*ResourceAttrDiff{
   988  					"reqnew": {
   989  						Old:         "",
   990  						New:         "new",
   991  						RequiresNew: true,
   992  					},
   993  				},
   994  			},
   995  			true,
   996  			"",
   997  		},
   998  
   999  		// Innner computed set should allow outer change in key
  1000  		{
  1001  			&InstanceDiff{
  1002  				Attributes: map[string]*ResourceAttrDiff{
  1003  					"foo.#": {
  1004  						Old: "0",
  1005  						New: "1",
  1006  					},
  1007  					"foo.~1.outer_val": {
  1008  						Old: "",
  1009  						New: "foo",
  1010  					},
  1011  					"foo.~1.inner.#": {
  1012  						Old: "0",
  1013  						New: "1",
  1014  					},
  1015  					"foo.~1.inner.~2.value": {
  1016  						Old:         "",
  1017  						New:         "${var.bar}",
  1018  						NewComputed: true,
  1019  					},
  1020  				},
  1021  			},
  1022  			&InstanceDiff{
  1023  				Attributes: map[string]*ResourceAttrDiff{
  1024  					"foo.#": {
  1025  						Old: "0",
  1026  						New: "1",
  1027  					},
  1028  					"foo.12.outer_val": {
  1029  						Old: "",
  1030  						New: "foo",
  1031  					},
  1032  					"foo.12.inner.#": {
  1033  						Old: "0",
  1034  						New: "1",
  1035  					},
  1036  					"foo.12.inner.42.value": {
  1037  						Old: "",
  1038  						New: "baz",
  1039  					},
  1040  				},
  1041  			},
  1042  			true,
  1043  			"",
  1044  		},
  1045  
  1046  		// Innner computed list should allow outer change in key
  1047  		{
  1048  			&InstanceDiff{
  1049  				Attributes: map[string]*ResourceAttrDiff{
  1050  					"foo.#": {
  1051  						Old: "0",
  1052  						New: "1",
  1053  					},
  1054  					"foo.~1.outer_val": {
  1055  						Old: "",
  1056  						New: "foo",
  1057  					},
  1058  					"foo.~1.inner.#": {
  1059  						Old: "0",
  1060  						New: "1",
  1061  					},
  1062  					"foo.~1.inner.0.value": {
  1063  						Old:         "",
  1064  						New:         "${var.bar}",
  1065  						NewComputed: true,
  1066  					},
  1067  				},
  1068  			},
  1069  			&InstanceDiff{
  1070  				Attributes: map[string]*ResourceAttrDiff{
  1071  					"foo.#": {
  1072  						Old: "0",
  1073  						New: "1",
  1074  					},
  1075  					"foo.12.outer_val": {
  1076  						Old: "",
  1077  						New: "foo",
  1078  					},
  1079  					"foo.12.inner.#": {
  1080  						Old: "0",
  1081  						New: "1",
  1082  					},
  1083  					"foo.12.inner.0.value": {
  1084  						Old: "",
  1085  						New: "baz",
  1086  					},
  1087  				},
  1088  			},
  1089  			true,
  1090  			"",
  1091  		},
  1092  
  1093  		// When removing all collection items, the diff is allowed to contain
  1094  		// nothing when re-creating the resource. This should be the "Same"
  1095  		// since we said we were going from 1 to 0.
  1096  		{
  1097  			&InstanceDiff{
  1098  				Attributes: map[string]*ResourceAttrDiff{
  1099  					"foo.%": {
  1100  						Old:         "1",
  1101  						New:         "0",
  1102  						RequiresNew: true,
  1103  					},
  1104  					"foo.bar": {
  1105  						Old:         "baz",
  1106  						New:         "",
  1107  						NewRemoved:  true,
  1108  						RequiresNew: true,
  1109  					},
  1110  				},
  1111  			},
  1112  			&InstanceDiff{},
  1113  			true,
  1114  			"",
  1115  		},
  1116  
  1117  		{
  1118  			&InstanceDiff{
  1119  				Attributes: map[string]*ResourceAttrDiff{
  1120  					"foo.#": {
  1121  						Old:         "1",
  1122  						New:         "0",
  1123  						RequiresNew: true,
  1124  					},
  1125  					"foo.0": {
  1126  						Old:         "baz",
  1127  						New:         "",
  1128  						NewRemoved:  true,
  1129  						RequiresNew: true,
  1130  					},
  1131  				},
  1132  			},
  1133  			&InstanceDiff{},
  1134  			true,
  1135  			"",
  1136  		},
  1137  
  1138  		// Make sure that DestroyTainted diffs pass as well, especially when diff
  1139  		// two works off of no state.
  1140  		{
  1141  			&InstanceDiff{
  1142  				DestroyTainted: true,
  1143  				Attributes: map[string]*ResourceAttrDiff{
  1144  					"foo": {
  1145  						Old: "foo",
  1146  						New: "foo",
  1147  					},
  1148  				},
  1149  			},
  1150  			&InstanceDiff{
  1151  				DestroyTainted: true,
  1152  				Attributes: map[string]*ResourceAttrDiff{
  1153  					"foo": {
  1154  						Old: "",
  1155  						New: "foo",
  1156  					},
  1157  				},
  1158  			},
  1159  			true,
  1160  			"",
  1161  		},
  1162  		// RequiresNew in different attribute
  1163  		{
  1164  			&InstanceDiff{
  1165  				Attributes: map[string]*ResourceAttrDiff{
  1166  					"foo": {
  1167  						Old: "foo",
  1168  						New: "foo",
  1169  					},
  1170  					"bar": {
  1171  						Old:         "bar",
  1172  						New:         "baz",
  1173  						RequiresNew: true,
  1174  					},
  1175  				},
  1176  			},
  1177  			&InstanceDiff{
  1178  				Attributes: map[string]*ResourceAttrDiff{
  1179  					"foo": {
  1180  						Old: "",
  1181  						New: "foo",
  1182  					},
  1183  					"bar": {
  1184  						Old:         "",
  1185  						New:         "baz",
  1186  						RequiresNew: true,
  1187  					},
  1188  				},
  1189  			},
  1190  			true,
  1191  			"",
  1192  		},
  1193  	}
  1194  
  1195  	for i, tc := range cases {
  1196  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
  1197  			same, reason := tc.One.Same(tc.Two)
  1198  			if same != tc.Same {
  1199  				t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v",
  1200  					i, tc.Same, same, reason, tc.One, tc.Two)
  1201  			}
  1202  			if reason != tc.Reason {
  1203  				t.Fatalf(
  1204  					"%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason)
  1205  			}
  1206  		})
  1207  	}
  1208  }
  1209  
  1210  const moduleDiffStrBasic = `
  1211  CREATE: nodeA
  1212    bar:       "foo" => "<computed>"
  1213    foo:       "foo" => "bar"
  1214    longfoo:   "foo" => "bar" (forces new resource)
  1215    secretfoo: "<sensitive>" => "<sensitive>" (attribute changed)
  1216  `
  1217  
  1218  func TestCountFlatmapContainerValues(t *testing.T) {
  1219  	for i, tc := range []struct {
  1220  		attrs map[string]string
  1221  		key   string
  1222  		count string
  1223  	}{
  1224  		{
  1225  			attrs: map[string]string{"set.2.list.#": "9999", "set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"},
  1226  			key:   "set.2.list.#",
  1227  			count: "1",
  1228  		},
  1229  		{
  1230  			attrs: map[string]string{"set.2.list.#": "9999", "set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"},
  1231  			key:   "set.#",
  1232  			count: "1",
  1233  		},
  1234  		{
  1235  			attrs: map[string]string{"set.2.list.0": "x", "set.2.list.0.z": "y", "set.2.attr": "bar", "set.#": "9999"},
  1236  			key:   "set.#",
  1237  			count: "1",
  1238  		},
  1239  		{
  1240  			attrs: map[string]string{"map.#": "3", "map.a": "b", "map.a.#": "0", "map.b": "4"},
  1241  			key:   "map.#",
  1242  			count: "2",
  1243  		},
  1244  	} {
  1245  		t.Run(strconv.Itoa(i), func(t *testing.T) {
  1246  			count := countFlatmapContainerValues(tc.key, tc.attrs)
  1247  			if count != tc.count {
  1248  				t.Fatalf("expected %q, got %q", tc.count, count)
  1249  			}
  1250  		})
  1251  	}
  1252  }