Skip to content

Commit 011ce7b

Browse files
authored
chore: improve parallel mocha output (#7207)
This adds a global counter next to the local error numbers as well as adding the file name next to the failure counter of each file. In addition, it accounts to crashes as failed tests instead of getting a confused output.
1 parent 4f81eaf commit 011ce7b

1 file changed

Lines changed: 51 additions & 14 deletions

File tree

scripts/mocha-parallel-files.js

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)