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