From c51112a30ceee0cd752c4a8fa97db8fea51efe15 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Thu, 15 Jul 2021 12:03:01 -0400 Subject: [PATCH] Fix flapping JSON output test 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. --- internal/command/views/json_view_test.go | 10 +- internal/command/views/operation_test.go | 221 +++++++++++++---------- 2 files changed, 135 insertions(+), 96 deletions(-) diff --git a/internal/command/views/json_view_test.go b/internal/command/views/json_view_test.go index a88e50299..c755cf0b7 100644 --- a/internal/command/views/json_view_test.go +++ b/internal/command/views/json_view_test.go @@ -303,12 +303,16 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string gotLines := strings.Split(output, "\n") if len(gotLines) != len(want) { - t.Fatalf("unexpected number of messages. got %d, want %d", 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 { @@ -323,12 +327,12 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string // Verify the timestamp format if _, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", timestamp.(string)); err != nil { - t.Fatalf("error parsing timestamp: %s", err) + t.Errorf("error parsing timestamp on line %d: %s", i, err) } } if !cmp.Equal(wantStruct, gotStruct) { - t.Fatalf("unexpected output on line %d:\n%s", i, cmp.Diff(wantStruct, gotStruct)) + t.Errorf("unexpected output on line %d:\n%s", i, cmp.Diff(wantStruct, gotStruct)) } } } diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index 4f9c20e8a..7d2964443 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -517,103 +517,10 @@ func TestOperationJSON_plan(t *testing.T) { }, }, }, - PrevRunState: states.BuildState(func(state *states.SyncState) { - // Update - state.SetResourceInstanceCurrent( - boop.Instance(addrs.IntKey(0)).Absolute(root), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"bar"}`), - }, - root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - // Delete - state.SetResourceInstanceCurrent( - boop.Instance(addrs.IntKey(1)).Absolute(root), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"boop"}`), - }, - root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - // No-op - state.SetResourceInstanceCurrent( - beep.Instance(addrs.NoKey).Absolute(root), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"boop"}`), - }, - root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), - PriorState: states.BuildState(func(state *states.SyncState) { - // Update - state.SetResourceInstanceCurrent( - boop.Instance(addrs.IntKey(0)).Absolute(root), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"baz"}`), - }, - root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - // Delete - state.SetResourceInstanceCurrent( - boop.Instance(addrs.IntKey(1)).Absolute(root), - nil, - root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - // No-op - state.SetResourceInstanceCurrent( - beep.Instance(addrs.NoKey).Absolute(root), - &states.ResourceInstanceObjectSrc{ - Status: states.ObjectReady, - AttrsJSON: []byte(`{"foo":"boop"}`), - }, - root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), - ) - }), } v.Plan(plan, testSchemas()) want := []map[string]interface{}{ - // Drift detected: update - { - "@level": "info", - "@message": "test_resource.boop[0]: Drift detected (update)", - "@module": "terraform.ui", - "type": "resource_drift", - "change": map[string]interface{}{ - "action": "update", - "resource": map[string]interface{}{ - "addr": "test_resource.boop[0]", - "implied_provider": "test", - "module": "", - "resource": "test_resource.boop[0]", - "resource_key": float64(0), - "resource_name": "boop", - "resource_type": "test_resource", - }, - }, - }, - // Drift detected: delete - { - "@level": "info", - "@message": "test_resource.boop[1]: Drift detected (delete)", - "@module": "terraform.ui", - "type": "resource_drift", - "change": map[string]interface{}{ - "action": "delete", - "resource": map[string]interface{}{ - "addr": "test_resource.boop[1]", - "implied_provider": "test", - "module": "", - "resource": "test_resource.boop[1]", - "resource_key": float64(1), - "resource_name": "boop", - "resource_type": "test_resource", - }, - }, - }, // Create-then-delete should result in replace { "@level": "info", @@ -728,6 +635,134 @@ func TestOperationJSON_plan(t *testing.T) { testJSONViewOutputEquals(t, done(t).Stdout(), want) } +func TestOperationJSON_planDrift(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := &OperationJSON{view: NewJSONView(NewView(streams))} + + root := addrs.RootModuleInstance + boop := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "boop"} + beep := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "beep"} + derp := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_resource", Name: "derp"} + + plan := &plans.Plan{ + Changes: &plans.Changes{ + Resources: []*plans.ResourceInstanceChangeSrc{}, + }, + PrevRunState: states.BuildState(func(state *states.SyncState) { + // Update + state.SetResourceInstanceCurrent( + boop.Instance(addrs.NoKey).Absolute(root), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"foo":"bar"}`), + }, + root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + ) + // Delete + state.SetResourceInstanceCurrent( + beep.Instance(addrs.NoKey).Absolute(root), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"foo":"boop"}`), + }, + root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + ) + // No-op + state.SetResourceInstanceCurrent( + derp.Instance(addrs.NoKey).Absolute(root), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"foo":"boop"}`), + }, + root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + ) + }), + PriorState: states.BuildState(func(state *states.SyncState) { + // Update + state.SetResourceInstanceCurrent( + boop.Instance(addrs.NoKey).Absolute(root), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"foo":"baz"}`), + }, + root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + ) + // Delete + state.SetResourceInstanceCurrent( + beep.Instance(addrs.NoKey).Absolute(root), + nil, + root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + ) + // No-op + state.SetResourceInstanceCurrent( + derp.Instance(addrs.NoKey).Absolute(root), + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectReady, + AttrsJSON: []byte(`{"foo":"boop"}`), + }, + root.ProviderConfigDefault(addrs.NewDefaultProvider("test")), + ) + }), + } + v.Plan(plan, testSchemas()) + + want := []map[string]interface{}{ + // Drift detected: update + { + "@level": "info", + "@message": "test_resource.boop: Drift detected (update)", + "@module": "terraform.ui", + "type": "resource_drift", + "change": map[string]interface{}{ + "action": "update", + "resource": map[string]interface{}{ + "addr": "test_resource.boop", + "implied_provider": "test", + "module": "", + "resource": "test_resource.boop", + "resource_key": nil, + "resource_name": "boop", + "resource_type": "test_resource", + }, + }, + }, + // Drift detected: delete + { + "@level": "info", + "@message": "test_resource.beep: Drift detected (delete)", + "@module": "terraform.ui", + "type": "resource_drift", + "change": map[string]interface{}{ + "action": "delete", + "resource": map[string]interface{}{ + "addr": "test_resource.beep", + "implied_provider": "test", + "module": "", + "resource": "test_resource.beep", + "resource_key": nil, + "resource_name": "beep", + "resource_type": "test_resource", + }, + }, + }, + // No changes + { + "@level": "info", + "@message": "Plan: 0 to add, 0 to change, 0 to destroy.", + "@module": "terraform.ui", + "type": "change_summary", + "changes": map[string]interface{}{ + "operation": "plan", + "add": float64(0), + "change": float64(0), + "remove": float64(0), + }, + }, + } + + testJSONViewOutputEquals(t, done(t).Stdout(), want) +} + func TestOperationJSON_plannedChange(t *testing.T) { streams, done := terminal.StreamsForTesting(t) v := &OperationJSON{view: NewJSONView(NewView(streams))}