github.com/aspring/terraform@v0.8.2-0.20161216122603-6a8619a5db2e/terraform/diff_test.go (about)

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