Merge branch 'jc/blame'
[gitweb.git] / t / test-lib.sh
index c861141667eb03eb42f559cbe174c6a76dd704c9..7a847ecbde018f36911ee6a1074ba169af1f350d 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
 
@@ -35,6 +51,7 @@ unset GIT_WORK_TREE
 unset GIT_EXTERNAL_DIFF
 unset GIT_INDEX_FILE
 unset GIT_OBJECT_DIRECTORY
+unset GIT_CEILING_DIRECTORIES
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
 GIT_MERGE_VERBOSITY=5
@@ -80,6 +97,8 @@ do
                debug=t; shift ;;
        -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
                immediate=t; shift ;;
+       -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
+               GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;;
        -h|--h|--he|--hel|--help)
                help=t; shift ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
@@ -91,6 +110,10 @@ do
        --no-python)
                # noop now...
                shift ;;
+       --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
+               valgrind=t; verbose=t; shift ;;
+       --tee)
+               shift ;; # was handled already
        *)
                break ;;
        esac
@@ -109,8 +132,9 @@ if test -n "$color"; then
                        *) test -n "$quiet" && return;;
                esac
                shift
-               echo "* $*"
+               printf "* %s" "$*"
                tput sgr0
+               echo
                )
        }
 else
@@ -123,7 +147,7 @@ fi
 
 error () {
        say_color error "error: $*"
-       trap - exit
+       trap - EXIT
        exit 1
 }
 
@@ -152,13 +176,14 @@ test_failure=0
 test_count=0
 test_fixed=0
 test_broken=0
+test_success=0
 
 die () {
        echo >&5 "FATAL: Unexpected exit with code $?"
        exit 1
 }
 
-trap 'die' exit
+trap 'die' EXIT
 
 # The semantics of the editor variables are that of invoking
 # sh -c "$EDITOR \"$@\"" files ...
@@ -188,11 +213,37 @@ 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"
+}
+
 # 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)
        say_color "" "  ok $test_count: $@"
 }
 
@@ -202,7 +253,7 @@ test_failure_ () {
        say_color error "FAIL $test_count: $1"
        shift
        echo "$@" | sed -e 's/^/        /'
-       test "$immediate" = "" || { trap - exit; exit 1; }
+       test "$immediate" = "" || { trap - EXIT; exit 1; }
 }
 
 test_known_broken_ok_ () {
@@ -302,6 +353,64 @@ test_expect_code () {
        echo >&3 ""
 }
 
+# test_external runs external test scripts that provide continuous
+# test output about their progress, and succeeds/fails on
+# zero/non-zero exit code.  It outputs the test output on stdout even
+# in non-verbose mode, and announces the external script with "* run
+# <n>: ..." before running it.  When providing relative paths, keep in
+# mind that all scripts run in "trash directory".
+# 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"
+       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 ($*)"
+               # Run command; redirect its stderr to &4 as in
+               # test_run_, but keep its stdout on our stdout even in
+               # non-verbose mode.
+               "$@" 2>&4
+               if [ "$?" = 0 ]
+               then
+                       test_ok_ "$descr"
+               else
+                       test_failure_ "$descr" "$@"
+               fi
+       fi
+}
+
+# Like test_external, but in addition tests that the command generated
+# no output on stderr.
+test_external_without_stderr () {
+       # The temporary file has no (and must have no) security
+       # implications.
+       tmp="$TMPDIR"; if [ -z "$tmp" ]; then tmp=/tmp; fi
+       stderr="$tmp/git-external-stderr.$$.tmp"
+       test_external "$@" 4> "$stderr"
+       [ -f "$stderr" ] || error "Internal error: $stderr disappeared."
+       descr="no stderr: $1"
+       shift
+       say >&3 "expecting no stderr from previous command"
+       if [ ! -s "$stderr" ]; then
+               rm "$stderr"
+               test_ok_ "$descr"
+       else
+               if [ "$verbose" = t ]; then
+                       output=`echo; echo Stderr is:; cat "$stderr"`
+               else
+                       output=
+               fi
+               # rm first in case test_failure exits.
+               rm "$stderr"
+               test_failure_ "$descr" "$@" "$output"
+       fi
+}
+
 # This is not among top-level (test_expect_success | test_expect_failure)
 # but is a prefix that can be used in the test script, like:
 #
@@ -316,7 +425,7 @@ test_expect_code () {
 
 test_must_fail () {
        "$@"
-       test $? -gt 0 -a $? -le 129
+       test $? -gt 0 -a $? -le 129 -o $? -gt 192
 }
 
 # test_cmp is a helper function to compare actual and expected output.
@@ -343,16 +452,26 @@ test_create_repo () {
        error "bug in the test script: not 1 parameter to test-create-repo"
        owd=`pwd`
        repo="$1"
-       mkdir "$repo"
+       mkdir -p "$repo"
        cd "$repo" || error "Cannot setup test environment"
-       "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >/dev/null 2>&1 ||
+       "$GIT_EXEC_PATH/git" init "--template=$owd/../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
+       trap - EXIT
+       test_results_dir="$TEST_DIRECTORY/test-results"
+       mkdir -p "$test_results_dir"
+       test_results_path="$test_results_dir/${0%-*}-$$"
+
+       echo "total $test_count" >> $test_results_path
+       echo "success $test_success" >> $test_results_path
+       echo "fixed $test_fixed" >> $test_results_path
+       echo "broken $test_broken" >> $test_results_path
+       echo "failed $test_failure" >> $test_results_path
+       echo "" >> $test_results_path
 
        if test "$test_fixed" != 0
        then
@@ -376,6 +495,11 @@ test_done () {
                # we will leave things as they are.
 
                say_color pass "passed all $msg"
+
+               test -d "$remove_trash" &&
+               cd "$(dirname "$remove_trash")" &&
+               rm -rf "$(basename "$remove_trash")"
+
                exit 0 ;;
 
        *)
@@ -387,11 +511,76 @@ test_done () {
 
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
-PATH=$(pwd)/..:$PATH
-GIT_EXEC_PATH=$(pwd)/..
+TEST_DIRECTORY=$(pwd)
+if test -z "$valgrind"
+then
+       PATH=$TEST_DIRECTORY/..:$PATH
+       GIT_EXEC_PATH=$TEST_DIRECTORY/..
+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
-unset GIT_CONFIG_LOCAL
 GIT_CONFIG_NOSYSTEM=1
 GIT_CONFIG_NOGLOBAL=1
 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
@@ -411,9 +600,10 @@ fi
 . ../GIT-BUILD-OPTIONS
 
 # Test repository
-test="trash directory"
+test="trash directory.$(basename "$0" .sh)"
+test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test"
 rm -fr "$test" || {
-       trap - exit
+       trap - EXIT
        echo >&5 "FATAL: Cannot prepare test area"
        exit 1
 }