diff --git a/.changes/unreleased/BUG FIXES-20260210-153921.yaml b/.changes/unreleased/BUG FIXES-20260210-153921.yaml new file mode 100644 index 00000000..122903f6 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20260210-153921.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'helper/resource: Test steps in `Config` mode using the `TF_ACC_REFRESH_AFTER_APPLY` compatibility flag will now force post-apply plans to refresh.' +time: 2026-02-10T15:39:21.121944-05:00 +custom: + Issue: "602" diff --git a/.changes/unreleased/BUG FIXES-20260210-154312.yaml b/.changes/unreleased/BUG FIXES-20260210-154312.yaml new file mode 100644 index 00000000..31950c87 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20260210-154312.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'helper/resource: Test steps in `Config` mode using `Destroy: true` and `Check` functions will now create an additional destroy plan prior to + running `terraform apply` to avoid a potential "Saved Plan is Stale" error from Terraform.' +time: 2026-02-10T15:43:12.029656-05:00 +custom: + Issue: "602" diff --git a/.changes/unreleased/BUG FIXES-20260210-154602.yaml b/.changes/unreleased/BUG FIXES-20260210-154602.yaml new file mode 100644 index 00000000..8b66da13 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20260210-154602.yaml @@ -0,0 +1,5 @@ +kind: BUG FIXES +body: 'helper/resource: Test steps in `Config` mode using the `TF_ACC_REFRESH_AFTER_APPLY` compatibility flag will not refresh if `ExpectNonEmptyPlan` is true.' +time: 2026-02-10T15:46:02.221648-05:00 +custom: + Issue: "602" diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index 1a9b7520..9baaaf9a 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -26,6 +26,7 @@ jobs: name: test (Go ${{ matrix.go-version }} / TF ${{ matrix.terraform }}) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: go-version: [ '1.25', '1.24' ] terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V5) }} diff --git a/helper/resource/testing_new_config.go b/helper/resource/testing_new_config.go index fb97d4ec..f41c0fe4 100644 --- a/helper/resource/testing_new_config.go +++ b/helper/resource/testing_new_config.go @@ -191,7 +191,7 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint return wd.CreatePlan(ctx, opts...) }) if err != nil { - return fmt.Errorf("Error running pre-apply plan: %w", err) + return fmt.Errorf("Error running destroy plan after step.Check shimmed state was retrieved: %w", err) } } diff --git a/helper/resource/testing_new_config_test.go b/helper/resource/testing_new_config_test.go index 8ce9747f..5f2a39d9 100644 --- a/helper/resource/testing_new_config_test.go +++ b/helper/resource/testing_new_config_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) @@ -906,3 +907,58 @@ func Test_PostApplyFunc_Called(t *testing.T) { t.Error("expected ConfigStateChecks spy1 to be called at least once") } } + +// This regression test ensures that the combination of Config, Destroy, and Check never result in +// a "Saved Plan is Stale" error message, which occurs when the state serial does not match the plan. +// +// This can occur when the refresh that is only done to produce the shimmed "Check" state produces a new state serial. +// Running a fresh plan after refreshing solves that issue, which was introduced in: https://github.com/hashicorp/terraform-plugin-testing/pull/602 +func Test_Destroy_Checks_Avoid_Stale_Plan(t *testing.T) { + t.Parallel() + + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "test"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: `resource "test_resource" "test" {}`, + Destroy: true, + Check: func(s *terraform.State) error { return nil }, + }, + }, + }) +}