diff --git a/test/common/assertSnapshot.js b/test/common/assertSnapshot.js index af4345f5111f24..c3a946ca5fe095 100644 --- a/test/common/assertSnapshot.js +++ b/test/common/assertSnapshot.js @@ -33,8 +33,28 @@ function replaceWindowsPaths(str) { function transformProjectRoot(replacement = '') { const projectRoot = path.resolve(__dirname, '../..'); + const fsRoot = path.parse(projectRoot).root; + + // If the project root is the filesystem root (e.g. "/"), do not attempt to + // strip it. Stripping "/" would mangle unrelated strings like URLs + // ("https://") and break snapshots. + if (projectRoot === fsRoot) { + return (str) => str.replaceAll('\\\'', "'"); + } + + // Some outputs can already contain POSIX separators even on Windows. + const projectRootPosix = projectRoot.replaceAll(path.win32.sep, path.posix.sep); + const reProjectRoot = new RegExp(`${RegExp.escape(projectRoot)}(?=[\\\\/]|$)`, 'g'); + const reProjectRootPosix = + projectRootPosix === projectRoot ? + null : + new RegExp(`${RegExp.escape(projectRootPosix)}(?=[\\\\/]|$)`, 'g'); + return (str) => { - return str.replaceAll('\\\'', "'").replaceAll(projectRoot, replacement); + let out = str.replaceAll('\\\'', "'"); + out = out.replaceAll(reProjectRoot, replacement); + if (reProjectRootPosix) out = out.replaceAll(reProjectRootPosix, replacement); + return out; }; } diff --git a/test/es-module/test-esm-import-meta.mjs b/test/es-module/test-esm-import-meta.mjs index 638989724bbfd4..5d18fb602fe5dc 100644 --- a/test/es-module/test-esm-import-meta.mjs +++ b/test/es-module/test-esm-import-meta.mjs @@ -16,17 +16,17 @@ for (const descriptor of Object.values(descriptors)) { }); } -const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/; +const urlReg = /^file:\/\/\/(?:.*\/)?test\/es-module\/test-esm-import-meta\.mjs$/; assert.match(import.meta.url, urlReg); // Match *nix paths: `/some/path/test/es-module` // Match Windows paths: `d:\\some\\path\\test\\es-module` -const dirReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\)es-module$/; +const dirReg = /^(?:\/|\w:\\)(?:.*(?:\/|\\))?test(?:\/|\\)es-module$/; assert.match(import.meta.dirname, dirReg); // Match *nix paths: `/some/path/test/es-module/test-esm-import-meta.mjs` // Match Windows paths: `d:\\some\\path\\test\\es-module\\test-esm-import-meta.js` -const fileReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\)es-module(\/|\\)test-esm-import-meta\.mjs$/; +const fileReg = /^(?:\/|\w:\\)(?:.*(?:\/|\\))?test(?:\/|\\)es-module(?:\/|\\)test-esm-import-meta\.mjs$/; assert.match(import.meta.filename, fileReg); // Verify that `data:` imports do not behave like `file:` imports. diff --git a/test/parallel/test-assert-snapshot-transform-project-root.js b/test/parallel/test-assert-snapshot-transform-project-root.js new file mode 100644 index 00000000000000..35ba64adf5466c --- /dev/null +++ b/test/parallel/test-assert-snapshot-transform-project-root.js @@ -0,0 +1,39 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const path = require('node:path'); + +const snapshot = require('../common/assertSnapshot'); + +// `transformProjectRoot()` is used by many snapshot-based tests to strip the +// project root from stack traces and other outputs. Ensure it only strips the +// project root when it is a real path prefix, not when it is part of some other +// token (e.g., "node_modules" or URLs). +{ + const stripProjectRoot = snapshot.transformProjectRoot(''); + const projectRoot = path.resolve(__dirname, '..', '..'); + + assert.strictEqual( + stripProjectRoot(`${projectRoot}${path.sep}test${path.sep}fixtures`), + `${path.sep}test${path.sep}fixtures`, + ); + + const shouldNotStrip = `${projectRoot}_modules`; + assert.strictEqual(stripProjectRoot(shouldNotStrip), shouldNotStrip); + + const urlLike = `https://${projectRoot}js.org`; + assert.strictEqual(stripProjectRoot(urlLike), urlLike); + + if (process.platform === 'win32') { + const projectRootPosix = projectRoot.replaceAll(path.win32.sep, path.posix.sep); + + assert.strictEqual( + stripProjectRoot(`${projectRootPosix}/test/fixtures`), + '/test/fixtures', + ); + + const shouldNotStripPosix = `${projectRootPosix}_modules`; + assert.strictEqual(stripProjectRoot(shouldNotStripPosix), shouldNotStripPosix); + } +} + diff --git a/test/parallel/test-cli-permission-deny-fs.js b/test/parallel/test-cli-permission-deny-fs.js index d5744cac94db3d..1bdb59b9607efd 100644 --- a/test/parallel/test-cli-permission-deny-fs.js +++ b/test/parallel/test-cli-permission-deny-fs.js @@ -134,7 +134,14 @@ const path = require('path'); { const { root } = path.parse(process.cwd()); const abs = (p) => path.join(root, p); - const firstPath = abs(path.sep + process.cwd().split(path.sep, 2)[1]); + let firstPath = abs(path.sep + process.cwd().split(path.sep, 2)[1]); + if (path.resolve(firstPath) === root) { + // When the repository itself is in the filesystem root (e.g. "/"), + // allowing reads from `root` would also allow reading `/etc/passwd` and + // make the test meaningless. Allow reads from `/test` instead, + // which still contains the fixture entry point. + firstPath = path.join(root, 'test'); + } if (firstPath.startsWith('/etc')) { common.skip('/etc as firstPath'); } diff --git a/test/parallel/test-node-output-console.mjs b/test/parallel/test-node-output-console.mjs index e989b79ab505f6..b91facbb62413d 100644 --- a/test/parallel/test-node-output-console.mjs +++ b/test/parallel/test-node-output-console.mjs @@ -12,8 +12,13 @@ function replaceStackTrace(str) { } describe('console output', { concurrency: !process.env.TEST_PARALLEL }, () => { + const stripProjectRoot = snapshot.transformProjectRoot(''); + function normalize(str) { - return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '').replaceAll('/', '*').replaceAll(process.version, '*').replaceAll(/\d+/g, '*'); + return stripProjectRoot(str) + .replaceAll('/', '*') + .replaceAll(process.version, '*') + .replaceAll(/\d+/g, '*'); } const tests = [ { name: 'console/2100bytes.js' }, diff --git a/test/parallel/test-node-output-errors.mjs b/test/parallel/test-node-output-errors.mjs index 3d913475d8d2a0..1be0bd5cd80c88 100644 --- a/test/parallel/test-node-output-errors.mjs +++ b/test/parallel/test-node-output-errors.mjs @@ -4,11 +4,11 @@ import * as snapshot from '../common/assertSnapshot.js'; import * as os from 'node:os'; import { describe, it } from 'node:test'; import { basename } from 'node:path'; -import { pathToFileURL } from 'node:url'; const skipForceColors = (common.isWindows && (Number(os.release().split('.')[0]) !== 10 || Number(os.release().split('.')[2]) < 14393)); // See https://github.com/nodejs/node/pull/33132 +const stripProjectRoot = snapshot.transformProjectRoot(''); function replaceStackTrace(str) { return snapshot.replaceStackTrace(str, '$1at *$7\n'); @@ -22,8 +22,7 @@ function replaceForceColorsStackTrace(str) { describe('errors output', { concurrency: !process.env.TEST_PARALLEL }, () => { function normalize(str) { const baseName = basename(process.argv0 || 'node', '.exe'); - return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '') - .replaceAll(pathToFileURL(process.cwd()).pathname, '') + return stripProjectRoot(str) .replaceAll('//', '*') .replaceAll(/\/(\w)/g, '*$1') .replaceAll('*test*', '*') diff --git a/test/parallel/test-node-output-eval.mjs b/test/parallel/test-node-output-eval.mjs index 56a880c8adfee1..2a7c01ff823803 100644 --- a/test/parallel/test-node-output-eval.mjs +++ b/test/parallel/test-node-output-eval.mjs @@ -8,8 +8,10 @@ import { basename } from 'node:path'; import { describe, it } from 'node:test'; describe('eval output', { concurrency: true }, () => { + const stripProjectRoot = snapshot.transformProjectRoot(''); + function normalize(str) { - return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '') + return stripProjectRoot(str) .replaceAll(/\d+:\d+/g, '*:*'); } diff --git a/test/parallel/test-node-output-v8-warning.mjs b/test/parallel/test-node-output-v8-warning.mjs index b5e52d493baf55..c744608efe8ffb 100644 --- a/test/parallel/test-node-output-v8-warning.mjs +++ b/test/parallel/test-node-output-v8-warning.mjs @@ -14,13 +14,15 @@ function replaceExecName(str) { } describe('v8 output', { concurrency: !process.env.TEST_PARALLEL }, () => { + const stripProjectRoot = snapshot.transformProjectRoot(''); + function normalize(str) { - return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '') - .replaceAll(/:\d+/g, ':*') - .replaceAll('/', '*') - .replaceAll('*test*', '*') - .replaceAll(/.*?\*fixtures\*v8\*/g, '(node:*) V8: *') // Replace entire path before fixtures/v8 - .replaceAll('*fixtures*v8*', '*'); + return stripProjectRoot(str) + .replaceAll(/:\d+/g, ':*') + .replaceAll('/', '*') + .replaceAll('*test*', '*') + .replaceAll(/.*?\*fixtures\*v8\*/g, '(node:*) V8: *') // Replace entire path before fixtures/v8 + .replaceAll('*fixtures*v8*', '*'); } const common = snapshot .transform(snapshot.replaceWindowsLineEndings, snapshot.replaceWindowsPaths, replaceNodeVersion, replaceExecName); diff --git a/test/parallel/test-node-output-vm.mjs b/test/parallel/test-node-output-vm.mjs index aa5072007b4fd7..d2bed1a81764a5 100644 --- a/test/parallel/test-node-output-vm.mjs +++ b/test/parallel/test-node-output-vm.mjs @@ -8,8 +8,14 @@ function replaceNodeVersion(str) { } describe('vm output', { concurrency: !process.env.TEST_PARALLEL }, () => { + const stripProjectRoot = snapshot.transformProjectRoot(''); + function normalize(str) { - return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '').replaceAll('//', '*').replaceAll(/\/(\w)/g, '*$1').replaceAll('*test*', '*').replaceAll(/node:vm:\d+:\d+/g, 'node:vm:*'); + return stripProjectRoot(str) + .replaceAll('//', '*') + .replaceAll(/\/(\w)/g, '*$1') + .replaceAll('*test*', '*') + .replaceAll(/node:vm:\d+:\d+/g, 'node:vm:*'); } const defaultTransform = snapshot