mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
test: support inspecting WPTs in child processes
Add WPT_INSPECT to launch one generated main-thread test with --inspect-brk on an available port. Forward debugger stderr so an inspector client can attach while the child is paused. Require an exact generated test path and reject worker variants, whose test code runs in a nested Worker. Cover backend precedence, selector errors, inspector attachment, and clean shutdown. Refs: https://github.com/nodejs/node/issues/51854 Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65510 Reviewed-By: Aviv Keller <me@aviv.sh> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
1 parent
a15da05d61
commit
49c87ef145
4 files changed
+162
-3
No files matched your search
+32
-2
@@ -754,6 +754,8 @@ function runSpecOnThread(execArgv, workerData, handlers) {
|
||||
* @returns {SpecHandle}
|
||||
*/
|
||||
function runSpecInProcess(execArgv, workerData, handlers) {
|
||||
const forwardStderr = execArgv.some(
|
||||
(flag) => flag === '--inspect-brk' || flag.startsWith('--inspect-brk='));
|
||||
const child = fork(workerPath, {
|
||||
execArgv,
|
||||
// Status files may skip subtests by regular expression, which JSON
|
||||
@@ -767,6 +769,9 @@ function runSpecInProcess(execArgv, workerData, handlers) {
|
||||
child.stderr.setEncoding('utf8');
|
||||
child.stderr.on('data', (chunk) => {
|
||||
stderr += chunk;
|
||||
if (forwardStderr) {
|
||||
process.stderr.write(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
child.on('message', (message) => {
|
||||
@@ -789,7 +794,8 @@ function runSpecInProcess(execArgv, workerData, handlers) {
|
||||
const name = signal ?
|
||||
`Test process was killed by signal ${signal}` :
|
||||
`Test process exited with code ${code}`;
|
||||
if (!handlers.failure({ name, message: name, stack: stderr }) && stderr) {
|
||||
if (!handlers.failure({ name, message: name, stack: stderr }) &&
|
||||
stderr && !forwardStderr) {
|
||||
process.stderr.write(stderr);
|
||||
}
|
||||
});
|
||||
@@ -823,9 +829,15 @@ class WPTRunner {
|
||||
concurrency = Math.min(10, concurrency);
|
||||
}
|
||||
|
||||
this.inspectBrk = process.env.WPT_INSPECT !== undefined;
|
||||
|
||||
// The override exists so that every suite can be run either way without
|
||||
// editing the drivers, which is how the two backends are kept compatible.
|
||||
backend = process.env.WPT_BACKEND || backend;
|
||||
if (this.inspectBrk) {
|
||||
backend = 'process';
|
||||
} else {
|
||||
backend = process.env.WPT_BACKEND || backend;
|
||||
}
|
||||
this.runSpec = backends[backend];
|
||||
if (this.runSpec === undefined) {
|
||||
throw new Error(`Invalid WPT backend ${backend}, expected one of ` +
|
||||
@@ -841,6 +853,9 @@ class WPTRunner {
|
||||
// we enable the API globally. This has no practical
|
||||
// effect on the non-web-worker tests, however.
|
||||
this.flags = ['--experimental-web-worker'];
|
||||
if (this.inspectBrk) {
|
||||
this.flags.push('--inspect-brk=0');
|
||||
}
|
||||
this.globalThisInitScripts = [];
|
||||
this.initScript = null;
|
||||
|
||||
@@ -1299,6 +1314,9 @@ class WPTRunner {
|
||||
const queue = [];
|
||||
this.skippedSpecCount = 0;
|
||||
const arg = process.argv[2];
|
||||
if (this.inspectBrk && !arg) {
|
||||
throw new Error('WPT_INSPECT requires a WPT test path');
|
||||
}
|
||||
for (const spec of this.specs) {
|
||||
if (arg) {
|
||||
if (spec.isSelectedBy(arg)) {
|
||||
@@ -1330,6 +1348,18 @@ class WPTRunner {
|
||||
if (arg && queue.length === 0) {
|
||||
throw new Error(`${arg} not found!`);
|
||||
}
|
||||
if (this.inspectBrk && queue.length !== 1) {
|
||||
const matches = queue.map((spec) => spec.getTestPath()).join('\n');
|
||||
throw new Error(
|
||||
`WPT_INSPECT requires exactly one generated WPT test path; ` +
|
||||
`${arg} matched ${queue.length}:\n${matches}`,
|
||||
);
|
||||
}
|
||||
if (this.inspectBrk && queue[0].isWebWorkerTest()) {
|
||||
throw new Error(
|
||||
`WPT_INSPECT does not support worker tests: ${queue[0].getTestPath()}`,
|
||||
);
|
||||
}
|
||||
|
||||
return queue;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ const { spawnSync } = require('child_process');
|
||||
const { backends, WPTRunner } = require('../common/wpt');
|
||||
|
||||
const queueProbe = process.env.NODE_TEST_WPT_QUEUE_PROBE === '1';
|
||||
const backendProbe = process.env.NODE_TEST_WPT_BACKEND_PROBE;
|
||||
|
||||
const harnessPath = fixtures.path('wpt', 'resources', 'testharness.js');
|
||||
const specPath = fixtures.path('wpt-backends-spec.js');
|
||||
@@ -156,7 +157,74 @@ function checkQueuedSpecsKeepRunnerAlive() {
|
||||
assert.strictEqual(status, 0, `Queued WPT probe failed:\n${stdout}${stderr}`);
|
||||
}
|
||||
|
||||
function runDefaultBackendProbe() {
|
||||
const runner = new WPTRunner('compression');
|
||||
assert.strictEqual(runner.runSpec, backends[backendProbe]);
|
||||
}
|
||||
|
||||
function checkBackendSelection() {
|
||||
const env = { ...process.env };
|
||||
delete env.WPT_BACKEND;
|
||||
delete env.WPT_INSPECT;
|
||||
|
||||
for (const [name, expected, args, overrides] of [
|
||||
['default', 'thread', [__filename]],
|
||||
['environment override', 'process', [__filename], { WPT_BACKEND: 'process' }],
|
||||
['inspect override', 'process', [__filename], {
|
||||
WPT_BACKEND: 'thread',
|
||||
WPT_INSPECT: '1',
|
||||
}],
|
||||
]) {
|
||||
const result = spawnSync(process.execPath, args, {
|
||||
env: {
|
||||
...env,
|
||||
...overrides,
|
||||
NODE_TEST_WPT_BACKEND_PROBE: expected,
|
||||
},
|
||||
encoding: 'utf8',
|
||||
timeout: common.platformTimeout(10_000),
|
||||
});
|
||||
const { error, status, stdout, stderr } = result;
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(
|
||||
status,
|
||||
0,
|
||||
`${name} WPT backend probe failed:\n${stdout}${stderr}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkInspectSelection() {
|
||||
const driver = path.join(__dirname, '../wpt/test-compression.js');
|
||||
const env = { ...process.env, WPT_INSPECT: '1' };
|
||||
delete env.WPT_BACKEND;
|
||||
|
||||
const runFailure = common.mustCall((args, expected) => {
|
||||
const result = spawnSync(process.execPath, [driver, ...args], {
|
||||
env,
|
||||
encoding: 'utf8',
|
||||
timeout: common.platformTimeout(10_000),
|
||||
});
|
||||
const { error, status, stdout, stderr } = result;
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(status, 1, `WPT inspect probe passed:\n${stdout}${stderr}`);
|
||||
assert.match(stderr, expected);
|
||||
}, 3);
|
||||
|
||||
runFailure([], /WPT_INSPECT requires a WPT test path/);
|
||||
runFailure(
|
||||
['compression-bad-chunks.any.js'],
|
||||
/WPT_INSPECT requires exactly one generated WPT test path; .* matched 2:\r?\ncompression\/compression-bad-chunks\.any\.html\r?\ncompression\/compression-bad-chunks\.any\.worker\.html/,
|
||||
);
|
||||
runFailure(
|
||||
['compression/compression-bad-chunks.any.worker.html'],
|
||||
/WPT_INSPECT does not support worker tests: compression\/compression-bad-chunks\.any\.worker\.html/,
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
checkBackendSelection();
|
||||
checkInspectSelection();
|
||||
checkQueuedSpecsKeepRunnerAlive();
|
||||
|
||||
const completed = await compare(false);
|
||||
@@ -197,7 +265,9 @@ async function main() {
|
||||
assert.deepStrictEqual(workerResults, windowResults);
|
||||
}
|
||||
|
||||
if (queueProbe) {
|
||||
if (backendProbe) {
|
||||
runDefaultBackendProbe();
|
||||
} else if (queueProbe) {
|
||||
runQueueProbe();
|
||||
} else {
|
||||
main().then(common.mustCall());
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const { NodeInstance } = require('../common/inspector-helper');
|
||||
|
||||
const driver = path.join(__dirname, '../wpt/test-compression.js');
|
||||
|
||||
async function main() {
|
||||
const parent = new NodeInstance([], `
|
||||
delete process.env.WPT_BACKEND;
|
||||
process.env.WPT_INSPECT = '1';
|
||||
process.argv[2] = 'compression/compression-bad-chunks.any.html';
|
||||
require(${JSON.stringify(driver)});
|
||||
`, '', {
|
||||
log() {},
|
||||
error() {},
|
||||
});
|
||||
const stderr = [];
|
||||
parent.on('stderr', (line) => stderr.push(line));
|
||||
|
||||
const session = await parent.connectInspectorSession();
|
||||
await session.send([
|
||||
{ method: 'Runtime.enable' },
|
||||
{ method: 'Debugger.enable' },
|
||||
{ method: 'Runtime.runIfWaitingForDebugger' },
|
||||
]);
|
||||
await session.waitForNotification('Debugger.paused');
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
await session.disconnect();
|
||||
|
||||
const { exitCode, signal } = await parent.expectShutdown();
|
||||
assert.strictEqual(signal, null);
|
||||
assert.strictEqual(exitCode, 0);
|
||||
assert.strictEqual(
|
||||
stderr.filter((line) => line.startsWith('Debugger listening on ')).length,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
main().then(common.mustCall());
|
||||
@@ -42,6 +42,20 @@ tests in child processes instead:
|
||||
WPT_BACKEND=process tools/test.py wpt
|
||||
```
|
||||
|
||||
### Debugging a test
|
||||
|
||||
Set `WPT_INSPECT=1` to run one generated main-thread test in a child process
|
||||
with `--inspect-brk` on an available port:
|
||||
|
||||
```bash
|
||||
WPT_INSPECT=1 out/Release/node test/wpt/test-compression.js \
|
||||
'compression/compression-bad-chunks.any.html'
|
||||
```
|
||||
|
||||
Connect an inspector client to the URL printed on stderr. A source file that
|
||||
generates multiple tests is rejected with the exact paths to choose from.
|
||||
Worker tests are not supported by inspect mode.
|
||||
|
||||
<a id="add-tests"></a>
|
||||
|
||||
## How to add tests for a new module
|
||||
@@ -96,6 +110,7 @@ selected backend.
|
||||
that require sequential execution (e.g. web-locks, webstorage).
|
||||
* `backend` {string} Test execution backend. Must be either `'thread'` or
|
||||
`'process'`. Defaults to `'thread'`. `WPT_BACKEND` overrides this option.
|
||||
`WPT_INSPECT` always uses `'process'`.
|
||||
|
||||
#### `runner.setFlags(flags)`
|
||||
|
||||
|
||||
Reference in new issue
Block a user