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  }