From dd871fa477cf5515c5caf424b4e7088e4430d6df Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sat, 25 Jul 2026 10:01:09 +0200 Subject: [PATCH 1/2] test: unflake debugger and REPL tests Run the debugger function-formatting checks against an auto-resumed, long-lived target so they do not depend on initial-break rendering. Use a synchronous VM evaluator for the REPL handleError test. Submit the throwing input separately and wait for clean REPL teardown. Signed-off-by: Matteo Collina --- .../test-debugger-extract-function-name.mjs | 8 +++-- test/parallel/test-repl-user-error-handler.js | 30 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/test/parallel/test-debugger-extract-function-name.mjs b/test/parallel/test-debugger-extract-function-name.mjs index a7de32ad4e2cf5..b787750453f9cc 100644 --- a/test/parallel/test-debugger-extract-function-name.mjs +++ b/test/parallel/test-debugger-extract-function-name.mjs @@ -7,10 +7,14 @@ import startCLI from '../common/debugger.js'; import assert from 'assert'; -const cli = startCLI([fixtures.path('debugger', 'three-lines.js')]); +const env = { + ...process.env, + NODE_INSPECT_RESUME_ON_START: '1', +}; +const cli = startCLI( + [fixtures.path('debugger', 'alive.js')], [], { env }); try { - await cli.waitForInitialBreak(); await cli.waitForPrompt(); await cli.command('exec a = function func() {}; a;'); assert.match(cli.output, /\[Function: func\]/); diff --git a/test/parallel/test-repl-user-error-handler.js b/test/parallel/test-repl-user-error-handler.js index 1b9e591e629a6a..ff706ee3d16cbb 100644 --- a/test/parallel/test-repl-user-error-handler.js +++ b/test/parallel/test-repl-user-error-handler.js @@ -6,9 +6,18 @@ const { PassThrough } = require('node:stream'); const { once } = require('node:events'); const test = require('node:test'); const { spawn } = require('node:child_process'); +const vm = require('node:vm'); common.skipIfInspectorDisabled(); +function evaluate(code, context, filename, callback) { + try { + callback(null, vm.runInContext(code, context, { filename })); + } catch (err) { + callback(err); + } +} + function* generateCases() { for (const async of [false, true]) { for (const handleErrorReturn of ['ignore', 'print', 'unhandled', 'badvalue']) { @@ -23,13 +32,12 @@ function* generateCases() { for (const { async, handleErrorReturn } of generateCases()) { test(`async: ${async}, handleErrorReturn: ${handleErrorReturn}`, async () => { - let err; const options = { input: new PassThrough(), output: new PassThrough().setEncoding('utf8'), - handleError: common.mustCall((e) => { - err = e; - queueMicrotask(() => repl.emit('handled-error')); + eval: evaluate, + handleError: common.mustCall((err) => { + queueMicrotask(() => repl.emit('handled-error', err)); return handleErrorReturn; }) }; @@ -45,13 +53,16 @@ for (const { async, handleErrorReturn } of generateCases()) { let outputString = ''; options.output.on('data', (chunk) => { outputString += chunk; }); - const inputString = async ? - 'setImmediate(() => { throw new Error("testerror") })\n42\n' : - 'throw new Error("testerror")\n42\n'; - options.input.end(inputString); + const errorInput = async ? + 'setImmediate(() => { throw new Error("testerror") })\n' : + 'throw new Error("testerror")\n'; + const handledErrorEvent = once(repl, 'handled-error'); + options.input.write(errorInput); - await once(repl, 'handled-error'); + const [err] = await handledErrorEvent; assert.strictEqual(err.message, 'testerror'); + const exitEvent = once(repl, 'exit'); + options.input.end('42\n'); while (!/42/.test(outputString)) { await once(options.output, 'data'); } @@ -66,6 +77,7 @@ for (const { async, handleErrorReturn } of generateCases()) { const [uncaughtErr] = await uncaughtExceptionEvent; assert.strictEqual(uncaughtErr, err); } + await exitEvent; }); } From 9e4ff2c2518aac8aeacef7a86a66dcb12ee863d1 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 26 Jul 2026 10:26:44 +0100 Subject: [PATCH 2/2] test: harden debugger prompt test Use a long-lived debugger target and assert that the prompt is received before sending the exit command. Handle child exit and timeout failures explicitly instead of leaving the test hanging when startup does not produce a prompt. Signed-off-by: Matteo Collina --- test/sequential/test-debug-prompt.js | 46 ++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/test/sequential/test-debug-prompt.js b/test/sequential/test-debug-prompt.js index e32f4646900536..59a1461c98e691 100644 --- a/test/sequential/test-debug-prompt.js +++ b/test/sequential/test-debug-prompt.js @@ -2,17 +2,45 @@ const common = require('../common'); common.skipIfInspectorDisabled(); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); const spawn = require('child_process').spawn; -const proc = spawn(process.execPath, ['inspect', 'foo']); +const proc = spawn(process.execPath, [ + 'inspect', fixtures.path('debugger', 'alive.js'), +]); proc.stdout.setEncoding('utf8'); -let needToSendExit = true; +const TIMEOUT = common.platformTimeout(10_000); let output = ''; -proc.stdout.on('data', (data) => { - output += data; - if (output.includes('debug> ') && needToSendExit) { - proc.stdin.write('.exit\n'); - needToSendExit = false; - } -}); +let promptSeen = false; + +(async () => { + await new Promise((resolve, reject) => { + const timer = setTimeout(() => { + proc.kill(); + reject(new Error(`Timed out waiting for the debugger prompt; output: ${output}`)); + }, TIMEOUT); + + proc.stdout.on('data', (data) => { + output += data; + if (output.includes('debug> ') && !promptSeen) { + promptSeen = true; + proc.stdin.end('.exit\n'); + } + }); + + proc.once('error', reject); + proc.once('close', common.mustCall((code, signal) => { + clearTimeout(timer); + if (!promptSeen) { + reject(new Error( + `Debugger exited before showing the prompt (code ${code}, signal ${signal}); ` + + `output: ${output}`)); + return; + } + assert.strictEqual(code, 0); + resolve(); + })); + }); +})().then(common.mustCall());