diff --git a/doc/api/debugger.md b/doc/api/debugger.md index 43f713ad824206..0ac62b64dcc984 100644 --- a/doc/api/debugger.md +++ b/doc/api/debugger.md @@ -235,6 +235,13 @@ debug> > Stability: 1 - Experimental @@ -261,7 +268,9 @@ $ node inspect --probe :[:] --expr * `--probe :[:]`: Source location of the probe. When execution reaches the location, the provided expressions are evaluated and printed in - the output. Line and column numbers are 1-based. When omitted, column defaults to 1. + the output. `` matches the URL suffix of the script to probe. + `` and `` numbers are 1-based. When `` is omitted, the probe + binds to the first executable column on the line. * `--expr `: JavaScript expression to evaluate whenever execution reaches the location specified by the preceding `--probe`. Must immediately follow the `--probe` it belongs to. @@ -313,13 +322,17 @@ Without `--json`, by default the output is printed in a human-readable text form ```console $ node inspect --probe cli.js:5 --expr 'rss' cli.js -Hit 1 at cli.js:5 +Hit 1 at file:///path/to/cli.js:5:3 rss = 54935552 -Hit 2 at cli.js:5 +Hit 2 at file:///path/to/cli.js:5:3 rss = 55083008 Completed ``` +The original `:[:]` passed to `--probe` may be resolved to a different +location to ensure it's pausable, or it can match multiple loaded scripts, so the actual +evaluation location helps disambiguate the results. + Primitive results are printed directly, while objects and arrays use Chrome DevTools Protocol preview data when available. Other non-primitive values fall back to the Chrome DevTools Protocol `description` string. @@ -331,16 +344,24 @@ When `--json` is used, the output shape looks like this: ```console $ node inspect --json --probe cli.js:5 --expr 'rss' cli.js -{"v":1,"probes":[{"expr":"rss","target":["cli.js",5]}],"results":[{"probe":0,"event":"hit","hit":1,"result":{"type":"number","value":55443456,"description":"55443456"}},{"probe":0,"event":"hit","hit":2,"result":{"type":"number","value":55574528,"description":"55574528"}},{"event":"completed"}]} +{"v":2,"probes":[{"expr":"rss","target":{"suffix":"cli.js","line":5}}],"results":[{"probe":0,"event":"hit","hit":1,"location":{"url":"file:///path/to/cli.js","line":5,"column":3},"result":{"type":"number","value":55443456,"description":"55443456"}},{"probe":0,"event":"hit","hit":2,"location":{"url":"file:///path/to/cli.js","line":5,"column":3},"result":{"type":"number","value":55574528,"description":"55574528"}},{"event":"completed"}]} ``` ```json { - "v": 1, // Probe JSON schema version. + "v": 2, // Probe JSON schema version. "probes": [ { "expr": "rss", // The expression paired with --probe. - "target": ["cli.js", 5] // [file, line] or [file, line, col]. + "target": { + // The user's probe specification. `suffix` is the raw passed + // to --probe and is matched as a path-separator-anchored suffix + // against every loaded script's URL. `column` is present only if the + // user supplied `:col`. The actual evaluation location may differ + // from the target and will be reported in each hit's `location` field. + "suffix": "cli.js", + "line": 5 + } } ], "results": [ @@ -348,6 +369,14 @@ $ node inspect --json --probe cli.js:5 --expr 'rss' cli.js "probe": 0, // Index into probes[]. "event": "hit", // Hit events are recorded in observation order. "hit": 1, // 1-based hit count for this probe. + "location": { + // The actual location where the execution is paused to evaluate + // the expression of the probe. This may differ from the probe's + // target due to pausability adjustments or multiple matches. + "url": "file:///path/to/cli.js", + "line": 5, + "column": 3 + }, "result": { "type": "number", "value": 55443456, @@ -359,6 +388,7 @@ $ node inspect --json --probe cli.js:5 --expr 'rss' cli.js "probe": 0, "event": "hit", "hit": 2, + "location": { "url": "file:///path/to/cli.js", "line": 5, "column": 3 }, "result": { "type": "number", "value": 55574528, @@ -427,9 +457,9 @@ $ node inspect --probe app.js:4 --expr 'x' --probe app.js:4 --expr 'y' -- app.js Prints ```text -Hit 1 at app.js:4 +Hit 1 at file:///path/to/app.js:4:1 x = {x: 42} -Hit 1 at app.js:4 +Hit 1 at file:///path/to/app.js:4:1 y = {y: 35} Completed ``` @@ -441,7 +471,7 @@ $ node inspect --probe app.js:4 --expr 'x' --probe app.js:4 --expr 'y' --json -- Prints ```json -{"v":1,"probes":[{"expr":"x","target":["app.js",4]},{"expr":"y","target":["app.js",4]}],"results":[{"probe":0,"event":"hit","hit":1,"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"x","type":"number","value":"42"}]}}},{"probe":1,"event":"hit","hit":1,"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"y","type":"number","value":"35"}]}}},{"event":"completed"}]} +{"v":2,"probes":[{"expr":"x","target":{"suffix":"app.js","line":4}},{"expr":"y","target":{"suffix":"app.js","line":4}}],"results":[{"probe":0,"event":"hit","hit":1,"location":{"url":"file:///path/to/app.js","line":4,"column":1},"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"x","type":"number","value":"42"}]}}},{"probe":1,"event":"hit","hit":1,"location":{"url":"file:///path/to/app.js","line":4,"column":1},"result":{"type":"object","description":"Object","preview":{"type":"object","description":"Object","overflow":false,"properties":[{"name":"y","type":"number","value":"35"}]}}},{"event":"completed"}]} ``` ### Selecting the probe location @@ -459,7 +489,7 @@ console.log(x); // line 3 ```console $ node inspect --probe app.js:1 --expr 'x' app.js -Hit 1 at app.js:1 +Hit 1 at file:///path/to/app.js:1:1 [error] x = ReferenceError: Cannot access 'x' from debugger ... Completed @@ -469,13 +499,16 @@ Instead, probe at a location where the variable is already initialized: ```console $ node inspect --probe app.js:3 --expr 'x' app.js -Hit 1 at app.js:3 +Hit 1 at file:///path/to/app.js:3:1 x = 42 Completed ``` -Probe paths are matched against loaded script URLs by basename, similar to how -native debuggers typically match breakpoints. Given: +The `` argument is matched as a path suffix of every loaded +script URL, anchored on a path separator. Passing only a basename +matches every loaded script with that basename, similar to how native +debuggers typically match breakpoints, while passing a partial path +narrows the match. Given: ```text project/ @@ -484,7 +517,10 @@ project/ ``` `--probe utils.js:10` binds to _both_ files and produces one hit per match. -To disambiguate, specify a fuller path that only matches the intended file: +Each hit carries its own `location` field identifying where the expression +was actually executed, so consumers can attribute the result to one of the +two files accurately. To disambiguate at bind time, specify a fuller path +that only matches the intended file: ```console $ node inspect --probe src/utils.js:10 --expr 'x' main.js # matches only src/utils.js diff --git a/lib/internal/debugger/inspect_helpers.js b/lib/internal/debugger/inspect_helpers.js index 0511f06f5176a5..d0049acc06cd22 100644 --- a/lib/internal/debugger/inspect_helpers.js +++ b/lib/internal/debugger/inspect_helpers.js @@ -99,9 +99,12 @@ Example: Options: --probe :[:] - Source location of the probe (1-based, col defaults - to 1). Matches by file basename, use a fuller path to - disambiguate. Must be immediately followed by --expr. + Source location of the probe. is matched as a + path suffix of every loaded script URL, anchored on + a path separator. and the optional are + 1-based. If is omitted, the probe binds to + the first executable column on the line. This option + must be immediately followed by a pairing --expr. --expr Expression to evaluate in the lexical scope of the preceding --probe each time execution reaches it. Avoid probing let/const-bound variables at their @@ -114,6 +117,8 @@ Options: Semantics: * Multiple --probe/--expr pairs are allowed. Same-location --probes share a pause and scope, their --exprs are evaluated in command-line order. +* --probe utils.js:[:] matches every loaded utils.js. Pass a + fuller path e.g. src/utils.js to narrow the match. * Use -- before any Node.js flags intended for the child process. * Target errors are surfaced in the report as a terminal 'error' event. The probing process exits 0 unless it encounters an error itself. diff --git a/lib/internal/debugger/inspect_probe.js b/lib/internal/debugger/inspect_probe.js index c872202f1876f7..4600d8eb10ee3b 100644 --- a/lib/internal/debugger/inspect_probe.js +++ b/lib/internal/debugger/inspect_probe.js @@ -40,11 +40,41 @@ const { } = internalBinding('errors'); const kProbeDefaultTimeout = 30000; -const kProbeVersion = 1; +const kProbeVersion = 2; const kProbeDisconnectSentinel = 'Waiting for the debugger to disconnect...'; const kDigitsRegex = /^\d+$/; const kInspectPortRegex = /^--inspect-port=(\d+)$/; +/** + * The probe request specified by --probe, serialized into the public report. + * @typedef {object} ProbeTarget + * @property {string} suffix The raw suffix supplied by the user. + * @property {number} line 1-based line number. + * @property {number} [column] 1-based column number. + */ + +/** + * Location where the probe was evaluated, serialized into the public report. + * @typedef {object} Location + * @property {string} url V8-reported script URL. + * @property {number} line 1-based line number. + * @property {number} column 1-based column number. + */ + +/** + * Per-breakpoint state keyed by V8 `breakpointId` from `Debugger.setBreakpointByUrl`. + * @typedef {object} BreakpointDefinition + * @property {number[]} probeIndices Indices into probes that bound to this breakpoint. + */ + +/** + * Per-probe state corresponds to each --probe --expr pair. + * @typedef {object} Probe + * @property {string} expr Expression to evaluate on hit. + * @property {ProbeTarget} target User's original --probe request shape. + * @property {number} hits Count of hits observed. + */ + function parseUnsignedInteger(value, name, allowZero = false) { if (typeof value !== 'string' || RegExpPrototypeExec(kDigitsRegex, value) === null) { throw new ERR_DEBUGGER_STARTUP_ERROR(`Invalid ${name}: ${value}`); @@ -56,32 +86,34 @@ function parseUnsignedInteger(value, name, allowZero = false) { return parsed; } -// Accepts file:line or file:line:column formats. -// Non-greedy (.+?) allows Windows drive-letter paths like C:\foo.js:10. -function parseProbeLocation(text) { +/** + * @param {string} text Raw `--probe` argument. + * @returns {ProbeTarget} + */ +function parseProbeTarget(text) { + // Accepts file:line or file:line:column formats. + // Non-greedy (.+?) allows Windows drive-letter paths like C:\foo.js:10. const match = RegExpPrototypeExec(/^(.+?):(\d+)(?::(\d+))?$/, text); if (match === null) { throw new ERR_DEBUGGER_STARTUP_ERROR(`Invalid probe location: ${text}`); } - const file = match[1]; + const suffix = match[1]; const line = parseUnsignedInteger(match[2], 'probe location'); - const column = match[3] !== undefined ? - parseUnsignedInteger(match[3], 'probe location') : undefined; - const target = column === undefined ? [file, line] : [file, line, column]; + // Column is left as undefined if the user does not supply one. + const column = match[3] !== undefined ? parseUnsignedInteger(match[3], 'probe location') : undefined; + return { suffix, line, column }; +} - return { - file, - lineNumber: line - 1, - columnNumber: column === undefined ? undefined : column - 1, - target, - }; +function formatTargetText(target) { + const { suffix, line, column } = target; + return column === undefined ? `${suffix}:${line}` : `${suffix}:${line}:${column}`; } function formatPendingProbeLocations(probes, pending) { const seen = new SafeSet(); for (const probeIndex of pending) { - seen.add(ArrayPrototypeJoin(probes[probeIndex].target, ':')); + seen.add(formatTargetText(probes[probeIndex].target)); } return ArrayPrototypeJoin(ArrayFrom(seen), ', '); } @@ -202,6 +234,10 @@ function formatRemoteObject(result) { } } +function formatHitLocation(location) { + return `${location.url}:${location.line}:${location.column}`; +} + // Built human-readable text output for probe reports. function buildProbeTextReport(report) { const lines = []; @@ -209,8 +245,11 @@ function buildProbeTextReport(report) { for (const result of report.results) { if (result.event === 'hit') { const probe = report.probes[result.probe]; - const location = ArrayPrototypeJoin(probe.target, ':'); - ArrayPrototypePush(lines, `Hit ${result.hit} at ${location}`); + // If Debugger.scriptParsed was missed and the URL is unknown, fall back to the user's + // probe target text for readability. This is unlikely unless there's a bug in V8. + const locText = (result.location.url !== undefined) ? + formatHitLocation(result.location) : formatTargetText(probe.target); + ArrayPrototypePush(lines, `Hit ${result.hit} at ${locText}`); if (result.error !== undefined) { ArrayPrototypePush(lines, ` [error] ${probe.expr} = ` + @@ -268,7 +307,7 @@ function parseProbeTokens(tokens, args) { let json = false; let sawSeparator = false; let childStartIndex = args.length; - let pendingLocation; + let pendingTarget; let expectedExprIndex = -1; const probes = []; @@ -279,16 +318,16 @@ function parseProbeTokens(tokens, args) { break; } - if (pendingLocation !== undefined) { + if (pendingTarget !== undefined) { if (token.kind === 'option' && token.name === 'expr' && token.index === expectedExprIndex && token.value !== undefined) { ArrayPrototypePush(probes, { expr: token.value, - location: pendingLocation, + target: pendingTarget, }); - pendingLocation = undefined; + pendingTarget = undefined; continue; } @@ -321,7 +360,7 @@ function parseProbeTokens(tokens, args) { preview = true; break; case 'probe': - pendingLocation = parseProbeLocation(token.value); + pendingTarget = parseProbeTarget(token.value); expectedExprIndex = token.index + (token.inlineValue ? 1 : 2); break; case 'expr': @@ -335,7 +374,7 @@ function parseProbeTokens(tokens, args) { } } - if (pendingLocation !== undefined) { + if (pendingTarget !== undefined) { throw new ERR_DEBUGGER_STARTUP_ERROR( 'Each --probe must be followed immediately by --expr '); } @@ -391,24 +430,23 @@ class ProbeInspectorSession { this.finished = false; this.started = false; this.stderrBuffer = ''; + /** @type {Map} keyed by V8 breakpointId. */ this.breakpointDefinitions = new SafeMap(); + /** @type {Map} scriptId -> URL. */ + this.scriptIdToUrl = new SafeMap(); this.results = []; this.timeout = null; this.resolveCompletion = null; this.completionPromise = new Promise((resolve) => { this.resolveCompletion = resolve; }); - this.probes = ArrayPrototypeMap(options.probes, (probe) => ({ - expr: probe.expr, - target: probe.location.target, - location: probe.location, - hits: 0, - })); - + /** @type {Probe[]} */ + this.probes = ArrayPrototypeMap(options.probes, ({ expr, target }) => ({ expr, target, hits: 0 })); this.onChildOutput = FunctionPrototypeBind(this.onChildOutput, this); this.onChildExit = FunctionPrototypeBind(this.onChildExit, this); this.onClientClose = FunctionPrototypeBind(this.onClientClose, this); this.onPaused = FunctionPrototypeBind(this.onPaused, this); + this.onScriptParsed = FunctionPrototypeBind(this.onScriptParsed, this); } finish(state) { @@ -498,24 +536,38 @@ class ProbeInspectorSession { return; } - const callFrameId = params.callFrames?.[0]?.callFrameId; + const topFrame = params.callFrames?.[0]; + const callFrameId = topFrame?.callFrameId; if (callFrameId === undefined) { await this.resume(); return; } + const { scriptId, lineNumber, columnNumber } = topFrame.location; + // `Debugger.scriptParsed` should always precede a pause for the same script. + // It should only be undefined if there's a bug (even in that case, just omit it). + const location = { + url: this.scriptIdToUrl.get(scriptId), + // CDP locations are 0-based, locations in public report are 1-based. + line: lineNumber + 1, + column: columnNumber + 1, + }; for (const breakpointId of hitBreakpoints) { + // The breakpoint ID is stable even for scripts parsed after the initial resolution + // so we can count on it here. const definition = this.breakpointDefinitions.get(breakpointId); if (definition === undefined) { continue; } + + // Evaluate the expressions in the order they appear on the command line. for (const probeIndex of definition.probeIndices) { - await this.evaluateProbe(callFrameId, probeIndex); + await this.evaluateProbe(callFrameId, probeIndex, location); } } await this.resume(); } - async evaluateProbe(callFrameId, probeIndex) { + async evaluateProbe(callFrameId, probeIndex, location) { const probe = this.probes[probeIndex]; const evaluation = await this.client.callMethod('Debugger.evaluateOnCallFrame', { callFrameId, @@ -524,8 +576,7 @@ class ProbeInspectorSession { }); probe.hits++; - const result = { probe: probeIndex, event: 'hit', hit: probe.hits }; - + const result = { probe: probeIndex, event: 'hit', hit: probe.hits, location }; if (evaluation.exceptionDetails !== undefined) { result.error = evaluation.result === undefined ? { type: 'object', @@ -553,31 +604,35 @@ class ProbeInspectorSession { this.child.on('exit', this.onChildExit); this.client.on('close', this.onClientClose); this.client.on('Debugger.paused', this.onPaused); + this.client.on('Debugger.scriptParsed', this.onScriptParsed); + } + + onScriptParsed(params) { + // This map grows by the number of scripts parsed, which is limited, and is just a + // small string -> string map. The lifetime is bounded by probe timeout etc. so cleanup is overkill. + this.scriptIdToUrl.set(params.scriptId, params.url); } async bindBreakpoints() { - const uniqueLocations = new SafeMap(); + const uniqueTargets = new SafeMap(); for (let probeIndex = 0; probeIndex < this.probes.length; probeIndex++) { - const probe = this.probes[probeIndex]; - const key = `${probe.location.file}\n${probe.location.lineNumber}\n` + - `${probe.location.columnNumber === undefined ? '' : probe.location.columnNumber}`; - let entry = uniqueLocations.get(key); + const { target } = this.probes[probeIndex]; + const key = `${target.suffix}\n${target.line}\n${target.column ?? ''}`; + let entry = uniqueTargets.get(key); if (entry === undefined) { - entry = { location: probe.location, probeIndices: [] }; - uniqueLocations.set(key, entry); + entry = { target, probeIndices: [] }; + uniqueTargets.set(key, entry); } ArrayPrototypePush(entry.probeIndices, probeIndex); } - for (const { location, probeIndices } of uniqueLocations.values()) { - // TODO(joyeecheung): Normalize relative probe paths and avoid suffix matches that can - // bind unrelated loaded scripts with the same basename. + for (const { target, probeIndices } of uniqueTargets.values()) { // On Windows, normalize backslashes to forward slashes so the regex matches // V8 script URLs which always use forward slashes. const normalizedFile = process.platform === 'win32' ? - SideEffectFreeRegExpPrototypeSymbolReplace(/\\/g, location.file, '/') : - location.file; + SideEffectFreeRegExpPrototypeSymbolReplace(/\\/g, target.suffix, '/') : + target.suffix; const escapedPath = SideEffectFreeRegExpPrototypeSymbolReplace( /([/\\.?*()^${}|[\]])/g, normalizedFile, @@ -585,10 +640,13 @@ class ProbeInspectorSession { ); const params = { urlRegex: `^(.*[\\/\\\\])?${escapedPath}$`, - lineNumber: location.lineNumber, + // CDP locations are 0-based, the probe target from CLI is 1-based. + lineNumber: target.line - 1, }; - if (location.columnNumber !== undefined) { - params.columnNumber = location.columnNumber; + if (target.column !== undefined) { + // Only pass columnNumber to CDP when the user specifies one, otherwise let + // the inspector bind to the first executable column. + params.columnNumber = target.column - 1; } const result = await this.client.callMethod('Debugger.setBreakpointByUrl', params); @@ -610,9 +668,7 @@ class ProbeInspectorSession { const pending = this.getPendingProbeIndices(); const report = { v: kProbeVersion, - probes: ArrayPrototypeMap(this.probes, (probe) => { - return { expr: probe.expr, target: probe.target }; - }), + probes: ArrayPrototypeMap(this.probes, ({ expr, target }) => ({ expr, target })), results: ArrayPrototypeSlice(this.results), }; @@ -685,6 +741,8 @@ class ProbeInspectorSession { this.onChildOutput, { skipPortPreflight }); this.child = child; + // On Debugger.enable, V8 emits Debugger.scriptParsed for all existing scripts. + // Attach the listener early to make sure we don't miss any events. this.attachListeners(); await this.client.connect(actualPort, actualHost); diff --git a/test/common/debugger-probe.js b/test/common/debugger-probe.js index e4c7f831bab7cc..c9e67931ee4573 100644 --- a/test/common/debugger-probe.js +++ b/test/common/debugger-probe.js @@ -1,12 +1,6 @@ 'use strict'; const assert = require('assert'); -const fixtures = require('./fixtures'); -const path = require('path'); - -function debuggerFixturePath(name) { - return path.relative(process.cwd(), fixtures.path('debugger', name)); -} // Work around a pre-existing inspector issue: if the debuggee exits too quickly // the inspector can segfault while tearing down. For now normalize the segfault @@ -49,9 +43,4 @@ function assertProbeText(output, expected) { module.exports = { assertProbeJson, assertProbeText, - missScript: debuggerFixturePath('probe-miss.js'), - probeScript: debuggerFixturePath('probe.js'), - throwScript: debuggerFixturePath('probe-throw.js'), - probeTypesScript: debuggerFixturePath('probe-types.js'), - timeoutScript: debuggerFixturePath('probe-timeout.js'), }; diff --git a/test/fixtures/debugger/probe-bound-never-hit.js b/test/fixtures/debugger/probe-bound-never-hit.js new file mode 100644 index 00000000000000..65277d09c8f5c9 --- /dev/null +++ b/test/fixtures/debugger/probe-bound-never-hit.js @@ -0,0 +1,10 @@ +'use strict'; + +function neverCalled() { + console.log('unreachable'); + console.log('also unreachable'); + console.log('still unreachable'); +} + +console.log('reached'); +module.exports = neverCalled; // Keep the function alive. diff --git a/test/fixtures/debugger/probe-indented.js b/test/fixtures/debugger/probe-indented.js new file mode 100644 index 00000000000000..3d05242a3ba62e --- /dev/null +++ b/test/fixtures/debugger/probe-indented.js @@ -0,0 +1,10 @@ +'use strict'; + +function run() { + let x = 0; + // The first executable column is past column 1. + x = 42; + return x; +} + +run(); diff --git a/test/fixtures/debugger/probe-late-entry.js b/test/fixtures/debugger/probe-late-entry.js new file mode 100644 index 00000000000000..2e8350bd7a28a1 --- /dev/null +++ b/test/fixtures/debugger/probe-late-entry.js @@ -0,0 +1,7 @@ +'use strict'; + +import('./probe-late-target.cjs').then(({ add }) => { + const result = add(3, 2); + const { multiply } = require('./probe-late-target.mjs'); + console.log(multiply(result, 4)); +}); diff --git a/test/fixtures/debugger/probe-late-target.cjs b/test/fixtures/debugger/probe-late-target.cjs new file mode 100644 index 00000000000000..0e324b1a7daf95 --- /dev/null +++ b/test/fixtures/debugger/probe-late-target.cjs @@ -0,0 +1,7 @@ +'use strict'; + +exports.add = function(a, b) { + let value = a; + value += b; + return value; +}; diff --git a/test/fixtures/debugger/probe-late-target.mjs b/test/fixtures/debugger/probe-late-target.mjs new file mode 100644 index 00000000000000..832a16e8fcbf60 --- /dev/null +++ b/test/fixtures/debugger/probe-late-target.mjs @@ -0,0 +1,5 @@ +export function multiply(a, b) { + let value = a; + value *= b; + return value; +} diff --git a/test/fixtures/debugger/probe-multi-a/utils.js b/test/fixtures/debugger/probe-multi-a/utils.js new file mode 100644 index 00000000000000..0e324b1a7daf95 --- /dev/null +++ b/test/fixtures/debugger/probe-multi-a/utils.js @@ -0,0 +1,7 @@ +'use strict'; + +exports.add = function(a, b) { + let value = a; + value += b; + return value; +}; diff --git a/test/fixtures/debugger/probe-multi-b/utils.js b/test/fixtures/debugger/probe-multi-b/utils.js new file mode 100644 index 00000000000000..786ddaef1a794f --- /dev/null +++ b/test/fixtures/debugger/probe-multi-b/utils.js @@ -0,0 +1,7 @@ +'use strict'; + +exports.multiply = function(a, b) { + let value = a; + value *= b; + return value; +}; diff --git a/test/fixtures/debugger/probe-multi-entry.js b/test/fixtures/debugger/probe-multi-entry.js new file mode 100644 index 00000000000000..3eef08774e980b --- /dev/null +++ b/test/fixtures/debugger/probe-multi-entry.js @@ -0,0 +1,6 @@ +'use strict'; + +const { add } = require('./probe-multi-a/utils'); +const { multiply } = require('./probe-multi-b/utils'); + +multiply(add(1, 2), 3); diff --git a/test/fixtures/debugger/probe-multi-statement.js b/test/fixtures/debugger/probe-multi-statement.js new file mode 100644 index 00000000000000..e014edbe1119a8 --- /dev/null +++ b/test/fixtures/debugger/probe-multi-statement.js @@ -0,0 +1,7 @@ +'use strict'; + +const acc = []; +function fill() { + acc.push(1); acc.push(2); acc.push(3); +} +fill(); diff --git a/test/parallel/test-debugger-probe-bound-never-hit.js b/test/parallel/test-debugger-probe-bound-never-hit.js new file mode 100644 index 00000000000000..2dc8b5a5e9d921 --- /dev/null +++ b/test/parallel/test-debugger-probe-bound-never-hit.js @@ -0,0 +1,34 @@ +// Tests that probing an unreachable line produces a `miss` with no hit events. +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); + +spawnSyncAndAssert(process.execPath, [ + 'inspect', + '--json', + '--probe', 'probe-bound-never-hit.js:4', + '--expr', '1', + 'probe-bound-never-hit.js', +], { cwd }, { + stdout(output) { + assertProbeJson(output, { + v: 2, + probes: [{ + expr: '1', + target: { suffix: 'probe-bound-never-hit.js', line: 4 }, + }], + results: [{ + event: 'miss', + pending: [0], + }], + }); + }, + trim: true, +}); diff --git a/test/parallel/test-debugger-probe-child-inspect-port-zero.js b/test/parallel/test-debugger-probe-child-inspect-port-zero.js index 2abc81441edeb1..468f7fdccba83a 100644 --- a/test/parallel/test-debugger-probe-child-inspect-port-zero.js +++ b/test/parallel/test-debugger-probe-child-inspect-port-zero.js @@ -4,29 +4,34 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { assertProbeJson, probeScript } = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const probeUrl = fixtures.fileURL('debugger', 'probe.js').href; spawnSyncAndAssert(process.execPath, [ 'inspect', '--json', - '--probe', `${probeScript}:12`, + '--probe', 'probe.js:12', '--expr', 'finalValue', '--', '--inspect-port=0', - probeScript, -], { + 'probe.js', +], { cwd }, { stdout(output) { assertProbeJson(output, { - v: 1, + v: 2, probes: [{ expr: 'finalValue', - target: [probeScript, 12], + target: { suffix: 'probe.js', line: 12 }, }], results: [{ probe: 0, event: 'hit', hit: 1, + location: { url: probeUrl, line: 12, column: 1 }, result: { type: 'number', value: 81, description: '81' }, }, { event: 'completed', diff --git a/test/parallel/test-debugger-probe-explicit-column.js b/test/parallel/test-debugger-probe-explicit-column.js new file mode 100644 index 00000000000000..ac65b572a37847 --- /dev/null +++ b/test/parallel/test-debugger-probe-explicit-column.js @@ -0,0 +1,72 @@ +// Tests that probes on the same line but different columns are hit separately. +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const fixtureUrl = fixtures.fileURL('debugger', 'probe-multi-statement.js').href; + +// col 3: acc === [] +// col 16: acc === [1] +// col 29: acc === [1, 2] +spawnSyncAndAssert(process.execPath, [ + 'inspect', + '--json', + '--probe', 'probe-multi-statement.js:5:3', + '--expr', 'acc.length', + '--probe', 'probe-multi-statement.js:5:16', + '--expr', 'acc.length', + '--probe', 'probe-multi-statement.js:5:29', + '--expr', 'acc.length', + 'probe-multi-statement.js', +], { cwd }, { + stdout(output) { + assertProbeJson(output, { + v: 2, + probes: [ + { + expr: 'acc.length', + target: { suffix: 'probe-multi-statement.js', line: 5, column: 3 }, + }, + { + expr: 'acc.length', + target: { suffix: 'probe-multi-statement.js', line: 5, column: 16 }, + }, + { + expr: 'acc.length', + target: { suffix: 'probe-multi-statement.js', line: 5, column: 29 }, + }, + ], + results: [ + { + probe: 0, + event: 'hit', + hit: 1, + location: { url: fixtureUrl, line: 5, column: 3 }, + result: { type: 'number', value: 0, description: '0' }, + }, + { + probe: 1, + event: 'hit', + hit: 1, + location: { url: fixtureUrl, line: 5, column: 16 }, + result: { type: 'number', value: 1, description: '1' }, + }, + { + probe: 2, + event: 'hit', + hit: 1, + location: { url: fixtureUrl, line: 5, column: 29 }, + result: { type: 'number', value: 2, description: '2' }, + }, + { event: 'completed' }, + ], + }); + }, + trim: true, +}); diff --git a/test/parallel/test-debugger-probe-global-option-order.js b/test/parallel/test-debugger-probe-global-option-order.js index 19487c8f623795..33e9b688f2e0d8 100644 --- a/test/parallel/test-debugger-probe-global-option-order.js +++ b/test/parallel/test-debugger-probe-global-option-order.js @@ -4,27 +4,32 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { assertProbeJson, probeScript } = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const probeUrl = fixtures.fileURL('debugger', 'probe.js').href; spawnSyncAndAssert(process.execPath, [ 'inspect', - '--probe', `${probeScript}:12`, + '--probe', 'probe.js:12', '--expr', 'finalValue', '--json', - probeScript, -], { + 'probe.js', +], { cwd }, { stdout(output) { assertProbeJson(output, { - v: 1, + v: 2, probes: [{ expr: 'finalValue', - target: [probeScript, 12], + target: { suffix: 'probe.js', line: 12 }, }], results: [{ probe: 0, event: 'hit', hit: 1, + location: { url: probeUrl, line: 12, column: 1 }, result: { type: 'number', value: 81, description: '81' }, }, { event: 'completed', diff --git a/test/parallel/test-debugger-probe-json-preview.js b/test/parallel/test-debugger-probe-json-preview.js index 853939075aa326..acfd5bbbc7804b 100644 --- a/test/parallel/test-debugger-probe-json-preview.js +++ b/test/parallel/test-debugger-probe-json-preview.js @@ -4,39 +4,41 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { - assertProbeJson, - probeTypesScript, -} = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); -const location = `${probeTypesScript}:17`; +const cwd = fixtures.path('debugger'); +const probeArg = 'probe-types.js:17'; +const target = { suffix: 'probe-types.js', line: 17 }; +const location = { url: fixtures.fileURL('debugger', 'probe-types.js').href, line: 17, column: 1 }; spawnSyncAndAssert(process.execPath, [ 'inspect', '--json', '--preview', - '--probe', location, + '--probe', probeArg, '--expr', 'objectValue', - '--probe', location, + '--probe', probeArg, '--expr', 'arrayValue', - '--probe', location, + '--probe', probeArg, '--expr', 'errorValue', - probeTypesScript, -], { + 'probe-types.js', +], { cwd }, { stdout(output) { assertProbeJson(output, { - v: 1, + v: 2, probes: [ - { expr: 'objectValue', target: [probeTypesScript, 17] }, - { expr: 'arrayValue', target: [probeTypesScript, 17] }, - { expr: 'errorValue', target: [probeTypesScript, 17] }, + { expr: 'objectValue', target }, + { expr: 'arrayValue', target }, + { expr: 'errorValue', target }, ], results: [ { probe: 0, event: 'hit', hit: 1, + location, result: { type: 'object', description: 'Object', @@ -55,6 +57,7 @@ spawnSyncAndAssert(process.execPath, [ probe: 1, event: 'hit', hit: 1, + location, result: { type: 'object', subtype: 'array', @@ -76,6 +79,7 @@ spawnSyncAndAssert(process.execPath, [ probe: 2, event: 'hit', hit: 1, + location, result: { type: 'object', subtype: 'error', diff --git a/test/parallel/test-debugger-probe-json-special-values.js b/test/parallel/test-debugger-probe-json-special-values.js index 97782c9e314e3d..e24ea5b3080b8b 100644 --- a/test/parallel/test-debugger-probe-json-special-values.js +++ b/test/parallel/test-debugger-probe-json-special-values.js @@ -4,104 +4,113 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { - assertProbeJson, - probeTypesScript, -} = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); -const location = `${probeTypesScript}:17`; +const cwd = fixtures.path('debugger'); +const probeArg = 'probe-types.js:17'; +const target = { suffix: 'probe-types.js', line: 17 }; +const location = { url: fixtures.fileURL('debugger', 'probe-types.js').href, line: 17, column: 1 }; spawnSyncAndAssert(process.execPath, [ 'inspect', '--json', - '--probe', location, + '--probe', probeArg, '--expr', 'stringValue', - '--probe', location, + '--probe', probeArg, '--expr', 'booleanValue', - '--probe', location, + '--probe', probeArg, '--expr', 'undefinedValue', - '--probe', location, + '--probe', probeArg, '--expr', 'nullValue', - '--probe', location, + '--probe', probeArg, '--expr', 'nanValue', - '--probe', location, + '--probe', probeArg, '--expr', 'bigintValue', - '--probe', location, + '--probe', probeArg, '--expr', 'symbolValue', - '--probe', location, + '--probe', probeArg, '--expr', 'functionValue', - '--probe', location, + '--probe', probeArg, '--expr', 'objectValue', - '--probe', location, + '--probe', probeArg, '--expr', 'arrayValue', - '--probe', location, + '--probe', probeArg, '--expr', 'errorValue', - probeTypesScript, -], { + 'probe-types.js', +], { cwd }, { stdout(output) { assertProbeJson(output, { - v: 1, + v: 2, probes: [ - { expr: 'stringValue', target: [probeTypesScript, 17] }, - { expr: 'booleanValue', target: [probeTypesScript, 17] }, - { expr: 'undefinedValue', target: [probeTypesScript, 17] }, - { expr: 'nullValue', target: [probeTypesScript, 17] }, - { expr: 'nanValue', target: [probeTypesScript, 17] }, - { expr: 'bigintValue', target: [probeTypesScript, 17] }, - { expr: 'symbolValue', target: [probeTypesScript, 17] }, - { expr: 'functionValue', target: [probeTypesScript, 17] }, - { expr: 'objectValue', target: [probeTypesScript, 17] }, - { expr: 'arrayValue', target: [probeTypesScript, 17] }, - { expr: 'errorValue', target: [probeTypesScript, 17] }, + { expr: 'stringValue', target }, + { expr: 'booleanValue', target }, + { expr: 'undefinedValue', target }, + { expr: 'nullValue', target }, + { expr: 'nanValue', target }, + { expr: 'bigintValue', target }, + { expr: 'symbolValue', target }, + { expr: 'functionValue', target }, + { expr: 'objectValue', target }, + { expr: 'arrayValue', target }, + { expr: 'errorValue', target }, ], results: [ { probe: 0, event: 'hit', hit: 1, + location, result: { type: 'string', value: 'hello' }, }, { probe: 1, event: 'hit', hit: 1, + location, result: { type: 'boolean', value: true }, }, { probe: 2, event: 'hit', hit: 1, + location, result: { type: 'undefined' }, }, { probe: 3, event: 'hit', hit: 1, + location, result: { type: 'object', subtype: 'null', value: null }, }, { probe: 4, event: 'hit', hit: 1, + location, result: { type: 'number', unserializableValue: 'NaN', description: 'NaN' }, }, { probe: 5, event: 'hit', hit: 1, + location, result: { type: 'bigint', unserializableValue: '1n', description: '1n' }, }, { probe: 6, event: 'hit', hit: 1, + location, result: { type: 'symbol', description: 'Symbol(tag)' }, }, { probe: 7, event: 'hit', hit: 1, + location, result: { type: 'function', description: '() => 1', @@ -111,6 +120,7 @@ spawnSyncAndAssert(process.execPath, [ probe: 8, event: 'hit', hit: 1, + location, result: { type: 'object', description: 'Object', @@ -120,6 +130,7 @@ spawnSyncAndAssert(process.execPath, [ probe: 9, event: 'hit', hit: 1, + location, result: { type: 'object', subtype: 'array', @@ -130,6 +141,7 @@ spawnSyncAndAssert(process.execPath, [ probe: 10, event: 'hit', hit: 1, + location, result: { type: 'object', subtype: 'error', diff --git a/test/parallel/test-debugger-probe-json.js b/test/parallel/test-debugger-probe-json.js index be2288ee78ddac..559392cc69d9b5 100644 --- a/test/parallel/test-debugger-probe-json.js +++ b/test/parallel/test-debugger-probe-json.js @@ -4,57 +4,68 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { assertProbeJson, probeScript } = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const probeUrl = fixtures.fileURL('debugger', 'probe.js').href; +const locationAt8 = { url: probeUrl, line: 8, column: 3 }; +const locationAt12 = { url: probeUrl, line: 12, column: 1 }; spawnSyncAndAssert(process.execPath, [ 'inspect', '--json', - '--probe', `${probeScript}:8`, + '--probe', 'probe.js:8', '--expr', 'index', - '--probe', `${probeScript}:8`, + '--probe', 'probe.js:8', '--expr', 'total', - '--probe', `${probeScript}:12`, + '--probe', 'probe.js:12', '--expr', 'finalValue', - probeScript, -], { + 'probe.js', +], { cwd }, { stdout(output) { assertProbeJson(output, { - v: 1, + v: 2, probes: [ - { expr: 'index', target: [probeScript, 8] }, - { expr: 'total', target: [probeScript, 8] }, - { expr: 'finalValue', target: [probeScript, 12] }, + { expr: 'index', target: { suffix: 'probe.js', line: 8 } }, + { expr: 'total', target: { suffix: 'probe.js', line: 8 } }, + { expr: 'finalValue', target: { suffix: 'probe.js', line: 12 } }, ], results: [ { probe: 0, event: 'hit', hit: 1, + location: locationAt8, result: { type: 'number', value: 0, description: '0' }, }, { probe: 1, event: 'hit', hit: 1, + location: locationAt8, result: { type: 'number', value: 0, description: '0' }, }, { probe: 0, event: 'hit', hit: 2, + location: locationAt8, result: { type: 'number', value: 1, description: '1' }, }, { probe: 1, event: 'hit', hit: 2, + location: locationAt8, result: { type: 'number', value: 40, description: '40' }, }, { probe: 2, event: 'hit', hit: 1, + location: locationAt12, result: { type: 'number', value: 81, description: '81' }, }, { event: 'completed' }, diff --git a/test/parallel/test-debugger-probe-late-resolution.js b/test/parallel/test-debugger-probe-late-resolution.js new file mode 100644 index 00000000000000..202019122c45b8 --- /dev/null +++ b/test/parallel/test-debugger-probe-late-resolution.js @@ -0,0 +1,52 @@ +// Tests that probing scripts loaded mid-session via require(esm) and import(cjs) +// still resolves and hits correctly. +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const esmUrl = fixtures.fileURL('debugger', 'probe-late-target.mjs').href; +const cjsUrl = fixtures.fileURL('debugger', 'probe-late-target.cjs').href; + +spawnSyncAndAssert(process.execPath, [ + 'inspect', + '--json', + '--probe', 'probe-late-target.mjs:3', + '--expr', 'value', + '--probe', 'probe-late-target.cjs:5', + '--expr', 'value', + 'probe-late-entry.js', +], { cwd }, { + stdout(output) { + assertProbeJson(output, { + v: 2, + probes: [ + { expr: 'value', target: { suffix: 'probe-late-target.mjs', line: 3 } }, + { expr: 'value', target: { suffix: 'probe-late-target.cjs', line: 5 } }, + ], + results: [ + { + probe: 1, + event: 'hit', + hit: 1, + location: { url: cjsUrl, line: 5, column: 3 }, + result: { type: 'number', value: 3, description: '3' }, + }, + { + probe: 0, + event: 'hit', + hit: 1, + location: { url: esmUrl, line: 3, column: 3 }, + result: { type: 'number', value: 5, description: '5' }, + }, + { event: 'completed' }, + ], + }); + }, + trim: true, +}); diff --git a/test/parallel/test-debugger-probe-launch.js b/test/parallel/test-debugger-probe-launch.js index 09511fabf2caaf..24b299ac227453 100644 --- a/test/parallel/test-debugger-probe-launch.js +++ b/test/parallel/test-debugger-probe-launch.js @@ -5,17 +5,19 @@ const common = require('../common'); common.skipIfInspectorDisabled(); const assert = require('assert'); +const fixtures = require('../common/fixtures'); const { spawnSyncAndExit } = require('../common/child_process'); -const { probeScript } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); spawnSyncAndExit(process.execPath, [ 'inspect', - '--probe', `${probeScript}:12`, + '--probe', 'probe.js:12', '--expr', 'finalValue', '--', '--not-a-real-node-flag', - probeScript, -], { + 'probe.js', +], { cwd }, { signal: null, status: 1, stderr(output) { diff --git a/test/parallel/test-debugger-probe-miss.js b/test/parallel/test-debugger-probe-miss.js index 2908b8092aeb7d..4fd4824ed0c1d3 100644 --- a/test/parallel/test-debugger-probe-miss.js +++ b/test/parallel/test-debugger-probe-miss.js @@ -4,20 +4,25 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { assertProbeJson, missScript } = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); +const cwd = fixtures.path('debugger'); spawnSyncAndAssert(process.execPath, [ 'inspect', '--json', - '--probe', `${missScript}:99`, + '--probe', 'probe-miss.js:99', '--expr', '42', - missScript, -], { + 'probe-miss.js', +], { cwd }, { stdout(output) { assertProbeJson(output, { - v: 1, - probes: [{ expr: '42', target: [missScript, 99] }], + v: 2, + probes: [{ + expr: '42', + target: { suffix: 'probe-miss.js', line: 99 }, + }], results: [{ event: 'miss', pending: [0], diff --git a/test/parallel/test-debugger-probe-missing-expr.js b/test/parallel/test-debugger-probe-missing-expr.js index 57adb70b1b7f35..3011bc51f587e4 100644 --- a/test/parallel/test-debugger-probe-missing-expr.js +++ b/test/parallel/test-debugger-probe-missing-expr.js @@ -4,14 +4,16 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndExit } = require('../common/child_process'); -const { probeScript } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); spawnSyncAndExit(process.execPath, [ 'inspect', - '--probe', `${probeScript}:12`, - probeScript, -], { + '--probe', 'probe.js:12', + 'probe.js', +], { cwd }, { signal: null, status: 9, stderr: /Each --probe must be followed immediately by --expr/, diff --git a/test/parallel/test-debugger-probe-multi-location.js b/test/parallel/test-debugger-probe-multi-location.js new file mode 100644 index 00000000000000..15638f880ed421 --- /dev/null +++ b/test/parallel/test-debugger-probe-multi-location.js @@ -0,0 +1,49 @@ +// Tests that when probing a suffix that resolves to two files, +// both are probed and each hit is attributed to the right script. +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const urlA = fixtures.fileURL('debugger', 'probe-multi-a', 'utils.js').href; +const urlB = fixtures.fileURL('debugger', 'probe-multi-b', 'utils.js').href; + +spawnSyncAndAssert(process.execPath, [ + 'inspect', + '--json', + '--probe', 'utils.js:5', + '--expr', 'b', + 'probe-multi-entry.js', +], { cwd }, { + stdout(output) { + assertProbeJson(output, { + v: 2, + probes: [ + { expr: 'b', target: { suffix: 'utils.js', line: 5 } }, + ], + results: [ + { + probe: 0, + event: 'hit', + hit: 1, + location: { url: urlA, line: 5, column: 3 }, + result: { type: 'number', value: 2, description: '2' }, + }, + { + probe: 0, + event: 'hit', + hit: 2, + location: { url: urlB, line: 5, column: 3 }, + result: { type: 'number', value: 3, description: '3' }, + }, + { event: 'completed' }, + ], + }); + }, + trim: true, +}); diff --git a/test/parallel/test-debugger-probe-narrow-suffix.js b/test/parallel/test-debugger-probe-narrow-suffix.js new file mode 100644 index 00000000000000..194ddfbdc5d4f7 --- /dev/null +++ b/test/parallel/test-debugger-probe-narrow-suffix.js @@ -0,0 +1,40 @@ +// Tests that a path-qualified suffix narrows the match to a single script. +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { assertProbeJson } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); +const urlA = fixtures.fileURL('debugger', 'probe-multi-a', 'utils.js').href; + +spawnSyncAndAssert(process.execPath, [ + 'inspect', + '--json', + '--probe', 'probe-multi-a/utils.js:5', + '--expr', 'b', + 'probe-multi-entry.js', +], { cwd }, { + stdout(output) { + assertProbeJson(output, { + v: 2, + probes: [ + { expr: 'b', target: { suffix: 'probe-multi-a/utils.js', line: 5 } }, + ], + results: [ + { + probe: 0, + event: 'hit', + hit: 1, + location: { url: urlA, line: 5, column: 3 }, + result: { type: 'number', value: 2, description: '2' }, + }, + { event: 'completed' }, + ], + }); + }, + trim: true, +}); diff --git a/test/parallel/test-debugger-probe-no-column-indent.js b/test/parallel/test-debugger-probe-no-column-indent.js new file mode 100644 index 00000000000000..351f1b89e79a3f --- /dev/null +++ b/test/parallel/test-debugger-probe-no-column-indent.js @@ -0,0 +1,43 @@ +// Tests probing an indented line without specifying a column should relocate +// the breakpoint to the first executable column. +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const fixtures = require('../common/fixtures'); +const { spawnSyncAndAssert } = require('../common/child_process'); +const { assertProbeJson } = require('../common/debugger-probe'); +const cwd = fixtures.path('debugger'); +const fixtureUrl = fixtures.fileURL('debugger', 'probe-indented.js').href; + +spawnSyncAndAssert(process.execPath, [ + 'inspect', + '--json', + '--probe', 'probe-indented.js:6', // No `:col` + '--expr', 'x', + 'probe-indented.js', +], { cwd }, { + stdout(output) { + assertProbeJson(output, { + v: 2, + probes: [{ + expr: 'x', + // No `:col` in `target`, reflecting the user spec. + target: { suffix: 'probe-indented.js', line: 6 }, + }], + results: [{ + probe: 0, + event: 'hit', + hit: 1, + // V8 should relocate the breakpoint to the first executable column (3). + location: { url: fixtureUrl, line: 6, column: 3 }, + // Pauses *before* the assignment runs, so `x` still holds the value from line 4. + result: { type: 'number', value: 0, description: '0' }, + }, { + event: 'completed', + }], + }); + }, + trim: true, +}); diff --git a/test/parallel/test-debugger-probe-requires-separator.js b/test/parallel/test-debugger-probe-requires-separator.js index bbbefb0069805f..efc81fda4d69fd 100644 --- a/test/parallel/test-debugger-probe-requires-separator.js +++ b/test/parallel/test-debugger-probe-requires-separator.js @@ -4,16 +4,18 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndExit } = require('../common/child_process'); -const { probeScript } = require('../common/debugger-probe'); + +const cwd = fixtures.path('debugger'); spawnSyncAndExit(process.execPath, [ 'inspect', - '--probe', `${probeScript}:12`, + '--probe', 'probe.js:12', '--expr', 'finalValue', '--inspect-port=0', - probeScript, -], { + 'probe.js', +], { cwd }, { signal: null, status: 9, stderr: /Use -- before child Node\.js flags in probe mode/, diff --git a/test/parallel/test-debugger-probe-text-special-values.js b/test/parallel/test-debugger-probe-text-special-values.js index 3886eb66daed8b..ea62c45970a1e4 100644 --- a/test/parallel/test-debugger-probe-text-special-values.js +++ b/test/parallel/test-debugger-probe-text-special-values.js @@ -4,63 +4,62 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { - assertProbeText, - probeTypesScript, -} = require('../common/debugger-probe'); - -const location = `${probeTypesScript}:17`; +const { assertProbeText } = require('../common/debugger-probe'); +const cwd = fixtures.path('debugger'); +const probeArg = 'probe-types.js:17'; +const hitText = `${fixtures.fileURL('debugger', 'probe-types.js').href}:17:1`; spawnSyncAndAssert(process.execPath, [ 'inspect', - '--probe', location, + '--probe', probeArg, '--expr', 'stringValue', - '--probe', location, + '--probe', probeArg, '--expr', 'booleanValue', - '--probe', location, + '--probe', probeArg, '--expr', 'undefinedValue', - '--probe', location, + '--probe', probeArg, '--expr', 'nullValue', - '--probe', location, + '--probe', probeArg, '--expr', 'nanValue', - '--probe', location, + '--probe', probeArg, '--expr', 'bigintValue', - '--probe', location, + '--probe', probeArg, '--expr', 'symbolValue', - '--probe', location, + '--probe', probeArg, '--expr', 'functionValue', - '--probe', location, + '--probe', probeArg, '--expr', 'objectValue', - '--probe', location, + '--probe', probeArg, '--expr', 'arrayValue', - '--probe', location, + '--probe', probeArg, '--expr', 'errorValue', - probeTypesScript, -], { + 'probe-types.js', +], { cwd }, { stdout(output) { assertProbeText(output, [ - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' stringValue = "hello"', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' booleanValue = true', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' undefinedValue = undefined', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' nullValue = null', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' nanValue = NaN', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' bigintValue = 1n', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' symbolValue = Symbol(tag)', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' functionValue = () => 1', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' objectValue = {alpha: 1, beta: "two"}', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' arrayValue = [1, "two", 3]', - `Hit 1 at ${location}`, + `Hit 1 at ${hitText}`, ' errorValue = Error: boom', 'Completed', ].join('\n')); diff --git a/test/parallel/test-debugger-probe-text.js b/test/parallel/test-debugger-probe-text.js index 30e77b25985d16..f6752741a223d6 100644 --- a/test/parallel/test-debugger-probe-text.js +++ b/test/parallel/test-debugger-probe-text.js @@ -4,18 +4,21 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndAssert } = require('../common/child_process'); -const { assertProbeText, probeScript } = require('../common/debugger-probe'); +const { assertProbeText } = require('../common/debugger-probe'); +const cwd = fixtures.path('debugger'); +const probeUrl = fixtures.fileURL('debugger', 'probe.js').href; spawnSyncAndAssert(process.execPath, [ 'inspect', - '--probe', `${probeScript}:12`, + '--probe', 'probe.js:12', '--expr', 'finalValue', - probeScript, -], { + 'probe.js', +], { cwd }, { stdout(output) { assertProbeText(output, - `Hit 1 at ${probeScript}:12\n` + + `Hit 1 at ${probeUrl}:12:1\n` + ' finalValue = 81\n' + 'Completed'); }, diff --git a/test/parallel/test-debugger-probe-timeout.js b/test/parallel/test-debugger-probe-timeout.js index fe641f31943af0..da877741ca0cb1 100644 --- a/test/parallel/test-debugger-probe-timeout.js +++ b/test/parallel/test-debugger-probe-timeout.js @@ -4,29 +4,34 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const fixtures = require('../common/fixtures'); const { spawnSyncAndExit } = require('../common/child_process'); -const { assertProbeJson, timeoutScript } = require('../common/debugger-probe'); +const { assertProbeJson } = require('../common/debugger-probe'); +const cwd = fixtures.path('debugger'); spawnSyncAndExit(process.execPath, [ 'inspect', '--json', '--timeout=200', - '--probe', `${timeoutScript}:99`, + '--probe', 'probe-timeout.js:99', '--expr', '1', - timeoutScript, -], { + 'probe-timeout.js', +], { cwd }, { signal: null, status: 1, stdout(output) { assertProbeJson(output, { - v: 1, - probes: [{ expr: '1', target: [timeoutScript, 99] }], + v: 2, + probes: [{ + expr: '1', + target: { suffix: 'probe-timeout.js', line: 99 }, + }], results: [{ event: 'timeout', pending: [0], error: { code: 'probe_timeout', - message: `Timed out after 200ms waiting for probes: ${timeoutScript}:99`, + message: 'Timed out after 200ms waiting for probes: probe-timeout.js:99', }, }], });