| package statemgr |
| |
| import ( |
| "reflect" |
| "testing" |
| |
| "github.com/davecgh/go-spew/spew" |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/states" |
| "github.com/hashicorp/terraform/internal/states/statefile" |
| ) |
| |
| // TestFull is a helper for testing full state manager implementations. It |
| // expects that the given implementation is pre-loaded with a snapshot of the |
| // result from TestFullInitialState. |
| // |
| // If the given state manager also implements PersistentMeta, this function |
| // will test that the snapshot metadata changes as expected between calls |
| // to the methods of Persistent. |
| func TestFull(t *testing.T, s Full) { |
| t.Helper() |
| |
| if err := s.RefreshState(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| // Check that the initial state is correct. |
| // These do have different Lineages, but we will replace current below. |
| initial := TestFullInitialState() |
| if state := s.State(); !state.Equal(initial) { |
| t.Fatalf("state does not match expected initial state\n\ngot:\n%s\nwant:\n%s", spew.Sdump(state), spew.Sdump(initial)) |
| } |
| |
| var initialMeta SnapshotMeta |
| if sm, ok := s.(PersistentMeta); ok { |
| initialMeta = sm.StateSnapshotMeta() |
| } |
| |
| // Now we've proven that the state we're starting with is an initial |
| // state, we'll complete our work here with that state, since otherwise |
| // further writes would violate the invariant that we only try to write |
| // states that share the same lineage as what was initially written. |
| current := s.State() |
| |
| // Write a new state and verify that we have it |
| current.RootModule().SetOutputValue("bar", cty.StringVal("baz"), false) |
| |
| if err := s.WriteState(current); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| if actual := s.State(); !actual.Equal(current) { |
| t.Fatalf("bad:\n%#v\n\n%#v", actual, current) |
| } |
| |
| // Test persistence |
| if err := s.PersistState(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| // Refresh if we got it |
| if err := s.RefreshState(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| var newMeta SnapshotMeta |
| if sm, ok := s.(PersistentMeta); ok { |
| newMeta = sm.StateSnapshotMeta() |
| if got, want := newMeta.Lineage, initialMeta.Lineage; got != want { |
| t.Errorf("Lineage changed from %q to %q", want, got) |
| } |
| if after, before := newMeta.Serial, initialMeta.Serial; after == before { |
| t.Errorf("Serial didn't change from %d after new module added", before) |
| } |
| } |
| |
| // Same serial |
| serial := newMeta.Serial |
| if err := s.WriteState(current); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| if err := s.PersistState(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| if sm, ok := s.(PersistentMeta); ok { |
| newMeta = sm.StateSnapshotMeta() |
| if newMeta.Serial != serial { |
| t.Fatalf("serial changed after persisting with no changes: got %d, want %d", newMeta.Serial, serial) |
| } |
| } |
| |
| if sm, ok := s.(PersistentMeta); ok { |
| newMeta = sm.StateSnapshotMeta() |
| } |
| |
| // Change the serial |
| current = current.DeepCopy() |
| current.EnsureModule(addrs.RootModuleInstance).SetOutputValue( |
| "serialCheck", cty.StringVal("true"), false, |
| ) |
| if err := s.WriteState(current); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| if err := s.PersistState(); err != nil { |
| t.Fatalf("err: %s", err) |
| } |
| |
| if sm, ok := s.(PersistentMeta); ok { |
| oldMeta := newMeta |
| newMeta = sm.StateSnapshotMeta() |
| |
| if newMeta.Serial <= serial { |
| t.Fatalf("serial incorrect after persisting with changes: got %d, want > %d", newMeta.Serial, serial) |
| } |
| |
| if newMeta.TerraformVersion != oldMeta.TerraformVersion { |
| t.Fatalf("TFVersion changed from %s to %s", oldMeta.TerraformVersion, newMeta.TerraformVersion) |
| } |
| |
| // verify that Lineage doesn't change along with Serial, or during copying. |
| if newMeta.Lineage != oldMeta.Lineage { |
| t.Fatalf("Lineage changed from %q to %q", oldMeta.Lineage, newMeta.Lineage) |
| } |
| } |
| |
| // Check that State() returns a copy by modifying the copy and comparing |
| // to the current state. |
| stateCopy := s.State() |
| stateCopy.EnsureModule(addrs.RootModuleInstance.Child("another", addrs.NoKey)) |
| if reflect.DeepEqual(stateCopy, s.State()) { |
| t.Fatal("State() should return a copy") |
| } |
| |
| // our current expected state should also marshal identically to the persisted state |
| if !statefile.StatesMarshalEqual(current, s.State()) { |
| t.Fatalf("Persisted state altered unexpectedly.\n\ngot:\n%s\nwant:\n%s", spew.Sdump(s.State()), spew.Sdump(current)) |
| } |
| } |
| |
| // TestFullInitialState is a state that should be snapshotted into a |
| // full state manager before passing it into TestFull. |
| func TestFullInitialState() *states.State { |
| state := states.NewState() |
| childMod := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) |
| rAddr := addrs.Resource{ |
| Mode: addrs.ManagedResourceMode, |
| Type: "null_resource", |
| Name: "foo", |
| } |
| providerAddr := addrs.AbsProviderConfig{ |
| Provider: addrs.NewDefaultProvider(rAddr.ImpliedProvider()), |
| Module: addrs.RootModule, |
| } |
| childMod.SetResourceProvider(rAddr, providerAddr) |
| return state |
| } |