test-lib: add the '--stress' option to help reproduce occasional failures in flaky tests
cover test-lib: add the '--stress' option to help reproduce occasional failures in flaky tests
Inspired by Peff's 'stress' script mentioned in:
https://public-inbox.org/git/[email protected]/
the last patch in this series brings that functionality to our test library to help to reproduce failures in flaky tests. So
./t1234-foo --stress
will run that test script repeatedly in multiple parallel invocations, in the hope that the increased load creates enough variance in the timing of the test's commands that a failure is evenually triggered.
SZEDER Gábor (3):
test-lib: consolidate naming of test-results paths
test-lib-functions: introduce the 'test_set_port' helper function
test-lib: add the '--stress' option to run a test repeatedly under
load
t/README | 13 +++++- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +--- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 39 ++++++++++++++++ t/test-lib.sh | 99 +++++++++++++++++++++++++++++++++++----- 9 files changed, 143 insertions(+), 26 deletions(-)
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH 1/3 test-lib: consolidate naming of test-results paths
There are two places where we strip off any leading path components and the '.sh' suffix from the test script's pathname, and there are two places where we construct the filename of test output files in 't/test-results/'. The last patch in this series will add even more.
Factor these out into helper variables to avoid repeating ourselves.
--- t/test-lib.sh | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-)
t/test-lib.sh +10 -11
@@ -71,6 +71,9 @@ then exit 1 fi +TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_BASE="$TEST_OUTPUT_DIRECTORY/test-results/$TEST_NAME" + # 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
@@ -78,12 +81,11 @@ done,*) # do not redirect again ;; *' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*) - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" + mkdir -p "$(dirname "$TEST_RESULTS_BASE")" # Make this filename available to the sub-process in case it is using # --verbose-log. - GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out export GIT_TEST_TEE_OUTPUT_FILE # Truncate before calling "tee -a" to get rid of the results
@@ -91,8 +93,8 @@ done,*) >"$GIT_TEST_TEE_OUTPUT_FILE" (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 + echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit ;; esac
@@ -818,12 +820,9 @@ test_done () { if test -z "$HARNESS_ACTIVE" then - test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}.counts" + mkdir -p "$(dirname "$TEST_RESULTS_BASE")" - cat >"$test_results_path" <<-EOF + cat >"$TEST_RESULTS_BASE.counts" <<-EOF total $test_count success $test_success fixed $test_fixed
@@ -1029,7 +1028,7 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" +TRASH_DIRECTORY="trash directory.$TEST_NAME" test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" case "$TRASH_DIRECTORY" in /*) ;; # absolute path is good
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
Re: [PATCH 1/3] test-lib: consolidate naming of test-results paths
Quoted 6 lines · SZEDER Gábor
There are two places where we strip off any leading path components and the '.sh' suffix from the test script's pathname, and there are two places where we construct the filename of test output files in 't/test-results/'. The last patch in this series will add even more.
Factor these out into helper variables to avoid repeating ourselves.
Makes sense.
t/test-lib.sh then · line 75
Quoted 2 lines
t/test-lib.sh +2
-0+TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_BASE="$TEST_OUTPUT_DIRECTORY/test-results/$TEST_NAME"
Hmm, since we are building up this BASE variable anyway, why not:
TEST_RESULTS_DIR=$TEST_OUTPUT_DIRECTORY/test-results TEST_RESULTS_BASE=$TEST_RESULTS_DIR/$TEST_NAME
? That saves having to run dirname on it later.
I guess one could argue that saying "the directory name of the file I'm writing" is more readable. I just generally try to avoid extra manipulation of the strings when possible (especially in shell).
-Peff
PATCH 2/3 test-lib-functions: introduce the 'test_set_port' helper function
In reply to SZEDER GáborSeveral test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Factor out finding the port for a daemon into the common helper function 'test_set_port' to avoid repeating ourselves.
Take special care of test scripts with "low" numbers:
- Test numbers below 1024 would result in a port that's only usable as root, so set their port to '10000 + test-nr' to make sure it doesn't interfere with other tests in the test suite. This makes the hardcoded port number in 't0410-partial-clone.sh' unnecessary, remove it.
- The shell's arithmetic evaluation interprets numbers with leading zeros as octal values, which means that test number below 1000 and containing the digits 8 or 9 will trigger an error. Remove all leading zeros from the test numbers to prevent this.
Note that the Perforce tests are unlike the other tests involving daemons in that:
- 'lib-git-p4.sh' doesn't use the test's number for unique port as is, but does a bit of additional arithmetic on top [1].
- The port is not overridable via an environment variable.
With this patch even Perforce tests will use the test's number as default port, and it will be overridable via the P4DPORT environment variable.
[1] Commit fc00233071 (git-p4 tests: refactor and cleanup, 2011-08-22) introduced that "unusual" unique port computation without explaining why it was necessary (as opposed to simply using the test number as is). It seems to be just unnecessary complication, and in any case that commit came way before the "test nr as unique port" got "standardized" for other daemons in commits c44132fcf3 (tests: auto-set git-daemon port, 2014-02-10), 3bb486e439 (tests:
auto-set LIB_HTTPD_PORT from test name, 2014-02-10), and bf9d7df950 (t/lib-git-svn.sh: improve svnserve tests with parallel make test, 2017-12-01).
--- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +-------- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 41 insertions(+), 13 deletions(-)
t/lib-git-daemon.sh +1 -1
@@ -28,7 +28,7 @@ then test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" fi -LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} +test_set_port LIB_GIT_DAEMON_PORT GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
t/lib-git-p4.sh +1 -8
@@ -53,14 +53,7 @@ time_in_seconds () { (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } -# Try to pick a unique port: guess a large number, then hope -# no more than one of each test is running. -# -# This does not handle the case where somebody else is running the -# same tests and has chosen the same ports. -testid=${this_test#t} -git_p4_test_start=9800 -P4DPORT=$((10669 + ($testid - $git_p4_test_start))) +test_set_port P4DPORT P4PORT=localhost:$P4DPORT P4CLIENT=client
t/lib-git-svn.sh +1 -1
@@ -13,6 +13,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +test_set_port SVNSERVE_PORT svn >/dev/null 2>&1 if test $? -ne 1
@@ -119,7 +120,6 @@ require_svnserve () { } start_svnserve () { - SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} svnserve --listen-port $SVNSERVE_PORT \ --root "$rawsvnrepo" \ --listen-once \
t/lib-httpd.sh +1 -1
@@ -82,7 +82,7 @@ case $(uname) in esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}} +test_set_port LIB_HTTPD_PORT TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd
t/t0410-partial-clone.sh +0 -1
@@ -480,7 +480,6 @@ test_expect_success 'gc stops traversal when a missing but promised object is re ! grep "$TREE_HASH" out ' -LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
t/t5512-ls-remote.sh +1 -1
@@ -260,7 +260,7 @@ test_lazy_prereq GIT_DAEMON ' # This test spawns a daemon, so run it only if the user would be OK with # testing with git-daemon. test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' - JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + test_set_port JGIT_DAEMON_PORT && JGIT_DAEMON_PID= && git init --bare empty.git && >empty.git/git-daemon-export-ok &&
t/test-lib-functions.sh +36 -0
@@ -1263,3 +1263,39 @@ test_oid () { fi && eval "printf '%s' \"\${$var}\"" } + +# Choose a port number based on the test script's number and store it in +# the given variable name, unless that variable already contains a number. +test_set_port () { + local var=$1 port + + if test $# -ne 1 || test -z "$var" + then + BUG "test_set_port requires a variable name" + fi + + eval port=\"\${$var}\" + case "$port" in + "") + # No port is set in the given env var, use the test + # number as port number instead. + # Remove not only the leading 't', but all leading zeros + # as well, so the arithmetic below won't (mis)interpret + # a test number like '0123' as an octal value. + port=${this_test#${this_test%%[1-9]*}} + if test "${port:-0}" -lt 1024 + then + # root-only port, use a larger one instead. + port=$(($port + 10000)) + fi + + eval $var=$port + ;; + *[^0-9]*) + error >&7 "invalid port number: $port" + ;; + *) + # The user has specified the port. + ;; + esac +}
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
Re: [PATCH 2/3] test-lib-functions: introduce the 'test_set_port' helper function
Quoted 11 lines · SZEDER Gábor
Several test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Factor out finding the port for a daemon into the common helper function '
test_set_port' to avoid repeating ourselves.
OK. Looks like this should keep the same port numbers for all of the existing tests, which I think is good. As nice as choosing random high port numbers might be for some cases, it can also be confusing when there are random conflicts.
I've also run into non-random conflicts, but at least once you figure them out they're predictable (the most confusing I've seen is that adb, the android debugger, sometimes but not always leaves a daemon hanging around on port 5561).
Quoted 7 lines
Take special care of test scripts with "low" numbers:
- Test numbers below 1024 would result in a port that's only usable as root, so set their port to '10000 + test-nr' to make sure it doesn't interfere with other tests in the test suite. This makes the hardcoded port number in '
t0410-partial-clone.sh' unnecessary, remove it.
This part is what made me think a bit more about conflicting with dynamically allocated ports. Arguably the http parts of t0410 ought to be in a much higher-numbered script, but I don't know that we've held over the years very well to the idea that scripts should only depend on the bits from lower numbered scripts.
Quoted 9 lines
--- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +-------- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 41 insertions(+), 13 deletions(-)
The patch itself looks good to me.
t/test-lib-functions.sh test_oid () { · line 1278
Quoted 2 lines
t/test-lib-functions.sh +2
-0+ eval port=\"\${$var}\" + case "$port" in
The quotes in the eval'd variable assignment aren't necessary. Usually I don't mind them in a simple:
FOO="$bar"
even though they're redundant (and there were a few instances in the prior patch, I think). But here the escaping makes it harder to read, compared to:
eval port=\$$var
It might be worth simplifying, but I don't feel strongly about it (we'd run into problems if $var contained spaces with either variant, but I don't think that's worth caring about).
-Peff
Re: [PATCH 2/3] test-lib-functions: introduce the 'test_set_port' helper function
Quoted 11 lines · Jeff King
Quoted 9 lines · SZEDER Gábor
Several test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Quoted 5 lines
Quoted 2 lines
Factor out finding the port for a daemon into the common helper function '
test_set_port' to avoid repeating ourselves.OK. Looks like this should keep the same port numbers for all of the existing tests, which I think is good.
Not for all existing tests, though: t0410 and the 'git p4' tests do get different ports.
Hrm, speaking of affecting 'git p4' tests... I should have put Luke on Cc, so doing it now.
Re: [PATCH 2/3] test-lib-functions: introduce the 'test_set_port' helper function
Quoted 12 lines · SZEDER Gábor
Quoted 11 lines · Jeff King
Quoted 9 lines · SZEDER Gábor
Several test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
Factor out finding the port for a daemon into the common helper function '
test_set_port' to avoid repeating ourselves.OK. Looks like this should keep the same port numbers for all of the existing tests, which I think is good.
Not for all existing tests, though: t0410 and the 'git p4' tests do get different ports.
Yeah, I should have said "most". The important thing is that they remain static and predictable for normal non-stress runs, which they do.
-Peff
RFC PATCH 3/3 test-lib: add the '--stress' option to run a test repeatedly under load
In reply to SZEDER GáborUnfortunately, we have a few flaky tests, whose failures tend to be hard to reproduce. We've found that the best we can do to reproduce such a failure is to run the test repeatedly while the machine is under load, and wait in the hope that the load creates enough variance in the timing of the test's commands that a failure is evenually triggered. I have a command to do that, and I noticed that two other contributors have rolled their own scripts to do the same, all choosing slightly different approaches.
To help reproduce failures in flaky tests, introduce the '--stress' option to run a test script repeatedly in multiple parallel invocations until one of them fails, thereby using the test script itself to increase the load on the machine.
The number of parallel invocations is determined by, in order of
precedence: the number specified as '--stress=<N>', or the value of the GIT_TEST_STRESS_LOAD environment variable, or twice the number of available processors in '/proc/cpuinfo', or 8.
To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
- Make '--stress' imply '--verbose-log' and discard the test's standard ouput and error; dumping the output of several parallel tests to the terminal would create a big ugly mess.
'wait' for all parallel jobs before exiting (either because a failure was found or because the user lost patience and aborted the stress test), allowing the still running tests to finish. Otherwise the "OK X.Y" progress output from the last iteration would likely arrive after the user got back the shell prompt, interfering with typing in the next command. OTOH, this waiting might induce a considerable delay between hitting ctrl-C and the test actually exiting; I'm not sure this is the right tradeoff.
Based on Jeff King's 'stress' script.
--- t/README | 13 ++++++- t/test-lib-functions.sh | 7 +++- t/test-lib.sh | 82 +++++++++++++++++++++++++++++++++++++++-- 3 files changed, 96 insertions(+), 6 deletions(-)
t/README +12 -1
@@ -186,6 +186,16 @@ appropriately before running "make". this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. +--stress:: +--stress=<N>:: + Run the test script repeatedly in multiple parallel + invocations until one of them fails. Useful for reproducing + rare failures in flaky tests. The number of parallel + invocations is, in order of precedence: <N>, or the value of + the GIT_TEST_STRESS_LOAD environment variable, or twice the + number of available processors in '/proc/cpuinfo', or 8. + Implies `--verbose-log`. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various
@@ -425,7 +435,8 @@ This test harness library does the following things: - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash directory.$test_name_without_dotsh', with t/ subject to change by - the --root option documented above. + the --root option documented above, and a '.stress-<N>' suffix + appended by the --stress option. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave
t/test-lib-functions.sh +5 -2
@@ -1288,8 +1288,6 @@ test_set_port () { # root-only port, use a larger one instead. port=$(($port + 10000)) fi - - eval $var=$port ;; *[^0-9]*) error >&7 "invalid port number: $port"
@@ -1298,4 +1296,9 @@ test_set_port () { # The user has specified the port. ;; esac + + # Make sure that parallel '--stress' test jobs get different + # ports. + port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0})) + eval $var=$port }
t/test-lib.sh +79 -3
107 lines not on this page · raw message
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 4 lines · SZEDER Gábor
The number of parallel invocations is determined by, in order of
precedence: the number specified as '--stress=<N>', or the value of theGIT_TEST_STRESS_LOADenvironment variable, or twice the number of available processors in '/proc/cpuinfo', or 8.
With this series we have at least 3 ways to get this number. First online_cpus() in the C code, then Peff's recent getconf _NPROCESSORS_ONLN in doc-diff, and now this /proc/cpuinfo parsing.
Perhaps it makes sense to split online_cpus() into a helper to use from the shellscripts instead?
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 8 lines · Ævar Arnfjörð Bjarmason
Quoted 4 lines · SZEDER Gábor
The number of parallel invocations is determined by, in order of
precedence: the number specified as '--stress=<N>', or the value of theGIT_TEST_STRESS_LOADenvironment variable, or twice the number of available processors in '/proc/cpuinfo', or 8.
Quoted 6 lines
With this series we have at least 3 ways to get this number. First
online_cpus()in the C code, then Peff's recentgetconf _NPROCESSORS_ONLNin doc-diff, and now this/proc/cpuinfoparsing.Perhaps it makes sense to split
online_cpus()into a helper to use from the shellscripts instead?
I don't think so, but I will update this patch to use 'getconf' instead.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
In reply to Ævar Arnfjörð BjarmasonQuoted 11 lines · Ævar Arnfjörð Bjarmason
Quoted 4 lines · SZEDER Gábor
The number of parallel invocations is determined by, in order of
precedence: the number specified as '--stress=<N>', or the value of theGIT_TEST_STRESS_LOADenvironment variable, or twice the number of available processors in '/proc/cpuinfo', or 8.With this series we have at least 3 ways to get this number. First
online_cpus()in the C code, then Peff's recentgetconf _NPROCESSORS_ONLNin doc-diff, and now this/proc/cpuinfoparsing.
To be fair, I only added the "getconf" thing because somebody complained that I was parsing /proc/cpuinfo, and suggested it instead. ;)
I don't think it's especially portable, but it should work on Linux and modern BSD/macOS, which may be enough (unlike doc-diff, I'd be a little more inclined to ask somebody on a random platform to stress test if they're hitting a bug).
Quoted 2 lines
Perhaps it makes sense to split
online_cpus()into a helper to use from the shellscripts instead?
I think somebody proposed that in a recent thread for other reasons, too. I'd be OK with that, but probably just using getconf is reasonable in the meantime.
-Peff
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
In reply to SZEDER GáborQuoted 34 lines · SZEDER Gábor
Unfortunately, we have a few flaky tests, whose failures tend to be hard to reproduce. We've found that the best we can do to reproduce such a failure is to run the test repeatedly while the machine is under load, and wait in the hope that the load creates enough variance in the timing of the test's commands that a failure is evenually triggered. I have a command to do that, and I noticed that two other contributors have rolled their own scripts to do the same, all choosing slightly different approaches.
To help reproduce failures in flaky tests, introduce the '--stress' option to run a test script repeatedly in multiple parallel invocations until one of them fails, thereby using the test script itself to increase the load on the machine.
The number of parallel invocations is determined by, in order of
precedence: the number specified as '--stress=<N>', or the value of theGIT_TEST_STRESS_LOADenvironment variable, or twice the number of available processors in '/proc/cpuinfo', or 8.To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
- Make '--stress' imply '--verbose-log' and discard the test's standard ouput and error; dumping the output of several parallel tests to the terminal would create a big ugly mess.
Quoted 8 lines
'wait' for all parallel jobs before exiting (either because a failure was found or because the user lost patience and aborted the stress test), allowing the still running tests to finish. Otherwise the "OK X.Y" progress output from the last iteration would likely arrive after the user got back the shell prompt, interfering with typing in the next command. OTOH, this waiting might induce a considerable delay between hitting ctrl-C and the test actually exiting; I'm not sure this is the right tradeoff.
I think it makes sense to generalize this and split it up into two features.
It's a frequent annoyance of mine in the test suite that I'm e.g. running t*.sh with some parallel "prove" in one screen, and then I run tABCD*.sh manually, and get unlucky because they use the same trash dir, and both tests go boom.
You can fix that with --root, which is much of what this patch does. My one-liner for doing --stress has been something like:
perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh --root=trash-{} -v'But it would be great if I didn't have to worry about that and could just run two concurrent:
./t0000-basic.sh
So I think we could just set some env variable where instead of having the predictable trash directory we have a $TRASHDIR.$N as this patch does, except we pick $N by locking some test-runs/tABCD.Nth file with flock() during the run.
Then a stress mode like this would just be:
GIT_TEST_FLOCKED_TRASH_DIRS=1 perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh'
And sure, we could ship a --stress option too, but it wouldn't be magical in any way, just another "spawn N in a loop" implementation, but you could also e.g. use GNU parallel to drive it, and without needing to decide to stress test in advance, since we'd either flock() the trash dir, or just mktemp(1)-it.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 13 lines · Ævar Arnfjörð Bjarmason
It's a frequent annoyance of mine in the test suite that I'm e.g. running t*.sh with some parallel "prove" in one screen, and then I run tABCD*.sh manually, and get unlucky because they use the same trash dir, and both tests go boom.
You can fix that with --root, which is much of what this patch does. My one-liner for doing --stress has been something like:
perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh --root=trash-{} -v'But it would be great if I didn't have to worry about that and could just run two concurrent:
Quoted 6 lines
./t0000-basic.shSo I think we could just set some env variable where instead of having the predictable trash directory we have a $TRASHDIR.$N as this patch does, except we pick $N by locking some test-runs/tABCD.Nth file with
flock()during the run.
That actually sounds kind of annoying when doing a single run, since now you have this extra ".N". I guess it would at least be predictable, and I tend to tab-complete the trash dirs anyway.
I accomplish the same thing by doing my "big" runs using --root specified in config.mak (which points to a RAM disk -- if you're not using one to run the tests, you really should look into it, as it's way faster). And then for one-offs, investigating failures, etc, I do "cd t && ./t0000-basic.sh -v -i", which naturally runs in the local directory.
-Peff
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
In reply to Ævar Arnfjörð BjarmasonQuoted 13 lines · Ævar Arnfjörð Bjarmason
It's a frequent annoyance of mine in the test suite that I'm e.g. running t*.sh with some parallel "prove" in one screen, and then I run tABCD*.sh manually, and get unlucky because they use the same trash dir, and both tests go boom.
You can fix that with --root, which is much of what this patch does. My one-liner for doing --stress has been something like:
perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh --root=trash-{} -v'But it would be great if I didn't have to worry about that and could just run two concurrent:
Quoted 6 lines
./t0000-basic.shSo I think we could just set some env variable where instead of having the predictable trash directory we have a $TRASHDIR.$N as this patch does, except we pick $N by locking some test-runs/tABCD.Nth file with
flock()during the run.
Is 'flock' portable? It doesn't appear so.
Quoted 3 lines
Then a stress mode like this would just be:
GIT_TEST_FLOCKED_TRASH_DIRS=1 perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh'
This doesn't address the issue of TCP ports for various daemons.
Quoted 3 lines
And sure, we could ship a --stress option too, but it wouldn't be magical in any way, just another "spawn N in a loop" implementation, but you could also e.g. use GNU parallel to drive it, and without needing to
GNU 'parallel' may or may not be available on the platform, that's why I stuck with the barebones shell-loops-in-the-background approach.
Quoted 2 lines
decide to stress test in advance, since we'd either
flock()the trash dir, or just mktemp(1)-it.
While 'mktemp' seems to be more portable than 'flock', it doesn't seem to be portable enough; at least it's not in POSIX.
And in general I'd prefer deterministic trash directory names. If I re-run a failed test after fixing the bug, then currently the trash directory will be overwritten, and that's good. With 'mktemp's junk in the trash direcory's name it won't be overwritten, and if my bugfix doesn't work, then I'll start accumulating trash directories and won't even know which one is from the last run.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 14 lines · SZEDER Gábor
Quoted 13 lines · Ævar Arnfjörð Bjarmason
It's a frequent annoyance of mine in the test suite that I'm e.g. running t*.sh with some parallel "prove" in one screen, and then I run tABCD*.sh manually, and get unlucky because they use the same trash dir, and both tests go boom.
You can fix that with --root, which is much of what this patch does. My one-liner for doing --stress has been something like:
perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh --root=trash-{} -v'But it would be great if I didn't have to worry about that and could just run two concurrent:
Quoted 8 lines
Quoted 6 lines
./t0000-basic.shSo I think we could just set some env variable where instead of having the predictable trash directory we have a $TRASHDIR.$N as this patch does, except we pick $N by locking some test-runs/tABCD.Nth file with
flock()during the run.Is 'flock' portable? It doesn't appear so.
Portable enough, and since it's just an alias for "grab lock atomically" it can devolve into other more basic FS functions on systems that don't have it.
Quoted 5 lines
Quoted 3 lines
Then a stress mode like this would just be:
GIT_TEST_FLOCKED_TRASH_DIRS=1 perl -E 'say ++$_ while 1' | parallel --jobs=100% --halt-on-error soon,fail=1 './t0000-basic.sh'This doesn't address the issue of TCP ports for various daemons.
Once we have a test ABCD and slot offset N (for a fixed size of N) we can pick a port from that.
Quoted 6 lines
Quoted 3 lines
And sure, we could ship a --stress option too, but it wouldn't be magical in any way, just another "spawn N in a loop" implementation, but you could also e.g. use GNU parallel to drive it, and without needing to
GNU 'parallel' may or may not be available on the platform, that's why I stuck with the barebones shell-loops-in-the-background approach.
Yes, my point is not that you should drop that part of your patch for using GNU parallel, but just to demonstrate that we can get pretty close to this for most tests with an external tool, and that it would make this sort of thing work for concurrent tests without needing to decide to concurrently test in advance.
Quoted 5 lines
Quoted 2 lines
decide to stress test in advance, since we'd either
flock()the trash dir, or just mktemp(1)-it.While 'mktemp' seems to be more portable than 'flock', it doesn't seem to be portable enough; at least it's not in POSIX.>
We are already relying on stuff like mktemp() being reliable for e.g. the split index.
Quoted 6 lines
And in general I'd prefer deterministic trash directory names. If I re-run a failed test after fixing the bug, then currently the trash directory will be overwritten, and that's good. With 'mktemp's junk in the trash direcory's name it won't be overwritten, and if my bugfix doesn't work, then I'll start accumulating trash directories and won't even know which one is from the last run.
In general you wouldn't need to set GIT_TEST_FLOCKED_TRASH_DIRS=1 and would get the same monolithic trash names as now, and for something like the flock() version it could use a job number as a suffix like your patch does.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 8 lines · Ævar Arnfjörð Bjarmason
Quoted 5 lines
Quoted 2 lines
decide to stress test in advance, since we'd either
flock()the trash dir, or just mktemp(1)-it.While 'mktemp' seems to be more portable than 'flock', it doesn't seem to be portable enough; at least it's not in POSIX.>
We are already relying on stuff like
mktemp()being reliable for e.g. the split index.
That's the mktemp() function from libc; I meant the 'mktemp' command that we would use this early in 'test-lib.sh', where PATH has not been set up for testing yet.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 7 lines · SZEDER Gábor
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 5 lines
Quoted 2 lines
decide to stress test in advance, since we'd either
flock()the trash dir, or just mktemp(1)-it.While 'mktemp' seems to be more portable than 'flock', it doesn't seem to be portable enough; at least it's not in POSIX.>
Quoted 6 lines
Quoted 2 lines
We are already relying on stuff like
mktemp()being reliable for e.g. the split index.That's the
mktemp()function from libc; I meant the 'mktemp' command that we would use this early in 'test-lib.sh', where PATH has not been set up for testing yet.
Ah, if that ever becomes an issue it's a one-line test-tool wrapper.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
In reply to SZEDER GáborQuoted 6 lines · SZEDER Gábor
To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
Makes sense.
Quoted 3 lines
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
In my script I just use an arbitrary sequence of ports. That means two stress runs may stomp on each other, but stress runs tend not to interfere with regular runs (whereas with your scheme, a stress run of t5561 is going to stomp on t5562). It probably doesn't matter much either way, as I tend not to do too much with the machine during a stress run.
Quoted 3 lines
- Make '--stress' imply '--verbose-log' and discard the test's standard ouput and error; dumping the output of several parallel tests to the terminal would create a big ugly mess.
Makes sense. My script just redirects the output to a file, but it predates --verbose-log.
My script always runs with "-x". I guess it's not that hard to add it if you want, but it is annoying to finally get a failure after several minutes only to realize that your are missing some important information. ;)
Ditto for "-i", which leaves more readable output (you know the interesting part is at the end of the log), and leaves a trash directory state that is more likely to be useful for inspecting.
My script also implies "--root". That's not strictly necessary, though I suspect it is much more likely to catch races when it's run on a ram disk (simply because it leaves the CPU as the main bottleneck).
Quoted 8 lines
'wait' for all parallel jobs before exiting (either because a failure was found or because the user lost patience and aborted the stress test), allowing the still running tests to finish. Otherwise the "OK X.Y" progress output from the last iteration would likely arrive after the user got back the shell prompt, interfering with typing in the next command. OTOH, this waiting might induce a considerable delay between hitting ctrl-C and the test actually exiting; I'm not sure this is the right tradeoff.
If there is a delay, it's actually quite annoying to _not_ wait; you start doing something in the terminal, and then a bunch of extra test statuses print at a random time.
Quoted 4 lines
t/test-lib.sh +4
-0+ job_nr=0 + while test $job_nr -lt "$job_count" + do + (
t/test-lib.sh then · line 116
Quoted 10 lines
t/test-lib.sh +10
-0+ GIT_TEST_STRESS_STARTED=done + GIT_TEST_STRESS_JOB_NR=$job_nr + export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR + + cnt=0 + while ! test -e "$stressfail" + do + if $TEST_SHELL_PATH "$0" "$@" >/dev/null 2>&1 + then + printf >&2 "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt
OK, this adds a counter for each job number (compared to my script). Seems helpful.
t/test-lib.sh then · line 124
Quoted 8 lines
t/test-lib.sh +8
-0+ elif test -f "$stressfail" && + test "$(cat "$stressfail")" = "aborted" + then + printf >&2 "ABORTED %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + else + printf >&2 "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail" + fi
Hmm. What happens if we see a failure _and_ there's an abort? That's actually pretty plausible if you see a failure whiz by, and you want to abort the remaining scripts because they take a long time to run.
If the abort happens first, then the goal is to assume any errors are due to the abort. But there's a race between the individual jobs reading $stressfail and the parent signal handler writing it. So you may end up with "aborted\n5" or similar (after which any other jobs would fail to match "aborted" and declare themselves failures, as well). I think my script probably also has this race, too (it doesn't check the abort case explicitly, but it would race in checking "test -e fail").
If the fail happens first (which I think is the more likely case), then the abort handler will overwrite the file with "aborted", and we'll forget that there was a real failure. This works in my script because of the symlinking (if a real failure symlinked $fail to a directory, then the attempt to write "aborted" will just be a noop).
t/test-lib.sh then · line 136
Quoted 6 lines
t/test-lib.sh +6
-0+ job_nr=0 + while test $job_nr -lt "$job_count" + do + wait + job_nr=$(($job_nr + 1)) + done
Do we need to loop? Calling "wait" with no arguments should wait for all children.
t/test-lib.sh then · line 145
Quoted 8 lines
t/test-lib.sh +8
-0+ if test -f "$stressfail" && test "$(cat "$stressfail")" != "aborted" + then + echo "Log(s) of failed test run(s) be found in:" + for f in $(cat "$stressfail") + do + echo " $TEST_RESULTS_BASE.stress-$f.out" + done + fi
In my experience, outputting the failed log saves a step (especially with "-i"), since seeing the failed test and its output is often sufficient.
I'm also sad to lose the symlink to the failed trash directory, which saves cutting and pasting when you have to inspect it. But I guess we can't rely on symlinks everywhere.
-Peff
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Just a quick reply to this one point for now:
Quoted 10 lines · Jeff King
t/test-lib.sh then · line 136
Quoted 6 lines · SZEDER Gábor
t/test-lib.sh +6
-0+ job_nr=0 + while test $job_nr -lt "$job_count" + do + wait + job_nr=$(($job_nr + 1)) + doneDo we need to loop? Calling "wait" with no arguments should wait for all children.
It should, but in dash, ksh, ksh93, Bash v4.2 or older it doesn't seem to do so, at least not on my machine, and I get back my shell prompt right after hitting ctrl-C or the first failed test, and then get the progress output from the remaining jobs later.
Bash 4.3 or later are strange: I get back the shell prompt immediately after ctrl-C as well, so it doesn't appear to be waiting for all remaining jobs to finish either, but! I don't get any of the progress output from those jobs to mess up my next command.
And mksh and zsh can't run our tests, and I don't have any more shells at hand to try.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 15 lines · SZEDER Gábor
Just a quick reply to this one point for now:
Quoted 10 lines · Jeff King
Review
Quoted 6 lines · SZEDER Gábor
(quoted diff) +6
-0+ job_nr=0 + while test $job_nr -lt "$job_count" + do + wait + job_nr=$(($job_nr + 1)) + doneDo we need to loop? Calling "wait" with no arguments should wait for all children.
Quoted 4 lines
It should, but in dash, ksh, ksh93, Bash v4.2 or older it doesn't seem to do so, at least not on my machine, and I get back my shell prompt right after hitting ctrl-C or the first failed test, and then get the progress output from the remaining jobs later.
That's weird. I run my stress script under dash with a single "wait", and it handles the failing case just fine. And switching to a single "wait" in your script works for me.
But the ^C case is interesting. Try running your script under "sh -x" and hitting ^C. The signal interrupts the first wait. In my script (or yours modified to use a single wait), we then proceed immediately to the "exit", and get output from the subshells after we've exited.
But in your script with the loop, the first wait is interrupted, and then the _second_ wait (in the second loop iteration) picks up all of the exiting processes. The subsequent waits are all noops that return immediately, because there are no processes left.
So the right number of waits is either "1" or "2". Looping means we do too many (which is mostly a harmless noop) or too few (in the off chance that you have only a single job ;) ). So it works out in practice.
But I think a more clear way to do it is to simply wait in the signal trap, to cover the case when our original wait was aborted.
I.e., something like this:
t/test-lib.sh +2 -7
@@ -98,7 +98,7 @@ done,*) mkdir -p "$(dirname "$TEST_RESULTS_BASE")" stressfail="$TEST_RESULTS_BASE.stress-failed" rm -f "$stressfail" - trap 'echo aborted >"$stressfail"' TERM INT HUP + trap 'echo aborted >"$stressfail"; wait' TERM INT HUP job_nr=0 while test $job_nr -lt "$job_count"
@@ -128,12 +128,7 @@ done,*) job_nr=$(($job_nr + 1)) done - job_nr=0 - while test $job_nr -lt "$job_count" - do - wait - job_nr=$(($job_nr + 1)) - done + wait if test -f "$stressfail" && test "$(cat "$stressfail")" != "aborted" then
which behaves well for me in all cases.
Quoted 4 lines
Bash 4.3 or later are strange: I get back the shell prompt immediately after ctrl-C as well, so it doesn't appear to be waiting for all remaining jobs to finish either, but! I don't get any of the progress output from those jobs to mess up my next command.
Interesting. My bash 4.4 seems to behave the same as dash. It almost sounds like the SIGINT is getting passed to the subshells for you. Probably not really worth digging into, though.
In case anybody is playing with this and needed to simulate a stress failure, here's what I used:
t/t0000-basic.sh +7 -0
@@ -1171,4 +1171,11 @@ test_expect_success 'very long name in the index handled sanely' ' test $len = 4098 ' +test_expect_success 'roll those dice' ' + case "$(openssl rand -base64 1)" in + z*) + return 1 + esac +' + test_done
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 5 lines · Jeff King
But the ^C case is interesting. Try running your script under "sh -x" and hitting ^C. The signal interrupts the first wait. In my script (or yours modified to use a single wait), we then proceed immediately to the "exit", and get output from the subshells after we've exited.
Quoted 8 lines
But in your script with the loop, the first wait is interrupted, and then the _second_ wait (in the second loop iteration) picks up all of the exiting processes. The subsequent waits are all noops that return immediately, because there are no processes left.
So the right number of waits is either "1" or "2". Looping means we do too many (which is mostly a harmless noop) or too few (in the off chance that you have only a single job ;) ). So it works out in practice.
Well, if you time your ^C perfectly, no number of waits is right, I am afraid. You spawn N processes and while looping N times waiting for them, you can ^C out of wait before these N processes all die, no?
Quoted 2 lines
But I think a more clear way to do it is to simply wait in the signal trap, to cover the case when our original wait was aborted.
Sounds good.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 8 lines · Junio C Hamano
Quoted 3 lines
So the right number of waits is either "1" or "2". Looping means we do too many (which is mostly a harmless noop) or too few (in the off chance that you have only a single job ;) ). So it works out in practice.
Well, if you time your ^C perfectly, no number of waits is right, I am afraid. You spawn N processes and while looping N times waiting for them, you can ^C out of wait before these N processes all die, no?
Each "wait" will try to collect all processes, but may be interrupted by a signal. So the correct number is actually "1 plus the number of times the user hits ^C". I had assumed the user was just hitting it once, though putting the wait into the trap means we do that "1 plus" thing anyway.
I could also see an argument that subsequent ^C's should exit immediately, but I think we are getting well into the realm of over-engineering.
-Peff
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 3 lines · Jeff King
Each "wait" will try to collect all processes, but may be interrupted by a signal. So the correct number is actually "1 plus the number of times the user hits ^C".
Yeah and that is not bounded. It is OK not to catch multiple ^C that races with what we do, so having ane extra wait in the clean-up procedure after receiving a signal like you suggested would be both good enough and the cleanest solution, I think.
Thanks.
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
In reply to Jeff KingQuoted 1 line · Jeff King
The signal interrupts the first wait.
Ah, of course. I'm ashamed to say that this is not the first time I forget about that...
Quoted 8 lines
Quoted 4 lines
Bash 4.3 or later are strange: I get back the shell prompt immediately after ctrl-C as well, so it doesn't appear to be waiting for all remaining jobs to finish either, but! I don't get any of the progress output from those jobs to mess up my next command.
Interesting. My bash 4.4 seems to behave the same as dash. It almost sounds like the SIGINT is getting passed to the subshells for you. Probably not really worth digging into, though.
The subshell does indeed get SIGINT. I don't know why that happens, to my understanding that should not happen.
Quoted 8 lines
In case anybody is playing with this and needed to simulate a stress failure, here's what I used:
t/t0000-basic.sh +0
-0
t/t0000-basic.sh test_expect_success 'very long name in the index handled sanely' ' · line 1179
Quoted 9 lines
t/t0000-basic.sh +6
-0@@ -1171,4 +1171,11 @@ test_expect_success 'very long name in the index handled sanely' ' test $len = 4098 ' +test_expect_success 'roll those dice' ' + case "$(openssl rand -base64 1)" in + z*) + return 1 + esac +'
Wasteful :)
test $(($$ % 42)) -ne 0
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 10 lines · SZEDER Gábor
t/t0000-basic.sh test_expect_success 'very long name in the index handled sanely' ' · line 1179
Quoted 6 lines
t/t0000-basic.sh +6
-0+test_expect_success 'roll those dice' ' + case "$(openssl rand -base64 1)" in + z*) + return 1 + esac +'Wasteful :)
test $(($$ % 42)) -ne 0
Oh, indeed, that is much more clever. :)
-Peff
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
In reply to Jeff KingQuoted 15 lines · Jeff King
Quoted 6 lines · SZEDER Gábor
To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
Makes sense.
Quoted 3 lines
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
Quoted 6 lines
In my script I just use an arbitrary sequence of ports. That means two stress runs may stomp on each other, but stress runs tend not to interfere with regular runs (whereas with your scheme, a stress run of t5561 is going to stomp on t5562). It probably doesn't matter much either way, as I tend not to do too much with the machine during a stress run.
A custom port can always be set via environment variables, though the user has to remember to set it (I doubt that I would remember it until reminded by test failures...)
Quoted 7 lines
Quoted 3 lines
- Make '--stress' imply '--verbose-log' and discard the test's standard ouput and error; dumping the output of several parallel tests to the terminal would create a big ugly mess.
Makes sense. My script just redirects the output to a file, but it predates --verbose-log.
Quoted 8 lines
My script always runs with "-x". I guess it's not that hard to add it if you want, but it is annoying to finally get a failure after several minutes only to realize that your are missing some important information. ;)
Ditto for "-i", which leaves more readable output (you know the interesting part is at the end of the log), and leaves a trash directory state that is more likely to be useful for inspecting.
I wanted to imply only the bare minimum of options that are necessary to prevent the tests from stomping on each other. Other than that I agree and wouldn't run '--stress' without '-x -i' myself, and at one point considered to recommend '-x -i' in the description of '--stress'.
Quoted 13 lines
My script also implies "--root". That's not strictly necessary, though I suspect it is much more likely to catch races when it's run on a ram disk (simply because it leaves the CPU as the main bottleneck).
Quoted 8 lines
'wait' for all parallel jobs before exiting (either because a failure was found or because the user lost patience and aborted the stress test), allowing the still running tests to finish. Otherwise the "OK X.Y" progress output from the last iteration would likely arrive after the user got back the shell prompt, interfering with typing in the next command. OTOH, this waiting might induce a considerable delay between hitting ctrl-C and the test actually exiting; I'm not sure this is the right tradeoff.
Quoted 3 lines
If there is a delay, it's actually quite annoying to _not_ wait; you start doing something in the terminal, and then a bunch of extra test statuses print at a random time.
At least the INT/TERM/etc. handler should say something like "Waiting for background jobs to finish", to let the user know why it doesn't exit right away.
An alternative would be to exit right away, and make the background loops a tad bit smarter to not print these progress lines when aborting.
Quoted 31 lines
Quoted 4 lines
t/test-lib.sh +4
-0+ job_nr=0 + while test $job_nr -lt "$job_count" + do + (t/test-lib.sh then · line 116
Quoted 10 lines
t/test-lib.sh +10
-0+ GIT_TEST_STRESS_STARTED=done + GIT_TEST_STRESS_JOB_NR=$job_nr + export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR + + cnt=0 + while ! test -e "$stressfail" + do + if $TEST_SHELL_PATH "$0" "$@" >/dev/null 2>&1 + then + printf >&2 "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cntOK, this adds a counter for each job number (compared to my script). Seems helpful.
t/test-lib.sh then · line 124
Quoted 8 lines
t/test-lib.sh +8
-0+ elif test -f "$stressfail" && + test "$(cat "$stressfail")" = "aborted" + then + printf >&2 "ABORTED %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + else + printf >&2 "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail" + fiHmm. What happens if we see a failure _and_ there's an abort? That's actually pretty plausible if you see a failure whiz by, and you want to abort the remaining scripts because they take a long time to run.
Quoted 2 lines
If the abort happens first, then the goal is to assume any errors are due to the abort.
Yeah, that's why I added this ABORTED case. When I hit ctrl-C, a couple of the tests tend to fail, but that's not the rare failure we are hoping to find when stress testing, so printing FAIL would be misleading. The test script exits with 1 all the same, so we can't tell the difference, but since rare failures are, well, rare, this failure is most likely due to the abort.
Quoted 7 lines
But there's a race between the individual jobs reading $stressfail and the parent signal handler writing it. So you may end up with "aborted\n5" or similar (after which any other jobs would fail to match "aborted" and declare themselves failures, as well). I think my script probably also has this race, too (it doesn't check the abort case explicitly, but it would race in checking "test -e fail").
Quoted 5 lines
If the fail happens first (which I think is the more likely case), then the abort handler will overwrite the file with "aborted", and we'll forget that there was a real failure. This works in my script because of the symlinking (if a real failure symlinked $fail to a directory, then the attempt to write "aborted" will just be a noop).
OK, I think the abort handler should append, not overwrite, and then the file's contents should be checked e.g. with a case statement looking for *aborted*.
Or use two separate files for signaling abort and test failure.
In any case, you could always grab the job number from the "FAIL X.Y" line, and then look at its logs and trash direcory.
Quoted 10 lines
t/test-lib.sh then · line 136
Quoted 6 lines
t/test-lib.sh +6
-0+ job_nr=0 + while test $job_nr -lt "$job_count" + do + wait + job_nr=$(($job_nr + 1)) + doneDo we need to loop? Calling "wait" with no arguments should wait for all children.
Quoted 12 lines
t/test-lib.sh then · line 145
Quoted 8 lines
t/test-lib.sh +8
-0+ if test -f "$stressfail" && test "$(cat "$stressfail")" != "aborted" + then + echo "Log(s) of failed test run(s) be found in:" + for f in $(cat "$stressfail") + do + echo " $TEST_RESULTS_BASE.stress-$f.out" + done + fiIn my experience, outputting the failed log saves a step (especially with "-i"), since seeing the failed test and its output is often sufficient.
I just don't like when test output, especially with '-x', is dumped on my terminal :)
Quoted 3 lines
I'm also sad to lose the symlink to the failed trash directory, which saves cutting and pasting when you have to inspect it. But I guess we can't rely on symlinks everywhere.
You can tab-complete most of the trash directory, and then there are only those 1-2-3 digits of the job number that you have to type. That worked out well enough for me so far (but I've only used it for a few days, so...).
We could rename the trash directory of the failed test to something short, sweet, and common, but that could be racy in the unlikely case that two tests fail at the same time. And I like the 'trash directory.' prefix, because then 'make clean' will delete that one, too, without modifying 't/Makefile' (which is also the reason for adding the 'stress-N' suffix instead of a prefix, and putting the fail file into the 'test-results' directory).
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 16 lines · SZEDER Gábor
Quoted 7 lines
Quoted 3 lines
- Make '--stress' imply '--verbose-log' and discard the test's standard ouput and error; dumping the output of several parallel tests to the terminal would create a big ugly mess.
Makes sense. My script just redirects the output to a file, but it predates --verbose-log.
Quoted 8 lines
My script always runs with "-x". I guess it's not that hard to add it if you want, but it is annoying to finally get a failure after several minutes only to realize that your are missing some important information. ;)
Ditto for "-i", which leaves more readable output (you know the interesting part is at the end of the log), and leaves a trash directory state that is more likely to be useful for inspecting.
Quoted 5 lines
I wanted to imply only the bare minimum of options that are necessary to prevent the tests from stomping on each other. Other than that I agree and wouldn't run '--stress' without '-x -i' myself, and at one point considered to recommend '-x -i' in the description of '--stress'.
Yeah, it probably is the right thing to make the options as orthogonal as possible, and let the user decide what they want. I was just wondering if I could replace my "stress" script with this. But I think I'd still want it to remember to use a sane set of options (including "--root" in my case).
Quoted 9 lines
Quoted 9 lines
Quoted 8 lines
'wait' for all parallel jobs before exiting (either because a failure was found or because the user lost patience and aborted the stress test), allowing the still running tests to finish. Otherwise the "OK X.Y" progress output from the last iteration would likely arrive after the user got back the shell prompt, interfering with typing in the next command. OTOH, this waiting might induce a considerable delay between hitting ctrl-C and the test actually exiting; I'm not sure this is the right tradeoff.
Quoted 7 lines
Quoted 3 lines
If there is a delay, it's actually quite annoying to _not_ wait; you start doing something in the terminal, and then a bunch of extra test statuses print at a random time.
At least the INT/TERM/etc. handler should say something like "Waiting for background jobs to finish", to let the user know why it doesn't exit right away.
That seems reasonable. There's also no point in waiting for them to finish if we know we're aborting anyway. Could we just kill them all?
I guess it's a little tricky, because $! is going to give us the pid of each subshell. We actually want to kill the test sub-process. That takes a few contortions, but the output is nice (every sub-job immediately says "ABORTED ...", and the final process does not exit until the whole tree is done):
t/test-lib.sh +8 -9
@@ -98,8 +98,9 @@ done,*) mkdir -p "$(dirname "$TEST_RESULTS_BASE")" stressfail="$TEST_RESULTS_BASE.stress-failed" rm -f "$stressfail" - trap 'echo aborted >"$stressfail"' TERM INT HUP + trap 'echo aborted >"$stressfail"; kill $job_pids 2>/dev/null; wait' TERM INT HUP + job_pids= job_nr=0 while test $job_nr -lt "$job_count" do
@@ -108,10 +109,13 @@ done,*) GIT_TEST_STRESS_JOB_NR=$job_nr export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR + trap 'kill $test_pid 2>/dev/null' TERM + cnt=0 while ! test -e "$stressfail" do - if $TEST_SHELL_PATH "$0" "$@" >/dev/null 2>&1 + $TEST_SHELL_PATH "$0" "$@" >/dev/null 2>&1 & test_pid=$! + if wait then printf >&2 "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt elif test -f "$stressfail" &&
@@ -124,16 +128,11 @@ done,*) fi cnt=$(($cnt + 1)) done - ) & + ) & job_pids="$job_pids $!" job_nr=$(($job_nr + 1)) done - job_nr=0 - while test $job_nr -lt "$job_count" - do - wait - job_nr=$(($job_nr + 1)) - done + wait if test -f "$stressfail" && test "$(cat "$stressfail")" != "aborted" then
Quoted 16 lines
Quoted 13 lines
Review
Quoted 8 lines
(quoted diff) +8
-0+ elif test -f "$stressfail" && + test "$(cat "$stressfail")" = "aborted" + then + printf >&2 "ABORTED %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + else + printf >&2 "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail" + fiHmm. What happens if we see a failure _and_ there's an abort? That's actually pretty plausible if you see a failure whiz by, and you want to abort the remaining scripts because they take a long time to run.
Quoted 2 lines
If the abort happens first, then the goal is to assume any errors are due to the abort.
Quoted 6 lines
Yeah, that's why I added this ABORTED case. When I hit ctrl-C, a couple of the tests tend to fail, but that's not the rare failure we are hoping to find when stress testing, so printing FAIL would be misleading. The test script exits with 1 all the same, so we can't tell the difference, but since rare failures are, well, rare, this failure is most likely due to the abort.
With the patch as posted I actually see most of them still say "OK", because the SIGINT does not make it to them (it would be a different story if I killed the whole process group).
But with the modification above, I get "ABORTED" for most, which is what I'd want. And it should be pretty race-free, since the "aborted" file is put into place before triggering the signal cascade.
Quoted 11 lines
Quoted 5 lines
If the fail happens first (which I think is the more likely case), then the abort handler will overwrite the file with "aborted", and we'll forget that there was a real failure. This works in my script because of the symlinking (if a real failure symlinked $fail to a directory, then the attempt to write "aborted" will just be a noop).
OK, I think the abort handler should append, not overwrite, and then the file's contents should be checked e.g. with a case statement looking for *aborted*.
Or use two separate files for signaling abort and test failure.
Two separate files seems a lot simpler to me.
Quoted 6 lines
Quoted 3 lines
In my experience, outputting the failed log saves a step (especially with "-i"), since seeing the failed test and its output is often sufficient.
I just don't like when test output, especially with '-x', is dumped on my terminal :)
Don't you then use your terminal to display the file? ;) (I'm kidding, I assume you load it in "less" or whatever, which is distinct).
I wish this could be optional somehow, though. I really prefer the dump. I guess if I continue to use my script as a wrapper, it can dump for me (after digging in the $stressfail file, I guess).
Quoted 9 lines
Quoted 3 lines
I'm also sad to lose the symlink to the failed trash directory, which saves cutting and pasting when you have to inspect it. But I guess we can't rely on symlinks everywhere.
You can tab-complete most of the trash directory, and then there are only those 1-2-3 digits of the job number that you have to type. That worked out well enough for me so far (but I've only used it for a few days, so...).
Quoted 7 lines
We could rename the trash directory of the failed test to something short, sweet, and common, but that could be racy in the unlikely case that two tests fail at the same time. And I like the 'trash directory.' prefix, because then 'make clean' will delete that one, too, without modifying 't/Makefile' (which is also the reason for adding the 'stress-N' suffix instead of a prefix, and putting the fail file into the 'test-results' directory).
We could rename it to "trash directory.xxx.failed", which is at least predictable. That can even be done atomically, though I think it's a little tricky to do portably with shell tools. I guess I can continue to do the symlink thing in my wrapper script.
-Peff
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 7 lines · Jeff King
Could we just kill them all?
I guess it's a little tricky, because $! is going to give us the pid of each subshell. We actually want to kill the test sub-process. That takes a few contortions, but the output is nice (every sub-job immediately says "ABORTED ...", and the final process does not exit until the whole tree is done):
It's trickier than that:
- Nowadays our test lib translates SIGINT to exit, but not TERM (or HUP, in case a user decides to close the terminal window), which means that if the test runs any daemons in the background, then those won't be killed when the stress test is aborted.
This is easy enough to address, and I've been meaning to do this in an other patch series anyway.
- With the (implied) '--verbose-log' the process killed in the background subshell's SIGTERM handler is not the actual test process, because '--verbose-log' runs the test again in a piped subshell to save its output:
(GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1;
echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
That 'kill' kills the "outer" shell process.
And though I get "ABORTED..." immediately and I get back my prompt right away, the commands involved in the above construct are still running in the background:
$ ./t3903-stash.sh --stress=1
^CABORTED 0.0
$ ps a |grep t3903
1280 pts/17 S 0:00 /bin/sh ./t3903-stash.sh --stress=1
1281 pts/17 S 0:00 tee -a <...>/test-results/t3903-stash.stress-0.out
1282 pts/17 S 0:00 /bin/sh ./t3903-stash.sh --stress=1
4173 pts/17 S+ 0:00 grep t3903
I see this behavior with all shells I tried.
I haven't yet started to think it through why this happens :)
Not implying '--verbose-log' but redirecting the test script's output, like you did in your 'stress' script, seems to work in dash, ksh, and ks93, but not in Bash v4.3 or later, where, for whatever reason, the subshell get SIGINT before the SIGTERM would arrive.
While we could easily handle SIGINT in the subshell with the same signal handler as SIGTERM, it bothers me that something fundamental is wrong here.
Furthermore, then we should perhaps forbid '--stress' in combination with '--verbose-log' or '--tee'.
Quoted 50 lines
t/test-lib.sh +8
-9@@ -98,8 +98,9 @@ done,*) mkdir -p "$(dirname "$TEST_RESULTS_BASE")" stressfail="$TEST_RESULTS_BASE.stress-failed" rm -f "$stressfail" - trap 'echo aborted >"$stressfail"' TERM INT HUP + trap 'echo aborted >"$stressfail"; kill $job_pids 2>/dev/null; wait' TERM INT HUP + job_pids= job_nr=0 while test $job_nr -lt "$job_count" do@@ -108,10 +109,13 @@ done,*) GIT_TEST_STRESS_JOB_NR=$job_nr export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR + trap 'kill $test_pid 2>/dev/null' TERM + cnt=0 while ! test -e "$stressfail" do - if $TEST_SHELL_PATH "$0" "$@" >/dev/null 2>&1 + $TEST_SHELL_PATH "$0" "$@" >/dev/null 2>&1 & test_pid=$! + if wait then printf >&2 "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt elif test -f "$stressfail" &&@@ -124,16 +128,11 @@ done,*) fi cnt=$(($cnt + 1)) done - ) & + ) & job_pids="$job_pids $!" job_nr=$(($job_nr + 1)) done - job_nr=0 - while test $job_nr -lt "$job_count" - do - wait - job_nr=$(($job_nr + 1)) - done + wait if test -f "$stressfail" && test "$(cat "$stressfail")" != "aborted" then
Re: [RFC PATCH 3/3] test-lib: add the '--stress' option to run a test repeatedly under load
Quoted 11 lines · SZEDER Gábor
Quoted 7 lines · Jeff King
Could we just kill them all?
I guess it's a little tricky, because $! is going to give us the pid of each subshell. We actually want to kill the test sub-process. That takes a few contortions, but the output is nice (every sub-job immediately says "ABORTED ...", and the final process does not exit until the whole tree is done):
It's trickier than that:
Quoted 7 lines
- Nowadays our test lib translates SIGINT to exit, but not TERM (or HUP, in case a user decides to close the terminal window), which means that if the test runs any daemons in the background, then those won't be killed when the stress test is aborted.
This is easy enough to address, and I've been meaning to do this in an other patch series anyway.
Yeah, trapping on more signals seems reasonable (or just propagating INT instead of TERM). I'm more worried about this one:
Quoted 4 lines
- With the (implied) '--verbose-log' the process killed in the background subshell's SIGTERM handler is not the actual test process, because '--verbose-log' runs the test again in a piped subshell to save its output:
Hmm, yeah. The patch I sent earlier already propagates through the subshell, so this is really just another layer there. I.e., would it be reasonable when we are using --verbose-log (or --tee) for the parent process to propagate signals down to child it spawned?
That would fix this case, and it would make things more predictable in general (e.g., a user who manually runs kill).
It does feel like a lot of boilerplate to be propagating these signals manually. I think the "right" Unix-y solution here is process groups:
each sub-job should get its own group, and then the top-level runner script can kill the whole group. We may run into portability issues, though (setsid is in util-linux, but I don't know if there's an equivalent elsewhere; a small perl or C helper could do it, but I have no idea what that would mean on Windows).
Quoted 10 lines
(GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1;
echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
That 'kill' kills the "outer" shell process.
And though I get "ABORTED..." immediately and I get back my
prompt right away, the commands involved in the above construct
are still running in the background:
$ ./t3903-stash.sh --stress=1
^CABORTED 0.0Quoted 8 lines
$ ps a |grep t3903
1280 pts/17 S 0:00 /bin/sh ./t3903-stash.sh --stress=1
1281 pts/17 S 0:00 tee -a <...>/test-results/t3903-stash.stress-0.out
1282 pts/17 S 0:00 /bin/sh ./t3903-stash.sh --stress=1
4173 pts/17 S+ 0:00 grep t3903
I see this behavior with all shells I tried.
I haven't yet started to think it through why this happens :)I think you get ABORTED because we are just watching for the parent-process to exit (and reporting its status). The fact that that the subshell (and the tee) are still running is not important. That all makes sense to me.
Quoted 8 lines
Not implying '--verbose-log' but redirecting the test script's output, like you did in your 'stress' script, seems to work in dash, ksh, and ks93, but not in Bash v4.3 or later, where, for whatever reason, the subshell get SIGINT before the SIGTERM would arrive.
While we could easily handle SIGINT in the subshell with the same signal handler as SIGTERM, it bothers me that something fundamental is wrong here.
Yeah, I don't understand the SIGINT behavior you're seeing with bash. I can't replicate it with bash 4.4.23 (from Debian unstable).
-Peff
v2 cover test-lib: add the '--stress' option to help reproduce occasional failures in flaky tests
In reply to SZEDER GáborThis patch series tries to make reproducing rare failures in flaky tests easier: it adds the '--stress' option to our test library to run the test script repeatedly in multiple parallel jobs, in the hope that the increased load creates enough variance in the timing of the test's commands that such a failure is eventually triggered.
Notable changes since v1:
- Made it more Peff-friendly :), namely it will now show the log of the failed test and will rename its trash directory.
Furthermore, '--stress' will now imply '--verbose -x --immediate'.
- Improved abort handling based on the discussion of the previous version. (As a result, the patch is so heavily modified, that 'range-diff' with default parameters consideres it a different patch; increasing the creation factor then results in one big ugly diff of a diff, so I left it as-is.)
- Added a few new patches, mostly preparatory refactorings, though the first one might be considered a bugfix.
- Other minor cleanups suggested in the previous discussion.
v1: https://public-inbox.org/git/[email protected]/T/
SZEDER Gábor (7):
test-lib: translate SIGTERM and SIGHUP to an exit
test-lib: parse some --options earlier
test-lib: consolidate naming of test-results paths
test-lib: set $TRASH_DIRECTORY earlier
test-lib: extract Bash version check for '-x' tracing
test-lib-functions: introduce the 'test_set_port' helper function
test-lib: add the '--stress' option to run a test repeatedly under
load
t/README | 19 +++- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 39 +++++++ t/test-lib.sh | 227 +++++++++++++++++++++++++++++---------- 9 files changed, 230 insertions(+), 73 deletions(-)
Range-diff:
-: ---------- > 1: 3a5c926167 test-lib: translate SIGTERM and SIGHUP to an exit -: ---------- > 2: 8eee8d7fba test-lib: parse some --options earlier 1: f4bb53e676 ! 3: dd20ae5e9a test-lib: consolidate naming of test-results paths
@@ -4,8 +4,9 @@
There are two places where we strip off any leading path components and the '.sh' suffix from the test script's pathname, and there are
- two places where we construct the filename of test output files in
- 't/test-results/'. The last patch in this series will add even more.
+ four places where we construct the name of the 't/test-results'
+ directory or the name of various test-specific files in there. The
+ last patch in this series will add even more.
Factor these out into helper variables to avoid repeating ourselves.
@@ -15,22 +16,23 @@
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@
- exit 1
- fi
+ esac
+ done
+TEST_NAME="$(basename "$0" .sh)"
-+TEST_RESULTS_BASE="$TEST_OUTPUT_DIRECTORY/test-results/$TEST_NAME" ++TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" ++TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME"
+
# 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
+ if test "$GIT_TEST_TEE_STARTED" = "done"
@@
- # do not redirect again
- ;;
- *' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*)
+ elif test -n "$tee" || test -n "$verbose_log" ||
+ test -n "$valgrind" || test -n "$valgrind_only"
+ then
- mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
- BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
-+ mkdir -p "$(dirname "$TEST_RESULTS_BASE")"
++ mkdir -p "$TEST_RESULTS_DIR"
# Make this filename available to the sub-process in case it is using # --verbose-log.
@@ -48,8 +50,8 @@
+ echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE"
+ test "$(cat "$TEST_RESULTS_BASE.exit")" = 0
exit
- ;;
- esac
+ fi
+
@@
if test -z "$HARNESS_ACTIVE"
@@ -58,7 +60,7 @@
- mkdir -p "$test_results_dir"
- base=${0##*/}
- test_results_path="$test_results_dir/${base%.sh}.counts"
-+ mkdir -p "$(dirname "$TEST_RESULTS_BASE")"
++ mkdir -p "$TEST_RESULTS_DIR"
- cat >"$test_results_path" <<-EOF
+ cat >"$TEST_RESULTS_BASE.counts" <<-EOF
-: ---------- > 4: f3941077e6 test-lib: set $TRASH_DIRECTORY earlier -: ---------- > 5: fefbca96ee test-lib: extract Bash version check for '-x' tracing 2: 9aec8662f9 ! 6: b4b6844a3e test-lib-functions: introduce the 'test_set_port' helper function
@@ -27,7 +27,7 @@
containing the digits 8 or 9 will trigger an error. Remove all leading zeros from the test numbers to prevent this.
- Note that the Perforce tests are unlike the other tests involving
+ Note that the 'git p4' tests are unlike the other tests involving
daemons in that:
- 'lib-git-p4.sh' doesn't use the test's number for unique port as
@@ -35,7 +35,7 @@
- The port is not overridable via an environment variable.
- With this patch even Perforce tests will use the test's number as
+ With this patch even 'git p4' tests will use the test's number as
default port, and it will be overridable via the P4DPORT environment variable.
@@ -161,7 +161,7 @@
+ BUG "test_set_port requires a variable name"
+ fi
+
-+ eval port=\"\${$var}\"
++ eval port=\$$var
+ case "$port" in
+ "")
+ # No port is set in the given env var, use the test
3: a5aa71f20c < -: ---------- test-lib: add the '--stress' option to run a test repeatedly under load -: ---------- > 7: 8470b55f65 test-lib: add the '--stress' option to run a test repeatedly under load
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH v2 1/7 test-lib: translate SIGTERM and SIGHUP to an exit
Right now if a test script receives SIGTERM or SIGHUP (e.g., because a test was hanging and the user 'kill'-ed it or simply closed the terminal window the test was running in), the shell exits immediately. This can be annoying if the test script did any global setup, like starting apache or git-daemon, as it will not have an opportunity to clean up after itself. A subsequent run of the test won't be able to start its own daemon, and will either fail or skip the tests.
Instead, let's trap SIGTERM and SIGHUP as well to make sure we do a clean shutdown, and just chain it to a normal exit (which will trigger any cleanup).
This patch follows suit of da706545f7 (t: translate SIGINT to an exit, 2015-03-13), and even stole its commit message as well.
--- t/test-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/test-lib.sh +1 -1
@@ -476,7 +476,7 @@ die () { GIT_EXIT_OK= trap 'die' EXIT -trap 'exit $?' INT +trap 'exit $?' INT TERM HUP # The user-facing functions are loaded from a separate file so that # test_perf subshells can have them too
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
Re: [PATCH v2 1/7] test-lib: translate SIGTERM and SIGHUP to an exit
Quoted 8 lines · SZEDER Gábor
Right now if a test script receives SIGTERM or SIGHUP (e.g., because a test was hanging and the user 'kill'-ed it or simply closed the terminal window the test was running in), the shell exits immediately. This can be annoying if the test script did any global setup, like starting apache or git-daemon, as it will not have an opportunity to clean up after itself. A subsequent run of the test won't be able to start its own daemon, and will either fail or skip the tests.
Quoted 6 lines
Instead, let's trap SIGTERM and SIGHUP as well to make sure we do a clean shutdown, and just chain it to a normal exit (which will trigger any cleanup).
This patch follows suit of
da706545f7(t: translate SIGINT to an exit, 2015-03-13), and even stole its commit message as well.
No wonder it was so nicely explained. ;)
I think this is quite a reasonable thing to do. Since we're trying to clean up, in theory we would like to hook any signal death, but these three are the common ones in practice. We handle QUIT and PIPE as well in our C code; the latter isn't an issue here. SIGQUIT is a possibility, I guess, but seems rather unlikely.
-Peff
PATCH v2 2/7 test-lib: parse some --options earlier
In reply to SZEDER Gábor'test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error. This happens way before the actual option parsing loop, and the condition looking for these options looks a bit odd, too. This patch series will add two more options to look out for, and, in addition, will have to extract these options' stuck arguments (i.e. '--opt=arg') as well.
Add a proper option parsing loop to check these select options early in 'test-lib.sh', making this early option checking more readable and keeping those later changes in this series simpler. Use a 'for opt in "$@"' loop to iterate over the options to preserve "$@" intact, so options like '--verbose-log' can execute the test script again with all the original options.
As an alternative, we could parse all options early, but there are options that do require an _unstuck_ argument, which is tricky to handle properly in such a for loop, and the resulting complexity is, in my opinion, worse than having this extra, partial option parsing loop.
--- t/test-lib.sh | 53 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 21 deletions(-)
t/test-lib.sh +32 -21
@@ -71,13 +71,33 @@ then exit 1 fi +# Parse some options early, taking care to leave $@ intact. +for opt +do + case "$opt" in + --tee) + tee=t ;; + -V|--verbose-log) + verbose_log=t ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=memcheck ;; + --valgrind=*) + valgrind=${opt#--*=} ;; + --valgrind-only=*) + valgrind_only=${opt#--*=} ;; + *) + # Other options will be handled later. + esac +done + # 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'*|*' -V '*|*' --verbose-log '*) +if test "$GIT_TEST_TEE_STARTED" = "done" +then + : # do not redirect again +elif test -n "$tee" || test -n "$verbose_log" || + test -n "$valgrind" || test -n "$valgrind_only" +then mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
@@ -94,8 +114,7 @@ done,*) echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" test "$(cat "$BASE.exit")" = 0 exit - ;; -esac +fi # For repeatability, reset the environment to known value. # TERM is sanitized below, after saving color control sequences.
@@ -296,17 +315,6 @@ do with_dashes=t; shift ;; --no-color) color=; shift ;; - --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck - shift ;; - --valgrind=*) - valgrind=${1#--*=} - shift ;; - --valgrind-only=*) - valgrind_only=${1#--*=} - shift ;; - --tee) - shift ;; # was handled already --root=*) root=${1#--*=} shift ;;
@@ -336,9 +344,12 @@ do echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" fi shift ;; - -V|--verbose-log) - verbose_log=t - shift ;; + --tee|\ + -V|--verbose-log|\ + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind|\ + --valgrind=*|\ + --valgrind-only=*) + shift ;; # These options were handled already. *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
Re: [PATCH v2 2/7] test-lib: parse some --options earlier
Quoted 15 lines · SZEDER Gábor
'
test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error. This happens way before the actual option parsing loop, and the condition looking for these options looks a bit odd, too. This patch series will add two more options to look out for, and, in addition, will have to extract these options' stuck arguments (i.e. '--opt=arg') as well.Add a proper option parsing loop to check these select options early in '
test-lib.sh', making this early option checking more readable and keeping those later changes in this series simpler. Use a 'for opt in "$@"' loop to iterate over the options to preserve "$@" intact, so options like '--verbose-log' can execute the test script again with all the original options.
Quoted 5 lines
As an alternative, we could parse all options early, but there are options that do require an _unstuck_ argument, which is tricky to handle properly in such a for loop, and the resulting complexity is, in my opinion, worse than having this extra, partial option parsing loop.
In general, I'm not wild about having multiple option-parsing loops that skip the normal left-to-right parsing, since it introduces funny corner cases (like "-foo --bar" which should be the same as "--foo=--bar" instead thinking that "--bar" was passed as an option).
But looking at what this is replacing:
t/test-lib.sh then · line 80
Quoted 5 lines
t/test-lib.sh +0
-5-case "$GIT_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*)
your version is easily an order of magnitude less horrible. ;)
Quoted 2 lines
t/test-lib.sh | 53 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 21 deletions(-)
This looks good to me overall, though...
Quoted 11 lines
t/test-lib.sh +11
-0+# Parse some options early, taking care to leave $@ intact. +for opt +do + case "$opt" in + --tee) + tee=t ;; + -V|--verbose-log) + verbose_log=t ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=memcheck ;; + --valgrind=*)
t/test-lib.sh then · line 99
Quoted 10 lines
t/test-lib.sh +9
-0+ valgrind=${opt#--*=} ;; + --valgrind-only=*) + valgrind_only=${opt#--*=} ;; + *) + # Other options will be handled later. + esac +done +elif test -n "$tee" || test -n "$verbose_log" || + test -n "$valgrind" || test -n "$valgrind_only"
Now that we've nicely moved the parsing up, would it make sense to put the annotation for "this option implies --tee" with those options?
I.e., set tee=t when we see --verbose-log, which keeps all of the verbose-log logic together?
Quoted 4 lines
t/test-lib.sh +0
-0@@ -336,9 +344,12 @@ do echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" fi shift ;;
t/test-lib.sh do · line 353
Quoted 10 lines
t/test-lib.sh +6
-3- -V|--verbose-log) - verbose_log=t - shift ;; + --tee|\ + -V|--verbose-log|\ + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind|\ + --valgrind=*|\ + --valgrind-only=*) + shift ;; # These options were handled already. *)
It's too bad there's not an easy way to selectively remove from the $@ array (which would avoid duplicating this list here).
The best I could come up with is:
-- >8 --
first=t
for i in "$@"; do
test -n "$first" && set -- first= case "$i" in --foo) echo "saw foo" ;; *) set -- "$@" "$i" ;; esac
done
for i in "$@"; do
echo "remainder: $i"
done
-- 8< --
but I won't be surprised if there are portability problems with assigning $@ in the middle of a loop that iterates over it.
-Peff
Re: [PATCH v2 2/7] test-lib: parse some --options earlier
Quoted 23 lines · Jeff King
Quoted 15 lines · SZEDER Gábor
'
test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error. This happens way before the actual option parsing loop, and the condition looking for these options looks a bit odd, too. This patch series will add two more options to look out for, and, in addition, will have to extract these options' stuck arguments (i.e. '--opt=arg') as well.Add a proper option parsing loop to check these select options early in '
test-lib.sh', making this early option checking more readable and keeping those later changes in this series simpler. Use a 'for opt in "$@"' loop to iterate over the options to preserve "$@" intact, so options like '--verbose-log' can execute the test script again with all the original options.Quoted 5 lines
As an alternative, we could parse all options early, but there are options that do require an _unstuck_ argument, which is tricky to handle properly in such a for loop, and the resulting complexity is, in my opinion, worse than having this extra, partial option parsing loop.
Quoted 4 lines
In general, I'm not wild about having multiple option-parsing loops that skip the normal left-to-right parsing, since it introduces funny corner cases (like "-foo --bar" which should be the same as "--foo=--bar" instead thinking that "--bar" was passed as an option).
Yeah, that's already an "issue" in the current implementation as well, though there are no such options that require options as argument.
Quoted 7 lines
But looking at what this is replacing:
Review
Quoted 5 lines
(quoted diff) +0
-5-case "$GIT_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*)
Anyway, I had another crack at turning the current option parsing loop into a for loop keeping $@ intact, and the results don't look all that bad this time. Note that this diff below only does the while -> for conversion, but leaves the loop where it is, so the changes are easily visible. The important bits are the conditions at the beginning of the loop and after the loop, and the handling of '-r'; the rest is mostly s/shift// and sort-of s/$1/$opt/.
Thoughts? Is it better than two loops? I think it's better.
t/test-lib.sh +44 -31
@@ -264,58 +264,65 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 +store_arg_to= +prev_opt= +for opt do - case "$1" in + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + case "$opt" in -d|--d|--de|--deb|--debu|--debug) - debug=t; shift ;; + debug=t ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t; shift ;; + immediate=t ;; -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 ;; + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; -r) - shift; test "$#" -ne 0 || { - echo 'error: -r requires an argument' >&2; - exit 1; - } - run_list=$1; shift ;; + store_arg_to=run_list + prev_opt=-r + ;; --run=*) - run_list=${1#--*=}; shift ;; + run_list=${opt#--*=} ;; -h|--h|--he|--hel|--help) - help=t; shift ;; + help=t ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t; shift ;; + verbose=t ;; --verbose-only=*) - verbose_only=${1#--*=} - shift ;; + verbose_only=${opt#--*=} + ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; + test -z "$HARNESS_ACTIVE" && quiet=t ;; --with-dashes) - with_dashes=t; shift ;; + with_dashes=t ;; --no-color) - color=; shift ;; + color= ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) valgrind=memcheck - shift ;; + ;; --valgrind=*) - valgrind=${1#--*=} - shift ;; + valgrind=${opt#--*=} + ;; --valgrind-only=*) - valgrind_only=${1#--*=} - shift ;; + valgrind_only=${opt#--*=} + ;; --tee) - shift ;; # was handled already + ;; # was handled already --root=*) - root=${1#--*=} - shift ;; + root=${opt#--*=} + ;; --chain-lint) GIT_TEST_CHAIN_LINT=1 - shift ;; + ;; --no-chain-lint) GIT_TEST_CHAIN_LINT=0 - shift ;; + ;; -x) # Some test scripts can't be reliably traced with '-x', # unless the test is run with a Bash version supporting
@@ -335,15 +342,21 @@ do else echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" fi - shift ;; + ;; -V|--verbose-log) verbose_log=t - shift ;; + ;; *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; + echo "error: unknown test option '$opt'" >&2; exit 1 ;; esac done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi + if test -n "$valgrind_only" then test -z "$valgrind" && valgrind=memcheck
Re: [PATCH v2 2/7] test-lib: parse some --options earlier
Quoted 17 lines · SZEDER Gábor
Quoted 7 lines
But looking at what this is replacing:
Review
Quoted 5 lines
(quoted diff) +0
-5-case "$GIT_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*)Anyway, I had another crack at turning the current option parsing loop into a for loop keeping $@ intact, and the results don't look all that bad this time. Note that this diff below only does the while -> for conversion, but leaves the loop where it is, so the changes are easily visible. The important bits are the conditions at the beginning of the loop and after the loop, and the handling of '-r'; the rest is mostly s/shift// and sort-of s/$1/$opt/.
Quoted 1 line
Thoughts? Is it better than two loops? I think it's better.
It certainly looks better to me. It also makes sense to me to validate the options before forking/logging, though I suppose one could argue the opposite.
I wonder why we didn't do it this way in the beginning (i.e., why the tee bits were all handled separately before the parsing phase). I guess just because we have to pass the options down to the sub-process.
Quoted 11 lines
t/test-lib.sh +2
-1@@ -264,58 +264,65 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 +store_arg_to= +prev_opt=
t/test-lib.sh test "x$TERM" != "xdumb" && ( · line 277
Quoted 10 lines
t/test-lib.sh +8
-1+for opt do - case "$1" in + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi
OK, so this is set for the unstuck options, which then pick up the option in the next loop iteration. That's perhaps less gross than my "re-build the options with set --" trick.
A simple variable set is enough for "-r". In theory we could make this:
if test -n "$handle_unstuck_arg"
then
eval "$handle_unstuck_arg \$1"
fi
...
-r)
handle_unstuck_arg=handle_opt_r ;;
and handle_opt_r() could do whatever it wants. But I don't really foresee us adding a lot of new options (in fact, given that this is just the internal tests, I am tempted to say that we should just make it "-r<arg>" for the sake of simplicity and consistency. But maybe somebody would be annoyed. I have never used "-r" ever myself).
-Peff
Re: [PATCH v2 2/7] test-lib: parse some --options earlier
Quoted 33 lines · Jeff King
Quoted 11 lines · SZEDER Gábor
t/test-lib.sh +2
-1@@ -264,58 +264,65 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 +store_arg_to= +prev_opt=t/test-lib.sh test "x$TERM" != "xdumb" && ( · line 277
Quoted 10 lines
t/test-lib.sh +8
-1+for opt do - case "$1" in + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fiOK, so this is set for the unstuck options, which then pick up the option in the next loop iteration. That's perhaps less gross than my "re-build the options with set --" trick.
A simple variable set is enough for "-r". In theory we could make this:
if test -n "$handle_unstuck_arg"
theneval "$handle_unstuck_arg \$1"
Quoted 8 lines
fi
...-r)
handle_unstuck_arg=handle_opt_r ;;and
handle_opt_r()could do whatever it wants. But I don't really foresee us adding a lot of new options
Yeah, I would refrain from making it too general and fancy with a callback function for now, when there is only a single option that could use it.
Quoted 4 lines
(in fact, given that this is just
the internal tests, I am tempted to say that we should just make it "-r<arg>" for the sake of simplicity and consistency. But maybe somebody would be annoyed. I have never used "-r" ever myself).
I didn't even know what '-r' does...
And I agree that changing it to '-r<arg>' would be the best, but this patch series is about adding '--stress', so changing how '-r' gets its mandatory argument (and potentially annoying someone) is beyond the scope, I would say.
Re: [PATCH v2 2/7] test-lib: parse some --options earlier
Quoted 6 lines · SZEDER Gábor
Quoted 4 lines
(in fact, given that this is just
the internal tests, I am tempted to say that we should just make it "-r<arg>" for the sake of simplicity and consistency. But maybe somebody would be annoyed. I have never used "-r" ever myself).I didn't even know what '-r' does...
I had to look it up, too. :)
Quoted 4 lines
And I agree that changing it to '-r<arg>' would be the best, but this patch series is about adding '--stress', so changing how '-r' gets its mandatory argument (and potentially annoying someone) is beyond the scope, I would say.
OK, I'm fine with that (though once we've built the infrastructure to handle its unstuck form, I don't know if there's much point in changing it, so we can probably just let it live on forever).
-Peff
PATCH v2 3/7 test-lib: consolidate naming of test-results paths
In reply to SZEDER GáborThere are two places where we strip off any leading path components and the '.sh' suffix from the test script's pathname, and there are four places where we construct the name of the 't/test-results' directory or the name of various test-specific files in there. The last patch in this series will add even more.
Factor these out into helper variables to avoid repeating ourselves.
--- t/test-lib.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
t/test-lib.sh +11 -11
@@ -90,6 +90,10 @@ do esac done +TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" + # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. if test "$GIT_TEST_TEE_STARTED" = "done"
@@ -98,12 +102,11 @@ then elif test -n "$tee" || test -n "$verbose_log" || test -n "$valgrind" || test -n "$valgrind_only" then - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" + mkdir -p "$TEST_RESULTS_DIR" # Make this filename available to the sub-process in case it is using # --verbose-log. - GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out export GIT_TEST_TEE_OUTPUT_FILE # Truncate before calling "tee -a" to get rid of the results
@@ -111,8 +114,8 @@ then >"$GIT_TEST_TEE_OUTPUT_FILE" (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 + echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit fi
@@ -829,12 +832,9 @@ test_done () { if test -z "$HARNESS_ACTIVE" then - test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}.counts" + mkdir -p "$TEST_RESULTS_DIR" - cat >"$test_results_path" <<-EOF + cat >"$TEST_RESULTS_BASE.counts" <<-EOF total $test_count success $test_success fixed $test_fixed
@@ -1040,7 +1040,7 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" +TRASH_DIRECTORY="trash directory.$TEST_NAME" test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" case "$TRASH_DIRECTORY" in /*) ;; # absolute path is good
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH v2 4/7 test-lib: set $TRASH_DIRECTORY earlier
In reply to SZEDER GáborA later patch in this series will need to know the path to the trash directory early in 'test-lib.sh', but $TRASH_DIRECTORY is set much later. Furthermore, the path to the trash directory depends on the '--root=<path>' option, which, too, is parsed too late.
Move parsing '--root=...' to the early option parsing loop, and set $TRASH_DIRECTORY where the other test-specific path variables are set.
--- t/test-lib.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-)
t/test-lib.sh +10 -10
@@ -85,6 +85,8 @@ do valgrind=${opt#--*=} ;; --valgrind-only=*) valgrind_only=${opt#--*=} ;; + --root=*) + root=${opt#--*=} ;; *) # Other options will be handled later. esac
@@ -93,6 +95,12 @@ done TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" +TRASH_DIRECTORY="trash directory.$TEST_NAME" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.
@@ -318,9 +326,6 @@ do with_dashes=t; shift ;; --no-color) color=; shift ;; - --root=*) - root=${1#--*=} - shift ;; --chain-lint) GIT_TEST_CHAIN_LINT=1 shift ;;
@@ -351,7 +356,8 @@ do -V|--verbose-log|\ --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind|\ --valgrind=*|\ - --valgrind-only=*) + --valgrind-only=*|\ + --root=*) shift ;; # These options were handled already. *) echo "error: unknown test option '$1'" >&2; exit 1 ;;
@@ -1040,12 +1046,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$TEST_NAME" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area"
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH v2 5/7 test-lib: extract Bash version check for '-x' tracing
In reply to SZEDER GáborSome of our test scripts can't be reliably run with '-x' tracing enabled unless executed with a Bash version supporting BASH_XTRACEFD (since v4.1), and we have a lengthy condition to disable tracing if such a test script is not executed with a suitable Bash version.
Move this check out from the option parsing loop, so other options can imply '-x' by setting 'trace=t', without missing this Bash version check.
--- t/test-lib.sh | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-)
t/test-lib.sh +19 -18
@@ -333,24 +333,7 @@ do GIT_TEST_CHAIN_LINT=0 shift ;; -x) - # Some test scripts can't be reliably traced with '-x', - # unless the test is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). Check whether - # this test is marked as such, and ignore '-x' if it - # isn't executed with a suitable Bash version. - if test -z "$test_untraceable" || { - test -n "$BASH_VERSION" && { - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - } - } - then - trace=t - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - fi + trace=t shift ;; --tee|\ -V|--verbose-log|\
@@ -373,6 +356,24 @@ then test -z "$verbose_log" && verbose=t fi +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + if test -n "$BASH_VERSION" && { + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + } + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi if test -n "$trace" && test -z "$verbose_log" then verbose=t
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH v2 6/7 test-lib-functions: introduce the 'test_set_port' helper function
In reply to SZEDER GáborSeveral test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Factor out finding the port for a daemon into the common helper function 'test_set_port' to avoid repeating ourselves.
Take special care of test scripts with "low" numbers:
- Test numbers below 1024 would result in a port that's only usable as root, so set their port to '10000 + test-nr' to make sure it doesn't interfere with other tests in the test suite. This makes the hardcoded port number in 't0410-partial-clone.sh' unnecessary, remove it.
- The shell's arithmetic evaluation interprets numbers with leading zeros as octal values, which means that test number below 1000 and containing the digits 8 or 9 will trigger an error. Remove all leading zeros from the test numbers to prevent this.
Note that the 'git p4' tests are unlike the other tests involving daemons in that:
- 'lib-git-p4.sh' doesn't use the test's number for unique port as is, but does a bit of additional arithmetic on top [1].
- The port is not overridable via an environment variable.
With this patch even 'git p4' tests will use the test's number as default port, and it will be overridable via the P4DPORT environment variable.
[1] Commit fc00233071 (git-p4 tests: refactor and cleanup, 2011-08-22) introduced that "unusual" unique port computation without explaining why it was necessary (as opposed to simply using the test number as is). It seems to be just unnecessary complication, and in any case that commit came way before the "test nr as unique port" got "standardized" for other daemons in commits c44132fcf3 (tests: auto-set git-daemon port, 2014-02-10), 3bb486e439 (tests:
auto-set LIB_HTTPD_PORT from test name, 2014-02-10), and bf9d7df950 (t/lib-git-svn.sh: improve svnserve tests with parallel make test, 2017-12-01).
--- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +-------- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 41 insertions(+), 13 deletions(-)
t/lib-git-daemon.sh +1 -1
@@ -28,7 +28,7 @@ then test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" fi -LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} +test_set_port LIB_GIT_DAEMON_PORT GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
t/lib-git-p4.sh +1 -8
@@ -53,14 +53,7 @@ time_in_seconds () { (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } -# Try to pick a unique port: guess a large number, then hope -# no more than one of each test is running. -# -# This does not handle the case where somebody else is running the -# same tests and has chosen the same ports. -testid=${this_test#t} -git_p4_test_start=9800 -P4DPORT=$((10669 + ($testid - $git_p4_test_start))) +test_set_port P4DPORT P4PORT=localhost:$P4DPORT P4CLIENT=client
t/lib-git-svn.sh +1 -1
@@ -13,6 +13,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +test_set_port SVNSERVE_PORT svn >/dev/null 2>&1 if test $? -ne 1
@@ -119,7 +120,6 @@ require_svnserve () { } start_svnserve () { - SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} svnserve --listen-port $SVNSERVE_PORT \ --root "$rawsvnrepo" \ --listen-once \
t/lib-httpd.sh +1 -1
@@ -82,7 +82,7 @@ case $(uname) in esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}} +test_set_port LIB_HTTPD_PORT TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd
t/t0410-partial-clone.sh +0 -1
@@ -480,7 +480,6 @@ test_expect_success 'gc stops traversal when a missing but promised object is re ! grep "$TREE_HASH" out ' -LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
t/t5512-ls-remote.sh +1 -1
@@ -260,7 +260,7 @@ test_lazy_prereq GIT_DAEMON ' # This test spawns a daemon, so run it only if the user would be OK with # testing with git-daemon. test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' - JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + test_set_port JGIT_DAEMON_PORT && JGIT_DAEMON_PID= && git init --bare empty.git && >empty.git/git-daemon-export-ok &&
t/test-lib-functions.sh +36 -0
@@ -1263,3 +1263,39 @@ test_oid () { fi && eval "printf '%s' \"\${$var}\"" } + +# Choose a port number based on the test script's number and store it in +# the given variable name, unless that variable already contains a number. +test_set_port () { + local var=$1 port + + if test $# -ne 1 || test -z "$var" + then + BUG "test_set_port requires a variable name" + fi + + eval port=\$$var + case "$port" in + "") + # No port is set in the given env var, use the test + # number as port number instead. + # Remove not only the leading 't', but all leading zeros + # as well, so the arithmetic below won't (mis)interpret + # a test number like '0123' as an octal value. + port=${this_test#${this_test%%[1-9]*}} + if test "${port:-0}" -lt 1024 + then + # root-only port, use a larger one instead. + port=$(($port + 10000)) + fi + + eval $var=$port + ;; + *[^0-9]*) + error >&7 "invalid port number: $port" + ;; + *) + # The user has specified the port. + ;; + esac +}
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH v2 7/7 test-lib: add the '--stress' option to run a test repeatedly under load
In reply to SZEDER GáborUnfortunately, we have a few flaky tests, whose failures tend to be hard to reproduce. We've found that the best we can do to reproduce such a failure is to run the test repeatedly while the machine is under load, and wait in the hope that the load creates enough variance in the timing of the test's commands that a failure is evenually triggered. I have a command to do that, and I noticed that two other contributors have rolled their own scripts to do the same, all choosing slightly different approaches.
To help reproduce failures in flaky tests, introduce the '--stress' option to run a test script repeatedly in multiple parallel jobs until one of them fails, thereby using the test script itself to increase the load on the machine.
The number of parallel jobs is determined by, in order of precedence:
the number specified as '--stress=<N>', or the value of the GIT_TEST_STRESS_LOAD environment variable, or twice the number of available processors (as reported by the 'getconf' utility), or 8.
Make '--stress' imply '--verbose -x --immediate' to get the most information about rare failures; there is really no point in spending all the extra effort to reproduce such a failure, and then not know which command failed and why.
To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
- Redirect each parallel test run's output to 't/test-results/$TEST_NAME.stress-<nr>.out', because dumping the output of several parallel running tests to the terminal would create a big ugly mess.
For convenience, print the output of the failed test job at the end, and rename its trash directory to end with the '.stress-failed' suffix, so it's easy to find in a predictable path. If, in an unlikely case, more than one jobs were to fail nearly at the same time, then print the output of all failed jobs, and rename the trash directory of only the last one (i.e. with the highest job number), as it is the trash directory of the test whose output will be at the bottom of the user's terminal.
Based on Jeff King's 'stress' script.
--- t/README | 19 +++++++- t/test-lib-functions.sh | 7 ++- t/test-lib.sh | 103 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 123 insertions(+), 6 deletions(-)
t/README +18 -1
@@ -186,6 +186,22 @@ appropriately before running "make". this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. +--stress:: +--stress=<N>:: + Run the test script repeatedly in multiple parallel jobs until + one of them fails. Useful for reproducing rare failures in + flaky tests. The number of parallel jobs is, in order of + precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD + environment variable, or twice the number of available + processors (as shown by the 'getconf' utility), or 8. + Implies `--verbose -x --immediate` to get the most information + about the failure. Note that the verbose output of each test + job is saved to 't/test-results/$TEST_NAME.stress-<nr>.out', + and only the output of the failed test job is shown on the + terminal. The names of the trash directories get a + '.stress-<nr>' suffix, and the trash directory of the failed + test job is renamed to end with a '.stress-failed' suffix. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various
@@ -425,7 +441,8 @@ This test harness library does the following things: - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash directory.$test_name_without_dotsh', with t/ subject to change by - the --root option documented above. + the --root option documented above, and a '.stress-<N>' suffix + appended by the --stress option. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave
t/test-lib-functions.sh +5 -2
@@ -1288,8 +1288,6 @@ test_set_port () { # root-only port, use a larger one instead. port=$(($port + 10000)) fi - - eval $var=$port ;; *[^0-9]*) error >&7 "invalid port number: $port"
@@ -1298,4 +1296,9 @@ test_set_port () { # The user has specified the port. ;; esac + + # Make sure that parallel '--stress' test jobs get different + # ports. + port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0})) + eval $var=$port }
t/test-lib.sh +100 -3
134 lines not on this page · raw message
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
PATCH 1/1 fixup! test-lib: add the '--stress' option to run a test repeatedly under load
---
Erm. 'trace=t' must be set before checking whether the shell supports BASH_XTRACEFD.
t/test-lib.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
t/test-lib.sh +7 -7
@@ -446,6 +446,13 @@ then test -z "$verbose_log" && verbose=t fi +if test -n "$stress" +then + verbose=t + trace=t + immediate=t +fi + if test -n "$trace" && test -n "$test_untraceable" then # '-x' tracing requested, but this test script can't be reliably
@@ -469,13 +476,6 @@ then verbose=t fi -if test -n "$stress" -then - verbose=t - trace=t - immediate=t -fi - if test -n "$color" then # Save the color control sequences now rather than run tput
Signature · 1 line
2.20.0.rc2.156.g5a9fd2ce9c
Quoted 6 lines · SZEDER Gábor
This patch series tries to make reproducing rare failures in flaky tests easier: it adds the '--stress' option to our test library to run the test script repeatedly in multiple parallel jobs, in the hope that the increased load creates enough variance in the timing of the test's commands that such a failure is eventually triggered.
Quoted 6 lines
Notable changes since v1:
- Made it more Peff-friendly :), namely it will now show the log of the failed test and will rename its trash directory.
Furthermore, '--stress' will now imply '--verbose -x --immediate'.
:) Thanks. I do sympathize with the notion of keeping orthogonal things orthogonal, but I hope that this will make the tool a little more pleasant to use out of the box. We can always tweak the behavior later, too. It's not like this is something user-facing that we've promised as a scripting interface.
Quoted 5 lines
- Improved abort handling based on the discussion of the previous version. (As a result, the patch is so heavily modified, that 'range-diff' with default parameters consideres it a different patch; increasing the creation factor then results in one big ugly diff of a diff, so I left it as-is.)
Yeah, this all looked good to me.
Quoted 2 lines
- Added a few new patches, mostly preparatory refactorings, though the first one might be considered a bugfix.
I left a few minor comments, but these all looked good to me.
-Peff
v3 cover test-lib: add the '--stress' option to help reproduce occasional failures in flaky tests
In reply to SZEDER GáborTo recap: this patch series tries to make reproducing rare failures in flaky tests easier: it adds the '--stress' option to our test library to run the test script repeatedly in multiple parallel jobs, in the hope that the increased load creates enough variance in the timing of the test's commands that such a failure is eventually triggered.
Changes since v2:
- Most importantly, parse all options before handling '--tee', '--verbose-log' or '--stress'. This constitutes the bulk of the range diff.
- Add a bit of sanity check for the argument of '--stress=123'.
- Minor commit message updates.
v2 was quickly followed up with a fixup! patch; the range-diff below includes that fixup! already squashed in.
Previous versions:
v2: https://public-inbox.org/git/[email protected]/T/#u
v1: https://public-inbox.org/git/[email protected]/T/
SZEDER Gábor (8):
test-lib: translate SIGTERM and SIGHUP to an exit
test-lib: parse options in a for loop to keep $@ intact
test-lib: parse command line options earlier
test-lib: consolidate naming of test-results paths
test-lib: set $TRASH_DIRECTORY earlier
test-lib: extract Bash version check for '-x' tracing
test-lib-functions: introduce the 'test_set_port' helper function
test-lib: add the '--stress' option to run a test repeatedly under
load
t/README | 19 ++- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 39 +++++ t/test-lib.sh | 360 ++++++++++++++++++++++++++------------- 9 files changed, 303 insertions(+), 133 deletions(-)
Range-diff:
1: 3a5c926167 = 1: 48a2b19218 test-lib: translate SIGTERM and SIGHUP to an exit 2: 8eee8d7fba < -: ---------- test-lib: parse some --options earlier -: ---------- > 2: adc5e8a86e test-lib: parse options in a for loop to keep $@ intact -: ---------- > 3: 89748074db test-lib: parse command line options earlier 3: dd20ae5e9a ! 4: 5a6d17f54a test-lib: consolidate naming of test-results paths
@@ -16,8 +16,8 @@
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@
- esac
- done
+ verbose=t
+ fi
+TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
@@ -27,8 +27,8 @@
# additionally to the file test-results/$BASENAME.out, too. if test "$GIT_TEST_TEE_STARTED" = "done"
@@
- elif test -n "$tee" || test -n "$verbose_log" ||
- test -n "$valgrind" || test -n "$valgrind_only"
+ : # do not redirect again
+ elif test -n "$tee"
then
- mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
- BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
4: f6e56c91c4 ! 5: cd7626b782 test-lib: set $TRASH_DIRECTORY earlier
@@ -15,15 +15,6 @@
diff --git a/t/test-lib.sh b/t/test-lib.sh
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
-@@
- valgrind=${opt#--*=} ;;
- --valgrind-only=*)
- valgrind_only=${opt#--*=} ;;
-+ --root=*)
-+ root=${opt#--*=} ;;
- *)
- # Other options will be handled later.
- esac
@@
TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
@@ -37,26 +28,6 @@
# if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.
-@@
- with_dashes=t; shift ;;
- --no-color)
- color=; shift ;;
-- --root=*)
-- root=${1#--*=}
-- shift ;;
- --chain-lint)
- GIT_TEST_CHAIN_LINT=1
- shift ;;
-@@
- -V|--verbose-log|\
- --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind|\
- --valgrind=*|\
-- --valgrind-only=*)
-+ --valgrind-only=*|\
-+ --root=*)
- shift ;; # These options were handled already.
- *)
- echo "error: unknown test option '$1'" >&2; exit 1 ;;
@@
fi
5: 99ecd2902e ! 6: 90296ac776 test-lib: extract Bash version check for '-x' tracing
@@ -2,23 +2,30 @@
test-lib: extract Bash version check for '-x' tracing
- Some of our test scripts can't be reliably run with '-x' tracing
- enabled unless executed with a Bash version supporting BASH_XTRACEFD
- (since v4.1), and we have a lengthy condition to disable tracing if
- such a test script is not executed with a suitable Bash version.
+ One of our test scripts, 't1510-repo-setup.sh' [1], still can't be
+ reliably run with '-x' tracing enabled, unless it's executed with a
+ Bash version supporting BASH_XTRACEFD (since v4.1). We have a lengthy
+ condition to check the version of the shell running the test script,
+ and disable tracing if it's not executed with a suitable Bash version
+ [2].
Move this check out from the option parsing loop, so other options can imply '-x' by setting 'trace=t', without missing this Bash version check.
+ [1] 5827506928 (t1510-repo-setup: mark as untraceable with '-x',
+ 2018-02-24)
+ [2] 5fc98e79fc (t: add means to disable '-x' tracing for individual
+ test scripts, 2018-02-24)
+
diff --git a/t/test-lib.sh b/t/test-lib.sh
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@
- GIT_TEST_CHAIN_LINT=0
- shift ;;
+ --no-chain-lint)
+ GIT_TEST_CHAIN_LINT=0 ;;
-x)
- # Some test scripts can't be reliably traced with '-x',
- # unless the test is run with a Bash version supporting
@@ -38,10 +45,11 @@
- else
- echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD"
- fi
-+ trace=t
- shift ;;
- --tee|\
- -V|--verbose-log|\
+- ;;
++ trace=t ;;
+ -V|--verbose-log)
+ verbose_log=t
+ tee=t
@@
test -z "$verbose_log" && verbose=t
fi
6: dcf7d2a397 ! 7: a4a3e7cefa test-lib-functions: introduce the 'test_set_port' helper function
@@ -178,7 +178,7 @@
+
+ eval $var=$port
+ ;;
-+ *[^0-9]*)
++ *[^0-9]*|0*)
+ error >&7 "invalid port number: $port"
+ ;;
+ *)7: f27f2fc1d0 ! 8: b0dcff0d3f test-lib: add the '--stress' option to run a test repeatedly under load
@@ -4,12 +4,12 @@
Unfortunately, we have a few flaky tests, whose failures tend to be hard to reproduce. We've found that the best we can do to reproduce
- such a failure is to run the test repeatedly while the machine is
- under load, and wait in the hope that the load creates enough variance
- in the timing of the test's commands that a failure is evenually
- triggered. I have a command to do that, and I noticed that two other
- contributors have rolled their own scripts to do the same, all
- choosing slightly different approaches.
+ such a failure is to run the test script repeatedly while the machine
+ is under load, and wait in the hope that the load creates enough
+ variance in the timing of the test's commands that a failure is
+ evenually triggered. I have a command to do that, and I noticed that
+ two other contributors have rolled their own scripts to do the same,
+ all choosing slightly different approaches.
To help reproduce failures in flaky tests, introduce the '--stress' option to run a test script repeatedly in multiple parallel jobs until
@@ -37,19 +37,21 @@
user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
- - Redirect each parallel test run's output to
+ - Redirect each parallel test run's verbose output to
't/test-results/$TEST_NAME.stress-<nr>.out', because dumping the output of several parallel running tests to the terminal would create a big ugly mess.
For convenience, print the output of the failed test job at the end, and rename its trash directory to end with the '.stress-failed'
- suffix, so it's easy to find in a predictable path. If, in an
- unlikely case, more than one jobs were to fail nearly at the same
- time, then print the output of all failed jobs, and rename the trash
- directory of only the last one (i.e. with the highest job number), as
- it is the trash directory of the test whose output will be at the
- bottom of the user's terminal.
+ suffix, so it's easy to find in a predictable path (OTOH, all absolute
+ paths recorded in the trash directory become invalid; we'll see
+ whether this causes any issues in practice). If, in an unlikely case,
+ more than one jobs were to fail nearly at the same time, then print
+ the output of all failed jobs, and rename the trash directory of only
+ the last one (i.e. with the highest job number), as it is the trash
+ directory of the test whose output will be at the bottom of the user's
+ terminal.
Based on Jeff King's 'stress' script.
@@ -102,7 +104,7 @@
-
- eval $var=$port
;;
- *[^0-9]*)
+ *[^0-9]*|0*)
error >&7 "invalid port number: $port"
@@
# The user has specified the port.
@@ -119,17 +121,42 @@
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@
- valgrind_only=${opt#--*=} ;;
- --root=*)
- root=${opt#--*=} ;;
+ verbose_log=t
+ tee=t
+ ;;
+ --stress)
+ stress=t ;;
+ --stress=*)
-+ stress=${opt#--*=} ;;
++ stress=${opt#--*=}
++ case "$stress" in
++ *[^0-9]*|0*|"")
++ echo "error: --stress=<N> requires the number of jobs to run" >&2
++ exit 1
++ ;;
++ *) # Good.
++ ;;
++ esac
++ ;;
*)
- # Other options will be handled later.
+ echo "error: unknown test option '$opt'" >&2; exit 1 ;;
esac
- done
+@@
+ test -z "$verbose_log" && verbose=t
+ fi
+
++if test -n "$stress"
++then
++ verbose=t
++ trace=t
++ immediate=t
++fi
++
+ if test -n "$trace" && test -n "$test_untraceable"
+ then
+ # '-x' tracing requested, but this test script can't be reliably
+@@
+ verbose=t
+ fi
+TEST_STRESS_JOB_SFX="${GIT_TEST_STRESS_JOB_NR:+.stress-$GIT_TEST_STRESS_JOB_NR}"
TEST_NAME="$(basename "$0" .sh)"
@@ -231,27 +258,3 @@
# if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.
if test "$GIT_TEST_TEE_STARTED" = "done"
-@@
- --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind|\
- --valgrind=*|\
- --valgrind-only=*|\
-- --root=*)
-+ --root=*|\
-+ --stress|--stress=*)
- shift ;; # These options were handled already.
- *)
- echo "error: unknown test option '$1'" >&2; exit 1 ;;
-@@
- verbose=t
- fi
-
-+if test -n "$stress"
-+then
-+ verbose=t
-+ trace=t
-+ immediate=t
-+fi
-+
- if test -n "$color"
- then
- # Save the color control sequences now rather than run tput
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 1/8 test-lib: translate SIGTERM and SIGHUP to an exit
Right now if a test script receives SIGTERM or SIGHUP (e.g., because a test was hanging and the user 'kill'-ed it or simply closed the terminal window the test was running in), the shell exits immediately. This can be annoying if the test script did any global setup, like starting apache or git-daemon, as it will not have an opportunity to clean up after itself. A subsequent run of the test won't be able to start its own daemon, and will either fail or skip the tests.
Instead, let's trap SIGTERM and SIGHUP as well to make sure we do a clean shutdown, and just chain it to a normal exit (which will trigger any cleanup).
This patch follows suit of da706545f7 (t: translate SIGINT to an exit, 2015-03-13), and even stole its commit message as well.
--- t/test-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/test-lib.sh +1 -1
@@ -476,7 +476,7 @@ die () { GIT_EXIT_OK= trap 'die' EXIT -trap 'exit $?' INT +trap 'exit $?' INT TERM HUP # The user-facing functions are loaded from a separate file so that # test_perf subshells can have them too
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 2/8 test-lib: parse options in a for loop to keep $@ intact
In reply to SZEDER Gábor'test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error, and to do so it needs the original command line options the test was invoked with.
The next patch is about to move the option parsing loop earlier in 'test-lib.sh', but it is implemented using 'shift' in a while loop, effecively destroying "$@" by the end of the option parsing. Not good.
As a preparatory step, turn that option parsing loop into a 'for opt in "$@"' loop to preserve "$@" intact while iterating over the options, and taking extra care to handle the '-r' option's required argument (or the lack thereof).
--- t/test-lib.sh | 77 ++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 35 deletions(-)
t/test-lib.sh +42 -35
@@ -264,58 +264,59 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 +store_arg_to= +prev_opt= +for opt do - case "$1" in + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + + case "$opt" in -d|--d|--de|--deb|--debu|--debug) - debug=t; shift ;; + debug=t ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t; shift ;; + immediate=t ;; -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 ;; + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; -r) - shift; test "$#" -ne 0 || { - echo 'error: -r requires an argument' >&2; - exit 1; - } - run_list=$1; shift ;; + store_arg_to=run_list + ;; --run=*) - run_list=${1#--*=}; shift ;; + run_list=${opt#--*=} ;; -h|--h|--he|--hel|--help) - help=t; shift ;; + help=t ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t; shift ;; + verbose=t ;; --verbose-only=*) - verbose_only=${1#--*=} - shift ;; + verbose_only=${opt#--*=} + ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; + test -z "$HARNESS_ACTIVE" && quiet=t ;; --with-dashes) - with_dashes=t; shift ;; + with_dashes=t ;; --no-color) - color=; shift ;; + color= ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck - shift ;; + valgrind=memcheck ;; --valgrind=*) - valgrind=${1#--*=} - shift ;; + valgrind=${opt#--*=} ;; --valgrind-only=*) - valgrind_only=${1#--*=} - shift ;; + valgrind_only=${opt#--*=} ;; --tee) - shift ;; # was handled already + ;; # was handled already --root=*) - root=${1#--*=} - shift ;; + root=${opt#--*=} ;; --chain-lint) - GIT_TEST_CHAIN_LINT=1 - shift ;; + GIT_TEST_CHAIN_LINT=1 ;; --no-chain-lint) - GIT_TEST_CHAIN_LINT=0 - shift ;; + GIT_TEST_CHAIN_LINT=0 ;; -x) # Some test scripts can't be reliably traced with '-x', # unless the test is run with a Bash version supporting
@@ -335,14 +336,20 @@ do else echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" fi - shift ;; + ;; -V|--verbose-log) - verbose_log=t - shift ;; + verbose_log=t ;; *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; + echo "error: unknown test option '$opt'" >&2; exit 1 ;; esac + + prev_opt=$opt done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi if test -n "$valgrind_only" then
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 3/8 test-lib: parse command line options earlier
In reply to SZEDER Gábor'test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error. It looks for '--valgrind' as well, to set up some Valgrind-specific stuff. These all happen before the actual option parsing loop, and the conditions looking for these options look a bit odd, too. They are not completely correct, either, because in a bogus invocation like './t1234-foo.sh -r --tee' they recognize '--tee', although it should be handled as the required argument of the '-r' option. This patch series will add two more options to look out for early, and, in addition, will have to extract these options' stuck arguments (i.e. '--opt=arg') as well.
So let's move the option parsing loop and the couple of related conditions following it earlier in 'test-lib.sh', before the place where the test script is executed again for '--tee' and its friends.
--- t/test-lib.sh | 228 ++++++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 109 deletions(-)
t/test-lib.sh +119 -109
@@ -71,13 +71,125 @@ then exit 1 fi +# Parse options while taking care to leave $@ intact, so we will still +# have all the original command line options when executing the test +# script again for '--tee' and '--verbose-log' below. +store_arg_to= +prev_opt= +for opt +do + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + + case "$opt" in + -d|--d|--de|--deb|--debu|--debug) + debug=t ;; + -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) + immediate=t ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; + -r) + store_arg_to=run_list + ;; + --run=*) + run_list=${opt#--*=} ;; + -h|--h|--he|--hel|--help) + help=t ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + --verbose-only=*) + verbose_only=${opt#--*=} + ;; + -q|--q|--qu|--qui|--quie|--quiet) + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t ;; + --with-dashes) + with_dashes=t ;; + --no-color) + color= ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=memcheck + tee=t + ;; + --valgrind=*) + valgrind=${opt#--*=} + tee=t + ;; + --valgrind-only=*) + valgrind_only=${opt#--*=} + tee=t + ;; + --tee) + tee=t ;; + --root=*) + root=${opt#--*=} ;; + --chain-lint) + GIT_TEST_CHAIN_LINT=1 ;; + --no-chain-lint) + GIT_TEST_CHAIN_LINT=0 ;; + -x) + # Some test scripts can't be reliably traced with '-x', + # unless the test is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). Check whether + # this test is marked as such, and ignore '-x' if it + # isn't executed with a suitable Bash version. + if test -z "$test_untraceable" || { + test -n "$BASH_VERSION" && { + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + } + } + then + trace=t + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + fi + ;; + -V|--verbose-log) + verbose_log=t + tee=t + ;; + *) + echo "error: unknown test option '$opt'" >&2; exit 1 ;; + esac + + prev_opt=$opt +done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi + +if test -n "$valgrind_only" +then + test -z "$valgrind" && valgrind=memcheck + test -z "$verbose" && verbose_only="$valgrind_only" +elif test -n "$valgrind" +then + test -z "$verbose_log" && verbose=t +fi + +if test -n "$trace" && test -z "$verbose_log" +then + verbose=t +fi + # 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'*|*' -V '*|*' --verbose-log '*) +if test "$GIT_TEST_TEE_STARTED" = "done" +then + : # do not redirect again +elif test -n "$tee" +then mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
@@ -94,8 +206,7 @@ done,*) echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" test "$(cat "$BASE.exit")" = 0 exit - ;; -esac +fi # For repeatability, reset the environment to known value. # TERM is sanitized below, after saving color control sequences.
@@ -193,7 +304,7 @@ fi # Add libc MALLOC and MALLOC_PERTURB test # only if we are not executing the test with valgrind -if expr " $GIT_TEST_OPTS " : ".* --valgrind " >/dev/null || +if test -n "$valgrind" || test -n "$TEST_NO_MALLOC_CHECK" then setup_malloc_check () {
@@ -264,107 +375,6 @@ test "x$TERM" != "xdumb" && ( ) && color=t -store_arg_to= -prev_opt= -for opt -do - if test -n "$store_arg_to" - then - eval $store_arg_to=\$opt - store_arg_to= - prev_opt= - continue - fi - - case "$opt" in - -d|--d|--de|--deb|--debu|--debug) - debug=t ;; - -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t ;; - -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) - GIT_TEST_LONG=t; export GIT_TEST_LONG ;; - -r) - store_arg_to=run_list - ;; - --run=*) - run_list=${opt#--*=} ;; - -h|--h|--he|--hel|--help) - help=t ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t ;; - --verbose-only=*) - verbose_only=${opt#--*=} - ;; - -q|--q|--qu|--qui|--quie|--quiet) - # Ignore --quiet under a TAP::Harness. Saying how many tests - # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t ;; - --with-dashes) - with_dashes=t ;; - --no-color) - color= ;; - --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck ;; - --valgrind=*) - valgrind=${opt#--*=} ;; - --valgrind-only=*) - valgrind_only=${opt#--*=} ;; - --tee) - ;; # was handled already - --root=*) - root=${opt#--*=} ;; - --chain-lint) - GIT_TEST_CHAIN_LINT=1 ;; - --no-chain-lint) - GIT_TEST_CHAIN_LINT=0 ;; - -x) - # Some test scripts can't be reliably traced with '-x', - # unless the test is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). Check whether - # this test is marked as such, and ignore '-x' if it - # isn't executed with a suitable Bash version. - if test -z "$test_untraceable" || { - test -n "$BASH_VERSION" && { - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - } - } - then - trace=t - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - fi - ;; - -V|--verbose-log) - verbose_log=t ;; - *) - echo "error: unknown test option '$opt'" >&2; exit 1 ;; - esac - - prev_opt=$opt -done -if test -n "$store_arg_to" -then - echo "error: $prev_opt requires an argument" >&2 - exit 1 -fi - -if test -n "$valgrind_only" -then - test -z "$valgrind" && valgrind=memcheck - test -z "$verbose" && verbose_only="$valgrind_only" -elif test -n "$valgrind" -then - test -z "$verbose_log" && verbose=t -fi - -if test -n "$trace" && test -z "$verbose_log" -then - verbose=t -fi - if test -n "$color" then # Save the color control sequences now rather than run tput
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 4/8 test-lib: consolidate naming of test-results paths
In reply to SZEDER GáborThere are two places where we strip off any leading path components and the '.sh' suffix from the test script's pathname, and there are four places where we construct the name of the 't/test-results' directory or the name of various test-specific files in there. The last patch in this series will add even more.
Factor these out into helper variables to avoid repeating ourselves.
--- t/test-lib.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
t/test-lib.sh +11 -11
@@ -183,6 +183,10 @@ then verbose=t fi +TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" + # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. if test "$GIT_TEST_TEE_STARTED" = "done"
@@ -190,12 +194,11 @@ then : # do not redirect again elif test -n "$tee" then - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" + mkdir -p "$TEST_RESULTS_DIR" # Make this filename available to the sub-process in case it is using # --verbose-log. - GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out export GIT_TEST_TEE_OUTPUT_FILE # Truncate before calling "tee -a" to get rid of the results
@@ -203,8 +206,8 @@ then >"$GIT_TEST_TEE_OUTPUT_FILE" (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 + echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit fi
@@ -835,12 +838,9 @@ test_done () { if test -z "$HARNESS_ACTIVE" then - test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}.counts" + mkdir -p "$TEST_RESULTS_DIR" - cat >"$test_results_path" <<-EOF + cat >"$TEST_RESULTS_BASE.counts" <<-EOF total $test_count success $test_success fixed $test_fixed
@@ -1046,7 +1046,7 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" +TRASH_DIRECTORY="trash directory.$TEST_NAME" test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" case "$TRASH_DIRECTORY" in /*) ;; # absolute path is good
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 5/8 test-lib: set $TRASH_DIRECTORY earlier
In reply to SZEDER GáborA later patch in this series will need to know the path to the trash directory early in 'test-lib.sh', but $TRASH_DIRECTORY is set much later. Furthermore, the path to the trash directory depends on the '--root=<path>' option, which, too, is parsed too late.
Move parsing '--root=...' to the early option parsing loop, and set $TRASH_DIRECTORY where the other test-specific path variables are set.
--- t/test-lib.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
t/test-lib.sh +6 -6
@@ -186,6 +186,12 @@ fi TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" +TRASH_DIRECTORY="trash directory.$TEST_NAME" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.
@@ -1046,12 +1052,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$TEST_NAME" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area"
Signature · 1 line
2.20.1.151.gec613c4b75
Re: [PATCH v3 5/8] test-lib: set $TRASH_DIRECTORY earlier
Quoted 7 lines · SZEDER Gábor
A later patch in this series will need to know the path to the trash directory early in '
test-lib.sh', but $TRASH_DIRECTORY is set much later. Furthermore, the path to the trash directory depends on the '--root=<path>' option, which, too, is parsed too late.Move parsing '--root=...' to the early option parsing loop, and set $TRASH_DIRECTORY where the other test-specific path variables are set.
Sigh.
After moving the whole option parsing loop, I should have updated this commit message as well.
Hang on for a sec...
Quoted 38 lines
Signed-off-by SZEDER Gábor <[email protected]>--- t/test-lib.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)t/test-lib.sh +6
-6@@ -186,6 +186,12 @@ fi TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" +TRASH_DIRECTORY="trash directory.$TEST_NAME" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.@@ -1046,12 +1052,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$TEST_NAME" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area"Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 5/8 test-lib: set $TRASH_DIRECTORY earlier
A later patch in this series will need to know the path to the trash directory early in 'test-lib.sh', but $TRASH_DIRECTORY is set much later.
Set $TRASH_DIRECTORY earlier, where the other test-specific path variables are set.
---
Updated the commit message, the diff is the same.
t/test-lib.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
t/test-lib.sh +6 -6
@@ -186,6 +186,12 @@ fi TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" +TRASH_DIRECTORY="trash directory.$TEST_NAME" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.
@@ -1046,12 +1052,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$TEST_NAME" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area"
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 6/8 test-lib: extract Bash version check for '-x' tracing
In reply to SZEDER GáborOne of our test scripts, 't1510-repo-setup.sh' [1], still can't be reliably run with '-x' tracing enabled, unless it's executed with a Bash version supporting BASH_XTRACEFD (since v4.1). We have a lengthy condition to check the version of the shell running the test script, and disable tracing if it's not executed with a suitable Bash version [2].
Move this check out from the option parsing loop, so other options can imply '-x' by setting 'trace=t', without missing this Bash version check.
[1] 5827506928 (t1510-repo-setup: mark as untraceable with '-x',
2018-02-24)
[2] 5fc98e79fc (t: add means to disable '-x' tracing for individual
test scripts, 2018-02-24)--- t/test-lib.sh | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-)
t/test-lib.sh +19 -19
@@ -134,25 +134,7 @@ do --no-chain-lint) GIT_TEST_CHAIN_LINT=0 ;; -x) - # Some test scripts can't be reliably traced with '-x', - # unless the test is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). Check whether - # this test is marked as such, and ignore '-x' if it - # isn't executed with a suitable Bash version. - if test -z "$test_untraceable" || { - test -n "$BASH_VERSION" && { - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - } - } - then - trace=t - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - fi - ;; + trace=t ;; -V|--verbose-log) verbose_log=t tee=t
@@ -178,6 +160,24 @@ then test -z "$verbose_log" && verbose=t fi +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + if test -n "$BASH_VERSION" && { + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + } + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi if test -n "$trace" && test -z "$verbose_log" then verbose=t
Signature · 1 line
2.20.1.151.gec613c4b75
Re: [PATCH v3 6/8] test-lib: extract Bash version check for '-x' tracing
slightly offtopic and definitely not meant to be relevant as a critic on this specific change which I found otherwise fine, but just a heads up that this test itself is bash specific (since it uses arrays, against the recommendation in Documentation/CodingGuidelines) and therefore breaks, at least in NetBSD as reported in :
https://public-inbox.org/git/[email protected]/
Carlo
PATCH v3 7/8 test-lib-functions: introduce the 'test_set_port' helper function
In reply to SZEDER GáborSeveral test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Factor out finding the port for a daemon into the common helper function 'test_set_port' to avoid repeating ourselves.
Take special care of test scripts with "low" numbers:
- Test numbers below 1024 would result in a port that's only usable as root, so set their port to '10000 + test-nr' to make sure it doesn't interfere with other tests in the test suite. This makes the hardcoded port number in 't0410-partial-clone.sh' unnecessary, remove it.
- The shell's arithmetic evaluation interprets numbers with leading zeros as octal values, which means that test number below 1000 and containing the digits 8 or 9 will trigger an error. Remove all leading zeros from the test numbers to prevent this.
Note that the 'git p4' tests are unlike the other tests involving daemons in that:
- 'lib-git-p4.sh' doesn't use the test's number for unique port as is, but does a bit of additional arithmetic on top [1].
- The port is not overridable via an environment variable.
With this patch even 'git p4' tests will use the test's number as default port, and it will be overridable via the P4DPORT environment variable.
[1] Commit fc00233071 (git-p4 tests: refactor and cleanup, 2011-08-22) introduced that "unusual" unique port computation without explaining why it was necessary (as opposed to simply using the test number as is). It seems to be just unnecessary complication, and in any case that commit came way before the "test nr as unique port" got "standardized" for other daemons in commits c44132fcf3 (tests: auto-set git-daemon port, 2014-02-10), 3bb486e439 (tests:
auto-set LIB_HTTPD_PORT from test name, 2014-02-10), and bf9d7df950 (t/lib-git-svn.sh: improve svnserve tests with parallel make test, 2017-12-01).
--- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +-------- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 41 insertions(+), 13 deletions(-)
t/lib-git-daemon.sh +1 -1
@@ -28,7 +28,7 @@ then test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" fi -LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} +test_set_port LIB_GIT_DAEMON_PORT GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
t/lib-git-p4.sh +1 -8
@@ -53,14 +53,7 @@ time_in_seconds () { (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } -# Try to pick a unique port: guess a large number, then hope -# no more than one of each test is running. -# -# This does not handle the case where somebody else is running the -# same tests and has chosen the same ports. -testid=${this_test#t} -git_p4_test_start=9800 -P4DPORT=$((10669 + ($testid - $git_p4_test_start))) +test_set_port P4DPORT P4PORT=localhost:$P4DPORT P4CLIENT=client
t/lib-git-svn.sh +1 -1
@@ -13,6 +13,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +test_set_port SVNSERVE_PORT svn >/dev/null 2>&1 if test $? -ne 1
@@ -119,7 +120,6 @@ require_svnserve () { } start_svnserve () { - SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} svnserve --listen-port $SVNSERVE_PORT \ --root "$rawsvnrepo" \ --listen-once \
t/lib-httpd.sh +1 -1
@@ -82,7 +82,7 @@ case $(uname) in esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}} +test_set_port LIB_HTTPD_PORT TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd
t/t0410-partial-clone.sh +0 -1
@@ -480,7 +480,6 @@ test_expect_success 'gc stops traversal when a missing but promised object is re ! grep "$TREE_HASH" out ' -LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
t/t5512-ls-remote.sh +1 -1
@@ -260,7 +260,7 @@ test_lazy_prereq GIT_DAEMON ' # This test spawns a daemon, so run it only if the user would be OK with # testing with git-daemon. test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' - JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + test_set_port JGIT_DAEMON_PORT && JGIT_DAEMON_PID= && git init --bare empty.git && >empty.git/git-daemon-export-ok &&
t/test-lib-functions.sh +36 -0
@@ -1263,3 +1263,39 @@ test_oid () { fi && eval "printf '%s' \"\${$var}\"" } + +# Choose a port number based on the test script's number and store it in +# the given variable name, unless that variable already contains a number. +test_set_port () { + local var=$1 port + + if test $# -ne 1 || test -z "$var" + then + BUG "test_set_port requires a variable name" + fi + + eval port=\$$var + case "$port" in + "") + # No port is set in the given env var, use the test + # number as port number instead. + # Remove not only the leading 't', but all leading zeros + # as well, so the arithmetic below won't (mis)interpret + # a test number like '0123' as an octal value. + port=${this_test#${this_test%%[1-9]*}} + if test "${port:-0}" -lt 1024 + then + # root-only port, use a larger one instead. + port=$(($port + 10000)) + fi + + eval $var=$port + ;; + *[^0-9]*|0*) + error >&7 "invalid port number: $port" + ;; + *) + # The user has specified the port. + ;; + esac +}
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v3 8/8 test-lib: add the '--stress' option to run a test repeatedly under load
In reply to SZEDER GáborUnfortunately, we have a few flaky tests, whose failures tend to be hard to reproduce. We've found that the best we can do to reproduce such a failure is to run the test script repeatedly while the machine is under load, and wait in the hope that the load creates enough variance in the timing of the test's commands that a failure is evenually triggered. I have a command to do that, and I noticed that two other contributors have rolled their own scripts to do the same, all choosing slightly different approaches.
To help reproduce failures in flaky tests, introduce the '--stress' option to run a test script repeatedly in multiple parallel jobs until one of them fails, thereby using the test script itself to increase the load on the machine.
The number of parallel jobs is determined by, in order of precedence:
the number specified as '--stress=<N>', or the value of the GIT_TEST_STRESS_LOAD environment variable, or twice the number of available processors (as reported by the 'getconf' utility), or 8.
Make '--stress' imply '--verbose -x --immediate' to get the most information about rare failures; there is really no point in spending all the extra effort to reproduce such a failure, and then not know which command failed and why.
To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
- Redirect each parallel test run's verbose output to 't/test-results/$TEST_NAME.stress-<nr>.out', because dumping the output of several parallel running tests to the terminal would create a big ugly mess.
For convenience, print the output of the failed test job at the end, and rename its trash directory to end with the '.stress-failed' suffix, so it's easy to find in a predictable path (OTOH, all absolute paths recorded in the trash directory become invalid; we'll see whether this causes any issues in practice). If, in an unlikely case, more than one jobs were to fail nearly at the same time, then print the output of all failed jobs, and rename the trash directory of only the last one (i.e. with the highest job number), as it is the trash directory of the test whose output will be at the bottom of the user's terminal.
Based on Jeff King's 'stress' script.
--- t/README | 19 ++++++- t/test-lib-functions.sh | 7 ++- t/test-lib.sh | 109 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 5 deletions(-)
t/README +18 -1
@@ -186,6 +186,22 @@ appropriately before running "make". this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. +--stress:: +--stress=<N>:: + Run the test script repeatedly in multiple parallel jobs until + one of them fails. Useful for reproducing rare failures in + flaky tests. The number of parallel jobs is, in order of + precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD + environment variable, or twice the number of available + processors (as shown by the 'getconf' utility), or 8. + Implies `--verbose -x --immediate` to get the most information + about the failure. Note that the verbose output of each test + job is saved to 't/test-results/$TEST_NAME.stress-<nr>.out', + and only the output of the failed test job is shown on the + terminal. The names of the trash directories get a + '.stress-<nr>' suffix, and the trash directory of the failed + test job is renamed to end with a '.stress-failed' suffix. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various
@@ -425,7 +441,8 @@ This test harness library does the following things: - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash directory.$test_name_without_dotsh', with t/ subject to change by - the --root option documented above. + the --root option documented above, and a '.stress-<N>' suffix + appended by the --stress option. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave
t/test-lib-functions.sh +5 -2
@@ -1288,8 +1288,6 @@ test_set_port () { # root-only port, use a larger one instead. port=$(($port + 10000)) fi - - eval $var=$port ;; *[^0-9]*|0*) error >&7 "invalid port number: $port"
@@ -1298,4 +1296,9 @@ test_set_port () { # The user has specified the port. ;; esac + + # Make sure that parallel '--stress' test jobs get different + # ports. + port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0})) + eval $var=$port }
t/test-lib.sh +107 -2
135 lines not on this page · raw message
Signature · 1 line
2.20.1.151.gec613c4b75
v4 cover test-lib: add the '--stress' option to help reproduce occasional failures in flaky tests
In reply to SZEDER GáborTo recap: this patch series tries to make reproducing rare failures in flaky tests easier: it adds the '--stress' option to our test library to run the test script repeatedly in multiple parallel jobs, in the hope that the increased load creates enough variance in the timing of the test's commands that such a failure is eventually triggered.
There is an issue with v3: by parsing all command line options before re-executing the test script for '--tee' or '--verbose-log', we not only parse the options twice (which is harmless), but check the shell's version for untraceable test scripts twice as well. Consequently, the warning about ignoring '-x' are issued twice:
$ ./t1510-repo-setup.sh -x --tee warning: ignoring -x; './t1510-repo-setup.sh' is untraceable without BASH_XTRACEFD warning: ignoring -x; './t1510-repo-setup.sh' is untraceable without BASH_XTRACEFD ok 1 - #0: nonbare repo, no explicit configuration <...>
Furthermore, when TEST_SHELL_PATH is not /bin/sh but is set to Bash v4.1 or later, then the above command issues one warning as it's run with /bin/sh from the shebang line, but then re-execs itself with Bash and does print the '-x' tracing output as well.
This version of the patch series fixes the above issue by checking the shell's version after '--tee' and '--verbose-log' re-execue the test script. To do so it makes more sense to move the 'test-lib: extract Bash version check for '-x' tracing' patch to the beginning of the series. This move, however, made the range-diff quite noisy, hence the interdiff below, as it describes the changes since v3 much better.
It's based on 'sg/test-bash-version-fix'.
SZEDER Gábor (8):
test-lib: translate SIGTERM and SIGHUP to an exit
test-lib: extract Bash version check for '-x' tracing
test-lib: parse options in a for loop to keep $@ intact
test-lib: parse command line options earlier
test-lib: consolidate naming of test-results paths
test-lib: set $TRASH_DIRECTORY earlier
test-lib-functions: introduce the 'test_set_port' helper function
test-lib: add the '--stress' option to run a test repeatedly under
load
t/README | 19 +- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 39 +++++ t/test-lib.sh | 365 ++++++++++++++++++++++++++------------- 9 files changed, 308 insertions(+), 133 deletions(-)
Interdiff:
t/test-lib.sh +28 -23
@@ -180,29 +180,6 @@ then immediate=t fi -if test -n "$trace" && test -n "$test_untraceable" -then - # '-x' tracing requested, but this test script can't be reliably - # traced, unless it is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). - if test -n "$BASH_VERSION" && eval ' - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - ' - then - : Executed by a Bash version supporting BASH_XTRACEFD. Good. - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - trace= - fi -fi -if test -n "$trace" && test -z "$verbose_log" -then - verbose=t -fi - TEST_STRESS_JOB_SFX="${GIT_TEST_STRESS_JOB_NR:+.stress-$GIT_TEST_STRESS_JOB_NR}" TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results"
@@ -322,6 +299,34 @@ then exit fi +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + # + # Perform this version check _after_ the test script was + # potentially re-executed with $TEST_SHELL_PATH for '--tee' or + # '--verbose-log', so the right shell is checked and the + # warning is issued only once. + if test -n "$BASH_VERSION" && eval ' + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + ' + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi +if test -n "$trace" && test -z "$verbose_log" +then + verbose=t +fi + # For repeatability, reset the environment to known value. # TERM is sanitized below, after saving color control sequences. LANG=C
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 1/8 test-lib: translate SIGTERM and SIGHUP to an exit
Right now if a test script receives SIGTERM or SIGHUP (e.g., because a test was hanging and the user 'kill'-ed it or simply closed the terminal window the test was running in), the shell exits immediately. This can be annoying if the test script did any global setup, like starting apache or git-daemon, as it will not have an opportunity to clean up after itself. A subsequent run of the test won't be able to start its own daemon, and will either fail or skip the tests.
Instead, let's trap SIGTERM and SIGHUP as well to make sure we do a clean shutdown, and just chain it to a normal exit (which will trigger any cleanup).
This patch follows suit of da706545f7 (t: translate SIGINT to an exit, 2015-03-13), and even stole its commit message as well.
--- t/test-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/test-lib.sh +1 -1
@@ -476,7 +476,7 @@ die () { GIT_EXIT_OK= trap 'die' EXIT -trap 'exit $?' INT +trap 'exit $?' INT TERM HUP # The user-facing functions are loaded from a separate file so that # test_perf subshells can have them too
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 2/8 test-lib: extract Bash version check for '-x' tracing
In reply to SZEDER GáborOne of our test scripts, 't1510-repo-setup.sh' [1], still can't be reliably run with '-x' tracing enabled, unless it's executed with a Bash version supporting BASH_XTRACEFD (since v4.1). We have a lengthy condition to check the version of the shell running the test script, and disable tracing if it's not executed with a suitable Bash version [2].
Move this check out from the option parsing loop, so other options can imply '-x' by setting 'trace=t', without missing this Bash version check.
[1] 5827506928 (t1510-repo-setup: mark as untraceable with '-x',
2018-02-24)
[2] 5fc98e79fc (t: add means to disable '-x' tracing for individual
test scripts, 2018-02-24)--- t/test-lib.sh | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-)
t/test-lib.sh +19 -18
@@ -317,24 +317,7 @@ do GIT_TEST_CHAIN_LINT=0 shift ;; -x) - # Some test scripts can't be reliably traced with '-x', - # unless the test is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). Check whether - # this test is marked as such, and ignore '-x' if it - # isn't executed with a suitable Bash version. - if test -z "$test_untraceable" || { - test -n "$BASH_VERSION" && eval ' - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - ' - } - then - trace=t - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - fi + trace=t shift ;; -V|--verbose-log) verbose_log=t
@@ -353,6 +336,24 @@ then test -z "$verbose_log" && verbose=t fi +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + if test -n "$BASH_VERSION" && eval ' + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + ' + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi if test -n "$trace" && test -z "$verbose_log" then verbose=t
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 3/8 test-lib: parse options in a for loop to keep $@ intact
In reply to SZEDER Gábor'test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error, and to do so it needs the original command line options the test was invoked with.
The next patch is about to move the option parsing loop earlier in 'test-lib.sh', but it is implemented using 'shift' in a while loop, effecively destroying "$@" by the end of the option parsing. Not good.
As a preparatory step, turn that option parsing loop into a 'for opt in "$@"' loop to preserve "$@" intact while iterating over the options, and taking extra care to handle the '-r' option's required argument (or the lack thereof).
--- t/test-lib.sh | 78 +++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 36 deletions(-)
t/test-lib.sh +42 -36
@@ -264,68 +264,74 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 +store_arg_to= +prev_opt= +for opt do - case "$1" in + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + + case "$opt" in -d|--d|--de|--deb|--debu|--debug) - debug=t; shift ;; + debug=t ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t; shift ;; + immediate=t ;; -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 ;; + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; -r) - shift; test "$#" -ne 0 || { - echo 'error: -r requires an argument' >&2; - exit 1; - } - run_list=$1; shift ;; + store_arg_to=run_list + ;; --run=*) - run_list=${1#--*=}; shift ;; + run_list=${opt#--*=} ;; -h|--h|--he|--hel|--help) - help=t; shift ;; + help=t ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t; shift ;; + verbose=t ;; --verbose-only=*) - verbose_only=${1#--*=} - shift ;; + verbose_only=${opt#--*=} + ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; + test -z "$HARNESS_ACTIVE" && quiet=t ;; --with-dashes) - with_dashes=t; shift ;; + with_dashes=t ;; --no-color) - color=; shift ;; + color= ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck - shift ;; + valgrind=memcheck ;; --valgrind=*) - valgrind=${1#--*=} - shift ;; + valgrind=${opt#--*=} ;; --valgrind-only=*) - valgrind_only=${1#--*=} - shift ;; + valgrind_only=${opt#--*=} ;; --tee) - shift ;; # was handled already + ;; # was handled already --root=*) - root=${1#--*=} - shift ;; + root=${opt#--*=} ;; --chain-lint) - GIT_TEST_CHAIN_LINT=1 - shift ;; + GIT_TEST_CHAIN_LINT=1 ;; --no-chain-lint) - GIT_TEST_CHAIN_LINT=0 - shift ;; + GIT_TEST_CHAIN_LINT=0 ;; -x) - trace=t - shift ;; + trace=t ;; -V|--verbose-log) - verbose_log=t - shift ;; + verbose_log=t ;; *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; + echo "error: unknown test option '$opt'" >&2; exit 1 ;; esac + + prev_opt=$opt done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi if test -n "$valgrind_only" then
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 4/8 test-lib: parse command line options earlier
In reply to SZEDER Gábor'test-lib.sh' looks for the presence of certain options like '--tee' and '--verbose-log', so it can execute the test script again to save its standard output and error. It looks for '--valgrind' as well, to set up some Valgrind-specific stuff. These all happen before the actual option parsing loop, and the conditions looking for these options look a bit odd, too. They are not completely correct, either, because in a bogus invocation like './t1234-foo.sh -r --tee' they recognize '--tee', although it should be handled as the required argument of the '-r' option. This patch series will add two more options to look out for early, and, in addition, will have to extract these options' stuck arguments (i.e. '--opt=arg') as well.
So let's move the option parsing loop and the couple of related conditions following it earlier in 'test-lib.sh', before the place where the test script is executed again for '--tee' and its friends.
--- t/test-lib.sh | 233 +++++++++++++++++++++++++++----------------------- 1 file changed, 124 insertions(+), 109 deletions(-)
t/test-lib.sh +124 -109
@@ -71,13 +71,102 @@ then exit 1 fi +# Parse options while taking care to leave $@ intact, so we will still +# have all the original command line options when executing the test +# script again for '--tee' and '--verbose-log' below. +store_arg_to= +prev_opt= +for opt +do + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + + case "$opt" in + -d|--d|--de|--deb|--debu|--debug) + debug=t ;; + -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) + immediate=t ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; + -r) + store_arg_to=run_list + ;; + --run=*) + run_list=${opt#--*=} ;; + -h|--h|--he|--hel|--help) + help=t ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + --verbose-only=*) + verbose_only=${opt#--*=} + ;; + -q|--q|--qu|--qui|--quie|--quiet) + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t ;; + --with-dashes) + with_dashes=t ;; + --no-color) + color= ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=memcheck + tee=t + ;; + --valgrind=*) + valgrind=${opt#--*=} + tee=t + ;; + --valgrind-only=*) + valgrind_only=${opt#--*=} + tee=t + ;; + --tee) + tee=t ;; + --root=*) + root=${opt#--*=} ;; + --chain-lint) + GIT_TEST_CHAIN_LINT=1 ;; + --no-chain-lint) + GIT_TEST_CHAIN_LINT=0 ;; + -x) + trace=t ;; + -V|--verbose-log) + verbose_log=t + tee=t + ;; + *) + echo "error: unknown test option '$opt'" >&2; exit 1 ;; + esac + + prev_opt=$opt +done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi + +if test -n "$valgrind_only" +then + test -z "$valgrind" && valgrind=memcheck + test -z "$verbose" && verbose_only="$valgrind_only" +elif test -n "$valgrind" +then + test -z "$verbose_log" && verbose=t +fi + # 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'*|*' -V '*|*' --verbose-log '*) +if test "$GIT_TEST_TEE_STARTED" = "done" +then + : # do not redirect again +elif test -n "$tee" +then mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
@@ -94,8 +183,35 @@ done,*) echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" test "$(cat "$BASE.exit")" = 0 exit - ;; -esac +fi + +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + # + # Perform this version check _after_ the test script was + # potentially re-executed with $TEST_SHELL_PATH for '--tee' or + # '--verbose-log', so the right shell is checked and the + # warning is issued only once. + if test -n "$BASH_VERSION" && eval ' + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + ' + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi +if test -n "$trace" && test -z "$verbose_log" +then + verbose=t +fi # For repeatability, reset the environment to known value. # TERM is sanitized below, after saving color control sequences.
@@ -193,7 +309,7 @@ fi # Add libc MALLOC and MALLOC_PERTURB test # only if we are not executing the test with valgrind -if expr " $GIT_TEST_OPTS " : ".* --valgrind " >/dev/null || +if test -n "$valgrind" || test -n "$TEST_NO_MALLOC_CHECK" then setup_malloc_check () {
@@ -264,107 +380,6 @@ test "x$TERM" != "xdumb" && ( ) && color=t -store_arg_to= -prev_opt= -for opt -do - if test -n "$store_arg_to" - then - eval $store_arg_to=\$opt - store_arg_to= - prev_opt= - continue - fi - - case "$opt" in - -d|--d|--de|--deb|--debu|--debug) - debug=t ;; - -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t ;; - -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) - GIT_TEST_LONG=t; export GIT_TEST_LONG ;; - -r) - store_arg_to=run_list - ;; - --run=*) - run_list=${opt#--*=} ;; - -h|--h|--he|--hel|--help) - help=t ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t ;; - --verbose-only=*) - verbose_only=${opt#--*=} - ;; - -q|--q|--qu|--qui|--quie|--quiet) - # Ignore --quiet under a TAP::Harness. Saying how many tests - # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t ;; - --with-dashes) - with_dashes=t ;; - --no-color) - color= ;; - --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck ;; - --valgrind=*) - valgrind=${opt#--*=} ;; - --valgrind-only=*) - valgrind_only=${opt#--*=} ;; - --tee) - ;; # was handled already - --root=*) - root=${opt#--*=} ;; - --chain-lint) - GIT_TEST_CHAIN_LINT=1 ;; - --no-chain-lint) - GIT_TEST_CHAIN_LINT=0 ;; - -x) - trace=t ;; - -V|--verbose-log) - verbose_log=t ;; - *) - echo "error: unknown test option '$opt'" >&2; exit 1 ;; - esac - - prev_opt=$opt -done -if test -n "$store_arg_to" -then - echo "error: $prev_opt requires an argument" >&2 - exit 1 -fi - -if test -n "$valgrind_only" -then - test -z "$valgrind" && valgrind=memcheck - test -z "$verbose" && verbose_only="$valgrind_only" -elif test -n "$valgrind" -then - test -z "$verbose_log" && verbose=t -fi - -if test -n "$trace" && test -n "$test_untraceable" -then - # '-x' tracing requested, but this test script can't be reliably - # traced, unless it is run with a Bash version supporting - # BASH_XTRACEFD (introduced in Bash v4.1). - if test -n "$BASH_VERSION" && eval ' - test ${BASH_VERSINFO[0]} -gt 4 || { - test ${BASH_VERSINFO[0]} -eq 4 && - test ${BASH_VERSINFO[1]} -ge 1 - } - ' - then - : Executed by a Bash version supporting BASH_XTRACEFD. Good. - else - echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" - trace= - fi -fi -if test -n "$trace" && test -z "$verbose_log" -then - verbose=t -fi - if test -n "$color" then # Save the color control sequences now rather than run tput
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 5/8 test-lib: consolidate naming of test-results paths
In reply to SZEDER GáborThere are two places where we strip off any leading path components and the '.sh' suffix from the test script's pathname, and there are four places where we construct the name of the 't/test-results' directory or the name of various test-specific files in there. The last patch in this series will add even more.
Factor these out into helper variables to avoid repeating ourselves.
--- t/test-lib.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
t/test-lib.sh +11 -11
@@ -160,6 +160,10 @@ then test -z "$verbose_log" && verbose=t fi +TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" + # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. if test "$GIT_TEST_TEE_STARTED" = "done"
@@ -167,12 +171,11 @@ then : # do not redirect again elif test -n "$tee" then - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" + mkdir -p "$TEST_RESULTS_DIR" # Make this filename available to the sub-process in case it is using # --verbose-log. - GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out export GIT_TEST_TEE_OUTPUT_FILE # Truncate before calling "tee -a" to get rid of the results
@@ -180,8 +183,8 @@ then >"$GIT_TEST_TEE_OUTPUT_FILE" (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 + echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit fi
@@ -840,12 +843,9 @@ test_done () { if test -z "$HARNESS_ACTIVE" then - test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}.counts" + mkdir -p "$TEST_RESULTS_DIR" - cat >"$test_results_path" <<-EOF + cat >"$TEST_RESULTS_BASE.counts" <<-EOF total $test_count success $test_success fixed $test_fixed
@@ -1051,7 +1051,7 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" +TRASH_DIRECTORY="trash directory.$TEST_NAME" test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" case "$TRASH_DIRECTORY" in /*) ;; # absolute path is good
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 6/8 test-lib: set $TRASH_DIRECTORY earlier
In reply to SZEDER GáborA later patch in this series will need to know the path to the trash directory early in 'test-lib.sh', but $TRASH_DIRECTORY is set much later.
Set $TRASH_DIRECTORY earlier, where the other test-specific path variables are set.
--- t/test-lib.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
t/test-lib.sh +6 -6
@@ -163,6 +163,12 @@ fi TEST_NAME="$(basename "$0" .sh)" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME" +TRASH_DIRECTORY="trash directory.$TEST_NAME" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too.
@@ -1051,12 +1057,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$TEST_NAME" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area"
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 7/8 test-lib-functions: introduce the 'test_set_port' helper function
In reply to SZEDER GáborSeveral test scripts run daemons like 'git-daemon' or Apache, and communicate with them through TCP sockets. To have unique ports where these daemons are accessible, the ports are usually the number of the corresponding test scripts, unless the user overrides them via environment variables, and thus all those tests and test libs contain more or less the same bit of one-liner boilerplate code to find out the port. The last patch in this series will make this a bit more complicated.
Factor out finding the port for a daemon into the common helper function 'test_set_port' to avoid repeating ourselves.
Take special care of test scripts with "low" numbers:
- Test numbers below 1024 would result in a port that's only usable as root, so set their port to '10000 + test-nr' to make sure it doesn't interfere with other tests in the test suite. This makes the hardcoded port number in 't0410-partial-clone.sh' unnecessary, remove it.
- The shell's arithmetic evaluation interprets numbers with leading zeros as octal values, which means that test number below 1000 and containing the digits 8 or 9 will trigger an error. Remove all leading zeros from the test numbers to prevent this.
Note that the 'git p4' tests are unlike the other tests involving daemons in that:
- 'lib-git-p4.sh' doesn't use the test's number for unique port as is, but does a bit of additional arithmetic on top [1].
- The port is not overridable via an environment variable.
With this patch even 'git p4' tests will use the test's number as default port, and it will be overridable via the P4DPORT environment variable.
[1] Commit fc00233071 (git-p4 tests: refactor and cleanup, 2011-08-22) introduced that "unusual" unique port computation without explaining why it was necessary (as opposed to simply using the test number as is). It seems to be just unnecessary complication, and in any case that commit came way before the "test nr as unique port" got "standardized" for other daemons in commits c44132fcf3 (tests: auto-set git-daemon port, 2014-02-10), 3bb486e439 (tests:
auto-set LIB_HTTPD_PORT from test name, 2014-02-10), and bf9d7df950 (t/lib-git-svn.sh: improve svnserve tests with parallel make test, 2017-12-01).
--- t/lib-git-daemon.sh | 2 +- t/lib-git-p4.sh | 9 +-------- t/lib-git-svn.sh | 2 +- t/lib-httpd.sh | 2 +- t/t0410-partial-clone.sh | 1 - t/t5512-ls-remote.sh | 2 +- t/test-lib-functions.sh | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 41 insertions(+), 13 deletions(-)
t/lib-git-daemon.sh +1 -1
@@ -28,7 +28,7 @@ then test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" fi -LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} +test_set_port LIB_GIT_DAEMON_PORT GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo
t/lib-git-p4.sh +1 -8
@@ -53,14 +53,7 @@ time_in_seconds () { (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } -# Try to pick a unique port: guess a large number, then hope -# no more than one of each test is running. -# -# This does not handle the case where somebody else is running the -# same tests and has chosen the same ports. -testid=${this_test#t} -git_p4_test_start=9800 -P4DPORT=$((10669 + ($testid - $git_p4_test_start))) +test_set_port P4DPORT P4PORT=localhost:$P4DPORT P4CLIENT=client
t/lib-git-svn.sh +1 -1
@@ -13,6 +13,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +test_set_port SVNSERVE_PORT svn >/dev/null 2>&1 if test $? -ne 1
@@ -119,7 +120,6 @@ require_svnserve () { } start_svnserve () { - SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} svnserve --listen-port $SVNSERVE_PORT \ --root "$rawsvnrepo" \ --listen-once \
t/lib-httpd.sh +1 -1
@@ -82,7 +82,7 @@ case $(uname) in esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}} +test_set_port LIB_HTTPD_PORT TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd
t/t0410-partial-clone.sh +0 -1
@@ -480,7 +480,6 @@ test_expect_success 'gc stops traversal when a missing but promised object is re ! grep "$TREE_HASH" out ' -LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
t/t5512-ls-remote.sh +1 -1
@@ -260,7 +260,7 @@ test_lazy_prereq GIT_DAEMON ' # This test spawns a daemon, so run it only if the user would be OK with # testing with git-daemon. test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' - JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + test_set_port JGIT_DAEMON_PORT && JGIT_DAEMON_PID= && git init --bare empty.git && >empty.git/git-daemon-export-ok &&
t/test-lib-functions.sh +36 -0
@@ -1263,3 +1263,39 @@ test_oid () { fi && eval "printf '%s' \"\${$var}\"" } + +# Choose a port number based on the test script's number and store it in +# the given variable name, unless that variable already contains a number. +test_set_port () { + local var=$1 port + + if test $# -ne 1 || test -z "$var" + then + BUG "test_set_port requires a variable name" + fi + + eval port=\$$var + case "$port" in + "") + # No port is set in the given env var, use the test + # number as port number instead. + # Remove not only the leading 't', but all leading zeros + # as well, so the arithmetic below won't (mis)interpret + # a test number like '0123' as an octal value. + port=${this_test#${this_test%%[1-9]*}} + if test "${port:-0}" -lt 1024 + then + # root-only port, use a larger one instead. + port=$(($port + 10000)) + fi + + eval $var=$port + ;; + *[^0-9]*|0*) + error >&7 "invalid port number: $port" + ;; + *) + # The user has specified the port. + ;; + esac +}
Signature · 1 line
2.20.1.151.gec613c4b75
PATCH v4 8/8 test-lib: add the '--stress' option to run a test repeatedly under load
In reply to SZEDER GáborUnfortunately, we have a few flaky tests, whose failures tend to be hard to reproduce. We've found that the best we can do to reproduce such a failure is to run the test script repeatedly while the machine is under load, and wait in the hope that the load creates enough variance in the timing of the test's commands that a failure is evenually triggered. I have a command to do that, and I noticed that two other contributors have rolled their own scripts to do the same, all choosing slightly different approaches.
To help reproduce failures in flaky tests, introduce the '--stress' option to run a test script repeatedly in multiple parallel jobs until one of them fails, thereby using the test script itself to increase the load on the machine.
The number of parallel jobs is determined by, in order of precedence:
the number specified as '--stress=<N>', or the value of the GIT_TEST_STRESS_LOAD environment variable, or twice the number of available processors (as reported by the 'getconf' utility), or 8.
Make '--stress' imply '--verbose -x --immediate' to get the most information about rare failures; there is really no point in spending all the extra effort to reproduce such a failure, and then not know which command failed and why.
To prevent the several parallel invocations of the same test from interfering with each other:
- Include the parallel job's number in the name of the trash directory and the various output files under 't/test-results/' as a '.stress-<Nr>' suffix.
- Add the parallel job's number to the port number specified by the user or to the test number, so even tests involving daemons listening on a TCP socket can be stressed.
- Redirect each parallel test run's verbose output to 't/test-results/$TEST_NAME.stress-<nr>.out', because dumping the output of several parallel running tests to the terminal would create a big ugly mess.
For convenience, print the output of the failed test job at the end, and rename its trash directory to end with the '.stress-failed' suffix, so it's easy to find in a predictable path (OTOH, all absolute paths recorded in the trash directory become invalid; we'll see whether this causes any issues in practice). If, in an unlikely case, more than one jobs were to fail nearly at the same time, then print the output of all failed jobs, and rename the trash directory of only the last one (i.e. with the highest job number), as it is the trash directory of the test whose output will be at the bottom of the user's terminal.
Based on Jeff King's 'stress' script.
--- t/README | 19 ++++++- t/test-lib-functions.sh | 7 ++- t/test-lib.sh | 109 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 5 deletions(-)
t/README +18 -1
@@ -186,6 +186,22 @@ appropriately before running "make". this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. +--stress:: +--stress=<N>:: + Run the test script repeatedly in multiple parallel jobs until + one of them fails. Useful for reproducing rare failures in + flaky tests. The number of parallel jobs is, in order of + precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD + environment variable, or twice the number of available + processors (as shown by the 'getconf' utility), or 8. + Implies `--verbose -x --immediate` to get the most information + about the failure. Note that the verbose output of each test + job is saved to 't/test-results/$TEST_NAME.stress-<nr>.out', + and only the output of the failed test job is shown on the + terminal. The names of the trash directories get a + '.stress-<nr>' suffix, and the trash directory of the failed + test job is renamed to end with a '.stress-failed' suffix. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various
@@ -425,7 +441,8 @@ This test harness library does the following things: - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash directory.$test_name_without_dotsh', with t/ subject to change by - the --root option documented above. + the --root option documented above, and a '.stress-<N>' suffix + appended by the --stress option. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave
t/test-lib-functions.sh +5 -2
@@ -1288,8 +1288,6 @@ test_set_port () { # root-only port, use a larger one instead. port=$(($port + 10000)) fi - - eval $var=$port ;; *[^0-9]*|0*) error >&7 "invalid port number: $port"
@@ -1298,4 +1296,9 @@ test_set_port () { # The user has specified the port. ;; esac + + # Make sure that parallel '--stress' test jobs get different + # ports. + port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0})) + eval $var=$port }
t/test-lib.sh +107 -2
129 lines not on this page · raw message
Signature · 1 line
2.20.1.151.gec613c4b75
Quoted 5 lines · SZEDER Gábor
To recap: this patch series tries to make reproducing rare failures in flaky tests easier: it adds the '--stress' option to our test library to run the test script repeatedly in multiple parallel jobs, in the hope that the increased load creates enough variance in the timing of the test's commands that such a failure is eventually triggered.
Thanks, I looked over this version (glossing over the bits that hadn't really changed since v2) and it all looks good to me.
-Peff