-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
helper/resource: Ensure TestStep.ExpectNonEmptyPlan accounts for output changes #234
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
kind: BUG FIXES | ||
body: 'helper/resource: Ensured `TestStep.ExpectNonEmptyPlan` accounts for output | ||
changes with Terraform 0.14 and later' | ||
time: 2023-11-30T10:23:26.348382-05:00 | ||
custom: | ||
Issue: "234" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ import ( | |
"strings" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"github.com/hashicorp/go-version" | ||
tfjson "github.com/hashicorp/terraform-json" | ||
"github.com/mitchellh/go-testing-interface" | ||
|
||
|
@@ -476,14 +477,25 @@ func stateIsEmpty(state *terraform.State) bool { | |
return state.Empty() || !state.HasResources() //nolint:staticcheck // legacy usage | ||
} | ||
|
||
func planIsEmpty(plan *tfjson.Plan) bool { | ||
func planIsEmpty(plan *tfjson.Plan, tfVersion *version.Version) bool { | ||
for _, rc := range plan.ResourceChanges { | ||
for _, a := range rc.Change.Actions { | ||
if a != tfjson.ActionNoop { | ||
return false | ||
} | ||
} | ||
} | ||
|
||
if tfVersion.LessThan(expectNonEmptyPlanOutputChangesMinTFVersion) { | ||
return true | ||
} | ||
|
||
for _, change := range plan.OutputChanges { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this a potentially breaking change for the reasons described in Austin's comment? |
||
if !change.Actions.NoOp() { | ||
return false | ||
} | ||
} | ||
|
||
return true | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,6 +8,7 @@ import ( | |||||
"errors" | ||||||
"fmt" | ||||||
|
||||||
"github.com/hashicorp/go-version" | ||||||
"github.com/hashicorp/terraform-exec/tfexec" | ||||||
tfjson "github.com/hashicorp/terraform-json" | ||||||
"github.com/mitchellh/go-testing-interface" | ||||||
|
@@ -20,6 +21,11 @@ import ( | |||||
"github.com/hashicorp/terraform-plugin-testing/internal/plugintest" | ||||||
) | ||||||
|
||||||
// expectNonEmptyPlanOutputChangesMinTFVersion is used to keep compatibility for | ||||||
// Terraform 0.12 and 0.13 after enabling ExpectNonEmptyPlan to check output | ||||||
// changes. Those older versions will always show outputs being created. | ||||||
var expectNonEmptyPlanOutputChangesMinTFVersion = version.Must(version.NewVersion("0.14.0")) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Nit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call -- sorry couldn't accept suggestion directly as imports also needed updates 👍 |
||||||
|
||||||
func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories, stepIndex int, helper *plugintest.Helper) error { | ||||||
t.Helper() | ||||||
|
||||||
|
@@ -236,7 +242,7 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint | |||||
} | ||||||
} | ||||||
|
||||||
if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan { | ||||||
if !planIsEmpty(plan, helper.TerraformVersion()) && !step.ExpectNonEmptyPlan { | ||||||
var stdout string | ||||||
err = runProviderCommand(ctx, t, func() error { | ||||||
var err error | ||||||
|
@@ -283,7 +289,7 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint | |||||
} | ||||||
|
||||||
// check if plan is empty | ||||||
if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan { | ||||||
if !planIsEmpty(plan, helper.TerraformVersion()) && !step.ExpectNonEmptyPlan { | ||||||
var stdout string | ||||||
err = runProviderCommand(ctx, t, func() error { | ||||||
var err error | ||||||
|
@@ -294,7 +300,7 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint | |||||
return fmt.Errorf("Error retrieving formatted second plan output: %w", err) | ||||||
} | ||||||
return fmt.Errorf("After applying this test step and performing a `terraform refresh`, the plan was not empty.\nstdout\n\n%s", stdout) | ||||||
} else if step.ExpectNonEmptyPlan && planIsEmpty(plan) { | ||||||
} else if step.ExpectNonEmptyPlan && planIsEmpty(plan, helper.TerraformVersion()) { | ||||||
return errors.New("Expected a non-empty plan, but got an empty plan") | ||||||
} | ||||||
|
||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to add a test for this 😃 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added positive and negative tests for func Test_RefreshState_ExpectNonEmptyPlan(t *testing.T) {
t.Parallel()
UnitTest(t, TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_4_0),
},
ExternalProviders: map[string]ExternalProvider{
"terraform": {Source: "terraform.io/builtin/terraform"},
},
Steps: []TestStep{
{
Config: `resource "terraform_data" "test" {
# Never recommended for real world configurations, but tests
# the intended behavior.
input = timestamp()
}`,
ExpectNonEmptyPlan: false, // intentional
ExpectError: regexp.MustCompile("After applying this test step, the plan was not empty."),
},
{
RefreshState: true,
ExpectNonEmptyPlan: true,
},
},
})
}
func Test_RefreshState_NonEmptyPlan_Error(t *testing.T) {
t.Parallel()
UnitTest(t, TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_4_0),
},
ExternalProviders: map[string]ExternalProvider{
"terraform": {Source: "terraform.io/builtin/terraform"},
},
Steps: []TestStep{
{
Config: `resource "terraform_data" "test" {
# Never recommended for real world configurations, but tests
# the intended behavior.
input = timestamp()
}`,
ExpectNonEmptyPlan: false, // intentional
ExpectError: regexp.MustCompile("After applying this test step, the plan was not empty."),
},
{
RefreshState: true,
ExpectNonEmptyPlan: false, // intentional
ExpectError: regexp.MustCompile("After refreshing state during this test step, a followup plan was not empty."),
},
},
})
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this PR also changes the "default" behavior (
TestStep.ExpectNonEmptyPlan = false
) of non-empty plan's throwing an error by also checking output changes.Should we describe that (potentially non-obvious) behavior change in a separate changelog?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call -- adding a second note entry along the lines of: