t: prepare `test_when_finished ()`/`test_atexit()` for `set -e`

Both `test_when_finished ()` and `test_atexit ()` build up a chain of cleanup commands by prepending each new command to the existing cleanup string. To preserve the exit code of the test body across cleanup execution, we append the following logic: } && (exit "$eval_ret"); eval_ret=$?; ... The intent of this is to run the cleanup block and then unconditionally restore `eval_ret`. The original behaviour of this is is: +------------------+---------+------------------------------------+ |test body │ cleanup │ old behaviour │ +------------------+---------+------------------------------------+ │pass (eval_ret=0) | pass │ && taken -> (exit 0) -> eval_ret=0 | +------------------+---------+------------------------------------+ │pass (eval_ret=0) | fail │ && not taken -> eval_ret=$? | +------------------+---------+------------------------------------+ │fail (eval_ret=1) | pass │ && taken -> (exit 1) -> eval_ret=1 | +------------------+---------+------------------------------------+ │fail (eval_ret=1) | fail | && not taken -> eval_ret=$? | +------------------+---------+------------------------------------+ This logic will start to fail once we enable `set -e`. When `$eval_ret` is non-zero, the subshell we create will fail, and with `set -e` we'll thus bail out without evaluating the logic after the semicolon. Fix this issue by instead using `|| eval_ret=\$?; ...`. Besides being a bit simpler, it also retains the original behaviour: +------------------+---------+------------------------------------+ |test body │ cleanup │ old behaviour │ +------------------+---------+------------------------------------+ │pass (eval_ret=0) | pass │ || not taken -> eval_ret unchanged | +------------------+---------+------------------------------------+ │pass (eval_ret=0) | fail │ || taken -> eval_ret=$? | +------------------+---------+------------------------------------+ │fail (eval_ret=1) | pass │ || not taken -> eval_ret unchanged | +------------------+---------+------------------------------------+ │fail (eval_ret=1) | fail | || taken -> eval_ret=$? | +------------------+---------+------------------------------------+ Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Apr 21, 2026 at 09:34 UTC c1e29bcfa88fb8997379c3c7c888961b728e581d
1 file changed +2 -2
t/test-lib-functions.sh
+2 -2
@@ -1512,7 +1512,7 @@ test_when_finished () {
1512 test "${BASH_SUBSHELL-0}" = 0 ||
1513 BUG "test_when_finished does nothing in a subshell"
1514 test_cleanup="{ $*
1515 - } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
1515 + } || eval_ret=\$?; $test_cleanup"
1516 }
1517
1518 # This function can be used to schedule some commands to be run
@@ -1540,7 +1540,7 @@ test_atexit () {
1540 test "${BASH_SUBSHELL-0}" = 0 ||
1541 BUG "test_atexit does nothing in a subshell"
1542 test_atexit_cleanup="{ $*
1543 - } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_atexit_cleanup"
1543 + } || eval_ret=\$?; $test_atexit_cleanup"
1544 }
1545
1546 # Deprecated wrapper for "git init", use "git init" directly instead