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

## Unreleased

### Fixed
- `tear_down_after_script` runs when `set_up_before_script` fails, so it can release file-scoped resources acquired before the failure (#1318)

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

### Added
Expand Down
6 changes: 4 additions & 2 deletions docs/test-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ Running tests/example_test.sh

This visibility helps identify slow setup operations that may impact test run time.

If `set_up_before_script` fails — any failing command, or the function returning a non-zero status (watch out for a trailing `cmd && var=value` guard: when `cmd` fails, the guard is the hook's return value) — bashunit reports the hook error, marks **every test in the file as failed** (they are included in the totals), and continues with the next test file. The rest of the suite always runs, and the failure is attributed to the hook rather than surfacing as mysterious individual test errors. If you want a missing optional dependency to skip tests instead of failing them, end the hook with an explicit success, e.g. `command -v jq >/dev/null 2>&1 && HAS_JQ=true; return 0` — then call `bashunit::skip` inside the tests.
A failing command or non-zero function status makes `set_up_before_script` fail. bashunit reports the hook error, marks **every test in the file as failed** (they are included in the totals), and continues with the next test file. Watch out for a trailing `cmd && var=value` guard: when `cmd` fails, the guard is the hook's return value. The rest of the suite always runs, and the failure is attributed to the hook rather than surfacing as mysterious individual test errors. `tear_down_after_script` still runs, so it can release resources acquired before the setup failure. Because setup may be only partially complete, guard optional state in teardown, for example `[ -n "${RESOURCE:-}" ] && rm -f "$RESOURCE"`.

If you want a missing optional dependency to skip tests instead of failing them, end the hook with an explicit success, for example `command -v jq >/dev/null 2>&1 && HAS_JQ=true; return 0`, then call `bashunit::skip` inside the tests.

::: code-group
```bash [Example]
Expand All @@ -163,7 +165,7 @@ function set_up_before_script() {

## `tear_down_after_script` function

The `tear_down_after_script` auxiliary function is called, if it is present in the test file, only once when all the test functions in the test file have been executed.
The `tear_down_after_script` auxiliary function is called, if it is present in the test file, once after the file finishes. It also runs when `set_up_before_script` fails, even though the test functions cannot run.
This auxiliary function is similar to how `set_up_before_script` works but at the end of the tests.
It provides a hook for any cleanup that should occur after all tests have run, such as deleting temporary files or releasing resources.

Expand Down
3 changes: 3 additions & 0 deletions src/runner/bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ function bashunit::runner::load_bench_files() {
bashunit::state::add_tests_failed
done
fi
# Setup may have acquired resources before it failed. Pair every setup
# invocation with teardown, as the per-test lifecycle already does.
bashunit::runner::run_tear_down_after_script "$bench_file"
bashunit::runner::clean_set_up_and_tear_down_after_script
bashunit::cleanup_script_temp_files
bashunit::runner::restore_workdir
Expand Down
3 changes: 3 additions & 0 deletions src/runner/discovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ function bashunit::runner::load_test_files() {
bashunit::state::add_tests_failed
done
fi
# Setup may have acquired resources before it failed. Pair every setup
# invocation with teardown, as the per-test lifecycle already does.
bashunit::runner::run_tear_down_after_script "$test_file"
# Same cleanup as the success path: without it the file's test functions
# leak into the next iteration's counts and the main shell (#829, #836).
bashunit::runner::clean_script_test_functions "$_script_fns_to_clean"
Expand Down
25 changes: 25 additions & 0 deletions tests/acceptance/bashunit_bench_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,31 @@ function test_bench_fails_when_a_files_set_up_before_script_fails() {
assert_contains "Set up before script" "$output"
}

function test_bench_cleans_up_when_set_up_before_script_fails() {
local dir fixture marker
dir="$(bashunit::temp_dir bench_setup_failure_cleanup)"
fixture="$dir/cleanup_bench.sh"
marker="$dir/resource"
{
printf 'RESOURCE=""\n'
printf 'function set_up_before_script() {\n'
printf ' RESOURCE="$CLEANUP_MARKER"\n'
printf ' : >"$RESOURCE"\n'
printf ' return 1\n'
printf '}\n'
printf 'function tear_down_after_script() {\n'
printf ' rm -f "$RESOURCE"\n'
printf '}\n'
printf 'function bench_never_runs() { :; }\n'
} >"$fixture"

local exit_code=0
CLEANUP_MARKER="$marker" ./bashunit bench "$fixture" >/dev/null 2>&1 || exit_code=$?

assert_general_error "" "" "$exit_code"
assert_file_not_exists "$marker"
}

function test_bench_fails_when_a_file_cannot_be_sourced() {
local dir
dir="$(bashunit::temp_dir bench_source_fail)"
Expand Down
30 changes: 30 additions & 0 deletions tests/acceptance/bashunit_setup_before_script_error_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,36 @@ function test_bashunit_when_set_up_before_script_fails_with_multiple_tests() {
assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")"
}

function test_tear_down_after_script_runs_when_set_up_before_script_fails() {
local dir fixture
dir="$(bashunit::temp_dir setup_failure_cleanup)"
fixture="$dir/file_hooks_test.sh"
{
printf 'RESOURCE=""\n'
printf 'function set_up_before_script() {\n'
printf ' RESOURCE="$CLEANUP_MARKER"\n'
printf ' : >"$RESOURCE"\n'
printf ' return 1\n'
printf '}\n'
printf 'function tear_down_after_script() {\n'
printf ' rm -f "$RESOURCE"\n'
printf '}\n'
printf 'function test_never_runs() { assert_true true; }\n'
} >"$fixture"

local mode marker output exit_code
for mode in --no-parallel --parallel; do
marker="$dir/${mode#--}.resource"
exit_code=0
output=$(CLEANUP_MARKER="$marker" ./bashunit "$mode" --detailed \
--skip-env-file "$fixture" 2>&1) || exit_code=$?

assert_general_error "" "" "$exit_code"
assert_contains "Set up before script" "$output"
assert_file_not_exists "$marker"
done
}

function test_bashunit_when_set_up_before_script_with_intermediate_failing_command() {
local test_file
test_file=./tests/acceptance/fixtures/\
Expand Down
Loading