Problem
Three symptoms, one cause: the parallel path prints for a human without checking the stream.
1. --stop-on-failure corrupts a machine --output (correctness)
$ bashunit --parallel --stop-on-failure --output json t_test.sh^MStop on failure enabled...{The document does not parse. Under --output junit the same line displaces the XML declaration. Stop on failure enabled... is human output, but its branch in src/main/run.sh has no machine-output guard — unlike the block immediately below it in the same function.
test_output_junit_starts_with_the_xml_declaration_under_parallel exists for precisely this hazard, but does not pass --stop-on-failure, so it stayed green.
2. A stray \r \r in every piped run
The spinner is erased after aggregation, but it only draws on a terminal, and also skips --no-progress. The erase in src/runner/discovery.sh was gated only on machine output, so those bytes went into every piped run — which is every CI log.
3. The "no result files" notice runs into the totals
src/state/parallel.sh printed it without a trailing newline, so an empty parallel run rendered No tests found Tests: 0 total where the sequential run rendered just the totals line.
Why none of this was caught
Five snapshots capture Stop on failure enabled..., and snapshot comparison strips carriage returns (src/assert/snapshot.sh:8), so the leading \r was invisible to all of them. The missing machine-output guard was invisible because no test combines that flag with --output.
Fix
Guard the message like its neighbour, drop its now-redundant \r (the spinner is already erased where it was drawn), newline-terminate the notice, and make the erase check the same conditions the spinner checks before drawing — with a comment saying they must stay matched.
Problem
Three symptoms, one cause: the parallel path prints for a human without checking the stream.
1.
--stop-on-failurecorrupts a machine--output(correctness)The document does not parse. Under
--output junitthe same line displaces the XML declaration.Stop on failure enabled...is human output, but its branch insrc/main/run.shhas no machine-output guard — unlike the block immediately below it in the same function.test_output_junit_starts_with_the_xml_declaration_under_parallelexists for precisely this hazard, but does not pass--stop-on-failure, so it stayed green.2. A stray
\r \rin every piped runThe spinner is erased after aggregation, but it only draws on a terminal, and also skips
--no-progress. The erase insrc/runner/discovery.shwas gated only on machine output, so those bytes went into every piped run — which is every CI log.3. The "no result files" notice runs into the totals
src/state/parallel.shprinted it without a trailing newline, so an empty parallel run renderedNo tests found Tests: 0 totalwhere the sequential run rendered just the totals line.Why none of this was caught
Five snapshots capture
Stop on failure enabled..., and snapshot comparison strips carriage returns (src/assert/snapshot.sh:8), so the leading\rwas invisible to all of them. The missing machine-output guard was invisible because no test combines that flag with--output.Fix
Guard the message like its neighbour, drop its now-redundant
\r(the spinner is already erased where it was drawn), newline-terminate the notice, and make the erase check the same conditions the spinner checks before drawing — with a comment saying they must stay matched.