@@ -7,7 +7,25 @@ const assert = require('node:assert/strict');
77const { pathToFileURL } = require ( 'node:url' ) ;
88const { 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 + ) ? (?: a t \s ) ? ) (?: ( .+ ?) \s + \( ) ? (?: ( n o d e : .+ ?) : ( \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 | ^ ) ( n o d e : .+ ?) : ( \d + ) ( \n .* \n \s * \^ (?: \n | $ ) ) / g;
27+ /* eslint-enable @stylistic/js/max-len,no-control-regex */
28+
1129const 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+ // ^
2461function 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 ] n o d e : . * / 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
@@ -55,11 +92,11 @@ function replaceWarningPid(str) {
5592 return str . replaceAll ( / \( n o d e : \d + \) / g, '(node:<pid>)' ) ;
5693}
5794
95+ const projectRoot = path . resolve ( __dirname , '..' , '..' ) ;
5896// Replaces path strings representing the nodejs/node repo full project root with
5997// `<project-root>`. Also replaces file URLs containing the full project root path.
6098// The project root path may contain unicode characters.
6199function transformProjectRoot ( replacement = '<project-root>' ) {
62- const projectRoot = path . resolve ( __dirname , '../..' ) ;
63100 // Handles output already processed by `replaceWindowsPaths`.
64101 const winPath = replaceWindowsPaths ( projectRoot ) ;
65102 // Handles URL encoded project root in file URL strings as well.
@@ -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 ( / \/ \. t m p \. \d + \/ / g, '/<tmpdir>/' ) ;
116+ }
117+
76118function transform ( ...args ) {
77119 return ( str ) => args . reduce ( ( acc , fn ) => fn ( acc ) , str ) ;
78120}
@@ -142,34 +184,24 @@ function replaceTestDuration(str) {
142184 . replaceAll ( / d u r a t i o n _ m s [ 0 - 9 . ] + / g, 'duration_ms *' ) ;
143185}
144186
145- const root = path . resolve ( __dirname , '..' , '..' ) ;
146- const color = '(\\[\\d+m)' ;
147- const stackTraceBasePath = new RegExp ( `${ color } \\(${ RegExp . escape ( root ) } /?${ color } (.*)${ color } \\)` , 'g' ) ;
148-
149187function replaceSpecDuration ( str ) {
150188 return str
151189 . replaceAll ( / [ 0 - 9 . ] + m s / g, '*ms' )
152- . replaceAll ( / d u r a t i o n _ m s [ 0 - 9 . ] + / g, 'duration_ms *' )
153- . replace ( stackTraceBasePath , '$3' ) ;
190+ . replaceAll ( / d u r a t i o n _ m s [ 0 - 9 . ] + / g, 'duration_ms *' ) ;
154191}
155192
156193function replaceJunitDuration ( str ) {
157194 return str
158195 . replaceAll ( / t i m e = " [ 0 - 9 . ] + " / g, 'time="*"' )
159196 . replaceAll ( / d u r a t i o n _ m s [ 0 - 9 . ] + / g, 'duration_ms *' )
160197 . replaceAll ( `hostname="${ hostname ( ) } "` , 'hostname="HOSTNAME"' )
161- . replaceAll ( / f i l e = " [ ^ " ] * " / g, 'file="*"' )
162- . replace ( stackTraceBasePath , '$3' ) ;
198+ . replaceAll ( / f i l e = " [ ^ " ] * " / g, 'file="*"' ) ;
163199}
164200
165201function removeWindowsPathEscaping ( str ) {
166202 return common . isWindows ? str . replaceAll ( / \\ \\ / g, '\\' ) : str ;
167203}
168204
169- function replaceTestLocationLine ( str ) {
170- return str . replaceAll ( / ( j s : ) ( \d + ) ( : \d + ) / g, '$1(LINE)$3' ) ;
171- }
172-
173205// The Node test coverage returns results for all files called by the test. This
174206// will make the output file change if files like test/common/index.js change.
175207// This transform picks only the first line and then the lines from the test
@@ -188,9 +220,11 @@ function pickTestFileFromLcov(str) {
188220}
189221
190222// Transforms basic patterns like:
191- // - platform specific path and line endings,
192- // - line trailing spaces,
193- // - executable specific path and versions.
223+ // - platform specific path and line endings
224+ // - line trailing spaces
225+ // - executable specific path and versions
226+ // - project root path and tmpdir
227+ // - node internal stack frames
194228const basicTransform = transform (
195229 replaceWindowsLineEndings ,
196230 replaceTrailingSpaces ,
@@ -199,35 +233,31 @@ const basicTransform = transform(
199233 replaceNodeVersion ,
200234 generalizeExeName ,
201235 replaceWarningPid ,
236+ transformProjectRoot ( ) ,
237+ transformTmpDir ,
238+ replaceInternalStackTrace ,
202239) ;
203240
204241const defaultTransform = transform (
205242 basicTransform ,
206- replaceStackTrace ,
207- transformProjectRoot ( ) ,
208243 replaceTestDuration ,
209- replaceTestLocationLine ,
210244) ;
211245const specTransform = transform (
212246 replaceSpecDuration ,
213247 basicTransform ,
214- replaceStackTrace ,
215248) ;
216249const junitTransform = transform (
217250 replaceJunitDuration ,
218251 basicTransform ,
219- replaceStackTrace ,
220252) ;
221253const lcovTransform = transform (
222254 basicTransform ,
223- replaceStackTrace ,
224- transformProjectRoot ( ) ,
225255 pickTestFileFromLcov ,
226256) ;
227257
228258function ensureCwdIsProjectRoot ( ) {
229- if ( process . cwd ( ) !== root ) {
230- process . chdir ( root ) ;
259+ if ( process . cwd ( ) !== projectRoot ) {
260+ process . chdir ( projectRoot ) ;
231261 }
232262}
233263
@@ -240,7 +270,6 @@ module.exports = {
240270 assertSnapshot,
241271 getSnapshotPath,
242272 replaceNodeVersion,
243- replaceStackTrace,
244273 replaceInternalStackTrace,
245274 replaceWindowsLineEndings,
246275 replaceWindowsPaths,
0 commit comments