github.com/mehmetalisavas/terraform@v0.7.10/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 sets may not contain all fields in the original diff, and
   754  		// because multiple entries for the same set can compute to the same
   755  		// hash before the values are computed or interpolated, the overall
   756  		// count can change as well.
   757  		{
   758  			&InstanceDiff{
   759  				Attributes: map[string]*ResourceAttrDiff{
   760  					"foo.#": &ResourceAttrDiff{
   761  						Old: "0",
   762  						New: "1",
   763  					},
   764  					"foo.~35964334.bar": &ResourceAttrDiff{
   765  						Old: "",
   766  						New: "${var.foo}",
   767  					},
   768  				},
   769  			},
   770  			&InstanceDiff{
   771  				Attributes: map[string]*ResourceAttrDiff{
   772  					"foo.#": &ResourceAttrDiff{
   773  						Old: "0",
   774  						New: "2",
   775  					},
   776  					"foo.87654323.bar": &ResourceAttrDiff{
   777  						Old: "",
   778  						New: "12",
   779  					},
   780  					"foo.87654325.bar": &ResourceAttrDiff{
   781  						Old: "",
   782  						New: "12",
   783  					},
   784  					"foo.87654325.baz": &ResourceAttrDiff{
   785  						Old: "",
   786  						New: "12",
   787  					},
   788  				},
   789  			},
   790  			true,
   791  			"",
   792  		},
   793  
   794  		// Computed values in maps will fail the "Same" check as well
   795  		{
   796  			&InstanceDiff{
   797  				Attributes: map[string]*ResourceAttrDiff{
   798  					"foo.%": &ResourceAttrDiff{
   799  						Old:         "",
   800  						New:         "",
   801  						NewComputed: true,
   802  					},
   803  				},
   804  			},
   805  			&InstanceDiff{
   806  				Attributes: map[string]*ResourceAttrDiff{
   807  					"foo.%": &ResourceAttrDiff{
   808  						Old:         "0",
   809  						New:         "1",
   810  						NewComputed: false,
   811  					},
   812  					"foo.val": &ResourceAttrDiff{
   813  						Old: "",
   814  						New: "something",
   815  					},
   816  				},
   817  			},
   818  			true,
   819  			"",
   820  		},
   821  
   822  		// In a DESTROY/CREATE scenario, the plan diff will be run against the
   823  		// state of the old instance, while the apply diff will be run against an
   824  		// empty state (because the state is cleared when the destroy runs.)
   825  		// For complex attributes, this can result in keys that seem to disappear
   826  		// between the two diffs, when in reality everything is working just fine.
   827  		//
   828  		// Same() needs to take into account this scenario by analyzing NewRemoved
   829  		// and treating as "Same" a diff that does indeed have that key removed.
   830  		{
   831  			&InstanceDiff{
   832  				Attributes: map[string]*ResourceAttrDiff{
   833  					"somemap.oldkey": &ResourceAttrDiff{
   834  						Old:        "long ago",
   835  						New:        "",
   836  						NewRemoved: true,
   837  					},
   838  					"somemap.newkey": &ResourceAttrDiff{
   839  						Old: "",
   840  						New: "brave new world",
   841  					},
   842  				},
   843  			},
   844  			&InstanceDiff{
   845  				Attributes: map[string]*ResourceAttrDiff{
   846  					"somemap.newkey": &ResourceAttrDiff{
   847  						Old: "",
   848  						New: "brave new world",
   849  					},
   850  				},
   851  			},
   852  			true,
   853  			"",
   854  		},
   855  
   856  		// Another thing that can occur in DESTROY/CREATE scenarios is that list
   857  		// values that are going to zero have diffs that show up at plan time but
   858  		// are gone at apply time. The NewRemoved handling catches the fields and
   859  		// treats them as OK, but it also needs to treat the .# field itself as
   860  		// okay to be present in the old diff but not in the new one.
   861  		{
   862  			&InstanceDiff{
   863  				Attributes: map[string]*ResourceAttrDiff{
   864  					"reqnew": &ResourceAttrDiff{
   865  						Old:         "old",
   866  						New:         "new",
   867  						RequiresNew: true,
   868  					},
   869  					"somemap.#": &ResourceAttrDiff{
   870  						Old: "1",
   871  						New: "0",
   872  					},
   873  					"somemap.oldkey": &ResourceAttrDiff{
   874  						Old:        "long ago",
   875  						New:        "",
   876  						NewRemoved: true,
   877  					},
   878  				},
   879  			},
   880  			&InstanceDiff{
   881  				Attributes: map[string]*ResourceAttrDiff{
   882  					"reqnew": &ResourceAttrDiff{
   883  						Old:         "",
   884  						New:         "new",
   885  						RequiresNew: true,
   886  					},
   887  				},
   888  			},
   889  			true,
   890  			"",
   891  		},
   892  
   893  		{
   894  			&InstanceDiff{
   895  				Attributes: map[string]*ResourceAttrDiff{
   896  					"reqnew": &ResourceAttrDiff{
   897  						Old:         "old",
   898  						New:         "new",
   899  						RequiresNew: true,
   900  					},
   901  					"somemap.%": &ResourceAttrDiff{
   902  						Old: "1",
   903  						New: "0",
   904  					},
   905  					"somemap.oldkey": &ResourceAttrDiff{
   906  						Old:        "long ago",
   907  						New:        "",
   908  						NewRemoved: true,
   909  					},
   910  				},
   911  			},
   912  			&InstanceDiff{
   913  				Attributes: map[string]*ResourceAttrDiff{
   914  					"reqnew": &ResourceAttrDiff{
   915  						Old:         "",
   916  						New:         "new",
   917  						RequiresNew: true,
   918  					},
   919  				},
   920  			},
   921  			true,
   922  			"",
   923  		},
   924  	}
   925  
   926  	for i, tc := range cases {
   927  		same, reason := tc.One.Same(tc.Two)
   928  		if same != tc.Same {
   929  			t.Fatalf("%d: expected same: %t, got %t (%s)\n\n one: %#v\n\ntwo: %#v",
   930  				i, tc.Same, same, reason, tc.One, tc.Two)
   931  		}
   932  		if reason != tc.Reason {
   933  			t.Fatalf(
   934  				"%d: bad reason\n\nexpected: %#v\n\ngot: %#v", i, tc.Reason, reason)
   935  		}
   936  	}
   937  }
   938  
   939  const moduleDiffStrBasic = `
   940  CREATE: nodeA
   941    bar:       "foo" => "<computed>"
   942    foo:       "foo" => "bar"
   943    longfoo:   "foo" => "bar" (forces new resource)
   944    secretfoo: "<sensitive>" => "<sensitive>" (attribute changed)
   945  `