Files
node/test/common/debugger-probe.js
Joyee Cheung 8fec65d877 test: normalize Windows crash in debugger test normalization
The inspector can crash on teardown when the debuggee exits quickly,
previously we ignored it for debugger tests as that's likely an
upstream issue unrelated to Node.js's own debugger utilities,
but the detection only matches patternsf on UNIX-y platforms.
On Windows this manifests as exit code 3221225477 (0xC0000005
STATUS_ACCESS_VIOLATION) instead of SIGSEGV. Extend the helper
to recognize and normalize both variants.

Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64332
Reviewed-By: Stefan Stojanovic <stefan.stojanovic@janeasystems.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Filip Skokan <panva.ip@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
2026-07-09 11:24:31 +00:00

105 lines
3.7 KiB
JavaScript

'use strict';
const assert = require('assert');
const { spawnSyncAndExit } = require('./child_process');
// Work around a pre-existing inspector issue: if the debuggee exits too quickly
// the inspector can crash while tearing down. For now normalize the crash
// back to the expected terminal event (e.g. "completed" or "miss")
// until the upstream bug is fixed.
// See https://github.com/nodejs/node/issues/62765
// https://github.com/nodejs/node/issues/58245
// The crash shows up as with exit code 3221225477 on Windows, or signal
// SIGSEGV on other platforms.
const probeTargetExitSignal = 'SIGSEGV';
// 0xC0000005 STATUS_ACCESS_VIOLATION on Windows
const probeTargetExitCode = 3221225477;
function isProbeSegvTeardown(result) {
if (result?.event !== 'error') { return false; }
const error = result.error;
if (error?.signal !== probeTargetExitSignal && error?.exitCode !== probeTargetExitCode) { return false; }
return error.code === 'probe_target_exit' || error.code === 'probe_failure';
}
function findProbeSegvTeardownLine(output) {
const signalPrefix = `Target exited with signal ${probeTargetExitSignal}`;
const codePrefix = `Target exited with code ${probeTargetExitCode}`;
for (const prefix of [signalPrefix, codePrefix]) {
if (output.startsWith(prefix)) { return 0; }
const idx = output.indexOf(`\n${prefix}`);
if (idx !== -1) { return idx + 1; }
}
return -1;
}
// Replace volatile fields in a probe report (stack frames, Node.js version,
// scriptId, callFrames) with stable placeholders for deepStrictEqual.
function normalizeProbeReport(value) {
if (typeof value === 'string') {
return value
.replace(/(?:\n[ \t]+at\s[^\n]*)+/g, '\n<stack>')
.replace(/\nNode\.js v[^\n]+/g, '\nNode.js <version>');
}
if (Array.isArray(value)) {
return value.map(normalizeProbeReport);
}
if (value !== null && typeof value === 'object') {
const result = {};
for (const key of Object.keys(value)) {
if (key === 'scriptId') {
result[key] = '<scriptId>';
} else if (key === 'callFrames') {
result[key] = '<callFrames>';
} else {
result[key] = normalizeProbeReport(value[key]);
}
}
return result;
}
return value;
}
function assertProbeJson(output, expected) {
const normalized = typeof output === 'string' ? JSON.parse(output) : output;
const lastResult = normalized.results?.[normalized.results.length - 1];
if (isProbeSegvTeardown(lastResult)) {
// Log to facilitate debugging if this normalization is occurring.
console.log('Normalizing trailing SIGSEGV in JSON probe output');
normalized.results[normalized.results.length - 1] = expected.results.at(-1);
}
assert.deepStrictEqual(normalizeProbeReport(normalized), normalizeProbeReport(expected));
}
function assertProbeText(output, expected) {
const lineStart = findProbeSegvTeardownLine(output);
let normalized;
if (lineStart !== -1) {
// Log to facilitate debugging if this normalization is occurring.
console.log('Normalizing trailing SIGSEGV in text probe output');
const expectedLineStart = expected.lastIndexOf('\n');
const terminalLine = expectedLineStart === -1 ? expected : expected.slice(expectedLineStart + 1);
normalized = lineStart === 0 ? terminalLine : `${output.slice(0, lineStart - 1)}\n${terminalLine}`;
} else {
normalized = output;
}
assert.strictEqual(normalized, expected);
}
function assertProbeCliError(inspectArgs, expectedStderr, { cwd } = {}) {
spawnSyncAndExit(process.execPath, ['inspect', ...inspectArgs], { cwd }, {
signal: null,
status: 9,
stderr: expectedStderr,
trim: true,
});
}
module.exports = {
assertProbeJson,
assertProbeCliError,
assertProbeText,
};