Code

test-lib: allow exit trap to be used for cleanup by tests
[git.git] / t / test-lib.sh
index 1b4f21638574a5cd9eb70bd9ba5d16ff10ba4058..5fdc5d94a20cfcf231bf23590784c5146a3e44bc 100644 (file)
@@ -3,6 +3,22 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
+# if --tee was passed, write the output not only to the terminal, but
+# additionally to the file test-results/$BASENAME.out, too.
+case "$GIT_TEST_TEE_STARTED, $* " in
+done,*)
+       # do not redirect again
+       ;;
+*' --tee '*|*' --va'*)
+       mkdir -p test-results
+       BASE=test-results/$(basename "$0" .sh)
+       (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1;
+        echo $? > $BASE.exit) | tee $BASE.out
+       test "$(cat $BASE.exit)" = 0
+       exit
+       ;;
+esac
+
 # Keep the original TERM for say_color
 ORIGINAL_TERM=$TERM
 
@@ -94,8 +110,12 @@ do
        --no-python)
                # noop now...
                shift ;;
+       --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
+               valgrind=t; verbose=t; shift ;;
+       --tee)
+               shift ;; # was handled already
        *)
-               break ;;
+               echo "error: unknown test option '$1'" >&2; exit 1 ;;
        esac
 done
 
@@ -127,7 +147,7 @@ fi
 
 error () {
        say_color error "error: $*"
-       trap - exit
+       GIT_EXIT_OK=t
        exit 1
 }
 
@@ -159,11 +179,18 @@ test_broken=0
 test_success=0
 
 die () {
-       echo >&5 "FATAL: Unexpected exit with code $?"
-       exit 1
+       code=$?
+       if test -n "$GIT_EXIT_OK"
+       then
+               exit $code
+       else
+               echo >&5 "FATAL: Unexpected exit with code $code"
+               exit 1
+       fi
 }
 
-trap 'die' exit
+GIT_EXIT_OK=
+trap 'die' EXIT
 
 # The semantics of the editor variables are that of invoking
 # sh -c "$EDITOR \"$@\"" files ...
@@ -193,32 +220,87 @@ test_tick () {
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
 }
 
+# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+#
+# This will commit a file with the given contents and the given commit
+# message.  It will also add a tag with <message> as name.
+#
+# Both <file> and <contents> default to <message>.
+
+test_commit () {
+       file=${2:-"$1.t"}
+       echo "${3-$1}" > "$file" &&
+       git add "$file" &&
+       test_tick &&
+       git commit -m "$1" &&
+       git tag "$1"
+}
+
+# Call test_merge with the arguments "<message> <commit>", where <commit>
+# can be a tag pointing to the commit-to-merge.
+
+test_merge () {
+       test_tick &&
+       git merge -m "$1" "$2" &&
+       git tag "$1"
+}
+
+# This function helps systems where core.filemode=false is set.
+# Use it instead of plain 'chmod +x' to set or unset the executable bit
+# of a file in the working directory and add it to the index.
+
+test_chmod () {
+       chmod "$@" &&
+       git update-index --add "--chmod=$@"
+}
+
+# Use test_set_prereq to tell that a particular prerequisite is available.
+# The prerequisite can later be checked for in two ways:
+#
+# - Explicitly using test_have_prereq.
+#
+# - Implicitly by specifying the prerequisite tag in the calls to
+#   test_expect_{success,failure,code}.
+#
+# The single parameter is the prerequisite tag (a simple word, in all
+# capital letters by convention).
+
+test_set_prereq () {
+       satisfied="$satisfied$1 "
+}
+satisfied=" "
+
+test_have_prereq () {
+       case $satisfied in
+       *" $1 "*)
+               : yes, have it ;;
+       *)
+               ! : nope ;;
+       esac
+}
+
 # You are not expected to call test_ok_ and test_failure_ directly, use
 # the text_expect_* functions instead.
 
 test_ok_ () {
-       test_count=$(expr "$test_count" + 1)
-       test_success=$(expr "$test_success" + 1)
+       test_success=$(($test_success + 1))
        say_color "" "  ok $test_count: $@"
 }
 
 test_failure_ () {
-       test_count=$(expr "$test_count" + 1)
-       test_failure=$(expr "$test_failure" + 1);
+       test_failure=$(($test_failure + 1))
        say_color error "FAIL $test_count: $1"
        shift
        echo "$@" | sed -e 's/^/        /'
-       test "$immediate" = "" || { trap - exit; exit 1; }
+       test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; }
 }
 
 test_known_broken_ok_ () {
-       test_count=$(expr "$test_count" + 1)
        test_fixed=$(($test_fixed+1))
        say_color "" "  FIXED $test_count: $@"
 }
 
 test_known_broken_failure_ () {
-       test_count=$(expr "$test_count" + 1)
        test_broken=$(($test_broken+1))
        say_color skip "  still broken $test_count: $@"
 }
@@ -234,20 +316,23 @@ test_run_ () {
 }
 
 test_skip () {
-       this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
-       this_test="$this_test.$(expr "$test_count" + 1)"
+       test_count=$(($test_count+1))
        to_skip=
        for skp in $GIT_SKIP_TESTS
        do
-               case "$this_test" in
+               case $this_test.$test_count in
                $skp)
                        to_skip=t
                esac
        done
+       if test -z "$to_skip" && test -n "$prereq" &&
+          ! test_have_prereq "$prereq"
+       then
+               to_skip=t
+       fi
        case "$to_skip" in
        t)
                say_color skip >&3 "skipping test: $@"
-               test_count=$(expr "$test_count" + 1)
                say_color skip "skip $test_count: $1"
                : true
                ;;
@@ -258,8 +343,9 @@ test_skip () {
 }
 
 test_expect_failure () {
+       test "$#" = 3 && { prereq=$1; shift; } || prereq=
        test "$#" = 2 ||
-       error "bug in the test script: not 2 parameters to test-expect-failure"
+       error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
@@ -268,15 +354,16 @@ test_expect_failure () {
                then
                        test_known_broken_ok_ "$1"
                else
-                   test_known_broken_failure_ "$1"
+                       test_known_broken_failure_ "$1"
                fi
        fi
        echo >&3 ""
 }
 
 test_expect_success () {
+       test "$#" = 3 && { prereq=$1; shift; } || prereq=
        test "$#" = 2 ||
-       error "bug in the test script: not 2 parameters to test-expect-success"
+       error "bug in the test script: not 2 or 3 parameters to test-expect-success"
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
@@ -292,8 +379,9 @@ test_expect_success () {
 }
 
 test_expect_code () {
+       test "$#" = 4 && { prereq=$1; shift; } || prereq=
        test "$#" = 3 ||
-       error "bug in the test script: not 3 parameters to test-expect-code"
+       error "bug in the test script: not 3 or 4 parameters to test-expect-code"
        if ! test_skip "$@"
        then
                say >&3 "expecting exit code $1: $3"
@@ -317,15 +405,16 @@ test_expect_code () {
 # Usage: test_external description command arguments...
 # Example: test_external 'Perl API' perl ../path/to/test.pl
 test_external () {
-       test "$#" -eq 3 ||
-       error >&5 "bug in the test script: not 3 parameters to test_external"
+       test "$#" = 4 && { prereq=$1; shift; } || prereq=
+       test "$#" = 3 ||
+       error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
        descr="$1"
        shift
        if ! test_skip "$descr" "$@"
        then
                # Announce the script to reduce confusion about the
                # test output that follows.
-               say_color "" " run $(expr "$test_count" + 1): $descr ($*)"
+               say_color "" " run $test_count: $descr ($*)"
                # Run command; redirect its stderr to &4 as in
                # test_run_, but keep its stdout on our stdout even in
                # non-verbose mode.
@@ -409,17 +498,17 @@ test_create_repo () {
        repo="$1"
        mkdir -p "$repo"
        cd "$repo" || error "Cannot setup test environment"
-       "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >&3 2>&4 ||
+       "$GIT_EXEC_PATH/git-init" "--template=$TEST_DIRECTORY/../templates/blt/" >&3 2>&4 ||
        error "cannot run git init -- have you built things yet?"
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
 }
 
 test_done () {
-       trap - exit
+       GIT_EXIT_OK=t
        test_results_dir="$TEST_DIRECTORY/test-results"
        mkdir -p "$test_results_dir"
-       test_results_path="$test_results_dir/${0%-*}-$$"
+       test_results_path="$test_results_dir/${0%.sh}-$$"
 
        echo "total $test_count" >> $test_results_path
        echo "success $test_success" >> $test_results_path
@@ -459,8 +548,81 @@ test_done () {
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
 TEST_DIRECTORY=$(pwd)
-PATH=$TEST_DIRECTORY/..:$PATH
-GIT_EXEC_PATH=$(pwd)/..
+if test -z "$valgrind"
+then
+       if test -z "$GIT_TEST_INSTALLED"
+       then
+               PATH=$TEST_DIRECTORY/..:$PATH
+               GIT_EXEC_PATH=$TEST_DIRECTORY/..
+       else
+               GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
+               error "Cannot run git from $GIT_TEST_INSTALLED."
+               PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
+               GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
+       fi
+else
+       make_symlink () {
+               test -h "$2" &&
+               test "$1" = "$(readlink "$2")" || {
+                       # be super paranoid
+                       if mkdir "$2".lock
+                       then
+                               rm -f "$2" &&
+                               ln -s "$1" "$2" &&
+                               rm -r "$2".lock
+                       else
+                               while test -d "$2".lock
+                               do
+                                       say "Waiting for lock on $2."
+                                       sleep 1
+                               done
+                       fi
+               }
+       }
+
+       make_valgrind_symlink () {
+               # handle only executables
+               test -x "$1" || return
+
+               base=$(basename "$1")
+               symlink_target=$TEST_DIRECTORY/../$base
+               # do not override scripts
+               if test -x "$symlink_target" &&
+                   test ! -d "$symlink_target" &&
+                   test "#!" != "$(head -c 2 < "$symlink_target")"
+               then
+                       symlink_target=../valgrind.sh
+               fi
+               case "$base" in
+               *.sh|*.perl)
+                       symlink_target=../unprocessed-script
+               esac
+               # create the link, or replace it if it is out of date
+               make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
+       }
+
+       # override all git executables in TEST_DIRECTORY/..
+       GIT_VALGRIND=$TEST_DIRECTORY/valgrind
+       mkdir -p "$GIT_VALGRIND"/bin
+       for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-*
+       do
+               make_valgrind_symlink $file
+       done
+       OLDIFS=$IFS
+       IFS=:
+       for path in $PATH
+       do
+               ls "$path"/git-* 2> /dev/null |
+               while read file
+               do
+                       make_valgrind_symlink "$file"
+               done
+       done
+       IFS=$OLDIFS
+       PATH=$GIT_VALGRIND/bin:$PATH
+       GIT_EXEC_PATH=$GIT_VALGRIND/bin
+       export GIT_VALGRIND
+fi
 GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 unset GIT_CONFIG
 GIT_CONFIG_NOSYSTEM=1
@@ -485,7 +647,7 @@ fi
 test="trash directory.$(basename "$0" .sh)"
 test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
 rm -fr "$test" || {
-       trap - exit
+       GIT_EXIT_OK=t
        echo >&5 "FATAL: Cannot prepare test area"
        exit 1
 }
@@ -495,7 +657,8 @@ test_create_repo "$test"
 # in subprocesses like git equals our $PWD (for pathname comparisons).
 cd -P "$test" || exit 1
 
-this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
+this_test=${0##*/}
+this_test=${this_test%%-*}
 for skp in $GIT_SKIP_TESTS
 do
        to_skip=
@@ -513,3 +676,37 @@ do
                test_done
        esac
 done
+
+# Fix some commands on Windows
+case $(uname -s) in
+*MINGW*)
+       # Windows has its own (incompatible) sort and find
+       sort () {
+               /usr/bin/sort "$@"
+       }
+       find () {
+               /usr/bin/find "$@"
+       }
+       sum () {
+               md5sum "$@"
+       }
+       # git sees Windows-style pwd
+       pwd () {
+               builtin pwd -W
+       }
+       # no POSIX permissions
+       # backslashes in pathspec are converted to '/'
+       # exec does not inherit the PID
+       ;;
+*)
+       test_set_prereq POSIXPERM
+       test_set_prereq BSLASHPSPEC
+       test_set_prereq EXECKEEPSPID
+       ;;
+esac
+
+test -z "$NO_PERL" && test_set_prereq PERL
+
+# test whether the filesystem supports symbolic links
+ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
+rm -f y