test-lib: support running tests under valgrind in parallel

With the new --valgrind-parallel=<n> option, we support running the tests in a single test script under valgrind in parallel using 'n' processes. This really follows the dumbest approach possible, as follows: * We spawn the test script 'n' times, using a throw-away TEST_OUTPUT_DIRECTORY. Each of the instances is given options that ensures that it only runs every n-th test under valgrind, but together they cover the entire range. * We add up the numbers from the individual tests, and provide the usual output. This is really a gross hack at this point, and should be improved. In particular we should keep the actual outputs somewhere more easily discoverable, and summarize them to the user. Nevertheless, this is already workable and gives a speedup of more than 2 on a dual-core (hyperthreaded) machine, using n=4. This is expected since the overhead of valgrind is so big (on the order of 20x under good conditions, and a large startup overhead at every git invocation) that redundantly running the non-valgrind tests in between is not that expensive. Helped-by: Jeff King <peff@peff.net> Signed-off-by: Thomas Rast <trast@inf.ethz.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Thomas Rast committed Jun 23, 2013 at 20:12 UTC ad0e6233320b004f0d686f6887c803e508607bd2
1 file changed +84 -22
t/test-lib.sh
+84 -22
@@ -204,6 +204,15 @@ do
204 --valgrind-only=*)
205 valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
206 shift ;;
207 + --valgrind-parallel=*)
208 + valgrind_parallel=$(expr "z$1" : 'z[^=]*=\(.*\)')
209 + shift ;;
210 + --valgrind-only-stride=*)
211 + valgrind_only_stride=$(expr "z$1" : 'z[^=]*=\(.*\)')
212 + shift ;;
213 + --valgrind-only-offset=*)
214 + valgrind_only_offset=$(expr "z$1" : 'z[^=]*=\(.*\)')
215 + shift ;;
216 --tee)
217 shift ;; # was handled already
218 --root=*)
@@ -217,7 +226,7 @@ do
226 esac
227 done
228
220 -if test -n "$valgrind_only"
229 +if test -n "$valgrind_only" || test -n "$valgrind_only_stride"
230 then
231 test -z "$valgrind" && valgrind=memcheck
232 test -z "$verbose" && verbose_only="$valgrind_only"
@@ -367,7 +376,9 @@ maybe_teardown_verbose () {
376 last_verbose=t
377 maybe_setup_verbose () {
378 test -z "$verbose_only" && return
370 - if match_pattern_list $test_count $verbose_only
379 + if match_pattern_list $test_count $verbose_only ||
380 + { test -n "$valgrind_only_stride" &&
381 + expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null; }
382 then
383 exec 4>&2 3>&1
384 # Emit a delimiting blank line when going from
@@ -391,13 +402,17 @@ maybe_teardown_valgrind () {
402
403 maybe_setup_valgrind () {
404 test -z "$GIT_VALGRIND" && return
394 - if test -z "$valgrind_only"
405 + if test -z "$valgrind_only" && test -z "$valgrind_only_stride"
406 then
407 GIT_VALGRIND_ENABLED=t
408 return
409 fi
410 GIT_VALGRIND_ENABLED=
411 if match_pattern_list $test_count $valgrind_only
412 + then
413 + GIT_VALGRIND_ENABLED=t
414 + elif test -n "$valgrind_only_stride" &&
415 + expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null
416 then
417 GIT_VALGRIND_ENABLED=t
418 fi
@@ -552,6 +567,9 @@ test_done () {
567 esac
568 }
569
570 +
571 +# Set up a directory that we can put in PATH which redirects all git
572 +# calls to 'valgrind git ...'.
573 if test -n "$valgrind"
574 then
575 make_symlink () {
@@ -599,33 +617,42 @@ then
617 make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
618 }
619
602 - # override all git executables in TEST_DIRECTORY/..
603 - GIT_VALGRIND=$TEST_DIRECTORY/valgrind
604 - mkdir -p "$GIT_VALGRIND"/bin
605 - for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
606 - do
607 - make_valgrind_symlink $file
608 - done
609 - # special-case the mergetools loadables
610 - make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools"
611 - OLDIFS=$IFS
612 - IFS=:
613 - for path in $PATH
614 - do
615 - ls "$path"/git-* 2> /dev/null |
616 - while read file
620 + # In the case of --valgrind-parallel, we only need to do the
621 + # wrapping once, in the main script. The worker children all
622 + # have $valgrind_only_stride set, so we can skip based on that.
623 + if test -z "$valgrind_only_stride"
624 + then
625 + # override all git executables in TEST_DIRECTORY/..
626 + GIT_VALGRIND=$TEST_DIRECTORY/valgrind
627 + mkdir -p "$GIT_VALGRIND"/bin
628 + for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-*
629 do
618 - make_valgrind_symlink "$file"
630 + make_valgrind_symlink $file
631 done
620 - done
621 - IFS=$OLDIFS
632 + # special-case the mergetools loadables
633 + make_symlink "$GIT_BUILD_DIR"/mergetools "$GIT_VALGRIND/bin/mergetools"
634 + OLDIFS=$IFS
635 + IFS=:
636 + for path in $PATH
637 + do
638 + ls "$path"/git-* 2> /dev/null |
639 + while read file
640 + do
641 + make_valgrind_symlink "$file"
642 + done
643 + done
644 + IFS=$OLDIFS
645 + fi
646 PATH=$GIT_VALGRIND/bin:$PATH
647 GIT_EXEC_PATH=$GIT_VALGRIND/bin
648 export GIT_VALGRIND
649 GIT_VALGRIND_MODE="$valgrind"
650 export GIT_VALGRIND_MODE
651 GIT_VALGRIND_ENABLED=t
628 - test -n "$valgrind_only" && GIT_VALGRIND_ENABLED=
652 + if test -n "$valgrind_only" || test -n "$valgrind_only_stride"
653 + then
654 + GIT_VALGRIND_ENABLED=
655 + fi
656 export GIT_VALGRIND_ENABLED
657 elif test -n "$GIT_TEST_INSTALLED"
658 then
@@ -711,6 +738,41 @@ then
738 else
739 mkdir -p "$TRASH_DIRECTORY"
740 fi
741 +
742 +# Gross hack to spawn N sub-instances of the tests in parallel, and
743 +# summarize the results. Note that if this is enabled, the script
744 +# terminates at the end of this 'if' block.
745 +if test -n "$valgrind_parallel"
746 +then
747 + for i in $(test_seq 1 $valgrind_parallel)
748 + do
749 + root="$TRASH_DIRECTORY/vgparallel-$i"
750 + mkdir "$root"
751 + TEST_OUTPUT_DIRECTORY="$root" \
752 + ${SHELL_PATH} "$0" \
753 + --root="$root" --statusprefix="[$i] " \
754 + --valgrind="$valgrind" \
755 + --valgrind-only-stride="$valgrind_parallel" \
756 + --valgrind-only-offset="$i" &
757 + pids="$pids $!"
758 + done
759 + trap "kill $pids" INT TERM HUP
760 + wait $pids
761 + trap - INT TERM HUP
762 + for i in $(test_seq 1 $valgrind_parallel)
763 + do
764 + root="$TRASH_DIRECTORY/vgparallel-$i"
765 + eval "$(cat "$root/test-results/$(basename "$0" .sh)"-*.counts |
766 + sed 's/^\([a-z][a-z]*\) \([0-9][0-9]*\)/inner_\1=\2/')"
767 + test_count=$(expr $test_count + $inner_total)
768 + test_success=$(expr $test_success + $inner_success)
769 + test_fixed=$(expr $test_fixed + $inner_fixed)
770 + test_broken=$(expr $test_broken + $inner_broken)
771 + test_failure=$(expr $test_failure + $inner_failed)
772 + done
773 + test_done
774 +fi
775 +
776 # Use -P to resolve symlinks in our working directory so that the cwd
777 # in subprocesses like git equals our $PWD (for pathname comparisons).
778 cd -P "$TRASH_DIRECTORY" || exit 1