opentofu/internal/backend/local/hook_state_test.go
James Humphries 9c24b6183a
Revert PRs that introduced propogating contexts (#835)
Co-authored-by: Dmitry Kisler <admin@dkisler.com>
2023-11-08 21:09:14 +00:00

298 lines
8.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package local
import (
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/opentofu/opentofu/internal/tofu"
)
func TestStateHook_impl(t *testing.T) {
var _ tofu.Hook = new(StateHook)
}
func TestStateHook(t *testing.T) {
is := statemgr.NewTransientInMemory(nil)
var hook tofu.Hook = &StateHook{StateMgr: is}
s := statemgr.TestFullInitialState()
action, err := hook.PostStateUpdate(s)
if err != nil {
t.Fatalf("err: %s", err)
}
if action != tofu.HookActionContinue {
t.Fatalf("bad: %v", action)
}
if !is.State().Equal(s) {
t.Fatalf("bad state: %#v", is.State())
}
}
func TestStateHookStopping(t *testing.T) {
is := &testPersistentState{}
hook := &StateHook{
StateMgr: is,
Schemas: &tofu.Schemas{},
PersistInterval: 4 * time.Hour,
intermediatePersist: IntermediateStatePersistInfo{
LastPersist: time.Now(),
},
}
s := statemgr.TestFullInitialState()
action, err := hook.PostStateUpdate(s)
if err != nil {
t.Fatalf("unexpected error from PostStateUpdate: %s", err)
}
if got, want := action, tofu.HookActionContinue; got != want {
t.Fatalf("wrong hookaction %#v; want %#v", got, want)
}
if is.Written == nil || !is.Written.Equal(s) {
t.Fatalf("mismatching state written")
}
if is.Persisted != nil {
t.Fatalf("persisted too soon")
}
// We'll now force lastPersist to be long enough ago that persisting
// should be due on the next call.
hook.intermediatePersist.LastPersist = time.Now().Add(-5 * time.Hour)
hook.PostStateUpdate(s)
if is.Written == nil || !is.Written.Equal(s) {
t.Fatalf("mismatching state written")
}
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
hook.PostStateUpdate(s)
if is.Written == nil || !is.Written.Equal(s) {
t.Fatalf("mismatching state written")
}
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
gotLog := is.CallLog
wantLog := []string{
// Initial call before we reset lastPersist
"WriteState",
// Write and then persist after we reset lastPersist
"WriteState",
"PersistState",
// Final call when persisting wasn't due yet.
"WriteState",
}
if diff := cmp.Diff(wantLog, gotLog); diff != "" {
t.Fatalf("wrong call log so far\n%s", diff)
}
// We'll reset the log now before we try seeing what happens after
// we use "Stopped".
is.CallLog = is.CallLog[:0]
is.Persisted = nil
hook.Stopping()
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
is.Persisted = nil
hook.PostStateUpdate(s)
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
is.Persisted = nil
hook.PostStateUpdate(s)
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
gotLog = is.CallLog
wantLog = []string{
// "Stopping" immediately persisted
"PersistState",
// PostStateUpdate then writes and persists on every call,
// on the assumption that we're now bailing out after
// being cancelled and trying to save as much state as we can.
"WriteState",
"PersistState",
"WriteState",
"PersistState",
}
if diff := cmp.Diff(wantLog, gotLog); diff != "" {
t.Fatalf("wrong call log once in stopping mode\n%s", diff)
}
}
func TestStateHookCustomPersistRule(t *testing.T) {
is := &testPersistentStateThatRefusesToPersist{}
hook := &StateHook{
StateMgr: is,
Schemas: &tofu.Schemas{},
PersistInterval: 4 * time.Hour,
intermediatePersist: IntermediateStatePersistInfo{
LastPersist: time.Now(),
},
}
s := statemgr.TestFullInitialState()
action, err := hook.PostStateUpdate(s)
if err != nil {
t.Fatalf("unexpected error from PostStateUpdate: %s", err)
}
if got, want := action, tofu.HookActionContinue; got != want {
t.Fatalf("wrong hookaction %#v; want %#v", got, want)
}
if is.Written == nil || !is.Written.Equal(s) {
t.Fatalf("mismatching state written")
}
if is.Persisted != nil {
t.Fatalf("persisted too soon")
}
// We'll now force lastPersist to be long enough ago that persisting
// should be due on the next call.
hook.intermediatePersist.LastPersist = time.Now().Add(-5 * time.Hour)
hook.PostStateUpdate(s)
if is.Written == nil || !is.Written.Equal(s) {
t.Fatalf("mismatching state written")
}
if is.Persisted != nil {
t.Fatalf("has a persisted state, but shouldn't")
}
hook.PostStateUpdate(s)
if is.Written == nil || !is.Written.Equal(s) {
t.Fatalf("mismatching state written")
}
if is.Persisted != nil {
t.Fatalf("has a persisted state, but shouldn't")
}
gotLog := is.CallLog
wantLog := []string{
// Initial call before we reset lastPersist
"WriteState",
"ShouldPersistIntermediateState",
// Previous call should return false, preventing a "PersistState" call
// Write and then decline to persist
"WriteState",
"ShouldPersistIntermediateState",
// Previous call should return false, preventing a "PersistState" call
// Final call before we start "stopping".
"WriteState",
"ShouldPersistIntermediateState",
// Previous call should return false, preventing a "PersistState" call
}
if diff := cmp.Diff(wantLog, gotLog); diff != "" {
t.Fatalf("wrong call log so far\n%s", diff)
}
// We'll reset the log now before we try seeing what happens after
// we use "Stopped".
is.CallLog = is.CallLog[:0]
is.Persisted = nil
hook.Stopping()
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
is.Persisted = nil
hook.PostStateUpdate(s)
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
is.Persisted = nil
hook.PostStateUpdate(s)
if is.Persisted == nil || !is.Persisted.Equal(s) {
t.Fatalf("mismatching state persisted")
}
gotLog = is.CallLog
wantLog = []string{
"ShouldPersistIntermediateState",
// Previous call should return true, allowing the following "PersistState" call
"PersistState",
"WriteState",
"ShouldPersistIntermediateState",
// Previous call should return true, allowing the following "PersistState" call
"PersistState",
"WriteState",
"ShouldPersistIntermediateState",
// Previous call should return true, allowing the following "PersistState" call
"PersistState",
}
if diff := cmp.Diff(wantLog, gotLog); diff != "" {
t.Fatalf("wrong call log once in stopping mode\n%s", diff)
}
}
type testPersistentState struct {
CallLog []string
Written *states.State
Persisted *states.State
}
var _ statemgr.Writer = (*testPersistentState)(nil)
var _ statemgr.Persister = (*testPersistentState)(nil)
func (sm *testPersistentState) WriteState(state *states.State) error {
sm.CallLog = append(sm.CallLog, "WriteState")
sm.Written = state
return nil
}
func (sm *testPersistentState) PersistState(schemas *tofu.Schemas) error {
if schemas == nil {
return fmt.Errorf("no schemas")
}
sm.CallLog = append(sm.CallLog, "PersistState")
sm.Persisted = sm.Written
return nil
}
type testPersistentStateThatRefusesToPersist struct {
CallLog []string
Written *states.State
Persisted *states.State
}
var _ statemgr.Writer = (*testPersistentStateThatRefusesToPersist)(nil)
var _ statemgr.Persister = (*testPersistentStateThatRefusesToPersist)(nil)
var _ IntermediateStateConditionalPersister = (*testPersistentStateThatRefusesToPersist)(nil)
func (sm *testPersistentStateThatRefusesToPersist) WriteState(state *states.State) error {
sm.CallLog = append(sm.CallLog, "WriteState")
sm.Written = state
return nil
}
func (sm *testPersistentStateThatRefusesToPersist) PersistState(schemas *tofu.Schemas) error {
if schemas == nil {
return fmt.Errorf("no schemas")
}
sm.CallLog = append(sm.CallLog, "PersistState")
sm.Persisted = sm.Written
return nil
}
// ShouldPersistIntermediateState implements IntermediateStateConditionalPersister
func (sm *testPersistentStateThatRefusesToPersist) ShouldPersistIntermediateState(info *IntermediateStatePersistInfo) bool {
sm.CallLog = append(sm.CallLog, "ShouldPersistIntermediateState")
return info.ForcePersist
}