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.
This commit is contained in:
Alisdair McDiarmid 2021-07-15 12:03:01 -04:00
parent 3e5bfa7364
commit c51112a30c
2 changed files with 135 additions and 96 deletions

View File

@ -303,12 +303,16 @@ func testJSONViewOutputEqualsFull(t *testing.T, output string, want []map[string
gotLines := strings.Split(output, "\n") gotLines := strings.Split(output, "\n")
if len(gotLines) != len(want) { 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 // Unmarshal each line and compare to the expected value
for i := range gotLines { for i := range gotLines {
var gotStruct map[string]interface{} var gotStruct map[string]interface{}
if i >= len(want) {
t.Error("reached end of want messages too soon")
break
}
wantStruct := want[i] wantStruct := want[i]
if err := json.Unmarshal([]byte(gotLines[i]), &gotStruct); err != nil { 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 // Verify the timestamp format
if _, err := time.Parse("2006-01-02T15:04:05.000000Z07:00", timestamp.(string)); err != nil { 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) { 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))
} }
} }
} }

View File

@ -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()) v.Plan(plan, testSchemas())
want := []map[string]interface{}{ 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 // Create-then-delete should result in replace
{ {
"@level": "info", "@level": "info",
@ -728,6 +635,134 @@ func TestOperationJSON_plan(t *testing.T) {
testJSONViewOutputEquals(t, done(t).Stdout(), want) 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) { func TestOperationJSON_plannedChange(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := &OperationJSON{view: NewJSONView(NewView(streams))} v := &OperationJSON{view: NewJSONView(NewView(streams))}