What happens
bashunit::cleanup_script_temp_files forks rm -rf <glob> once per test file, unconditionally, even when nothing matches. Removing that fork — the obvious follow-up to #1269, worth about 3.4ms per file — makes the final total-runtime calculation fail:
src/util/math.sh: line 7: echo: write error: Broken pipe
src/util/math.sh: cannot duplicate fd 3 to fd 0: Bad file descriptor
Time taken: 0ms
Every test still passes and the exit code is 0. Only the reported duration is wrong, so it fails silently.
Reproduction
Apply the marker probe from #1269 to the script-level twin:
functionbashunit::cleanup_script_temp_files() {
if [ -n"${BASHUNIT_CURRENT_SCRIPT_ID:-}" ];thenif [ !-e"$BASHUNIT_TEMP_DIR/${BASHUNIT_CURRENT_SCRIPT_ID}_.mark" ];thenreturn 0
fi
rm -rf "$BASHUNIT_TEMP_DIR/${BASHUNIT_CURRENT_SCRIPT_ID}"_*fi
}Then run a directory of trivial test files, --no-parallel. Threshold is between 20 and 60 files; at 60+ it reproduces every time.
What it is not
- Not the early
return — putting any fork on the skip path (rm -rf "$BASHUNIT_TEMP_DIR/.__no_such_probe__") makes it go away, and the run is correct again at 3.46s. - Not baseline behaviour — current
main is clean at 60, 100 and 400 files. - Not reproducible standalone:
exec 3>&1; exec 3>&-; echo "1+1" | bc works on both bash 3.2 and 5.3.
So it depends on the fd state bashunit itself leaves behind. run_test is the only place that touches fd 3 (exec 3>&1 at entry, exec 3>&- before the duration block), and the failing call is the echo | bc in bashunit::math::calculate, where bash picks fd 3 to stage the pipe.
Why it matters
Two reasons, beyond the blocked optimisation:
Time taken can be silently wrong. A user sees 0ms for a real run and has no indication anything failed.bashunit::math::calculate is used elsewhere (coverage percentages). If a pipe can fail this way here, it can fail there — and a wrong percentage is worse than a wrong clock.
What happens
bashunit::cleanup_script_temp_filesforksrm -rf <glob>once per test file, unconditionally, even when nothing matches. Removing that fork — the obvious follow-up to #1269, worth about 3.4ms per file — makes the final total-runtime calculation fail:Every test still passes and the exit code is 0. Only the reported duration is wrong, so it fails silently.
Reproduction
Apply the marker probe from #1269 to the script-level twin:
Then run a directory of trivial test files,
--no-parallel. Threshold is between 20 and 60 files; at 60+ it reproduces every time.What it is not
return— putting any fork on the skip path (rm -rf "$BASHUNIT_TEMP_DIR/.__no_such_probe__") makes it go away, and the run is correct again at 3.46s.mainis clean at 60, 100 and 400 files.exec 3>&1; exec 3>&-; echo "1+1" | bcworks on both bash 3.2 and 5.3.So it depends on the fd state bashunit itself leaves behind.
run_testis the only place that touches fd 3 (exec 3>&1at entry,exec 3>&-before the duration block), and the failing call is theecho | bcinbashunit::math::calculate, where bash picks fd 3 to stage the pipe.Why it matters
Two reasons, beyond the blocked optimisation:
Time takencan be silently wrong. A user sees0msfor a real run and has no indication anything failed.bashunit::math::calculateis used elsewhere (coverage percentages). If a pipe can fail this way here, it can fail there — and a wrong percentage is worse than a wrong clock.