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