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  }