diff options
Diffstat (limited to 'testing/web-platform/mozilla/tests/webgpu/common/runtime/helper')
5 files changed, 327 insertions, 58 deletions
diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js index 139c3bc29f..01b7d95889 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js @@ -1,11 +1,14 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/let windowURL = undefined;function getWindowURL() {if (windowURL === undefined) { +**/import { unreachable } from '../../util/util.js';let windowURL = undefined; +function getWindowURL() { + if (windowURL === undefined) { windowURL = new URL(window.location.toString()); } return windowURL; } +/** Parse a runner option that is always boolean-typed. False if missing or '0'. */ export function optionEnabled( opt, searchParams = getWindowURL().searchParams) @@ -14,11 +17,32 @@ searchParams = getWindowURL().searchParams) return val !== null && val !== '0'; } +/** Parse a runner option that is string-typed. If the option is missing, returns `null`. */ export function optionString( opt, searchParams = getWindowURL().searchParams) { - return searchParams.get(opt) || ''; + return searchParams.get(opt); +} + +/** Runtime modes for running tests in different types of workers. */ + +/** Parse a runner option for different worker modes (as in `?worker=shared`). Null if no worker. */ +export function optionWorkerMode( +opt, +searchParams = getWindowURL().searchParams) +{ + const value = searchParams.get(opt); + if (value === null || value === '0') { + return null; + } else if (value === 'service') { + return 'service'; + } else if (value === 'shared') { + return 'shared'; + } else if (value === '' || value === '1' || value === 'dedicated') { + return 'dedicated'; + } + unreachable('invalid worker= option value'); } /** @@ -32,12 +56,16 @@ searchParams = getWindowURL().searchParams) + + export const kDefaultCTSOptions = { - worker: false, + worker: null, debug: true, compatibility: false, + forceFallbackAdapter: false, unrollConstEvalLoops: false, - powerPreference: '' + powerPreference: null, + logToWebSocket: false }; /** @@ -59,19 +87,30 @@ export const kDefaultCTSOptions = { * Options to the CTS. */ export const kCTSOptionsInfo = { - worker: { description: 'run in a worker' }, + worker: { + description: 'run in a worker', + parser: optionWorkerMode, + selectValueDescriptions: [ + { value: null, description: 'no worker' }, + { value: 'dedicated', description: 'dedicated worker' }, + { value: 'shared', description: 'shared worker' }, + { value: 'service', description: 'service worker' }] + + }, debug: { description: 'show more info' }, compatibility: { description: 'run in compatibility mode' }, + forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' }, unrollConstEvalLoops: { description: 'unroll const eval loops in WGSL' }, powerPreference: { description: 'set default powerPreference for some tests', parser: optionString, selectValueDescriptions: [ - { value: '', description: 'default' }, + { value: null, description: 'default' }, { value: 'low-power', description: 'low-power' }, { value: 'high-performance', description: 'high-performance' }] - } + }, + logToWebSocket: { description: 'send some logs to ws://localhost:59497/' } }; /** diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js index a0f13c54af..c5df860d0b 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js @@ -1,15 +1,11 @@ /** * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts -**/import { setBaseResourcePath } from '../../framework/resources.js';import { globalTestConfig } from '../../framework/test_config.js';import { DefaultTestFileLoader } from '../../internal/file_loader.js'; -import { Logger } from '../../internal/logging/logger.js'; -import { parseQuery } from '../../internal/query/parseQuery.js'; - -import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js'; +**/import { setBaseResourcePath } from '../../framework/resources.js';import { DefaultTestFileLoader } from '../../internal/file_loader.js';import { parseQuery } from '../../internal/query/parseQuery.js'; import { assert } from '../../util/util.js'; +import { setupWorkerEnvironment } from './utils_worker.js'; - -// Should be DedicatedWorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom". +// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom". @@ -17,25 +13,10 @@ const loader = new DefaultTestFileLoader(); setBaseResourcePath('../../../resources'); -self.onmessage = async (ev) => { - const query = ev.data.query; - const expectations = ev.data.expectations; - const ctsOptions = ev.data.ctsOptions; - - const { debug, unrollConstEvalLoops, powerPreference, compatibility } = ctsOptions; - globalTestConfig.unrollConstEvalLoops = unrollConstEvalLoops; - globalTestConfig.compatibility = compatibility; - - Logger.globalDebugMode = debug; - const log = new Logger(); +async function reportTestResults(ev) { + const { query, expectations, ctsOptions } = ev.data; - if (powerPreference || compatibility) { - setDefaultRequestAdapterOptions({ - ...(powerPreference && { powerPreference }), - // MAINTENANCE_TODO: Change this to whatever the option ends up being - ...(compatibility && { compatibilityMode: true }) - }); - } + const log = setupWorkerEnvironment(ctsOptions); const testcases = Array.from(await loader.loadCases(parseQuery(query))); assert(testcases.length === 1, 'worker query resulted in != 1 cases'); @@ -44,5 +25,23 @@ self.onmessage = async (ev) => { const [rec, result] = log.record(testcase.query.toString()); await testcase.run(rec, expectations); - self.postMessage({ query, result }); + this.postMessage({ + query, + result: { + ...result, + logs: result.logs?.map((l) => l.toRawData()) + } + }); +} + +self.onmessage = (ev) => { + void reportTestResults.call(ev.source || self, ev); +}; + +self.onconnect = (event) => { + const port = event.ports[0]; + + port.onmessage = (ev) => { + void reportTestResults.call(port, ev); + }; };
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js index 1d65394180..4039aa1332 100644 --- a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js @@ -2,48 +2,190 @@ * AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts **/import { LogMessageWithStack } from '../../internal/logging/log_message.js'; +import { timeout } from '../../util/timeout.js'; +import { assert } from '../../util/util.js'; import { kDefaultCTSOptions } from './options.js'; -export class TestWorker { +/** Query all currently-registered service workers, and unregister them. */ +function unregisterAllServiceWorkers() { + void navigator.serviceWorker.getRegistrations().then((registrations) => { + for (const registration of registrations) { + void registration.unregister(); + } + }); +} + +// NOTE: This code runs on startup for any runtime with worker support. Here, we use that chance to +// delete any leaked service workers, and register to clean up after ourselves at shutdown. +unregisterAllServiceWorkers(); +window.addEventListener('beforeunload', () => { + unregisterAllServiceWorkers(); +}); + +class TestBaseWorker { resolvers = new Map(); - constructor(ctsOptions) { - this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: true } }; - const selfPath = import.meta.url; - const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); - const workerPath = selfPathDir + '/test_worker-worker.js'; - this.worker = new Worker(workerPath, { type: 'module' }); - this.worker.onmessage = (ev) => { - const query = ev.data.query; - const result = ev.data.result; - if (result.logs) { - for (const l of result.logs) { - Object.setPrototypeOf(l, LogMessageWithStack.prototype); - } - } - this.resolvers.get(query)(result); + constructor(worker, ctsOptions) { + this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker } }; + } + + onmessage(ev) { + const query = ev.data.query; + const transferredResult = ev.data.result; - // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and - // update the entire results JSON somehow at some point). + const result = { + status: transferredResult.status, + timems: transferredResult.timems, + logs: transferredResult.logs?.map((l) => new LogMessageWithStack(l)) }; + + this.resolvers.get(query)(result); + this.resolvers.delete(query); + + // MAINTENANCE_TODO(kainino0x): update the Logger with this result (or don't have a logger and + // update the entire results JSON somehow at some point). } - async run( - rec, + makeRequestAndRecordResult( + target, query, - expectations = []) + expectations) { - this.worker.postMessage({ + const request = { query, expectations, ctsOptions: this.ctsOptions - }); - const workerResult = await new Promise((resolve) => { + }; + target.postMessage(request); + + return new Promise((resolve) => { + assert(!this.resolvers.has(query), "can't request same query twice simultaneously"); this.resolvers.set(query, resolve); }); - rec.injectResult(workerResult); + } + + async run( + rec, + query, + expectations = []) + { + try { + rec.injectResult(await this.runImpl(query, expectations)); + } catch (ex) { + rec.start(); + rec.threw(ex); + rec.finish(); + } + } + + + + + +} + +export class TestDedicatedWorker extends TestBaseWorker { + + + constructor(ctsOptions) { + super('dedicated', ctsOptions); + try { + if (typeof Worker === 'undefined') { + throw new Error('Dedicated Workers not available'); + } + + const selfPath = import.meta.url; + const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); + const workerPath = selfPathDir + '/test_worker-worker.js'; + this.worker = new Worker(workerPath, { type: 'module' }); + this.worker.onmessage = (ev) => this.onmessage(ev); + } catch (ex) { + assert(ex instanceof Error); + // Save the exception to re-throw in runImpl(). + this.worker = ex; + } + } + + runImpl(query, expectations = []) { + if (this.worker instanceof Worker) { + return this.makeRequestAndRecordResult(this.worker, query, expectations); + } else { + throw this.worker; + } + } +} + +/** @deprecated Use TestDedicatedWorker instead. */ +export class TestWorker extends TestDedicatedWorker {} + +export class TestSharedWorker extends TestBaseWorker { + /** MessagePort to the SharedWorker, or an Error if it couldn't be initialized. */ + + + constructor(ctsOptions) { + super('shared', ctsOptions); + try { + if (typeof SharedWorker === 'undefined') { + throw new Error('Shared Workers not available'); + } + + const selfPath = import.meta.url; + const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); + const workerPath = selfPathDir + '/test_worker-worker.js'; + const worker = new SharedWorker(workerPath, { type: 'module' }); + this.port = worker.port; + this.port.start(); + this.port.onmessage = (ev) => this.onmessage(ev); + } catch (ex) { + assert(ex instanceof Error); + // Save the exception to re-throw in runImpl(). + this.port = ex; + } + } + + runImpl(query, expectations = []) { + if (this.port instanceof MessagePort) { + return this.makeRequestAndRecordResult(this.port, query, expectations); + } else { + throw this.port; + } + } +} + +export class TestServiceWorker extends TestBaseWorker { + constructor(ctsOptions) { + super('service', ctsOptions); + } + + async runImpl(query, expectations = []) { + if (!('serviceWorker' in navigator)) { + throw new Error('Service Workers not available'); + } + const [suite, name] = query.split(':', 2); + const fileName = name.split(',').join('/'); + + const selfPath = import.meta.url; + const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/')); + // Construct the path to the worker file, then use URL to resolve the `../` components. + const serviceWorkerURL = new URL( + `${selfPathDir}/../../../${suite}/webworker/${fileName}.worker.js` + ).toString(); + + // If a registration already exists for this path, it will be ignored. + const registration = await navigator.serviceWorker.register(serviceWorkerURL, { + type: 'module' + }); + // Make sure the registration we just requested is active. (We don't worry about it being + // outdated from a previous page load, because we wipe all service workers on shutdown/startup.) + while (!registration.active || registration.active.scriptURL !== serviceWorkerURL) { + await new Promise((resolve) => timeout(resolve, 0)); + } + const serviceWorker = registration.active; + + navigator.serviceWorker.onmessage = (ev) => this.onmessage(ev); + return this.makeRequestAndRecordResult(serviceWorker, query, expectations); } }
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js new file mode 100644 index 0000000000..5a34070e14 --- /dev/null +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js @@ -0,0 +1,35 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/import { globalTestConfig } from '../../framework/test_config.js';import { Logger } from '../../internal/logging/logger.js'; +import { setDefaultRequestAdapterOptions } from '../../util/navigator_gpu.js'; + + + + + + + + + +/** + * Set config environment for workers with ctsOptions and return a Logger. + */ +export function setupWorkerEnvironment(ctsOptions) { + const { powerPreference, compatibility } = ctsOptions; + globalTestConfig.enableDebugLogs = ctsOptions.debug; + globalTestConfig.unrollConstEvalLoops = ctsOptions.unrollConstEvalLoops; + globalTestConfig.compatibility = compatibility; + globalTestConfig.logToWebSocket = ctsOptions.logToWebSocket; + + const log = new Logger(); + + if (powerPreference || compatibility) { + setDefaultRequestAdapterOptions({ + ...(powerPreference && { powerPreference }), + // MAINTENANCE_TODO: Change this to whatever the option ends up being + ...(compatibility && { compatibilityMode: true }) + }); + } + + return log; +}
\ No newline at end of file diff --git a/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js new file mode 100644 index 0000000000..69b5f375f1 --- /dev/null +++ b/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js @@ -0,0 +1,54 @@ +/** +* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts +**/import { LogMessageWithStack } from '../../internal/logging/log_message.js';import { comparePaths, comparePublicParamsPaths, Ordering } from '../../internal/query/compare.js'; +import { parseQuery } from '../../internal/query/parseQuery.js'; +import { TestQuerySingleCase } from '../../internal/query/query.js'; + +import { assert } from '../../util/util.js'; + +import { setupWorkerEnvironment } from './utils_worker.js'; + +/** + * Sets up the currently running Web Worker to wrap the TestGroup object `g`. + * `g` is the `g` exported from a `.spec.ts` file: a TestGroupBuilder<F> interface, + * which underneath is actually a TestGroup<F> object. + * + * This is used in the generated `.worker.js` files that are generated to use as service workers. + */ +export function wrapTestGroupForWorker(g) { + self.onmessage = async (ev) => { + const { query, expectations, ctsOptions } = ev.data; + try { + const log = setupWorkerEnvironment(ctsOptions); + + const testQuery = parseQuery(query); + assert(testQuery instanceof TestQuerySingleCase); + let testcase = null; + for (const t of g.iterate()) { + if (comparePaths(t.testPath, testQuery.testPathParts) !== Ordering.Equal) { + continue; + } + for (const c of t.iterate(testQuery.params)) { + if (comparePublicParamsPaths(c.id.params, testQuery.params) === Ordering.Equal) { + testcase = c; + } + } + } + assert(!!testcase, 'testcase not found'); + const [rec, result] = log.record(query); + await testcase.run(rec, testQuery, expectations); + + ev.source?.postMessage({ query, result }); + } catch (thrown) { + const ex = thrown instanceof Error ? thrown : new Error(`${thrown}`); + ev.source?.postMessage({ + query, + result: { + status: 'fail', + timems: 0, + logs: [LogMessageWithStack.wrapError('INTERNAL', ex)] + } + }); + } + }; +}
\ No newline at end of file |