Merge branch 'js/valgrind'
authorJunio C Hamano <gitster@pobox.com>
Thu, 5 Mar 2009 23:41:18 +0000 (15:41 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 5 Mar 2009 23:41:18 +0000 (15:41 -0800)
* js/valgrind:
valgrind: do not require valgrind 3.4.0 or newer
test-lib: avoid assuming that templates/ are in the GIT_EXEC_PATH
Tests: let --valgrind imply --verbose and --tee
Add a script to coalesce the valgrind outputs
t/Makefile: provide a 'valgrind' target
test-lib.sh: optionally output to test-results/$TEST.out, too
Valgrind support: check for more than just programming errors
valgrind: ignore ldso and more libz errors
Add valgrind support in test scripts

t/Makefile
t/README
t/test-lib.sh
t/valgrind/.gitignore [new file with mode: 0644]
t/valgrind/analyze.sh [new file with mode: 0755]
t/valgrind/default.supp [new file with mode: 0644]
t/valgrind/valgrind.sh [new file with mode: 0755]
index ed49c20b16b520da1b460960dbadd5d31c4927ba..09623414a755dc7e964a0d21768e136dba4556e2 100644 (file)
@@ -38,4 +38,7 @@ full-svn-test:
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
-.PHONY: pre-clean $(T) aggregate-results clean
+valgrind:
+       GIT_TEST_OPTS=--valgrind $(MAKE)
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
index f208cf1db972d580d977c356bb589cf6147247a7..d8f6c7de6d27e27a33982e82893baa3bb5ffd7fd 100644 (file)
--- a/t/README
+++ b/t/README
@@ -39,7 +39,8 @@ this:
     * passed all 3 test(s)
 
 You can pass --verbose (or -v), --debug (or -d), and --immediate
-(or -i) command line argument to the test.
+(or -i) command line argument to the test, or by setting GIT_TEST_OPTS
+appropriately before running "make".
 
 --verbose::
        This makes the test more verbose.  Specifically, the
@@ -58,6 +59,21 @@ You can pass --verbose (or -v), --debug (or -d), and --immediate
        This causes additional long-running tests to be run (where
        available), for more exhaustive testing.
 
+--valgrind::
+       Execute all Git binaries with valgrind and exit with status
+       126 on errors (just like regular tests, this will only stop
+       the test script when running under -i).  Valgrind errors
+       go to stderr, so you might want to pass the -v option, too.
+
+       Since it makes no sense to run the tests with --valgrind and
+       not see any output, this option implies --verbose.  For
+       convenience, it also implies --tee.
+
+--tee::
+       In addition to printing the test output to the terminal,
+       write it to files named 't/test-results/$TEST_NAME.out'.
+       As the names depend on the tests' file names, it is safe to
+       run the tests with this option in parallel.
 
 Skipping Tests
 --------------
index 0c455929e4e52795e616c3e2635f5899dff665c2..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
 
@@ -94,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
@@ -434,7 +454,7 @@ 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=$owd/../templates/blt/" >&3 2>&4 ||
        error "cannot run git init -- have you built things yet?"
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
@@ -492,8 +512,73 @@ 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
+       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
 GIT_CONFIG_NOSYSTEM=1
diff --git a/t/valgrind/.gitignore b/t/valgrind/.gitignore
new file mode 100644 (file)
index 0000000..d4ae667
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/templates
diff --git a/t/valgrind/analyze.sh b/t/valgrind/analyze.sh
new file mode 100755 (executable)
index 0000000..d8105d9
--- /dev/null
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+out_prefix=$(dirname "$0")/../test-results/valgrind.out
+output=
+count=0
+total_count=0
+missing_message=
+new_line='
+'
+
+# start outputting the current valgrind error in $out_prefix.++$count,
+# and the test case which failed in the corresponding .message file
+start_output () {
+       test -z "$output" || return
+
+       # progress
+       total_count=$(($total_count+1))
+       test -t 2 && printf "\rFound %d errors" $total_count >&2
+
+       count=$(($count+1))
+       output=$out_prefix.$count
+       : > $output
+
+       echo "*** $1 ***" > $output.message
+}
+
+finish_output () {
+       test ! -z "$output" || return
+       output=
+
+       # if a test case has more than one valgrind error, we need to
+       # copy the last .message file to the previous errors
+       test -z "$missing_message" || {
+               while test $missing_message -lt $count
+               do
+                       cp $out_prefix.$count.message \
+                               $out_prefix.$missing_message.message
+                       missing_message=$(($missing_message+1))
+               done
+               missing_message=
+       }
+}
+
+# group the valgrind errors by backtrace
+output_all () {
+       last_line=
+       j=0
+       i=1
+       while test $i -le $count
+       do
+               # output <number> <backtrace-in-one-line>
+               echo "$i $(tr '\n' ' ' < $out_prefix.$i)"
+               i=$(($i+1))
+       done |
+       sort -t ' ' -k 2 | # order by <backtrace-in-one-line>
+       while read number line
+       do
+               # find duplicates, do not output backtrace twice
+               if test "$line" != "$last_line"
+               then
+                       last_line=$line
+                       j=$(($j+1))
+                       printf "\nValgrind error $j:\n\n"
+                       cat $out_prefix.$number
+                       printf "\nfound in:\n"
+               fi
+               # print the test case where this came from
+               printf "\n"
+               cat $out_prefix.$number.message
+       done
+}
+
+handle_one () {
+       OLDIFS=$IFS
+       IFS="$new_line"
+       while read line
+       do
+               case "$line" in
+               # backtrace, possibly a new one
+               ==[0-9]*)
+
+                       # Does the current valgrind error have a message yet?
+                       case "$output" in
+                       *.message)
+                               test -z "$missing_message" &&
+                               missing_message=$count
+                               output=
+                       esac
+
+                       start_output $(basename $1)
+                       echo "$line" |
+                       sed 's/==[0-9]*==/==valgrind==/' >> $output
+                       ;;
+               # end of backtrace
+               '}')
+                       test -z "$output" || {
+                               echo "$line" >> $output
+                               test $output = ${output%.message} &&
+                               output=$output.message
+                       }
+                       ;;
+               # end of test case
+               '')
+                       finish_output
+                       ;;
+               # normal line; if $output is set, print the line
+               *)
+                       test -z "$output" || echo "$line" >> $output
+                       ;;
+               esac
+       done < $1
+       IFS=$OLDIFS
+
+       # just to be safe
+       finish_output
+}
+
+for test_script in "$(dirname "$0")"/../test-results/*.out
+do
+       handle_one $test_script
+done
+
+output_all
diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp
new file mode 100644 (file)
index 0000000..9e013fa
--- /dev/null
@@ -0,0 +1,45 @@
+{
+       ignore-zlib-errors-cond
+       Memcheck:Cond
+       obj:*libz.so*
+}
+
+{
+       ignore-zlib-errors-value8
+       Memcheck:Value8
+       obj:*libz.so*
+}
+
+{
+       ignore-zlib-errors-value4
+       Memcheck:Value4
+       obj:*libz.so*
+}
+
+{
+       ignore-ldso-cond
+       Memcheck:Cond
+       obj:*ld-*.so
+}
+
+{
+       ignore-ldso-addr8
+       Memcheck:Addr8
+       obj:*ld-*.so
+}
+
+{
+       ignore-ldso-addr4
+       Memcheck:Addr4
+       obj:*ld-*.so
+}
+
+{
+       writing-data-from-zlib-triggers-even-more-errors
+       Memcheck:Param
+       write(buf)
+       obj:/lib/ld-*.so
+       fun:write_in_full
+       fun:write_buffer
+       fun:write_loose_object
+}
diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh
new file mode 100755 (executable)
index 0000000..582b4dc
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+base=$(basename "$0")
+
+TRACK_ORIGINS=
+
+VALGRIND_VERSION=$(valgrind --version)
+VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)')
+VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)')
+test 3 -gt "$VALGRIND_MAJOR" ||
+test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" ||
+TRACK_ORIGINS=--track-origins=yes
+
+exec valgrind -q --error-exitcode=126 \
+       --leak-check=no \
+       --suppressions="$GIT_VALGRIND/default.supp" \
+       --gen-suppressions=all \
+       $TRACK_ORIGINS \
+       --log-fd=4 \
+       --input-fd=4 \
+       $GIT_VALGRIND_OPTIONS \
+       "$GIT_VALGRIND"/../../"$base" "$@"