@@ -17,13 +17,19 @@ function runner::load_test_files() {
1717 internal_log " Loading file" " $test_file "
1818 # shellcheck source=/dev/null
1919 source " $test_file "
20- runner::run_set_up_before_script
20+ if ! runner::run_set_up_before_script " $test_file " ; then
21+ runner::clean_set_up_and_tear_down_after_script
22+ if ! parallel::is_enabled; then
23+ cleanup_script_temp_files
24+ fi
25+ continue
26+ fi
2127 if parallel::is_enabled; then
2228 runner::call_test_functions " $test_file " " $filter " 2> /dev/null &
2329 else
2430 runner::call_test_functions " $test_file " " $filter "
2531 fi
26- runner::run_tear_down_after_script
32+ runner::run_tear_down_after_script " $test_file "
2733 runner::clean_set_up_and_tear_down_after_script
2834 if ! parallel::is_enabled; then
2935 cleanup_script_temp_files
@@ -57,9 +63,13 @@ function runner::load_bench_files() {
5763 export BASHUNIT_CURRENT_SCRIPT_ID=" $( helper::generate_id " ${test_file} " ) "
5864 # shellcheck source=/dev/null
5965 source " $bench_file "
60- runner::run_set_up_before_script
66+ if ! runner::run_set_up_before_script " $bench_file " ; then
67+ runner::clean_set_up_and_tear_down_after_script
68+ cleanup_script_temp_files
69+ continue
70+ fi
6171 runner::call_bench_functions " $bench_file " " $filter "
62- runner::run_tear_down_after_script
72+ runner::run_tear_down_after_script " $bench_file "
6373 runner::clean_set_up_and_tear_down_after_script
6474 cleanup_script_temp_files
6575 done
@@ -284,18 +294,27 @@ function runner::run_test() {
284294 exec 3>&1
285295
286296 local test_execution_result=$(
297+ # shellcheck disable=SC2064
287298 # shellcheck disable=SC2154
288- trap '
289- exit_code=$?
299+ trap "
300+ exit_code=\ $ ?
290301 set +e
291- state::set_test_exit_code "$exit_code"
292- runner::run_tear_down
302+ teardown_status=0
303+ runner::run_tear_down \" $test_file \" || teardown_status= \$ ?
293304 runner::clear_mocks
294305 cleanup_testcase_temp_files
306+ if [[ \$ teardown_status -ne 0 ]]; then
307+ state::set_test_exit_code \"\$ teardown_status\"
308+ else
309+ state::set_test_exit_code \"\$ exit_code\"
310+ fi
295311 state::export_subshell_context
296- ' EXIT
312+ " EXIT
297313 state::initialize_assertions_count
298- runner::run_set_up
314+ if ! runner::run_set_up " $test_file " ; then
315+ status=$?
316+ exit " $status "
317+ fi
299318
300319 # 2>&1: Redirects the std-error (FD 2) to the std-output (FD 1).
301320 # points to the original std-output.
@@ -369,17 +388,46 @@ function runner::run_test() {
369388 local test_title=" "
370389 [[ -n " $encoded_test_title " ]] && test_title=" $( helper::decode_base64 " $encoded_test_title " ) "
371390
391+ local encoded_hook_failure
392+ encoded_hook_failure=" ${test_execution_result##*## TEST_HOOK_FAILURE=} "
393+ encoded_hook_failure=" ${encoded_hook_failure%%##* } "
394+ local hook_failure=" "
395+ if [[ " $encoded_hook_failure " != " $test_execution_result " ]]; then
396+ hook_failure=" $encoded_hook_failure "
397+ fi
398+
399+ local encoded_hook_message
400+ encoded_hook_message=" ${test_execution_result##*## TEST_HOOK_MESSAGE=} "
401+ encoded_hook_message=" ${encoded_hook_message%%##* } "
402+ local hook_message=" "
403+ if [[ -n " $encoded_hook_message " ]]; then
404+ hook_message=" $( helper::decode_base64 " $encoded_hook_message " ) "
405+ fi
406+
372407 state::set_test_title " $test_title "
373408 local label
374409 label=" $( helper::normalize_test_function_name " $fn_name " " $interpolated_fn_name " ) "
375410 state::reset_test_title
376411
412+ local failure_label=" $label "
413+ local failure_function=" $fn_name "
414+ if [[ -n " $hook_failure " ]]; then
415+ failure_label=" $( helper::normalize_test_function_name " $hook_failure " ) "
416+ failure_function=" $hook_failure "
417+ fi
418+
377419 if [[ -n $runtime_error || $test_exit_code -ne 0 ]]; then
378420 state::add_tests_failed
379- console_results::print_error_test " $label " " $runtime_error "
380- reports::add_test_failed " $test_file " " $label " " $duration " " $total_assertions "
381- runner::write_failure_result_output " $test_file " " $fn_name " " $runtime_error "
382- internal_log " Test error" " $label " " $runtime_error "
421+ local error_message=" $runtime_error "
422+ if [[ -n " $hook_failure " && -n " $hook_message " ]]; then
423+ error_message=" $hook_message "
424+ elif [[ -z " $error_message " && -n " $hook_message " ]]; then
425+ error_message=" $hook_message "
426+ fi
427+ console_results::print_error_test " $failure_function " " $error_message "
428+ reports::add_test_failed " $test_file " " $failure_label " " $duration " " $total_assertions "
429+ runner::write_failure_result_output " $test_file " " $failure_function " " $error_message "
430+ internal_log " Test error" " $failure_label " " $error_message "
383431 return
384432 fi
385433
@@ -555,19 +603,135 @@ function runner::write_failure_result_output() {
555603 echo -e " $test_nr ) $test_file :$line_number \n$error_msg " >> " $FAILURES_OUTPUT_PATH "
556604}
557605
606+ function runner::record_file_hook_failure() {
607+ local hook_name=" $1 "
608+ local test_file=" $2 "
609+ local hook_output=" $3 "
610+ local status=" $4 "
611+ local render_header=" ${5:- false} "
612+
613+ if [[ " $render_header " == true ]]; then
614+ runner::render_running_file_header " $test_file "
615+ fi
616+
617+ if [[ -z " $hook_output " ]]; then
618+ hook_output=" Hook '$hook_name ' failed with exit code $status "
619+ fi
620+
621+ state::add_tests_failed
622+ console_results::print_error_test " $hook_name " " $hook_output "
623+ reports::add_test_failed " $test_file " " $( helper::normalize_test_function_name " $hook_name " ) " 0 0
624+ runner::write_failure_result_output " $test_file " " $hook_name " " $hook_output "
625+
626+ return " $status "
627+ }
628+
629+ function runner::execute_file_hook() {
630+ local hook_name=" $1 "
631+ local test_file=" $2 "
632+ local render_header=" ${3:- false} "
633+
634+ if [[ " $( type -t " $hook_name " ) " != " function" ]]; then
635+ return 0
636+ fi
637+
638+ local hook_output=" "
639+ local status=0
640+ local hook_output_file
641+ hook_output_file=$( temp_file " ${hook_name} _output" )
642+
643+ {
644+ " $hook_name "
645+ } > " $hook_output_file " 2>&1 || status=$?
646+
647+ if [[ -f " $hook_output_file " ]]; then
648+ hook_output=$( cat " $hook_output_file " )
649+ rm -f " $hook_output_file "
650+ fi
651+
652+ if [[ $status -ne 0 ]]; then
653+ runner::record_file_hook_failure " $hook_name " " $test_file " " $hook_output " " $status " " $render_header "
654+ return $status
655+ fi
656+
657+ if [[ -n " $hook_output " ]]; then
658+ printf " %s\n" " $hook_output "
659+ fi
660+
661+ return 0
662+ }
663+
558664function runner::run_set_up() {
665+ local _test_file=" ${1-} "
559666 internal_log " run_set_up"
560- helper::execute_function_if_exists ' set_up'
667+ runner::execute_test_hook ' set_up'
561668}
562669
563670function runner::run_set_up_before_script() {
671+ local test_file=" $1 "
564672 internal_log " run_set_up_before_script"
565- helper::execute_function_if_exists ' set_up_before_script'
673+ runner::execute_file_hook ' set_up_before_script' " $test_file " true
566674}
567675
568676function runner::run_tear_down() {
677+ local _test_file=" ${1-} "
569678 internal_log " run_tear_down"
570- helper::execute_function_if_exists ' tear_down'
679+ runner::execute_test_hook ' tear_down'
680+ }
681+
682+ function runner::execute_test_hook() {
683+ local hook_name=" $1 "
684+
685+ if [[ " $( type -t " $hook_name " ) " != " function" ]]; then
686+ return 0
687+ fi
688+
689+ local hook_output=" "
690+ local status=0
691+ local hook_output_file
692+ hook_output_file=$( temp_file " ${hook_name} _output" )
693+
694+ {
695+ " $hook_name "
696+ } > " $hook_output_file " 2>&1 || status=$?
697+
698+ if [[ -f " $hook_output_file " ]]; then
699+ hook_output=$( cat " $hook_output_file " )
700+ rm -f " $hook_output_file "
701+ fi
702+
703+ if [[ $status -ne 0 ]]; then
704+ local message=" $hook_output "
705+ if [[ -n " $hook_output " ]]; then
706+ printf " %s" " $hook_output "
707+ else
708+ message=" Hook '$hook_name ' failed with exit code $status "
709+ printf " %s\n" " $message " >&2
710+ fi
711+ runner::record_test_hook_failure " $hook_name " " $message " " $status "
712+ return " $status "
713+ fi
714+
715+ if [[ -n " $hook_output " ]]; then
716+ printf " %s" " $hook_output "
717+ fi
718+
719+ return 0
720+ }
721+
722+ function runner::record_test_hook_failure() {
723+ local hook_name=" $1 "
724+ local hook_message=" $2 "
725+ local status=" $3 "
726+
727+ if [[ -n " $( state::get_test_hook_failure) " ]]; then
728+ return " $status "
729+ fi
730+
731+ state::set_test_hook_failure " $hook_name "
732+ state::set_test_hook_message " $hook_message "
733+
734+ return " $status "
571735}
572736
573737function runner::clear_mocks() {
@@ -577,8 +741,9 @@ function runner::clear_mocks() {
577741}
578742
579743function runner::run_tear_down_after_script() {
744+ local test_file=" $1 "
580745 internal_log " run_tear_down_after_script"
581- helper::execute_function_if_exists ' tear_down_after_script'
746+ runner::execute_file_hook ' tear_down_after_script' " $test_file "
582747}
583748
584749function runner::clean_set_up_and_tear_down_after_script() {
0 commit comments