Skip to content

Commit f1f62e9

Browse files
committed
fix: set_up and tear_down functions when errors
1 parent 1a0438a commit f1f62e9

File tree

10 files changed

+240
-14
lines changed

10 files changed

+240
-14
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
- Add tasks storage policy clarifying `.tasks/` (versioned) vs `.task/` (private scratch, git-ignored)
99
- Include `set_test_title` helper in the single-file library
1010
- Fix lifecycle hooks capture-and-report flow errors
11+
- set_up
12+
- tear_down
13+
- set_up_before_script
14+
- tear_down_after_script
1115

1216
## [0.24.0](https://github.com/TypedDevs/bashunit/compare/0.23.0...0.24.0) - 2025-09-14
1317

src/runner.sh

Lines changed: 106 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -295,17 +295,25 @@ function runner::run_test() {
295295

296296
local test_execution_result=$(
297297
# shellcheck disable=SC2154
298-
trap '
299-
exit_code=$?
298+
trap "
299+
exit_code=\$?
300300
set +e
301-
state::set_test_exit_code "$exit_code"
302-
runner::run_tear_down
301+
teardown_status=0
302+
runner::run_tear_down \"$test_file\" || teardown_status=\$?
303303
runner::clear_mocks
304304
cleanup_testcase_temp_files
305+
if [[ \$teardown_status -ne 0 ]]; then
306+
state::set_test_exit_code \"\$teardown_status\"
307+
else
308+
state::set_test_exit_code \"\$exit_code\"
309+
fi
305310
state::export_subshell_context
306-
' EXIT
311+
" EXIT
307312
state::initialize_assertions_count
308-
runner::run_set_up
313+
if ! runner::run_set_up "$test_file"; then
314+
status=$?
315+
exit "$status"
316+
fi
309317
310318
# 2>&1: Redirects the std-error (FD 2) to the std-output (FD 1).
311319
# points to the original std-output.
@@ -379,17 +387,46 @@ function runner::run_test() {
379387
local test_title=""
380388
[[ -n "$encoded_test_title" ]] && test_title="$(helper::decode_base64 "$encoded_test_title")"
381389

390+
local encoded_hook_failure
391+
encoded_hook_failure="${test_execution_result##*##TEST_HOOK_FAILURE=}"
392+
encoded_hook_failure="${encoded_hook_failure%%##*}"
393+
local hook_failure=""
394+
if [[ "$encoded_hook_failure" != "$test_execution_result" ]]; then
395+
hook_failure="$encoded_hook_failure"
396+
fi
397+
398+
local encoded_hook_message
399+
encoded_hook_message="${test_execution_result##*##TEST_HOOK_MESSAGE=}"
400+
encoded_hook_message="${encoded_hook_message%%##*}"
401+
local hook_message=""
402+
if [[ -n "$encoded_hook_message" ]]; then
403+
hook_message="$(helper::decode_base64 "$encoded_hook_message")"
404+
fi
405+
382406
state::set_test_title "$test_title"
383407
local label
384408
label="$(helper::normalize_test_function_name "$fn_name" "$interpolated_fn_name")"
385409
state::reset_test_title
386410

411+
local failure_label="$label"
412+
local failure_function="$fn_name"
413+
if [[ -n "$hook_failure" ]]; then
414+
failure_label="$(helper::normalize_test_function_name "$hook_failure")"
415+
failure_function="$hook_failure"
416+
fi
417+
387418
if [[ -n $runtime_error || $test_exit_code -ne 0 ]]; then
388419
state::add_tests_failed
389-
console_results::print_error_test "$label" "$runtime_error"
390-
reports::add_test_failed "$test_file" "$label" "$duration" "$total_assertions"
391-
runner::write_failure_result_output "$test_file" "$fn_name" "$runtime_error"
392-
internal_log "Test error" "$label" "$runtime_error"
420+
local error_message="$runtime_error"
421+
if [[ -n "$hook_failure" && -n "$hook_message" ]]; then
422+
error_message="$hook_message"
423+
elif [[ -z "$error_message" && -n "$hook_message" ]]; then
424+
error_message="$hook_message"
425+
fi
426+
console_results::print_error_test "$failure_function" "$error_message"
427+
reports::add_test_failed "$test_file" "$failure_label" "$duration" "$total_assertions"
428+
runner::write_failure_result_output "$test_file" "$failure_function" "$error_message"
429+
internal_log "Test error" "$failure_label" "$error_message"
393430
return
394431
fi
395432

@@ -624,8 +661,9 @@ function runner::execute_file_hook() {
624661
}
625662

626663
function runner::run_set_up() {
664+
local _test_file="${1-}"
627665
internal_log "run_set_up"
628-
helper::execute_function_if_exists 'set_up'
666+
runner::execute_test_hook 'set_up'
629667
}
630668

631669
function runner::run_set_up_before_script() {
@@ -635,8 +673,64 @@ function runner::run_set_up_before_script() {
635673
}
636674

637675
function runner::run_tear_down() {
676+
local _test_file="${1-}"
638677
internal_log "run_tear_down"
639-
helper::execute_function_if_exists 'tear_down'
678+
runner::execute_test_hook 'tear_down'
679+
}
680+
681+
function runner::execute_test_hook() {
682+
local hook_name="$1"
683+
684+
if [[ "$(type -t "$hook_name")" != "function" ]]; then
685+
return 0
686+
fi
687+
688+
local hook_output=""
689+
local status=0
690+
local hook_output_file
691+
hook_output_file=$(temp_file "${hook_name}_output")
692+
693+
{
694+
"$hook_name"
695+
} >"$hook_output_file" 2>&1 || status=$?
696+
697+
if [[ -f "$hook_output_file" ]]; then
698+
hook_output=$(cat "$hook_output_file")
699+
rm -f "$hook_output_file"
700+
fi
701+
702+
if [[ $status -ne 0 ]]; then
703+
local message="$hook_output"
704+
if [[ -n "$hook_output" ]]; then
705+
printf "%s" "$hook_output"
706+
else
707+
message="Hook '$hook_name' failed with exit code $status"
708+
printf "%s\n" "$message" >&2
709+
fi
710+
runner::record_test_hook_failure "$hook_name" "$message" "$status"
711+
return "$status"
712+
fi
713+
714+
if [[ -n "$hook_output" ]]; then
715+
printf "%s" "$hook_output"
716+
fi
717+
718+
return 0
719+
}
720+
721+
function runner::record_test_hook_failure() {
722+
local hook_name="$1"
723+
local hook_message="$2"
724+
local status="$3"
725+
726+
if [[ -n "$(state::get_test_hook_failure)" ]]; then
727+
return "$status"
728+
fi
729+
730+
state::set_test_hook_failure "$hook_name"
731+
state::set_test_hook_message "$hook_message"
732+
733+
return "$status"
640734
}
641735

642736
function runner::clear_mocks() {

src/state.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ _DUPLICATED_TEST_FUNCTIONS_FOUND=false
1616
_TEST_OUTPUT=""
1717
_TEST_TITLE=""
1818
_TEST_EXIT_CODE=0
19+
_TEST_HOOK_FAILURE=""
20+
_TEST_HOOK_MESSAGE=""
1921

2022
function state::get_tests_passed() {
2123
echo "$_TESTS_PASSED"
@@ -145,6 +147,30 @@ function state::reset_test_title() {
145147
_TEST_TITLE=""
146148
}
147149

150+
function state::get_test_hook_failure() {
151+
echo "$_TEST_HOOK_FAILURE"
152+
}
153+
154+
function state::set_test_hook_failure() {
155+
_TEST_HOOK_FAILURE="$1"
156+
}
157+
158+
function state::reset_test_hook_failure() {
159+
_TEST_HOOK_FAILURE=""
160+
}
161+
162+
function state::get_test_hook_message() {
163+
echo "$_TEST_HOOK_MESSAGE"
164+
}
165+
166+
function state::set_test_hook_message() {
167+
_TEST_HOOK_MESSAGE="$1"
168+
}
169+
170+
function state::reset_test_hook_message() {
171+
_TEST_HOOK_MESSAGE=""
172+
}
173+
148174
function state::set_duplicated_functions_merged() {
149175
state::set_duplicated_test_functions_found
150176
state::set_file_with_duplicated_function_names "$1"
@@ -159,20 +185,26 @@ function state::initialize_assertions_count() {
159185
_ASSERTIONS_SNAPSHOT=0
160186
_TEST_OUTPUT=""
161187
_TEST_TITLE=""
188+
_TEST_HOOK_FAILURE=""
189+
_TEST_HOOK_MESSAGE=""
162190
}
163191

164192
function state::export_subshell_context() {
165193
local encoded_test_output
166194
local encoded_test_title
167195

196+
local encoded_test_hook_message
197+
168198
if base64 --help 2>&1 | grep -q -- "-w"; then
169199
# Alpine requires the -w 0 option to avoid wrapping
170200
encoded_test_output=$(echo -n "$_TEST_OUTPUT" | base64 -w 0)
171201
encoded_test_title=$(echo -n "$_TEST_TITLE" | base64 -w 0)
202+
encoded_test_hook_message=$(echo -n "$_TEST_HOOK_MESSAGE" | base64 -w 0)
172203
else
173204
# macOS and others: default base64 without wrapping
174205
encoded_test_output=$(echo -n "$_TEST_OUTPUT" | base64)
175206
encoded_test_title=$(echo -n "$_TEST_TITLE" | base64)
207+
encoded_test_hook_message=$(echo -n "$_TEST_HOOK_MESSAGE" | base64)
176208
fi
177209

178210
cat <<EOF
@@ -182,6 +214,8 @@ function state::export_subshell_context() {
182214
##ASSERTIONS_INCOMPLETE=$_ASSERTIONS_INCOMPLETE\
183215
##ASSERTIONS_SNAPSHOT=$_ASSERTIONS_SNAPSHOT\
184216
##TEST_EXIT_CODE=$_TEST_EXIT_CODE\
217+
##TEST_HOOK_FAILURE=$_TEST_HOOK_FAILURE\
218+
##TEST_HOOK_MESSAGE=$encoded_test_hook_message\
185219
##TEST_TITLE=$encoded_test_title\
186220
##TEST_OUTPUT=$encoded_test_output\
187221
##
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
function set_up_before_script() {
5+
TEST_ENV_FILE="tests/acceptance/fixtures/.env.default"
6+
}
7+
8+
function strip_ansi() {
9+
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g'
10+
}
11+
12+
function test_bashunit_when_set_up_errors() {
13+
local test_file=./tests/acceptance/fixtures/test_bashunit_when_setup_errors.sh
14+
local fixture=$test_file
15+
16+
local header_line="Running $fixture"
17+
local error_line="✗ Error: Set up"
18+
local message_line=" $fixture: line 4: invalid_function_name: command not found"
19+
local tests_summary="Tests: 1 failed, 1 total"
20+
local assertions_summary="Assertions: 0 failed, 0 total"
21+
22+
local actual_raw
23+
set +e
24+
actual_raw="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file")"
25+
set -e
26+
27+
local actual
28+
actual="$(printf "%s" "$actual_raw" | strip_ansi)"
29+
30+
assert_contains "$header_line" "$actual"
31+
assert_contains "$error_line" "$actual"
32+
assert_contains "$message_line" "$actual"
33+
assert_contains "$tests_summary" "$actual"
34+
assert_contains "$assertions_summary" "$actual"
35+
assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")"
36+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
function set_up_before_script() {
5+
TEST_ENV_FILE="tests/acceptance/fixtures/.env.default"
6+
}
7+
8+
function strip_ansi() {
9+
sed -E 's/\x1B\[[0-9;]*[A-Za-z]//g'
10+
}
11+
12+
function test_bashunit_when_tear_down_errors() {
13+
local test_file=./tests/acceptance/fixtures/test_bashunit_when_teardown_errors.sh
14+
local fixture=$test_file
15+
16+
local header_line="Running $fixture"
17+
local error_line="✗ Error: Tear down"
18+
local message_line=" $fixture: line 4: invalid_function_name: command not found"
19+
local tests_summary="Tests: 1 failed, 1 total"
20+
local assertions_summary="Assertions: 0 failed, 0 total"
21+
22+
local actual_raw
23+
set +e
24+
actual_raw="$(./bashunit --no-parallel --detailed --env "$TEST_ENV_FILE" "$test_file")"
25+
set -e
26+
27+
local actual
28+
actual="$(printf "%s" "$actual_raw" | strip_ansi)"
29+
30+
assert_contains "$header_line" "$actual"
31+
assert_contains "$error_line" "$actual"
32+
assert_contains "$message_line" "$actual"
33+
assert_contains "$tests_summary" "$actual"
34+
assert_contains "$assertions_summary" "$actual"
35+
assert_general_error "$(./bashunit --no-parallel --env "$TEST_ENV_FILE" "$test_file")"
36+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
function set_up() {
4+
invalid_function_name arg1
5+
}
6+
7+
function test_dummy() {
8+
:
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
function tear_down() {
4+
invalid_function_name arg1
5+
}
6+
7+
function test_dummy() {
8+
:
9+
}

tests/unit/globals_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function set_up() {
1919
}
2020

2121
function tear_down() {
22-
rm "$BASHUNIT_DEV_LOG"
22+
rm -f "$BASHUNIT_DEV_LOG"
2323
}
2424

2525
function test_globals_current_dir() {

tests/unit/redirect_error_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -euo pipefail
44
_ERROR_LOG=temp_error.log
55

66
function tear_down() {
7-
rm $_ERROR_LOG
7+
rm -f "$_ERROR_LOG"
88
}
99

1010
function test_redirect_error_with_log() {

tests/unit/state_test.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ function test_initialize_assertions_count() {
255255
##ASSERTIONS_INCOMPLETE=0\
256256
##ASSERTIONS_SNAPSHOT=0\
257257
##TEST_EXIT_CODE=0\
258+
##TEST_HOOK_FAILURE=\
259+
##TEST_HOOK_MESSAGE=\
258260
##TEST_TITLE=\
259261
##TEST_OUTPUT=\
260262
##"\
@@ -285,6 +287,8 @@ function test_export_assertions_count() {
285287
##ASSERTIONS_INCOMPLETE=12\
286288
##ASSERTIONS_SNAPSHOT=33\
287289
##TEST_EXIT_CODE=1\
290+
##TEST_HOOK_FAILURE=\
291+
##TEST_HOOK_MESSAGE=\
288292
##TEST_TITLE=\
289293
##TEST_OUTPUT=$(echo -n "something" | base64)##"\
290294
"$export_assertions_count"

0 commit comments

Comments
 (0)