diff --git a/.changes/unreleased/ENHANCEMENTS-20250123-101838.yaml b/.changes/unreleased/ENHANCEMENTS-20250123-101838.yaml new file mode 100644 index 000000000000..f60563616df5 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20250123-101838.yaml @@ -0,0 +1,5 @@ +kind: ENHANCEMENTS +body: Terraform Test command now accepts a -parallelism=n option, which sets the number of parallel operations in a test run's plan/apply operation. +time: 2025-01-23T10:18:38.979866+01:00 +custom: + Issue: "34237" diff --git a/go.mod b/go.mod index db39a54f5c84..005a998b6e68 100644 --- a/go.mod +++ b/go.mod @@ -28,12 +28,12 @@ require ( github.com/hashicorp/go-plugin v1.6.0 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/hashicorp/go-slug v0.16.3 - github.com/hashicorp/go-tfe v1.70.0 + github.com/hashicorp/go-tfe v1.74.1 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/hcl/v2 v2.23.1-0.20250203194505-ba0759438da2 - github.com/hashicorp/jsonapi v1.3.1 + github.com/hashicorp/jsonapi v1.3.2 github.com/hashicorp/terraform-registry-address v0.2.3 github.com/hashicorp/terraform-svchost v0.1.1 github.com/hashicorp/terraform/internal/backend/remote-state/azure v0.0.0-00010101000000-000000000000 @@ -256,7 +256,7 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.9.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index 9cdc192534c0..b01ca1eb7782 100644 --- a/go.sum +++ b/go.sum @@ -1123,8 +1123,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-tfe v1.70.0 h1:R5a9Z+jdVz6eRWtSLsl1nw+5Qe/swunZcJgeKK5NQtQ= -github.com/hashicorp/go-tfe v1.70.0/go.mod h1:2rOcdTxXwbWm0W7dCKjC3Ec8KQ+HhW165GiurXNshc4= +github.com/hashicorp/go-tfe v1.74.1 h1:I/8fOwSYox17IZV7SULIQH0ZRPNL2g/biW6hHWnOTVY= +github.com/hashicorp/go-tfe v1.74.1/go.mod h1:kGHWMZ3HHjitgqON8nBZ4kPVJ3cLbzM4JMgmNVMs9aQ= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= @@ -1141,8 +1141,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl/v2 v2.23.1-0.20250203194505-ba0759438da2 h1:JP8y98OtHTujECs4s/HxlKc5yql/RlC99Dt1Iz4R+lM= github.com/hashicorp/hcl/v2 v2.23.1-0.20250203194505-ba0759438da2/go.mod h1:k+HgkLpoWu9OS81sy4j1XKDXaWm/rLysG33v5ibdDnc= -github.com/hashicorp/jsonapi v1.3.1 h1:GtPvnmcWgYwCuDGvYT5VZBHcUyFdq9lSyCzDjn1DdPo= -github.com/hashicorp/jsonapi v1.3.1/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM= +github.com/hashicorp/jsonapi v1.3.2 h1:gP3fX2ZT7qXi+PbwieptzkspIohO2kCSiBUvUTBAbMs= +github.com/hashicorp/jsonapi v1.3.2/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= @@ -1460,8 +1460,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.588 h1:DYtBXB7sVc3EOW5horg8j55cLZynhsLYhHrvQ/jXKKM= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.588/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= @@ -1917,8 +1917,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/backend/remote-state/kubernetes/go.mod b/internal/backend/remote-state/kubernetes/go.mod index c82c77d67c0d..10449c5fb720 100644 --- a/internal/backend/remote-state/kubernetes/go.mod +++ b/internal/backend/remote-state/kubernetes/go.mod @@ -64,7 +64,7 @@ require ( golang.org/x/sys v0.29.0 // indirect golang.org/x/term v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.25.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/internal/backend/remote-state/kubernetes/go.sum b/internal/backend/remote-state/kubernetes/go.sum index 7da99cb5c9a0..c6af694f1c6c 100644 --- a/internal/backend/remote-state/kubernetes/go.sum +++ b/internal/backend/remote-state/kubernetes/go.sum @@ -492,8 +492,8 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/internal/cloud/test.go b/internal/cloud/test.go index e81d07494ff1..3f97be0f8d23 100644 --- a/internal/cloud/test.go +++ b/internal/cloud/test.go @@ -82,6 +82,10 @@ type TestSuiteRunner struct { // Verbose tells the runner to print out plan files during each test run. Verbose bool + // OperationParallelism is the limit Terraform places on total parallel operations + // during the plan or apply command within a single test run. + OperationParallelism int + // Filters restricts which test files will be executed. Filters []string @@ -204,6 +208,7 @@ func (runner *TestSuiteRunner) Test() (moduletest.Status, tfdiags.Diagnostics) { Filters: runner.Filters, TestDirectory: tfe.String(runner.TestingDirectory), Verbose: tfe.Bool(runner.Verbose), + Parallelism: tfe.Int(runner.OperationParallelism), Variables: func() []*tfe.RunVariable { runVariables := make([]*tfe.RunVariable, 0, len(variables)) for name, value := range variables { diff --git a/internal/cloud/test_test.go b/internal/cloud/test_test.go index 2b1dbee164dc..e33cec14df08 100644 --- a/internal/cloud/test_test.go +++ b/internal/cloud/test_test.go @@ -103,6 +103,81 @@ Success! 2 passed, 0 failed. } } +func TestTest_Parallelism(t *testing.T) { + + streams, _ := terminal.StreamsForTesting(t) + view := views.NewTest(arguments.ViewHuman, views.NewView(streams)) + + colorize := mockColorize() + colorize.Disable = true + + mock := NewMockClient() + client := &tfe.Client{ + ConfigurationVersions: mock.ConfigurationVersions, + Organizations: mock.Organizations, + RegistryModules: mock.RegistryModules, + TestRuns: mock.TestRuns, + } + + if _, err := client.Organizations.Create(context.Background(), tfe.OrganizationCreateOptions{ + Name: tfe.String("organisation"), + }); err != nil { + t.Fatalf("failed to create organisation: %v", err) + } + + if _, err := client.RegistryModules.Create(context.Background(), "organisation", tfe.RegistryModuleCreateOptions{ + Name: tfe.String("name"), + Provider: tfe.String("provider"), + RegistryName: "app.terraform.io", + Namespace: "organisation", + }); err != nil { + t.Fatalf("failed to create registry module: %v", err) + } + + runner := TestSuiteRunner{ + // Configuration data. + ConfigDirectory: "testdata/test", + TestingDirectory: "tests", + Config: nil, // We don't need this for this test. + Source: "app.terraform.io/organisation/name/provider", + + // Cancellation controls, we won't be doing any cancellations in this + // test. + Stopped: false, + Cancelled: false, + StoppedCtx: context.Background(), + CancelledCtx: context.Background(), + + // Test Options, empty for this test. + GlobalVariables: nil, + Verbose: false, + OperationParallelism: 4, + Filters: nil, + + // Outputs + Renderer: &jsonformat.Renderer{ + Streams: streams, + Colorize: colorize, + RunningInAutomation: false, + }, + View: view, + Streams: streams, + + // Networking + Services: nil, // Don't need this when the client is overridden. + clientOverride: client, + } + + _, diags := runner.Test() + if len(diags) > 0 { + t.Errorf("found diags and expected none: %s", diags.ErrWithWarnings()) + } + + if mock.TestRuns.parallelism != 4 { + t.Errorf("expected parallelism to be 4 but was %d", mock.TestRuns.parallelism) + } +} + func TestTest_JSON(t *testing.T) { streams, done := terminal.StreamsForTesting(t) diff --git a/internal/cloud/tfe_client_mock.go b/internal/cloud/tfe_client_mock.go index 53bfc374cf45..5a5ccaf33b70 100644 --- a/internal/cloud/tfe_client_mock.go +++ b/internal/cloud/tfe_client_mock.go @@ -1673,9 +1673,10 @@ type MockTestRuns struct { client *MockClient // TestRuns and modules keep track of our tfe.TestRun objects. - TestRuns map[string]*tfe.TestRun - modules map[string][]*tfe.TestRun - logs map[string]string + TestRuns map[string]*tfe.TestRun + modules map[string][]*tfe.TestRun + logs map[string]string + parallelism int // delayedCancel allows a mock test run to cancel an operation instead of // completing an operation. It's used @@ -1767,6 +1768,9 @@ func (m *MockTestRuns) Create(ctx context.Context, options tfe.TestRunCreateOpti "test.log", ) m.modules[tr.RegistryModule.ID] = append(m.modules[tr.RegistryModule.ID], tr) + if options.Parallelism != nil { + m.parallelism = *options.Parallelism + } return tr, nil } @@ -2170,6 +2174,30 @@ func (m *MockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, opt return w, nil } +func (m *MockWorkspaces) ListEffectiveTagBindings(ctx context.Context, workspaceID string) ([]*tfe.EffectiveTagBinding, error) { + w, ok := m.workspaceIDs[workspaceID] + if !ok { + return nil, tfe.ErrResourceNotFound + } + var effectiveTagBindings []*tfe.EffectiveTagBinding + for _, tb := range w.TagBindings { + effectiveTagBindings = append(effectiveTagBindings, &tfe.EffectiveTagBinding{ + Key: tb.Key, + Value: tb.Value, + }) + } + return effectiveTagBindings, nil +} + +func (m *MockWorkspaces) DeleteAllTagBindings(ctx context.Context, workspaceID string) error { + w, ok := m.workspaceIDs[workspaceID] + if !ok { + return tfe.ErrResourceNotFound + } + w.TagBindings = nil + return nil +} + func updateMockWorkspaceAttributes(w *tfe.Workspace, options tfe.WorkspaceUpdateOptions) error { // for TestCloud_setUnavailableTerraformVersion if w.Name == "unavailable-terraform-version" && options.TerraformVersion != nil { diff --git a/internal/command/arguments/test.go b/internal/command/arguments/test.go index 848ac819157a..bd5d3ec2131d 100644 --- a/internal/command/arguments/test.go +++ b/internal/command/arguments/test.go @@ -18,6 +18,10 @@ type Test struct { // will be executed. Filter []string + // OperationParallelism is the limit Terraform places on total parallel operations + // during the plan or apply command within a single test run. + OperationParallelism 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 @@ -55,6 +59,7 @@ func ParseTest(args []string) (*Test, tfdiags.Diagnostics) { cmdFlags.BoolVar(&jsonOutput, "json", false, "json") cmdFlags.StringVar(&test.JUnitXMLFile, "junit-xml", "", "junit-xml") cmdFlags.BoolVar(&test.Verbose, "verbose", false, "verbose") + cmdFlags.IntVar(&test.OperationParallelism, "parallelism", DefaultParallelism, "parallelism") // TODO: Finalise the name of this flag. cmdFlags.StringVar(&test.CloudRunSource, "cloud-run", "", "cloud-run") @@ -73,6 +78,10 @@ func ParseTest(args []string) (*Test, tfdiags.Diagnostics) { "The -junit-xml option is currently not compatible with remote test execution via the -cloud-run flag. If you are interested in JUnit XML output for remotely-executed tests please open an issue in GitHub.")) } + if test.OperationParallelism < 1 { + test.OperationParallelism = DefaultParallelism + } + switch { case jsonOutput: test.ViewType = ViewJSON diff --git a/internal/command/arguments/test_test.go b/internal/command/arguments/test_test.go index be02f149c7d0..018c961721ab 100644 --- a/internal/command/arguments/test_test.go +++ b/internal/command/arguments/test_test.go @@ -72,60 +72,88 @@ func TestParseTest(t *testing.T) { "defaults": { args: nil, want: &Test{ - Filter: nil, - TestDirectory: "tests", - ViewType: ViewHuman, - Vars: &Vars{}, + Filter: nil, + TestDirectory: "tests", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 10, }, wantDiags: nil, }, "with-filters": { args: []string{"-filter=one.tftest.hcl", "-filter=two.tftest.hcl"}, want: &Test{ - Filter: []string{"one.tftest.hcl", "two.tftest.hcl"}, - TestDirectory: "tests", - ViewType: ViewHuman, - Vars: &Vars{}, + Filter: []string{"one.tftest.hcl", "two.tftest.hcl"}, + TestDirectory: "tests", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 10, }, wantDiags: nil, }, "json": { args: []string{"-json"}, want: &Test{ - Filter: nil, - TestDirectory: "tests", - ViewType: ViewJSON, - Vars: &Vars{}, + Filter: nil, + TestDirectory: "tests", + ViewType: ViewJSON, + Vars: &Vars{}, + OperationParallelism: 10, }, wantDiags: nil, }, "test-directory": { args: []string{"-test-directory=other"}, want: &Test{ - Filter: nil, - TestDirectory: "other", - ViewType: ViewHuman, - Vars: &Vars{}, + Filter: nil, + TestDirectory: "other", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 10, }, wantDiags: nil, }, "verbose": { args: []string{"-verbose"}, want: &Test{ - Filter: nil, - TestDirectory: "tests", - ViewType: ViewHuman, - Verbose: true, - Vars: &Vars{}, + Filter: nil, + TestDirectory: "tests", + ViewType: ViewHuman, + Verbose: true, + Vars: &Vars{}, + OperationParallelism: 10, }, }, + "with-parallelism-set": { + args: []string{"-parallelism=5"}, + want: &Test{ + Filter: nil, + TestDirectory: "tests", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 5, + }, + wantDiags: nil, + }, + "with-parallelism-0": { + args: []string{"-parallelism=0"}, + want: &Test{ + Filter: nil, + TestDirectory: "tests", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 10, + }, + wantDiags: nil, + }, "unknown flag": { args: []string{"-boop"}, want: &Test{ - Filter: nil, - TestDirectory: "tests", - ViewType: ViewHuman, - Vars: &Vars{}, + Filter: nil, + TestDirectory: "tests", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 10, }, wantDiags: tfdiags.Diagnostics{ tfdiags.Sourceless( @@ -138,12 +166,13 @@ func TestParseTest(t *testing.T) { "incompatible flags: -junit-xml and -cloud-run": { args: []string{"-junit-xml=./output.xml", "-cloud-run=foobar"}, want: &Test{ - CloudRunSource: "foobar", - JUnitXMLFile: "./output.xml", - Filter: nil, - TestDirectory: "tests", - ViewType: ViewHuman, - Vars: &Vars{}, + CloudRunSource: "foobar", + JUnitXMLFile: "./output.xml", + Filter: nil, + TestDirectory: "tests", + ViewType: ViewHuman, + Vars: &Vars{}, + OperationParallelism: 10, }, wantDiags: tfdiags.Diagnostics{ tfdiags.Sourceless( diff --git a/internal/command/test.go b/internal/command/test.go index 38ccf46f5025..8b236fe779ac 100644 --- a/internal/command/test.go +++ b/internal/command/test.go @@ -59,6 +59,9 @@ Options: -no-color If specified, output won't contain any color. + -parallelism=n Limit the number of concurrent operations within the + plan/apply operation of a test run. Defaults to 10. + -test-directory=path Set the Terraform test directory, defaults to "tests". -var 'foo=bar' Set a value for one of the input variables in the root @@ -99,6 +102,7 @@ func (c *TestCommand) Run(rawArgs []string) int { c.View.HelpPrompt("test") return 1 } + c.Meta.parallelism = args.OperationParallelism view := views.NewTest(args.ViewType, c.View) @@ -183,21 +187,22 @@ func (c *TestCommand) Run(rawArgs []string) int { } runner = &cloud.TestSuiteRunner{ - ConfigDirectory: ".", // Always loading from the current directory. - TestingDirectory: args.TestDirectory, - Config: config, - Services: c.Services, - Source: args.CloudRunSource, - GlobalVariables: variables, - Stopped: false, - Cancelled: false, - StoppedCtx: stopCtx, - CancelledCtx: cancelCtx, - Verbose: args.Verbose, - Filters: args.Filter, - Renderer: renderer, - View: view, - Streams: c.Streams, + ConfigDirectory: ".", // Always loading from the current directory. + TestingDirectory: args.TestDirectory, + Config: config, + Services: c.Services, + Source: args.CloudRunSource, + GlobalVariables: variables, + Stopped: false, + Cancelled: false, + StoppedCtx: stopCtx, + CancelledCtx: cancelCtx, + Verbose: args.Verbose, + OperationParallelism: args.OperationParallelism, + Filters: args.Filter, + Renderer: renderer, + View: view, + Streams: c.Streams, } } else { localRunner := &local.TestSuiteRunner{ diff --git a/internal/command/test_test.go b/internal/command/test_test.go index 0a232c6a06b6..216276c132b0 100644 --- a/internal/command/test_test.go +++ b/internal/command/test_test.go @@ -35,6 +35,7 @@ func TestTest_Runs(t *testing.T) { code int initCode int skip bool + desc string }{ "simple_pass": { expectedOut: []string{"1 passed, 0 failed."}, @@ -54,6 +55,13 @@ func TestTest_Runs(t *testing.T) { expectedOut: []string{"1 passed, 0 failed."}, code: 0, }, + "simple_pass_cmd_parallel": { + override: "simple_pass", + args: []string{"-parallelism", "1"}, + expectedOut: []string{"1 passed, 0 failed."}, + code: 0, + desc: "simple_pass with parallelism set to 1", + }, "simple_pass_very_nested_alternate": { override: "simple_pass_very_nested", args: []string{"-test-directory", "./tests/subdir"}, diff --git a/website/docs/cli/commands/test.mdx b/website/docs/cli/commands/test.mdx index 555a3add7e8e..1e1e2bed7a54 100644 --- a/website/docs/cli/commands/test.mdx +++ b/website/docs/cli/commands/test.mdx @@ -38,6 +38,8 @@ The following options apply to the Terraform `terraform test` command: * `-verbose` - Prints out the plan or state for each `run` block within a test file, based on the `command` attribute of each `run` block. +* `-parallelism=` - Specifies the number of plan/apply operations to execute in parallel within a single test run. The default is 10. + ## State Management Each Terraform test file will maintain all Terraform state it requires within memory as it executes, starting empty. This state is entirely separate from any existing state for the configuration under test, so you can safely execute Terraform test commands without affecting any live infrastructure.