mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
c51112a30c
This test would previously fail randomly due to the use of multiple resource instances. Instance keys are iterated over as a map for presentation, which has intentionally inconsistent ordering. To fix this, I changed the test to use different resource addresses for the three drift cases. I also extracted them to a separate test, and tweaked the test helper functions to reduce the number of fatal exit points, to make failed test debugging easier.
352 lines
9.9 KiB
Go
352 lines
9.9 KiB
Go
package views
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
tfversion "github.com/hashicorp/terraform/version"
|
|
)
|
|
|
|
// Calling NewJSONView should also always output a version message, which is a
|
|
// convenient way to test that NewJSONView works.
|
|
func TestNewJSONView(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
NewJSONView(NewView(streams))
|
|
|
|
version := tfversion.String()
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": fmt.Sprintf("Terraform %s", version),
|
|
"@module": "terraform.ui",
|
|
"type": "version",
|
|
"terraform": version,
|
|
"ui": JSON_UI_VERSION,
|
|
},
|
|
}
|
|
|
|
testJSONViewOutputEqualsFull(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
func TestJSONView_Log(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
jv.Log("hello, world")
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": "hello, world",
|
|
"@module": "terraform.ui",
|
|
"type": "log",
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
// This test covers only the basics of JSON diagnostic rendering, as more
|
|
// complex diagnostics are tested elsewhere.
|
|
func TestJSONView_Diagnostics(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
var diags tfdiags.Diagnostics
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Warning,
|
|
`Improper use of "less"`,
|
|
`You probably mean "10 buckets or fewer"`,
|
|
))
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Error,
|
|
"Unusually stripey cat detected",
|
|
"Are you sure this random_pet isn't a cheetah?",
|
|
))
|
|
|
|
jv.Diagnostics(diags)
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "warn",
|
|
"@message": `Warning: Improper use of "less"`,
|
|
"@module": "terraform.ui",
|
|
"type": "diagnostic",
|
|
"diagnostic": map[string]interface{}{
|
|
"severity": "warning",
|
|
"summary": `Improper use of "less"`,
|
|
"detail": `You probably mean "10 buckets or fewer"`,
|
|
},
|
|
},
|
|
{
|
|
"@level": "error",
|
|
"@message": "Error: Unusually stripey cat detected",
|
|
"@module": "terraform.ui",
|
|
"type": "diagnostic",
|
|
"diagnostic": map[string]interface{}{
|
|
"severity": "error",
|
|
"summary": "Unusually stripey cat detected",
|
|
"detail": "Are you sure this random_pet isn't a cheetah?",
|
|
},
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
func TestJSONView_PlannedChange(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
foo, diags := addrs.ParseModuleInstanceStr("module.foo")
|
|
if len(diags) > 0 {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
managed := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "bar"}
|
|
cs := &plans.ResourceInstanceChangeSrc{
|
|
Addr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Create,
|
|
},
|
|
}
|
|
jv.PlannedChange(viewsjson.NewResourceInstanceChange(cs))
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": `module.foo.test_instance.bar["boop"]: Plan to create`,
|
|
"@module": "terraform.ui",
|
|
"type": "planned_change",
|
|
"change": map[string]interface{}{
|
|
"action": "create",
|
|
"resource": map[string]interface{}{
|
|
"addr": `module.foo.test_instance.bar["boop"]`,
|
|
"implied_provider": "test",
|
|
"module": "module.foo",
|
|
"resource": `test_instance.bar["boop"]`,
|
|
"resource_key": "boop",
|
|
"resource_name": "bar",
|
|
"resource_type": "test_instance",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
func TestJSONView_ResourceDrift(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
foo, diags := addrs.ParseModuleInstanceStr("module.foo")
|
|
if len(diags) > 0 {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
managed := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "bar"}
|
|
cs := &plans.ResourceInstanceChangeSrc{
|
|
Addr: managed.Instance(addrs.StringKey("boop")).Absolute(foo),
|
|
ChangeSrc: plans.ChangeSrc{
|
|
Action: plans.Update,
|
|
},
|
|
}
|
|
jv.ResourceDrift(viewsjson.NewResourceInstanceChange(cs))
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": `module.foo.test_instance.bar["boop"]: Drift detected (update)`,
|
|
"@module": "terraform.ui",
|
|
"type": "resource_drift",
|
|
"change": map[string]interface{}{
|
|
"action": "update",
|
|
"resource": map[string]interface{}{
|
|
"addr": `module.foo.test_instance.bar["boop"]`,
|
|
"implied_provider": "test",
|
|
"module": "module.foo",
|
|
"resource": `test_instance.bar["boop"]`,
|
|
"resource_key": "boop",
|
|
"resource_name": "bar",
|
|
"resource_type": "test_instance",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
func TestJSONView_ChangeSummary(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
jv.ChangeSummary(&viewsjson.ChangeSummary{
|
|
Add: 1,
|
|
Change: 2,
|
|
Remove: 3,
|
|
Operation: viewsjson.OperationApplied,
|
|
})
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Apply complete! Resources: 1 added, 2 changed, 3 destroyed.",
|
|
"@module": "terraform.ui",
|
|
"type": "change_summary",
|
|
"changes": map[string]interface{}{
|
|
"add": float64(1),
|
|
"change": float64(2),
|
|
"remove": float64(3),
|
|
"operation": "apply",
|
|
},
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
func TestJSONView_Hook(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
foo, diags := addrs.ParseModuleInstanceStr("module.foo")
|
|
if len(diags) > 0 {
|
|
t.Fatal(diags.Err())
|
|
}
|
|
managed := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "bar"}
|
|
addr := managed.Instance(addrs.StringKey("boop")).Absolute(foo)
|
|
hook := viewsjson.NewApplyComplete(addr, plans.Create, "id", "boop-beep", 34*time.Second)
|
|
|
|
jv.Hook(hook)
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": `module.foo.test_instance.bar["boop"]: Creation complete after 34s [id=boop-beep]`,
|
|
"@module": "terraform.ui",
|
|
"type": "apply_complete",
|
|
"hook": map[string]interface{}{
|
|
"resource": map[string]interface{}{
|
|
"addr": `module.foo.test_instance.bar["boop"]`,
|
|
"implied_provider": "test",
|
|
"module": "module.foo",
|
|
"resource": `test_instance.bar["boop"]`,
|
|
"resource_key": "boop",
|
|
"resource_name": "bar",
|
|
"resource_type": "test_instance",
|
|
},
|
|
"action": "create",
|
|
"id_key": "id",
|
|
"id_value": "boop-beep",
|
|
"elapsed_seconds": float64(34),
|
|
},
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
func TestJSONView_Outputs(t *testing.T) {
|
|
streams, done := terminal.StreamsForTesting(t)
|
|
jv := NewJSONView(NewView(streams))
|
|
|
|
jv.Outputs(viewsjson.Outputs{
|
|
"boop_count": {
|
|
Sensitive: false,
|
|
Value: json.RawMessage(`92`),
|
|
Type: json.RawMessage(`"number"`),
|
|
},
|
|
"password": {
|
|
Sensitive: true,
|
|
Value: json.RawMessage(`"horse-battery"`),
|
|
Type: json.RawMessage(`"string"`),
|
|
},
|
|
})
|
|
|
|
want := []map[string]interface{}{
|
|
{
|
|
"@level": "info",
|
|
"@message": "Outputs: 2",
|
|
"@module": "terraform.ui",
|
|
"type": "outputs",
|
|
"outputs": map[string]interface{}{
|
|
"boop_count": map[string]interface{}{
|
|
"sensitive": false,
|
|
"value": float64(92),
|
|
"type": "number",
|
|
},
|
|
"password": map[string]interface{}{
|
|
"sensitive": true,
|
|
"value": "horse-battery",
|
|
"type": "string",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
testJSONViewOutputEquals(t, done(t).Stdout(), want)
|
|
}
|
|
|
|
// This helper function tests a possibly multi-line JSONView output string
|
|
// against a slice of structs representing the desired log messages. It
|
|
// verifies that the output of JSONView is in JSON log format, one message per
|
|
// line.
|
|
func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string]interface{}) {
|
|
t.Helper()
|
|
|
|
// Remove final trailing newline
|
|
output = strings.TrimSuffix(output, "\n")
|
|
|
|
// Split log into lines, each of which should be a JSON log message
|
|
gotLines := strings.Split(output, "\n")
|
|
|
|
if len(gotLines) != len(want) {
|
|
t.Errorf("unexpected number of messages. got %d, want %d", len(gotLines), len(want))
|
|
}
|
|
|
|
// Unmarshal each line and compare to the expected value
|
|
for i := range gotLines {
|
|
var gotStruct map[string]interface{}
|
|
if i >= len(want) {
|
|
t.Error("reached end of want messages too soon")
|
|
break
|
|
}
|
|
wantStruct := want[i]
|
|
|
|
if err := json.Unmarshal([]byte(gotLines[i]), &gotStruct); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if timestamp, ok := gotStruct["@timestamp"]; !ok {
|
|
t.Errorf("message has no timestamp: %#v", gotStruct)
|
|
} else {
|
|
// Remove the timestamp value from the struct to allow comparison
|
|
delete(gotStruct, "@timestamp")
|
|
|
|
// Verify the timestamp format
|
|
if _, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", timestamp.(string)); err != nil {
|
|
t.Errorf("error parsing timestamp on line %d: %s", i, err)
|
|
}
|
|
}
|
|
|
|
if !cmp.Equal(wantStruct, gotStruct) {
|
|
t.Errorf("unexpected output on line %d:\n%s", i, cmp.Diff(wantStruct, gotStruct))
|
|
}
|
|
}
|
|
}
|
|
|
|
// testJSONViewOutputEquals skips the first line of output, since it ought to
|
|
// be a version message that we don't care about for most of our tests.
|
|
func testJSONViewOutputEquals(t *testing.T, output string, want []map[string]interface{}) {
|
|
t.Helper()
|
|
|
|
// Remove up to the first newline
|
|
index := strings.Index(output, "\n")
|
|
if index >= 0 {
|
|
output = output[index+1:]
|
|
}
|
|
testJSONViewOutputEqualsFull(t, output, want)
|
|
}
|