diff --git a/doc/api/errors.md b/doc/api/errors.md
index a63fb28ab8933d..b55f659c39ed94 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`).
+
### `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/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/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/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;
}
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/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);
+});
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-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');
+}));
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'));
+});
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 .+/);