Skip to content

Terraform Test: Allow skipping cleanup of entire test file or individual run blocks #36729

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

Merged
merged 4 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
381 changes: 353 additions & 28 deletions internal/command/test_test.go

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions internal/command/testdata/test/skip_cleanup/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "id" {
type = string
}

resource "test_resource" "resource" {
value = var.id
}

output "id" {
value = test_resource.resource.id
}
31 changes: 31 additions & 0 deletions internal/command/testdata/test/skip_cleanup/main.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
run "test" {
variables {
id = "test"
}
}

run "test_two" {
skip_cleanup = true
variables {
id = "test_two"
}
}

run "test_three" {
skip_cleanup = true
variables {
id = "test_three"
}
}

run "test_four" {
variables {
id = "test_four"
}
}

run "test_five" {
variables {
id = "test_five"
}
}
11 changes: 11 additions & 0 deletions internal/command/testdata/test/skip_file_cleanup/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "id" {
type = string
}

resource "test_resource" "resource" {
value = var.id
}

output "id" {
value = test_resource.resource.id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
test {
skip_cleanup = true
}

run "test" {
variables {
id = "test"
}
}

run "test_two" {
variables {
id = "test_two"
}
}

run "test_three" {
variables {
id = "test_three"
}
}

run "test_four" {
variables {
id = "test_four"
}
}

run "test_five" {
variables {
id = "test_five"
}
}
6 changes: 4 additions & 2 deletions internal/command/views/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,8 @@ func (t *TestHuman) DestroySummary(diags tfdiags.Diagnostics, run *moduletest.Ru
}
t.Diagnostics(run, file, diags)

if state.HasManagedResourceInstanceObjects() {
skipCleanup := run != nil && run.Config.SkipCleanup
if state.HasManagedResourceInstanceObjects() && !skipCleanup {
// FIXME: This message says "resources" but this is actually a list
// of resource instance objects.
t.view.streams.Eprint(format.WordWrap(fmt.Sprintf("\nTerraform left the following resources in state after executing %s, and they need to be cleaned up manually:\n", identifier), t.view.errorColumns()))
Expand Down Expand Up @@ -602,7 +603,8 @@ func (t *TestJSON) Run(run *moduletest.Run, file *moduletest.File, progress modu
}

func (t *TestJSON) DestroySummary(diags tfdiags.Diagnostics, run *moduletest.Run, file *moduletest.File, state *states.State) {
if state.HasManagedResourceInstanceObjects() {
skipCleanup := run != nil && run.Config.SkipCleanup
if state.HasManagedResourceInstanceObjects() && !skipCleanup {
cleanup := json.TestFileCleanup{}
for _, resource := range addrs.SetSortedNatural(state.AllManagedResourceInstanceObjectAddrs()) {
cleanup.FailedResources = append(cleanup.FailedResources, json.TestFailedResource{
Expand Down
36 changes: 18 additions & 18 deletions internal/command/views/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ func TestTestHuman_Run(t *testing.T) {
StdErr string
}{
"pass": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
Progress: moduletest.Complete,
StdOut: " run \"run_block\"... pass\n",
},
Expand All @@ -502,19 +502,19 @@ some warning happened during this test
},

"pending": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pending},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pending},
Progress: moduletest.Complete,
StdOut: " run \"run_block\"... pending\n",
},

"skip": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Skip},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Skip},
Progress: moduletest.Complete,
StdOut: " run \"run_block\"... skip\n",
},

"fail": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Fail},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Fail},
Progress: moduletest.Complete,
StdOut: " run \"run_block\"... fail\n",
},
Expand Down Expand Up @@ -542,7 +542,7 @@ other details
},

"error": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Error},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Error},
Progress: moduletest.Complete,
StdOut: " run \"run_block\"... fail\n",
},
Expand Down Expand Up @@ -725,15 +725,15 @@ resource "test_resource" "creating" {
// These next three tests should print nothing, as we only report on
// progress complete.
"progress_starting": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
Progress: moduletest.Starting,
},
"progress_running": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
Progress: moduletest.Running,
},
"progress_teardown": {
Run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
Run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
Progress: moduletest.TearDown,
},
}
Expand Down Expand Up @@ -822,7 +822,7 @@ this time it is very bad
diags: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Error, "first error", "this time it is very bad"),
},
run: &moduletest.Run{Name: "run_block"},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}},
file: &moduletest.File{Name: "main.tftest.hcl"},
state: states.NewState(),
stderr: `Terraform encountered an error destroying resources created while executing
Expand Down Expand Up @@ -1973,7 +1973,7 @@ func TestTestJSON_DestroySummary(t *testing.T) {
},
"state_from_run": {
file: &moduletest.File{Name: "main.tftest.hcl"},
run: &moduletest.Run{Name: "run_block"},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}},
state: states.BuildState(func(state *states.SyncState) {
state.SetResourceInstanceCurrent(
addrs.Resource{
Expand Down Expand Up @@ -2380,7 +2380,7 @@ func TestTestJSON_Run(t *testing.T) {
want []map[string]interface{}
}{
"starting": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
progress: moduletest.Starting,
want: []map[string]interface{}{
{
Expand All @@ -2401,7 +2401,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"running": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
progress: moduletest.Running,
elapsed: 2024,
want: []map[string]interface{}{
Expand All @@ -2423,7 +2423,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"teardown": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
progress: moduletest.TearDown,
want: []map[string]interface{}{
{
Expand All @@ -2444,7 +2444,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"pass": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pass},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pass},
progress: moduletest.Complete,
want: []map[string]interface{}{
{
Expand Down Expand Up @@ -2503,7 +2503,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"pending": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Pending},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Pending},
progress: moduletest.Complete,
want: []map[string]interface{}{
{
Expand All @@ -2524,7 +2524,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"skip": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Skip},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Skip},
progress: moduletest.Complete,
want: []map[string]interface{}{
{
Expand All @@ -2545,7 +2545,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"fail": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Fail},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Fail},
progress: moduletest.Complete,
want: []map[string]interface{}{
{
Expand Down Expand Up @@ -2620,7 +2620,7 @@ func TestTestJSON_Run(t *testing.T) {
},

"error": {
run: &moduletest.Run{Name: "run_block", Status: moduletest.Error},
run: &moduletest.Run{Name: "run_block", Config: &configs.TestRun{}, Status: moduletest.Error},
progress: moduletest.Complete,
want: []map[string]interface{}{
{
Expand Down
21 changes: 21 additions & 0 deletions internal/configs/test_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ type TestFileConfig struct {
// Parallel: Indicates if test runs should be executed in parallel.
Parallel bool

// SkipCleanup: Indicates if the test runs should skip the cleanup phase.
SkipCleanup bool

DeclRange hcl.Range
}

Expand Down Expand Up @@ -164,6 +167,10 @@ type TestRun struct {

Backend *Backend

// SkipCleanup: Indicates if the test run should skip the cleanup phase.
SkipCleanup bool
SkipCleanupSet bool

NameDeclRange hcl.Range
VariablesDeclRange hcl.Range
DeclRange hcl.Range
Expand Down Expand Up @@ -575,6 +582,11 @@ func decodeFileConfigBlock(fileContent *hcl.BodyContent) (*TestFileConfig, hcl.D
diags = append(diags, rawDiags...)
}

if attr, exists := content.Attributes["skip_cleanup"]; exists {
rawDiags := gohcl.DecodeExpression(attr.Expr, nil, &ret.SkipCleanup)
diags = append(diags, rawDiags...)
}

return ret, diags
}

Expand All @@ -591,6 +603,7 @@ func decodeTestRunBlock(block *hcl.Block, file *TestFile) (*TestRun, hcl.Diagnos
NameDeclRange: block.LabelRanges[0],
DeclRange: block.DefRange,
Parallel: file.Config != nil && file.Config.Parallel,
SkipCleanup: file.Config != nil && file.Config.SkipCleanup,
}

if !hclsyntax.ValidIdentifier(r.Name) {
Expand Down Expand Up @@ -822,6 +835,12 @@ func decodeTestRunBlock(block *hcl.Block, file *TestFile) (*TestRun, hcl.Diagnos
})
}

if attr, exists := content.Attributes["skip_cleanup"]; exists {
rawDiags := gohcl.DecodeExpression(attr.Expr, nil, &r.SkipCleanup)
diags = append(diags, rawDiags...)
r.SkipCleanupSet = true
}

return &r, diags
}

Expand Down Expand Up @@ -1021,6 +1040,7 @@ var testFileSchema = &hcl.BodySchema{
var testFileConfigBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "parallel"},
{Name: "skip_cleanup"},
},
}

Expand All @@ -1031,6 +1051,7 @@ var testRunBlockSchema = &hcl.BodySchema{
{Name: "expect_failures"},
{Name: "state_key"},
{Name: "parallel"},
{Name: "skip_cleanup"},
},
Blocks: []hcl.BlockHeaderSchema{
{
Expand Down
Loading
Loading