Skip to content

Commit db336a5

Browse files
committed
worker: add wpt tests for Web Workers
1 parent c73f2fe commit db336a5

1,326 files changed

Lines changed: 53594 additions & 23 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

β€Žtest/common/wpt.jsβ€Ž

Lines changed: 66 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const path = require('path');
88
const events = require('events');
99
const os = require('os');
1010
const { inspect } = require('util');
11+
const { pathToFileURL } = require('url');
1112
const { Worker } = require('worker_threads');
1213

1314
const workerPath = path.join(__dirname, 'wpt/worker.js');
@@ -194,6 +195,24 @@ class ResourceLoader {
194195
fixtures.path('wpt', base, url);
195196
}
196197

198+
/**
199+
* Map a URL that a test would have fetched from the WPT server (an
200+
* absolute path, or a path relative to the test file) to a file: URL
201+
* into the fixtures directory. URLs that already have a scheme (data:,
202+
* blob:, http:, ...) are returned unchanged.
203+
* @param {string} from the path of the file loading this resource,
204+
* relative to the WPT folder.
205+
* @param {string|URL} url the url of the resource being loaded.
206+
* @returns {string}
207+
*/
208+
mapServerURL(from, url) {
209+
url = `${url}`;
210+
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:|^\/\//.test(url)) {
211+
return url;
212+
}
213+
return pathToFileURL(this.toRealFilePath(from, url)).href;
214+
}
215+
197216
/**
198217
* Load a resource in test/fixtures/wpt specified with a URL
199218
* @param {string} from the path of the file loading this resource,
@@ -389,13 +408,15 @@ class WPTTestSpec {
389408
* @returns {{ script?: string[]; variant?: string[]; [key: string]: string }} parsed META tags of a spec file
390409
*/
391410
getMeta() {
392-
const matches = this.getContent().match(/\/\/ META: .+/g);
411+
// Like upstream, tolerate missing whitespace around "META:".
412+
// Refs: https://github.com/web-platform-tests/wpt/blob/master/tools/manifest/sourcefile.py
413+
const matches = this.getContent().match(/\/\/\s*META:\s*.+/g);
393414
if (!matches) {
394415
return {};
395416
}
396417
const result = {};
397418
for (const match of matches) {
398-
const parts = match.match(/\/\/ META: ([^=]+?)=(.+)/);
419+
const parts = match.match(/\/\/\s*META:\s*([^=]+?)=(.+)/);
399420
const key = parts[1];
400421
const value = parts[2];
401422
if (key === 'script' || key === 'variant') {
@@ -570,7 +591,11 @@ class WPTRunner {
570591
this.resource = new ResourceLoader(path);
571592
this.concurrency = concurrency;
572593

573-
this.flags = [];
594+
// Since we need to prepare the Web Worker APIs
595+
// in the harness that runs on all WPT workers,
596+
// we enable the API globally. This has no practical
597+
// effect on the non-web-worker tests, however.
598+
this.flags = ['--experimental-web-worker'];
574599
this.globalThisInitScripts = [];
575600
this.initScript = null;
576601

@@ -596,7 +621,7 @@ class WPTRunner {
596621
* @param {string[]} flags
597622
*/
598623
setFlags(flags) {
599-
this.flags = flags;
624+
this.flags = this.flags.concat(flags);
600625
}
601626

602627
/**
@@ -679,23 +704,41 @@ class WPTRunner {
679704
const absolutePath = spec.getAbsolutePath();
680705
const relativePath = spec.getRelativePath();
681706
const harnessPath = fixtures.path('wpt', 'resources', 'testharness.js');
682-
683-
// Scripts specified with the `// META: script=` header
684-
const scriptsToRun = meta.script?.map((script) => {
707+
// *.worker.js tests are dedicated worker tests by definition.
708+
// Multi-global (*.any.js) tests whose global scopes include a
709+
// dedicated worker but not a window also run inside an actual Web
710+
// Worker, like the .any.worker.html variant generated by the WPT
711+
// server does. Refs:
712+
// https://web-platform-tests.org/writing-tests/testharness.html#multi-global-tests
713+
const isAnyTest = /\.any\.js$/.test(spec.filename);
714+
const globalScopes = isAnyTest ?
715+
(meta.global?.split(',').map((s) => s.trim()) ??
716+
['window', 'dedicatedworker']) : [];
717+
const isWebWorkerTest = /\.worker\.js$/.test(spec.filename) ||
718+
(isAnyTest && !globalScopes.includes('window') &&
719+
(globalScopes.includes('worker') ||
720+
globalScopes.includes('dedicatedworker')));
721+
722+
// Scripts specified with the `// META: script=` header. For tests
723+
// that run inside a Web Worker they are imported by the worker
724+
// instead.
725+
const scriptsToRun = isWebWorkerTest ? [] : meta.script?.map((script) => {
685726
const obj = {
686727
filename: this.resource.toRealFilePath(relativePath, script),
687728
code: this.resource.read(relativePath, script),
688729
};
689730
this.scriptsModifier?.(obj);
690731
return obj;
691732
}) ?? [];
692-
// The actual test
693-
const obj = {
694-
code: content,
695-
filename: absolutePath,
696-
};
697-
this.scriptsModifier?.(obj);
698-
scriptsToRun.push(obj);
733+
if (!isWebWorkerTest) {
734+
// The actual test
735+
const obj = {
736+
code: content,
737+
filename: absolutePath,
738+
};
739+
this.scriptsModifier?.(obj);
740+
scriptsToRun.push(obj);
741+
}
699742

700743
run(async () => {
701744
const worker = new Worker(workerPath, {
@@ -710,6 +753,15 @@ class WPTRunner {
710753
filename: harnessPath,
711754
},
712755
scriptsToRun,
756+
// Set when the test runs inside an actual Web Worker.
757+
webWorker: isWebWorkerTest ? {
758+
path: absolutePath,
759+
isAnyTest,
760+
scripts: meta.script?.map(
761+
(script) => this.resource.toRealFilePath(relativePath, script),
762+
) ?? [],
763+
skippedTests: spec.skippedTests,
764+
} : undefined,
713765
needsGc: !!meta.script?.find((script) => script === '/common/gc.js'),
714766
skippedTests: spec.skippedTests,
715767
},

β€Žtest/common/wpt/webworker.jsβ€Ž

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use strict';
2+
3+
// Runs a WPT test file inside a Web Worker
4+
// Refs: https://web-platform-tests.org/writing-tests/testharness.html
5+
6+
const { pathToFileURL } = require('url');
7+
8+
globalThis.onmessage = ({ data }) => {
9+
// Let the test install its own handler.
10+
globalThis.onmessage = null;
11+
12+
const { ResourceLoader } = require(data.wptRunner);
13+
const resource = new ResourceLoader(data.wptPath);
14+
15+
// Pretend the worker was served from the URL the WPT server would have
16+
// used
17+
const fakePath = (data.isAnyTest ?
18+
data.testRelativePath.replace(/\.any\.js$/, '.any.worker.js') :
19+
data.testRelativePath).replace(/\\/g, '/');
20+
const fakeURL = new URL(`/${fakePath}`, 'http://wpt');
21+
// eslint-disable-next-line no-undef
22+
const fakeLocation = { __proto__: WorkerLocation.prototype };
23+
for (const key of ['href', 'origin', 'protocol', 'host', 'hostname',
24+
'port', 'pathname', 'search', 'hash']) {
25+
Object.defineProperty(fakeLocation, key, {
26+
value: fakeURL[key],
27+
enumerable: true,
28+
});
29+
}
30+
Object.defineProperty(fakeLocation, 'toString', {
31+
value: function toString() { return fakeURL.href; },
32+
enumerable: true,
33+
});
34+
Object.defineProperty(globalThis, 'location', {
35+
value: fakeLocation,
36+
enumerable: true,
37+
configurable: true,
38+
});
39+
40+
const testharnessPath =
41+
pathToFileURL(resource.toRealFilePath(data.testRelativePath,
42+
'/resources/testharness.js')).href;
43+
44+
// If there are skip patterns, wrap the test functions to prevent
45+
// execution of matching tests. This must happen after testharness.js is
46+
// loaded but before the test scripts run.
47+
function applySkips() {
48+
if (!data.skippedTests?.length) {
49+
return;
50+
}
51+
function isSkipped(name) {
52+
for (const matcher of data.skippedTests) {
53+
if (typeof matcher === 'string') {
54+
if (name === matcher) return true;
55+
} else if (matcher.test(name)) {
56+
return true;
57+
}
58+
}
59+
return false;
60+
}
61+
for (const fn of ['test', 'async_test', 'promise_test']) {
62+
const original = globalThis[fn];
63+
globalThis[fn] = function(func, name, ...rest) {
64+
if (typeof name === 'string' && isSkipped(name)) {
65+
// eslint-disable-next-line no-undef
66+
postMessage({ type: 'skip', name });
67+
return;
68+
}
69+
return original.call(this, func, name, ...rest);
70+
};
71+
}
72+
}
73+
74+
// Tests fetch scripts and nested worker scripts from the WPT server; map
75+
// those URLs into the fixtures directory.
76+
const realImportScripts = globalThis.importScripts;
77+
globalThis.importScripts = function importScripts(...urls) {
78+
const mapped = urls.map(
79+
(url) => resource.mapServerURL(data.testRelativePath, url));
80+
const result = realImportScripts.apply(this, mapped);
81+
if (mapped.includes(testharnessPath)) {
82+
applySkips();
83+
}
84+
return result;
85+
};
86+
const RealWorker = globalThis.Worker;
87+
globalThis.Worker = class Worker extends RealWorker {
88+
constructor(url, options) {
89+
super(resource.mapServerURL(data.testRelativePath, url), options);
90+
}
91+
};
92+
93+
if (data.isAnyTest) {
94+
// Emulate the generated .any.worker.js wrapper script.
95+
// Refs: https://github.com/web-platform-tests/wpt/blob/master/tools/serve/serve.py
96+
globalThis.GLOBAL = {
97+
isWindow() { return false; },
98+
isWorker() { return true; },
99+
isShadowRealm() { return false; },
100+
};
101+
globalThis.importScripts('/resources/testharness.js');
102+
for (const script of data.scripts) {
103+
globalThis.importScripts(pathToFileURL(script).href);
104+
}
105+
globalThis.importScripts(pathToFileURL(data.path).href);
106+
// eslint-disable-next-line no-undef
107+
done();
108+
} else {
109+
// *.worker.js tests import testharness.js and call done() themselves.
110+
globalThis.importScripts(pathToFileURL(data.path).href);
111+
}
112+
};

β€Žtest/common/wpt/worker.jsβ€Ž

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict';
22

3+
const path = require('path');
4+
const { pathToFileURL } = require('url');
35
const {
46
runInNewContext,
57
runInThisContext,
@@ -11,6 +13,15 @@ const { parentPort, workerData } = require('worker_threads');
1113
const { ResourceLoader } = require(workerData.wptRunner);
1214
const resource = new ResourceLoader(workerData.wptPath);
1315

16+
// Tests create workers with URLs the WPT server would have served them
17+
// from; map them into the fixtures directory.
18+
const RealWorker = globalThis.Worker;
19+
globalThis.Worker = class Worker extends RealWorker {
20+
constructor(url, options) {
21+
super(resource.mapServerURL(workerData.testRelativePath, url), options);
22+
}
23+
};
24+
1425
if (workerData.needsGc) {
1526
// See https://github.com/nodejs/node/issues/16595#issuecomment-340288680
1627
setFlagsFromString('--expose-gc');
@@ -104,3 +115,41 @@ for (const scriptToRun of workerData.scriptsToRun) {
104115
importModuleDynamically: USE_MAIN_CONTEXT_DEFAULT_LOADER,
105116
});
106117
}
118+
119+
if (workerData.webWorker) {
120+
const worker = new RealWorker(
121+
pathToFileURL(path.join(__dirname, 'webworker.js')));
122+
worker.postMessage({
123+
wptRunner: workerData.wptRunner,
124+
wptPath: workerData.wptPath,
125+
testRelativePath: workerData.testRelativePath,
126+
...workerData.webWorker,
127+
});
128+
129+
let started = false;
130+
worker.addEventListener('message', (event) => {
131+
started = true;
132+
// Skipped subtests never register with the testharness inside the
133+
// worker; the runner is notified about them directly.
134+
if (event.data?.type === 'skip') {
135+
parentPort.postMessage({ type: 'skip', name: event.data.name });
136+
}
137+
});
138+
worker.addEventListener('error', (event) => {
139+
if (started) {
140+
return;
141+
}
142+
clearTimeout(timeout);
143+
parentPort.postMessage({
144+
type: 'completion',
145+
status: {
146+
status: 1,
147+
message: event.message,
148+
stack: event.error?.stack,
149+
},
150+
});
151+
});
152+
153+
// eslint-disable-next-line no-undef
154+
fetch_tests_from_worker(worker);
155+
}

β€Žtest/fixtures/wpt/README.mdβ€Ž

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,46 @@
11
# Web Platform Test Fixtures
22

3-
The files in this folder are maintained by [`git node wpt`][].
3+
The files in this folder, including this document,
4+
are generated by [`git node wpt`][].
45

56
This folder contains a subset of the [Web Platform Tests][] for the
67
implementation of Web APIs in Node.js.
78

89
See [test/wpt](../../wpt/README.md) for information on how these tests are run.
910

10-
Pinned upstream WPT revisions for each imported subset are recorded in
11-
[`versions.json`](./versions.json).
11+
Last update:
12+
13+
- common: https://github.com/web-platform-tests/wpt/tree/dbd648158d/common
14+
- compression: https://github.com/web-platform-tests/wpt/tree/ae05f5cb53/compression
15+
- console: https://github.com/web-platform-tests/wpt/tree/e48251b778/console
16+
- dom/abort: https://github.com/web-platform-tests/wpt/tree/dc928169ee/dom/abort
17+
- dom/events: https://github.com/web-platform-tests/wpt/tree/0a811c5161/dom/events
18+
- encoding: https://github.com/web-platform-tests/wpt/tree/1ac8deee08/encoding
19+
- fetch/data-urls/resources: https://github.com/web-platform-tests/wpt/tree/7c79d998ff/fetch/data-urls/resources
20+
- FileAPI: https://github.com/web-platform-tests/wpt/tree/7f51301888/FileAPI
21+
- hr-time: https://github.com/web-platform-tests/wpt/tree/34cafd797e/hr-time
22+
- html/webappapis/atob: https://github.com/web-platform-tests/wpt/tree/f267e1dca6/html/webappapis/atob
23+
- html/webappapis/microtask-queuing: https://github.com/web-platform-tests/wpt/tree/2c5c3c4c27/html/webappapis/microtask-queuing
24+
- html/webappapis/structured-clone: https://github.com/web-platform-tests/wpt/tree/47d3fb280c/html/webappapis/structured-clone
25+
- html/webappapis/timers: https://github.com/web-platform-tests/wpt/tree/5873f2d8f1/html/webappapis/timers
26+
- interfaces: https://github.com/web-platform-tests/wpt/tree/a8392bd021/interfaces
27+
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/94caab7038/performance-timeline
28+
- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing
29+
- resources: https://github.com/web-platform-tests/wpt/tree/6a2f322376/resources
30+
- service-workers: https://github.com/web-platform-tests/wpt/tree/b7ed7c8dcd/service-workers
31+
- streams: https://github.com/web-platform-tests/wpt/tree/f8f26a372f/streams
32+
- url: https://github.com/web-platform-tests/wpt/tree/4832db4761/url
33+
- urlpattern: https://github.com/web-platform-tests/wpt/tree/5847ee5cfa/urlpattern
34+
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
35+
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/288c467d35/wasm/jsapi
36+
- wasm/webapi: https://github.com/web-platform-tests/wpt/tree/fd1b23eeaa/wasm/webapi
37+
- web-locks: https://github.com/web-platform-tests/wpt/tree/10a122a6bc/web-locks
38+
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/ec2fee39a4/WebCryptoAPI
39+
- webidl: https://github.com/web-platform-tests/wpt/tree/63ca529a02/webidl
40+
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/2f96fa1996/webidl/ecmascript-binding/es-exceptions
41+
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/6495c91853/webmessaging/broadcastchannel
42+
- webstorage: https://github.com/web-platform-tests/wpt/tree/1d2c5fb36a/webstorage
43+
- workers: https://github.com/web-platform-tests/wpt/tree/4832db4761/workers
1244

1345
[Web Platform Tests]: https://github.com/web-platform-tests/wpt
1446
[`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/main/docs/git-node.md#git-node-wpt
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
spec: https://w3c.github.io/ServiceWorker/
2+
suggested_reviewers:
3+
- asutherland
4+
- mkruisselbrink
5+
- mattto
6+
- wanderview
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- "*": [service-workers]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
suggested_reviewers:
2+
- inexorabletash
3+
- wanderview
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
rules:
2+
- "**": [service-workers]

0 commit comments

Comments
Β (0)