Issue 248: left-over resources after tofu test should be written to a file (#1243)

Signed-off-by: kazzhar <karthik.nayak@harness.io>
This commit is contained in:
Karthik Nayak 2024-03-12 19:59:06 +05:30 committed by GitHub
parent 670f2515c3
commit 311b5c37b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 562 additions and 10 deletions

View File

@ -980,6 +980,9 @@ func (runner *TestFileRunner) Cleanup(file *moduletest.File) {
} }
runner.Suite.View.DestroySummary(diags, state.Run, file, updated) runner.Suite.View.DestroySummary(diags, state.Run, file, updated)
if updated.HasManagedResourceInstanceObjects() {
views.SaveErroredTestStateFile(updated, state.Run, file, runner.Suite.View)
}
reset() reset()
} }
} }

View File

@ -19,10 +19,12 @@ import (
"github.com/opentofu/opentofu/internal/command/jsonstate" "github.com/opentofu/opentofu/internal/command/jsonstate"
"github.com/opentofu/opentofu/internal/command/views/json" "github.com/opentofu/opentofu/internal/command/views/json"
"github.com/opentofu/opentofu/internal/configs" "github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/moduletest" "github.com/opentofu/opentofu/internal/moduletest"
"github.com/opentofu/opentofu/internal/plans" "github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/states" "github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/states/statefile" "github.com/opentofu/opentofu/internal/states/statefile"
"github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu" "github.com/opentofu/opentofu/internal/tofu"
) )
@ -223,7 +225,7 @@ func (t *TestHuman) DestroySummary(diags tfdiags.Diagnostics, run *moduletest.Ru
t.Diagnostics(run, file, diags) t.Diagnostics(run, file, diags)
if state.HasManagedResourceInstanceObjects() { if state.HasManagedResourceInstanceObjects() {
t.view.streams.Eprint(format.WordWrap(fmt.Sprintf("\nOpenTofu left the following resources in state after executing %s, and they need to be cleaned up manually:\n", identifier), t.view.errorColumns())) t.view.streams.Eprint(format.WordWrap(fmt.Sprintf("\nOpenTofu left the following resources in state after executing %s, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:\n", identifier), t.view.errorColumns()))
for _, resource := range state.AllResourceInstanceObjectAddrs() { for _, resource := range state.AllResourceInstanceObjectAddrs() {
if resource.DeposedKey != states.NotDeposed { if resource.DeposedKey != states.NotDeposed {
t.view.streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey) t.view.streams.Eprintf(" - %s (%s)\n", resource.Instance, resource.DeposedKey)
@ -460,21 +462,19 @@ func (t *TestJSON) DestroySummary(diags tfdiags.Diagnostics, run *moduletest.Run
if run != nil { if run != nil {
t.view.log.Error( t.view.log.Error(
fmt.Sprintf("OpenTofu left some resources in state after executing %s/%s, they need to be cleaned up manually.", file.Name, run.Name), fmt.Sprintf("OpenTofu left some resources in state after executing %s/%s, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:", file.Name, run.Name),
"type", json.MessageTestCleanup, "type", json.MessageTestCleanup,
json.MessageTestCleanup, cleanup, json.MessageTestCleanup, cleanup,
"@testfile", file.Name, "@testfile", file.Name,
"@testrun", run.Name) "@testrun", run.Name)
} else { } else {
t.view.log.Error( t.view.log.Error(
fmt.Sprintf("OpenTofu left some resources in state after executing %s, they need to be cleaned up manually.", file.Name), fmt.Sprintf("OpenTofu left some resources in state after executing %s, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:", file.Name),
"type", json.MessageTestCleanup, "type", json.MessageTestCleanup,
json.MessageTestCleanup, cleanup, json.MessageTestCleanup, cleanup,
"@testfile", file.Name) "@testfile", file.Name)
} }
} }
t.Diagnostics(run, file, diags) t.Diagnostics(run, file, diags)
} }
@ -570,3 +570,61 @@ func testStatus(status moduletest.Status) string {
panic("unrecognized status: " + status.String()) panic("unrecognized status: " + status.String())
} }
} }
// SaveErroredTestStateFile is a helper function to invoked in DestorySummary
// to store the state to errored_test.tfstate and handle associated diagnostics and errors with this operation
func SaveErroredTestStateFile(state *states.State, run *moduletest.Run, file *moduletest.File, view Test) {
var diags tfdiags.Diagnostics
localFileSystem := statemgr.NewFilesystem("errored_test.tfstate", encryption.StateEncryptionDisabled())
stateFile := statemgr.NewStateFile()
stateFile.State = state
//creating an operation to invoke EmergencyDumpState()
var op Operation
switch v := view.(type) {
case *TestHuman:
op = NewOperation(arguments.ViewHuman, false, v.view)
v.view.streams.Eprint(format.WordWrap("\nWriting state to file: errored_test.tfstate\n", v.view.errorColumns()))
case *TestJSON:
op = &OperationJSON{
view: v.view,
}
v.view.log.Info("Writing state to file: errored_test.tfstate")
default:
}
writeErr := localFileSystem.WriteStateForMigration(stateFile, true)
if writeErr != nil {
// if the write operation to errored_test.tfstate executed by WriteStateForMigration fails, as a final attempt to
// prevent leaving the user with no state file at all, the JSON state is printed onto the terminal by EmergencyDumpState()
if dumpErr := op.EmergencyDumpState(stateFile, encryption.StateEncryptionDisabled()); dumpErr != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to serialize state",
fmt.Sprintf(stateWriteFatalErrorFmt, dumpErr),
))
}
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to persist state",
stateWriteConsoleFallbackError,
))
}
view.Diagnostics(run, file, diags)
}
const stateWriteFatalErrorFmt = `Failed to save state after an errored test run.
Error serializing state: %s
A catastrophic error has prevented OpenTofu from persisting the state during an errored test run.
This is a serious bug in OpenTofu and should be reported.
`
const stateWriteConsoleFallbackError = `The errors shown above prevented OpenTofu from writing the state to
the errored_test.tfstate. As a fallback, the raw state data is printed above as a JSON object.
To retry writing this state, copy the state data (from the first { to the last } inclusive) and save it into a local file named "errored_test.tfstate".
`

View File

@ -6,6 +6,8 @@
package views package views
import ( import (
"os"
"path/filepath"
"strings" "strings"
"testing" "testing"
@ -849,7 +851,9 @@ some thing not very bad happened again
`, `,
stderr: ` stderr: `
OpenTofu left the following resources in state after executing OpenTofu left the following resources in state after executing
main.tftest.hcl, and they need to be cleaned up manually: main.tftest.hcl, these left-over resources can be viewed by reading the
statefile written to disk(errored_test.tfstate) and they need to be cleaned
up manually:
- test.bar - test.bar
- test.bar (0fcb640a) - test.bar (0fcb640a)
- test.foo - test.foo
@ -921,10 +925,82 @@ Error: first error
this time it is very bad this time it is very bad
OpenTofu left the following resources in state after executing OpenTofu left the following resources in state after executing
main.tftest.hcl, and they need to be cleaned up manually: main.tftest.hcl, these left-over resources can be viewed by reading the
statefile written to disk(errored_test.tfstate) and they need to be cleaned
up manually:
- test.bar - test.bar
- test.bar (0fcb640a) - test.bar (0fcb640a)
- test.foo - test.foo
`,
},
"state_null_resource_with_errors": {
diags: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Warning, "first warning", "some thing not very bad happened"),
tfdiags.Sourceless(tfdiags.Warning, "second warning", "some thing not very bad happened again"),
tfdiags.Sourceless(tfdiags.Error, "first error", "this time it is very bad"),
},
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
Dependencies: []addrs.ConfigResource{
{
Module: []string{},
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
},
},
},
CreateBeforeDestroy: false,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
}),
stdout: `
Warning: first warning
some thing not very bad happened
Warning: second warning
some thing not very bad happened again
`,
stderr: `OpenTofu encountered an error destroying resources created while executing
main.tftest.hcl.
Error: first error
this time it is very bad
OpenTofu left the following resources in state after executing
main.tftest.hcl, these left-over resources can be viewed by reading the
statefile written to disk(errored_test.tfstate) and they need to be cleaned
up manually:
- null_resource.failing
- null_resource.failing_will_depend_on_me
`, `,
}, },
} }
@ -1949,7 +2025,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
want: []map[string]interface{}{ want: []map[string]interface{}{
{ {
"@level": "error", "@level": "error",
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl/run_block, they need to be cleaned up manually.", "@message": "OpenTofu left some resources in state after executing main.tftest.hcl/run_block, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
"@module": "tofu.ui", "@module": "tofu.ui",
"@testfile": "main.tftest.hcl", "@testfile": "main.tftest.hcl",
"@testrun": "run_block", "@testrun": "run_block",
@ -2015,7 +2091,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
want: []map[string]interface{}{ want: []map[string]interface{}{
{ {
"@level": "error", "@level": "error",
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, they need to be cleaned up manually.", "@message": "OpenTofu left some resources in state after executing main.tftest.hcl, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
"@module": "tofu.ui", "@module": "tofu.ui",
"@testfile": "main.tftest.hcl", "@testfile": "main.tftest.hcl",
"test_cleanup": map[string]interface{}{ "test_cleanup": map[string]interface{}{
@ -2112,7 +2188,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
want: []map[string]interface{}{ want: []map[string]interface{}{
{ {
"@level": "error", "@level": "error",
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, they need to be cleaned up manually.", "@message": "OpenTofu left some resources in state after executing main.tftest.hcl, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
"@module": "tofu.ui", "@module": "tofu.ui",
"@testfile": "main.tftest.hcl", "@testfile": "main.tftest.hcl",
"test_cleanup": map[string]interface{}{ "test_cleanup": map[string]interface{}{
@ -2169,6 +2245,107 @@ func TestTestJSON_DestroySummary(t *testing.T) {
}, },
}, },
}, },
"state_null_resource_with_errors": {
diags: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Warning, "first warning", "something not very bad happened"),
tfdiags.Sourceless(tfdiags.Warning, "second warning", "something not very bad happened again"),
tfdiags.Sourceless(tfdiags.Error, "first error", "this time it is very bad"),
},
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
Dependencies: []addrs.ConfigResource{
{
Module: []string{},
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
},
},
},
CreateBeforeDestroy: false,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
}), want: []map[string]interface{}{
{
"@level": "error",
"@message": "OpenTofu left some resources in state after executing main.tftest.hcl, these left-over resources can be viewed by reading the statefile written to disk(errored_test.tfstate) and they need to be cleaned up manually:",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"test_cleanup": map[string]interface{}{
"failed_resources": []interface{}{
map[string]interface{}{
"instance": "null_resource.failing",
},
map[string]interface{}{
"instance": "null_resource.failing_will_depend_on_me",
},
},
},
"type": "test_cleanup",
},
{
"@level": "warn",
"@message": "Warning: first warning",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]interface{}{
"detail": "something not very bad happened",
"severity": "warning",
"summary": "first warning",
},
"type": "diagnostic",
},
{
"@level": "warn",
"@message": "Warning: second warning",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]interface{}{
"detail": "something not very bad happened again",
"severity": "warning",
"summary": "second warning",
},
"type": "diagnostic",
},
{
"@level": "error",
"@message": "Error: first error",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]interface{}{
"detail": "this time it is very bad",
"severity": "error",
"summary": "first error",
},
"type": "diagnostic",
},
},
},
} }
for name, tc := range tcs { for name, tc := range tcs {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -3078,6 +3255,320 @@ func TestTestJSON_FatalInterruptSummary(t *testing.T) {
} }
} }
func TestSaveErroredStateFile(t *testing.T) {
tcsHuman := map[string]struct {
state *states.State
run *moduletest.Run
file *moduletest.File
stderr string
want interface{}
}{
"state_foo_bar_human": {
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
state.SetResourceInstanceDeposed(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
"0fcb640a",
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
}),
stderr: `
Writing state to file: errored_test.tfstate
`,
want: nil,
},
"state_null_resource_human": {
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
Dependencies: []addrs.ConfigResource{
{
Module: []string{},
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
},
},
},
CreateBeforeDestroy: false,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
}),
stderr: `
Writing state to file: errored_test.tfstate
`,
want: nil,
},
}
tcsJson := map[string]struct {
state *states.State
run *moduletest.Run
file *moduletest.File
stderr string
want interface{}
}{
"state_with_run_json": {
file: &moduletest.File{Name: "main.tftest.hcl"},
run: &moduletest.Run{Name: "run_block"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
}),
stderr: "",
want: []map[string]interface{}{
{
"@level": "info",
"@message": "Writing state to file: errored_test.tfstate",
"@module": string("tofu.ui"),
},
},
},
"state_foo_bar_json": {
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
state.SetResourceInstanceDeposed(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test",
Name: "bar",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
"0fcb640a",
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
})
}),
stderr: "",
want: []map[string]interface{}{
{
"@level": "info",
"@message": "Writing state to file: errored_test.tfstate",
"@module": "tofu.ui",
},
},
},
"state_null_resource_with_errors": {
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
state.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
Dependencies: []addrs.ConfigResource{
{
Module: []string{},
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "null_resource",
Name: "failing_will_depend_on_me",
},
},
},
CreateBeforeDestroy: false,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("null"),
})
}),
stderr: "",
want: []map[string]interface{}{
{
"@level": "info",
"@message": "Writing state to file: errored_test.tfstate",
"@module": "tofu.ui",
},
},
},
}
// Run tests for Human view
runTestSaveErroredStateFile(t, tcsHuman, arguments.ViewHuman)
// Run tests for JSON view
runTestSaveErroredStateFile(t, tcsJson, arguments.ViewJSON)
}
func runTestSaveErroredStateFile(t *testing.T, tc map[string]struct {
state *states.State
run *moduletest.Run
file *moduletest.File
stderr string
want interface{}
}, viewType arguments.ViewType) {
for name, data := range tc {
t.Run(name, func(t *testing.T) {
// Create a temporary directory
tempDir := t.TempDir()
// Modify the state file path to use the temporary directory
tempStateFilePath := filepath.Clean(filepath.Join(tempDir, "errored_test.tfstate"))
// Get the current working directory
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Error getting current working directory: %v", err)
}
// Change the working directory to the temporary directory
if err := os.Chdir(tempDir); err != nil {
t.Fatalf("Error changing working directory: %v", err)
}
defer func() {
// Change the working directory back to the original directory after the test
if err := os.Chdir(originalDir); err != nil {
t.Fatalf("Error changing working directory back: %v", err)
}
}()
streams, done := terminal.StreamsForTesting(t)
if viewType == arguments.ViewHuman {
view := NewTest(arguments.ViewHuman, NewView(streams))
SaveErroredTestStateFile(data.state, data.run, data.file, view)
output := done(t)
actual, expected := output.Stderr(), data.stderr
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
} else if viewType == arguments.ViewJSON {
view := NewTest(arguments.ViewJSON, NewView(streams))
SaveErroredTestStateFile(data.state, data.run, data.file, view)
want, ok := data.want.([]map[string]interface{})
if !ok {
t.Fatalf("Failed to assert want as []map[string]interface{}")
}
testJSONViewOutputEquals(t, done(t).All(), want)
} else {
t.Fatalf("Unsupported view type: %v", viewType)
}
// Check if the state file exists
if _, err := os.Stat(tempStateFilePath); os.IsNotExist(err) {
// File does not exist
t.Errorf("Expected state file 'errored_test.tfstate' to exist in: %s, but it does not.", tempDir)
}
})
}
}
func dynamicValue(t *testing.T, value cty.Value, typ cty.Type) plans.DynamicValue { func dynamicValue(t *testing.T, value cty.Value, typ cty.Type) plans.DynamicValue {
d, err := plans.NewDynamicValue(value, typ) d, err := plans.NewDynamicValue(value, typ)
if err != nil { if err != nil {