/** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/import { assert, unreachable } from '../util/util.js'; export class SkipTestCase extends Error {} export class UnexpectedPassError extends Error {} export { TestCaseRecorder } from '../internal/logging/test_case_recorder.js'; /** The fully-general type for params passed to a test function invocation. */ export class SubcaseBatchState { constructor( recorder, /** The case parameters for this test fixture shared state. Subcase params are not included. */ params) {this.recorder = recorder;this.params = params;} /** * Runs before the `.before()` function. * @internal MAINTENANCE_TODO: Make this not visible to test code? */ async init() {} /** * Runs between the `.before()` function and the subcases. * @internal MAINTENANCE_TODO: Make this not visible to test code? */ async postInit() {} /** * Runs after all subcases finish. * @internal MAINTENANCE_TODO: Make this not visible to test code? */ async finalize() {} /** Throws an exception marking the subcase as skipped. */ skip(msg) { throw new SkipTestCase(msg); } /** Throws an exception making the subcase as skipped if condition is true */ skipIf(cond, msg = '') { if (cond) { this.skip(typeof msg === 'function' ? msg() : msg); } } } /** * A Fixture is a class used to instantiate each test sub/case at run time. * A new instance of the Fixture is created for every single test subcase * (i.e. every time the test function is run). */ export class Fixture { /** * Interface for recording logs and test status. * * @internal */ eventualExpectations = []; numOutstandingAsyncExpectations = 0; objectsToCleanUp = []; static MakeSharedState(recorder, params) { return new SubcaseBatchState(recorder, params); } /** @internal */ constructor(sharedState, rec, params) { this._sharedState = sharedState; this.rec = rec; this._params = params; } /** * Returns the (case+subcase) parameters for this test function invocation. */ get params() { return this._params; } /** * Gets the test fixture's shared state. This object is shared between subcases * within the same testcase. */ get sharedState() { return this._sharedState; } /** * Override this to do additional pre-test-function work in a derived fixture. * This has to be a member function instead of an async `createFixture` function, because * we need to be able to ergonomically override it in subclasses. * * @internal MAINTENANCE_TODO: Make this not visible to test code? */ async init() {} /** * Override this to do additional post-test-function work in a derived fixture. * * Called even if init was unsuccessful. * * @internal MAINTENANCE_TODO: Make this not visible to test code? */ async finalize() { assert( this.numOutstandingAsyncExpectations === 0, 'there were outstanding immediateAsyncExpectations (e.g. expectUncapturedError) at the end of the test' ); // Loop to exhaust the eventualExpectations in case they chain off each other. while (this.eventualExpectations.length) { const p = this.eventualExpectations.shift(); try { await p; } catch (ex) { this.rec.threw(ex); } } // And clean up any objects now that they're done being used. for (const o of this.objectsToCleanUp) { if ('getExtension' in o) { const WEBGL_lose_context = o.getExtension('WEBGL_lose_context'); if (WEBGL_lose_context) WEBGL_lose_context.loseContext(); } else if ('destroy' in o) { o.destroy(); } else if ('destroyAsync' in o) { await o.destroyAsync(); } else if ('close' in o) { o.close(); } else { // HTMLVideoElement o.src = ''; o.srcObject = null; } } } /** * Tracks an object to be cleaned up after the test finishes. * * Usually when creating buffers/textures/query sets, you can use the helpers in GPUTest instead. */ trackForCleanup(o) { if (o instanceof Promise) { this.eventualAsyncExpectation(() => o.then( (o) => this.trackForCleanup(o), () => {} ) ); return o; } if (o instanceof GPUDevice) { this.objectsToCleanUp.push({ async destroyAsync() { o.destroy(); await o.lost; } }); } else { this.objectsToCleanUp.push(o); } return o; } /** Tracks an object, if it's destroyable, to be cleaned up after the test finishes. */ tryTrackForCleanup(o) { if (typeof o === 'object' && o !== null) { if ( 'destroy' in o || 'close' in o || o instanceof WebGLRenderingContext || o instanceof WebGL2RenderingContext) { this.objectsToCleanUp.push(o); } } return o; } /** Call requestDevice() and track the device for cleanup. */ requestDeviceTracked(adapter, desc = undefined) { return this.trackForCleanup(adapter.requestDevice(desc)); } /** Log a debug message. */ debug(msg) { if (!this.rec.debugging) return; if (typeof msg === 'function') { msg = msg(); } this.rec.debug(new Error(msg)); } /** * Log an info message. * **Use sparingly. Use `debug()` instead if logs are only needed with debug logging enabled.** */ info(msg) { this.rec.info(new Error(msg)); } /** Throws an exception marking the subcase as skipped. */ skip(msg) { throw new SkipTestCase(msg); } /** Throws an exception marking the subcase as skipped if condition is true */ skipIf(cond, msg = '') { if (cond) { this.skip(typeof msg === 'function' ? msg() : msg); } } /** Log a warning and increase the result status to "Warn". */ warn(msg) { this.rec.warn(new Error(msg)); } /** Log an error and increase the result status to "ExpectFailed". */ fail(msg) { this.rec.expectationFailed(new Error(msg)); } /** * Wraps an async function. Tracks its status to fail if the test tries to report a test status * before the async work has finished. */ async immediateAsyncExpectation(fn) { this.numOutstandingAsyncExpectations++; const ret = await fn(); this.numOutstandingAsyncExpectations--; return ret; } /** * Wraps an async function, passing it an `Error` object recording the original stack trace. * The async work will be implicitly waited upon before reporting a test status. */ eventualAsyncExpectation(fn) { const promise = fn(new Error()); this.eventualExpectations.push(promise); } expectErrorValue(expectedError, ex, niceStack) { if (!(ex instanceof Error)) { niceStack.message = `THREW non-error value, of type ${typeof ex}: ${ex}`; this.rec.expectationFailed(niceStack); return; } const actualName = ex.name; if (expectedError !== true && actualName !== expectedError) { niceStack.message = `THREW ${actualName}, instead of ${expectedError}: ${ex}`; this.rec.expectationFailed(niceStack); } else { niceStack.message = `OK: threw ${actualName}: ${ex.message}`; this.rec.debug(niceStack); } } /** Expect that the provided promise resolves (fulfills). */ shouldResolve(p, msg) { this.eventualAsyncExpectation(async (niceStack) => { const m = msg ? ': ' + msg : ''; try { await p; niceStack.message = 'resolved as expected' + m; } catch (ex) { niceStack.message = `REJECTED${m}`; if (ex instanceof Error) { niceStack.message += '\n' + ex.message; } this.rec.expectationFailed(niceStack); } }); } /** Expect that the provided promise rejects, with the provided exception name. */ shouldReject( expectedName, p, { allowMissingStack = false, message } = {}) { this.eventualAsyncExpectation(async (niceStack) => { const m = message ? ': ' + message : ''; try { await p; niceStack.message = 'DID NOT REJECT' + m; this.rec.expectationFailed(niceStack); } catch (ex) { this.expectErrorValue(expectedName, ex, niceStack); if (!allowMissingStack) { if (!(ex instanceof Error && typeof ex.stack === 'string')) { const exMessage = ex instanceof Error ? ex.message : '?'; niceStack.message = `rejected as expected, but missing stack (${exMessage})${m}`; this.rec.expectationFailed(niceStack); } } } }); } /** * Expect that the provided function throws (if `true` or `string`) or not (if `false`). * If a string is provided, expect that the throw exception has that name. * * MAINTENANCE_TODO: Change to `string | false` so the exception name is always checked. */ shouldThrow( expectedError, fn, { allowMissingStack = false, message } = {}) { const m = message ? ': ' + message : ''; try { fn(); if (expectedError === false) { this.rec.debug(new Error('did not throw, as expected' + m)); } else { this.rec.expectationFailed(new Error('unexpectedly did not throw' + m)); } } catch (ex) { if (expectedError === false) { this.rec.expectationFailed(new Error('threw unexpectedly' + m)); } else { this.expectErrorValue(expectedError, ex, new Error(m)); if (!allowMissingStack) { if (!(ex instanceof Error && typeof ex.stack === 'string')) { this.rec.expectationFailed(new Error('threw as expected, but missing stack' + m)); } } } } } /** * Expect that a condition is true. * * Note: You can pass a boolean condition, or a function that returns a boolean. * The advantage to passing a function is that if it's short it is self documenting. * * t.expect(size >= maxSize); // prints Expect OK: * t.expect(() => size >= maxSize) // prints Expect OK: () => size >= maxSize */ expect(cond, msg) { if (typeof cond === 'function') { if (msg === undefined) { msg = cond.toString(); } cond = cond(); } if (cond) { if (this.rec.debugging) { const m = msg ? ': ' + (typeof msg === 'function' ? msg() : msg) : ''; this.rec.debug(new Error('expect OK' + m)); } } else { this.rec.expectationFailed(new Error(typeof msg === 'function' ? msg() : msg)); } return cond; } /** * If the argument is an `Error`, fail (or warn). If it's `undefined`, no-op. * If the argument is an array, apply the above behavior on each of elements. */ expectOK( error, { mode = 'fail', niceStack } = {}) { const handleError = (error) => { if (error instanceof Error) { if (niceStack) { error.stack = niceStack.stack; } if (mode === 'fail') { this.rec.expectationFailed(error); } else if (mode === 'warn') { this.rec.warn(error); } else { unreachable(); } } }; if (Array.isArray(error)) { for (const e of error) { handleError(e); } } else { handleError(error); } } eventualExpectOK( error, { mode = 'fail' } = {}) { this.eventualAsyncExpectation(async (niceStack) => { this.expectOK(await error, { mode, niceStack }); }); } } /** * FixtureClass encapsulates a constructor for fixture and a corresponding * shared state factory function. An interface version of the type is also * defined for mixin declaration use ONLY. The interface version is necessary * because mixin classes need a constructor with a single any[] rest * parameter. */