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 .+/);