| 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 }); |
| } |
| }); |