From a3ddfbe19189efd1aa1b37edf3edae00c2eeab86 Mon Sep 17 00:00:00 2001 From: Xavier Stouder Date: Sat, 17 Jan 2026 18:33:57 +0100 Subject: [PATCH 1/6] test_runner: print info when test restarts When using spec reporter and running test in watch mode, print an info message containing the current datetime when tests restart. Fixes: https://github.com/nodejs/node/issues/57206 PR-URL: https://github.com/nodejs/node/pull/61160 Reviewed-By: Colin Ihrig Reviewed-By: Moshe Atlow --- lib/internal/test_runner/reporter/spec.js | 5 +++++ ...test-output-spec-reporter-watch-restart.mjs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 test/test-runner/test-output-spec-reporter-watch-restart.mjs diff --git a/lib/internal/test_runner/reporter/spec.js b/lib/internal/test_runner/reporter/spec.js index 515d17fd42b455..14c447f316492f 100644 --- a/lib/internal/test_runner/reporter/spec.js +++ b/lib/internal/test_runner/reporter/spec.js @@ -5,6 +5,8 @@ const { ArrayPrototypePush, ArrayPrototypeShift, ArrayPrototypeUnshift, + Date, + DatePrototypeToLocaleString, } = primordials; const assert = require('assert'); const Transform = require('internal/streams/transform'); @@ -101,6 +103,9 @@ class SpecReporter extends Transform { if (data.file === undefined) { return this.#formatFailedTestResults(); } + break; + case 'test:watch:restarted': + return `\nRestarted at ${DatePrototypeToLocaleString(new Date())}\n`; } } _transform({ type, data }, encoding, callback) { diff --git a/test/test-runner/test-output-spec-reporter-watch-restart.mjs b/test/test-runner/test-output-spec-reporter-watch-restart.mjs new file mode 100644 index 00000000000000..d158375974c48b --- /dev/null +++ b/test/test-runner/test-output-spec-reporter-watch-restart.mjs @@ -0,0 +1,18 @@ +import '../common/index.mjs'; +import { spec as SpecReporter } from 'node:test/reporters'; +import assert from 'node:assert'; + +const reporter = new SpecReporter(); +let output = ''; + +reporter.on('data', (chunk) => { + output += chunk; +}); + +reporter.write({ + type: 'test:watch:restarted', +}); + +reporter.end(); + +assert.match(output, /Restarted at .+/); From a00f5fea7029d3f0791ff6dddabc34b5a0ccf9b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heath=20Dutton=F0=9F=95=B4=EF=B8=8F?= Date: Sat, 17 Jan 2026 12:34:06 -0500 Subject: [PATCH 2/6] test_runner: fix coverage report when a directory is named file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The coverage tree traversal checked `tree[key].file` to detect file entries. When a directory named "file" contained a file also named "file", this check incorrectly matched the child entry instead of file metadata, causing a TypeError when accessing `.path`. Check for `.file?.path` instead to correctly identify file metadata. Fixes: https://github.com/nodejs/node/issues/61080 PR-URL: https://github.com/nodejs/node/pull/61169 Reviewed-By: Aviv Keller Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca Reviewed-By: Chemi Atlow Reviewed-By: Pietro Marchini Reviewed-By: Ulises Gascón Reviewed-By: Moshe Atlow --- lib/internal/test_runner/utils.js | 2 +- .../test-runner/coverage-file-name/file/file | 4 ++++ .../test-runner/coverage-file-name/test.js | 7 +++++++ test/parallel/test-runner-coverage.js | 16 ++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/test-runner/coverage-file-name/file/file create mode 100644 test/fixtures/test-runner/coverage-file-name/test.js diff --git a/lib/internal/test_runner/utils.js b/lib/internal/test_runner/utils.js index 937c52b08493ea..5b53342933cdcb 100644 --- a/lib/internal/test_runner/utils.js +++ b/lib/internal/test_runner/utils.js @@ -551,7 +551,7 @@ function getCoverageReport(pad, summary, symbol, color, table) { function printCoverageBodyTree(tree, depth = 0) { for (const key in tree) { - if (tree[key].file) { + if (tree[key].file?.path) { const file = tree[key].file; const fileName = ArrayPrototypePop(StringPrototypeSplit(file.path, sep)); diff --git a/test/fixtures/test-runner/coverage-file-name/file/file b/test/fixtures/test-runner/coverage-file-name/file/file new file mode 100644 index 00000000000000..9bdf0f53a413b2 --- /dev/null +++ b/test/fixtures/test-runner/coverage-file-name/file/file @@ -0,0 +1,4 @@ +'use strict'; +module.exports.fn = function() { + return 1; +}; diff --git a/test/fixtures/test-runner/coverage-file-name/test.js b/test/fixtures/test-runner/coverage-file-name/test.js new file mode 100644 index 00000000000000..e6371f2819fdaf --- /dev/null +++ b/test/fixtures/test-runner/coverage-file-name/test.js @@ -0,0 +1,7 @@ +'use strict'; +const { fn } = require('./file/file'); +const test = require('node:test'); + +test('coverage with file/file directory structure', () => { + fn(); +}); diff --git a/test/parallel/test-runner-coverage.js b/test/parallel/test-runner-coverage.js index e014035c2687f8..5a8f3d743538cb 100644 --- a/test/parallel/test-runner-coverage.js +++ b/test/parallel/test-runner-coverage.js @@ -550,3 +550,19 @@ test('correctly prints the coverage report of files contained in parent director assert(result.stdout.toString().includes(report)); assert.strictEqual(result.status, 0); }); + +// Regression test for https://github.com/nodejs/node/issues/61080 +test('coverage with directory and file named "file"', skipIfNoInspector, () => { + const fixture = fixtures.path('test-runner', 'coverage-file-name', 'test.js'); + const args = [ + '--experimental-test-coverage', + '--test-reporter', + 'tap', + fixture, + ]; + const result = spawnSync(process.execPath, args); + + assert.strictEqual(result.stderr.toString(), ''); + assert.strictEqual(result.status, 0); + assert(result.stdout.toString().includes('start of coverage report')); +}); From 24b16509d414cb2f48bb933109c4ac9c7303e0a9 Mon Sep 17 00:00:00 2001 From: Nicholas Paun Date: Sat, 17 Jan 2026 09:52:38 -0800 Subject: [PATCH 3/6] fs: fix errorOnExist behavior for directory copy in fs.cp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/60946 Reviewed-By: Yagiz Nizipli Reviewed-By: Dario Piotrowicz Reviewed-By: Juan José Arboleda Reviewed-By: Beth Griggs Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca Reviewed-By: Gürgün Dayıoğlu Reviewed-By: James M Snell --- lib/internal/fs/cp/cp.js | 11 ++++++- ...-fs-cp-async-dir-exists-error-on-exist.mjs | 30 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/parallel/test-fs-cp-async-dir-exists-error-on-exist.mjs diff --git a/lib/internal/fs/cp/cp.js b/lib/internal/fs/cp/cp.js index b632c650681e77..10c52b11463445 100644 --- a/lib/internal/fs/cp/cp.js +++ b/lib/internal/fs/cp/cp.js @@ -300,8 +300,17 @@ async function setDestTimestamps(src, dest) { return utimes(dest, updatedSrcStat.atime, updatedSrcStat.mtime); } -function onDir(srcStat, destStat, src, dest, opts) { +async function onDir(srcStat, destStat, src, dest, opts) { if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts); + if (opts.errorOnExist && !opts.force) { + throw new ERR_FS_CP_EEXIST({ + message: `${dest} already exists`, + path: dest, + syscall: 'cp', + errno: EEXIST, + code: 'EEXIST', + }); + } return copyDir(src, dest, opts); } diff --git a/test/parallel/test-fs-cp-async-dir-exists-error-on-exist.mjs b/test/parallel/test-fs-cp-async-dir-exists-error-on-exist.mjs new file mode 100644 index 00000000000000..90a7d19c2fc148 --- /dev/null +++ b/test/parallel/test-fs-cp-async-dir-exists-error-on-exist.mjs @@ -0,0 +1,30 @@ +// This tests that cp() returns error if errorOnExist is true, force is false, +// and the destination directory already exists (even if contents don't conflict). + +import { mustCall } from '../common/index.mjs'; +import { nextdir } from '../common/fs.js'; +import assert from 'node:assert'; +import { cp, mkdirSync, writeFileSync } from 'node:fs'; +import tmpdir from '../common/tmpdir.js'; + +tmpdir.refresh(); + +const src = nextdir(); +const dest = nextdir(); + +// Create source directory with a file +mkdirSync(src); +writeFileSync(`${src}/file.txt`, 'test'); + +// Create destination directory with different file +mkdirSync(dest); +writeFileSync(`${dest}/other.txt`, 'existing'); + +// Should fail because dest directory already exists +cp(src, dest, { + recursive: true, + errorOnExist: true, + force: false, +}, mustCall((err) => { + assert.strictEqual(err.code, 'ERR_FS_CP_EEXIST'); +})); From 79ddd1ba10322269777b9de0b9271794eade16cc Mon Sep 17 00:00:00 2001 From: "Abhishek Kv. Savani" Date: Thu, 27 Nov 2025 09:30:51 +0000 Subject: [PATCH 4/6] test_runner: fix memory leaks in runner - Close readline interface after child process exits Prevents accumulation of event listeners on stderr stream - Extract watch mode event handler to named function Allows proper cleanup when watch mode is aborted These changes prevent unbounded memory growth in long-running test suites and watch mode sessions. PR-URL: https://github.com/nodejs/node/pull/60860 Reviewed-By: Matteo Collina Reviewed-By: Moshe Atlow --- lib/internal/test_runner/runner.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 3f9e855f755212..a9b3cfb93c8abb 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -460,6 +460,9 @@ function runTestFile(path, filesWatcher, opts) { finished(child.stdout, { __proto__: null, signal: t.signal }), ]); + // Close readline interface to prevent memory leak + rl.close(); + if (watchMode) { filesWatcher.runningProcesses.delete(path); filesWatcher.runningSubtests.delete(path); @@ -522,7 +525,7 @@ function watchFiles(testFiles, opts) { } // Watch for changes in current filtered files - watcher.on('changed', ({ owners, eventType }) => { + const onChanged = ({ owners, eventType }) => { if (!opts.hasFiles && (eventType === 'rename' || eventType === 'change')) { const updatedTestFiles = createTestFileList(opts.globPatterns, opts.cwd); const newFileName = ArrayPrototypeFind(updatedTestFiles, (x) => !ArrayPrototypeIncludes(testFiles, x)); @@ -563,19 +566,29 @@ function watchFiles(testFiles, opts) { triggerUncaughtException(error, true /* fromPromise */); })); } - }); + }; + + watcher.on('changed', onChanged); + + // Cleanup function to remove event listener and prevent memory leak + const cleanup = () => { + watcher.removeListener('changed', onChanged); + opts.root.harness.watching = false; + opts.root.postRun(); + }; + if (opts.signal) { kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation; opts.signal.addEventListener( 'abort', - () => { - opts.root.harness.watching = false; - opts.root.postRun(); - }, + cleanup, { __proto__: null, once: true, [kResistStopPropagation]: true }, ); } + // Expose cleanup method for proper resource management + filesWatcher.cleanup = cleanup; + return filesWatcher; } From 04c88288638e6eb96ec4ac4977eade83a56b50c4 Mon Sep 17 00:00:00 2001 From: sangwook Date: Wed, 14 Jan 2026 23:38:05 +0900 Subject: [PATCH 5/6] lib: fix TypeScript support check in jitless mode WebAssembly is disabled when Node.js is run with --jitless. The internal TypeScript stripper relies on WebAssembly, and previously failed obscurely with a ReferenceError in this mode. This commit adds an explicit check for WebAssembly support when loading the TypeScript parser. If WebAssembly is unavailable, it now throws a descriptive ERR_WEBASSEMBLY_NOT_SUPPORTED error. Fixes: https://github.com/nodejs/node/issues/61353 PR-URL: https://github.com/nodejs/node/pull/61382 Reviewed-By: Marco Ippolito Reviewed-By: Chemi Atlow --- doc/api/errors.md | 8 ++++++++ lib/internal/errors.js | 3 +++ lib/internal/util.js | 4 ++++ test/es-module/test-typescript.mjs | 11 +++++++++++ 4 files changed, 26 insertions(+) diff --git a/doc/api/errors.md b/doc/api/errors.md index a63fb28ab8933d..c059fac476e503 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -3350,6 +3350,14 @@ The WASI instance has already started. The WASI instance has not been started. + + +### `ERR_WEBASSEMBLY_NOT_SUPPORTED` + +A feature requiring WebAssembly was used, but WebAssembly is not supported or +has been disabled in the current environment (for example, when running with +`--jitless`). The error message specifies which feature requires WebAssembly. + ### `ERR_WEBASSEMBLY_RESPONSE` diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 5fa4437b09e556..7c4728627731fe 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1905,6 +1905,9 @@ E('ERR_VM_MODULE_NOT_MODULE', 'Provided module is not an instance of Module', Error); E('ERR_VM_MODULE_STATUS', 'Module status %s', Error); E('ERR_WASI_ALREADY_STARTED', 'WASI instance has already started', Error); +E('ERR_WEBASSEMBLY_NOT_SUPPORTED', + 'WebAssembly is not supported in this environment, but is required for %s', + Error); E('ERR_WEBASSEMBLY_RESPONSE', 'WebAssembly response %s', TypeError); E('ERR_WORKER_INIT_FAILED', 'Worker initialization failure: %s', Error); E('ERR_WORKER_INVALID_EXEC_ARGV', (errors, msg = 'invalid execArgv flags') => diff --git a/lib/internal/util.js b/lib/internal/util.js index 9739422d08284a..28bb83e558c426 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -46,6 +46,7 @@ const { SymbolPrototypeGetDescription, SymbolReplace, SymbolSplit, + globalThis, } = primordials; const { @@ -53,6 +54,7 @@ const { ERR_NO_CRYPTO, ERR_NO_TYPESCRIPT, ERR_UNKNOWN_SIGNAL, + ERR_WEBASSEMBLY_NOT_SUPPORTED, }, isErrorStackTraceLimitWritable, overrideStackTrace, @@ -244,6 +246,8 @@ function assertCrypto() { function assertTypeScript() { if (noTypeScript) throw new ERR_NO_TYPESCRIPT(); + if (globalThis.WebAssembly === undefined) + throw new ERR_WEBASSEMBLY_NOT_SUPPORTED('TypeScript'); } /** diff --git a/test/es-module/test-typescript.mjs b/test/es-module/test-typescript.mjs index df25f60defd35b..2e98bc3f289d1e 100644 --- a/test/es-module/test-typescript.mjs +++ b/test/es-module/test-typescript.mjs @@ -342,3 +342,14 @@ test('check transform types warning', async () => { assert.match(result.stdout, /Hello, TypeScript!/); assert.strictEqual(result.code, 0); }); + +test('expect error when executing a TypeScript file with --jitless', async () => { + const result = await spawnPromisified(process.execPath, [ + '--jitless', + fixtures.path('typescript/ts/test-typescript.ts'), + ]); + + assert.match(result.stderr, /ERR_WEBASSEMBLY_NOT_SUPPORTED/); + assert.match(result.stderr, /WebAssembly is not supported in this environment, but is required for TypeScript/); + assert.strictEqual(result.code, 1); +}); From 9bcfbeb236307c5a9cc558477598b4338ed398b6 Mon Sep 17 00:00:00 2001 From: sangwook <73056306+Han5991@users.noreply.github.com> Date: Thu, 15 Jan 2026 18:59:49 +0900 Subject: [PATCH 6/6] doc: refine WebAssembly error documentation Co-authored-by: Marco Ippolito PR-URL: https://github.com/nodejs/node/pull/61382 Fixes: https://github.com/nodejs/node/issues/61353 Reviewed-By: Marco Ippolito Reviewed-By: Chemi Atlow --- doc/api/errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index c059fac476e503..b55f659c39ed94 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -3356,7 +3356,7 @@ The WASI instance has not been started. A feature requiring WebAssembly was used, but WebAssembly is not supported or has been disabled in the current environment (for example, when running with -`--jitless`). The error message specifies which feature requires WebAssembly. +`--jitless`).