Skip to content

Commit

Permalink
Add shared worker support
Browse files Browse the repository at this point in the history
  • Loading branch information
beaufortfrancois authored and greggman committed Feb 21, 2024
1 parent bc24cef commit 4641deb
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 226 deletions.
5 changes: 3 additions & 2 deletions docs/intro/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ You can use this to preview how your test plan will appear.
You can view different suites (webgpu, unittests, stress, etc.) or different subtrees of
the test suite.

- `http://localhost:8080/standalone/` (defaults to `?runnow=0&worker=0&debug=0&q=webgpu:*`)
- `http://localhost:8080/standalone/` (defaults to `?runnow=0&debug=0&q=webgpu:*`)
- `http://localhost:8080/standalone/?q=unittests:*`
- `http://localhost:8080/standalone/?q=unittests:basic:*`

The following url parameters change how the harness runs:

- `runnow=1` runs all matching tests on page load.
- `debug=1` enables verbose debug logging from tests.
- `worker=1` runs the tests on a Web Worker instead of the main thread.
- `worker=dedicated` runs the tests on a dedicated worker instead of the main thread.
- `worker=shared` runs the tests on a shared worker instead of the main thread.
- `power_preference=low-power` runs most tests passing `powerPreference: low-power` to `requestAdapter`
- `power_preference=high-performance` runs most tests passing `powerPreference: high-performance` to `requestAdapter`

Expand Down
6 changes: 3 additions & 3 deletions src/common/internal/query/query.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TestParams } from '../../framework/fixture.js';
import { optionEnabled } from '../../runtime/helper/options.js';
import { optionString } from '../../runtime/helper/options.js';
import { assert, unreachable } from '../../util/util.js';
import { Expectation } from '../logging/result.js';

Expand Down Expand Up @@ -188,12 +188,12 @@ export function parseExpectationsForTestQuery(
assert(
expectationURL.pathname === wptURL.pathname,
`Invalid expectation path ${expectationURL.pathname}
Expectation should be of the form path/to/cts.https.html?worker=0&q=suite:test_path:test_name:foo=1;bar=2;...
Expectation should be of the form path/to/cts.https.html?debug=0&q=suite:test_path:test_name:foo=1;bar=2;...
`
);

const params = expectationURL.searchParams;
if (optionEnabled('worker', params) !== optionEnabled('worker', wptURL.searchParams)) {
if (optionString('worker', params) !== optionString('worker', wptURL.searchParams)) {
continue;
}

Expand Down
14 changes: 11 additions & 3 deletions src/common/runtime/helper/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function optionString(
* The possible options for the tests.
*/
export interface CTSOptions {
worker: boolean;
worker?: 'dedicated' | 'shared' | '';
debug: boolean;
compatibility: boolean;
forceFallbackAdapter: boolean;
Expand All @@ -34,7 +34,7 @@ export interface CTSOptions {
}

export const kDefaultCTSOptions: CTSOptions = {
worker: false,
worker: '',
debug: true,
compatibility: false,
forceFallbackAdapter: false,
Expand All @@ -61,7 +61,15 @@ export type OptionsInfos<Type> = Record<keyof Type, OptionInfo>;
* Options to the CTS.
*/
export const kCTSOptionsInfo: OptionsInfos<CTSOptions> = {
worker: { description: 'run in a worker' },
worker: {
description: 'run in a worker',
parser: optionString,
selectValueDescriptions: [
{ value: '', description: 'no worker' },
{ value: 'dedicated', description: 'dedicated worker' },
{ value: 'shared', description: 'shared worker' },
],
},
debug: { description: 'show more info' },
compatibility: { description: 'run in compatibility mode' },
forceFallbackAdapter: { description: 'pass forceFallbackAdapter: true to requestAdapter' },
Expand Down
18 changes: 15 additions & 3 deletions src/common/runtime/helper/test_worker-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { assert } from '../../util/util.js';

import { CTSOptions } from './options.js';

// Should be DedicatedWorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
// Should be WorkerGlobalScope, but importing lib "webworker" conflicts with lib "dom".
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
declare const self: any;

const loader = new DefaultTestFileLoader();

setBaseResourcePath('../../../resources');

self.onmessage = async (ev: MessageEvent) => {
async function reportTestResults(this: MessagePort | Worker, ev: MessageEvent) {
const query: string = ev.data.query;
const expectations: TestQueryWithExpectation[] = ev.data.expectations;
const ctsOptions: CTSOptions = ev.data.ctsOptions;
Expand All @@ -44,5 +44,17 @@ self.onmessage = async (ev: MessageEvent) => {
const [rec, result] = log.record(testcase.query.toString());
await testcase.run(rec, expectations);

self.postMessage({ query, result });
this.postMessage({ query, result });
}

self.onmessage = (ev: MessageEvent) => {
void reportTestResults.call(self, ev);
};

self.onconnect = (event: MessageEvent) => {
const port = event.ports[0];

port.onmessage = (ev: MessageEvent) => {
void reportTestResults.call(port, ev);
};
};
49 changes: 47 additions & 2 deletions src/common/runtime/helper/test_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import { TestQueryWithExpectation } from '../../internal/query/query.js';

import { CTSOptions, kDefaultCTSOptions } from './options.js';

export class TestWorker {
export class TestDedicatedWorker {
private readonly ctsOptions: CTSOptions;
private readonly worker: Worker;
private readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();

constructor(ctsOptions?: CTSOptions) {
this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: true } };
this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: 'dedicated' } };
const selfPath = import.meta.url;
const selfPathDir = selfPath.substring(0, selfPath.lastIndexOf('/'));
const workerPath = selfPathDir + '/test_worker-worker.js';
Expand Down Expand Up @@ -47,3 +47,48 @@ export class TestWorker {
rec.injectResult(workerResult);
}
}

export class TestSharedWorker {
private readonly ctsOptions: CTSOptions;
private readonly port: MessagePort;
private readonly resolvers = new Map<string, (result: LiveTestCaseResult) => void>();

constructor(ctsOptions?: CTSOptions) {
this.ctsOptions = { ...(ctsOptions || kDefaultCTSOptions), ...{ worker: 'shared' } };
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 => {
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);

// 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,
query: string,
expectations: TestQueryWithExpectation[] = []
): Promise<void> {
this.port.postMessage({
query,
expectations,
ctsOptions: this.ctsOptions,
});
const workerResult = await new Promise<LiveTestCaseResult>(resolve => {
this.resolvers.set(query, resolve);
});
rec.injectResult(workerResult);
}
}
12 changes: 8 additions & 4 deletions src/common/runtime/standalone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
OptionsInfos,
camelCaseToSnakeCase,
} from './helper/options.js';
import { TestWorker } from './helper/test_worker.js';
import { TestDedicatedWorker, TestSharedWorker } from './helper/test_worker.js';

const rootQuerySpec = 'webgpu:*';
let promptBeforeReload = false;
Expand Down Expand Up @@ -63,7 +63,9 @@ const logger = new Logger();

setBaseResourcePath('../out/resources');

const worker = options.worker ? new TestWorker(options) : undefined;
const dedicatedWorker =
options.worker === 'dedicated' ? new TestDedicatedWorker(options) : undefined;
const sharedWorker = options.worker === 'shared' ? new TestSharedWorker(options) : undefined;

const autoCloseOnPass = document.getElementById('autoCloseOnPass') as HTMLInputElement;
const resultsVis = document.getElementById('resultsVis')!;
Expand Down Expand Up @@ -176,8 +178,10 @@ function makeCaseHTML(t: TestTreeLeaf): VisualizedSubtree {

const [rec, res] = logger.record(name);
caseResult = res;
if (worker) {
await worker.run(rec, name);
if (dedicatedWorker) {
await dedicatedWorker.run(rec, name);
} else if (sharedWorker) {
await sharedWorker.run(rec, name);
} else {
await t.run(rec);
}
Expand Down
15 changes: 9 additions & 6 deletions src/common/runtime/wpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { parseQuery } from '../internal/query/parseQuery.js';
import { parseExpectationsForTestQuery, relativeQueryString } from '../internal/query/query.js';
import { assert } from '../util/util.js';

import { optionEnabled } from './helper/options.js';
import { TestWorker } from './helper/test_worker.js';
import { optionEnabled, optionString } from './helper/options.js';
import { TestDedicatedWorker, TestSharedWorker } from './helper/test_worker.js';

// testharness.js API (https://web-platform-tests.org/writing-tests/testharness-api.html)
declare interface WptTestObject {
Expand All @@ -31,8 +31,9 @@ setup({
});

void (async () => {
const workerEnabled = optionEnabled('worker');
const worker = workerEnabled ? new TestWorker() : undefined;
const workerString = optionString('worker');
const dedicatedWorker = workerString === 'dedicated' ? new TestDedicatedWorker() : undefined;
const sharedWorker = workerString === 'shared' ? new TestSharedWorker() : undefined;

globalTestConfig.unrollConstEvalLoops = optionEnabled('unroll_const_eval_loops');

Expand Down Expand Up @@ -63,8 +64,10 @@ void (async () => {

const wpt_fn = async () => {
const [rec, res] = log.record(name);
if (worker) {
await worker.run(rec, name, expectations);
if (dedicatedWorker) {
await dedicatedWorker.run(rec, name, expectations);
} else if (sharedWorker) {
await sharedWorker.run(rec, name, expectations);
} else {
await testcase.run(rec, expectations);
}
Expand Down
12 changes: 6 additions & 6 deletions src/common/tools/gen_wpt_cts_html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ where arguments.txt is a file containing a list of arguments prefixes to both ge
in the expectations. The entire variant list generation runs *once per prefix*, so this
multiplies the size of the variant list.
?worker=0&q=
?worker=1&q=
?debug=0&q=
?debug=1&q=
and myexpectations.txt is a file containing a list of WPT paths to suppress, e.g.:
path/to/cts.https.html?worker=0&q=webgpu:a/foo:bar={"x":1}
path/to/cts.https.html?worker=1&q=webgpu:a/foo:bar={"x":1}
path/to/cts.https.html?debug=0&q=webgpu:a/foo:bar={"x":1}
path/to/cts.https.html?debug=1&q=webgpu:a/foo:bar={"x":1}
path/to/cts.https.html?worker=1&q=webgpu:a/foo:bar={"x":3}
path/to/cts.https.html?debug=1&q=webgpu:a/foo:bar={"x":3}
`);
process.exit(rc);
}
Expand Down Expand Up @@ -224,7 +224,7 @@ ${queryString}`
}

lines.push({
urlQueryString: prefix + query.toString(), // "?worker=0&q=..."
urlQueryString: prefix + query.toString(), // "?debug=0&q=..."
comment: useChunking ? `estimated: ${subtreeCounts?.totalTimeMS.toFixed(3)} ms` : undefined,
});

Expand Down
Loading

0 comments on commit 4641deb

Please sign in to comment.