Skip to content
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
5 changes: 5 additions & 0 deletions .changes/v1.13/BUG FIXES-20250704-182248.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: Test run Parallelism of 1 should not result in deadlock
time: 2025-07-04T18:22:48.934287+02:00
custom:
Issue: "37292"
5 changes: 5 additions & 0 deletions internal/command/arguments/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type Test struct {
// during the plan or apply command within a single test run.
OperationParallelism int

// RunParallelism is the limit Terraform places on parallel test runs. This
// is the number of test runs that can be executed in parallel within a file.
RunParallelism int

// TestDirectory allows the user to override the directory that the test
// command will use to discover test files, defaults to "tests". Regardless
// of the value here, test files within the configuration directory will
Expand Down Expand Up @@ -60,6 +64,7 @@ func ParseTest(args []string) (*Test, tfdiags.Diagnostics) {
cmdFlags.StringVar(&test.JUnitXMLFile, "junit-xml", "", "junit-xml")
cmdFlags.BoolVar(&test.Verbose, "verbose", false, "verbose")
cmdFlags.IntVar(&test.OperationParallelism, "parallelism", DefaultParallelism, "parallelism")
cmdFlags.IntVar(&test.RunParallelism, "run-parallelism", DefaultParallelism, "run-parallelism")

// TODO: Finalise the name of this flag.
cmdFlags.StringVar(&test.CloudRunSource, "cloud-run", "", "cloud-run")
Expand Down
35 changes: 35 additions & 0 deletions internal/command/arguments/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: nil,
},
Expand All @@ -88,6 +89,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: nil,
},
Expand All @@ -99,6 +101,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewJSON,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: nil,
},
Expand All @@ -110,6 +113,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: nil,
},
Expand All @@ -122,6 +126,7 @@ func TestParseTest(t *testing.T) {
Verbose: true,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
},
"with-parallelism-set": {
Expand All @@ -132,6 +137,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 5,
RunParallelism: 10,
},
wantDiags: nil,
},
Expand All @@ -143,9 +149,11 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: nil,
},

"cloud-with-parallelism-0": {
args: []string{"-parallelism=0", "-cloud-run=foobar"},
want: &Test{
Expand All @@ -155,6 +163,31 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 0,
RunParallelism: 10,
},
wantDiags: nil,
},
"with-run-parallelism-set": {
args: []string{"-run-parallelism=10"},
want: &Test{
Filter: nil,
TestDirectory: "tests",
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: nil,
},
"with-run-parallelism-0": {
args: []string{"-run-parallelism=0"},
want: &Test{
Filter: nil,
TestDirectory: "tests",
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 0,
},
wantDiags: nil,
},
Expand All @@ -166,6 +199,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: tfdiags.Diagnostics{
tfdiags.Sourceless(
Expand All @@ -185,6 +219,7 @@ func TestParseTest(t *testing.T) {
ViewType: ViewHuman,
Vars: &Vars{},
OperationParallelism: 10,
RunParallelism: 10,
},
wantDiags: tfdiags.Diagnostics{
tfdiags.Sourceless(
Expand Down
1 change: 1 addition & 0 deletions internal/command/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ func (c *TestCommand) Run(rawArgs []string) int {
CancelledCtx: cancelCtx,
Filter: args.Filter,
Verbose: args.Verbose,
Concurrency: args.RunParallelism,
}

// JUnit output is only compatible with local test execution
Expand Down
5 changes: 5 additions & 0 deletions internal/command/test_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ func TestTest_Runs(t *testing.T) {
expectedOut: []string{"1 passed, 0 failed."},
code: 0,
},
"simple_pass_count": {
expectedOut: []string{"1 passed, 0 failed."},
args: []string{"-run-parallelism", "1"},
code: 0,
},
"simple_pass_nested_alternate": {
args: []string{"-test-directory", "other"},
expectedOut: []string{"1 passed, 0 failed."},
Expand Down
4 changes: 4 additions & 0 deletions internal/command/testdata/test/simple_pass_count/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "test_resource" "foo" {
count = 3
value = "bar"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
run "validate_test_resource" {
assert {
condition = test_resource.foo[0].value == "bar"
error_message = "invalid value"
}
}
8 changes: 6 additions & 2 deletions internal/moduletest/graph/test_graph_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ func Walk(g *terraform.Graph, ctx *EvalContext) tfdiags.Diagnostics {
log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v))
}()

ctx.evalSem.Acquire()
defer ctx.evalSem.Release()
// expandable nodes are not executed, but they are walked and
// their children are executed, so they need not acquire the semaphore themselves.
if _, ok := v.(Subgrapher); !ok {
ctx.evalSem.Acquire()
defer ctx.evalSem.Release()
}

if executable, ok := v.(GraphNodeExecutable); ok {
executable.Execute(ctx)
Expand Down
11 changes: 10 additions & 1 deletion internal/moduletest/graph/transform_state_cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import (
"github.com/hashicorp/terraform/internal/terraform"
)

var _ GraphNodeExecutable = &TeardownSubgraph{}
var (
_ GraphNodeExecutable = &TeardownSubgraph{}
_ Subgrapher = &TeardownSubgraph{}
)

type Subgrapher interface {
isSubGrapher()
}

// TeardownSubgraph is a subgraph for cleaning up the state of
// resources defined in the state files created by the test runs.
Expand Down Expand Up @@ -54,6 +61,8 @@ func (b *TeardownSubgraph) Execute(ctx *EvalContext) {
b.opts.File.AppendDiagnostics(diags)
}

func (b *TeardownSubgraph) isSubGrapher() {}

// TestStateCleanupTransformer is a GraphTransformer that adds a cleanup node
// for each state that is created by the test runs.
type TestStateCleanupTransformer struct {
Expand Down