| // 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.View{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.View{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.View{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.View{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.View{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.View{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.View{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.View{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.View{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.View{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.View{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.View{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.View{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 | |
| } |