Skip to content

Commit fc94c8c

Browse files
author
Emanuel Covelli
committed
test: make snapshot path stripping CWD-agnostic
transformProjectRoot() now strips the project root only when it is a real path prefix, avoiding accidental matches inside tokens like 'node_modules' or URLs when the repository lives in paths such as '/node'. Also update the node output snapshot tests to use transformProjectRoot(), make permission deny tests handle running from the filesystem root, and relax import.meta path regexes for the same scenario. Add a regression test for transformProjectRoot(). Refs: #61303
1 parent e1fc3dc commit fc94c8c

9 files changed

+98
-15
lines changed

test/common/assertSnapshot.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,28 @@ function replaceWindowsPaths(str) {
3333

3434
function transformProjectRoot(replacement = '') {
3535
const projectRoot = path.resolve(__dirname, '../..');
36+
const fsRoot = path.parse(projectRoot).root;
37+
38+
// If the project root is the filesystem root (e.g. "/"), do not attempt to
39+
// strip it. Stripping "/" would mangle unrelated strings like URLs
40+
// ("https://") and break snapshots.
41+
if (projectRoot === fsRoot) {
42+
return (str) => str.replaceAll('\\\'', "'");
43+
}
44+
45+
// Some outputs can already contain POSIX separators even on Windows.
46+
const projectRootPosix = projectRoot.replaceAll(path.win32.sep, path.posix.sep);
47+
const reProjectRoot = new RegExp(`${RegExp.escape(projectRoot)}(?=[\\\\/]|$)`, 'g');
48+
const reProjectRootPosix =
49+
projectRootPosix === projectRoot ?
50+
null :
51+
new RegExp(`${RegExp.escape(projectRootPosix)}(?=[\\\\/]|$)`, 'g');
52+
3653
return (str) => {
37-
return str.replaceAll('\\\'', "'").replaceAll(projectRoot, replacement);
54+
let out = str.replaceAll('\\\'', "'");
55+
out = out.replaceAll(reProjectRoot, replacement);
56+
if (reProjectRootPosix) out = out.replaceAll(reProjectRootPosix, replacement);
57+
return out;
3858
};
3959
}
4060

test/es-module/test-esm-import-meta.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@ for (const descriptor of Object.values(descriptors)) {
1616
});
1717
}
1818

19-
const urlReg = /^file:\/\/\/.*\/test\/es-module\/test-esm-import-meta\.mjs$/;
19+
const urlReg = /^file:\/\/\/(?:.*\/)?test\/es-module\/test-esm-import-meta\.mjs$/;
2020
assert.match(import.meta.url, urlReg);
2121

2222
// Match *nix paths: `/some/path/test/es-module`
2323
// Match Windows paths: `d:\\some\\path\\test\\es-module`
24-
const dirReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\)es-module$/;
24+
const dirReg = /^(?:\/|\w:\\)(?:.*(?:\/|\\))?test(?:\/|\\)es-module$/;
2525
assert.match(import.meta.dirname, dirReg);
2626

2727
// Match *nix paths: `/some/path/test/es-module/test-esm-import-meta.mjs`
2828
// Match Windows paths: `d:\\some\\path\\test\\es-module\\test-esm-import-meta.js`
29-
const fileReg = /^(\/|\w:\\).*(\/|\\)test(\/|\\)es-module(\/|\\)test-esm-import-meta\.mjs$/;
29+
const fileReg = /^(?:\/|\w:\\)(?:.*(?:\/|\\))?test(?:\/|\\)es-module(?:\/|\\)test-esm-import-meta\.mjs$/;
3030
assert.match(import.meta.filename, fileReg);
3131

3232
// Verify that `data:` imports do not behave like `file:` imports.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('node:assert/strict');
5+
const path = require('node:path');
6+
7+
const snapshot = require('../common/assertSnapshot');
8+
9+
// `transformProjectRoot()` is used by many snapshot-based tests to strip the
10+
// project root from stack traces and other outputs. Ensure it only strips the
11+
// project root when it is a real path prefix, not when it is part of some other
12+
// token (e.g., "node_modules" or URLs).
13+
{
14+
const stripProjectRoot = snapshot.transformProjectRoot('');
15+
const projectRoot = path.resolve(__dirname, '..', '..');
16+
17+
assert.strictEqual(
18+
stripProjectRoot(`${projectRoot}${path.sep}test${path.sep}fixtures`),
19+
`${path.sep}test${path.sep}fixtures`,
20+
);
21+
22+
const shouldNotStrip = `${projectRoot}_modules`;
23+
assert.strictEqual(stripProjectRoot(shouldNotStrip), shouldNotStrip);
24+
25+
const urlLike = `https://${projectRoot}js.org`;
26+
assert.strictEqual(stripProjectRoot(urlLike), urlLike);
27+
28+
if (process.platform === 'win32') {
29+
const projectRootPosix = projectRoot.replaceAll(path.win32.sep, path.posix.sep);
30+
31+
assert.strictEqual(
32+
stripProjectRoot(`${projectRootPosix}/test/fixtures`),
33+
'/test/fixtures',
34+
);
35+
36+
const shouldNotStripPosix = `${projectRootPosix}_modules`;
37+
assert.strictEqual(stripProjectRoot(shouldNotStripPosix), shouldNotStripPosix);
38+
}
39+
}

test/parallel/test-cli-permission-deny-fs.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,14 @@ const path = require('path');
134134
{
135135
const { root } = path.parse(process.cwd());
136136
const abs = (p) => path.join(root, p);
137-
const firstPath = abs(path.sep + process.cwd().split(path.sep, 2)[1]);
137+
let firstPath = abs(path.sep + process.cwd().split(path.sep, 2)[1]);
138+
if (path.resolve(firstPath) === root) {
139+
// When the repository itself is in the filesystem root (e.g. "/"),
140+
// allowing reads from `root` would also allow reading `/etc/passwd` and
141+
// make the test meaningless. Allow reads from `<root>/test` instead,
142+
// which still contains the fixture entry point.
143+
firstPath = path.join(root, 'test');
144+
}
138145
if (firstPath.startsWith('/etc')) {
139146
common.skip('/etc as firstPath');
140147
}

test/parallel/test-node-output-console.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ function replaceStackTrace(str) {
1212
}
1313

1414
describe('console output', { concurrency: !process.env.TEST_PARALLEL }, () => {
15+
const stripProjectRoot = snapshot.transformProjectRoot('');
16+
1517
function normalize(str) {
16-
return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '').replaceAll('/', '*').replaceAll(process.version, '*').replaceAll(/\d+/g, '*');
18+
return stripProjectRoot(str)
19+
.replaceAll('/', '*')
20+
.replaceAll(process.version, '*')
21+
.replaceAll(/\d+/g, '*');
1722
}
1823
const tests = [
1924
{ name: 'console/2100bytes.js' },

test/parallel/test-node-output-errors.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { pathToFileURL } from 'node:url';
99
const skipForceColors =
1010
(common.isWindows && (Number(os.release().split('.')[0]) !== 10 || Number(os.release().split('.')[2]) < 14393)); // See https://github.com/nodejs/node/pull/33132
1111

12+
const stripProjectRoot = snapshot.transformProjectRoot('');
1213

1314
function replaceStackTrace(str) {
1415
return snapshot.replaceStackTrace(str, '$1at *$7\n');
@@ -22,7 +23,8 @@ function replaceForceColorsStackTrace(str) {
2223
describe('errors output', { concurrency: !process.env.TEST_PARALLEL }, () => {
2324
function normalize(str) {
2425
const baseName = basename(process.argv0 || 'node', '.exe');
25-
return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '')
26+
return stripProjectRoot(str)
27+
// Also strip the URL-encoded form of cwd (for file:// URLs).
2628
.replaceAll(pathToFileURL(process.cwd()).pathname, '')
2729
.replaceAll('//', '*')
2830
.replaceAll(/\/(\w)/g, '*$1')

test/parallel/test-node-output-eval.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import { basename } from 'node:path';
88
import { describe, it } from 'node:test';
99

1010
describe('eval output', { concurrency: true }, () => {
11+
const stripProjectRoot = snapshot.transformProjectRoot('');
12+
1113
function normalize(str) {
12-
return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '')
14+
return stripProjectRoot(str)
1315
.replaceAll(/\d+:\d+/g, '*:*');
1416
}
1517

test/parallel/test-node-output-v8-warning.mjs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ function replaceExecName(str) {
1414
}
1515

1616
describe('v8 output', { concurrency: !process.env.TEST_PARALLEL }, () => {
17+
const stripProjectRoot = snapshot.transformProjectRoot('');
18+
1719
function normalize(str) {
18-
return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '')
19-
.replaceAll(/:\d+/g, ':*')
20-
.replaceAll('/', '*')
21-
.replaceAll('*test*', '*')
22-
.replaceAll(/.*?\*fixtures\*v8\*/g, '(node:*) V8: *') // Replace entire path before fixtures/v8
23-
.replaceAll('*fixtures*v8*', '*');
20+
return stripProjectRoot(str)
21+
.replaceAll(/:\d+/g, ':*')
22+
.replaceAll('/', '*')
23+
.replaceAll('*test*', '*')
24+
.replaceAll(/.*?\*fixtures\*v8\*/g, '(node:*) V8: *') // Replace entire path before fixtures/v8
25+
.replaceAll('*fixtures*v8*', '*');
2426
}
2527
const common = snapshot
2628
.transform(snapshot.replaceWindowsLineEndings, snapshot.replaceWindowsPaths, replaceNodeVersion, replaceExecName);

test/parallel/test-node-output-vm.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,14 @@ function replaceNodeVersion(str) {
88
}
99

1010
describe('vm output', { concurrency: !process.env.TEST_PARALLEL }, () => {
11+
const stripProjectRoot = snapshot.transformProjectRoot('');
12+
1113
function normalize(str) {
12-
return str.replaceAll(snapshot.replaceWindowsPaths(process.cwd()), '').replaceAll('//', '*').replaceAll(/\/(\w)/g, '*$1').replaceAll('*test*', '*').replaceAll(/node:vm:\d+:\d+/g, 'node:vm:*');
14+
return stripProjectRoot(str)
15+
.replaceAll('//', '*')
16+
.replaceAll(/\/(\w)/g, '*$1')
17+
.replaceAll('*test*', '*')
18+
.replaceAll(/node:vm:\d+:\d+/g, 'node:vm:*');
1319
}
1420

1521
const defaultTransform = snapshot

0 commit comments

Comments
 (0)