After implementing vendoring in part 1, it's time to run it! This patch was broken out from part 1 to isolate very tedious portion of the review. Most of this patch is composed of: 1. Generated CTS test files from part 1. 2. A massive wall of test expectation management acknowledging current passes and failures. Currently, Linux and Windows are expected to pass with these noted failures. Many, but not all, current failures on MacOS are recorded. Differential Revision: https://phabricator.services.mozilla.com/D169953
3.7 KiB
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 likeexpect(),skip().GPUTest: Wraps every test in error scopes. Provides helpers likeexpectContents().ValidationTest: ExtendsGPUTest, provides helpers likeexpectValidationError(),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
GPUDevices 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 GPUDevices 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
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,
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:
t.expectValidationError(() => {
device.createThing();
});
does:
pushErrorScope('validation')popErrorScope()and "eventually" check whether it returned an error.
Example:
t.expectGPUBufferValuesEqual(srcBuffer, expectedData);
does:
- copy
srcBufferinto a new mappable bufferdst 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.