Files
opentf/internal/command/views/test_test.go
Martin Atkins a800d250e5 command: "go fix" on various files we've changed recently anyway
We don't typically just broadly run automatic rewriting tools like "go fix"
across our codebase because that tends to cause annoying and unnecessary
merge conflicts when we're backporting to earlier release branches.

But all of the files in this commit were changed in some non-trivial way
already during the OpenTofu v1.11 development period anyway, and so the
likelyhood we'd be able to successfully backport from them is reduced and
therefore this seems like a good opportunity to do some focused
modernization using "go fix".

My rules for what to include or not are admittedly quite "vibes-based", but
the general idea was:

 - Focusing on files under the "command" directory only, because that's
   already been an area of intentional refactoring during this development
   period.
 - If the existing diff in a file is already significantly larger than
   the changes the fixer proposed to make, or if the fixer is proposing
   to change a line that was already changed in this development period.
 - More willing to include "_test.go" files than non-test files, even if
   they hadn't changed as much already, just because backports from test
   files for bug fixes tend to be entirely new test cases more than they
   are modifications to existing test cases, and so the risk of conflicts
   is lower there.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-03-17 15:25:30 -07:00

3572 lines
94 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 views
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/moduletest"
"github.com/opentofu/opentofu/internal/plans"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/terminal"
"github.com/opentofu/opentofu/internal/tfdiags"
)
func TestTestHuman_Conclusion(t *testing.T) {
tcs := map[string]struct {
Suite *moduletest.Suite
Expected string
}{
"no tests": {
Suite: &moduletest.Suite{},
Expected: "\nExecuted 0 tests.\n",
},
"only skipped tests": {
Suite: &moduletest.Suite{
Status: moduletest.Skip,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Skip,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Skip,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
},
},
Expected: "\nExecuted 0 tests, 6 skipped.\n",
},
"only passed tests": {
Suite: &moduletest.Suite{
Status: moduletest.Pass,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
},
},
Expected: "\nSuccess! 6 passed, 0 failed.\n",
},
"passed and skipped tests": {
Suite: &moduletest.Suite{
Status: moduletest.Pass,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
},
},
Expected: "\nSuccess! 4 passed, 0 failed, 2 skipped.\n",
},
"only failed tests": {
Suite: &moduletest.Suite{
Status: moduletest.Fail,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
},
},
Expected: "\nFailure! 0 passed, 6 failed.\n",
},
"failed and skipped tests": {
Suite: &moduletest.Suite{
Status: moduletest.Fail,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
},
},
Expected: "\nFailure! 0 passed, 4 failed, 2 skipped.\n",
},
"failed, passed and skipped tests": {
Suite: &moduletest.Suite{
Status: moduletest.Fail,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
},
},
Expected: "\nFailure! 2 passed, 2 failed, 2 skipped.\n",
},
"failed and errored tests": {
Suite: &moduletest.Suite{
Status: moduletest.Error,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Error,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Error,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Error,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Error,
},
{
Name: "test_three",
Status: moduletest.Error,
},
},
},
},
},
Expected: "\nFailure! 0 passed, 6 failed.\n",
},
"failed, errored, passed, and skipped tests": {
Suite: &moduletest.Suite{
Status: moduletest.Error,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Error,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Error,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
},
},
Expected: "\nFailure! 2 passed, 2 failed, 2 skipped.\n",
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewHuman}, NewView(streams))
view.Conclusion(tc.Suite)
actual := done(t).Stdout()
expected := tc.Expected
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Fatalf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
})
}
}
func TestTestHuman_File(t *testing.T) {
tcs := map[string]struct {
File *moduletest.File
Expected string
}{
"pass": {
File: &moduletest.File{Name: "main.tf", Status: moduletest.Pass},
Expected: "main.tf... pass\n",
},
"pending": {
File: &moduletest.File{Name: "main.tf", Status: moduletest.Pending},
Expected: "main.tf... pending\n",
},
"skip": {
File: &moduletest.File{Name: "main.tf", Status: moduletest.Skip},
Expected: "main.tf... skip\n",
},
"fail": {
File: &moduletest.File{Name: "main.tf", Status: moduletest.Fail},
Expected: "main.tf... fail\n",
},
"error": {
File: &moduletest.File{Name: "main.tf", Status: moduletest.Error},
Expected: "main.tf... fail\n",
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewHuman}, NewView(streams))
view.File(tc.File)
actual := done(t).Stdout()
expected := tc.Expected
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Fatalf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
})
}
}
func TestTestHuman_Run(t *testing.T) {
tcs := map[string]struct {
Run *moduletest.Run
StdOut string
StdErr string
}{
"pass": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
StdOut: " run \"run_block\"... pass\n",
},
"pass_with_diags": {
Run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Pass,
Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Warning, "a warning occurred", "some warning happened during this test")},
},
StdOut: ` run "run_block"... pass
Warning: a warning occurred
some warning happened during this test
`,
},
"pending": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pending},
StdOut: " run \"run_block\"... pending\n",
},
"skip": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Skip},
StdOut: " run \"run_block\"... skip\n",
},
"fail": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Fail},
StdOut: " run \"run_block\"... fail\n",
},
"fail_with_diags": {
Run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Fail,
Diagnostics: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Error, "a comparison failed", "details details details"),
tfdiags.Sourceless(tfdiags.Error, "a second comparison failed", "other details"),
},
},
StdOut: " run \"run_block\"... fail\n",
StdErr: `
Error: a comparison failed
details details details
Error: a second comparison failed
other details
`,
},
"error": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Error},
StdOut: " run \"run_block\"... fail\n",
},
"error_with_diags": {
Run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Error,
Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "an error occurred", "something bad happened during this test")},
},
StdOut: " run \"run_block\"... fail\n",
StdErr: `
Error: an error occurred
something bad happened during this test
`,
},
"verbose_plan": {
Run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Pass,
Config: &configs.TestRun{
Command: configs.PlanTestCommand,
},
Verbose: &moduletest.Verbose{
Plan: &plans.Plan{
Changes: &plans.Changes{
Resources: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "creating",
},
},
},
PrevRunAddr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "creating",
},
},
},
ProviderAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
After: dynamicValue(
t,
cty.ObjectVal(map[string]cty.Value{
"value": cty.StringVal("Hello, world!"),
}),
cty.Object(map[string]cty.Type{
"value": cty.String,
})),
},
},
},
},
},
State: states.NewState(), // empty state
Config: &configs.Config{},
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
}: {
ResourceTypes: map[string]providers.Schema{
"test_resource": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
},
},
},
},
},
},
},
},
},
StdOut: ` run "run_block"... pass
OpenTofu used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
OpenTofu will perform the following actions:
# test_resource.creating will be created
+ resource "test_resource" "creating" {
+ value = "Hello, world!"
}
Plan: 1 to add, 0 to change, 0 to destroy.
`,
},
"verbose_apply": {
Run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Pass,
Config: &configs.TestRun{
Command: configs.ApplyTestCommand,
},
Verbose: &moduletest.Verbose{
Plan: &plans.Plan{}, // empty plan
State: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "creating",
},
},
},
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"value":"foobar"}`),
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
},
}, addrs.NoKey)
}),
Config: &configs.Config{},
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
}: {
ResourceTypes: map[string]providers.Schema{
"test_resource": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
},
},
},
},
},
},
},
},
},
StdOut: ` run "run_block"... pass
# test_resource.creating:
resource "test_resource" "creating" {
value = "foobar"
}
`,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
file := &moduletest.File{
Name: "main.tftest.hcl",
}
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewHuman}, NewView(streams))
view.Run(tc.Run, file)
output := done(t)
actual, expected := output.Stdout(), tc.StdOut
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
actual, expected = output.Stderr(), tc.StdErr
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
})
}
}
func TestTestHuman_DestroySummary(t *testing.T) {
tcs := map[string]struct {
diags tfdiags.Diagnostics
run *moduletest.Run
file *moduletest.File
state *states.State
stdout string
stderr string
}{
"empty": {
diags: nil,
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.NewState(),
},
"empty_state_only_warnings": {
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"),
},
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.NewState(),
stdout: `
Warning: first warning
some thing not very bad happened
Warning: second warning
some thing not very bad happened again
`,
},
"empty_state_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.NewState(),
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
`,
},
"error_from_run": {
diags: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Error, "first error", "this time it is very bad"),
},
run: &moduletest.Run{Name: "run_block"},
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.NewState(),
stderr: `OpenTofu encountered an error destroying resources created while executing
main.tftest.hcl/run_block.
Error: first error
this time it is very bad
`,
},
"state_only_warnings": {
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"),
},
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
stdout: `
Warning: first warning
some thing not very bad happened
Warning: second warning
some thing not very bad happened again
`,
stderr: `
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:
- test.bar
- test.bar (0fcb640a)
- test.foo
`,
},
"state_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: "test",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
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:
- test.bar
- test.bar (0fcb640a)
- 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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
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
`,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewHuman}, NewView(streams))
view.DestroySummary(tc.diags, tc.run, tc.file, tc.state)
output := done(t)
actual, expected := output.Stdout(), tc.stdout
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
actual, expected = output.Stderr(), tc.stderr
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
})
}
}
func TestTestHuman_FatalInterruptSummary(t *testing.T) {
tcs := map[string]struct {
states map[*moduletest.Run]*states.State
run *moduletest.Run
created []*plans.ResourceInstanceChangeSrc
want string
}{
"no_state_only_plan": {
states: make(map[*moduletest.Run]*states.State),
run: &moduletest.Run{
Config: &configs.TestRun{},
Name: "run_block",
},
created: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
},
want: `
OpenTofu was interrupted while executing main.tftest.hcl, and may not have
performed the expected cleanup operations.
OpenTofu was in the process of creating the following resources for
"run_block" from the module under test, and they may not have been destroyed:
- test_instance.one
- test_instance.two
`,
},
"file_state_no_plan": {
states: map[*moduletest.Run]*states.State{
nil: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
},
created: nil,
want: `
OpenTofu was interrupted while executing main.tftest.hcl, and may not have
performed the expected cleanup operations.
OpenTofu has already created the following resources from the module under
test:
- test_instance.one
- test_instance.two
`,
},
"run_states_no_plan": {
states: map[*moduletest.Run]*states.State{
&moduletest.Run{
Name: "setup_block",
Config: &configs.TestRun{
Module: &configs.TestRunModuleCall{
Source: addrs.ModuleSourceLocal("../setup"),
},
},
}: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
},
created: nil,
want: `
OpenTofu was interrupted while executing main.tftest.hcl, and may not have
performed the expected cleanup operations.
OpenTofu has already created the following resources for "setup_block" from
"../setup":
- test_instance.one
- test_instance.two
`,
},
"all_states_with_plan": {
states: map[*moduletest.Run]*states.State{
&moduletest.Run{
Name: "setup_block",
Config: &configs.TestRun{
Module: &configs.TestRunModuleCall{
Source: addrs.ModuleSourceLocal("../setup"),
},
},
}: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "setup_one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "setup_two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
nil: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
},
created: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "new_one",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "new_two",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
},
run: &moduletest.Run{
Config: &configs.TestRun{},
Name: "run_block",
},
want: `
OpenTofu was interrupted while executing main.tftest.hcl, and may not have
performed the expected cleanup operations.
OpenTofu has already created the following resources from the module under
test:
- test_instance.one
- test_instance.two
OpenTofu has already created the following resources for "setup_block" from
"../setup":
- test_instance.setup_one
- test_instance.setup_two
OpenTofu was in the process of creating the following resources for
"run_block" from the module under test, and they may not have been destroyed:
- test_instance.new_one
- test_instance.new_two
`,
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewHuman}, NewView(streams))
file := &moduletest.File{
Name: "main.tftest.hcl",
Runs: func() []*moduletest.Run {
var runs []*moduletest.Run
for run := range tc.states {
if run != nil {
runs = append(runs, run)
}
}
return runs
}(),
}
view.FatalInterruptSummary(tc.run, file, tc.states, tc.created)
actual, expected := done(t).Stderr(), tc.want
if diff := cmp.Diff(expected, actual); len(diff) > 0 {
t.Errorf("expected:\n%s\nactual:\n%s\ndiff:\n%s", expected, actual, diff)
}
})
}
}
func TestTestJSON_Abstract(t *testing.T) {
tcs := map[string]struct {
suite *moduletest.Suite
want []map[string]any
}{
"single": {
suite: &moduletest.Suite{
Files: map[string]*moduletest.File{
"main.tftest.hcl": {
Runs: []*moduletest.Run{
{
Name: "setup",
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Found 1 file and 1 run block",
"@module": "tofu.ui",
"test_abstract": map[string]any{
"main.tftest.hcl": []any{
"setup",
},
},
"type": "test_abstract",
},
},
},
"plural": {
suite: &moduletest.Suite{
Files: map[string]*moduletest.File{
"main.tftest.hcl": {
Runs: []*moduletest.Run{
{
Name: "setup",
},
{
Name: "test",
},
},
},
"other.tftest.hcl": {
Runs: []*moduletest.Run{
{
Name: "test",
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Found 2 files and 3 run blocks",
"@module": "tofu.ui",
"test_abstract": map[string]any{
"main.tftest.hcl": []any{
"setup",
"test",
},
"other.tftest.hcl": []any{
"test",
},
},
"type": "test_abstract",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
view.Abstract(tc.suite)
testJSONViewOutputEquals(t, done(t).All(), tc.want)
})
}
}
func TestTestJSON_Conclusion(t *testing.T) {
tcs := map[string]struct {
suite *moduletest.Suite
want []map[string]any
}{
"no tests": {
suite: &moduletest.Suite{},
want: []map[string]any{
{
"@level": "info",
"@message": "Executed 0 tests.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "pending",
"errored": 0.0,
"failed": 0.0,
"passed": 0.0,
"skipped": 0.0,
},
"type": "test_summary",
},
},
},
"only skipped tests": {
suite: &moduletest.Suite{
Status: moduletest.Skip,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Skip,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Skip,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Executed 0 tests, 6 skipped.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "skip",
"errored": 0.0,
"failed": 0.0,
"passed": 0.0,
"skipped": 6.0,
},
"type": "test_summary",
},
},
},
"only passed tests": {
suite: &moduletest.Suite{
Status: moduletest.Pass,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Success! 6 passed, 0 failed.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "pass",
"errored": 0.0,
"failed": 0.0,
"passed": 6.0,
"skipped": 0.0,
},
"type": "test_summary",
},
},
},
"passed and skipped tests": {
suite: &moduletest.Suite{
Status: moduletest.Pass,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Pass,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Success! 4 passed, 0 failed, 2 skipped.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "pass",
"errored": 0.0,
"failed": 0.0,
"passed": 4.0,
"skipped": 2.0,
},
"type": "test_summary",
},
},
},
"only failed tests": {
suite: &moduletest.Suite{
Status: moduletest.Fail,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Failure! 0 passed, 6 failed.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "fail",
"errored": 0.0,
"failed": 6.0,
"passed": 0.0,
"skipped": 0.0,
},
"type": "test_summary",
},
},
},
"failed and skipped tests": {
suite: &moduletest.Suite{
Status: moduletest.Fail,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Failure! 0 passed, 4 failed, 2 skipped.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "fail",
"errored": 0.0,
"failed": 4.0,
"passed": 0.0,
"skipped": 2.0,
},
"type": "test_summary",
},
},
},
"failed, passed and skipped tests": {
suite: &moduletest.Suite{
Status: moduletest.Fail,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Skip,
},
{
Name: "test_two",
Status: moduletest.Fail,
},
{
Name: "test_three",
Status: moduletest.Pass,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Failure! 2 passed, 2 failed, 2 skipped.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "fail",
"errored": 0.0,
"failed": 2.0,
"passed": 2.0,
"skipped": 2.0,
},
"type": "test_summary",
},
},
},
"failed and errored tests": {
suite: &moduletest.Suite{
Status: moduletest.Error,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Error,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Error,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Error,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Fail,
},
{
Name: "test_two",
Status: moduletest.Error,
},
{
Name: "test_three",
Status: moduletest.Error,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Failure! 0 passed, 6 failed.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "error",
"errored": 3.0,
"failed": 3.0,
"passed": 0.0,
"skipped": 0.0,
},
"type": "test_summary",
},
},
},
"failed, errored, passed, and skipped tests": {
suite: &moduletest.Suite{
Status: moduletest.Error,
Files: map[string]*moduletest.File{
"descriptive_test_name.tftest.hcl": {
Name: "descriptive_test_name.tftest.hcl",
Status: moduletest.Fail,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Pass,
},
{
Name: "test_two",
Status: moduletest.Pass,
},
{
Name: "test_three",
Status: moduletest.Fail,
},
},
},
"other_descriptive_test_name.tftest.hcl": {
Name: "other_descriptive_test_name.tftest.hcl",
Status: moduletest.Error,
Runs: []*moduletest.Run{
{
Name: "test_one",
Status: moduletest.Error,
},
{
Name: "test_two",
Status: moduletest.Skip,
},
{
Name: "test_three",
Status: moduletest.Skip,
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": "Failure! 2 passed, 2 failed, 2 skipped.",
"@module": "tofu.ui",
"test_summary": map[string]any{
"status": "error",
"errored": 1.0,
"failed": 1.0,
"passed": 2.0,
"skipped": 2.0,
},
"type": "test_summary",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
view.Conclusion(tc.suite)
testJSONViewOutputEquals(t, done(t).All(), tc.want)
})
}
}
func TestTestJSON_DestroySummary(t *testing.T) {
tcs := map[string]struct {
file *moduletest.File
run *moduletest.Run
state *states.State
diags tfdiags.Diagnostics
want []map[string]any
}{
"empty_state_only_warnings": {
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"),
},
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.NewState(),
want: []map[string]any{
{
"@level": "warn",
"@message": "Warning: first warning",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]any{
"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]any{
"detail": "something not very bad happened again",
"severity": "warning",
"summary": "second warning",
},
"type": "diagnostic",
},
},
},
"empty_state_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.NewState(),
want: []map[string]any{
{
"@level": "warn",
"@message": "Warning: first warning",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]any{
"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]any{
"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]any{
"detail": "this time it is very bad",
"severity": "error",
"summary": "first error",
},
"type": "diagnostic",
},
},
},
"state_from_run": {
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"),
}, addrs.NoKey)
}),
want: []map[string]any{
{
"@level": "error",
"@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",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_cleanup": map[string]any{
"failed_resources": []any{
map[string]any{
"instance": "test.foo",
},
},
},
"type": "test_cleanup",
},
},
},
"state_only_warnings": {
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"),
},
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
want: []map[string]any{
{
"@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]any{
"failed_resources": []any{
map[string]any{
"instance": "test.bar",
},
map[string]any{
"instance": "test.bar",
"deposed_key": "0fcb640a",
},
map[string]any{
"instance": "test.foo",
},
},
},
"type": "test_cleanup",
},
{
"@level": "warn",
"@message": "Warning: first warning",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]any{
"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]any{
"detail": "something not very bad happened again",
"severity": "warning",
"summary": "second warning",
},
"type": "diagnostic",
},
},
},
"state_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: "test",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.NewDefaultProvider("test"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
want: []map[string]any{
{
"@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]any{
"failed_resources": []any{
map[string]any{
"instance": "test.bar",
},
map[string]any{
"instance": "test.bar",
"deposed_key": "0fcb640a",
},
map[string]any{
"instance": "test.foo",
},
},
},
"type": "test_cleanup",
},
{
"@level": "warn",
"@message": "Warning: first warning",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"diagnostic": map[string]any{
"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]any{
"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]any{
"detail": "this time it is very bad",
"severity": "error",
"summary": "first error",
},
"type": "diagnostic",
},
},
},
"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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}), want: []map[string]any{
{
"@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]any{
"failed_resources": []any{
map[string]any{
"instance": "null_resource.failing",
},
map[string]any{
"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]any{
"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]any{
"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]any{
"detail": "this time it is very bad",
"severity": "error",
"summary": "first error",
},
"type": "diagnostic",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
view.DestroySummary(tc.diags, tc.run, tc.file, tc.state)
testJSONViewOutputEquals(t, done(t).All(), tc.want)
})
}
}
func TestTestJSON_File(t *testing.T) {
tcs := map[string]struct {
file *moduletest.File
want []map[string]any
}{
"pass": {
file: &moduletest.File{Name: "main.tf", Status: moduletest.Pass},
want: []map[string]any{
{
"@level": "info",
"@message": "main.tf... pass",
"@module": "tofu.ui",
"@testfile": "main.tf",
"test_file": map[string]any{
"path": "main.tf",
"status": "pass",
},
"type": "test_file",
},
},
},
"pending": {
file: &moduletest.File{Name: "main.tf", Status: moduletest.Pending},
want: []map[string]any{
{
"@level": "info",
"@message": "main.tf... pending",
"@module": "tofu.ui",
"@testfile": "main.tf",
"test_file": map[string]any{
"path": "main.tf",
"status": "pending",
},
"type": "test_file",
},
},
},
"skip": {
file: &moduletest.File{Name: "main.tf", Status: moduletest.Skip},
want: []map[string]any{
{
"@level": "info",
"@message": "main.tf... skip",
"@module": "tofu.ui",
"@testfile": "main.tf",
"test_file": map[string]any{
"path": "main.tf",
"status": "skip",
},
"type": "test_file",
},
},
},
"fail": {
file: &moduletest.File{Name: "main.tf", Status: moduletest.Fail},
want: []map[string]any{
{
"@level": "info",
"@message": "main.tf... fail",
"@module": "tofu.ui",
"@testfile": "main.tf",
"test_file": map[string]any{
"path": "main.tf",
"status": "fail",
},
"type": "test_file",
},
},
},
"error": {
file: &moduletest.File{Name: "main.tf", Status: moduletest.Error},
want: []map[string]any{
{
"@level": "info",
"@message": "main.tf... fail",
"@module": "tofu.ui",
"@testfile": "main.tf",
"test_file": map[string]any{
"path": "main.tf",
"status": "error",
},
"type": "test_file",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
view.File(tc.file)
testJSONViewOutputEquals(t, done(t).All(), tc.want)
})
}
}
func TestTestJSON_Run(t *testing.T) {
tcs := map[string]struct {
run *moduletest.Run
want []map[string]any
}{
"pass": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... pass",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "pass",
},
"type": "test_run",
},
},
},
"pass_with_diags": {
run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Pass,
Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Warning, "a warning occurred", "some warning happened during this test")},
},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... pass",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "pass",
},
"type": "test_run",
},
{
"@level": "warn",
"@message": "Warning: a warning occurred",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"diagnostic": map[string]any{
"detail": "some warning happened during this test",
"severity": "warning",
"summary": "a warning occurred",
},
"type": "diagnostic",
},
},
},
"pending": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pending},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... pending",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "pending",
},
"type": "test_run",
},
},
},
"skip": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Skip},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... skip",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "skip",
},
"type": "test_run",
},
},
},
"fail": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Fail},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... fail",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "fail",
},
"type": "test_run",
},
},
},
"fail_with_diags": {
run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Fail,
Diagnostics: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Error, "a comparison failed", "details details details"),
tfdiags.Sourceless(tfdiags.Error, "a second comparison failed", "other details"),
},
},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... fail",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "fail",
},
"type": "test_run",
},
{
"@level": "error",
"@message": "Error: a comparison failed",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"diagnostic": map[string]any{
"detail": "details details details",
"severity": "error",
"summary": "a comparison failed",
},
"type": "diagnostic",
},
{
"@level": "error",
"@message": "Error: a second comparison failed",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"diagnostic": map[string]any{
"detail": "other details",
"severity": "error",
"summary": "a second comparison failed",
},
"type": "diagnostic",
},
},
},
"error": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Error},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... fail",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "error",
},
"type": "test_run",
},
},
},
"error_with_diags": {
run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Error,
Diagnostics: tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "an error occurred", "something bad happened during this test")},
},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... fail",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "error",
},
"type": "test_run",
},
{
"@level": "error",
"@message": "Error: an error occurred",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"diagnostic": map[string]any{
"detail": "something bad happened during this test",
"severity": "error",
"summary": "an error occurred",
},
"type": "diagnostic",
},
},
},
"verbose_plan": {
run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Pass,
Config: &configs.TestRun{
Command: configs.PlanTestCommand,
},
Verbose: &moduletest.Verbose{
Plan: &plans.Plan{
Changes: &plans.Changes{
Resources: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "creating",
},
},
},
PrevRunAddr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "creating",
},
},
},
ProviderAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
After: dynamicValue(
t,
cty.ObjectVal(map[string]cty.Value{
"value": cty.StringVal("foobar"),
}),
cty.Object(map[string]cty.Type{
"value": cty.String,
})),
},
},
},
},
},
State: states.NewState(), // empty state
Config: &configs.Config{
Module: &configs.Module{
ProviderRequirements: &configs.RequiredProviders{},
},
},
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
}: {
ResourceTypes: map[string]providers.Schema{
"test_resource": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
},
},
},
},
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... pass",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "pass",
},
"type": "test_run",
},
{
"@level": "info",
"@message": "-verbose flag enabled, printing plan",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_plan": map[string]any{
"configuration": map[string]any{
"root_module": map[string]any{},
},
"errored": false,
"planned_values": map[string]any{
"root_module": map[string]any{
"resources": []any{
map[string]any{
"address": "test_resource.creating",
"mode": "managed",
"name": "creating",
"provider_name": "registry.opentofu.org/hashicorp/test",
"schema_version": 0.0,
"sensitive_values": map[string]any{},
"type": "test_resource",
"values": map[string]any{
"value": "foobar",
},
},
},
},
},
"resource_changes": []any{
map[string]any{
"address": "test_resource.creating",
"change": map[string]any{
"actions": []any{"create"},
"after": map[string]any{
"value": "foobar",
},
"after_sensitive": map[string]any{},
"after_unknown": map[string]any{},
"before": nil,
"before_sensitive": false,
},
"mode": "managed",
"name": "creating",
"provider_name": "registry.opentofu.org/hashicorp/test",
"type": "test_resource",
},
},
},
"type": "test_plan",
},
},
},
"verbose_apply": {
run: &moduletest.Run{
Name: "run_block",
Status: moduletest.Pass,
Config: &configs.TestRun{
Command: configs.ApplyTestCommand,
},
Verbose: &moduletest.Verbose{
Plan: &plans.Plan{}, // empty plan
State: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "creating",
},
},
},
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"value":"foobar"}`),
},
addrs.AbsProviderConfig{
Module: addrs.RootModule,
Provider: addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
},
}, addrs.NoKey)
}),
Config: &configs.Config{
Module: &configs.Module{},
},
Providers: map[addrs.Provider]providers.ProviderSchema{
addrs.Provider{
Hostname: addrs.DefaultProviderRegistryHost,
Namespace: "hashicorp",
Type: "test",
}: {
ResourceTypes: map[string]providers.Schema{
"test_resource": {
Block: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
},
},
},
},
},
},
},
},
},
want: []map[string]any{
{
"@level": "info",
"@message": " \"run_block\"... pass",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_run": map[string]any{
"path": "main.tftest.hcl",
"run": "run_block",
"status": "pass",
},
"type": "test_run",
},
{
"@level": "info",
"@message": "-verbose flag enabled, printing state",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"@testrun": "run_block",
"test_state": map[string]any{
"values": map[string]any{
"root_module": map[string]any{
"resources": []any{
map[string]any{
"address": "test_resource.creating",
"mode": "managed",
"name": "creating",
"provider_name": "registry.opentofu.org/hashicorp/test",
"schema_version": 0.0,
"sensitive_values": map[string]any{},
"type": "test_resource",
"values": map[string]any{
"value": "foobar",
},
},
},
},
},
},
"type": "test_state",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
file := &moduletest.File{Name: "main.tftest.hcl"}
view.Run(tc.run, file)
testJSONViewOutputEquals(t, done(t).All(), tc.want, cmp.FilterPath(func(path cmp.Path) bool {
return strings.Contains(path.Last().String(), "version") || strings.Contains(path.Last().String(), "timestamp")
}, cmp.Ignore()))
})
}
}
func TestTestJSON_FatalInterruptSummary(t *testing.T) {
tcs := map[string]struct {
states map[*moduletest.Run]*states.State
changes []*plans.ResourceInstanceChangeSrc
want []map[string]any
}{
"no_state_only_plan": {
states: make(map[*moduletest.Run]*states.State),
changes: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
},
want: []map[string]any{
{
"@level": "error",
"@message": "OpenTofu was interrupted during test execution, and may not have performed the expected cleanup operations.",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"test_interrupt": map[string]any{
"planned": []any{
"test_instance.one",
"test_instance.two",
},
},
"type": "test_interrupt",
},
},
},
"file_state_no_plan": {
states: map[*moduletest.Run]*states.State{
nil: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
},
changes: nil,
want: []map[string]any{
{
"@level": "error",
"@message": "OpenTofu was interrupted during test execution, and may not have performed the expected cleanup operations.",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"test_interrupt": map[string]any{
"state": []any{
map[string]any{
"instance": "test_instance.one",
},
map[string]any{
"instance": "test_instance.two",
},
},
},
"type": "test_interrupt",
},
},
},
"run_states_no_plan": {
states: map[*moduletest.Run]*states.State{
&moduletest.Run{Name: "setup_block"}: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
},
changes: nil,
want: []map[string]any{
{
"@level": "error",
"@message": "OpenTofu was interrupted during test execution, and may not have performed the expected cleanup operations.",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"test_interrupt": map[string]any{
"states": map[string]any{
"setup_block": []any{
map[string]any{
"instance": "test_instance.one",
},
map[string]any{
"instance": "test_instance.two",
},
},
},
},
"type": "test_interrupt",
},
},
},
"all_states_with_plan": {
states: map[*moduletest.Run]*states.State{
&moduletest.Run{Name: "setup_block"}: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "setup_one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "setup_two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
nil: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "one",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
state.SetResourceInstanceCurrent(
addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "two",
},
},
},
&states.ResourceInstanceObjectSrc{},
addrs.AbsProviderConfig{}, addrs.NoKey)
}),
},
changes: []*plans.ResourceInstanceChangeSrc{
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "new_one",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
{
Addr: addrs.AbsResourceInstance{
Module: addrs.RootModuleInstance,
Resource: addrs.ResourceInstance{
Resource: addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "new_two",
},
},
},
ChangeSrc: plans.ChangeSrc{
Action: plans.Create,
},
},
},
want: []map[string]any{
{
"@level": "error",
"@message": "OpenTofu was interrupted during test execution, and may not have performed the expected cleanup operations.",
"@module": "tofu.ui",
"@testfile": "main.tftest.hcl",
"test_interrupt": map[string]any{
"state": []any{
map[string]any{
"instance": "test_instance.one",
},
map[string]any{
"instance": "test_instance.two",
},
},
"states": map[string]any{
"setup_block": []any{
map[string]any{
"instance": "test_instance.setup_one",
},
map[string]any{
"instance": "test_instance.setup_two",
},
},
},
"planned": []any{
"test_instance.new_one",
"test_instance.new_two",
},
},
"type": "test_interrupt",
},
},
},
}
for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
streams, done := terminal.StreamsForTesting(t)
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
file := &moduletest.File{Name: "main.tftest.hcl"}
run := &moduletest.Run{Name: "run_block"}
view.FatalInterruptSummary(run, file, tc.states, tc.changes)
testJSONViewOutputEquals(t, done(t).All(), tc.want)
})
}
}
func TestSaveErroredStateFile(t *testing.T) {
tcsHuman := map[string]struct {
state *states.State
run *moduletest.Run
file *moduletest.File
stderr string
want any
}{
"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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
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 any
}{
"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"),
}, addrs.NoKey)
}),
stderr: "",
want: []map[string]any{
{
"@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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
stderr: "",
want: []map[string]any{
{
"@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"),
}, addrs.NoKey)
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"),
}, addrs.NoKey)
}),
stderr: "",
want: []map[string]any{
{
"@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 any
}, 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"))
// Change the working directory to the temporary directory
t.Chdir(tempDir)
streams, done := terminal.StreamsForTesting(t)
switch viewType {
case arguments.ViewHuman:
view := NewTest(arguments.ViewOptions{ViewType: 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)
}
case arguments.ViewJSON:
view := NewTest(arguments.ViewOptions{ViewType: arguments.ViewJSON}, NewView(streams))
SaveErroredTestStateFile(data.state, data.run, data.file, view)
want, ok := data.want.([]map[string]any)
if !ok {
t.Fatalf("Failed to assert want as []map[string]interface{}")
}
testJSONViewOutputEquals(t, done(t).All(), want)
default:
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)
}
// Trigger garbage collection to ensure that all open file handles are closed.
// This prevents TempDir RemoveAll cleanup errors on Windows.
if runtime.GOOS == "windows" {
runtime.GC()
}
})
}
}
func dynamicValue(t *testing.T, value cty.Value, typ cty.Type) plans.DynamicValue {
d, err := plans.NewDynamicValue(value, typ)
if err != nil {
t.Fatalf("failed to create dynamic value: %s", err)
}
return d
}