Skip to content

Commit 3374018

Browse files
authored
.ci/semgrep: add disappears-expect-resource-action rule (#47914)
This rule requires that `_disappears` acceptance tests include a `PostApplyPostRefresh` config plan check that asserts the resource under test is planned for creation. This ensures that the expected non-empty plan is caused by the resource being tested and not a dependency or other side-effect of the apply.
1 parent 4f8dcff commit 3374018

2 files changed

Lines changed: 302 additions & 0 deletions

File tree

.ci/semgrep/acctest/disappears.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// Copyright IBM Corp. 2014, 2026
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package main
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
11+
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
12+
"github.com/hashicorp/terraform-provider-aws/names"
13+
)
14+
15+
func testSDKDisappearsWithoutPlanCheck(t *testing.T) {
16+
ctx := acctest.Context(t)
17+
18+
const resourceName = "aws_example_thing.test"
19+
20+
acctest.ParallelTest(t, resource.TestCase{
21+
PreCheck: func() { acctest.PreCheck(ctx, t) },
22+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
23+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
24+
CheckDestroy: testAccCheckDestroy(ctx),
25+
Steps: []resource.TestStep{
26+
{
27+
Config: testAccConfig_basic(),
28+
Check: resource.ComposeTestCheckFunc(
29+
testAccCheckExists(ctx, t, resourceName),
30+
// ruleid: disappears-expect-resource-action
31+
acctest.CheckSDKResourceDisappears(ctx, t, ResourceThing(), resourceName),
32+
),
33+
ExpectNonEmptyPlan: true,
34+
},
35+
},
36+
})
37+
}
38+
39+
func testSDKDisappearsWithPlanCheck(t *testing.T) {
40+
ctx := acctest.Context(t)
41+
42+
const resourceName = "aws_example_thing.test"
43+
44+
acctest.ParallelTest(t, resource.TestCase{
45+
PreCheck: func() { acctest.PreCheck(ctx, t) },
46+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
47+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
48+
CheckDestroy: testAccCheckDestroy(ctx),
49+
Steps: []resource.TestStep{
50+
{
51+
Config: testAccConfig_basic(),
52+
Check: resource.ComposeTestCheckFunc(
53+
testAccCheckExists(ctx, t, resourceName),
54+
// ok: disappears-expect-resource-action
55+
acctest.CheckSDKResourceDisappears(ctx, t, ResourceThing(), resourceName),
56+
),
57+
ExpectNonEmptyPlan: true,
58+
ConfigPlanChecks: resource.ConfigPlanChecks{
59+
PostApplyPostRefresh: []plancheck.PlanCheck{
60+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
61+
},
62+
},
63+
},
64+
},
65+
})
66+
}
67+
68+
func testSDKDisappearsWithWrongAction(t *testing.T) {
69+
ctx := acctest.Context(t)
70+
71+
const resourceName = "aws_example_thing.test"
72+
73+
acctest.ParallelTest(t, resource.TestCase{
74+
PreCheck: func() { acctest.PreCheck(ctx, t) },
75+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
76+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
77+
CheckDestroy: testAccCheckDestroy(ctx),
78+
Steps: []resource.TestStep{
79+
{
80+
Config: testAccConfig_basic(),
81+
Check: resource.ComposeTestCheckFunc(
82+
testAccCheckExists(ctx, t, resourceName),
83+
// ruleid: disappears-expect-resource-action
84+
acctest.CheckSDKResourceDisappears(ctx, t, ResourceThing(), resourceName),
85+
),
86+
ExpectNonEmptyPlan: true,
87+
ConfigPlanChecks: resource.ConfigPlanChecks{
88+
PostApplyPostRefresh: []plancheck.PlanCheck{
89+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
90+
},
91+
},
92+
},
93+
},
94+
})
95+
}
96+
97+
func testFrameworkDisappearsWithoutPlanCheck(t *testing.T) {
98+
ctx := acctest.Context(t)
99+
100+
const resourceName = "aws_example_thing.test"
101+
102+
acctest.ParallelTest(t, resource.TestCase{
103+
PreCheck: func() { acctest.PreCheck(ctx, t) },
104+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
105+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
106+
CheckDestroy: testAccCheckDestroy(ctx),
107+
Steps: []resource.TestStep{
108+
{
109+
Config: testAccConfig_basic(),
110+
Check: resource.ComposeAggregateTestCheckFunc(
111+
testAccCheckExists(ctx, t, resourceName),
112+
// ruleid: disappears-expect-resource-action
113+
acctest.CheckFrameworkResourceDisappears(ctx, t, ResourceThing, resourceName),
114+
),
115+
ExpectNonEmptyPlan: true,
116+
},
117+
},
118+
})
119+
}
120+
121+
func testFrameworkDisappearsWithPlanCheck(t *testing.T) {
122+
ctx := acctest.Context(t)
123+
124+
const resourceName = "aws_example_thing.test"
125+
126+
acctest.ParallelTest(t, resource.TestCase{
127+
PreCheck: func() { acctest.PreCheck(ctx, t) },
128+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
129+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
130+
CheckDestroy: testAccCheckDestroy(ctx),
131+
Steps: []resource.TestStep{
132+
{
133+
Config: testAccConfig_basic(),
134+
Check: resource.ComposeAggregateTestCheckFunc(
135+
testAccCheckExists(ctx, t, resourceName),
136+
// ok: disappears-expect-resource-action
137+
acctest.CheckFrameworkResourceDisappears(ctx, t, ResourceThing, resourceName),
138+
),
139+
ExpectNonEmptyPlan: true,
140+
ConfigPlanChecks: resource.ConfigPlanChecks{
141+
PostApplyPostRefresh: []plancheck.PlanCheck{
142+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
143+
},
144+
},
145+
},
146+
},
147+
})
148+
}
149+
150+
func testFrameworkDisappearsWithWrongAction(t *testing.T) {
151+
ctx := acctest.Context(t)
152+
153+
const resourceName = "aws_example_thing.test"
154+
155+
acctest.ParallelTest(t, resource.TestCase{
156+
PreCheck: func() { acctest.PreCheck(ctx, t) },
157+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
158+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
159+
CheckDestroy: testAccCheckDestroy(ctx),
160+
Steps: []resource.TestStep{
161+
{
162+
Config: testAccConfig_basic(),
163+
Check: resource.ComposeAggregateTestCheckFunc(
164+
testAccCheckExists(ctx, t, resourceName),
165+
// ruleid: disappears-expect-resource-action
166+
acctest.CheckFrameworkResourceDisappears(ctx, t, ResourceThing, resourceName),
167+
),
168+
ExpectNonEmptyPlan: true,
169+
ConfigPlanChecks: resource.ConfigPlanChecks{
170+
PostApplyPostRefresh: []plancheck.PlanCheck{
171+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
172+
},
173+
},
174+
},
175+
},
176+
})
177+
}
178+
179+
func testFrameworkStateFuncDisappearsWithoutPlanCheck(t *testing.T) {
180+
ctx := acctest.Context(t)
181+
182+
const resourceName = "aws_example_thing.test"
183+
184+
acctest.ParallelTest(t, resource.TestCase{
185+
PreCheck: func() { acctest.PreCheck(ctx, t) },
186+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
187+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
188+
CheckDestroy: testAccCheckDestroy(ctx),
189+
Steps: []resource.TestStep{
190+
{
191+
Config: testAccConfig_basic(),
192+
Check: resource.ComposeTestCheckFunc(
193+
testAccCheckExists(ctx, t, resourceName),
194+
// ruleid: disappears-expect-resource-action
195+
acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, t, ResourceThing, resourceName, stateFunc),
196+
),
197+
ExpectNonEmptyPlan: true,
198+
},
199+
},
200+
})
201+
}
202+
203+
func testFrameworkStateFuncDisappearsWithPlanCheck(t *testing.T) {
204+
ctx := acctest.Context(t)
205+
206+
const resourceName = "aws_example_thing.test"
207+
208+
acctest.ParallelTest(t, resource.TestCase{
209+
PreCheck: func() { acctest.PreCheck(ctx, t) },
210+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
211+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
212+
CheckDestroy: testAccCheckDestroy(ctx),
213+
Steps: []resource.TestStep{
214+
{
215+
Config: testAccConfig_basic(),
216+
Check: resource.ComposeTestCheckFunc(
217+
testAccCheckExists(ctx, t, resourceName),
218+
// ok: disappears-expect-resource-action
219+
acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, t, ResourceThing, resourceName, stateFunc),
220+
),
221+
ExpectNonEmptyPlan: true,
222+
ConfigPlanChecks: resource.ConfigPlanChecks{
223+
PostApplyPostRefresh: []plancheck.PlanCheck{
224+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
225+
},
226+
},
227+
},
228+
},
229+
})
230+
}
231+
232+
func testFrameworkStateFuncDisappearsWithWrongAction(t *testing.T) {
233+
ctx := acctest.Context(t)
234+
235+
const resourceName = "aws_example_thing.test"
236+
237+
acctest.ParallelTest(t, resource.TestCase{
238+
PreCheck: func() { acctest.PreCheck(ctx, t) },
239+
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
240+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
241+
CheckDestroy: testAccCheckDestroy(ctx),
242+
Steps: []resource.TestStep{
243+
{
244+
Config: testAccConfig_basic(),
245+
Check: resource.ComposeTestCheckFunc(
246+
testAccCheckExists(ctx, t, resourceName),
247+
// ruleid: disappears-expect-resource-action
248+
acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, t, ResourceThing, resourceName, stateFunc),
249+
),
250+
ExpectNonEmptyPlan: true,
251+
ConfigPlanChecks: resource.ConfigPlanChecks{
252+
PostApplyPostRefresh: []plancheck.PlanCheck{
253+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
254+
},
255+
},
256+
},
257+
},
258+
})
259+
}

.ci/semgrep/acctest/disappears.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,46 @@ rules:
2222
pattern: acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, acctest.Provider, $RES, $NAME, $F)
2323
fix: acctest.CheckFrameworkResourceDisappearsWithStateFunc(ctx, t, $RES, $NAME, $F)
2424
severity: ERROR
25+
26+
- id: disappears-expect-resource-action
27+
languages: [go]
28+
message: |
29+
Disappears acceptance tests should include `ConfigPlanChecks` with a `PostApplyPostRefresh` plan check
30+
using `plancheck.ExpectResourceAction` to verify the resource is planned for re-creation.
31+
severity: ERROR
32+
patterns:
33+
- pattern-either:
34+
- pattern: acctest.CheckSDKResourceDisappears(...)
35+
- pattern: acctest.CheckFrameworkResourceDisappears(...)
36+
- pattern: acctest.CheckFrameworkResourceDisappearsWithStateFunc(...)
37+
- pattern-inside: |
38+
Steps: []resource.TestStep{
39+
...,
40+
}
41+
- pattern-not-inside: |
42+
{
43+
...,
44+
ConfigPlanChecks: resource.ConfigPlanChecks{
45+
...,
46+
PostApplyPostRefresh: []plancheck.PlanCheck{
47+
...,
48+
plancheck.ExpectResourceAction($NAME, plancheck.ResourceActionCreate),
49+
...,
50+
},
51+
...,
52+
},
53+
...,
54+
}
55+
# To roll out gradually, only fully-compliant services are included in the
56+
# paths list below. As each service is updated, add its path to the list.
57+
paths:
58+
include:
59+
- "/internal/service/bedrockagentcore/*_test.go"
60+
- "/internal/service/billing/*_test.go"
61+
- "/internal/service/cur/*_test.go"
62+
- "/internal/service/networkflowmonitor/*_test.go"
63+
- "/internal/service/notifications/*_test.go"
64+
- "/internal/service/notificationscontacts/*_test.go"
65+
- "/internal/service/observabilityadmin/*_test.go"
66+
- "/internal/service/timestreaminfluxdb/*_test.go"
67+
- "/internal/service/workmail/*_test.go"

0 commit comments

Comments
 (0)