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 @@ -7,6 +7,7 @@
- Under `--parallel`, `tear_down_after_script` runs after the file's own tests instead of alongside them, so a fixture it releases stays alive for the tests that read it. The same file no longer passed sequentially and failed in parallel (#1320)
- `--stop-on-failure` runs `tear_down_after_script` for the file it halts in, so a sequential run releases what `set_up_before_script` acquired before the halt (#1321)
- `bashunit bench` runs `tear_down_after_script` before it aborts on a malformed annotation, so the file releases what `set_up_before_script` acquired (#1322)
- Ctrl-C runs `tear_down_after_script` for the file it interrupts in a sequential run, so a file-scoped resource is released. A second Ctrl-C now ends the run even if that hook never returns (#1323)

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

Expand Down
10 changes: 10 additions & 0 deletions src/main/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -380,10 +380,20 @@ function bashunit::main::exec_benchmarks() {
}

function bashunit::main::cleanup() {
# Back to the default disposition first: the teardown hook below is user code
# and may never return, and an interrupt handler that cannot itself be
# interrupted would leave no way out but SIGKILL. A second Ctrl-C now ends the
# process. Safe to reset because SIGINT was trappable on entry, or this handler
# would not be running.
trap - INT
printf "%sCaught Ctrl-C, killing all child processes...%s\n" \
"${_BASHUNIT_COLOR_SKIPPED}" "${_BASHUNIT_COLOR_DEFAULT}"
# Kill all child processes of this script
pkill -P $$
# After the kill, so the per-test tear_down that the test subshell's EXIT trap
# runs comes first, as it does in a normal run. Before the temp-file sweep, so
# a hook reading a bashunit::temp_file still finds it (#1323).
bashunit::runner::run_pending_file_teardown || true
bashunit::cleanup_script_temp_files
if bashunit::parallel::is_enabled; then
bashunit::parallel::cleanup
Expand Down
61 changes: 61 additions & 0 deletions tests/unit/main/cleanup_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash

# bashunit::main::cleanup is the Ctrl-C handler. It kills the run's children,
# releases what the interrupted file still owes, and exits.
#
# Every call runs inside a subshell: the function ends in `exit`, and pkill and
# the three cleanup calls have to be replaced before they run for real. Left
# alone, cleanup_run_output_dir would delete the live run's own scratch
# directory, which is the disappearing-scratch-dir shape of #1137.
#
# A signal is never sent here. Delivering SIGINT to a real run depends on job
# control and on which frame the shell is in when it lands, which under a loaded
# --parallel suite is not reproducible. What #1323 changed is the handler's body,
# and that is what these pin.
#
# Arguments: $1 - order log, $2 - "owed" to record a pending file teardown
#
# The definitions below shadow what cleanup calls, so shellcheck sees five
# functions nothing in this file invokes.
# shellcheck disable=SC2329
function _run_cleanup() {
local order=$1
local owed=${2:-}
(
function pkill() { printf 'pkill\n' >>"$order"; return 0; }
function bashunit::cleanup_script_temp_files() { printf 'temp-files\n' >>"$order"; }
function bashunit::parallel::cleanup() { :; }
function bashunit::env::cleanup_run_output_dir() { :; }
function tear_down_after_script() { printf 'file-teardown\n' >>"$order"; }

if [ "$owed" = owed ]; then
bashunit::runner::mark_file_teardown_pending "some_test.sh"
fi

bashunit::main::cleanup
) >/dev/null 2>&1 || true
}

function test_cleanup_runs_the_file_teardown_the_interrupted_run_still_owed() {
local order
order="$(bashunit::temp_dir cleanup_owed)/order"

_run_cleanup "$order" owed

# After pkill, so the per-test tear_down that a test subshell's EXIT trap runs
# comes first as it does in a normal run, and before the temp-file sweep, so a
# hook reading a bashunit::temp_file still finds it.
assert_same "pkill
file-teardown
temp-files" "$(cat "$order")"
}

function test_cleanup_runs_no_file_teardown_when_the_run_owes_none() {
local order
order="$(bashunit::temp_dir cleanup_not_owed)/order"

_run_cleanup "$order"

assert_same "pkill
temp-files" "$(cat "$order")"
}
Loading