@@ -242,9 +242,9 @@ async function main () {
242242 code : /** @type {number|null } */ ( null ) ,
243243 signal : /** @type {NodeJS.Signals|null } */ ( null ) ,
244244
245- // Output buffers (in-memory)
246- stdoutBuf : /** @type { string[] } */ ( [ ] ) ,
247- stderrWarnBuf : /** @type {string[] } */ ( [ ] ) ,
245+ // Output buffers (in-memory). For non-active entries, preserve stdout/stderr warning ordering
246+ // by buffering a merged stream with per-line ordering.
247+ outBuf : /** @type {{stream:'stdout'|'stderr', text: string} [] } */ ( [ ] ) ,
248248 stderrErrBuf : /** @type {string[] } */ ( [ ] ) ,
249249 failureBuf : /** @type {string[] } */ ( [ ] ) ,
250250
@@ -262,13 +262,12 @@ async function main () {
262262 const entry = entries [ activeIndex ]
263263 if ( ! entry ) return
264264
265- if ( entry . stderrWarnBuf . length ) {
266- process . stderr . write ( entry . stderrWarnBuf . join ( '' ) )
267- entry . stderrWarnBuf . length = 0
268- }
269- if ( entry . stdoutBuf . length ) {
270- process . stdout . write ( entry . stdoutBuf . join ( '' ) )
271- entry . stdoutBuf . length = 0
265+ if ( entry . outBuf . length ) {
266+ for ( const { stream, text } of entry . outBuf ) {
267+ if ( stream === 'stderr' ) process . stderr . write ( text )
268+ else process . stdout . write ( text )
269+ }
270+ entry . outBuf . length = 0
272271 }
273272 }
274273
@@ -295,7 +294,7 @@ async function main () {
295294 entry . failureBuf . push ( line )
296295 } else {
297296 if ( isActive ( entryIndex ) ) process . stdout . write ( line )
298- else entry . stdoutBuf . push ( line )
297+ else entry . outBuf . push ( { stream : 'stdout' , text : line } )
299298 }
300299 }
301300
@@ -310,7 +309,7 @@ async function main () {
310309 const entry = entries [ entryIndex ]
311310 if ( isWarningLine ( line ) ) {
312311 if ( isActive ( entryIndex ) ) process . stderr . write ( line )
313- else entry . stderrWarnBuf . push ( line )
312+ else entry . outBuf . push ( { stream : 'stderr' , text : line } )
314313 } else {
315314 entry . stderrErrBuf . push ( line )
316315 }
@@ -454,6 +453,7 @@ async function main () {
454453 flushActiveBuffers ( )
455454
456455 // Print buffered error output (non-warning stderr + mocha failure blocks) after all output has been streamed.
456+ let globalFailureIndex = 0
457457 let hasConsolidatedErrors = false
458458 for ( const entry of entries ) {
459459 const stderrErrors = entry . stderrErrBuf . join ( '' ) . trim ( )
@@ -471,7 +471,32 @@ async function main () {
471471 if ( last && ! last . endsWith ( '\n' ) ) process . stdout . write ( '\n' )
472472 }
473473 if ( hasFailures ) {
474- process . stdout . write ( entry . failureBuf . join ( '' ) )
474+ let appendedFilenameToFailingLine = false
475+
476+ for ( const line of entry . failureBuf ) {
477+ let out = line
478+
479+ // Print `n failing in <file>` while ensuring the appended filename stays uncolored.
480+ if ( ! appendedFilenameToFailingLine && isFailureStartLine ( out ) ) {
481+ appendedFilenameToFailingLine = true
482+
483+ const hasNewline = out . endsWith ( '\n' )
484+ const base = hasNewline ? out . slice ( 0 , - 1 ) : out
485+ // Ensure `in <file>` is not red by resetting ANSI styles before printing the filename.
486+ // Avoid double-resetting if the line already ends with a reset.
487+ const reset = '\u001b[0m'
488+ out = ( base . endsWith ( reset ) ? base : base + reset ) + ' in ' + entry . file + ( hasNewline ? '\n' : '' )
489+ }
490+
491+ // Prefix local `n)` failure lines with a deterministic global counter `[n]`.
492+ if ( / ^ \s * \d + \) / . test ( stripAnsi ( out ) ) ) {
493+ globalFailureIndex ++
494+ out = `[${ globalFailureIndex } ] ` + out
495+ }
496+
497+ process . stdout . write ( out )
498+ }
499+
475500 const last = entry . failureBuf [ entry . failureBuf . length - 1 ]
476501 if ( last && ! last . endsWith ( '\n' ) ) process . stdout . write ( '\n' )
477502 }
@@ -490,8 +515,16 @@ async function main () {
490515 let totalPending = 0
491516 let totalTests = 0
492517 let totalDuration = 0
518+ let crashedFiles = 0
493519
494520 for ( const entry of entries ) {
521+ // If a child exited non-zero but never reported mocha stats (or reported 0 failures),
522+ // treat it as a "crash/harness failure" so summary reflects failure even when Mocha
523+ // couldn't produce a failing test count (e.g., hard crash, early process.exit()).
524+ if ( ( entry . code || entry . signal ) && ( ! entry . stats || ( entry . stats . failures || 0 ) === 0 ) ) {
525+ crashedFiles ++
526+ }
527+
495528 const result = entry . stats
496529 if ( ! result ) continue
497530 totalPasses += result . passes || 0
@@ -503,16 +536,20 @@ async function main () {
503536
504537 process . stdout . write ( '\n=== Summary ===\n' )
505538 process . stdout . write ( `Passed: ${ totalPasses } \n` )
506- process . stdout . write ( `Failed: ${ totalFailures } \n` )
539+ process . stdout . write ( `Failed: ${ totalFailures + crashedFiles } \n` )
507540 process . stdout . write ( `Pending: ${ totalPending } \n` )
508541 process . stdout . write ( `Total: ${ totalTests } \n` )
509542 process . stdout . write ( `Duration(ms): ${ totalDuration } \n` )
543+ if ( crashedFiles ) process . stdout . write ( `Crashed files: ${ crashedFiles } \n` )
510544
511545 if ( failed . length ) {
512546 process . stdout . write ( '\n=== Failed files ===\n' )
513547 for ( const { file, code, signal } of failed ) {
514548 process . stdout . write ( `- ${ file } (exit=${ code ?? 'null' } signal=${ signal ?? 'null' } )\n` )
515549 }
550+ process . stdout . write (
551+ 'Legend: [n] = global failure index across all files; n) = local failure index within a file.\n'
552+ )
516553 }
517554
518555 process . exit ( failures ? 1 : 0 )
0 commit comments