| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -338,6 +338,11 @@ added: REPLACEME | |||
| 338 | 338 | fail after. | |
| 339 | 339 | If unspecified, subtests inherit this value from their parent. | |
| 340 | 340 | **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`. | ||
| 341 | 346 | * Returns: {TapStream} | |
| 342 | 347 | ||
| 343 | 348 | ```js | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -120,6 +120,7 @@ function createWorkerProcess(id, env) { | |||
| 120 | 120 | const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/; | |
| 121 | 121 | const nodeOptions = process.env.NODE_OPTIONS || ''; | |
| 122 | 122 | ||
| 123 | + // TODO(MoLow): Use getInspectPort from internal/util/inspector | ||
| 123 | 124 | if (ArrayPrototypeSome(execArgv, | |
| 124 | 125 | (arg) => RegExpPrototypeExec(debugArgRegex, arg) !== null) || | |
| 125 | 126 | RegExpPrototypeExec(debugArgRegex, nodeOptions) !== null) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,12 +2,23 @@ | |||
| 2 | 2 | const { | |
| 3 | 3 | prepareMainThreadExecution, | |
| 4 | 4 | } = require('internal/bootstrap/pre_execution'); | |
| 5 | + const { isUsingInspector } = require('internal/util/inspector'); | ||
| 5 | 6 | const { run } = require('internal/test_runner/runner'); | |
| 6 | 7 | ||
| 7 | 8 | prepareMainThreadExecution(false); | |
| 8 | 9 | markBootstrapComplete(); | |
| 9 | 10 | ||
| 10 | - const tapStream = run(); | ||
| 11 | + let concurrency = true; | ||
| 12 | + let inspectPort; | ||
| 13 | + | ||
| 14 | + if (isUsingInspector()) { | ||
| 15 | + process.emitWarning('Using the inspector with --test forces running at a concurrency of 1. ' + | ||
| 16 | + 'Use the inspectPort option to run with concurrency'); | ||
| 17 | + concurrency = 1; | ||
| 18 | + inspectPort = process.debugPort; | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + const tapStream = run({ concurrency, inspectPort }); | ||
| 11 | 22 | tapStream.pipe(process.stdout); | |
| 12 | 23 | tapStream.once('test:fail', () => { | |
| 13 | 24 | process.exitCode = 1; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,18 +1,22 @@ | |||
| 1 | 1 | 'use strict'; | |
| 2 | 2 | const { | |
| 3 | 3 | ArrayFrom, | |
| 4 | - ArrayPrototypeConcat, | ||
| 5 | 4 | ArrayPrototypeFilter, | |
| 6 | 5 | ArrayPrototypeIncludes, | |
| 7 | 6 | ArrayPrototypeJoin, | |
| 7 | + ArrayPrototypePop, | ||
| 8 | + ArrayPrototypePush, | ||
| 8 | 9 | ArrayPrototypeSlice, | |
| 9 | 10 | ArrayPrototypeSort, | |
| 10 | 11 | ObjectAssign, | |
| 11 | 12 | PromisePrototypeThen, | |
| 13 | + RegExpPrototypeSymbolSplit, | ||
| 12 | 14 | SafePromiseAll, | |
| 13 | 15 | SafeSet, | |
| 16 | + StringPrototypeEndsWith, | ||
| 14 | 17 | } = primordials; | |
| 15 | 18 | ||
| 19 | + const { Buffer } = require('buffer'); | ||
| 16 | 20 | const { spawn } = require('child_process'); | |
| 17 | 21 | const { readdirSync, statSync } = require('fs'); | |
| 18 | 22 | const console = require('internal/console/global'); | |
@@ -22,6 +26,7 @@ const { | |||
| 22 | 26 | }, | |
| 23 | 27 | } = require('internal/errors'); | |
| 24 | 28 | const { validateArray } = require('internal/validators'); | |
| 29 | + const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector'); | ||
| 25 | 30 | const { kEmptyObject } = require('internal/util'); | |
| 26 | 31 | const { createTestTree } = require('internal/test_runner/harness'); | |
| 27 | 32 | const { kSubtestsFailed, Test } = require('internal/test_runner/test'); | |
@@ -100,25 +105,59 @@ function filterExecArgv(arg) { | |||
| 100 | 105 | return !ArrayPrototypeIncludes(kFilterArgs, arg); | |
| 101 | 106 | } | |
| 102 | 107 | ||
| 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) { | ||
| 104 | 141 | 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 }); | ||
| 108 | 143 | ||
| 109 | 144 | const child = spawn(process.execPath, args, { signal: t.signal, encoding: 'utf8' }); | |
| 110 | 145 | // TODO(cjihrig): Implement a TAP parser to read the child's stdout | |
| 111 | 146 | // instead of just displaying it all if the child fails. | |
| 112 | 147 | let err; | |
| 148 | + let stderr = ''; | ||
| 113 | 149 | ||
| 114 | 150 | child.on('error', (error) => { | |
| 115 | 151 | err = error; | |
| 116 | 152 | }); | |
| 117 | 153 | ||
| 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([ | ||
| 119 | 159 | once(child, 'exit', { signal: t.signal }), | |
| 120 | 160 | child.stdout.toArray({ signal: t.signal }), | |
| 121 | - child.stderr.toArray({ signal: t.signal }), | ||
| 122 | 161 | ]); | |
| 123 | 162 | ||
| 124 | 163 | if (code !== 0 || signal !== null) { | |
@@ -128,7 +167,7 @@ function runTestFile(path, root) { | |||
| 128 | 167 | exitCode: code, | |
| 129 | 168 | signal: signal, | |
| 130 | 169 | stdout: ArrayPrototypeJoin(stdout, ''), | |
| 131 | - stderr: ArrayPrototypeJoin(stderr, ''), | ||
| 170 | + stderr, | ||
| 132 | 171 | // The stack will not be useful since the failures came from tests | |
| 133 | 172 | // in a child process. | |
| 134 | 173 | stack: undefined, | |
@@ -145,7 +184,7 @@ function run(options) { | |||
| 145 | 184 | if (options === null || typeof options !== 'object') { | |
| 146 | 185 | options = kEmptyObject; | |
| 147 | 186 | } | |
| 148 | - const { concurrency, timeout, signal, files } = options; | ||
| 187 | + const { concurrency, timeout, signal, files, inspectPort } = options; | ||
| 149 | 188 | ||
| 150 | 189 | if (files != null) { | |
| 151 | 190 | validateArray(files, 'options.files'); | |
@@ -154,7 +193,7 @@ function run(options) { | |||
| 154 | 193 | const root = createTestTree({ concurrency, timeout, signal }); | |
| 155 | 194 | const testFiles = files ?? createTestFileList(); | |
| 156 | 195 | ||
| 157 | - PromisePrototypeThen(SafePromiseAll(testFiles, (path) => runTestFile(path, root)), | ||
| 196 | + PromisePrototypeThen(SafePromiseAll(testFiles, (path) => runTestFile(path, root, inspectPort)), | ||
| 158 | 197 | () => root.postRun()); | |
| 159 | 198 | ||
| 160 | 199 | return root.reporter; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -58,8 +58,6 @@ const kDefaultTimeout = null; | |||
| 58 | 58 | const noop = FunctionPrototype; | |
| 59 | 59 | const isTestRunner = getOptionValue('--test'); | |
| 60 | 60 | const testOnlyFlag = !isTestRunner && getOptionValue('--test-only'); | |
| 61 | - // TODO(cjihrig): Use uv_available_parallelism() once it lands. | ||
| 62 | - const rootConcurrency = isTestRunner ? MathMax(cpus().length - 1, 1) : 1; | ||
| 63 | 61 | const kShouldAbort = Symbol('kShouldAbort'); | |
| 64 | 62 | const kRunHook = Symbol('kRunHook'); | |
| 65 | 63 | const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']); | |
@@ -150,7 +148,7 @@ class Test extends AsyncResource { | |||
| 150 | 148 | } | |
| 151 | 149 | ||
| 152 | 150 | if (parent === null) { | |
| 153 | - this.concurrency = rootConcurrency; | ||
| 151 | + this.concurrency = 1; | ||
| 154 | 152 | this.indent = ''; | |
| 155 | 153 | this.indentString = kDefaultIndent; | |
| 156 | 154 | this.only = testOnlyFlag; | |
@@ -180,6 +178,7 @@ class Test extends AsyncResource { | |||
| 180 | 178 | ||
| 181 | 179 | case 'boolean': | |
| 182 | 180 | if (concurrency) { | |
| 181 | + // TODO(cjihrig): Use uv_available_parallelism() once it lands. | ||
| 183 | 182 | this.concurrency = parent === null ? MathMax(cpus().length - 1, 1) : Infinity; | |
| 184 | 183 | } else { | |
| 185 | 184 | this.concurrency = 1; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,12 +2,47 @@ | |||
| 2 | 2 | ||
| 3 | 3 | const { | |
| 4 | 4 | ArrayPrototypeConcat, | |
| 5 | + ArrayPrototypeSome, | ||
| 5 | 6 | FunctionPrototypeBind, | |
| 6 | 7 | ObjectDefineProperty, | |
| 7 | 8 | ObjectKeys, | |
| 8 | 9 | ObjectPrototypeHasOwnProperty, | |
| 10 | + RegExpPrototypeExec, | ||
| 9 | 11 | } = primordials; | |
| 10 | 12 | ||
| 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 | + | ||
| 11 | 46 | let session; | |
| 12 | 47 | function sendInspectorCommand(cb, onError) { | |
| 13 | 48 | const { hasInspector } = internalBinding('config'); | |
@@ -22,6 +57,10 @@ function sendInspectorCommand(cb, onError) { | |||
| 22 | 57 | } | |
| 23 | 58 | } | |
| 24 | 59 | ||
| 60 | + function isInspectorMessage(string) { | ||
| 61 | + return isUsingInspector() && RegExpPrototypeExec(kInspectMsgRegex, string) !== null; | ||
| 62 | + } | ||
| 63 | + | ||
| 25 | 64 | // Create a special require function for the inspector command line API | |
| 26 | 65 | function installConsoleExtensions(commandLineApi) { | |
| 27 | 66 | if (commandLineApi.require) { return; } | |
@@ -65,7 +104,10 @@ function wrapConsole(consoleFromNode, consoleFromVM) { | |||
| 65 | 104 | // Stores the console from VM, should be set during bootstrap. | |
| 66 | 105 | let consoleFromVM; | |
| 67 | 106 | module.exports = { | |
| 107 | + getInspectPort, | ||
| 68 | 108 | installConsoleExtensions, | |
| 109 | + isInspectorMessage, | ||
| 110 | + isUsingInspector, | ||
| 69 | 111 | sendInspectorCommand, | |
| 70 | 112 | wrapConsole, | |
| 71 | 113 | get consoleFromVM() { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -161,9 +161,6 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) { | |||
| 161 | 161 | errors->push_back("either --test or --watch can be used, not both"); | |
| 162 | 162 | } | |
| 163 | 163 | ||
| 164 | - if (debug_options_.inspector_enabled) { | ||
| 165 | - errors->push_back("the inspector cannot be used with --test"); | ||
| 166 | - } | ||
| 167 | 164 | #ifndef ALLOW_ATTACHING_DEBUGGER_IN_TEST_RUNNER | |
| 168 | 165 | debug_options_.allow_attaching_debugger = false; | |
| 169 | 166 | #endif | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,8 @@ const { | |||
| 52 | 52 | spawnPromisified, | |
| 53 | 53 | } = common; | |
| 54 | 54 | ||
| 55 | + const getPort = () => common.PORT; | ||
| 56 | + | ||
| 55 | 57 | export { | |
| 56 | 58 | isMainThread, | |
| 57 | 59 | isWindows, | |
@@ -100,4 +102,5 @@ export { | |||
| 100 | 102 | runWithInvalidFD, | |
| 101 | 103 | createRequire, | |
| 102 | 104 | spawnPromisified, | |
| 105 | + getPort, | ||
| 103 | 106 | }; | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff 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 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments