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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
## Unreleased

### Fixed
- Coverage propagates execution counts across multiline array literals, quoted strings, heredocs and backslash continuations, including when Bash 3.x records an array assignment on its closing `)`. Parent statement hits no longer mark commands inside command or process substitutions as covered, including within quotes or arrays (#1338)
- `--exclude-filter` treats a comma in one flag value as part of the test function name. Repeat the flag to exclude several names; the comma-separated `BASHUNIT_EXCLUDE_FILTER` cannot express a literal comma within one configured filter (#1340)
- Coverage counts every line of a multiline statement: array literals, quoted strings, heredocs and backslash continuations, including the array assignment Bash 3.x records on its closing `)`. A parent statement's hits no longer mark commands inside command or process substitutions as covered, even within quotes or arrays (#1338)
- `--exclude-filter` keeps a comma as part of the test function name. Repeat the flag to exclude several names. `BASHUNIT_EXCLUDE_FILTER` stays comma-separated, so one of its filters cannot hold a literal comma (#1340)
- A `--parallel` run no longer replays bash's `child setpgid` warning as the stderr of a file that wrote nothing. Job control puts each test in its own process group, and bash's parent-side `setpgid` loses a harmless race with a child that already exec'd. The child sets the group first, so only the message was ever wrong (#1344)
- A timed-out test finishes its `tear_down`. The watchdog allowed a flat 0.3s between SIGTERM and SIGKILL, which a loaded machine missed in about one parallel full-suite run in three. It now polls for the body to exit, capped at 2s (#1344)

## [0.50.1](https://github.com/TypedDevs/bashunit/compare/0.50.0...0.50.1) - 2026-08-22

Expand Down
12 changes: 11 additions & 1 deletion src/console/test_line.sh
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,21 @@ function bashunit::console_results::print_worker_stderr() {
local test_file="$1"
local stderr_file="$2"

# Not the file's output: the worker turns on job control so each test is its
# own process group, and bash's parent-side setpgid loses a harmless race
# against a child that already exec'd. The child set the group itself before
# exec, so the group is right and only the diagnostic is wrong -- but bash
# reports every errno except ESRCH, and macOS answers EPERM.
local noise='child setpgid ('

# Nothing but noise means no block at all, so a clean run stays quiet.
grep -qv "$noise" "$stderr_file" || return 0

# To stderr, which is where this text came from: on stdout it landed ahead of
# the document `--output json|junit` promises that stream is, so a worker that
# wrote anything to stderr -- a failing `set_up`, for one -- made the report
# unparseable.
printf "\n%sStderr from %s%s\n" \
"$_BASHUNIT_COLOR_SKIPPED" "$test_file" "$_BASHUNIT_COLOR_DEFAULT" >&2
sed 's/^/|/' "$stderr_file" >&2
grep -v "$noise" "$stderr_file" | sed 's/^/|/' >&2
}
13 changes: 12 additions & 1 deletion src/runner/exec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,18 @@ function bashunit::runner::run_with_timeout() {
kill -0 "$test_pid" 2>/dev/null || exit 0
: >"$marker_file"
kill -TERM -"$test_pid" 2>/dev/null
sleep 0.3
# Poll for the body to go instead of sleeping a flat window. The TERM has to
# kill whatever the body blocked on, wake bash, reach the EXIT trap and run
# tear_down; a flat 0.3s was a scheduling hiccup, not a grace, and on a
# loaded machine the body had not reached tear_down when the SIGKILL landed.
# Bounded at 2s so a hook that hangs cannot outlive the run, and the common
# case leaves as soon as the body is reaped -- sooner than the old sleep.
grace_ticks=0
while [ "$grace_ticks" -lt 20 ]; do
kill -0 "$test_pid" 2>/dev/null || break
sleep 0.1
grace_ticks=$((grace_ticks + 1))
done
kill -KILL -"$test_pid" 2>/dev/null
) </dev/null >/dev/null 2>&1 &
local watchdog_pid=$!
Expand Down
23 changes: 23 additions & 0 deletions tests/acceptance/bashunit_timeout_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,26 @@ function test_bashunit_runs_tear_down_for_a_timed_out_test() {
assert_file_exists "$marker.setup"
assert_file_exists "$marker.teardown"
}

# The watchdog gave the body a flat 0.3s between its SIGTERM and its SIGKILL,
# which is a scheduling hiccup, not a grace: on a loaded machine the body had
# not even reached tear_down yet, and the #1324 test above failed about one
# parallel full-suite run in three. A tear_down slower than that flat window is
# the deterministic form of the same miss.
function test_bashunit_waits_for_a_slow_tear_down_of_a_timed_out_test() {
local dir fixture marker
dir="$(bashunit::temp_dir timeout_slow_teardown)"
fixture="$dir/hanging_test.sh"
marker="$dir/marker"
{
printf 'function tear_down() { sleep 1; : >"$TIMEOUT_MARKER.teardown"; }\n'
printf 'function test_hangs() { sleep 30; assert_true true; }\n'
} >"$fixture"

local output
output="$(TIMEOUT_MARKER="$marker" ./bashunit --no-parallel --env "$TEST_ENV_FILE" \
--test-timeout 1 "$fixture")" || true

assert_contains "Test timed out after 1s" "$output"
assert_file_exists "$marker.teardown"
}
24 changes: 24 additions & 0 deletions tests/unit/console/results_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -885,3 +885,27 @@ function test_tap_line_unknown_type_is_not_ok() {

assert_same "not ok 9 - Something odd" "$out"
}

function test_worker_stderr_drops_bash_job_control_noise() {
local stderr_file out
stderr_file=$(bashunit::temp_file worker_stderr)
printf '%s\n' \
'./src/runner/exec.sh: child setpgid (68809 to 68809): Operation not permitted' \
'diagnostic written by the file under test' >"$stderr_file"

out=$(bashunit::console_results::print_worker_stderr "some_test.sh" "$stderr_file" 2>&1)

assert_contains "diagnostic written by the file under test" "$out"
assert_not_contains "child setpgid" "$out"
}

function test_worker_stderr_block_is_omitted_when_only_job_control_noise() {
local stderr_file out
stderr_file=$(bashunit::temp_file worker_stderr)
printf '%s\n' \
'./src/runner/exec.sh: child setpgid (1 to 1): Operation not permitted' >"$stderr_file"

out=$(bashunit::console_results::print_worker_stderr "some_test.sh" "$stderr_file" 2>&1)

assert_empty "$out"
}
Loading