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