Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 603eb54

Browse filesBrowse files
MoLowRafaelGSS
authored andcommitted
test_runner: support using --inspect with --test
PR-URL: #44520 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent 35f55ae commit 603eb54
Copy full SHA for 603eb54

File tree

Expand file treeCollapse file tree

13 files changed

+391
-23
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

13 files changed

+391
-23
lines changed
Open diff view settings
Collapse file

‎doc/api/test.md‎

Copy file name to clipboardExpand all lines: doc/api/test.md
+5Lines changed: 5 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ added: v18.9.0
338338
fail after.
339339
If unspecified, subtests inherit this value from their parent.
340340
**Default:** `Infinity`.
341+
* `inspectPort` {number|Function} Sets inspector port of test child process.
342+
This can be a number, or a function that takes no arguments and returns a
343+
number. If a nullish value is provided, each process gets its own port,
344+
incremented from the primary's `process.debugPort`.
345+
**Default:** `undefined`.
341346
* Returns: {TapStream}
342347

343348
```js
Collapse file

‎lib/internal/cluster/primary.js‎

Copy file name to clipboardExpand all lines: lib/internal/cluster/primary.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ function createWorkerProcess(id, env) {
120120
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
121121
const nodeOptions = process.env.NODE_OPTIONS || '';
122122

123+
// TODO(MoLow): Use getInspectPort from internal/util/inspector
123124
if (ArrayPrototypeSome(execArgv,
124125
(arg) => RegExpPrototypeExec(debugArgRegex, arg) !== null) ||
125126
RegExpPrototypeExec(debugArgRegex, nodeOptions) !== null) {
Collapse file

‎lib/internal/main/test_runner.js‎

Copy file name to clipboardExpand all lines: lib/internal/main/test_runner.js
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,23 @@ const {
33
prepareMainThreadExecution,
44
markBootstrapComplete
55
} = require('internal/process/pre_execution');
6+
const { isUsingInspector } = require('internal/util/inspector');
67
const { run } = require('internal/test_runner/runner');
78

89
prepareMainThreadExecution(false);
910
markBootstrapComplete();
1011

11-
const tapStream = run();
12+
let concurrency = true;
13+
let inspectPort;
14+
15+
if (isUsingInspector()) {
16+
process.emitWarning('Using the inspector with --test forces running at a concurrency of 1. ' +
17+
'Use the inspectPort option to run with concurrency');
18+
concurrency = 1;
19+
inspectPort = process.debugPort;
20+
}
21+
22+
const tapStream = run({ concurrency, inspectPort });
1223
tapStream.pipe(process.stdout);
1324
tapStream.once('test:fail', () => {
1425
process.exitCode = 1;
Collapse file

‎lib/internal/test_runner/runner.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/runner.js
+49-10Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
'use strict';
22
const {
33
ArrayFrom,
4-
ArrayPrototypeConcat,
54
ArrayPrototypeFilter,
65
ArrayPrototypeIncludes,
76
ArrayPrototypeJoin,
7+
ArrayPrototypePop,
8+
ArrayPrototypePush,
89
ArrayPrototypeSlice,
910
ArrayPrototypeSort,
1011
ObjectAssign,
1112
PromisePrototypeThen,
13+
RegExpPrototypeSymbolSplit,
1214
SafePromiseAll,
1315
SafeSet,
16+
StringPrototypeEndsWith,
1417
} = primordials;
1518

19+
const { Buffer } = require('buffer');
1620
const { spawn } = require('child_process');
1721
const { readdirSync, statSync } = require('fs');
1822
const console = require('internal/console/global');
@@ -22,6 +26,7 @@ const {
2226
},
2327
} = require('internal/errors');
2428
const { validateArray } = require('internal/validators');
29+
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
2530
const { kEmptyObject } = require('internal/util');
2631
const { createTestTree } = require('internal/test_runner/harness');
2732
const { kSubtestsFailed, Test } = require('internal/test_runner/test');
@@ -100,25 +105,59 @@ function filterExecArgv(arg) {
100105
return !ArrayPrototypeIncludes(kFilterArgs, arg);
101106
}
102107

103-
function runTestFile(path, root) {
108+
function getRunArgs({ path, inspectPort }) {
109+
const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv);
110+
if (isUsingInspector()) {
111+
ArrayPrototypePush(argv, `--inspect-port=${getInspectPort(inspectPort)}`);
112+
}
113+
ArrayPrototypePush(argv, path);
114+
return argv;
115+
}
116+
117+
function makeStderrCallback(callback) {
118+
if (!isUsingInspector()) {
119+
return callback;
120+
}
121+
let buffer = Buffer.alloc(0);
122+
return (data) => {
123+
callback(data);
124+
const newData = Buffer.concat([buffer, data]);
125+
const str = newData.toString('utf8');
126+
let lines = str;
127+
if (StringPrototypeEndsWith(lines, '\n')) {
128+
buffer = Buffer.alloc(0);
129+
} else {
130+
lines = RegExpPrototypeSymbolSplit(/\r?\n/, str);
131+
buffer = Buffer.from(ArrayPrototypePop(lines), 'utf8');
132+
lines = ArrayPrototypeJoin(lines, '\n');
133+
}
134+
if (isInspectorMessage(lines)) {
135+
process.stderr.write(lines);
136+
}
137+
};
138+
}
139+
140+
function runTestFile(path, root, inspectPort) {
104141
const subtest = root.createSubtest(Test, path, async (t) => {
105-
const args = ArrayPrototypeConcat(
106-
ArrayPrototypeFilter(process.execArgv, filterExecArgv),
107-
path);
142+
const args = getRunArgs({ path, inspectPort });
108143

109144
const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8' });
110145
// TODO(cjihrig): Implement a TAP parser to read the child's stdout
111146
// instead of just displaying it all if the child fails.
112147
let err;
148+
let stderr = '';
113149

114150
child.on('error', (error) => {
115151
err = error;
116152
});
117153

118-
const { 0: { 0: code, 1: signal }, 1: stdout, 2: stderr } = await SafePromiseAll([
154+
child.stderr.on('data', makeStderrCallback((data) => {
155+
stderr += data;
156+
}));
157+
158+
const { 0: { 0: code, 1: signal }, 1: stdout } = await SafePromiseAll([
119159
once(child, 'exit', { signal: t.signal }),
120160
child.stdout.toArray({ signal: t.signal }),
121-
child.stderr.toArray({ signal: t.signal }),
122161
]);
123162

124163
if (code !== 0 || signal !== null) {
@@ -128,7 +167,7 @@ function runTestFile(path, root) {
128167
exitCode: code,
129168
signal: signal,
130169
stdout: ArrayPrototypeJoin(stdout, ''),
131-
stderr: ArrayPrototypeJoin(stderr, ''),
170+
stderr,
132171
// The stack will not be useful since the failures came from tests
133172
// in a child process.
134173
stack: undefined,
@@ -145,7 +184,7 @@ function run(options) {
145184
if (options === null || typeof options !== 'object') {
146185
options = kEmptyObject;
147186
}
148-
const { concurrency, timeout, signal, files } = options;
187+
const { concurrency, timeout, signal, files, inspectPort } = options;
149188

150189
if (files != null) {
151190
validateArray(files, 'options.files');
@@ -154,7 +193,7 @@ function run(options) {
154193
const root = createTestTree({ concurrency, timeout, signal });
155194
const testFiles = files ?? createTestFileList();
156195

157-
PromisePrototypeThen(SafePromiseAll(testFiles, (path) => runTestFile(path, root)),
196+
PromisePrototypeThen(SafePromiseAll(testFiles, (path) => runTestFile(path, root, inspectPort)),
158197
() => root.postRun());
159198

160199
return root.reporter;
Collapse file

‎lib/internal/test_runner/test.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/test.js
+2-3Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ const kDefaultTimeout = null;
5757
const noop = FunctionPrototype;
5858
const isTestRunner = getOptionValue('--test');
5959
const testOnlyFlag = !isTestRunner && getOptionValue('--test-only');
60-
// TODO(cjihrig): Use uv_available_parallelism() once it lands.
61-
const rootConcurrency = isTestRunner ? MathMax(cpus().length - 1, 1) : 1;
6260
const kShouldAbort = Symbol('kShouldAbort');
6361
const kRunHook = Symbol('kRunHook');
6462
const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']);
@@ -146,7 +144,7 @@ class Test extends AsyncResource {
146144
}
147145

148146
if (parent === null) {
149-
this.concurrency = rootConcurrency;
147+
this.concurrency = 1;
150148
this.indent = '';
151149
this.indentString = kDefaultIndent;
152150
this.only = testOnlyFlag;
@@ -176,6 +174,7 @@ class Test extends AsyncResource {
176174

177175
case 'boolean':
178176
if (concurrency) {
177+
// TODO(cjihrig): Use uv_available_parallelism() once it lands.
179178
this.concurrency = parent === null ? MathMax(cpus().length - 1, 1) : Infinity;
180179
} else {
181180
this.concurrency = 1;
Collapse file

‎lib/internal/util/inspector.js‎

Copy file name to clipboardExpand all lines: lib/internal/util/inspector.js
+42Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,47 @@
22

33
const {
44
ArrayPrototypeConcat,
5+
ArrayPrototypeSome,
56
FunctionPrototypeBind,
67
ObjectDefineProperty,
78
ObjectKeys,
89
ObjectPrototypeHasOwnProperty,
10+
RegExpPrototypeExec,
911
} = primordials;
1012

13+
const { validatePort } = require('internal/validators');
14+
15+
const kMinPort = 1024;
16+
const kMaxPort = 65535;
17+
const kInspectArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
18+
const kInspectMsgRegex = /Debugger listening on ws:\/\/\[?(.+?)\]?:(\d+)\/|Debugger attached|Waiting for the debugger to disconnect\.\.\./;
19+
20+
let _isUsingInspector;
21+
function isUsingInspector() {
22+
_isUsingInspector ??=
23+
ArrayPrototypeSome(process.execArgv, (arg) => RegExpPrototypeExec(kInspectArgRegex, arg) !== null) ||
24+
RegExpPrototypeExec(kInspectArgRegex, process.env.NODE_OPTIONS) !== null;
25+
return _isUsingInspector;
26+
}
27+
28+
let debugPortOffset = 1;
29+
function getInspectPort(inspectPort) {
30+
if (!isUsingInspector()) {
31+
return null;
32+
}
33+
if (typeof inspectPort === 'function') {
34+
inspectPort = inspectPort();
35+
} else if (inspectPort == null) {
36+
inspectPort = process.debugPort + debugPortOffset;
37+
if (inspectPort > kMaxPort)
38+
inspectPort = inspectPort - kMaxPort + kMinPort - 1;
39+
debugPortOffset++;
40+
}
41+
validatePort(inspectPort);
42+
43+
return inspectPort;
44+
}
45+
1146
let session;
1247
function sendInspectorCommand(cb, onError) {
1348
const { hasInspector } = internalBinding('config');
@@ -22,6 +57,10 @@ function sendInspectorCommand(cb, onError) {
2257
}
2358
}
2459

60+
function isInspectorMessage(string) {
61+
return isUsingInspector() && RegExpPrototypeExec(kInspectMsgRegex, string) !== null;
62+
}
63+
2564
// Create a special require function for the inspector command line API
2665
function installConsoleExtensions(commandLineApi) {
2766
if (commandLineApi.require) { return; }
@@ -63,7 +102,10 @@ function wrapConsole(consoleFromNode) {
63102
}
64103

65104
module.exports = {
105+
getInspectPort,
66106
installConsoleExtensions,
107+
isInspectorMessage,
108+
isUsingInspector,
67109
sendInspectorCommand,
68110
wrapConsole,
69111
};
Collapse file

‎src/node_options.cc‎

Copy file name to clipboardExpand all lines: src/node_options.cc
-3Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,6 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
161161
errors->push_back("either --test or --watch can be used, not both");
162162
}
163163

164-
if (debug_options_.inspector_enabled) {
165-
errors->push_back("the inspector cannot be used with --test");
166-
}
167164
#ifndef ALLOW_ATTACHING_DEBUGGER_IN_TEST_RUNNER
168165
debug_options_.allow_attaching_debugger = false;
169166
#endif
Collapse file

‎test/common/index.mjs‎

Copy file name to clipboardExpand all lines: test/common/index.mjs
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const {
5252
spawnPromisified,
5353
} = common;
5454

55+
const getPort = () => common.PORT;
56+
5557
export {
5658
isMainThread,
5759
isWindows,
@@ -100,4 +102,5 @@ export {
100102
runWithInvalidFD,
101103
createRequire,
102104
spawnPromisified,
105+
getPort,
103106
};
Collapse file
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const fixtures = require('../../common/fixtures');
5+
const { run } = require('node:test');
6+
const assert = require('node:assert');
7+
8+
const badPortError = { name: 'RangeError', code: 'ERR_SOCKET_BAD_PORT' };
9+
let inspectPort = 'inspectPort' in process.env ? Number(process.env.inspectPort) : undefined;
10+
let expectedError;
11+
12+
if (process.env.inspectPort === 'addTwo') {
13+
inspectPort = common.mustCall(() => { return process.debugPort += 2; });
14+
} else if (process.env.inspectPort === 'string') {
15+
inspectPort = 'string';
16+
expectedError = badPortError;
17+
} else if (process.env.inspectPort === 'null') {
18+
inspectPort = null;
19+
} else if (process.env.inspectPort === 'bignumber') {
20+
inspectPort = 1293812;
21+
expectedError = badPortError;
22+
} else if (process.env.inspectPort === 'negativenumber') {
23+
inspectPort = -9776;
24+
expectedError = badPortError;
25+
} else if (process.env.inspectPort === 'bignumberfunc') {
26+
inspectPort = common.mustCall(() => 123121);
27+
expectedError = badPortError;
28+
} else if (process.env.inspectPort === 'strfunc') {
29+
inspectPort = common.mustCall(() => 'invalidPort');
30+
expectedError = badPortError;
31+
}
32+
33+
const stream = run({ files: [fixtures.path('test-runner/run_inspect_assert.js')], inspectPort });
34+
if (expectedError) {
35+
stream.on('test:fail', common.mustCall(({ error }) => {
36+
assert.deepStrictEqual({ name: error.cause.name, code: error.cause.code }, expectedError);
37+
}));
38+
} else {
39+
stream.on('test:fail', common.mustNotCall());
40+
}
Collapse file
+19Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const assert = require('node:assert');
4+
5+
const { expectedPort, expectedInitialPort, expectedHost } = process.env;
6+
const debugOptions =
7+
require('internal/options').getOptionValue('--inspect-port');
8+
9+
if ('expectedPort' in process.env) {
10+
assert.strictEqual(process.debugPort, +expectedPort);
11+
}
12+
13+
if ('expectedInitialPort' in process.env) {
14+
assert.strictEqual(debugOptions.port, +expectedInitialPort);
15+
}
16+
17+
if ('expectedHost' in process.env) {
18+
assert.strictEqual(debugOptions.host, expectedHost);
19+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.