mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-13 09:32:24 -06:00
cb2e9119aa
Signed-off-by: namgyalangmo <75657887+namgyalangmo@users.noreply.github.com>
300 lines
8.1 KiB
Go
300 lines
8.1 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 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
|
|
}
|