@@ -590,6 +590,11 @@ function bashunit::runner::run_test() {
590590 exec 3>&1
591591
592592 local test_execution_result=$(
593+ # Save subshell stdout to FD 5 so the EXIT trap can restore it.
594+ # When set -e kills the subshell during a redirected block in
595+ # execute_test_hook, the redirect leaks into the EXIT trap,
596+ # causing export_subshell_context output to be lost.
597+ exec 5>&1
593598 # shellcheck disable=SC2064
594599 trap " exit_code=\$ ?; bashunit::runner::cleanup_on_exit \" $test_file \" \"\$ exit_code\" " EXIT
595600 bashunit::state::initialize_assertions_count
@@ -612,9 +617,12 @@ function bashunit::runner::run_test() {
612617 fi
613618
614619 # Run set_up and capture exit code without || to preserve errexit behavior
620+ # shellcheck disable=SC2030
621+ _BASHUNIT_SETUP_COMPLETED=false
615622 local setup_exit_code=0
616623 bashunit::runner::run_set_up " $test_file "
617624 setup_exit_code=$?
625+ _BASHUNIT_SETUP_COMPLETED=true
618626 if [[ $setup_exit_code -ne 0 ]]; then
619627 exit $setup_exit_code
620628 fi
@@ -829,6 +837,26 @@ function bashunit::runner::cleanup_on_exit() {
829837 fi
830838
831839 set +e
840+
841+ # Detect unexpected subshell exit during set_up (Issue #611).
842+ # When 'source' of a non-existent file fails under set -eE, the ERR trap
843+ # does not fire. On macOS Bash 3.2, $? is 0 in the EXIT trap; on Linux
844+ # Bash 5.x, $? is 1. In both cases the hook failure is not recorded.
845+ # Additionally, the stdout redirect from execute_test_hook leaks into the
846+ # EXIT trap. Restore stdout from saved FD 5 so export_subshell_context
847+ # output reaches test_execution_result.
848+ # shellcheck disable=SC2031
849+ if [[ " ${_BASHUNIT_SETUP_COMPLETED:- true} " != " true" ]]; then
850+ exec 1>&5
851+ if [[ " $exit_code " -eq 0 ]]; then
852+ exit_code=1
853+ fi
854+ if [[ -z " ${_BASHUNIT_TEST_HOOK_FAILURE:- } " ]]; then
855+ bashunit::state::set_test_hook_failure " set_up"
856+ bashunit::state::set_test_hook_message " Hook 'set_up' failed unexpectedly (e.g., source of non-existent file)"
857+ fi
858+ fi
859+
832860 # Don't use || here - it disables ERR trap in the entire call chain
833861 bashunit::runner::run_tear_down " $test_file "
834862 local teardown_status=$?
0 commit comments