From abdc26df23a2ed82957172ba085f3ae13b7b6638 Mon Sep 17 00:00:00 2001 From: Aditya Sharma Date: Wed, 19 Aug 2026 21:28:47 +0530 Subject: [PATCH 1/2] fix: clean up script temp files on source failure (#1325) --- src/runner/discovery.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner/discovery.sh b/src/runner/discovery.sh index 1b232702..27501fae 100644 --- a/src/runner/discovery.sh +++ b/src/runner/discovery.sh @@ -132,6 +132,7 @@ function bashunit::runner::load_test_files() { bashunit::runner::record_file_hook_failure \ "source" "$test_file" "$message" 1 true bashunit::runner::clean_set_up_and_tear_down_after_script + bashunit::cleanup_script_temp_files bashunit::runner::restore_workdir continue fi From 16c38fe04041fab4fc409998b6cd1c18309238bc Mon Sep 17 00:00:00 2001 From: Chemaclass Date: Fri, 21 Aug 2026 11:14:37 +0200 Subject: [PATCH 2/2] test(runner): cover the temp-file sweep on the source-failure path Two acceptance tests pin it: the sequential path, and --parallel --list, which reaches no end-of-run sweep and is why this call is not behind the is_enabled guard the other paths out of the loop use. --- CHANGELOG.md | 1 + src/runner/discovery.sh | 3 ++ .../bashunit_hook_source_failure_test.sh | 42 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 774b69cb..676ae441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - `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) - A test killed by `--test-timeout` runs its `tear_down`, so a per-test resource is released. Best effort within the watchdog's grace before it sends SIGKILL, so a hook cannot outlive the timeout it cleans up after (#1324) +- A test file that fails to source sweeps its script temp files, so a `bashunit::temp_file` it created at top level no longer survives the run. `bashunit bench` already did this (#1325) ## [0.50.0](https://github.com/TypedDevs/bashunit/compare/0.49.0...0.50.0) - 2026-08-18 diff --git a/src/runner/discovery.sh b/src/runner/discovery.sh index b0be9097..2e17b0eb 100644 --- a/src/runner/discovery.sh +++ b/src/runner/discovery.sh @@ -132,6 +132,9 @@ function bashunit::runner::load_test_files() { bashunit::runner::record_file_hook_failure \ "source" "$test_file" "$message" 1 true bashunit::runner::clean_set_up_and_tear_down_after_script + # Unconditional, unlike the sweeps further down: the file dispatched no + # worker, and --parallel --list never reaches the end-of-run sweep that + # would otherwise own its ids (#1325). bashunit::cleanup_script_temp_files bashunit::runner::restore_workdir continue diff --git a/tests/acceptance/bashunit_hook_source_failure_test.sh b/tests/acceptance/bashunit_hook_source_failure_test.sh index 5d13890b..61974aa8 100644 --- a/tests/acceptance/bashunit_hook_source_failure_test.sh +++ b/tests/acceptance/bashunit_hook_source_failure_test.sh @@ -67,3 +67,45 @@ function test_a_silent_source_failure_reports_the_file_size() { assert_contains "no stderr" "$(printf '%s' "$actual" | strip_ansi)" assert_contains "bytes" "$(printf '%s' "$actual" | strip_ansi)" } + +# The bashunit::temp_file a fixture creates at its top level before it fails to +# source. Echoed whether or not the run swept it, so the caller can assert on it. +# Arguments: $@ - the flags to run bashunit with +function _script_temp_file_of_failed_source() { + local fixture leak_record + fixture="$(bashunit::temp_file source_failure_sweep).sh" + leak_record="$(bashunit::temp_file source_failure_sweep_record)" + { + printf 'printf "%%s\\n" "$(bashunit::temp_file leaky)" >"$LEAK_RECORD"\n' + printf 'function test_x() { assert_same 1 1; }\n' + printf 'false\n' + } >"$fixture" + + set +e + LEAK_RECORD="$leak_record" ./bashunit "$@" --env "$TEST_ENV_FILE" "$fixture" >/dev/null 2>&1 + set -e + + cat "$leak_record" +} + +# Everything above the failure has already run, so the file may own script temp +# files. This path skipped the sweep and they survived the run (#1325). +function test_a_source_failure_sweeps_the_files_script_temp_files() { + local leaked + leaked="$(_script_temp_file_of_failed_source --no-parallel)" + + # Without this the next assertion passes vacuously: an empty path is no file. + assert_not_empty "$leaked" + assert_file_not_exists "$leaked" +} + +# --list dispatches no worker, so the end-of-run loop that sweeps every id under +# --parallel is skipped. The sweep here is therefore unconditional, not behind +# the is_enabled guard the other paths out of the loop use (#1325). +function test_a_source_failure_sweeps_its_temp_files_under_parallel_list() { + local leaked + leaked="$(_script_temp_file_of_failed_source --parallel --list)" + + assert_not_empty "$leaked" + assert_file_not_exists "$leaked" +}