mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-05-17 01:03:30 -04:00
The "terraform_version" property of the JSON state format (intentionally misnamed for backward-compatibility with our predecessor) changes each time we make a new release, so we'll compare it with the currently-expected version number instead of with a hard-coded value. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
715 lines
21 KiB
Go
715 lines
21 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package views
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
regaddr "github.com/opentofu/registry-address/v2"
|
|
"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/configschema"
|
|
"github.com/opentofu/opentofu/internal/providers"
|
|
"github.com/opentofu/opentofu/internal/states"
|
|
"github.com/opentofu/opentofu/internal/states/statefile"
|
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
"github.com/opentofu/opentofu/internal/tofu"
|
|
"github.com/opentofu/opentofu/version"
|
|
)
|
|
|
|
func TestStateViews(t *testing.T) {
|
|
tests := map[string]struct {
|
|
viewCall func(state State)
|
|
wantJson []map[string]any
|
|
wantStdout string
|
|
wantStderr string
|
|
ignoreTimestamp bool
|
|
}{
|
|
"stateNotFound": {
|
|
viewCall: func(state State) {
|
|
state.StateNotFound()
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: No state file was found",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "State management commands require a state file. Run this command in a directory where OpenTofu has been run or use the -state flag to point the command to a specific state location.",
|
|
"severity": "error",
|
|
"summary": "No state file was found",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: No state file was found
|
|
|
|
State management commands require a state file. Run this command in a
|
|
directory where OpenTofu has been run or use the -state flag to point the
|
|
command to a specific state location.
|
|
`,
|
|
},
|
|
"stateLoadingFailure": {
|
|
viewCall: func(state State) {
|
|
state.StateLoadingFailure("failed to read state file")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": `Error: Error loading the state`,
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "Please ensure that your OpenTofu state exists and that you've configured it properly. You can use the \"-state\" flag to point OpenTofu at another state file.\n\nCause: failed to read state file",
|
|
"severity": "error",
|
|
"summary": "Error loading the state",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: Error loading the state
|
|
|
|
Please ensure that your OpenTofu state exists and that you've configured it
|
|
properly. You can use the "-state" flag to point OpenTofu at another state
|
|
file.
|
|
|
|
Cause: failed to read state file
|
|
`,
|
|
},
|
|
"stateSavingError": {
|
|
viewCall: func(state State) {
|
|
state.StateSavingError("failed to save state file")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: Error saving the state",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "The state was not saved. No items were removed from the persisted state. No backup was created since no modification occurred. Please resolve the issue above and try again.\n\nCause: failed to save state file",
|
|
"severity": "error",
|
|
"summary": "Error saving the state",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: Error saving the state
|
|
|
|
The state was not saved. No items were removed from the persisted state. No
|
|
backup was created since no modification occurred. Please resolve the issue
|
|
above and try again.
|
|
|
|
Cause: failed to save state file
|
|
`,
|
|
},
|
|
"stateListAddr": {
|
|
viewCall: func(state State) {
|
|
addr, diags := addrs.ParseAbsResourceInstanceStr("null_resource.example[0]")
|
|
if diags.HasErrors() {
|
|
panic(diags.Err())
|
|
}
|
|
state.StateListAddr(addr)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "null_resource.example[0]",
|
|
"@module": "tofu.ui",
|
|
"type": "resource_address",
|
|
},
|
|
},
|
|
wantStdout: withNewline("null_resource.example[0]"),
|
|
},
|
|
"errorMovingToAlreadyExistingDst": {
|
|
viewCall: func(state State) {
|
|
state.ErrorMovingToAlreadyExistingDst()
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: Destination module already exists",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "Please ensure your addresses and state paths are valid. No state was persisted. Your existing states are untouched.",
|
|
"severity": "error",
|
|
"summary": "Destination module already exists",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: Destination module already exists
|
|
|
|
Please ensure your addresses and state paths are valid. No state was
|
|
persisted. Your existing states are untouched.
|
|
`,
|
|
},
|
|
"resourceMoveStatus with dryRun=true": {
|
|
viewCall: func(state State) {
|
|
state.ResourceMoveStatus(true, "test_res.name1", "test_res.name2")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": `Would move "test_res.name1" to "test_res.name2"`,
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`Would move "test_res.name1" to "test_res.name2"`),
|
|
},
|
|
"resourceMoveStatus with dryRun=false": {
|
|
viewCall: func(state State) {
|
|
state.ResourceMoveStatus(false, "test_res.name1", "test_res.name2")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": `Move "test_res.name1" to "test_res.name2"`,
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`Move "test_res.name1" to "test_res.name2"`),
|
|
},
|
|
"dryRunMovedStatus with 0 resources": {
|
|
viewCall: func(state State) {
|
|
state.DryRunMovedStatus(0)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": `Would have moved nothing`,
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`Would have moved nothing.`),
|
|
},
|
|
"dryRunMovedStatus with >0 resources": {
|
|
viewCall: func(state State) {
|
|
state.DryRunMovedStatus(1)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{},
|
|
},
|
|
wantStdout: "",
|
|
},
|
|
"moveFinalStatus with 0 resources": {
|
|
viewCall: func(state State) {
|
|
state.MoveFinalStatus(0)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": `No matching objects found`,
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`No matching objects found.`),
|
|
},
|
|
"moveFinalStatus with >0 resources": {
|
|
viewCall: func(state State) {
|
|
state.MoveFinalStatus(1)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Successfully moved 1 object(s)",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline("Successfully moved 1 object(s)."),
|
|
},
|
|
"printPulledState": {
|
|
viewCall: func(state State) {
|
|
state.PrintPulledState(`{"version":4,"terraform_version":"1.11.5","serial":9,"lineage":"9ba8c556-ae6c-20ee-f6ed-b57c7cc04dcd","outputs":{},"resources":[]}`)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "printing the pulled state is not available in the JSON view. The `tofu state pull` should not be configured with the `-json` flag",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`{"version":4,"terraform_version":"1.11.5","serial":9,"lineage":"9ba8c556-ae6c-20ee-f6ed-b57c7cc04dcd","outputs":{},"resources":[]}`),
|
|
},
|
|
"noMatchingResourcesForProviderReplacement": {
|
|
viewCall: func(state State) {
|
|
state.NoMatchingResourcesForProviderReplacement()
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "No matching resources found",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`No matching resources found.`),
|
|
},
|
|
"replaceProviderOverview": {
|
|
viewCall: func(state State) {
|
|
state.ReplaceProviderOverview(
|
|
regaddr.NewProvider("registry1.org", "ns", "prov"),
|
|
regaddr.NewProvider("registry2.org", "ns", "prov"),
|
|
[]*states.Resource{
|
|
{
|
|
Addr: addrs.AbsResource{Resource: addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "res",
|
|
Name: "foo",
|
|
}},
|
|
},
|
|
},
|
|
)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "OpenTofu will replace provider from registry1.org/ns/prov to registry2.org/ns/prov for 1 resources",
|
|
"resources": []any{"res.foo"},
|
|
"@module": "tofu.ui",
|
|
"type": "replace_provider",
|
|
"from": "registry1.org/ns/prov",
|
|
"to": "registry2.org/ns/prov",
|
|
},
|
|
},
|
|
wantStdout: `OpenTofu will perform the following actions:
|
|
|
|
~ Updating provider:
|
|
- registry1.org/ns/prov
|
|
+ registry2.org/ns/prov
|
|
|
|
Changing 1 resources:
|
|
|
|
res.foo
|
|
`,
|
|
},
|
|
"replaceProviderCancelled": {
|
|
viewCall: func(state State) {
|
|
state.ReplaceProviderCancelled()
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Cancelled replacing providers",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`Cancelled replacing providers.`),
|
|
},
|
|
"providerReplaced": {
|
|
viewCall: func(state State) {
|
|
state.ProviderReplaced(2)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Successfully replaced provider for 2 resources",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline(`Successfully replaced provider for 2 resources.`),
|
|
},
|
|
"resourceRemoveStatus with dryRun=true": {
|
|
viewCall: func(state State) {
|
|
state.ResourceRemoveStatus(true, "test_res.name1")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Would remove test_res.name1",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline("Would remove test_res.name1"),
|
|
},
|
|
"resourceRemoveStatus with dryRun=false": {
|
|
viewCall: func(state State) {
|
|
state.ResourceRemoveStatus(false, "test_res.name1")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Removed test_res.name1",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline("Removed test_res.name1"),
|
|
},
|
|
"dryRunRemovedStatus with 0 resources": {
|
|
viewCall: func(state State) {
|
|
state.DryRunRemovedStatus(0)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Would have removed nothing",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline("Would have removed nothing."),
|
|
},
|
|
"dryRunRemovedStatus with >0 resources": {
|
|
viewCall: func(state State) {
|
|
state.DryRunRemovedStatus(1)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{},
|
|
},
|
|
wantStdout: "",
|
|
},
|
|
"removeFinalStatus with 0 resources": {
|
|
viewCall: func(state State) {
|
|
state.RemoveFinalStatus(0)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{},
|
|
},
|
|
wantStdout: "",
|
|
},
|
|
"removeFinalStatus with >0 resources": {
|
|
viewCall: func(state State) {
|
|
state.RemoveFinalStatus(2)
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Successfully removed 2 resource instance(s)",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
wantStdout: withNewline("Successfully removed 2 resource instance(s)."),
|
|
},
|
|
"unsupportedLocalOp": {
|
|
viewCall: func(state State) {
|
|
state.UnsupportedLocalOp()
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: The configured backend doesn't support this operation",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": `The "backend" in OpenTofu defines how OpenTofu operates. The default backend performs all operations locally on your machine. Your configuration is configured to use a non-local backend. This backend doesn't support this operation.`,
|
|
"severity": "error",
|
|
"summary": "The configured backend doesn't support this operation",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: The configured backend doesn't support this operation
|
|
|
|
The "backend" in OpenTofu defines how OpenTofu operates. The default backend
|
|
performs all operations locally on your machine. Your configuration is
|
|
configured to use a non-local backend. This backend doesn't support this
|
|
operation.
|
|
`,
|
|
},
|
|
"addressParsingError": {
|
|
viewCall: func(state State) {
|
|
state.AddressParsingError("aws_instance.example")
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: Error parsing instance address \"aws_instance.example\"",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": `This command requires that the address references one specific instance. To view the available instances, use "tofu state list". Please modify the address to reference a specific instance.`,
|
|
"severity": "error",
|
|
"summary": `Error parsing instance address "aws_instance.example"`,
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: Error parsing instance address "aws_instance.example"
|
|
|
|
This command requires that the address references one specific instance. To
|
|
view the available instances, use "tofu state list". Please modify the
|
|
address to reference a specific instance.
|
|
`,
|
|
},
|
|
"noInstanceFoundError": {
|
|
viewCall: func(state State) {
|
|
state.NoInstanceFoundError()
|
|
},
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: No instance found for the given address",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": `This command requires that the address references one specific instance. To view the available instances, use "tofu state list". Please modify the address to reference a specific instance.`,
|
|
"severity": "error",
|
|
"summary": "No instance found for the given address",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
wantStderr: `
|
|
Error: No instance found for the given address
|
|
|
|
This command requires that the address references one specific instance. To
|
|
view the available instances, use "tofu state list". Please modify the
|
|
address to reference a specific instance.
|
|
`,
|
|
},
|
|
// ShowResourceState for success cases has its own dedicated test because in that situation the json output
|
|
// is in a raw format and not adhere to the way "informative" messages are shown.
|
|
"showResourceState with nil state": {
|
|
viewCall: func(state State) {
|
|
state.ShowResourceState(context.Background(), nil, nil)
|
|
},
|
|
wantStdout: withNewline("No state."),
|
|
wantStderr: "",
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "info",
|
|
"@message": "no state",
|
|
"@module": "tofu.ui",
|
|
},
|
|
},
|
|
},
|
|
"showResourceState with proper state": {
|
|
ignoreTimestamp: true,
|
|
viewCall: func(state State) {
|
|
stateFile := states.BuildState(func(s *states.SyncState) {
|
|
s.SetResourceInstanceCurrent(
|
|
addrs.Resource{
|
|
Mode: addrs.ManagedResourceMode,
|
|
Type: "test_resource",
|
|
Name: "foo",
|
|
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
|
|
&states.ResourceInstanceObjectSrc{
|
|
AttrsJSON: []byte(`{"id":"bar","foo":"value"}`),
|
|
Status: states.ObjectReady,
|
|
},
|
|
addrs.AbsProviderConfig{
|
|
Provider: addrs.NewDefaultProvider("test"),
|
|
Module: addrs.RootModule,
|
|
},
|
|
addrs.NoKey,
|
|
)
|
|
})
|
|
resState := statefile.New(stateFile, "", 0)
|
|
schema := tofu.Schemas{
|
|
Providers: map[addrs.Provider]providers.ProviderSchema{
|
|
addrs.NewDefaultProvider("test"): {
|
|
ResourceTypes: map[string]providers.Schema{
|
|
"test_resource": {
|
|
Block: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"id": {
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"foo": {
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
state.ShowResourceState(context.Background(), resState, &schema)
|
|
},
|
|
wantStdout: withNewline(`# test_resource.foo:
|
|
resource "test_resource" "foo" {
|
|
foo = "value"
|
|
id = "bar"
|
|
}`),
|
|
wantStderr: "",
|
|
wantJson: []map[string]any{
|
|
{
|
|
"format_version": "1.0",
|
|
"terraform_version": version.SemVer.String(),
|
|
"values": map[string]any{
|
|
"root_module": map[string]any{
|
|
"resources": []any{
|
|
map[string]any{
|
|
"address": "test_resource.foo",
|
|
"mode": "managed",
|
|
"type": "test_resource",
|
|
"name": "foo",
|
|
"provider_name": "registry.opentofu.org/hashicorp/test",
|
|
"schema_version": float64(0),
|
|
"values": map[string]any{
|
|
"foo": "value",
|
|
"id": "bar",
|
|
},
|
|
"sensitive_values": map[string]any{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
// Diagnostics
|
|
"warning": {
|
|
viewCall: func(state State) {
|
|
diags := tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(tfdiags.Warning, "A warning occurred", "foo bar"),
|
|
}
|
|
state.Diagnostics(diags)
|
|
},
|
|
wantStdout: withNewline("\nWarning: A warning occurred\n\nfoo bar"),
|
|
wantStderr: "",
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "warn",
|
|
"@message": "Warning: A warning occurred",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "foo bar",
|
|
"severity": "warning",
|
|
"summary": "A warning occurred",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
},
|
|
"error": {
|
|
viewCall: func(state State) {
|
|
diags := tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(tfdiags.Error, "An error occurred", "foo bar"),
|
|
}
|
|
state.Diagnostics(diags)
|
|
},
|
|
wantStdout: "",
|
|
wantStderr: withNewline("\nError: An error occurred\n\nfoo bar"),
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: An error occurred",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "foo bar",
|
|
"severity": "error",
|
|
"summary": "An error occurred",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
},
|
|
"multiple_diagnostics": {
|
|
viewCall: func(state State) {
|
|
diags := tfdiags.Diagnostics{
|
|
tfdiags.Sourceless(tfdiags.Warning, "A warning", "foo bar warning"),
|
|
tfdiags.Sourceless(tfdiags.Error, "An error", "foo bar error"),
|
|
}
|
|
state.Diagnostics(diags)
|
|
},
|
|
wantStdout: withNewline("\nWarning: A warning\n\nfoo bar warning"),
|
|
wantStderr: withNewline("\nError: An error\n\nfoo bar error"),
|
|
wantJson: []map[string]any{
|
|
{
|
|
"@level": "warn",
|
|
"@message": "Warning: A warning",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "foo bar warning",
|
|
"severity": "warning",
|
|
"summary": "A warning",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: An error",
|
|
"@module": "tofu.ui",
|
|
"diagnostic": map[string]any{
|
|
"detail": "foo bar error",
|
|
"severity": "error",
|
|
"summary": "An error",
|
|
},
|
|
"type": "diagnostic",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
testStateHuman(t, tc.viewCall, tc.wantStdout, tc.wantStderr)
|
|
testStateJson(t, tc.viewCall, tc.wantJson, !tc.ignoreTimestamp)
|
|
testStateMulti(t, tc.viewCall, tc.wantStdout, tc.wantStderr, tc.wantJson, !tc.ignoreTimestamp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testStateHuman(t *testing.T, call func(state State), wantStdout, wantStderr string) {
|
|
view, done := testView(t)
|
|
stateView := NewState(arguments.ViewOptions{ViewType: arguments.ViewHuman}, view)
|
|
call(stateView)
|
|
output := done(t)
|
|
if diff := cmp.Diff(wantStderr, output.Stderr()); diff != "" {
|
|
t.Errorf("invalid stderr (-want, +got):\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(wantStdout, output.Stdout()); diff != "" {
|
|
t.Errorf("invalid stdout (-want, +got):\n%s", diff)
|
|
}
|
|
}
|
|
|
|
func testStateJson(t *testing.T, call func(state State), want []map[string]interface{}, withTimestamp bool) {
|
|
view, done := testView(t)
|
|
stateView := NewState(arguments.ViewOptions{ViewType: arguments.ViewJSON}, view)
|
|
call(stateView)
|
|
output := done(t)
|
|
if output.Stderr() != "" {
|
|
t.Errorf("expected no stderr but got:\n%s", output.Stderr())
|
|
}
|
|
|
|
if withTimestamp {
|
|
testJSONViewOutputEquals(t, output.Stdout(), want)
|
|
return
|
|
}
|
|
testJSONViewOutputEqualsIgnoringTimestamp(t, output.Stdout(), want)
|
|
}
|
|
|
|
func testStateMulti(t *testing.T, call func(state State), wantStdout string, wantStderr string, want []map[string]interface{}, withTimestamp bool) {
|
|
jsonInto, err := os.CreateTemp(t.TempDir(), "json-into-*")
|
|
if err != nil {
|
|
t.Fatalf("failed to create the file to write json content into: %s", err)
|
|
}
|
|
view, done := testView(t)
|
|
stateView := NewState(arguments.ViewOptions{ViewType: arguments.ViewHuman, JSONInto: jsonInto}, view)
|
|
call(stateView)
|
|
{
|
|
if err := jsonInto.Close(); err != nil {
|
|
t.Fatalf("failed to close the jsonInto file: %s", err)
|
|
}
|
|
// check the fileInto content
|
|
fileContent, err := os.ReadFile(jsonInto.Name())
|
|
if err != nil {
|
|
t.Fatalf("failed to read the file content with the json output: %s", err)
|
|
}
|
|
if withTimestamp {
|
|
testJSONViewOutputEquals(t, string(fileContent), want)
|
|
return
|
|
}
|
|
testJSONViewOutputEqualsIgnoringTimestamp(t, string(fileContent), want)
|
|
}
|
|
{
|
|
// check the human output
|
|
output := done(t)
|
|
if diff := cmp.Diff(wantStderr, output.Stderr()); diff != "" {
|
|
t.Errorf("invalid stderr (-want, +got):\n%s", diff)
|
|
}
|
|
if diff := cmp.Diff(wantStdout, output.Stdout()); diff != "" {
|
|
t.Errorf("invalid stdout (-want, +got):\n%s", diff)
|
|
}
|
|
}
|
|
}
|