@@ -14,6 +14,25 @@ const infrastructure = createMatcher(FAILURE_PATTERNS.infrastructure);
1414const gitStart = createMatcher ( FAILURE_MARKERS . git . map ( ( { start } ) => start ) ) ;
1515const gitEnd = createMatcher ( FAILURE_MARKERS . git . map ( ( { end } ) => end ) ) ;
1616
17+ // Keep both ends of large diagnostics without retaining whole lines or TAP blocks.
18+ class FailureExcerpt {
19+ head = '' ;
20+ tail = '' ;
21+ length = 0 ;
22+
23+ append ( text ) {
24+ const remaining = Math . max ( 0 , 4096 - this . head . length ) ;
25+ this . head += text . slice ( 0 , remaining ) ;
26+ this . tail = ( this . tail + text . slice ( remaining ) ) . slice ( - 4096 ) ;
27+ this . length += text . length ;
28+ }
29+
30+ toString ( ) {
31+ const omitted = this . length > 8192 ? '\n[... failure output truncated ...]\n' : '' ;
32+ return this . head + omitted + this . tail ;
33+ }
34+ }
35+
1736function fileAliases ( filename ) {
1837 const paths = new Set ( [ filename ] ) ;
1938 if ( filename . startsWith ( 'test/' ) ) {
@@ -35,15 +54,16 @@ async function * logWindows(source, overlap) {
3554
3655 function * consume ( raw ) {
3756 raw = raw . replace ( / \r / g, '' ) ;
38- let text = raw . replace ( / \\ + / g, '/' ) ;
39- if ( afterBackslash && raw . startsWith ( '\\' ) ) text = text . slice ( 1 ) ;
40- if ( raw ) afterBackslash = raw . endsWith ( '\\' ) ;
41- for ( let offset = 0 ; offset < text . length ; ) {
42- const newline = text . indexOf ( '\n' , offset ) ;
43- const end = Math . min ( offset + 8192 , newline < 0 ? text . length : newline + 1 ) ;
44- const window = tail + text . slice ( offset , end ) ;
57+ for ( let offset = 0 ; offset < raw . length ; ) {
58+ const newline = raw . indexOf ( '\n' , offset ) ;
59+ const end = Math . min ( offset + 8192 , newline < 0 ? raw . length : newline + 1 ) ;
60+ const content = raw . slice ( offset , end ) ;
61+ let normalized = content . replace ( / \\ + / g, '/' ) ;
62+ if ( afterBackslash && content . startsWith ( '\\' ) ) normalized = normalized . slice ( 1 ) ;
63+ afterBackslash = content . endsWith ( '\\' ) ;
64+ const window = tail + normalized ;
4565 const lineEnd = end === newline + 1 ;
46- yield { text : window , lineStart, lineEnd } ;
66+ yield { text : window , content , lineStart, lineEnd } ;
4767 if ( lineEnd ) {
4868 tail = '' ;
4969 lineStart = true ;
@@ -57,14 +77,15 @@ async function * logWindows(source, overlap) {
5777
5878 for await ( const chunk of source ) yield * consume ( decoder . write ( chunk ) ) ;
5979 yield * consume ( decoder . end ( ) ) ;
60- if ( tail ) yield { text : `${ tail } \n` , lineStart, lineEnd : true } ;
80+ if ( tail ) yield { text : `${ tail } \n` , content : '\n' , lineStart, lineEnd : true } ;
6181}
6282
63- // Each line is reduced to a filename and failure markers. Neither a giant line
64- // nor a giant TAP block needs to survive in memory.
83+ // Retain failure markers and a bounded excerpt for each line.
6584async function * failureLines ( windows , matchFile ) {
6685 let line = { } ;
67- for await ( const { text, lineStart, lineEnd } of windows ) {
86+ let excerpt = new FailureExcerpt ( ) ;
87+ for await ( const { text, content, lineStart, lineEnd } of windows ) {
88+ excerpt . append ( content ) ;
6889 line . file ??= matchFile ( text , lineStart ) ;
6990 line . diagnostic ||= diagnostic ( text ) ;
7091 line . infrastructure ||= infrastructure ( text ) ;
@@ -75,53 +96,71 @@ async function * failureLines(windows, matchFile) {
7596 line . gitStart ||= gitStart ( text ) ;
7697 line . gitEnd ||= gitEnd ( text ) ;
7798 if ( lineEnd ) {
99+ line . text = excerpt . toString ( ) ;
78100 yield line ;
79101 line = { } ;
102+ excerpt = new FailureExcerpt ( ) ;
80103 }
81104 }
82105}
83106
84107async function findFailure ( lines ) {
85108 let history = [ ] ;
86109 let followingLines = 0 ;
110+ let diagnosticExcerpt ;
87111 let tap = null ;
88112 let git = null ;
89113
90114 for await ( const line of lines ) {
91115 const precedingLines = history ;
92- history = [ ...history , line . file ] . slice ( - 5 ) ;
116+ history = [ ...history , line ] . slice ( - 5 ) ;
93117 if ( line . tapStart ) {
94- tap = { } ;
118+ tap = { excerpt : new FailureExcerpt ( ) } ;
95119 followingLines = 0 ;
96120 }
97121 if ( tap ) {
122+ tap . excerpt . append ( line . text ) ;
98123 tap . file ??= line . file ;
99124 tap . todo ||= line . todo ;
100125 // A later TODO can mark this as an expected failure; wait for the ending.
101126 if ( line . tapEnd ) {
102- if ( ! tap . todo && tap . file ) return tap . file ;
127+ if ( ! tap . todo && tap . file ) {
128+ return { filename : tap . file , reason : tap . excerpt . toString ( ) . trimEnd ( ) } ;
129+ }
103130 tap = null ;
104131 }
105132 continue ;
106133 }
107134
108- if ( line . gitStart ) git = { } ;
135+ if ( line . gitStart ) git = { excerpt : new FailureExcerpt ( ) } ;
109136 if ( git ) {
137+ git . excerpt . append ( line . text ) ;
110138 git . file ??= line . file ;
111139 if ( line . gitEnd ) {
112- if ( git . file ) return git . file ;
140+ if ( git . file ) return { filename : git . file , reason : git . excerpt . toString ( ) . trimEnd ( ) } ;
113141 git = null ;
114142 }
115143 }
116144 if ( line . infrastructure || line . cpp ) {
117145 const before = line . cpp ? 5 : 1 ;
118- const file = line . file || precedingLines . slice ( - before ) . find ( Boolean ) ;
119- if ( file ) return file ;
146+ const context = [ ...precedingLines . slice ( - before ) , line ] ;
147+ const filename = line . file || context . find ( previous => previous . file ) ?. file ;
148+ if ( filename ) {
149+ const excerpt = new FailureExcerpt ( ) ;
150+ for ( const previous of context ) excerpt . append ( previous . text ) ;
151+ return { filename, reason : excerpt . toString ( ) . trimEnd ( ) } ;
152+ }
153+ }
154+ if ( line . diagnostic ) {
155+ followingLines = 6 ;
156+ diagnosticExcerpt = new FailureExcerpt ( ) ;
120157 }
121- if ( line . diagnostic ) followingLines = 6 ;
122158 if ( followingLines > 0 ) {
123159 followingLines -- ;
124- if ( line . file ) return line . file ;
160+ diagnosticExcerpt . append ( line . text ) ;
161+ if ( line . file ) {
162+ return { filename : line . file , reason : diagnosticExcerpt . toString ( ) . trimEnd ( ) } ;
163+ }
125164 }
126165 }
127166}
0 commit comments