Skip to content

Commit 9be16b6

Browse files
a-frantzadthrasher
andauthored
feat: output assertions (#29)
* feat: output assertions * call for feedback on future assertions * feat: document new `Empty` assertion * review feedback * Update subcommands/test.md Co-authored-by: Andrew Thrasher <adthrasher@gmail.com> --------- Co-authored-by: Andrew Thrasher <adthrasher@gmail.com>
1 parent 672bb64 commit 9be16b6

1 file changed

Lines changed: 92 additions & 19 deletions

File tree

subcommands/test.md

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,35 @@ This is a single test named "kitchen_sink" which defines an input matrix that ge
7373

7474
If not otherwise specified, a test is considered successful so long as the entrypoint runs to completion, exits without an error, and all outputs are evaluated successfully. For tasks, the test framework assumes the expected exit code is `0`.
7575

76-
Fail conditions can be tested by specifying an `assertions:` section for the YAML definition. The assertions available depend on whether the entrypoint is a task or a workflow. At the time of writing, the only assertion available for workflows is `should_fail: <boolean>`. This defaults to `false`, but may be specified as `should_fail: true`, which will invert expectations. The `should_fail` assertion is ignored for task executions. To unit test a fail case for a task entrypoint, a non-zero `exit_code: <integer>` should be specified.
76+
### Assertions
77+
78+
Assertions are available for ensuring more complex expectations than simple "success" remain true. These more complex conditions can be specified with an `assertions:` section for the YAML definition.
79+
80+
The assertions available depend on whether the entrypoint is a workflow or a task.
81+
82+
For workflows, there is a `should_fail: <boolean>` assertion. This defaults to `false`, but may be specified as `should_fail: true`. The `should_fail` assertion is ignored for task executions.
83+
84+
```yaml
85+
validate_flag_filter: # this is a workflow
86+
- name: valid_FlagFilter
87+
inputs:
88+
flags:
89+
- include_if_all: "3"
90+
exclude_if_any: "0xF04"
91+
include_if_any: "03"
92+
exclude_if_all: "4095"
93+
- name: invalid_FlagFilter
94+
inputs:
95+
flags:
96+
- include_if_all: "3"
97+
exclude_if_any: "0xF04"
98+
include_if_any: "03"
99+
exclude_if_all: "whoops! I'll trigger a fail :("
100+
assertions:
101+
should_fail: true
102+
```
103+
104+
To unit test a fail case for a task, a non-zero `exit_code: <integer>` can be specified.
77105

78106
```yaml
79107
validate_string_is_12bit_int: # this is a task
@@ -96,28 +124,11 @@ validate_string_is_12bit_int: # this is a task
96124
- "+1"
97125
assertions:
98126
exit_code: 42
99-
validate_flag_filter: # this is a workflow
100-
- name: valid_FlagFilter
101-
inputs:
102-
flags:
103-
- include_if_all: "3"
104-
exclude_if_any: "0xF04"
105-
include_if_any: "03"
106-
exclude_if_all: "4095"
107-
- name: invalid_FlagFilter
108-
inputs:
109-
flags:
110-
- include_if_all: "3"
111-
exclude_if_any: "0xF04"
112-
include_if_any: "03"
113-
exclude_if_all: "whoops! I'll trigger a fail :("
114-
assertions:
115-
should_fail: true
116127
```
117128

118129
Assertions are shared by all executions of a test. In the example above, there are 4 executions defined for the `validate_string_is_12bit_int::valid_numbers` test. This test is considered passed if all 4 of those executions evaluate with an exit code of `0`. The `invalid_numbers` test contains 7 executions, and every one of those executions must exit with a code of `42` for the test to be considered a success.
119130

120-
At the time of writing, the only other assertions available for tasks are the `stdout` and `stderr` assertions. Both of these work very similarly; they expect a YAML sequence of strings that are interpreted as [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) which should match on the task's STDOUT/STDERR stream. For example:
131+
Other assertions available for tasks are the `stdout` and `stderr` assertions. Both of these work very similarly; they expect a YAML sequence of strings that are interpreted as [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) which should match on the task's STDOUT/STDERR stream. For example:
121132

122133
```yaml
123134
validate_string_is_12bit_int:
@@ -134,6 +145,68 @@ validate_string_is_12bit_int:
134145
- But number must be less than 4096!
135146
```
136147

148+
#### Output Assertions
149+
150+
It is often important to test for specific properties of the outputs for a WDL task or workflow. This is possible via the `outputs` section of the `assertions` YAML block. Just like other assertions, output assertions are expected to pass for every execution of a test.
151+
152+
```yaml
153+
read_group_to_string:
154+
- name: valid_read_groups
155+
inputs:
156+
read_group:
157+
- ID: R1
158+
SM: sampleFoo
159+
- ID: R1
160+
SM: sampleFoo
161+
LB: spaces are allowed in LB
162+
BC: barcode with a space
163+
assertions:
164+
outputs:
165+
validated_read_group: # this output is of type `String`
166+
- Contains: R1
167+
- Contains: sampleFoo
168+
```
169+
170+
Each key under `outputs` should be an exact match to an output of the task or workflow. The assertions available for that output are going to depend on the WDL type of the output. It wouldn't make sense to assert whether a WDL `Float` "contains" a string, so Sprocket will emit an informative error if there are any type mismatches between the assertions used and the expected type of each output.
171+
172+
The currently supported basic output assertions are:
173+
174+
- `Defined: <boolean>`: is the output defined (i.e. any value other than `None`)?
175+
- available for all optional WDL types
176+
- `BoolEquals: <boolean>`: is the output `Boolean` equal to this value?
177+
- `StrEquals: <string>`: is the output `String` equal to this value?
178+
- `IntEquals: <integer>`: is the output `Int` equal to this value?
179+
- `FloatEquals: <float>`: is the output `Float` equal to this value?
180+
- `Contains: <string>`: does the output `String` contain this substring?
181+
- `Name: <string>`: does the output `File` or `Directory` have this exact match basename?
182+
- `Length: <unsigned integer>`: does the output `String`, `Array`, or `Map` have this length?
183+
- `Empty: <boolean>`: is the output `String`, `Array`, or `Map` empty?
184+
- "empty" for `String` types means length zero
185+
186+
There are also several recursive output assertions which can be combined with the above in order to test properties of compound WDL types:
187+
188+
- `First: <inner assertion>`: applies the inner assertion to the first element of an output `Array`
189+
- `Last: <inner assertion>`: applies the inner assertion to the last element of an output `Array`
190+
- `Left: <inner assertion>`: applies the inner assertion to the left element of an output `Pair`
191+
- `Right: <inner assertion>`: applies the inner assertion to the right element of an output `Pair`
192+
193+
An example combining these:
194+
195+
```yaml
196+
assertions:
197+
outputs:
198+
fusion_bams: # this output is of type `Array[File]`
199+
- Length: 2
200+
- First: { Name: fusions_1.bam }
201+
- Last: { Name: fusions_2.bam }
202+
```
203+
204+
::: tip Other assertions
205+
206+
The above assertions are just a starting point! We intend to continue to grow the list of builtin assertions available to `test` in subsequent releases. Please reach out via the [#sprocket channel on the WDL Slack](https://join.slack.com/t/openwdl/shared_invite/zt-ctmj4mhf-cFBNxIiZYs6SY9HgM9UAVw) or file a feature request on [the Sprocket repository](https://github.com/stjude-rust-labs/sprocket/issues) to let us know what would be most useful to you during WDL development.
207+
208+
:::
209+
137210
### How to use test fixtures
138211

139212
WDL tasks and workflows often take file inputs, so the Sprocket unit testing framework has attempted to make it as easy as possible to reference "test fixtures" from test YAML.

0 commit comments

Comments
 (0)