summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts202
1 files changed, 172 insertions, 30 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
index 9bbcab0946..f9a44bb7bc 100644
--- a/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
+++ b/dom/webgpu/tests/cts/checkout/src/common/runtime/helper/test_worker.ts
@@ -2,48 +2,190 @@ import { LogMessageWithStack } from '../../internal/logging/log_message.js';
import { TransferredTestCaseResult, LiveTestCaseResult } from '../../internal/logging/result.js';
import { TestCaseRecorder } from '../../internal/logging/test_case_recorder.js';
import { TestQueryWithExpectation } from '../../internal/query/query.js';
+import { timeout } from '../../util/timeout.js';
+import { assert } from '../../util/util.js';
-import { CTSOptions, kDefaultCTSOptions } from './options.js';
+import { CTSOptions, WorkerMode, kDefaultCTSOptions } from './options.js';
+import { WorkerTestRunRequest } from './utils_worker.js';
-export class TestWorker {
- private readonly ctsOptions: CTSOptions;
- private readonly worker: Worker;
- private readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();
+/** 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();
+ }
+ });
+}
- constructor(ctsOptions?: 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: string = ev.data.query;
- const result: TransferredTestCaseResult = ev.data.result;
- if (result.logs) {
- for (const l of result.logs) {
- Object.setPrototypeOf(l, LogMessageWithStack.prototype);
- }
- }
- this.resolvers.get(query)!(result as LiveTestCaseResult);
+// 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();
+});
+
+abstract class TestBaseWorker {
+ protected readonly ctsOptions: CTSOptions;
+ protected readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();
+
+ constructor(worker: WorkerMode, ctsOptions?: CTSOptions) {
+ this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker } };
+ }
- // 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).
+ onmessage(ev: MessageEvent) {
+ const query: string = ev.data.query;
+ const transferredResult: TransferredTestCaseResult = ev.data.result;
+
+ const result: LiveTestCaseResult = {
+ 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: TestCaseRecorder,
+ makeRequestAndRecordResult(
+ target: MessagePort | Worker | ServiceWorker,
query: string,
- expectations: TestQueryWithExpectation[] = []
- ): Promise<void> {
- this.worker.postMessage({
+ expectations: TestQueryWithExpectation[]
+ ): Promise<LiveTestCaseResult> {
+ const request: WorkerTestRunRequest = {
query,
expectations,
ctsOptions: this.ctsOptions,
- });
- const workerResult = await new Promise<LiveTestCaseResult>(resolve => {
+ };
+ target.postMessage(request);
+
+ return new Promise<LiveTestCaseResult>(resolve => {
+ assert(!this.resolvers.has(query), "can't request same query twice simultaneously");
this.resolvers.set(query, resolve);
});
- rec.injectResult(workerResult);
+ }
+
+ async run(
+ rec: TestCaseRecorder,
+ query: string,
+ expectations: TestQueryWithExpectation[] = []
+ ): Promise<void> {
+ try {
+ rec.injectResult(await this.runImpl(query, expectations));
+ } catch (ex) {
+ rec.start();
+ rec.threw(ex);
+ rec.finish();
+ }
+ }
+
+ protected abstract runImpl(
+ query: string,
+ expectations: TestQueryWithExpectation[]
+ ): Promise<LiveTestCaseResult>;
+}
+
+export class TestDedicatedWorker extends TestBaseWorker {
+ private readonly worker: Worker | Error;
+
+ constructor(ctsOptions?: 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;
+ }
+ }
+
+ override runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ 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. */
+ private readonly port: MessagePort | Error;
+
+ constructor(ctsOptions?: 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;
+ }
+ }
+
+ override runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ if (this.port instanceof MessagePort) {
+ return this.makeRequestAndRecordResult(this.port, query, expectations);
+ } else {
+ throw this.port;
+ }
+ }
+}
+
+export class TestServiceWorker extends TestBaseWorker {
+ constructor(ctsOptions?: CTSOptions) {
+ super('service', ctsOptions);
+ }
+
+ override async runImpl(query: string, expectations: TestQueryWithExpectation[] = []) {
+ 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);
}
}