Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shared worker support #3345

Merged
merged 1 commit into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading