Skip to content

Commit 9f7ada0

Browse files
committed
test: unify assertSnapshot stacktrace transform
The snapshotted stack frames should hide node internal stack frames so that general node core development does not need updating the snapshot. For userland stack frames, they are highly fixture related and any fixture change should reflect in a change of the snapshot. Additionally, the line and column number are highly relevant to the correctness of the snapshot, these should not be redacted. A change in node core that affects userland stack frames should be alarming and be reflected in the snapshots. Features like test runner and source map support both should snapshot userland stack frames to ensure that userland code locations are computed correctly.
1 parent 1818bb7 commit 9f7ada0

File tree

74 files changed

+1079
-2286
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1079
-2286
lines changed

test/common/assertSnapshot.js

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,25 @@ const assert = require('node:assert/strict');
77
const { pathToFileURL } = require('node:url');
88
const { hostname } = require('node:os');
99

10-
const stackFramesRegexp = /(?<=\n)(\s+)((.+?)\s+\()?(?:\(?(.+?):(\d+)(?::(\d+))?)\)?(\s+\{)?(\[\d+m)?(\n|$)/g;
10+
/* eslint-disable @stylistic/js/max-len,no-control-regex */
11+
/**
12+
* Group 1: Line start (including color codes and escapes)
13+
* Group 2: Function name
14+
* Group 3: Filename
15+
* Group 4: Line number
16+
* Group 5: Column number
17+
* Group 6: Line end (including color codes and `{` which indicates the start of an error object details)
18+
*/
19+
// Mappings: (g1 ) (g2 ) (g3 ) (g4 ) (g5 ) (g6 )
20+
const internalStackFramesRegexp = /(?<=\n)(\s*(?:\x1b?\[\d+m\s+)?(?:at\s)?)(?:(.+?)\s+\()?(?:(node:.+?):(\d+)(?::(\d+))?)\)?((?:\x1b?\[\d+m)?\s*{?\n|$)/g;
21+
/**
22+
* Group 1: Filename
23+
* Group 2: Line number
24+
* Group 3: Line end and source code line
25+
*/
26+
const internalErrorSourceLines = /(?<=\n|^)(node:.+?):(\d+)(\n.*\n\s*\^(?:\n|$))/g;
27+
/* eslint-enable @stylistic/js/max-len,no-control-regex */
28+
1129
const windowNewlineRegexp = /\r/g;
1230

1331
// Replaces the current Node.js executable version strings with a
@@ -17,14 +35,33 @@ function replaceNodeVersion(str) {
1735
return str.replaceAll(process.version, '<node-version>');
1836
}
1937

20-
function replaceStackTrace(str, replacement = '$1*$7$8\n') {
21-
return str.replace(stackFramesRegexp, replacement);
38+
// Collapse consecutive identical lines containing the keyword into
39+
// one single line. The `str` should have been processed by `replaceWindowsLineEndings`.
40+
function foldIdenticalLines(str, keyword) {
41+
const lines = str.split('\n');
42+
const folded = lines.filter((line, idx) => {
43+
if (idx === 0) {
44+
return true;
45+
}
46+
if (line.includes(keyword) && line === lines[idx - 1]) {
47+
return false;
48+
}
49+
return true;
50+
});
51+
return folded.join('\n');
2252
}
2353

54+
const kInternalFrame = '<node-internal-frames>';
55+
// Replace non-internal frame `at TracingChannel.traceSync (node:diagnostics_channel:328:14)`
56+
// as well as `at node:internal/main/run_main_module:33:47` with `at <node-internal-frames>`.
57+
// Also replaces error source line like:
58+
// node:internal/mod.js:44
59+
// throw err;
60+
// ^
2461
function replaceInternalStackTrace(str) {
25-
// Replace non-internal frame `at TracingChannel.traceSync (node:diagnostics_channel:328:14)`
26-
// as well as `at node:internal/main/run_main_module:33:47` with `*`.
27-
return str.replaceAll(/(\W+).*[(\s]node:.*/g, '$1*');
62+
const result = str.replaceAll(internalErrorSourceLines, `$1:<line>$3`)
63+
.replaceAll(internalStackFramesRegexp, `$1${kInternalFrame}$6`);
64+
return foldIdenticalLines(result, kInternalFrame);
2865
}
2966

3067
// Replaces Windows line endings with posix line endings for unified snapshots
@@ -73,6 +110,11 @@ function transformProjectRoot(replacement = '<project-root>') {
73110
};
74111
}
75112

113+
// Replaces tmpdirs created by `test/common/tmpdir.js`.
114+
function transformTmpDir(str) {
115+
return str.replaceAll(/\/\.tmp\.\d+\//g, '/<tmpdir>/');
116+
}
117+
76118
function transform(...args) {
77119
return (str) => args.reduce((acc, fn) => fn(acc), str);
78120
}
@@ -143,33 +185,24 @@ function replaceTestDuration(str) {
143185
}
144186

145187
const root = path.resolve(__dirname, '..', '..');
146-
const color = '(\\[\\d+m)';
147-
const stackTraceBasePath = new RegExp(`${color}\\(${RegExp.escape(root)}/?${color}(.*)${color}\\)`, 'g');
148-
149188
function replaceSpecDuration(str) {
150189
return str
151190
.replaceAll(/[0-9.]+ms/g, '*ms')
152-
.replaceAll(/duration_ms [0-9.]+/g, 'duration_ms *')
153-
.replace(stackTraceBasePath, '$3');
191+
.replaceAll(/duration_ms [0-9.]+/g, 'duration_ms *');
154192
}
155193

156194
function replaceJunitDuration(str) {
157195
return str
158196
.replaceAll(/time="[0-9.]+"/g, 'time="*"')
159197
.replaceAll(/duration_ms [0-9.]+/g, 'duration_ms *')
160198
.replaceAll(`hostname="${hostname()}"`, 'hostname="HOSTNAME"')
161-
.replaceAll(/file="[^"]*"/g, 'file="*"')
162-
.replace(stackTraceBasePath, '$3');
199+
.replaceAll(/file="[^"]*"/g, 'file="*"');
163200
}
164201

165202
function removeWindowsPathEscaping(str) {
166203
return common.isWindows ? str.replaceAll(/\\\\/g, '\\') : str;
167204
}
168205

169-
function replaceTestLocationLine(str) {
170-
return str.replaceAll(/(js:)(\d+)(:\d+)/g, '$1(LINE)$3');
171-
}
172-
173206
// The Node test coverage returns results for all files called by the test. This
174207
// will make the output file change if files like test/common/index.js change.
175208
// This transform picks only the first line and then the lines from the test
@@ -188,9 +221,11 @@ function pickTestFileFromLcov(str) {
188221
}
189222

190223
// Transforms basic patterns like:
191-
// - platform specific path and line endings,
192-
// - line trailing spaces,
193-
// - executable specific path and versions.
224+
// - platform specific path and line endings
225+
// - line trailing spaces
226+
// - executable specific path and versions
227+
// - project root path and tmpdir
228+
// - node internal stack frames
194229
const basicTransform = transform(
195230
replaceWindowsLineEndings,
196231
replaceTrailingSpaces,
@@ -199,29 +234,25 @@ const basicTransform = transform(
199234
replaceNodeVersion,
200235
generalizeExeName,
201236
replaceWarningPid,
237+
transformProjectRoot(),
238+
transformTmpDir,
239+
replaceInternalStackTrace,
202240
);
203241

204242
const defaultTransform = transform(
205243
basicTransform,
206-
replaceStackTrace,
207-
transformProjectRoot(),
208244
replaceTestDuration,
209-
replaceTestLocationLine,
210245
);
211246
const specTransform = transform(
212247
replaceSpecDuration,
213248
basicTransform,
214-
replaceStackTrace,
215249
);
216250
const junitTransform = transform(
217251
replaceJunitDuration,
218252
basicTransform,
219-
replaceStackTrace,
220253
);
221254
const lcovTransform = transform(
222255
basicTransform,
223-
replaceStackTrace,
224-
transformProjectRoot(),
225256
pickTestFileFromLcov,
226257
);
227258

@@ -240,7 +271,6 @@ module.exports = {
240271
assertSnapshot,
241272
getSnapshotPath,
242273
replaceNodeVersion,
243-
replaceStackTrace,
244274
replaceInternalStackTrace,
245275
replaceWindowsLineEndings,
246276
replaceWindowsPaths,
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
11
Trace: foo
2-
at *
3-
at *
4-
at *
5-
at *
6-
at *
7-
at *
8-
at *
9-
at *
2+
at Object.<anonymous> (<project-root>/test/fixtures/console/console.js:5:9)
3+
at <node-internal-frames>

test/fixtures/console/stack_overflow.snapshot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
before
2-
<project-root>/test/fixtures/console/stack_overflow.js:*
2+
<project-root>/test/fixtures/console/stack_overflow.js:39
33
JSON.stringify(array);
44
^
55

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Error: test
22
at one (<project-root>/test/fixtures/async-error.js:4:9)
33
at two (<project-root>/test/fixtures/async-error.js:17:9)
4-
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
4+
at <node-internal-frames>
55
at async three (<project-root>/test/fixtures/async-error.js:20:3)
66
at async four (<project-root>/test/fixtures/async-error.js:24:3)
77
at async main (<project-root>/test/fixtures/errors/async_error_nexttick_main.js:7:5)
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
node:punycode:54
1+
node:punycode:<line>
22
throw new RangeError(errors[type]);
33
^
44

55
RangeError: Invalid input
6-
at error (node:punycode:54:8)
7-
at Object.decode (node:punycode:247:5)
6+
at <node-internal-frames>
87
at Object.<anonymous> (<project-root>/test/fixtures/errors/core_line_numbers.js:13:10)
98

109
Node.js <node-version>

test/fixtures/errors/error_aggregateTwoErrors.snapshot

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:*
1+
<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:15
22
throw aggregateTwoErrors(err, originalError);
33
^
44

55
AggregateError: original
6-
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:*:*) {
6+
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:15:7) {
77
code: 'ERR0',
88
[errors]: [
99
Error: original
10-
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:*:*) {
10+
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:9:23) {
1111
code: 'ERR0'
1212
},
1313
Error: second error
14-
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:*:*) {
14+
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_aggregateTwoErrors.js:10:13) {
1515
code: 'ERR1'
1616
}
1717
]

test/fixtures/errors/error_exit.snapshot

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
Exiting with code=1
2-
node:internal/assert/utils:*
2+
node:internal/assert/utils:<line>
33
throw error;
44
^
55

66
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
77

88
1 !== 2
99

10-
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_exit.js:*:*) {
10+
at Object.<anonymous> (<project-root>/test/fixtures/errors/error_exit.js:32:8) {
1111
generatedMessage: true,
1212
code: 'ERR_ASSERTION',
1313
actual: 1,
1 Byte
Binary file not shown.
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
node:events:*
1+
node:events:<line>
22
throw er; // Unhandled 'error' event
33
^
44

55
Error: foo:bar
6-
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:*:*)
7-
at foo (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:*:*)
6+
at bar (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:9:12)
7+
at foo (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:12:10)
88
Emitted 'error' event at:
9-
at quux (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:*:*)
10-
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:*:*)
9+
at quux (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:19:6)
10+
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_common_trace.js:22:1)
1111

1212
Node.js <node-version>
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
node:events:*
1+
node:events:<line>
22
throw er; // Unhandled 'error' event
33
^
44

55
Error
6-
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:*:*)
6+
at Object.<anonymous> (<project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:6:12)
77
Emitted 'error' event at:
8-
at <project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:*:*
8+
at <project-root>/test/fixtures/errors/events_unhandled_error_nexttick.js:8:22
99

1010
Node.js <node-version>

0 commit comments

Comments
 (0)