From 1a01d48075e31ef59dcf4893fbfc582962560431 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 14 Oct 2026 11:37:36 +0200 Subject: [PATCH 1/6] test: reserve both families for local port test Reserve the source port with a dual-stack listener and keep it open until the destination server is listening. This prevents choosing an occupied IPv6 port or letting the destination reuse the source port. Refs: https://github.com/nodejs/reliability/issues/1686 Signed-off-by: Filip Skokan Assisted-by: Codex --- .../test-net-autoselectfamily-local-port.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/parallel/test-net-autoselectfamily-local-port.js b/test/parallel/test-net-autoselectfamily-local-port.js index ba3ae408dfb5..ef13d8a70825 100644 --- a/test/parallel/test-net-autoselectfamily-local-port.js +++ b/test/parallel/test-net-autoselectfamily-local-port.js @@ -13,12 +13,17 @@ TCP.prototype.connect6 = function() { }; const reserved = createServer(); -reserved.listen(0, '127.0.0.1', common.mustCall(() => { +// Reserve both address families: an IPv4-only reservation may choose a port +// that is already in use by an IPv6-only socket. +reserved.listen(0, '::', common.mustCall(() => { const localPort = reserved.address().port; - reserved.close(common.mustCall(() => { - const server = createServer(common.mustCall((socket) => socket.end())); - server.listen(0, '127.0.0.1', common.mustCall(() => { - const port = server.address().port; + const server = createServer(common.mustCall((socket) => socket.end())); + // Keep the reservation until the destination listens so it cannot take + // the source port that the client is about to use. + server.listen(0, '127.0.0.1', common.mustCall(() => { + const port = server.address().port; + assert.notStrictEqual(port, localPort); + reserved.close(common.mustCall(() => { const connection = createConnection({ host: 'example.org', port, From 0d9e954e8fd42f95ae7bdace19ae8986197041aa Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 14 Oct 2026 11:37:36 +0200 Subject: [PATCH 2/6] test: retry unready process timeout reports Retry attempts that did not reach the blocked Worker before parsing their report. A timeout during startup can force exit before the report is complete. Keep the report and exit assertions once startup is ready. Refs: https://github.com/nodejs/reliability/issues/1687 Signed-off-by: Filip Skokan Assisted-by: Codex --- test/report/test-report-process-timeout-worker.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/report/test-report-process-timeout-worker.js b/test/report/test-report-process-timeout-worker.js index a42e2513ccba..d7fe24a7a0d0 100644 --- a/test/report/test-report-process-timeout-worker.js +++ b/test/report/test-report-process-timeout-worker.js @@ -31,6 +31,12 @@ for (let timeout = common.platformTimeout(1000); ; timeout *= 2) { marker, ], { cwd: tmpdir.path, encoding: 'utf8' }); + // If startup did not reach the blocked Worker, the watchdog may have + // interrupted report generation too. Retry before reading that report. + if (!fs.existsSync(marker) && timeout < common.platformTimeout(16000)) { + continue; + } + const reports = helper.findReports(child.pid, tmpdir.path); assert.strictEqual(reports.length, 1, child.stderr); helper.validate(reports[0], [ From 6c190e7087072f7187437954fab0dd44095f4a0f Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 14 Oct 2026 11:37:36 +0200 Subject: [PATCH 3/6] test: wait for inspector restart before exit The self-directed debug signal restarts the inspector asynchronously. Wait for the replacement server to listen before asking the child to exit, instead of racing startup against a zero-delay timer. Refs: https://github.com/nodejs/reliability/issues/1687 Signed-off-by: Filip Skokan Assisted-by: Codex --- test/parallel/test-inspector-debug-end.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/test/parallel/test-inspector-debug-end.js b/test/parallel/test-inspector-debug-end.js index 328f15628e3c..fe5a62430fde 100644 --- a/test/parallel/test-inspector-debug-end.js +++ b/test/parallel/test-inspector-debug-end.js @@ -22,18 +22,23 @@ async function testNoSessionNoCrash() { async function testSessionNoCrash() { console.log('Test there\'s no crash stopping server after connecting'); const script = `process._debugEnd(); - process._debugProcess(process.pid); - setTimeout(() => { - console.log("Done"); - process.exit(42); - });`; + process.stdin.once('data', () => { + console.log("Done"); + process.exit(42); + }); + process._debugProcess(process.pid);`; const instance = new NodeInstance('--inspect-brk=0', script); const session = await instance.connectInspectorSession(); await session.send({ method: 'NodeRuntime.enable' }); await session.waitForNotification('NodeRuntime.waitingForDebugger'); + instance.resetPort(); await session.send({ 'method': 'Runtime.runIfWaitingForDebugger' }); await session.waitForServerDisconnect(); + // _debugProcess() starts the inspector asynchronously. Wait for the new + // server to listen before asking the child to exit. + await instance.portPromise; + instance.write('exit'); assert.strictEqual((await instance.expectShutdown()).exitCode, 42); } From e1fb3f4768a2a50bf99703261836010262212c54 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 14 Oct 2026 11:37:37 +0200 Subject: [PATCH 4/6] test: allow periodic flush to finish under load Keep the event loop alive until the periodic write drains and let the test runner enforce the timeout. A one-second deadline can expire while asynchronous file I/O is still pending on a busy CI machine. Refs: https://github.com/nodejs/reliability/issues/1690 Signed-off-by: Filip Skokan Assisted-by: Codex --- test/parallel/test-fastutf8stream-periodicflush.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/parallel/test-fastutf8stream-periodicflush.js b/test/parallel/test-fastutf8stream-periodicflush.js index 4889fa8b19eb..ed9f8aef883b 100644 --- a/test/parallel/test-fastutf8stream-periodicflush.js +++ b/test/parallel/test-fastutf8stream-periodicflush.js @@ -85,13 +85,12 @@ function runTests(sync) { minLength: 5000, periodicFlush: common.platformTimeout(10), }); - const timeout = setTimeout( - common.mustNotCall('periodic flush did not complete'), - common.platformTimeout(1000), - ); + // Periodic flush timers are unrefed. Keep the event loop alive until the + // write completes, and let the test runner enforce the timeout. + const keepAlive = setInterval(() => {}, 1000); stream.once('drain', common.mustCall(() => { - clearTimeout(timeout); + clearInterval(keepAlive); stream.destroy(); readFile(dest, 'utf8', common.mustSucceed((data) => { assert.strictEqual(data, 'periodic flush\n'); From 42a9e9e25622450ee74c40934d2c0aeaca1c079e Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 14 Oct 2026 11:37:37 +0200 Subject: [PATCH 5/6] test: move large SQLite string cases to pummel The oversized-string cases allocate hundreds of megabytes and can make the ordinary statement test time out under parallel suite load. Move all five cases unchanged to the sequential pummel suite, which also allows more time for intensive tests. Refs: https://github.com/nodejs/reliability/issues/1690 Signed-off-by: Filip Skokan Assisted-by: Codex --- test/parallel/test-sqlite-statement.js | 55 +---------------- test/pummel/test-sqlite-statement-too-long.js | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 test/pummel/test-sqlite-statement-too-long.js diff --git a/test/parallel/test-sqlite-statement.js b/test/parallel/test-sqlite-statement.js index 79e72a89d18a..a66ef9585be8 100644 --- a/test/parallel/test-sqlite-statement.js +++ b/test/parallel/test-sqlite-statement.js @@ -1,13 +1,11 @@ // Flags: --expose-gc 'use strict'; -const { enoughTestMem, skipIfSQLiteMissing } = require('../common'); +const { skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); const { Database, Statement, - constants: { SQLITE_OK }, } = require('node:sqlite'); -const { constants } = require('node:buffer'); const { suite, test } = require('node:test'); suite('Statement() constructor', () => { @@ -1447,54 +1445,3 @@ suite('options.persistent', () => { t.assert.deepStrictEqual(stmt.get(), { __proto__: null, val: 42n }); }); }); - -suite('values larger than the maximum string length', { skip: !enoughTestMem }, () => { - // hex() doubles its input, so this is the smallest blob whose text form - // exceeds what V8 can hold in a string. - const blobSize = (constants.MAX_STRING_LENGTH >>> 1) + 1; - // '\u20ac' is 1 UTF-16 code unit but 3 UTF-8 bytes, so this identifier is a - // valid JS string whose UTF-8 form exceeds the limit. - const longIdentifier = '\u20ac'.repeat(Math.ceil(constants.MAX_STRING_LENGTH / 3) + 10); - const tooLong = { code: 'ERR_STRING_TOO_LONG', name: 'Error' }; - - test('get() throws instead of returning undefined', (t) => { - using db = new Database(':memory:'); - using stmt = db.prepare('SELECT hex(zeroblob(?))'); - t.assert.throws(() => { - stmt.get(blobSize); - }, tooLong); - }); - - test('prepare() throws when the SQLite error message is too long', (t) => { - using db = new Database(':memory:'); - // SQLite repeats the identifier in the error. - t.assert.throws(() => { - db.prepare(`SELECT 1 FROM "${longIdentifier}"`); - }, tooLong); - }); - - test('prepare() throws for an oversized authorizer argument', (t) => { - using db = new Database(':memory:'); - db.setAuthorizer(() => SQLITE_OK); - t.assert.throws(() => db.prepare(`CREATE TABLE "${longIdentifier}" (x)`), tooLong); - }); - - test('columns() throws for an oversized declared type', (t) => { - using db = new Database(':memory:'); - db.exec(`CREATE TABLE t (x "${longIdentifier}")`); - using stmt = db.prepare('SELECT x FROM t'); - t.assert.throws(() => stmt.columns(), tooLong); - }); - - test('exec() surfaces the error from a user-defined function', (t) => { - using db = new Database(':memory:'); - db.exec('CREATE TABLE data(val TEXT)'); - db.function('identity', (val) => val); - - t.assert.throws(() => { - db.exec(`INSERT INTO data (val) VALUES (identity(hex(zeroblob(${blobSize}))))`); - }, tooLong); - using stmt = db.prepare('SELECT count(*) AS count FROM data'); - t.assert.deepStrictEqual(stmt.get(), { __proto__: null, count: 0 }); - }); -}); diff --git a/test/pummel/test-sqlite-statement-too-long.js b/test/pummel/test-sqlite-statement-too-long.js new file mode 100644 index 000000000000..02c3faf0ac38 --- /dev/null +++ b/test/pummel/test-sqlite-statement-too-long.js @@ -0,0 +1,60 @@ +'use strict'; + +const { enoughTestMem, skipIfSQLiteMissing } = require('../common'); +skipIfSQLiteMissing(); +const { Database, constants: { SQLITE_OK } } = require('node:sqlite'); +const { constants } = require('node:buffer'); +const { suite, test } = require('node:test'); + +// These queries allocate strings and blobs of several hundred megabytes. +// Run them separately from the ordinary statement tests. +suite('values larger than the maximum string length', { skip: !enoughTestMem }, () => { + // hex() doubles its input, so this is the smallest blob whose text form + // exceeds what V8 can hold in a string. + const blobSize = (constants.MAX_STRING_LENGTH >>> 1) + 1; + // '\u20ac' is 1 UTF-16 code unit but 3 UTF-8 bytes, so this identifier is a + // valid JS string whose UTF-8 form exceeds the limit. + const longIdentifier = '\u20ac'.repeat(Math.ceil(constants.MAX_STRING_LENGTH / 3) + 10); + const tooLong = { code: 'ERR_STRING_TOO_LONG', name: 'Error' }; + + test('get() throws instead of returning undefined', (t) => { + using db = new Database(':memory:'); + using stmt = db.prepare('SELECT hex(zeroblob(?))'); + t.assert.throws(() => { + stmt.get(blobSize); + }, tooLong); + }); + + test('prepare() throws when the SQLite error message is too long', (t) => { + using db = new Database(':memory:'); + // SQLite repeats the identifier in the error. + t.assert.throws(() => { + db.prepare(`SELECT 1 FROM "${longIdentifier}"`); + }, tooLong); + }); + + test('prepare() throws for an oversized authorizer argument', (t) => { + using db = new Database(':memory:'); + db.setAuthorizer(() => SQLITE_OK); + t.assert.throws(() => db.prepare(`CREATE TABLE "${longIdentifier}" (x)`), tooLong); + }); + + test('columns() throws for an oversized declared type', (t) => { + using db = new Database(':memory:'); + db.exec(`CREATE TABLE t (x "${longIdentifier}")`); + using stmt = db.prepare('SELECT x FROM t'); + t.assert.throws(() => stmt.columns(), tooLong); + }); + + test('exec() surfaces the error from a user-defined function', (t) => { + using db = new Database(':memory:'); + db.exec('CREATE TABLE data(val TEXT)'); + db.function('identity', (val) => val); + + t.assert.throws(() => { + db.exec(`INSERT INTO data (val) VALUES (identity(hex(zeroblob(${blobSize}))))`); + }, tooLong); + using stmt = db.prepare('SELECT count(*) AS count FROM data'); + t.assert.deepStrictEqual(stmt.get(), { __proto__: null, count: 0 }); + }); +}); From ab9e085d60a9c1e275ecf4c752e99faa10a7e450 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Wed, 14 Oct 2026 11:37:37 +0200 Subject: [PATCH 6/6] test: wait for shared-isolate Worker exit Ten thousand non-blocking loop turns can finish before the Worker has started. Use a thirty-second deadline and yield between turns while waiting for its exit, preserving the expected exit-code assertion. Refs: https://github.com/nodejs/reliability/issues/1687 Signed-off-by: Filip Skokan Assisted-by: Codex --- test/cctest/test_environment_shared_isolate.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/cctest/test_environment_shared_isolate.cc b/test/cctest/test_environment_shared_isolate.cc index 3e86063b064c..b3807b7bb709 100644 --- a/test/cctest/test_environment_shared_isolate.cc +++ b/test/cctest/test_environment_shared_isolate.cc @@ -354,9 +354,13 @@ TEST_P(SharedIsolateTest, EnvironmentsComeAndGoWhileSiblingsRun) { " return 0;\n" "})()"), 0); - for (int turn = 0; turn < 10000; turn++) { + // Loop turns do not bound Worker startup time. Yield to the Worker and + // allow it to finish even when the main thread pumps the loop quickly. + const uint64_t deadline = uv_hrtime() + 30ULL * 1000 * 1000 * 1000; + while (uv_hrtime() < deadline) { PumpLoop(1); if (EvaluateInt(third.get(), "state.workerExit") == 7) break; + uv_sleep(1); } EXPECT_EQ(EvaluateInt(third.get(), "state.workerExit"), 7); }