mirror of
https://github.com/opentofu/opentofu.git
synced 2026-06-11 09:31:23 -04:00
Some checks are pending
build / Build for freebsd_386 (push) Waiting to run
build / Build for linux_386 (push) Waiting to run
build / Build for openbsd_386 (push) Waiting to run
build / Build for windows_386 (push) Waiting to run
build / Build for freebsd_amd64 (push) Waiting to run
build / Build for linux_amd64 (push) Waiting to run
build / Build for openbsd_amd64 (push) Waiting to run
build / Build for solaris_amd64 (push) Waiting to run
build / Build for windows_amd64 (push) Waiting to run
build / Build for freebsd_arm (push) Waiting to run
build / Build for linux_arm (push) Waiting to run
build / Build for linux_arm64 (push) Waiting to run
build / Build for darwin_amd64 (push) Waiting to run
build / Build for darwin_arm64 (push) Waiting to run
build / End-to-end Tests for linux_386 (push) Waiting to run
build / End-to-end Tests for windows_386 (push) Waiting to run
build / End-to-end Tests for darwin_amd64 (push) Waiting to run
build / End-to-end Tests for linux_amd64 (push) Waiting to run
build / End-to-end Tests for windows_amd64 (push) Waiting to run
Quick Checks / List files changed for pull request (push) Waiting to run
Quick Checks / Unit tests for linux_386 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for windows_amd64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm (push) Blocked by required conditions
Quick Checks / Unit tests for darwin_arm64 (push) Blocked by required conditions
Quick Checks / Unit tests for linux_arm64 (push) Blocked by required conditions
Quick Checks / Race Tests (push) Blocked by required conditions
Quick Checks / End-to-end Tests (push) Blocked by required conditions
Quick Checks / Code Consistency Checks (push) Blocked by required conditions
Quick Checks / License Checks (push) Waiting to run
Website checks / List files changed for pull request (push) Waiting to run
Website checks / Build (push) Blocked by required conditions
Website checks / Test Installation Instructions (push) Blocked by required conditions
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>
3571 lines
94 KiB
Go
3571 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
|
|
}
|