test-lib: fix non-portable pattern bracket expressions
[gitweb.git] / t / test-lib.sh
index 6c6c0af7a128ea7f35fb1753ed39c073208976ea..4e7cb52b57a59b76404f701a542fc58ce06ec685 100644 (file)
@@ -71,19 +71,236 @@ 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
+               ;;
+       --stress)
+               stress=t ;;
+       --stress=*)
+               stress=${opt#--*=}
+               case "$stress" in
+               *[!0-9]*|0*|"")
+                       echo "error: --stress=<N> requires the number of jobs to run" >&2
+                       exit 1
+                       ;;
+               *)      # Good.
+                       ;;
+               esac
+               ;;
+       --stress-limit=*)
+               stress_limit=${opt#--*=}
+               case "$stress_limit" in
+               *[!0-9]*|0*|"")
+                       echo "error: --stress-limit=<N> requires the number of repetitions" >&2
+                       exit 1
+                       ;;
+               *)      # Good.
+                       ;;
+               esac
+               ;;
+       *)
+               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 "$stress"
+then
+       verbose=t
+       trace=t
+       immediate=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"
+TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX"
+TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX"
+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 --stress was passed, run this test repeatedly in several parallel loops.
+if test "$GIT_TEST_STRESS_STARTED" = "done"
+then
+       : # Don't stress test again.
+elif test -n "$stress"
+then
+       if test "$stress" != t
+       then
+               job_count=$stress
+       elif test -n "$GIT_TEST_STRESS_LOAD"
+       then
+               job_count="$GIT_TEST_STRESS_LOAD"
+       elif job_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null) &&
+            test -n "$job_count"
+       then
+               job_count=$((2 * $job_count))
+       else
+               job_count=8
+       fi
+
+       mkdir -p "$TEST_RESULTS_DIR"
+       stressfail="$TEST_RESULTS_BASE.stress-failed"
+       rm -f "$stressfail"
+
+       stress_exit=0
+       trap '
+               kill $job_pids 2>/dev/null
+               wait
+               stress_exit=1
+       ' TERM INT HUP
+
+       job_pids=
+       job_nr=0
+       while test $job_nr -lt "$job_count"
+       do
+               (
+                       GIT_TEST_STRESS_STARTED=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
+                               wait
+                               exit 1
+                       ' TERM INT
+
+                       cnt=1
+                       while ! test -e "$stressfail" &&
+                             { test -z "$stress_limit" ||
+                               test $cnt -le $stress_limit ; }
+                       do
+                               $TEST_SHELL_PATH "$0" "$@" >"$TEST_RESULTS_BASE.stress-$job_nr.out" 2>&1 &
+                               test_pid=$!
+
+                               if wait $test_pid
+                               then
+                                       printf "OK   %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt
+                               else
+                                       echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail"
+                                       printf "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt
+                               fi
+                               cnt=$(($cnt + 1))
+                       done
+               ) &
+               job_pids="$job_pids $!"
+               job_nr=$(($job_nr + 1))
+       done
+
+       wait
+
+       if test -f "$stressfail"
+       then
+               stress_exit=1
+               echo "Log(s) of failed test run(s):"
+               for failed_job_nr in $(sort -n "$stressfail")
+               do
+                       echo "Contents of '$TEST_RESULTS_BASE.stress-$failed_job_nr.out':"
+                       cat "$TEST_RESULTS_BASE.stress-$failed_job_nr.out"
+               done
+               rm -rf "$TRASH_DIRECTORY.stress-failed"
+               # Move the last one.
+               mv "$TRASH_DIRECTORY.stress-$failed_job_nr" "$TRASH_DIRECTORY.stress-failed"
+       fi
+
+       exit $stress_exit
+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 '*)
-       mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results"
-       BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)"
+if test "$GIT_TEST_TEE_STARTED" = "done"
+then
+       : # do not redirect again
+elif test -n "$tee"
+then
+       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
@@ -91,11 +308,38 @@ 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
+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 +437,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,100 +508,6 @@ test "x$TERM" != "xdumb" && (
        ) &&
        color=t
 
-while test "$#" -ne 0
-do
-       case "$1" in
-       -d|--d|--de|--deb|--debu|--debug)
-               debug=t; shift ;;
-       -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
-               immediate=t; shift ;;
-       -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
-               GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;;
-       -r)
-               shift; test "$#" -ne 0 || {
-                       echo 'error: -r requires an argument' >&2;
-                       exit 1;
-               }
-               run_list=$1; shift ;;
-       --run=*)
-               run_list=${1#--*=}; shift ;;
-       -h|--h|--he|--hel|--help)
-               help=t; shift ;;
-       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
-               verbose=t; shift ;;
-       --verbose-only=*)
-               verbose_only=${1#--*=}
-               shift ;;
-       -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 ;;
-       --with-dashes)
-               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 ;;
-       --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
-               # 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
-               shift ;;
-       -V|--verbose-log)
-               verbose_log=t
-               shift ;;
-       *)
-               echo "error: unknown test option '$1'" >&2; exit 1 ;;
-       esac
-done
-
-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
@@ -402,6 +552,10 @@ error () {
        exit 1
 }
 
+BUG () {
+       error >&7 "bug in the test script: $*"
+}
+
 say () {
        say_color info "$*"
 }
@@ -472,7 +626,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
@@ -729,7 +883,7 @@ test_run_ () {
                if $(printf '%s\n' "$1" | sed -f "$GIT_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!') ||
                        test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)"
                then
-                       error "bug in the test script: broken &&-chain or run-away HERE-DOC: $1"
+                       BUG "broken &&-chain or run-away HERE-DOC: $1"
                fi
                trace=$trace_tmp
        fi
@@ -814,12 +968,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
@@ -1025,12 +1176,6 @@ then
 fi
 
 # Test repository
-TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)"
-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"
@@ -1231,7 +1376,7 @@ test_lazy_prereq SANITY '
        chmod -w SANETESTD.1 &&
        chmod -r SANETESTD.1/x &&
        chmod -rx SANETESTD.2 ||
-       error "bug in test sript: cannot prepare SANETESTD"
+       BUG "cannot prepare SANETESTD"
 
        ! test -r SANETESTD.1/x &&
        ! rm SANETESTD.1/x && ! test -f SANETESTD.2/x
@@ -1239,7 +1384,7 @@ test_lazy_prereq SANITY '
 
        chmod +rwx SANETESTD.1 SANETESTD.2 &&
        rm -rf SANETESTD.1 SANETESTD.2 ||
-       error "bug in test sript: cannot clean SANETESTD"
+       BUG "cannot clean SANETESTD"
        return $status
 '