blob: 2348ac1ea99ebb7827bc84b943ada13065c46129 [file] [log] [blame]
export const description = `
Error scope validation tests.
Note these must create their own device, not use GPUTest (that one already has error scopes on it).
TODO: (POSTV1) Test error scopes of different threads and make sure they go to the right place.
TODO: (POSTV1) Test that unhandled errors go the right device, and nowhere if the device was dropped.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { kErrorScopeFilters, kGeneratableErrorScopeFilters } from '../../capability_info.js';
import { ErrorTest } from '../../error_test.js';
export const g = makeTestGroup(ErrorTest);
g.test('simple')
.desc(
`
Tests that error scopes catches their expected errors, firing an uncaptured error event otherwise.
- Same error and error filter (popErrorScope should return the error)
- Different error from filter (uncaptured error should result)
`
)
.params(u =>
u.combine('errorType', kGeneratableErrorScopeFilters).combine('errorFilter', kErrorScopeFilters)
)
.fn(async t => {
const { errorType, errorFilter } = t.params;
t.device.pushErrorScope(errorFilter);
if (errorType !== errorFilter) {
// Different error case
const uncapturedErrorEvent = await t.expectUncapturedError(() => {
t.generateError(errorType);
});
t.expect(t.isInstanceOfError(errorType, uncapturedErrorEvent.error));
const error = await t.device.popErrorScope();
t.expect(error === null);
} else {
// Same error as filter
t.generateError(errorType);
const error = await t.device.popErrorScope();
t.expect(t.isInstanceOfError(errorType, error));
}
});
g.test('empty')
.desc(
`
Tests that popping an empty error scope stack should reject.
`
)
.fn(t => {
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise, { allowMissingStack: true });
});
g.test('parent_scope')
.desc(
`
Tests that an error bubbles to the correct parent scope.
- Different error types as the parent scope
- Different depths of non-capturing filters for the generated error
`
)
.params(u =>
u
.combine('errorFilter', kGeneratableErrorScopeFilters)
.combine('stackDepth', [1, 10, 100, 1000])
)
.fn(async t => {
const { errorFilter, stackDepth } = t.params;
t.device.pushErrorScope(errorFilter);
// Push a bunch of error filters onto the stack (none that match errorFilter)
const unmatchedFilters = kErrorScopeFilters.filter(filter => {
return filter !== errorFilter;
});
for (let i = 0; i < stackDepth; i++) {
t.device.pushErrorScope(unmatchedFilters[i % unmatchedFilters.length]);
}
// Cause the error and then pop all the unrelated filters.
t.generateError(errorFilter);
await t.chunkedPopManyErrorScopes(stackDepth);
// Finally the actual error should have been caught by the parent scope.
const error = await t.device.popErrorScope();
t.expect(t.isInstanceOfError(errorFilter, error));
});
g.test('current_scope')
.desc(
`
Tests that an error does not bubbles to parent scopes when local scope matches.
- Different error types as the current scope
- Different depths of non-capturing filters for the generated error
`
)
.params(u =>
u
.combine('errorFilter', kGeneratableErrorScopeFilters)
.combine('stackDepth', [1, 10, 100, 1000, 100000])
)
.fn(async t => {
const { errorFilter, stackDepth } = t.params;
// Push a bunch of error filters onto the stack
for (let i = 0; i < stackDepth; i++) {
t.device.pushErrorScope(kErrorScopeFilters[i % kErrorScopeFilters.length]);
}
// Current scope should catch the error immediately.
t.device.pushErrorScope(errorFilter);
t.generateError(errorFilter);
const error = await t.device.popErrorScope();
t.expect(t.isInstanceOfError(errorFilter, error));
// Remaining scopes shouldn't catch anything.
await t.chunkedPopManyErrorScopes(stackDepth);
});
g.test('balanced_siblings')
.desc(
`
Tests that sibling error scopes need to be balanced.
- Different error types as the current scope
- Different number of sibling errors
`
)
.params(u =>
u.combine('errorFilter', kErrorScopeFilters).combine('numErrors', [1, 10, 100, 1000])
)
.fn(async t => {
const { errorFilter, numErrors } = t.params;
const promises = [];
for (let i = 0; i < numErrors; i++) {
t.device.pushErrorScope(errorFilter);
promises.push(t.device.popErrorScope());
}
{
// Trying to pop an additional non-existing scope should reject.
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise, { allowMissingStack: true });
}
const errors = await Promise.all(promises);
t.expect(errors.every(e => e === null));
});
g.test('balanced_nesting')
.desc(
`
Tests that nested error scopes need to be balanced.
- Different error types as the current scope
- Different number of nested errors
`
)
.params(u =>
u.combine('errorFilter', kErrorScopeFilters).combine('numErrors', [1, 10, 100, 1000])
)
.fn(async t => {
const { errorFilter, numErrors } = t.params;
for (let i = 0; i < numErrors; i++) {
t.device.pushErrorScope(errorFilter);
}
const promises = [];
for (let i = 0; i < numErrors; i++) {
promises.push(t.device.popErrorScope());
}
const errors = await Promise.all(promises);
t.expect(errors.every(e => e === null));
{
// Trying to pop an additional non-existing scope should reject.
const promise = t.device.popErrorScope();
t.shouldReject('OperationError', promise, { allowMissingStack: true });
}
});