Merge branch 'ps/test-set-e-clean'

The test suite harness and many individual test scripts have been
updated to work correctly when 'set -e' is in effect, which helps
detect misspelled test commands.

* ps/test-set-e-clean:
  t: detect errors outside of test cases
  t9902: fix use of `read` with `set -e`
  t6002: fix use of `expr` with `set -e`
  t1301: don't fail in case setfacl(1) doesn't exist or fails
  t0008: silence error in subshell when using `grep -v`
  t: prepare `test_when_finished ()`/`test_atexit()` for `set -e`
  t: prepare execution of potentially failing commands for `set -e`
  t: prepare conditional test execution for `set -e`
  t: prepare `git config --unset` calls for `set -e`
  t: prepare `stop_git_daemon ()` for `set -e`
  t: prepare `test_must_fail ()` for `set -e`
  t: prepare `test_match_signal ()` calls for `set -e`
main
Junio C Hamano 2026-05-11 10:05:54 +09:00
commit 6a5621b604
23 changed files with 108 additions and 68 deletions

View File

@ -10,7 +10,14 @@ export TEST_CONTRIB_TOO=yes
case "$jobname" in case "$jobname" in
linux-musl-meson) linux-musl-meson)
MESONFLAGS="$MESONFLAGS -Drust=disabled" MESONFLAGS="$MESONFLAGS -Drust=disabled"
export GIT_TEST_USE_SET_E=yes
;; ;;
almalinux-*|debian-*|fedora-*|linux-*)
export GIT_TEST_USE_SET_E=yes
;;
esac

case "$jobname" in
fedora-breaking-changes-musl|linux-breaking-changes) fedora-breaking-changes-musl|linux-breaking-changes)
export WITH_BREAKING_CHANGES=YesPlease export WITH_BREAKING_CHANGES=YesPlease
MESONFLAGS="$MESONFLAGS -Dbreaking_changes=true" MESONFLAGS="$MESONFLAGS -Dbreaking_changes=true"

View File

@ -85,14 +85,16 @@ stop_git_daemon() {


# kill git-daemon child of git # kill git-daemon child of git
say >&3 "Stopping git daemon ..." say >&3 "Stopping git daemon ..."

kill "$GIT_DAEMON_PID" kill "$GIT_DAEMON_PID"
wait "$GIT_DAEMON_PID" >&3 2>&4 ret=0; wait "$GIT_DAEMON_PID" >&3 2>&4 || ret=$?
ret=$?
if ! test_match_signal 15 $ret if ! test_match_signal 15 $ret
then then
error "git daemon exited with status: $ret" error "git daemon exited with status: $ret"
fi fi
kill "$(cat "$GIT_DAEMON_PIDFILE")" 2>/dev/null
kill "$(cat "$GIT_DAEMON_PIDFILE")" 2>/dev/null || :
GIT_DAEMON_PID= GIT_DAEMON_PID=
rm -f git_daemon_output "$GIT_DAEMON_PIDFILE" rm -f git_daemon_output "$GIT_DAEMON_PIDFILE"
} }

View File

@ -15,8 +15,7 @@ GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn
SVN_TREE=$GIT_SVN_DIR/svn-tree SVN_TREE=$GIT_SVN_DIR/svn-tree
test_set_port SVNSERVE_PORT test_set_port SVNSERVE_PORT


svn >/dev/null 2>&1 if ! svn help >/dev/null 2>&1
if test $? -ne 1
then then
skip_all='skipping git svn tests, svn not found' skip_all='skipping git svn tests, svn not found'
test_done test_done
@ -27,13 +26,13 @@ export svnrepo
svnconf=$PWD/svnconf svnconf=$PWD/svnconf
export svnconf export svnconf


x=0
perl -w -e " perl -w -e "
use SVN::Core; use SVN::Core;
use SVN::Repos; use SVN::Repos;
\$SVN::Core::VERSION gt '1.1.0' or exit(42); \$SVN::Core::VERSION gt '1.1.0' or exit(42);
system(qw/svnadmin create --fs-type fsfs/, \$ENV{svnrepo}) == 0 or exit(41); system(qw/svnadmin create --fs-type fsfs/, \$ENV{svnrepo}) == 0 or exit(41);
" >&3 2>&4 " >&3 2>&4 || x=$?
x=$?
if test $x -ne 0 if test $x -ne 0
then then
if test $x -eq 42; then if test $x -eq 42; then

View File

@ -235,11 +235,10 @@ start_httpd() {


test_atexit stop_httpd test_atexit stop_httpd


"$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ if ! "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
-f "$TEST_PATH/apache.conf" $HTTPD_PARA \ -f "$TEST_PATH/apache.conf" $HTTPD_PARA \
-c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start \ -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start \
>&3 2>&4 >&3 2>&4
if test $? -ne 0
then then
cat "$HTTPD_ROOT_PATH"/error.log >&4 2>/dev/null cat "$HTTPD_ROOT_PATH"/error.log >&4 2>/dev/null
test_skip_or_die GIT_TEST_HTTPD "web server setup failed" test_skip_or_die GIT_TEST_HTTPD "web server setup failed"

View File

@ -42,12 +42,12 @@ test_expect_success 'create blob' '
' '


test_expect_success !MINGW 'a constipated git dies with SIGPIPE' ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE' '
OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) && OUT=$( ((large_git && echo 0 1>&3 || echo $? 1>&3) | :) 3>&1 ) &&
test_match_signal 13 "$OUT" test_match_signal 13 "$OUT"
' '


test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' '
OUT=$( ((trap "" PIPE && large_git; echo $? 1>&3) | :) 3>&1 ) && OUT=$( ((trap "" PIPE && large_git && echo 0 1>&3 || echo $? 1>&3) | :) 3>&1 ) &&
test_match_signal 13 "$OUT" test_match_signal 13 "$OUT"
' '



View File

@ -122,8 +122,8 @@ test_expect_success_multiple () {
fi fi
testname="$1" expect_all="$2" code="$3" testname="$1" expect_all="$2" code="$3"


expect_verbose=$( echo "$expect_all" | grep -v '^:: ' ) expect_verbose=$(echo "$expect_all" | grep -v '^:: ' || :)
expect=$( echo "$expect_verbose" | sed -e 's/.* //' ) expect=$(echo "$expect_verbose" | sed -e 's/.* //')


test_expect_success $prereq "$testname${no_index_opt:+ with $no_index_opt}" ' test_expect_success $prereq "$testname${no_index_opt:+ with $no_index_opt}" '
expect "$expect" && expect "$expect" &&

View File

@ -12,7 +12,7 @@ TEST_CREATE_REPO_NO_TEMPLATE=1
. ./test-lib.sh . ./test-lib.sh


# Remove a default ACL from the test dir if possible. # Remove a default ACL from the test dir if possible.
setfacl -k . 2>/dev/null setfacl -k . 2>/dev/null || :


# User must have read permissions to the repo -> failure on --shared=0400 # User must have read permissions to the repo -> failure on --shared=0400
test_expect_success 'shared = 0400 (faulty permission u-w)' ' test_expect_success 'shared = 0400 (faulty permission u-w)' '

View File

@ -260,7 +260,7 @@ test_expect_success 'choking "git rm" should not let it die with cruft (induce S


test_expect_success !MINGW 'choking "git rm" should not let it die with cruft (induce and check SIGPIPE)' ' test_expect_success !MINGW 'choking "git rm" should not let it die with cruft (induce and check SIGPIPE)' '
choke_git_rm_setup && choke_git_rm_setup &&
OUT=$( ((trap "" PIPE && git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) && OUT=$( ((trap "" PIPE && git rm -n "some-file-*" && echo 0 1>&3 || echo $? 1>&3) | :) 3>&1 ) &&
test_match_signal 13 "$OUT" && test_match_signal 13 "$OUT" &&
test_path_is_missing .git/index.lock test_path_is_missing .git/index.lock
' '

View File

@ -28,7 +28,8 @@ check_encoding () {
8859) 8859)
grep "^encoding ISO8859-1" ;; grep "^encoding ISO8859-1" ;;
*) *)
grep "^encoding ISO8859-1"; test "$?" != 0 ;; ret=0; grep "^encoding ISO8859-1" || ret=$?
test "$ret" != 0 ;;
esac || return 1 esac || return 1
j=$i j=$i
i=$(($i+1)) i=$(($i+1))

View File

@ -17,7 +17,7 @@ f() {


t() { t() {
use_config= use_config=
git config --unset diff.interHunkContext git config --unset diff.interHunkContext || :


case $# in case $# in
4) hunks=$4; cmd="diff -U$3";; 4) hunks=$4; cmd="diff -U$3";;
@ -40,11 +40,13 @@ t() {
test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks
" "


test -f $expected && if test -f $expected
test_expect_success "$label: check output" " then
git $cmd $file | grep -v '^index ' >actual && test_expect_success "$label: check output" "
test_cmp $expected actual git $cmd $file | grep -v '^index ' >actual &&
" test_cmp $expected actual
"
fi
} }


cat <<EOF >expected.f1.0.1 || exit 1 cat <<EOF >expected.f1.0.1 || exit 1

View File

@ -503,8 +503,8 @@ test_expect_success LONG_IS_64BIT 'set up repository with huge blob' '
# would generate the whole 64GB). # would generate the whole 64GB).
test_expect_success LONG_IS_64BIT 'generate tar with huge size' ' test_expect_success LONG_IS_64BIT 'generate tar with huge size' '
{ {
git archive HEAD { ret=0 && git archive HEAD || ret=$?; } &&
echo $? >exit-code echo "$ret" >exit-code
} | test_copy_bytes 4096 >huge.tar && } | test_copy_bytes 4096 >huge.tar &&
echo 141 >expect && echo 141 >expect &&
test_cmp expect exit-code test_cmp expect exit-code

View File

@ -27,13 +27,16 @@ test_bisection_diff()
# Test if bisection size is close to half of list size within # Test if bisection size is close to half of list size within
# tolerance. # tolerance.
# #
_bisect_err=$(expr $_list_size - $_bisection_size \* 2) _bisect_err=$(($_list_size - $_bisection_size * 2))
test "$_bisect_err" -lt 0 && _bisect_err=$(expr 0 - $_bisect_err) if test "$_bisect_err" -lt 0
_bisect_err=$(expr $_bisect_err / 2) ; # floor then
_bisect_err=$((0 - $_bisect_err))
fi
_bisect_err=$(($_bisect_err / 2)) ; # floor


test_expect_success \ test_expect_success "bisection diff $_bisect_option $_head $* <= $_max_diff" '
"bisection diff $_bisect_option $_head $* <= $_max_diff" \ test $_bisect_err -le $_max_diff
'test $_bisect_err -le $_max_diff' '
} }


date >path0 date >path0

View File

@ -198,7 +198,7 @@ test_expect_success !MINGW 'git submodule status --recursive propagates SIGPIPE'
( (
cd repo && cd repo &&
GIT_ALLOW_PROTOCOL=file git submodule add "$(pwd)"/../submodule && GIT_ALLOW_PROTOCOL=file git submodule add "$(pwd)"/../submodule &&
{ git submodule status --recursive 2>err; echo $?>status; } | { { ret=0 && git submodule status --recursive 2>err || ret=$?; } && echo $ret >status; } |
grep -q recursive-submodule-path-1 && grep -q recursive-submodule-path-1 &&
test_must_be_empty err && test_must_be_empty err &&
test_match_signal 13 "$(cat status)" test_match_signal 13 "$(cat status)"

View File

@ -220,17 +220,19 @@ check_dotx_symlink () {
) )
' '


test -n "$refuse_index" && if test -n "$refuse_index"
test_expect_success "refuse to load symlinked $name into index ($type)" ' then
test_must_fail \ test_expect_success "refuse to load symlinked $name into index ($type)" '
git -C $dir \ test_must_fail \
-c core.protectntfs \ git -C $dir \
-c core.protecthfs \ -c core.protectntfs \
read-tree $tree 2>err && -c core.protecthfs \
grep "invalid path.*$name" err && read-tree $tree 2>err &&
git -C $dir ls-files -s >out && grep "invalid path.*$name" err &&
test_must_be_empty out git -C $dir ls-files -s >out &&
' test_must_be_empty out
'
fi
} }


check_dotx_symlink gitmodules vanilla .gitmodules check_dotx_symlink gitmodules vanilla .gitmodules

View File

@ -773,8 +773,8 @@ test_expect_success TTY 'status --porcelain ignores color.status' '
' '


# recover unconditionally from color tests # recover unconditionally from color tests
git config --unset color.status git config --unset color.status || :
git config --unset color.ui git config --unset color.ui || :


test_expect_success 'status --porcelain respects -b' ' test_expect_success 'status --porcelain respects -b' '



View File

@ -68,8 +68,8 @@ test_expect_success 'authors-file overrode authors-prog' '
) )
' '


git --git-dir=x/.git config --unset svn.authorsfile git --git-dir=x/.git config --unset svn.authorsfile || :
git --git-dir=x/.git config --unset svn.authorsprog git --git-dir=x/.git config --unset svn.authorsprog || :


test_expect_success 'authors-prog imported user without email' ' test_expect_success 'authors-prog imported user without email' '
svn mkdir -m gg --username gg-hermit "$svnrepo"/gg && svn mkdir -m gg --username gg-hermit "$svnrepo"/gg &&

View File

@ -11,8 +11,7 @@ if ! test_have_prereq PERL; then
test_done test_done
fi fi


cvs >/dev/null 2>&1 if ! cvs version >/dev/null 2>&1
if test $? -ne 1
then then
skip_all='skipping git cvsexportcommit tests, cvs not found' skip_all='skipping git cvsexportcommit tests, cvs not found'
test_done test_done

View File

@ -17,12 +17,13 @@ if ! test_have_prereq PERL; then
skip_all='skipping git cvsserver tests, perl not available' skip_all='skipping git cvsserver tests, perl not available'
test_done test_done
fi fi
cvs >/dev/null 2>&1
if test $? -ne 1 if ! cvs version >/dev/null 2>&1
then then
skip_all='skipping git-cvsserver tests, cvs not found' skip_all='skipping git-cvsserver tests, cvs not found'
test_done test_done
fi fi

perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable' skip_all='skipping git-cvsserver tests, Perl SQLite interface unavailable'
test_done test_done

View File

@ -60,12 +60,12 @@ check_status_options() {
return $stat return $stat
} }


cvs >/dev/null 2>&1 if ! cvs version >/dev/null 2>&1
if test $? -ne 1
then then
skip_all='skipping git-cvsserver tests, cvs not found' skip_all='skipping git-cvsserver tests, cvs not found'
test_done test_done
fi fi

if ! test_have_prereq PERL if ! test_have_prereq PERL
then then
skip_all='skipping git-cvsserver tests, perl not available' skip_all='skipping git-cvsserver tests, perl not available'

View File

@ -68,12 +68,12 @@ check_diff() {


######### #########


cvs >/dev/null 2>&1 if ! cvs version >/dev/null 2>&1
if test $? -ne 1
then then
skip_all='skipping git-cvsserver tests, cvs not found' skip_all='skipping git-cvsserver tests, cvs not found'
test_done test_done
fi fi

if ! test_have_prereq PERL if ! test_have_prereq PERL
then then
skip_all='skipping git-cvsserver tests, perl not available' skip_all='skipping git-cvsserver tests, perl not available'

View File

@ -590,12 +590,10 @@ test_expect_success '__gitcomp - doesnt fail because of invalid variable name' '
__gitcomp "$invalid_variable_name" __gitcomp "$invalid_variable_name"
' '


read -r -d "" refs <<-\EOF refs='main
main
maint maint
next next
seen seen'
EOF


test_expect_success '__gitcomp_nl - trailing space' ' test_expect_success '__gitcomp_nl - trailing space' '
test_gitcomp_nl "m" "$refs" <<-EOF test_gitcomp_nl "m" "$refs" <<-EOF

View File

@ -1195,8 +1195,9 @@ test_must_fail () {
echo >&7 "test_must_fail: only 'git' is allowed: $*" echo >&7 "test_must_fail: only 'git' is allowed: $*"
return 1 return 1
fi fi
"$@" 2>&7
exit_code=$? exit_code=0; "$@" 2>&7 || exit_code=$?

if test $exit_code -eq 0 && ! list_contains "$_test_ok" success if test $exit_code -eq 0 && ! list_contains "$_test_ok" success
then then
echo >&4 "test_must_fail: command succeeded: $*" echo >&4 "test_must_fail: command succeeded: $*"
@ -1247,8 +1248,7 @@ test_might_fail () {
test_expect_code () { test_expect_code () {
want_code=$1 want_code=$1
shift shift
"$@" 2>&7 exit_code=0; "$@" 2>&7 || exit_code=$?
exit_code=$?
if test $exit_code = $want_code if test $exit_code = $want_code
then then
return 0 return 0
@ -1512,7 +1512,7 @@ test_when_finished () {
test "${BASH_SUBSHELL-0}" = 0 || test "${BASH_SUBSHELL-0}" = 0 ||
BUG "test_when_finished does nothing in a subshell" BUG "test_when_finished does nothing in a subshell"
test_cleanup="{ $* test_cleanup="{ $*
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } || eval_ret=\$?; $test_cleanup"
} }


# This function can be used to schedule some commands to be run # This function can be used to schedule some commands to be run
@ -1540,7 +1540,7 @@ test_atexit () {
test "${BASH_SUBSHELL-0}" = 0 || test "${BASH_SUBSHELL-0}" = 0 ||
BUG "test_atexit does nothing in a subshell" BUG "test_atexit does nothing in a subshell"
test_atexit_cleanup="{ $* test_atexit_cleanup="{ $*
} && (exit \"\$eval_ret\"); eval_ret=\$?; $test_atexit_cleanup" } || eval_ret=\$?; $test_atexit_cleanup"
} }


# Deprecated wrapper for "git init", use "git init" directly instead # Deprecated wrapper for "git init", use "git init" directly instead

View File

@ -15,6 +15,31 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see https://www.gnu.org/licenses/ . # along with this program. If not, see https://www.gnu.org/licenses/ .


# Enable the use of errexit so that any unexpected failures will cause us to
# abort tests, even when outside of a specific test case.
#
# Note that we only enable this on Bash 5 and newer, or when explicitly
# requested by the user via `GIT_TEST_USE_SET_E=true`. This ib secause `set -e`
# has wildly different behaviour across shells. The list of default-enabled
# shells may be extended going forward.
if test -z "$GIT_TEST_USE_SET_E" && test "${BASH_VERSINFO:=0}" -ge 5
then
GIT_TEST_USE_SET_E=true
fi

# We cannot use `test-tool env-helper` here, as it's not yet available.
case "${GIT_TEST_USE_SET_E:-false}" in
1|on|true|yes)
set -e
;;
0|off|false|no)
;;
*)
echo "GIT_TEST_USE_SET_E requires a boolean" >&2
exit 1
;;
esac

# Test the binaries we have just built. The tests are kept in # Test the binaries we have just built. The tests are kept in
# t/ subdirectory and are run in 'trash directory' subdirectory. # t/ subdirectory and are run in 'trash directory' subdirectory.
if test -z "$TEST_DIRECTORY" if test -z "$TEST_DIRECTORY"
@ -143,8 +168,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
################################################################ ################################################################
# It appears that people try to run tests without building... # It appears that people try to run tests without building...
GIT_BINARY="${GIT_TEST_INSTALLED:-$GIT_BUILD_DIR}/git$X" GIT_BINARY="${GIT_TEST_INSTALLED:-$GIT_BUILD_DIR}/git$X"
"$GIT_BINARY" >/dev/null
if test $? != 1 if ! "$GIT_BINARY" version >/dev/null
then then
if test -n "$GIT_TEST_INSTALLED" if test -n "$GIT_TEST_INSTALLED"
then then
@ -454,8 +479,10 @@ then
# from any previous runs. # from any previous runs.
>"$GIT_TEST_TEE_OUTPUT_FILE" >"$GIT_TEST_TEE_OUTPUT_FILE"


(GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; (
echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" ret=0 && GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1 || ret=$?
echo "$ret" >"$TEST_RESULTS_BASE.exit"
) | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 test "$(cat "$TEST_RESULTS_BASE.exit")" = 0
exit exit
fi fi