Repository navigation
Description
| Q | A |
|---|---|
| OS | macOS 26.5.2 (arm64) |
| Shell & version | bash 3.2.57 |
| bashunit version | 0.50.0 |
Summary
A test killed by --test-timeout never runs its tear_down. The watchdog signals the test's process group, the subshell dies without running its EXIT trap, and bashunit::runner::cleanup_on_exit never calls the hook.
Same class as #1318, at test scope. The file-scoped pair survives a timeout, because the loop keeps going and runs tear_down_after_script. The per-test pair does not.
This is the opposite asymmetry from #1323, where tear_down runs and tear_down_after_script does not. Both are the same missing guarantee seen from two sides.
Current behavior
The test is reported as Test timed out after 1s, the run continues, and whatever set_up acquired for that test is left behind.
How to reproduce
timeout_test.sh:
function set_up_before_script() { : >"$CLEANUP_MARKER.setupfile"; }
function tear_down_after_script() { : >"$CLEANUP_MARKER.RAN_FILE_TEARDOWN"; }
function set_up() { : >"$CLEANUP_MARKER.setuptest"; }
function tear_down() { : >"$CLEANUP_MARKER.RAN_TEST_TEARDOWN"; }
function test_hangs() { sleep 10; assert_true true; }$ CLEANUP_MARKER=/tmp/bu-to.marker ./bashunit --no-parallel --test-timeout 1 timeout_test.sh
✗ Error: Hangs
Test timed out after 1s
$ ls /tmp/bu-to.marker.*
/tmp/bu-to.marker.RAN_FILE_TEARDOWN
/tmp/bu-to.marker.setupfile
/tmp/bu-to.marker.setuptest
RAN_TEST_TEARDOWN is absent. The per-test set_up ran, its tear_down did not.
Verified on bash 3.2.57. Bash's default SIGTERM handling inside a forked subshell has changed across versions, so confirm on bash 5 before choosing a fix. A closely related path behaves differently on the same machine: pkill -P $$ on Ctrl-C does let the EXIT trap run.
Expected behavior
A timed-out test runs tear_down, so a per-test resource can be released. The timeout still bounds the run.
Where it happens
src/runner/exec.sh:363: the test body is spawned as(bashunit::runner::execute_test_body ...) &underset -msrc/runner/exec.sh:399-402: the watchdog writes the marker, sendskill -TERM -"$test_pid"to the group, sleeps 0.3s, thenkill -KILL -"$test_pid"src/runner/hooks.sh:37:bashunit::runner::run_tear_downlives incleanup_on_exit, the subshell'sEXITtrap, which never firessrc/runner/hooks.sh:38-40:clear_mocksandcleanup_testcase_temp_filesare skipped on the same path
Acceptance criteria
- An acceptance test:
--test-timeout 1against a hanging test whosetear_downreleases a resource, asserted gone. - The
Test timed out after Nsmessage and the failure classification are unchanged.tests/acceptancetimeout coverage still passes. - A
tear_downthat hangs cannot extend the run past the watchdog'sKILL. The 0.3s grace, or whatever replaces it, still bounds the process. - Verified on bash 3.2 and bash 5. The
make test-bash-3path exists for the first. -
--parallelruns behave the same way. -
CHANGELOG.mdgets aFixedentry.
Notes for the implementer
- Trapping
TERMinside the test-body subshell so theEXITtrap gets a chance is the direct fix. The0.3sbetweenTERMandKILLis the whole budget the hook gets, so measure whether that is enough or has to grow. - Weigh the tradeoff and write the decision down: a teardown that never returns is worse than a teardown that never runs. A bounded best-effort hook is the useful behaviour, not an unbounded one.
- The watchdog notes at
src/runner/exec.sh:366-390explain why it is detached and why it re-checkskill -0. Do not break that. The suites acceptance test is flaky on Bash 3.0 under load #1137 is the receipt. - Bash 3.0+ only. See
.claude/rules/bash-style.md. make sa,make lint,./bashunit tests/,./bashunit --parallel tests/.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
Chemaclass commentedon Aug 21, 2026
Done in #1333 (merged).