1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# Test Implementation
Concepts important to understand when writing tests. See existing tests for examples to copy from.
## Test fixtures
Most tests can use one of the several common test fixtures:
- `Fixture`: Base fixture, provides core functions like `expect()`, `skip()`.
- `GPUTest`: Wraps every test in error scopes. Provides helpers like `expectContents()`.
- `ValidationTest`: Extends `GPUTest`, provides helpers like `expectValidationError()`, `getErrorTextureView()`.
- Or create your own. (Often not necessary - helper functions can be used instead.)
Test fixtures or helper functions may be defined in `.spec.ts` files, but if used by multiple
test files, should be defined in separate `.ts` files (without `.spec`) alongside the files that
use them.
### GPUDevices in tests
`GPUDevice`s are largely stateless (except for `lost`-ness, error scope stack, and `label`).
This allows the CTS to reuse one device across multiple test cases using the `DevicePool`,
which provides `GPUDevice` objects to tests.
Currently, there is one `GPUDevice` with the default descriptor, and
a cache of several more, for devices with additional capabilities.
Devices in the `DevicePool` are automatically removed when certain things go wrong.
Later, there may be multiple `GPUDevice`s to allow multiple test cases to run concurrently.
## Test parameterization
The CTS provides helpers (`.params()` and friends) for creating large cartesian products of test parameters.
These generate "test cases" further subdivided into "test subcases".
See `basic,*` in `examples.spec.ts` for examples, and the [helper index](./helper_index.txt)
for a list of capabilities.
Test parameterization should be applied liberally to ensure the maximum coverage
possible within reasonable time. You can skip some with `.filter()`. And remember: computers are
pretty fast - thousands of test cases can be reasonable.
Use existing lists of parameters values (such as
[`kTextureFormats`](https://github.com/gpuweb/cts/blob/0f38b85/src/suites/cts/capability_info.ts#L61),
to parameterize tests), instead of making your own list. Use the info tables (such as
`kTextureFormatInfo`) to define and retrieve information about the parameters.
## Asynchrony in tests
Since there are no synchronous operations in WebGPU, almost every test is asynchronous in some
way. For example:
- Checking the result of a readback.
- Capturing the result of a `popErrorScope()`.
That said, test functions don't always need to be `async`; see below.
### Checking asynchronous errors/results
Validation is inherently asynchronous (`popErrorScope()` returns a promise). However, the error
scope stack itself is synchronous - operations immediately after a `popErrorScope()` are outside
that error scope.
As a result, tests can assert things like validation errors/successes without having an `async`
test body.
**Example:**
```typescript
t.expectValidationError(() => {
device.createThing();
});
```
does:
- `pushErrorScope('validation')`
- `popErrorScope()` and "eventually" check whether it returned an error.
**Example:**
```typescript
t.expectGPUBufferValuesEqual(srcBuffer, expectedData);
```
does:
- copy `srcBuffer` into a new mappable buffer `dst`
- `dst.mapReadAsync()`, and "eventually" check what data it returned.
Internally, this is accomplished via an "eventual expectation": `eventualAsyncExpectation()`
takes an async function, calls it immediately, and stores off the resulting `Promise` to
automatically await at the end before determining the pass/fail state.
### Asynchronous parallelism
A side effect of test asynchrony is that it's possible for multiple tests to be in flight at
once. We do not currently do this, but it will eventually be an option to run `N` tests in
"parallel", for faster local test runs.
|