@@ -31,6 +31,8 @@ async function scan(...args) {
3131// Complete console diagnostics and truncated report excerpts from September 23–25, 2026.
3232const reliabilityFailures = JSON . parse ( readFileSync (
3333 new URL ( '../fixtures/ci-reliability-failures.json' , import . meta. url ) , 'utf8' ) ) ;
34+ const makeFailure = JSON . parse ( readFileSync (
35+ new URL ( '../fixtures/ci-resume-make-failure.json' , import . meta. url ) , 'utf8' ) ) ;
3436
3537describe ( 'Reliability report diagnostics' , ( ) => {
3638 for ( const { name, kind, filenames, log } of reliabilityFailures ) {
@@ -49,6 +51,74 @@ describe('Reliability report diagnostics', () => {
4951 }
5052} ) ;
5153
54+ describe ( 'Make recipe failure summaries' , ( ) => {
55+ const summary = makeFailure . summary ;
56+
57+ it ( 'attributes a test timeout to the test rather than the make recipe' , async ( ) => {
58+ const log = `${ makeFailure . failure } \n${ summary } \n` ;
59+ for ( const size of [ 1 , 31 , 8192 ] ) {
60+ assert . equal ( await scan ( log , [ 'Makefile' ] , size ) , undefined ) ;
61+ assert . deepEqual ( await scanFailure ( log , [ 'Makefile' , makeFailure . filename ] , size ) ,
62+ { filename : makeFailure . filename , reason : makeFailure . failure } ) ;
63+ }
64+ } ) ;
65+
66+ for ( const [ name , footer ] of [
67+ [ 'recursive make' , summary ] ,
68+ [ 'top-level make' , 'make: *** [Makefile:660: test-ci] Error 2' ] ,
69+ [ 'gmake' , 'gmake[2]: *** [Makefile:660: test-ci] Error 1' ] ,
70+ [ 'segmentation fault' , 'make[1]: *** [Makefile:660: test-ci] Segmentation fault (core dumped)' ] ,
71+ [ 'abort' , 'make[1]: *** [Makefile:660: test-ci] Aborted (core dumped)' ] ,
72+ [ 'failed recipe' , "Makefile:660: recipe for target 'test-ci' failed" ] ,
73+ [ 'prefixed stdout' , ' [out] make[1]: *** [Makefile:660: test-ci] Error 1' ] ,
74+ [ 'prefixed stderr' , " [err] Makefile:660: recipe for target 'test-ci' failed" ]
75+ ] ) {
76+ it ( `ignores a propagated exit summary: ${ name } ` , async ( ) => {
77+ for ( const size of [ 1 , 31 , 8192 ] ) {
78+ assert . equal ( await scan ( `${ footer } \n` , [ 'Makefile' ] , size ) , undefined ) ;
79+ }
80+ } ) ;
81+ }
82+
83+ for ( const [ name , log ] of [
84+ [ 'preceding error' , `error: unrelated failure\n${ summary } \n` ] ,
85+ [ 'following filesystem error' , `${ summary } \nRead-only file system\n` ] ,
86+ [ 'following C++ failure' , `${ summary } \n[ FAILED ] Example\n` ] ,
87+ [ 'following filename' , `${ summary } \nMakefile\n` ] ,
88+ [ 'TAP block' , tap ( ` ${ summary } ` ) ] ,
89+ [ 'git failure block' ,
90+ `Changes not staged for commit:\n${ summary } \nno changes added to commit\n` ]
91+ ] ) {
92+ it ( `does not use a propagated exit summary as failure context: ${ name } ` , async ( ) => {
93+ for ( const size of [ 1 , 31 , 8192 ] ) {
94+ assert . equal ( await scan ( log , [ 'Makefile' ] , size ) , undefined ) ;
95+ }
96+ } ) ;
97+ }
98+
99+ it ( 'continues to later failures and preserves the summary as output context' , async ( ) => {
100+ const failure = tap ( ` ${ summary } \n AssertionError: failure` ) ;
101+ const log = `${ summary } \n${ failure } ` ;
102+ assert . deepEqual ( await scanFailure ( log , [ 'Makefile' , filename ] , 1 ) ,
103+ { filename, reason : failure . trimEnd ( ) } ) ;
104+ } ) ;
105+
106+ for ( const [ name , reason ] of [
107+ [ 'missing separator' , 'Makefile:123: *** missing separator. Stop.' ] ,
108+ [ 'unterminated variable' , 'Makefile:123: *** unterminated variable reference. Stop.' ] ,
109+ [ 'recipe before target' , 'Makefile:123: *** recipe commences before first target. Stop.' ] ,
110+ [ 'invalid recipe' , 'Makefile:123: error: invalid recipe' ] ,
111+ [ 'unreadable makefile' , 'fatal: Unable to read Makefile: No such file or directory' ]
112+ ] ) {
113+ it ( `retains a genuine Makefile diagnostic: ${ name } ` , async ( ) => {
114+ for ( const size of [ 1 , 31 , 8192 ] ) {
115+ assert . deepEqual ( await scanFailure ( `${ reason } \n` , [ 'Makefile' ] , size ) ,
116+ { filename : 'Makefile' , reason } ) ;
117+ }
118+ } ) ;
119+ }
120+ } ) ;
121+
52122describe ( 'Streaming failure file scanner' , ( ) => {
53123 it ( 'returns the matching TAP failure without duplicating chunk overlaps' , async ( ) => {
54124 const failure = tap ( ' severity: fail\n AssertionError: expected true, received false' ) ;
0 commit comments