Repository navigation
Description
| Q | A |
|---|---|
| OS | macOS 26.5.2 (arm64) |
| Shell & version | bash 3.2.57 |
| bashunit version | 0.50.0 |
Summary
Ctrl-C runs bashunit::main::cleanup, which kills the children, sweeps the temp directories and exits. It never runs tear_down_after_script for the file in flight, so a file-scoped resource acquired in set_up_before_script is left behind.
Same class as #1318. The per-test half of the pair already survives this: pkill sends SIGTERM to the test subshell, its EXIT trap fires, and tear_down runs. The file-scoped half does not.
Current behavior
● set_up_before_script 11ms
Caught Ctrl-C, killing all child processes...
tear_down ran. tear_down_after_script did not. The resource survives.
How to reproduce
sigint_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_slow() { sleep 10; assert_true true; }A shell that backgrounds a job sets SIGINT to SIG_IGN in the child, and bash refuses to trap a signal that was ignored on entry. So the repro has to reset the disposition before exec, or the signal is swallowed and the run finishes normally:
$ CLEANUP_MARKER=/tmp/bu-si.marker \
perl -e '$SIG{INT}="DEFAULT"; exec @ARGV' \
./bashunit --no-parallel sigint_test.sh &
$ sleep 3; kill -INT $!
$ ls /tmp/bu-si.marker.*
/tmp/bu-si.marker.RAN_TEST_TEARDOWN
/tmp/bu-si.marker.setupfile
/tmp/bu-si.marker.setuptest
RAN_FILE_TEARDOWN is absent.
Expected behavior
Ctrl-C runs tear_down_after_script for the file whose set_up_before_script already ran, then exits 1 as it does today.
Where it happens
src/main/run.sh:82:trap 'bashunit::main::cleanup' SIGINTsrc/main/run.sh:382:bashunit::main::cleanuprunspkill -P $$,bashunit::cleanup_script_temp_files,bashunit::parallel::cleanup,bashunit::env::cleanup_run_output_dir,exit 1--watchis the same path:src/main/watch.sh:38trapsINTin the parent andexec_testsruns in a( )subshell, so fixingcleanupcovers both
Acceptance criteria
-
bashunit::main::cleanuprunstear_down_after_scriptfor the current file when one is defined and itsset_up_before_scriptalready ran. - It runs before
bashunit::cleanup_script_temp_files, so a hook that reads abashunit::temp_filestill finds it. - It runs after
pkill -P $$, matching the normal order: per-testtear_downfirst, then the file hook. - Nothing runs for a file whose setup never ran, and nothing runs twice.
- Ctrl-C still prints "Caught Ctrl-C, killing all child processes..." and still exits 1.
- A hook that blocks does not hang the interrupt. Bound it, or write down in a comment why it is safe not to.
- A test covering it. The
SIG_IGNinheritance above has to be handled inside the test, and the reason belongs in a comment so the next reader does not delete the workaround. -
CHANGELOG.mdgets aFixedentry.
Notes for the implementer
- The trap cannot see the loop variable in
bashunit::runner::load_test_files. Record the current file in a global when the loop starts one, and clear it after the file's teardown, so the trap knows both which file and whether the hook is still owed. - The same global answers the
--stop-on-failurequestion in--stop-on-failureskipstear_down_after_scriptin sequential runs #1321. Worth building once. - 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 19, 2026
Done in #1332 (merged). The
--parallelhalf is tracked in #1331.