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>
This commit is contained in:
Thomas Rast 2013-06-23 20:12:59 +02:00 committed by Junio C Hamano
parent e939e15d24
commit ad0e623332

View File

@ -204,6 +204,15 @@ do
--valgrind-only=*) --valgrind-only=*)
valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)') valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;; shift ;;
--valgrind-parallel=*)
valgrind_parallel=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;;
--valgrind-only-stride=*)
valgrind_only_stride=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;;
--valgrind-only-offset=*)
valgrind_only_offset=$(expr "z$1" : 'z[^=]*=\(.*\)')
shift ;;
--tee) --tee)
shift ;; # was handled already shift ;; # was handled already
--root=*) --root=*)
@ -217,7 +226,7 @@ do
esac esac
done done
if test -n "$valgrind_only" if test -n "$valgrind_only" || test -n "$valgrind_only_stride"
then then
test -z "$valgrind" && valgrind=memcheck test -z "$valgrind" && valgrind=memcheck
test -z "$verbose" && verbose_only="$valgrind_only" test -z "$verbose" && verbose_only="$valgrind_only"
@ -367,7 +376,9 @@ maybe_teardown_verbose () {
last_verbose=t last_verbose=t
maybe_setup_verbose () { maybe_setup_verbose () {
test -z "$verbose_only" && return test -z "$verbose_only" && return
if match_pattern_list $test_count $verbose_only if match_pattern_list $test_count $verbose_only ||
{ test -n "$valgrind_only_stride" &&
expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null; }
then then
exec 4>&2 3>&1 exec 4>&2 3>&1
# Emit a delimiting blank line when going from # Emit a delimiting blank line when going from
@ -391,13 +402,17 @@ maybe_teardown_valgrind () {
maybe_setup_valgrind () { maybe_setup_valgrind () {
test -z "$GIT_VALGRIND" && return test -z "$GIT_VALGRIND" && return
if test -z "$valgrind_only" if test -z "$valgrind_only" && test -z "$valgrind_only_stride"
then then
GIT_VALGRIND_ENABLED=t GIT_VALGRIND_ENABLED=t
return return
fi fi
GIT_VALGRIND_ENABLED= GIT_VALGRIND_ENABLED=
if match_pattern_list $test_count $valgrind_only if match_pattern_list $test_count $valgrind_only
then
GIT_VALGRIND_ENABLED=t
elif test -n "$valgrind_only_stride" &&
expr $test_count "%" $valgrind_only_stride - $valgrind_only_offset = 0 >/dev/null
then then
GIT_VALGRIND_ENABLED=t GIT_VALGRIND_ENABLED=t
fi fi
@ -552,6 +567,9 @@ test_done () {
esac esac
} }
# Set up a directory that we can put in PATH which redirects all git
# calls to 'valgrind git ...'.
if test -n "$valgrind" if test -n "$valgrind"
then then
make_symlink () { make_symlink () {
@ -599,6 +617,11 @@ then
make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
} }
# In the case of --valgrind-parallel, we only need to do the
# wrapping once, in the main script. The worker children all
# have $valgrind_only_stride set, so we can skip based on that.
if test -z "$valgrind_only_stride"
then
# override all git executables in TEST_DIRECTORY/.. # override all git executables in TEST_DIRECTORY/..
GIT_VALGRIND=$TEST_DIRECTORY/valgrind GIT_VALGRIND=$TEST_DIRECTORY/valgrind
mkdir -p "$GIT_VALGRIND"/bin mkdir -p "$GIT_VALGRIND"/bin
@ -619,13 +642,17 @@ then
done done
done done
IFS=$OLDIFS IFS=$OLDIFS
fi
PATH=$GIT_VALGRIND/bin:$PATH PATH=$GIT_VALGRIND/bin:$PATH
GIT_EXEC_PATH=$GIT_VALGRIND/bin GIT_EXEC_PATH=$GIT_VALGRIND/bin
export GIT_VALGRIND export GIT_VALGRIND
GIT_VALGRIND_MODE="$valgrind" GIT_VALGRIND_MODE="$valgrind"
export GIT_VALGRIND_MODE export GIT_VALGRIND_MODE
GIT_VALGRIND_ENABLED=t GIT_VALGRIND_ENABLED=t
test -n "$valgrind_only" && GIT_VALGRIND_ENABLED= if test -n "$valgrind_only" || test -n "$valgrind_only_stride"
then
GIT_VALGRIND_ENABLED=
fi
export GIT_VALGRIND_ENABLED export GIT_VALGRIND_ENABLED
elif test -n "$GIT_TEST_INSTALLED" elif test -n "$GIT_TEST_INSTALLED"
then then
@ -711,6 +738,41 @@ then
else else
mkdir -p "$TRASH_DIRECTORY" mkdir -p "$TRASH_DIRECTORY"
fi fi
# Gross hack to spawn N sub-instances of the tests in parallel, and
# summarize the results. Note that if this is enabled, the script
# terminates at the end of this 'if' block.
if test -n "$valgrind_parallel"
then
for i in $(test_seq 1 $valgrind_parallel)
do
root="$TRASH_DIRECTORY/vgparallel-$i"
mkdir "$root"
TEST_OUTPUT_DIRECTORY="$root" \
${SHELL_PATH} "$0" \
--root="$root" --statusprefix="[$i] " \
--valgrind="$valgrind" \
--valgrind-only-stride="$valgrind_parallel" \
--valgrind-only-offset="$i" &
pids="$pids $!"
done
trap "kill $pids" INT TERM HUP
wait $pids
trap - INT TERM HUP
for i in $(test_seq 1 $valgrind_parallel)
do
root="$TRASH_DIRECTORY/vgparallel-$i"
eval "$(cat "$root/test-results/$(basename "$0" .sh)"-*.counts |
sed 's/^\([a-z][a-z]*\) \([0-9][0-9]*\)/inner_\1=\2/')"
test_count=$(expr $test_count + $inner_total)
test_success=$(expr $test_success + $inner_success)
test_fixed=$(expr $test_fixed + $inner_fixed)
test_broken=$(expr $test_broken + $inner_broken)
test_failure=$(expr $test_failure + $inner_failed)
done
test_done
fi
# Use -P to resolve symlinks in our working directory so that the cwd # Use -P to resolve symlinks in our working directory so that the cwd
# in subprocesses like git equals our $PWD (for pathname comparisons). # in subprocesses like git equals our $PWD (for pathname comparisons).
cd -P "$TRASH_DIRECTORY" || exit 1 cd -P "$TRASH_DIRECTORY" || exit 1