summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/mozilla/tests/webgpu/common/runtime/helper
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/mozilla/tests/webgpu/common/runtime/helper')
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/options.js53
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker-worker.js51
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/test_worker.js192
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/utils_worker.js35
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/common/runtime/helper/wrap_for_worker.js54
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