From 137e8391c77d21b13a24cb6e28e2d93ffaeb6605 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 24 Sep 2026 09:39:04 +0200 Subject: [PATCH 1/2] test: deflake inspector async hook setup Async hook activation can be deferred when the inspector command arrives during a V8 interrupt. Schedule timeout creation with setImmediate so the event loop finishes enabling the hooks before recording its async stack. Signed-off-by: Filip Skokan Assisted-by: Codex --- .../test-inspector-async-hook-setup-at-signal.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-inspector-async-hook-setup-at-signal.js b/test/parallel/test-inspector-async-hook-setup-at-signal.js index 64a3835e4157..88d8dfb26a51 100644 --- a/test/parallel/test-inspector-async-hook-setup-at-signal.js +++ b/test/parallel/test-inspector-async-hook-setup-at-signal.js @@ -33,7 +33,6 @@ function waitUntilDebugged() { // This function is called by the inspector client (session) function setupTimeoutWithBreak() { - clearInterval(waiting); process._rawDebug('Debugger ready, setting up timeout with a break'); setTimeout(() => { debugger; }, 50); } @@ -46,16 +45,20 @@ async function waitForInitialSetup(session) { async function setupTimeoutForStackTrace(session) { console.error('[test]', 'Setting up timeout for async stack trace'); + // Async hook setup is deferred when requested from a V8 interrupt. Let the + // event loop finish that setup before creating the timeout whose stack we test. await session.send([ { 'method': 'Runtime.evaluate', - 'params': { expression: 'setupTimeoutWithBreak()' } }, + 'params': { + expression: 'clearInterval(waiting); setImmediate(setupTimeoutWithBreak)', + } }, { 'method': 'Debugger.resume' }, ]); } async function checkAsyncStackTrace(session) { console.error('[test]', 'Verify basic properties of asyncStackTrace'); - const paused = await session.waitForBreakOnLine(23, '[eval]'); + const paused = await session.waitForBreakOnLine(22, '[eval]'); assert(paused.params.asyncStackTrace, `${Object.keys(paused.params)} contains "asyncStackTrace" property`); assert(paused.params.asyncStackTrace.description, 'Timeout'); From b7526ad3feecae3970e5e9b017a238060af83d63 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Thu, 24 Sep 2026 09:39:15 +0200 Subject: [PATCH 2/2] test: deflake test runner timeout coverage Use a dedicated fixture with a pending promise and assertion plan instead of competing assertion timers. Keep both timeout failure assertions and clear the keepalive interval after the tests finish. Signed-off-by: Filip Skokan Assisted-by: Codex --- test/fixtures/test-runner/run-timeout.mjs | 11 +++++++++++ test/parallel/test-runner-run.mjs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/test-runner/run-timeout.mjs diff --git a/test/fixtures/test-runner/run-timeout.mjs b/test/fixtures/test-runner/run-timeout.mjs new file mode 100644 index 000000000000..8367bd22b4cf --- /dev/null +++ b/test/fixtures/test-runner/run-timeout.mjs @@ -0,0 +1,11 @@ +import test, { after } from 'node:test'; + +// Keep the event loop alive until both tests time out. +const interval = setInterval(() => {}, 1000); +after(() => clearInterval(interval)); + +test('pending promise', () => new Promise(() => {})); + +test('pending assertion plan', (t) => { + t.plan(1, { wait: true }); +}); diff --git a/test/parallel/test-runner-run.mjs b/test/parallel/test-runner-run.mjs index d1d384a522e2..a973ecb7cb71 100644 --- a/test/parallel/test-runner-run.mjs +++ b/test/parallel/test-runner-run.mjs @@ -90,7 +90,7 @@ describe('require(\'node:test\').run', { concurrency: true }, () => { it('should support timeout', async () => { const stream = run({ timeout: 50, files: [ - fixtures.path('test-runner', 'plan', 'timeout-basic.mjs'), + fixtures.path('test-runner', 'run-timeout.mjs'), ] }); stream.on('test:fail', common.mustCall((data) => { assert.strictEqual(data.details.error.failureType, 'testTimeoutFailure');