Skip to content

Commit d1a924c

Browse files
committed
Add docs for TF test parallel execution (#36372)
1 parent 8440a77 commit d1a924c

File tree

1 file changed

+245
-1
lines changed

1 file changed

+245
-1
lines changed

website/docs/language/tests/index.mdx

Lines changed: 245 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ Each Terraform test lives in a test file. Terraform discovers test files are bas
2424

2525
Each test file contains the following root level attributes and blocks:
2626

27+
- Zero to one [`test`](#test-block) blocks.
2728
- One to many [`run`](#run-blocks) blocks.
2829
- Zero to one [`variables`](#variables) block.
2930
- Zero to many [`provider`](#providers) blocks.
3031

31-
Terraform executes `run` blocks in order, simulating a series of Terraform commands executing directly within the configuration directory. The order of the `variables` and `provider` blocks doesn't matter, Terraform processes all the values within these blocks at the beginning of the test operation. We recommend defining your `variables` and `provider` blocks first, at the beginning of the test file.
32+
By default, Terraform executes `run` blocks sequentially. To execute `run` blocks in parallel, refer to [`Parallel execution`](#parallel-execution). Each `run` block simulates a series of Terraform commands executing directly within the configuration directory. The order of the `variables` and `provider` blocks doesn't matter, Terraform processes all the values within these blocks at the beginning of the test operation. We recommend defining your `variables` and `provider` blocks first, at the beginning of the test file.
3233

3334
### Example
3435

@@ -75,6 +76,24 @@ run "valid_string_concat" {
7576
}
7677
```
7778

79+
## Test block
80+
81+
The optional `test` block defines the configuration of the test file, allowing you to configure how the framework
82+
executes its runs. The block has the following fields:
83+
84+
| Field or Block Name | Description | Default Value |
85+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------- |
86+
| `parallel` | An optional boolean attribute. If `true`, Terraform executes all eligible `run` blocks simultaneously. Refer to [Parallel execution](#parallel-execution) for more information. | `false` |
87+
88+
### Example usage
89+
90+
```hcl
91+
# with_config.tftest.hcl
92+
test {
93+
parallel = true
94+
}
95+
```
96+
7897
## Run blocks
7998

8099
Each `run` block has the following fields and blocks:
@@ -92,6 +111,7 @@ Each `run` block has the following fields and blocks:
92111
| [`assert`](#assertions) | Optional `assert` blocks. | |
93112
| `expect_failures` | An optional attribute. | |
94113
| `state_key` | An optional attribute. | |
114+
| `parallel` | An optional boolean attribute. | `false` |
95115

96116
The `command` attribute and `plan_options` block tell Terraform which command and options to execute for each run block. The default operation, if you do not specify a `command` attribute or the `plan_options` block, is a normal Terraform apply operation.
97117

@@ -101,6 +121,45 @@ The `plan_options` block allows test authors to customize the [planning mode](/t
101121

102122
The `state_key` allows for fine-grained control over which internal state file Terraform uses for a given run block. Refer to [Modules State](#modules-state) for more information.
103123

124+
The `parallel` attribute lets you run multiple `run` blocks in parallel. By default, this attribute is set to `false`. When set to `true`, Terraform attempts to execute the `run` block in parallel with other `run` blocks that also have the `parallel` attribute set to `true` and are not dependent on each other.
125+
126+
### Example usage
127+
128+
```hcl
129+
# with_config.tftest.hcl
130+
test {
131+
parallel = true
132+
}
133+
134+
variables {
135+
bucket_prefix = "test"
136+
}
137+
138+
run "first" {
139+
assert {
140+
condition = aws_s3_bucket.bucket.bucket == "test-bucket"
141+
error_message = "S3 bucket name did not match expected"
142+
}
143+
}
144+
145+
run "second" {
146+
assert {
147+
condition = aws_s3_bucket.bucket.bucket == "test-bucket"
148+
error_message = "S3 bucket name did not match expected"
149+
}
150+
}
151+
152+
run "third" {
153+
parallel = false
154+
assert {
155+
condition = aws_s3_bucket.bucket.bucket == "test-bucket"
156+
error_message = "S3 bucket name did not match expected"
157+
}
158+
}
159+
```
160+
161+
In the above example, the `first` and `second` `run` blocks implicitly have `parallel` set to `true` since the `test` block enables parallel runs. The `third` `run` block sets `parallel` to `false` to override the global setting.
162+
104163
### Assertions
105164

106165
Terraform run block assertions are [Custom Conditions](/terraform/language/expressions/custom-conditions), consisting of a [condition](/terraform/language/expressions/custom-conditions#condition-expressions) and an [error message](/terraform/language/expressions/custom-conditions#error-messages).
@@ -766,3 +825,188 @@ There are instances when Terraform does not execute a custom condition during th
766825
Other kinds of failure _besides_ the specified expected failures in the checkable object still result in the overall test failing. For example, a variable that expects a boolean value as input fails the surrounding test if Terraform provides the wrong kind of value, even if that variable is included in an `expect_failures` attribute.
767826

768827
The `expect_failures` attribute is included to allow authors to test their configuration and any logic defined within. A type mismatch, as in the previous example, is not something Terraform authors should have to worry about testing as Terraform itself will handle enforce type constraints. As such, you can only `expect_failures` in custom conditions.
828+
829+
## Parallel execution
830+
831+
By default, Terraform executes `run` blocks sequentially. However, you can set the `parallel` attribute to `true` in the optional `test` block or in individual `run` blocks to enable parallel execution.
832+
833+
Two or more `run` blocks can be executed in parallel if:
834+
835+
- They do not reference outputs from each other.
836+
- They do not share the same state file. The state file is determined by the state key or the module source when the state key is not set. If two `run` blocks that share the same module configuration, they must specify different state keys to be executed in parallel.
837+
- They both have the `parallel` attribute set to `true`.
838+
839+
### Example usage
840+
841+
```hcl
842+
# parallel.tftest.hcl
843+
844+
test {
845+
// This would set the parallel flag to true in all runs
846+
parallel = true
847+
}
848+
849+
variables {
850+
foo = "foo"
851+
}
852+
853+
854+
run "primary_db" {
855+
// This is the first run block, and it is available to be executed right away.
856+
state_key = "primary"
857+
module {
858+
source = "./setup"
859+
}
860+
861+
variables {
862+
input = "foo"
863+
}
864+
865+
assert {
866+
condition = output.value == var.foo
867+
error_message = "bad"
868+
}
869+
}
870+
871+
run "secondary_db" {
872+
// This run block can be executed in parallel with the `primary_db` run block, because it does not reference its
873+
// output and has a different state key.
874+
state_key = "secondary"
875+
module {
876+
source = "./setup"
877+
}
878+
879+
variables {
880+
input = "foo"
881+
}
882+
883+
assert {
884+
condition = output.value == var.foo
885+
error_message = "bad"
886+
}
887+
}
888+
889+
run "site_one" {
890+
// This run block can only be executed after the `primary_db` run block is completed, because it references the output of the `primary_db` run block.
891+
state_key = "unique_2"
892+
variables {
893+
input = run.primary_db.value
894+
}
895+
896+
assert {
897+
condition = output.value == var.foo
898+
error_message = "double bad"
899+
}
900+
}
901+
902+
run "site_two" {
903+
// This run block can only be executed after the `primary_db` run block is completed, because it references the output of the `primary_db` run block.
904+
// After that, it can be executed in parallel with the `site_one` run block.
905+
state_key = "unique_3"
906+
variables {
907+
input = run.primary_db.value
908+
}
909+
910+
assert {
911+
condition = output.value == var.foo
912+
error_message = "double bad"
913+
}
914+
}
915+
916+
run "using_external_db" {
917+
// This run block does not reference the output of any other run block, and it has a different state key from its
918+
// preceding runs, so it can be executed in parallel with runs `primary_db` and `secondary_db`.
919+
state_key = "unique_4"
920+
variables {
921+
input = "externally_created_db"
922+
}
923+
924+
assert {
925+
condition = output.value == var.foo
926+
error_message = "double bad"
927+
}
928+
}
929+
930+
run "site_four" {
931+
// This run block has set `parallel = false`.
932+
// Therefore, it will wait for all preceding runs to complete before it can be executed.
933+
state_key = "unique_5"
934+
935+
// This overrides the global parallel flag.
936+
parallel = false
937+
variables {
938+
input = run.secondary_db.value
939+
}
940+
941+
assert {
942+
condition = output.value == var.foo
943+
error_message = "double bad"
944+
}
945+
}
946+
947+
run "site_five" {
948+
// This run block will wait for the run `site_four` to complete, because run `site_four` has the `parallel` attribute set to `false`.
949+
state_key = "unique_6"
950+
variables {
951+
input = run.secondary_db.value
952+
}
953+
954+
assert {
955+
condition = output.value == var.foo
956+
error_message = "double bad"
957+
}
958+
}
959+
960+
run "site_six" {
961+
// This run block can only be executed after the run `site_five` is completed, because it references the output of the run `site_five`.
962+
state_key = "unique_7"
963+
variables {
964+
input = run.site_five.value
965+
}
966+
967+
assert {
968+
condition = output.value == var.foo
969+
error_message = "double bad"
970+
}
971+
}
972+
973+
run "same_state" {
974+
// This run block uses an existing state key "unique_7", so it will wait for the run `site_six` to complete.
975+
state_key = "unique_7"
976+
variables {
977+
input = "another_external_db"
978+
}
979+
980+
assert {
981+
condition = output.value == var.foo
982+
error_message = "double bad"
983+
}
984+
}
985+
986+
run "site_eight" {
987+
// This run block is entirely unrelated to the other run blocks, however, because `site_four`,
988+
// which is one of its preceding runs has the `parallel` attribute set to `false`,
989+
// it cannot run until site_four is completed. It can be executed in parallel with `site_five` and `site_six`.
990+
state_key = "unique_7"
991+
variables {
992+
input = "yet_another_external_db"
993+
}
994+
995+
assert {
996+
condition = output.value == var.foo
997+
error_message = "double bad"
998+
}
999+
}
1000+
```
1001+
1002+
-> **Note:** When you configure a series of runs that have `parallel=true` in a test file and include a single run with `parallel=false` you create synchronization point that divides the workflow into two groups. All runs before the `parallel=false` run must complete first. Then, after the run with `parallel=false` completes, the subsequent runs begin.
1003+
1004+
```
1005+
Run A (parallel: true)
1006+
Run B (parallel: true)
1007+
Run C (parallel: false)
1008+
Run D (parallel: true)
1009+
Run E (parallel: true)
1010+
```
1011+
1012+
Runs A and B execute simultaneously. Run C waits until both A and B are done before starting. Finally, runs D and E run in parallel, but only after C completes.

0 commit comments

Comments
 (0)