Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Performance
- Faster runtime-error detection: single `case` glob instead of 23-iteration loop in `detect_runtime_error` (#668)
- Hot-path coverage flag now cached in `_BASHUNIT_COVERAGE_ON`, removing a function dispatch per call (#664)
- Parallel runner blocks on `wait -n` on Bash 4.3+ instead of polling `jobs -r`, removing sleep-induced slot-release latency (#667)

## [0.36.0](https://github.com/TypedDevs/bashunit/compare/0.35.0...0.36.0) - 2026-05-07

Expand Down
25 changes: 23 additions & 2 deletions src/runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,35 @@ function bashunit::runner::print_verbose_test_summary() {
printf '%*s\n' "$TERMINAL_WIDTH" '' | tr ' ' '-'
}

# Returns 0 when this Bash supports `wait -n` (Bash 4.3+), 1 otherwise.
function bashunit::runner::_supports_wait_n() {
local major="${BASH_VERSINFO[0]:-0}"
local minor="${BASH_VERSINFO[1]:-0}"
if [ "$major" -gt 4 ]; then
return 0
fi
if [ "$major" -eq 4 ] && [ "$minor" -ge 3 ]; then
return 0
fi
return 1
}

function bashunit::runner::wait_for_job_slot() {
local max_jobs="${BASHUNIT_PARALLEL_JOBS:-0}"
if [ "$max_jobs" -le 0 ]; then
return 0
fi

# Adaptive backoff: start at 50ms, grow to 200ms to reduce `jobs -r` overhead
# on long-running tests while keeping short tests responsive.
if bashunit::runner::_supports_wait_n; then
# Bash 4.3+: block until any child exits. No polling, no sleep latency.
while [ "$(jobs -r | wc -l)" -ge "$max_jobs" ]; do
wait -n 2>/dev/null || break
done
return 0
fi

# Bash 3.x fallback: adaptive poll starting at 50ms, growing to 200ms to
# reduce `jobs -r` overhead on long-running tests while staying responsive.
local delay="0.05"
local iterations=0
while true; do
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/parallel_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,29 @@ function test_wait_for_job_slot_returns_immediately_when_under_limit() {
assert_successful_code "$?"
}

function test_supports_wait_n_matches_running_bash_version() {
local major="${BASH_VERSINFO[0]:-0}"
local minor="${BASH_VERSINFO[1]:-0}"
local expected_rc=1
if [ "$major" -gt 4 ] || { [ "$major" -eq 4 ] && [ "$minor" -ge 3 ]; }; then
expected_rc=0
fi

bashunit::runner::_supports_wait_n
assert_same "$expected_rc" "$?"
}

function test_wait_for_job_slot_releases_when_background_job_finishes() {
export BASHUNIT_PARALLEL_JOBS=1

# Launch a short-lived job, occupy the only slot, then call wait_for_job_slot.
# On Bash 4.3+ this exercises the `wait -n` path; on Bash 3.x the poll path.
(sleep 0.1) &
bashunit::runner::wait_for_job_slot

assert_successful_code "$?"
}

# === is_enabled tests ===

function test_parallel_enabled_on_windows() {
Expand Down
Loading