github.com/pulumi/terraform@v1.4.0/pkg/states/state_test.go (about)

     1  package states
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  
     8  	"github.com/go-test/deep"
     9  	"github.com/zclconf/go-cty/cty"
    10  
    11  	"github.com/pulumi/terraform/pkg/addrs"
    12  	"github.com/pulumi/terraform/pkg/lang/marks"
    13  )
    14  
    15  func TestState(t *testing.T) {
    16  	// This basic tests exercises the main mutation methods to construct
    17  	// a state. It is not fully comprehensive, so other tests should visit
    18  	// more esoteric codepaths.
    19  
    20  	state := NewState()
    21  
    22  	rootModule := state.RootModule()
    23  	if rootModule == nil {
    24  		t.Errorf("root module is nil; want valid object")
    25  	}
    26  
    27  	rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
    28  	rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
    29  	rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true)
    30  	rootModule.SetResourceInstanceCurrent(
    31  		addrs.Resource{
    32  			Mode: addrs.ManagedResourceMode,
    33  			Type: "test_thing",
    34  			Name: "baz",
    35  		}.Instance(addrs.IntKey(0)),
    36  		&ResourceInstanceObjectSrc{
    37  			Status:        ObjectReady,
    38  			SchemaVersion: 1,
    39  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
    40  		},
    41  		addrs.AbsProviderConfig{
    42  			Provider: addrs.NewDefaultProvider("test"),
    43  			Module:   addrs.RootModule,
    44  		},
    45  	)
    46  
    47  	childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
    48  	childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false)
    49  	multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")))
    50  	multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false)
    51  	multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")))
    52  	multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false)
    53  
    54  	want := &State{
    55  		Modules: map[string]*Module{
    56  			"": {
    57  				Addr: addrs.RootModuleInstance,
    58  				LocalValues: map[string]cty.Value{
    59  					"foo": cty.StringVal("foo value"),
    60  				},
    61  				OutputValues: map[string]*OutputValue{
    62  					"bar": {
    63  						Addr: addrs.AbsOutputValue{
    64  							OutputValue: addrs.OutputValue{
    65  								Name: "bar",
    66  							},
    67  						},
    68  						Value:     cty.StringVal("bar value"),
    69  						Sensitive: false,
    70  					},
    71  					"secret": {
    72  						Addr: addrs.AbsOutputValue{
    73  							OutputValue: addrs.OutputValue{
    74  								Name: "secret",
    75  							},
    76  						},
    77  						Value:     cty.StringVal("secret value"),
    78  						Sensitive: true,
    79  					},
    80  				},
    81  				Resources: map[string]*Resource{
    82  					"test_thing.baz": {
    83  						Addr: addrs.Resource{
    84  							Mode: addrs.ManagedResourceMode,
    85  							Type: "test_thing",
    86  							Name: "baz",
    87  						}.Absolute(addrs.RootModuleInstance),
    88  
    89  						Instances: map[addrs.InstanceKey]*ResourceInstance{
    90  							addrs.IntKey(0): {
    91  								Current: &ResourceInstanceObjectSrc{
    92  									SchemaVersion: 1,
    93  									Status:        ObjectReady,
    94  									AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
    95  								},
    96  								Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{},
    97  							},
    98  						},
    99  						ProviderConfig: addrs.AbsProviderConfig{
   100  							Provider: addrs.NewDefaultProvider("test"),
   101  							Module:   addrs.RootModule,
   102  						},
   103  					},
   104  				},
   105  			},
   106  			"module.child": {
   107  				Addr:        addrs.RootModuleInstance.Child("child", addrs.NoKey),
   108  				LocalValues: map[string]cty.Value{},
   109  				OutputValues: map[string]*OutputValue{
   110  					"pizza": {
   111  						Addr: addrs.AbsOutputValue{
   112  							Module: addrs.RootModuleInstance.Child("child", addrs.NoKey),
   113  							OutputValue: addrs.OutputValue{
   114  								Name: "pizza",
   115  							},
   116  						},
   117  						Value:     cty.StringVal("hawaiian"),
   118  						Sensitive: false,
   119  					},
   120  				},
   121  				Resources: map[string]*Resource{},
   122  			},
   123  			`module.multi["a"]`: {
   124  				Addr:        addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")),
   125  				LocalValues: map[string]cty.Value{},
   126  				OutputValues: map[string]*OutputValue{
   127  					"pizza": {
   128  						Addr: addrs.AbsOutputValue{
   129  							Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")),
   130  							OutputValue: addrs.OutputValue{
   131  								Name: "pizza",
   132  							},
   133  						},
   134  						Value:     cty.StringVal("cheese"),
   135  						Sensitive: false,
   136  					},
   137  				},
   138  				Resources: map[string]*Resource{},
   139  			},
   140  			`module.multi["b"]`: {
   141  				Addr:        addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")),
   142  				LocalValues: map[string]cty.Value{},
   143  				OutputValues: map[string]*OutputValue{
   144  					"pizza": {
   145  						Addr: addrs.AbsOutputValue{
   146  							Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")),
   147  							OutputValue: addrs.OutputValue{
   148  								Name: "pizza",
   149  							},
   150  						},
   151  						Value:     cty.StringVal("sausage"),
   152  						Sensitive: false,
   153  					},
   154  				},
   155  				Resources: map[string]*Resource{},
   156  			},
   157  		},
   158  	}
   159  
   160  	{
   161  		// Our structure goes deep, so we need to temporarily override the
   162  		// deep package settings to ensure that we visit the full structure.
   163  		oldDeepDepth := deep.MaxDepth
   164  		oldDeepCompareUnexp := deep.CompareUnexportedFields
   165  		deep.MaxDepth = 50
   166  		deep.CompareUnexportedFields = true
   167  		defer func() {
   168  			deep.MaxDepth = oldDeepDepth
   169  			deep.CompareUnexportedFields = oldDeepCompareUnexp
   170  		}()
   171  	}
   172  
   173  	for _, problem := range deep.Equal(state, want) {
   174  		t.Error(problem)
   175  	}
   176  
   177  	expectedOutputs := map[string]string{
   178  		`module.multi["a"].output.pizza`: "cheese",
   179  		`module.multi["b"].output.pizza`: "sausage",
   180  	}
   181  
   182  	for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) {
   183  		addr := o.Addr.String()
   184  		expected := expectedOutputs[addr]
   185  		delete(expectedOutputs, addr)
   186  
   187  		if expected != o.Value.AsString() {
   188  			t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString())
   189  		}
   190  	}
   191  
   192  	for addr, o := range expectedOutputs {
   193  		t.Fatalf("missing output %q:%q", addr, o)
   194  	}
   195  }
   196  
   197  func TestStateDeepCopyObject(t *testing.T) {
   198  	obj := &ResourceInstanceObject{
   199  		Value: cty.ObjectVal(map[string]cty.Value{
   200  			"id": cty.StringVal("id"),
   201  		}),
   202  		Private: []byte("private"),
   203  		Status:  ObjectReady,
   204  		Dependencies: []addrs.ConfigResource{
   205  			{
   206  				Module: addrs.RootModule,
   207  				Resource: addrs.Resource{
   208  					Mode: addrs.ManagedResourceMode,
   209  					Type: "test_instance",
   210  					Name: "bar",
   211  				},
   212  			},
   213  		},
   214  		CreateBeforeDestroy: true,
   215  	}
   216  
   217  	objCopy := obj.DeepCopy()
   218  	if !reflect.DeepEqual(obj, objCopy) {
   219  		t.Fatalf("not equal\n%#v\n%#v", obj, objCopy)
   220  	}
   221  }
   222  
   223  func TestStateDeepCopy(t *testing.T) {
   224  	state := NewState()
   225  
   226  	rootModule := state.RootModule()
   227  	if rootModule == nil {
   228  		t.Errorf("root module is nil; want valid object")
   229  	}
   230  
   231  	rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
   232  	rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
   233  	rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true)
   234  	rootModule.SetResourceInstanceCurrent(
   235  		addrs.Resource{
   236  			Mode: addrs.ManagedResourceMode,
   237  			Type: "test_thing",
   238  			Name: "baz",
   239  		}.Instance(addrs.IntKey(0)),
   240  		&ResourceInstanceObjectSrc{
   241  			Status:              ObjectReady,
   242  			SchemaVersion:       1,
   243  			AttrsJSON:           []byte(`{"woozles":"confuzles"}`),
   244  			Private:             []byte("private data"),
   245  			Dependencies:        []addrs.ConfigResource{},
   246  			CreateBeforeDestroy: true,
   247  		},
   248  		addrs.AbsProviderConfig{
   249  			Provider: addrs.NewDefaultProvider("test"),
   250  			Module:   addrs.RootModule,
   251  		},
   252  	)
   253  	rootModule.SetResourceInstanceCurrent(
   254  		addrs.Resource{
   255  			Mode: addrs.ManagedResourceMode,
   256  			Type: "test_thing",
   257  			Name: "bar",
   258  		}.Instance(addrs.IntKey(0)),
   259  		&ResourceInstanceObjectSrc{
   260  			Status:        ObjectReady,
   261  			SchemaVersion: 1,
   262  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   263  			// Sensitive path at "woozles"
   264  			AttrSensitivePaths: []cty.PathValueMarks{
   265  				{
   266  					Path:  cty.Path{cty.GetAttrStep{Name: "woozles"}},
   267  					Marks: cty.NewValueMarks(marks.Sensitive),
   268  				},
   269  			},
   270  			Private: []byte("private data"),
   271  			Dependencies: []addrs.ConfigResource{
   272  				{
   273  					Module: addrs.RootModule,
   274  					Resource: addrs.Resource{
   275  						Mode: addrs.ManagedResourceMode,
   276  						Type: "test_thing",
   277  						Name: "baz",
   278  					},
   279  				},
   280  			},
   281  		},
   282  		addrs.AbsProviderConfig{
   283  			Provider: addrs.NewDefaultProvider("test"),
   284  			Module:   addrs.RootModule,
   285  		},
   286  	)
   287  
   288  	childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   289  	childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false)
   290  
   291  	stateCopy := state.DeepCopy()
   292  	if !state.Equal(stateCopy) {
   293  		t.Fatalf("\nexpected:\n%q\ngot:\n%q\n", state, stateCopy)
   294  	}
   295  }
   296  
   297  func TestStateHasResourceInstanceObjects(t *testing.T) {
   298  	providerConfig := addrs.AbsProviderConfig{
   299  		Module:   addrs.RootModule,
   300  		Provider: addrs.MustParseProviderSourceString("test/test"),
   301  	}
   302  	childModuleProviderConfig := addrs.AbsProviderConfig{
   303  		Module:   addrs.RootModule.Child("child"),
   304  		Provider: addrs.MustParseProviderSourceString("test/test"),
   305  	}
   306  
   307  	tests := map[string]struct {
   308  		Setup func(ss *SyncState)
   309  		Want  bool
   310  	}{
   311  		"empty": {
   312  			func(ss *SyncState) {},
   313  			false,
   314  		},
   315  		"one current, ready object in root module": {
   316  			func(ss *SyncState) {
   317  				ss.SetResourceInstanceCurrent(
   318  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   319  					&ResourceInstanceObjectSrc{
   320  						AttrsJSON: []byte(`{}`),
   321  						Status:    ObjectReady,
   322  					},
   323  					providerConfig,
   324  				)
   325  			},
   326  			true,
   327  		},
   328  		"one current, ready object in child module": {
   329  			func(ss *SyncState) {
   330  				ss.SetResourceInstanceCurrent(
   331  					mustAbsResourceAddr("module.child.test.foo").Instance(addrs.NoKey),
   332  					&ResourceInstanceObjectSrc{
   333  						AttrsJSON: []byte(`{}`),
   334  						Status:    ObjectReady,
   335  					},
   336  					childModuleProviderConfig,
   337  				)
   338  			},
   339  			true,
   340  		},
   341  		"one current, tainted object in root module": {
   342  			func(ss *SyncState) {
   343  				ss.SetResourceInstanceCurrent(
   344  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   345  					&ResourceInstanceObjectSrc{
   346  						AttrsJSON: []byte(`{}`),
   347  						Status:    ObjectTainted,
   348  					},
   349  					providerConfig,
   350  				)
   351  			},
   352  			true,
   353  		},
   354  		"one deposed, ready object in root module": {
   355  			func(ss *SyncState) {
   356  				ss.SetResourceInstanceDeposed(
   357  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   358  					DeposedKey("uhoh"),
   359  					&ResourceInstanceObjectSrc{
   360  						AttrsJSON: []byte(`{}`),
   361  						Status:    ObjectTainted,
   362  					},
   363  					providerConfig,
   364  				)
   365  			},
   366  			true,
   367  		},
   368  		"one empty resource husk in root module": {
   369  			func(ss *SyncState) {
   370  				// Current Terraform doesn't actually create resource husks
   371  				// as part of its everyday work, so this is a "should never
   372  				// happen" case but we'll test to make sure we're robust to
   373  				// it anyway, because this was a historical bug blocking
   374  				// "terraform workspace delete" and similar.
   375  				ss.SetResourceInstanceCurrent(
   376  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   377  					&ResourceInstanceObjectSrc{
   378  						AttrsJSON: []byte(`{}`),
   379  						Status:    ObjectTainted,
   380  					},
   381  					providerConfig,
   382  				)
   383  				s := ss.Lock()
   384  				delete(s.Modules[""].Resources["test.foo"].Instances, addrs.NoKey)
   385  				ss.Unlock()
   386  			},
   387  			false,
   388  		},
   389  		"one current data resource object in root module": {
   390  			func(ss *SyncState) {
   391  				ss.SetResourceInstanceCurrent(
   392  					mustAbsResourceAddr("data.test.foo").Instance(addrs.NoKey),
   393  					&ResourceInstanceObjectSrc{
   394  						AttrsJSON: []byte(`{}`),
   395  						Status:    ObjectReady,
   396  					},
   397  					providerConfig,
   398  				)
   399  			},
   400  			false, // data resources aren't managed resources, so they don't count
   401  		},
   402  	}
   403  
   404  	for name, test := range tests {
   405  		t.Run(name, func(t *testing.T) {
   406  			state := BuildState(test.Setup)
   407  			got := state.HasManagedResourceInstanceObjects()
   408  			if got != test.Want {
   409  				t.Errorf("wrong result\nstate content: (using legacy state string format; might not be comprehensive)\n%s\n\ngot:  %t\nwant: %t", state, got, test.Want)
   410  			}
   411  		})
   412  	}
   413  
   414  }
   415  
   416  func TestState_MoveAbsResource(t *testing.T) {
   417  	// Set up a starter state for the embedded tests, which should start from a copy of this state.
   418  	state := NewState()
   419  	rootModule := state.RootModule()
   420  	rootModule.SetResourceInstanceCurrent(
   421  		addrs.Resource{
   422  			Mode: addrs.ManagedResourceMode,
   423  			Type: "test_thing",
   424  			Name: "foo",
   425  		}.Instance(addrs.IntKey(0)),
   426  		&ResourceInstanceObjectSrc{
   427  			Status:        ObjectReady,
   428  			SchemaVersion: 1,
   429  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   430  		},
   431  		addrs.AbsProviderConfig{
   432  			Provider: addrs.NewDefaultProvider("test"),
   433  			Module:   addrs.RootModule,
   434  		},
   435  	)
   436  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance)
   437  
   438  	t.Run("basic move", func(t *testing.T) {
   439  		s := state.DeepCopy()
   440  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance)
   441  
   442  		s.MoveAbsResource(src, dst)
   443  
   444  		if s.Empty() {
   445  			t.Fatal("unexpected empty state")
   446  		}
   447  
   448  		if len(s.RootModule().Resources) != 1 {
   449  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources))
   450  		}
   451  
   452  		got := s.Resource(dst)
   453  		if got.Addr.Resource != dst.Resource {
   454  			t.Fatalf("dst resource not in state")
   455  		}
   456  	})
   457  
   458  	t.Run("move to new module", func(t *testing.T) {
   459  		s := state.DeepCopy()
   460  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("one"))
   461  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(dstModule)
   462  
   463  		s.MoveAbsResource(src, dst)
   464  
   465  		if s.Empty() {
   466  			t.Fatal("unexpected empty state")
   467  		}
   468  
   469  		if s.Module(dstModule) == nil {
   470  			t.Fatalf("child module %s not in state", dstModule.String())
   471  		}
   472  
   473  		if len(s.Module(dstModule).Resources) != 1 {
   474  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources))
   475  		}
   476  
   477  		got := s.Resource(dst)
   478  		if got.Addr.Resource != dst.Resource {
   479  			t.Fatalf("dst resource not in state")
   480  		}
   481  	})
   482  
   483  	t.Run("from a child module to root", func(t *testing.T) {
   484  		s := state.DeepCopy()
   485  		srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey)
   486  		cm := s.EnsureModule(srcModule)
   487  		cm.SetResourceInstanceCurrent(
   488  			addrs.Resource{
   489  				Mode: addrs.ManagedResourceMode,
   490  				Type: "test_thing",
   491  				Name: "child",
   492  			}.Instance(addrs.IntKey(0)), // Moving the AbsResouce moves all instances
   493  			&ResourceInstanceObjectSrc{
   494  				Status:        ObjectReady,
   495  				SchemaVersion: 1,
   496  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   497  			},
   498  			addrs.AbsProviderConfig{
   499  				Provider: addrs.NewDefaultProvider("test"),
   500  				Module:   addrs.RootModule,
   501  			},
   502  		)
   503  		cm.SetResourceInstanceCurrent(
   504  			addrs.Resource{
   505  				Mode: addrs.ManagedResourceMode,
   506  				Type: "test_thing",
   507  				Name: "child",
   508  			}.Instance(addrs.IntKey(1)), // Moving the AbsResouce moves all instances
   509  			&ResourceInstanceObjectSrc{
   510  				Status:        ObjectReady,
   511  				SchemaVersion: 1,
   512  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   513  			},
   514  			addrs.AbsProviderConfig{
   515  				Provider: addrs.NewDefaultProvider("test"),
   516  				Module:   addrs.RootModule,
   517  			},
   518  		)
   519  
   520  		src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule)
   521  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(addrs.RootModuleInstance)
   522  		s.MoveAbsResource(src, dst)
   523  
   524  		if s.Empty() {
   525  			t.Fatal("unexpected empty state")
   526  		}
   527  
   528  		// The child module should have been removed after removing its only resource
   529  		if s.Module(srcModule) != nil {
   530  			t.Fatalf("child module %s was not removed from state after mv", srcModule.String())
   531  		}
   532  
   533  		if len(s.RootModule().Resources) != 2 {
   534  			t.Fatalf("wrong number of resources in state; expected 2, found %d", len(s.RootModule().Resources))
   535  		}
   536  
   537  		if len(s.Resource(dst).Instances) != 2 {
   538  			t.Fatalf("wrong number of resource instances for dst, got %d expected 2", len(s.Resource(dst).Instances))
   539  		}
   540  
   541  		got := s.Resource(dst)
   542  		if got.Addr.Resource != dst.Resource {
   543  			t.Fatalf("dst resource not in state")
   544  		}
   545  	})
   546  
   547  	t.Run("module to new module", func(t *testing.T) {
   548  		s := NewState()
   549  		srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists"))
   550  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new"))
   551  		cm := s.EnsureModule(srcModule)
   552  		cm.SetResourceInstanceCurrent(
   553  			addrs.Resource{
   554  				Mode: addrs.ManagedResourceMode,
   555  				Type: "test_thing",
   556  				Name: "child",
   557  			}.Instance(addrs.NoKey),
   558  			&ResourceInstanceObjectSrc{
   559  				Status:        ObjectReady,
   560  				SchemaVersion: 1,
   561  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   562  			},
   563  			addrs.AbsProviderConfig{
   564  				Provider: addrs.NewDefaultProvider("test"),
   565  				Module:   addrs.RootModule,
   566  			},
   567  		)
   568  
   569  		src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule)
   570  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule)
   571  		s.MoveAbsResource(src, dst)
   572  
   573  		if s.Empty() {
   574  			t.Fatal("unexpected empty state")
   575  		}
   576  
   577  		// The child module should have been removed after removing its only resource
   578  		if s.Module(srcModule) != nil {
   579  			t.Fatalf("child module %s was not removed from state after mv", srcModule.String())
   580  		}
   581  
   582  		gotMod := s.Module(dstModule)
   583  		if len(gotMod.Resources) != 1 {
   584  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources))
   585  		}
   586  
   587  		got := s.Resource(dst)
   588  		if got.Addr.Resource != dst.Resource {
   589  			t.Fatalf("dst resource not in state")
   590  		}
   591  	})
   592  
   593  	t.Run("module to new module", func(t *testing.T) {
   594  		s := NewState()
   595  		srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists"))
   596  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new"))
   597  		cm := s.EnsureModule(srcModule)
   598  		cm.SetResourceInstanceCurrent(
   599  			addrs.Resource{
   600  				Mode: addrs.ManagedResourceMode,
   601  				Type: "test_thing",
   602  				Name: "child",
   603  			}.Instance(addrs.NoKey),
   604  			&ResourceInstanceObjectSrc{
   605  				Status:        ObjectReady,
   606  				SchemaVersion: 1,
   607  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   608  			},
   609  			addrs.AbsProviderConfig{
   610  				Provider: addrs.NewDefaultProvider("test"),
   611  				Module:   addrs.RootModule,
   612  			},
   613  		)
   614  
   615  		src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule)
   616  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule)
   617  		s.MoveAbsResource(src, dst)
   618  
   619  		if s.Empty() {
   620  			t.Fatal("unexpected empty state")
   621  		}
   622  
   623  		// The child module should have been removed after removing its only resource
   624  		if s.Module(srcModule) != nil {
   625  			t.Fatalf("child module %s was not removed from state after mv", srcModule.String())
   626  		}
   627  
   628  		gotMod := s.Module(dstModule)
   629  		if len(gotMod.Resources) != 1 {
   630  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources))
   631  		}
   632  
   633  		got := s.Resource(dst)
   634  		if got.Addr.Resource != dst.Resource {
   635  			t.Fatalf("dst resource not in state")
   636  		}
   637  	})
   638  }
   639  
   640  func TestState_MaybeMoveAbsResource(t *testing.T) {
   641  	state := NewState()
   642  	rootModule := state.RootModule()
   643  	rootModule.SetResourceInstanceCurrent(
   644  		addrs.Resource{
   645  			Mode: addrs.ManagedResourceMode,
   646  			Type: "test_thing",
   647  			Name: "foo",
   648  		}.Instance(addrs.IntKey(0)),
   649  		&ResourceInstanceObjectSrc{
   650  			Status:        ObjectReady,
   651  			SchemaVersion: 1,
   652  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   653  		},
   654  		addrs.AbsProviderConfig{
   655  			Provider: addrs.NewDefaultProvider("test"),
   656  			Module:   addrs.RootModule,
   657  		},
   658  	)
   659  
   660  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance)
   661  	dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance)
   662  
   663  	// First move, success
   664  	t.Run("first move", func(t *testing.T) {
   665  		moved := state.MaybeMoveAbsResource(src, dst)
   666  		if !moved {
   667  			t.Fatal("wrong result")
   668  		}
   669  	})
   670  
   671  	// Trying to move a resource that doesn't exist in state to a resource which does exist should be a noop.
   672  	t.Run("noop", func(t *testing.T) {
   673  		moved := state.MaybeMoveAbsResource(src, dst)
   674  		if moved {
   675  			t.Fatal("wrong result")
   676  		}
   677  	})
   678  }
   679  
   680  func TestState_MoveAbsResourceInstance(t *testing.T) {
   681  	state := NewState()
   682  	rootModule := state.RootModule()
   683  	rootModule.SetResourceInstanceCurrent(
   684  		addrs.Resource{
   685  			Mode: addrs.ManagedResourceMode,
   686  			Type: "test_thing",
   687  			Name: "foo",
   688  		}.Instance(addrs.NoKey),
   689  		&ResourceInstanceObjectSrc{
   690  			Status:        ObjectReady,
   691  			SchemaVersion: 1,
   692  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   693  		},
   694  		addrs.AbsProviderConfig{
   695  			Provider: addrs.NewDefaultProvider("test"),
   696  			Module:   addrs.RootModule,
   697  		},
   698  	)
   699  	// src resource from the state above
   700  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   701  
   702  	t.Run("resource to resource instance", func(t *testing.T) {
   703  		s := state.DeepCopy()
   704  		// For a little extra fun, move a resource to a resource instance: test_thing.foo to test_thing.foo[1]
   705  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance)
   706  
   707  		s.MoveAbsResourceInstance(src, dst)
   708  
   709  		if s.Empty() {
   710  			t.Fatal("unexpected empty state")
   711  		}
   712  
   713  		if len(s.RootModule().Resources) != 1 {
   714  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources))
   715  		}
   716  
   717  		got := s.ResourceInstance(dst)
   718  		if got == nil {
   719  			t.Fatalf("dst resource not in state")
   720  		}
   721  	})
   722  
   723  	t.Run("move to new module", func(t *testing.T) {
   724  		s := state.DeepCopy()
   725  		// test_thing.foo to module.kinder.test_thing.foo["baz"]
   726  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey)
   727  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(dstModule)
   728  
   729  		s.MoveAbsResourceInstance(src, dst)
   730  
   731  		if s.Empty() {
   732  			t.Fatal("unexpected empty state")
   733  		}
   734  
   735  		if s.Module(dstModule) == nil {
   736  			t.Fatalf("child module %s not in state", dstModule.String())
   737  		}
   738  
   739  		if len(s.Module(dstModule).Resources) != 1 {
   740  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources))
   741  		}
   742  
   743  		got := s.ResourceInstance(dst)
   744  		if got == nil {
   745  			t.Fatalf("dst resource not in state")
   746  		}
   747  	})
   748  }
   749  
   750  func TestState_MaybeMoveAbsResourceInstance(t *testing.T) {
   751  	state := NewState()
   752  	rootModule := state.RootModule()
   753  	rootModule.SetResourceInstanceCurrent(
   754  		addrs.Resource{
   755  			Mode: addrs.ManagedResourceMode,
   756  			Type: "test_thing",
   757  			Name: "foo",
   758  		}.Instance(addrs.NoKey),
   759  		&ResourceInstanceObjectSrc{
   760  			Status:        ObjectReady,
   761  			SchemaVersion: 1,
   762  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   763  		},
   764  		addrs.AbsProviderConfig{
   765  			Provider: addrs.NewDefaultProvider("test"),
   766  			Module:   addrs.RootModule,
   767  		},
   768  	)
   769  
   770  	// For a little extra fun, let's go from a resource to a resource instance: test_thing.foo to test_thing.bar[1]
   771  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   772  	dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance)
   773  
   774  	// First move, success
   775  	t.Run("first move", func(t *testing.T) {
   776  		moved := state.MaybeMoveAbsResourceInstance(src, dst)
   777  		if !moved {
   778  			t.Fatal("wrong result")
   779  		}
   780  		got := state.ResourceInstance(dst)
   781  		if got == nil {
   782  			t.Fatal("destination resource instance not in state")
   783  		}
   784  	})
   785  
   786  	// Moving a resource instance that doesn't exist in state to a resource which does exist should be a noop.
   787  	t.Run("noop", func(t *testing.T) {
   788  		moved := state.MaybeMoveAbsResourceInstance(src, dst)
   789  		if moved {
   790  			t.Fatal("wrong result")
   791  		}
   792  	})
   793  }
   794  
   795  func TestState_MoveModuleInstance(t *testing.T) {
   796  	state := NewState()
   797  	srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey)
   798  	m := state.EnsureModule(srcModule)
   799  	m.SetResourceInstanceCurrent(
   800  		addrs.Resource{
   801  			Mode: addrs.ManagedResourceMode,
   802  			Type: "test_thing",
   803  			Name: "foo",
   804  		}.Instance(addrs.NoKey),
   805  		&ResourceInstanceObjectSrc{
   806  			Status:        ObjectReady,
   807  			SchemaVersion: 1,
   808  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   809  		},
   810  		addrs.AbsProviderConfig{
   811  			Provider: addrs.NewDefaultProvider("test"),
   812  			Module:   addrs.RootModule,
   813  		},
   814  	)
   815  
   816  	dstModule := addrs.RootModuleInstance.Child("child", addrs.IntKey(3))
   817  	state.MoveModuleInstance(srcModule, dstModule)
   818  
   819  	// srcModule should have been removed, dstModule should exist and have one resource
   820  	if len(state.Modules) != 2 { // kinder[3] and root
   821  		t.Fatalf("wrong number of modules in state. Expected 2, got %d", len(state.Modules))
   822  	}
   823  
   824  	got := state.Module(dstModule)
   825  	if got == nil {
   826  		t.Fatal("dstModule not found")
   827  	}
   828  
   829  	gone := state.Module(srcModule)
   830  	if gone != nil {
   831  		t.Fatal("srcModule not removed from state")
   832  	}
   833  
   834  	r := got.Resource(mustAbsResourceAddr("test_thing.foo").Resource)
   835  	if r.Addr.Module.String() != dstModule.String() {
   836  		fmt.Println(r.Addr.Module.String())
   837  		t.Fatal("resource address was not updated")
   838  	}
   839  
   840  }
   841  
   842  func TestState_MaybeMoveModuleInstance(t *testing.T) {
   843  	state := NewState()
   844  	src := addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))
   845  	cm := state.EnsureModule(src)
   846  	cm.SetResourceInstanceCurrent(
   847  		addrs.Resource{
   848  			Mode: addrs.ManagedResourceMode,
   849  			Type: "test_thing",
   850  			Name: "foo",
   851  		}.Instance(addrs.NoKey),
   852  		&ResourceInstanceObjectSrc{
   853  			Status:        ObjectReady,
   854  			SchemaVersion: 1,
   855  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   856  		},
   857  		addrs.AbsProviderConfig{
   858  			Provider: addrs.NewDefaultProvider("test"),
   859  			Module:   addrs.RootModule,
   860  		},
   861  	)
   862  
   863  	dst := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("b"))
   864  
   865  	// First move, success
   866  	t.Run("first move", func(t *testing.T) {
   867  		moved := state.MaybeMoveModuleInstance(src, dst)
   868  		if !moved {
   869  			t.Fatal("wrong result")
   870  		}
   871  	})
   872  
   873  	// Second move, should be a noop
   874  	t.Run("noop", func(t *testing.T) {
   875  		moved := state.MaybeMoveModuleInstance(src, dst)
   876  		if moved {
   877  			t.Fatal("wrong result")
   878  		}
   879  	})
   880  }
   881  
   882  func TestState_MoveModule(t *testing.T) {
   883  	// For this test, add two module instances (kinder and kinder["a"]).
   884  	// MoveModule(kinder) should move both instances.
   885  	state := NewState() // starter state, should be copied by the subtests.
   886  	srcModule := addrs.RootModule.Child("kinder")
   887  	m := state.EnsureModule(srcModule.UnkeyedInstanceShim())
   888  	m.SetResourceInstanceCurrent(
   889  		addrs.Resource{
   890  			Mode: addrs.ManagedResourceMode,
   891  			Type: "test_thing",
   892  			Name: "foo",
   893  		}.Instance(addrs.NoKey),
   894  		&ResourceInstanceObjectSrc{
   895  			Status:        ObjectReady,
   896  			SchemaVersion: 1,
   897  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   898  		},
   899  		addrs.AbsProviderConfig{
   900  			Provider: addrs.NewDefaultProvider("test"),
   901  			Module:   addrs.RootModule,
   902  		},
   903  	)
   904  
   905  	moduleInstance := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("a"))
   906  	mi := state.EnsureModule(moduleInstance)
   907  	mi.SetResourceInstanceCurrent(
   908  		addrs.Resource{
   909  			Mode: addrs.ManagedResourceMode,
   910  			Type: "test_thing",
   911  			Name: "foo",
   912  		}.Instance(addrs.NoKey),
   913  		&ResourceInstanceObjectSrc{
   914  			Status:        ObjectReady,
   915  			SchemaVersion: 1,
   916  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   917  		},
   918  		addrs.AbsProviderConfig{
   919  			Provider: addrs.NewDefaultProvider("test"),
   920  			Module:   addrs.RootModule,
   921  		},
   922  	)
   923  
   924  	_, mc := srcModule.Call()
   925  	src := mc.Absolute(addrs.RootModuleInstance.Child("kinder", addrs.NoKey))
   926  
   927  	t.Run("basic", func(t *testing.T) {
   928  		s := state.DeepCopy()
   929  		_, dstMC := addrs.RootModule.Child("child").Call()
   930  		dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   931  		s.MoveModule(src, dst)
   932  
   933  		// srcModule should have been removed, dstModule should exist and have one resource
   934  		if len(s.Modules) != 3 { // child, child["a"] and root
   935  			t.Fatalf("wrong number of modules in state. Expected 3, got %d", len(s.Modules))
   936  		}
   937  
   938  		got := s.Module(dst.Module)
   939  		if got == nil {
   940  			t.Fatal("dstModule not found")
   941  		}
   942  
   943  		got = s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a")))
   944  		if got == nil {
   945  			t.Fatal("dstModule instance \"a\" not found")
   946  		}
   947  
   948  		gone := s.Module(srcModule.UnkeyedInstanceShim())
   949  		if gone != nil {
   950  			t.Fatal("srcModule not removed from state")
   951  		}
   952  	})
   953  
   954  	t.Run("nested modules", func(t *testing.T) {
   955  		s := state.DeepCopy()
   956  
   957  		// add a child module to module.kinder
   958  		mi := mustParseModuleInstanceStr(`module.kinder.module.grand[1]`)
   959  		m := s.EnsureModule(mi)
   960  		m.SetResourceInstanceCurrent(
   961  			addrs.Resource{
   962  				Mode: addrs.ManagedResourceMode,
   963  				Type: "test_thing",
   964  				Name: "foo",
   965  			}.Instance(addrs.NoKey),
   966  			&ResourceInstanceObjectSrc{
   967  				Status:        ObjectReady,
   968  				SchemaVersion: 1,
   969  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   970  			},
   971  			addrs.AbsProviderConfig{
   972  				Provider: addrs.NewDefaultProvider("test"),
   973  				Module:   addrs.RootModule,
   974  			},
   975  		)
   976  
   977  		_, dstMC := addrs.RootModule.Child("child").Call()
   978  		dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   979  		s.MoveModule(src, dst)
   980  
   981  		moved := s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a")))
   982  		if moved == nil {
   983  			t.Fatal("dstModule not found")
   984  		}
   985  
   986  		// The nested module's relative address should also have been updated
   987  		nested := s.Module(mustParseModuleInstanceStr(`module.child.module.grand[1]`))
   988  		if nested == nil {
   989  			t.Fatal("nested child module of src wasn't moved")
   990  		}
   991  	})
   992  }
   993  
   994  func mustParseModuleInstanceStr(str string) addrs.ModuleInstance {
   995  	addr, diags := addrs.ParseModuleInstanceStr(str)
   996  	if diags.HasErrors() {
   997  		panic(diags.Err())
   998  	}
   999  	return addr
  1000  }
  1001  
  1002  func mustAbsResourceAddr(s string) addrs.AbsResource {
  1003  	addr, diags := addrs.ParseAbsResourceStr(s)
  1004  	if diags.HasErrors() {
  1005  		panic(diags.Err())
  1006  	}
  1007  	return addr
  1008  }