1package statemgr
2
3import (
4	"reflect"
5	"testing"
6
7	"github.com/davecgh/go-spew/spew"
8	"github.com/zclconf/go-cty/cty"
9
10	"github.com/hashicorp/terraform/internal/addrs"
11	"github.com/hashicorp/terraform/internal/states"
12	"github.com/hashicorp/terraform/internal/states/statefile"
13)
14
15// TestFull is a helper for testing full state manager implementations. It
16// expects that the given implementation is pre-loaded with a snapshot of the
17// result from TestFullInitialState.
18//
19// If the given state manager also implements PersistentMeta, this function
20// will test that the snapshot metadata changes as expected between calls
21// to the methods of Persistent.
22func TestFull(t *testing.T, s Full) {
23	t.Helper()
24
25	if err := s.RefreshState(); err != nil {
26		t.Fatalf("err: %s", err)
27	}
28
29	// Check that the initial state is correct.
30	// These do have different Lineages, but we will replace current below.
31	initial := TestFullInitialState()
32	if state := s.State(); !state.Equal(initial) {
33		t.Fatalf("state does not match expected initial state\n\ngot:\n%s\nwant:\n%s", spew.Sdump(state), spew.Sdump(initial))
34	}
35
36	var initialMeta SnapshotMeta
37	if sm, ok := s.(PersistentMeta); ok {
38		initialMeta = sm.StateSnapshotMeta()
39	}
40
41	// Now we've proven that the state we're starting with is an initial
42	// state, we'll complete our work here with that state, since otherwise
43	// further writes would violate the invariant that we only try to write
44	// states that share the same lineage as what was initially written.
45	current := s.State()
46
47	// Write a new state and verify that we have it
48	current.RootModule().SetOutputValue("bar", cty.StringVal("baz"), false)
49
50	if err := s.WriteState(current); err != nil {
51		t.Fatalf("err: %s", err)
52	}
53
54	if actual := s.State(); !actual.Equal(current) {
55		t.Fatalf("bad:\n%#v\n\n%#v", actual, current)
56	}
57
58	// Test persistence
59	if err := s.PersistState(); err != nil {
60		t.Fatalf("err: %s", err)
61	}
62
63	// Refresh if we got it
64	if err := s.RefreshState(); err != nil {
65		t.Fatalf("err: %s", err)
66	}
67
68	var newMeta SnapshotMeta
69	if sm, ok := s.(PersistentMeta); ok {
70		newMeta = sm.StateSnapshotMeta()
71		if got, want := newMeta.Lineage, initialMeta.Lineage; got != want {
72			t.Errorf("Lineage changed from %q to %q", want, got)
73		}
74		if after, before := newMeta.Serial, initialMeta.Serial; after == before {
75			t.Errorf("Serial didn't change from %d after new module added", before)
76		}
77	}
78
79	// Same serial
80	serial := newMeta.Serial
81	if err := s.WriteState(current); err != nil {
82		t.Fatalf("err: %s", err)
83	}
84	if err := s.PersistState(); err != nil {
85		t.Fatalf("err: %s", err)
86	}
87
88	if sm, ok := s.(PersistentMeta); ok {
89		newMeta = sm.StateSnapshotMeta()
90		if newMeta.Serial != serial {
91			t.Fatalf("serial changed after persisting with no changes: got %d, want %d", newMeta.Serial, serial)
92		}
93	}
94
95	if sm, ok := s.(PersistentMeta); ok {
96		newMeta = sm.StateSnapshotMeta()
97	}
98
99	// Change the serial
100	current = current.DeepCopy()
101	current.EnsureModule(addrs.RootModuleInstance).SetOutputValue(
102		"serialCheck", cty.StringVal("true"), false,
103	)
104	if err := s.WriteState(current); err != nil {
105		t.Fatalf("err: %s", err)
106	}
107	if err := s.PersistState(); err != nil {
108		t.Fatalf("err: %s", err)
109	}
110
111	if sm, ok := s.(PersistentMeta); ok {
112		oldMeta := newMeta
113		newMeta = sm.StateSnapshotMeta()
114
115		if newMeta.Serial <= serial {
116			t.Fatalf("serial incorrect after persisting with changes: got %d, want > %d", newMeta.Serial, serial)
117		}
118
119		if newMeta.TerraformVersion != oldMeta.TerraformVersion {
120			t.Fatalf("TFVersion changed from %s to %s", oldMeta.TerraformVersion, newMeta.TerraformVersion)
121		}
122
123		// verify that Lineage doesn't change along with Serial, or during copying.
124		if newMeta.Lineage != oldMeta.Lineage {
125			t.Fatalf("Lineage changed from %q to %q", oldMeta.Lineage, newMeta.Lineage)
126		}
127	}
128
129	// Check that State() returns a copy by modifying the copy and comparing
130	// to the current state.
131	stateCopy := s.State()
132	stateCopy.EnsureModule(addrs.RootModuleInstance.Child("another", addrs.NoKey))
133	if reflect.DeepEqual(stateCopy, s.State()) {
134		t.Fatal("State() should return a copy")
135	}
136
137	// our current expected state should also marshal identically to the persisted state
138	if !statefile.StatesMarshalEqual(current, s.State()) {
139		t.Fatalf("Persisted state altered unexpectedly.\n\ngot:\n%s\nwant:\n%s", spew.Sdump(s.State()), spew.Sdump(current))
140	}
141}
142
143// TestFullInitialState is a state that should be snapshotted into a
144// full state manager before passing it into TestFull.
145func TestFullInitialState() *states.State {
146	state := states.NewState()
147	childMod := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
148	rAddr := addrs.Resource{
149		Mode: addrs.ManagedResourceMode,
150		Type: "null_resource",
151		Name: "foo",
152	}
153	providerAddr := addrs.AbsProviderConfig{
154		Provider: addrs.NewDefaultProvider(rAddr.ImpliedProvider()),
155		Module:   addrs.RootModule,
156	}
157	childMod.SetResourceProvider(rAddr, providerAddr)
158	return state
159}
160