From a1c65da4f22313dfcf2c7698a58f1136cf8bc78a Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Thu, 15 Dec 2016 00:21:10 -0800 Subject: [PATCH] testlib: make test-lib-main.sh pure functions Make the code in test-lib-main.sh exclusively function definitions so that it can be safely re-read at any time without causing any harm. This also guarantees that any state it uses may be saved and restored indpendently of defining the functions themselves. Signed-off-by: Kyle J. McKay --- t/test-lib-main.sh | 1210 +++++++++++++++++++++++++++------------------------- t/test-lib.sh | 1 + 2 files changed, 620 insertions(+), 591 deletions(-) diff --git a/t/test-lib-main.sh b/t/test-lib-main.sh index ddc137f..57b6285 100644 --- a/t/test-lib-main.sh +++ b/t/test-lib-main.sh @@ -6,6 +6,11 @@ # * Many "GIT_..." variables removed -- some were kept as TESTLIB_..." instead # (Except "GIT_PATH" is new and is the full path to a "git" executable) # +# * IMPORTANT: test-lib-main.sh SHOULD NOT EXECUTE ANY CODE! A new +# function "test_lib_main_init" has been added that will be called +# and MUST contain any lines of code to be executed. This will ALWAYS +# be the LAST function defined in this file for easy locatability. +# # * Added cmd_path, fatal, whats_my_dir, vcmp, test_possibly_broken_ok_ and # test_possibly_broken_failure_ functions # @@ -28,6 +33,12 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ . +# +## IMPORTANT: THIS FILE MUST NOT CONTAIN ANYTHING OTHER THAN FUNCTION +## DEFINITION!!! INITIALIZATION GOES IN THE LAST FUNCTION +## DEFINED IN THIS FILE "test_lib_main_init" AS REQUIRED! +# + cmd_path() ( { "unset" -f command unset unalias "$1"; } >/dev/null 2>&1 || : { "unalias" -a; } >/dev/null 2>&1 || : @@ -81,458 +92,141 @@ vcmp() ( echo 0 ) -! [ -f ../TG-BUILD-SETTINGS ] || . ../TG-BUILD-SETTINGS -! [ -f TG-TEST-SETTINGS ] || . ./TG-TEST-SETTINGS +error() { + say_color error "error: $*" + TESTLIB_EXIT_OK=t + exit 1 +} -: "${SHELL_PATH:=/bin/sh}" -: "${DIFF:=diff}" -: "${GIT_PATH:=$(cmd_path git)}" -: "${PERL_PATH:=$(cmd_path perl || :)}" -TESTLIB_DIRECTORY="$(whats_my_dir)" +say() { + say_color info "$*" +} -# Test the binaries we have just built. The tests are kept in -# t/ subdirectory and are run in 'trash directory' subdirectory. -if test -z "$TEST_DIRECTORY" -then - # We allow tests to override this, in case they want to run tests - # outside of t/, e.g. for running tests on the test library - # itself. - TEST_DIRECTORY="$TESTLIB_DIRECTORY" -else - # ensure that TEST_DIRECTORY is an absolute path so that it - # is valid even if the current working directory is changed - TEST_DIRECTORY="$(cd "$TEST_DIRECTORY" && pwd)" || exit 1 -fi -if test -z "$TEST_OUTPUT_DIRECTORY" -then - # Similarly, override this to store the test-results subdir - # elsewhere - TEST_OUTPUT_DIRECTORY="$TEST_DIRECTORY" -fi -[ -d "$TESTLIB_DIRECTORY"/empty ] || { - mkdir "$TESTLIB_DIRECTORY/empty" - chmod a-w "$TESTLIB_DIRECTORY/empty" +die() { + code=$? + if test -n "$TESTLIB_EXIT_OK" + then + exit $code + else + echo >&5 "FATAL: Unexpected exit with code $code" + exit 1 + fi } -EMPTY_DIRECTORY="$TESTLIB_DIRECTORY/empty" +# You are not expected to call test_ok_ and test_failure_ directly, use +# the test_expect_* functions instead. -################################################################ -# It appears that people try to run tests with missing perl or git... -git_version="$("$GIT_PATH" --version 2>&1)" || - fatal 'error: you do not seem to have git available?' -case "$git_version" in [Gg][Ii][Tt]\ [Vv][Ee][Rr][Ss][Ii][Oo][Nn]\ [0-9]*) :;; *) - fatal "error: git --version returned bogus value: $git_version" -esac -#"$PERL_PATH" --version >/dev/null 2>&1 || -# fatal 'error: you do not seem to have perl available?' +test_ok_() { + test_success=$(($test_success + 1)) + say_color "" "ok $test_count - $@" +} -# if --tee was passed, write the output not only to the terminal, but -# additionally to the file test-results/$BASENAME.out, too. -case "$TESTLIB_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --verbose-log '*) - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" +test_failure_() { + test_failure=$(($test_failure + 1)) + say_color error "not ok $test_count - $1" + shift + printf '%s\n' "$*" | sed -e 's/^/# /' + test "$immediate" = "" || { TESTLIB_EXIT_OK=t; exit 1; } +} - # Make this filename available to the sub-process in case it is using - # --verbose-log. - TESTLIB_TEST_TEE_OUTPUT_FILE=$BASE.out - export TESTLIB_TEST_TEE_OUTPUT_FILE +test_known_broken_ok_() { + test_fixed=$(($test_fixed + 1)) + say_color error "ok $test_count - $@ # TODO known breakage vanished" +} - # Truncate before calling "tee -a" to get rid of the results - # from any previous runs. - >"$TESTLIB_TEST_TEE_OUTPUT_FILE" +test_known_broken_failure_() { + test_broken=$(($test_broken + 1)) + say_color warn "not ok $test_count - $@ # TODO known breakage" +} - (TESTLIB_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$TESTLIB_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 - exit - ;; -esac +test_possibly_broken_ok_() { + test_success=$(($test_success + 1)) + say_color "" "ok $test_count - $@" +} -# For repeatability, reset the environment to known value. -# TERM is sanitized below, after saving color control sequences. -LANG=C -LC_ALL=C -PAGER=cat -TZ=UTC -export LANG LC_ALL PAGER TZ -EDITOR=: -# A call to "unset" with no arguments causes at least Solaris 10 -# /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets -# deriving from the command substitution clustered with the other -# ones. -unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' - my @env = keys %ENV; - my $ok = join("|", qw( - TRACE - DEBUG - USE_LOOKUP - TEST - .*_TEST - MINIMUM_VERSION - PATH - PROVE - UNZIP - PERF_ - CURL_VERBOSE - TRACE_CURL - )); - my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); - print join("\n", @vars); -') -unset XDG_CONFIG_HOME -unset GITPERLLIB -GIT_AUTHOR_NAME='Te s t' -GIT_AUTHOR_EMAIL=test@example.net -GIT_COMMITTER_NAME='Fra mewor k' -GIT_COMMITTER_EMAIL=framework@example.org -GIT_MERGE_VERBOSITY=5 -GIT_MERGE_AUTOEDIT=no -GIT_TEMPLATE_DIR="$EMPTY_DIRECTORY" -GIT_CONFIG_NOSYSTEM=1 -GIT_ATTR_NOSYSTEM=1 -export PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM -export GIT_MERGE_VERBOSITY GIT_MERGE_AUTOEDIT -export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME -export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME -export EDITOR +test_possibly_broken_failure_() { + test_broken=$(($test_broken + 1)) + say_color warn "not ok $test_count - $@ # TODO tolerated breakage" +} -# Tests using GIT_TRACE typically don't want : output -GIT_TRACE_BARE=1 -export GIT_TRACE_BARE +test_debug() { + test "$debug" = "" || test $# -eq 0 || test -z "$*" || "$@" +} -# Protect ourselves from common misconfiguration to export -# CDPATH into the environment -unset CDPATH +match_pattern_list() { + arg="$1" + shift + test -z "$*" && return 1 + for pattern_ + do + case "$arg" in + $pattern_) + return 0 + esac + done + return 1 +} -unset GREP_OPTIONS -unset UNZIP +match_test_selector_list() { + title="$1" + shift + arg="$1" + shift + test -z "$1" && return 0 -case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in -1|2|true) - GIT_TRACE=4 - ;; -esac + # Both commas and whitespace are accepted as separators. + OLDIFS=$IFS + IFS=' ,' + set -- $1 + IFS=$OLDIFS -# Convenience -# -# A regexp to match 5 and 40 hexdigits -_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" + # If the first selector is negative we include by default. + include= + case "$1" in + !*) include=t ;; + esac -# Zero SHA-1 -_z40=0000000000000000000000000000000000000000 + for selector + do + orig_selector=$selector -EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 + positive=t + case "$selector" in + !*) + positive= + selector=${selector##?} + ;; + esac -# Line feed -LF=' -' + test -z "$selector" && continue -# UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores -# when case-folding filenames -u200c=$(printf '\342\200\214') + case "$selector" in + *-*) + if expr "z${selector%%-*}" : "z[0-9]*[^0-9]" >/dev/null + then + echo "error: $title: invalid non-numeric in range" \ + "start: '$orig_selector'" >&2 + exit 1 + fi + if expr "z${selector#*-}" : "z[0-9]*[^0-9]" >/dev/null + then + echo "error: $title: invalid non-numeric in range" \ + "end: '$orig_selector'" >&2 + exit 1 + fi + ;; + *) + if expr "z$selector" : "z[0-9]*[^0-9]" >/dev/null + then + echo "error: $title: invalid non-numeric in test" \ + "selector: '$orig_selector'" >&2 + exit 1 + fi + esac -export _x05 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB - -test "x$TERM" != "xdumb" && ( - test -t 1 && - tput bold >/dev/null 2>&1 && - tput setaf 1 >/dev/null 2>&1 && - tput sgr0 >/dev/null 2>&1 - ) && - 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) - TESTLIB_TEST_LONG=t; export TESTLIB_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 ;; - --no-color) - color=; shift ;; - --tee) - shift ;; # was handled already - --root=*) - root=${1#--*=} - shift ;; - --chain-lint) - TESTLIB_TEST_CHAIN_LINT=1 - shift ;; - --no-chain-lint) - TESTLIB_TEST_CHAIN_LINT=0 - shift ;; - -x) - trace=t - verbose=t - shift ;; - --verbose-log) - verbose_log=t - shift ;; - *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; - esac -done - -if test -n "$color" -then - # Save the color control sequences now rather than run tput - # each time say_color() is called. This is done for two - # reasons: - # * TERM will be changed to dumb - # * HOME will be changed to a temporary directory and tput - # might need to read ~/.terminfo from the original HOME - # directory to get the control sequences - # Note: This approach assumes the control sequences don't end - # in a newline for any terminal of interest (command - # substitutions strip trailing newlines). Given that most - # (all?) terminals in common use are related to ECMA-48, this - # shouldn't be a problem. - say_color_error=$(tput bold; tput setaf 1) # bold red - say_color_skip=$(tput setaf 4) # blue - say_color_warn=$(tput setaf 3) # brown/yellow - say_color_pass=$(tput setaf 2) # green - say_color_info=$(tput setaf 6) # cyan - say_color_reset=$(tput sgr0) - say_color_="" # no formatting for normal text - say_color() { - test -z "$1" && test -n "$quiet" && return - eval "say_color_color=\$say_color_$1" - shift - printf "%s\\n" "$say_color_color$*$say_color_reset" - } -else - say_color() { - test -z "$1" && test -n "$quiet" && return - shift - printf "%s\n" "$*" - } -fi - -TERM=dumb -export TERM - -error() { - say_color error "error: $*" - TESTLIB_EXIT_OK=t - exit 1 -} - -say() { - say_color info "$*" -} - -if test -n "$HARNESS_ACTIVE" -then - if test "$verbose" = t || test -n "$verbose_only" - then - printf 'Bail out! %s\n' \ - 'verbose mode forbidden under TAP harness; try --verbose-log' - exit 1 - fi -fi - -test "${test_description}" != "" || -error "Test script did not set test_description." - -if test "$help" = "t" -then - printf '%s\n' "$test_description" - exit 0 -fi - -exec 5>&1 -exec 6<&0 -if test "$verbose_log" = "t" -then - exec 3>>"$TESTLIB_TEST_TEE_OUTPUT_FILE" 4>&3 -elif test "$verbose" = "t" -then - exec 4>&2 3>&1 -else - exec 4>/dev/null 3>/dev/null -fi - -# Send any "-x" output directly to stderr to avoid polluting tests -# which capture stderr. We can do this unconditionally since it -# has no effect if tracing isn't turned on. -# -# Note that this sets up the trace fd as soon as we assign the variable, so it -# must come after the creation of descriptor 4 above. Likewise, we must never -# unset this, as it has the side effect of closing descriptor 4, which we -# use to show verbose tests to the user. -# -# Note also that we don't need or want to export it. The tracing is local to -# this shell, and we would not want to influence any shells we exec. -BASH_XTRACEFD=4 - -test_failure=0 -test_count=0 -test_fixed=0 -test_broken=0 -test_success=0 - -test_external_has_tap=0 - -die() { - code=$? - if test -n "$TESTLIB_EXIT_OK" - then - exit $code - else - echo >&5 "FATAL: Unexpected exit with code $code" - exit 1 - fi -} - -TESTLIB_EXIT_OK= -trap 'die' EXIT -trap 'exit $?' HUP INT QUIT ABRT PIPE TERM - -# The user-facing functions are loaded from a separate file -. "$TEST_DIRECTORY/test-lib-functions.sh" -test_lib_functions_init - -# You are not expected to call test_ok_ and test_failure_ directly, use -# the test_expect_* functions instead. - -test_ok_() { - test_success=$(($test_success + 1)) - say_color "" "ok $test_count - $@" -} - -test_failure_() { - test_failure=$(($test_failure + 1)) - say_color error "not ok $test_count - $1" - shift - printf '%s\n' "$*" | sed -e 's/^/# /' - test "$immediate" = "" || { TESTLIB_EXIT_OK=t; exit 1; } -} - -test_known_broken_ok_() { - test_fixed=$(($test_fixed + 1)) - say_color error "ok $test_count - $@ # TODO known breakage vanished" -} - -test_known_broken_failure_() { - test_broken=$(($test_broken + 1)) - say_color warn "not ok $test_count - $@ # TODO known breakage" -} - -test_possibly_broken_ok_() { - test_success=$(($test_success + 1)) - say_color "" "ok $test_count - $@" -} - -test_possibly_broken_failure_() { - test_broken=$(($test_broken + 1)) - say_color warn "not ok $test_count - $@ # TODO tolerated breakage" -} - -test_debug() { - test "$debug" = "" || test $# -eq 0 || test -z "$*" || "$@" -} - -match_pattern_list() { - arg="$1" - shift - test -z "$*" && return 1 - for pattern_ - do - case "$arg" in - $pattern_) - return 0 - esac - done - return 1 -} - -match_test_selector_list() { - title="$1" - shift - arg="$1" - shift - test -z "$1" && return 0 - - # Both commas and whitespace are accepted as separators. - OLDIFS=$IFS - IFS=' ,' - set -- $1 - IFS=$OLDIFS - - # If the first selector is negative we include by default. - include= - case "$1" in - !*) include=t ;; - esac - - for selector - do - orig_selector=$selector - - positive=t - case "$selector" in - !*) - positive= - selector=${selector##?} - ;; - esac - - test -z "$selector" && continue - - case "$selector" in - *-*) - if expr "z${selector%%-*}" : "z[0-9]*[^0-9]" >/dev/null - then - echo "error: $title: invalid non-numeric in range" \ - "start: '$orig_selector'" >&2 - exit 1 - fi - if expr "z${selector#*-}" : "z[0-9]*[^0-9]" >/dev/null - then - echo "error: $title: invalid non-numeric in range" \ - "end: '$orig_selector'" >&2 - exit 1 - fi - ;; - *) - if expr "z$selector" : "z[0-9]*[^0-9]" >/dev/null - then - echo "error: $title: invalid non-numeric in test" \ - "selector: '$orig_selector'" >&2 - exit 1 - fi - esac - - # Short cut for "obvious" cases - test -z "$include" && test -z "$positive" && continue - test -n "$include" && test -n "$positive" && continue + # Short cut for "obvious" cases + test -z "$include" && test -z "$positive" && continue + test -n "$include" && test -n "$positive" && continue case "$selector" in -*) @@ -572,7 +266,6 @@ maybe_teardown_verbose() { verbose= } -last_verbose=t maybe_setup_verbose() { test -z "$verbose_only" && return if match_pattern_list $test_count $verbose_only @@ -621,176 +314,528 @@ test_eval_() { test_eval_ret_=$? if want_trace then - set +x - if test "$test_eval_ret_" != 0 + set +x + if test "$test_eval_ret_" != 0 + then + say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" + fi + fi + } 2>/dev/null + return $test_eval_ret_ +} + +test_run_() { + test_cleanup=: + expecting_failure=$2 + + if test "${TESTLIB_TEST_CHAIN_LINT:-1}" != 0; then + # turn off tracing for this test-eval, as it simply creates + # confusing noise in the "-x" output + trace_tmp=$trace + trace= + # 117 is magic because it is unlikely to match the exit + # code of other programs + test_eval_ "(exit 117) && $1" + if test "$?" != 117; then + error "bug in the test script: broken &&-chain: $1" + fi + trace=$trace_tmp + fi + + test_eval_ "$1" + eval_ret=$? + + if test -z "$immediate" || test $eval_ret = 0 || + test -n "$expecting_failure" && test "${test_cleanup:-:}" != ":" + then + test_eval_ "$test_cleanup" + fi + if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE" + then + echo "" + fi + return "$eval_ret" +} + +test_start_() { + test_count=$(($test_count+1)) + maybe_setup_verbose +} + +test_finish_() { + echo >&3 "" + maybe_teardown_verbose +} + +test_skip() { + to_skip= + skipped_reason= + if match_pattern_list $this_test.$test_count $TESTLIB_SKIP_TESTS + then + to_skip=t + skipped_reason="TESTLIB_SKIP_TESTS" + fi + if test -z "$to_skip" && test -n "$test_prereq" && + ! test_have_prereq "$test_prereq" + then + to_skip=t + + of_prereq= + if test "$missing_prereq" != "$test_prereq" + then + of_prereq=" of $test_prereq" + fi + skipped_reason="missing $missing_prereq${of_prereq}" + fi + if test -z "$to_skip" && test -n "$run_list" && + ! match_test_selector_list '--run' $test_count "$run_list" + then + to_skip=t + skipped_reason="--run" + fi + + case "$to_skip" in + t) + say_color skip >&3 "skipping test: $@" + say_color skip "ok $test_count # skip $1 ($skipped_reason)" + : true + ;; + *) + false + ;; + esac +} + +# stub; runs at end of each successful test +test_at_end_hook_() { + : +} + +test_done() { + TESTLIB_EXIT_OK=t + + 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" + + cat >"$test_results_path" <<-EOF + total $test_count + success $test_success + fixed $test_fixed + broken $test_broken + failed $test_failure + + EOF + fi + + if test "$test_fixed" != 0 + then + say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" + fi + if test "$test_broken" != 0 + then + say_color warn "# still have $test_broken known breakage(s)" + fi + if test "$test_broken" != 0 || test "$test_fixed" != 0 + then + test_remaining=$(( $test_count - $test_broken - $test_fixed )) + msg="remaining $test_remaining test(s)" + else + test_remaining=$test_count + msg="$test_count test(s)" + fi + case "$test_failure" in + 0) + # Maybe print SKIP message + if test -n "$skip_all" && test $test_count -gt 0 + then + error "Can't use skip_all after running some tests" + fi + test -z "$skip_all" || skip_all=" # SKIP $skip_all" + + if test $test_external_has_tap -eq 0 + then + if test $test_remaining -gt 0 then - say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" + say_color pass "# passed all $msg" fi + say "1..$test_count$skip_all" fi - } 2>/dev/null - return $test_eval_ret_ -} -test_run_() { - test_cleanup=: - expecting_failure=$2 + test -d "$remove_trash" && + cd "$(dirname "$remove_trash")" && + rm -rf "$(basename "$remove_trash")" - if test "${TESTLIB_TEST_CHAIN_LINT:-1}" != 0; then - # turn off tracing for this test-eval, as it simply creates - # confusing noise in the "-x" output - trace_tmp=$trace - trace= - # 117 is magic because it is unlikely to match the exit - # code of other programs - test_eval_ "(exit 117) && $1" - if test "$?" != 117; then - error "bug in the test script: broken &&-chain: $1" + test_at_end_hook_ + + exit 0 ;; + + *) + if test $test_external_has_tap -eq 0 + then + say_color error "# failed $test_failure among $msg" + say "1..$test_count" fi - trace=$trace_tmp - fi - test_eval_ "$1" - eval_ret=$? + exit 1 ;; - if test -z "$immediate" || test $eval_ret = 0 || - test -n "$expecting_failure" && test "${test_cleanup:-:}" != ":" - then - test_eval_ "$test_cleanup" - fi - if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE" + esac +} + +# Provide an implementation of the 'yes' utility +yes() { + if test $# = 0 then - echo "" + y=y + else + y="$*" fi - return "$eval_ret" + + i=0 + while test $i -lt 99 + do + echo "$y" + i=$(($i+1)) + done } -test_start_() { - test_count=$(($test_count+1)) - maybe_setup_verbose +run_with_limited_cmdline() { + (ulimit -s 128 && "$@") } -test_finish_() { - echo >&3 "" - maybe_teardown_verbose +# +# THIS SHOULD ALWAYS BE THE LAST FUNCTION DEFINED IN THIS FILE +# +# Any client that sources this file should immediately execute this function +# afterwards with the command line arguments +# +# THERE SHOULD NOT BE ANY DIRECTLY EXECUTED LINES OF CODE IN THIS FILE +# +test_lib_main_init() { +# Note that this code is NOT indented +# Begin test_lib_main_init code + + +! [ -f ../TG-BUILD-SETTINGS ] || . ../TG-BUILD-SETTINGS +! [ -f TG-TEST-SETTINGS ] || . ./TG-TEST-SETTINGS + +: "${SHELL_PATH:=/bin/sh}" +: "${DIFF:=diff}" +: "${GIT_PATH:=$(cmd_path git)}" +: "${PERL_PATH:=$(cmd_path perl || :)}" +TESTLIB_DIRECTORY="$(whats_my_dir)" + +# Test the binaries we have just built. The tests are kept in +# t/ subdirectory and are run in 'trash directory' subdirectory. +if test -z "$TEST_DIRECTORY" +then + # We allow tests to override this, in case they want to run tests + # outside of t/, e.g. for running tests on the test library + # itself. + TEST_DIRECTORY="$TESTLIB_DIRECTORY" +else + # ensure that TEST_DIRECTORY is an absolute path so that it + # is valid even if the current working directory is changed + TEST_DIRECTORY="$(cd "$TEST_DIRECTORY" && pwd)" || exit 1 +fi +if test -z "$TEST_OUTPUT_DIRECTORY" +then + # Similarly, override this to store the test-results subdir + # elsewhere + TEST_OUTPUT_DIRECTORY="$TEST_DIRECTORY" +fi +[ -d "$TESTLIB_DIRECTORY"/empty ] || { + mkdir "$TESTLIB_DIRECTORY/empty" + chmod a-w "$TESTLIB_DIRECTORY/empty" } +EMPTY_DIRECTORY="$TESTLIB_DIRECTORY/empty" -test_skip() { - to_skip= - skipped_reason= - if match_pattern_list $this_test.$test_count $TESTLIB_SKIP_TESTS - then - to_skip=t - skipped_reason="TESTLIB_SKIP_TESTS" - fi - if test -z "$to_skip" && test -n "$test_prereq" && - ! test_have_prereq "$test_prereq" - then - to_skip=t +################################################################ +# It appears that people try to run tests with missing perl or git... +git_version="$("$GIT_PATH" --version 2>&1)" || + fatal 'error: you do not seem to have git available?' +case "$git_version" in [Gg][Ii][Tt]\ [Vv][Ee][Rr][Ss][Ii][Oo][Nn]\ [0-9]*) :;; *) + fatal "error: git --version returned bogus value: $git_version" +esac +#"$PERL_PATH" --version >/dev/null 2>&1 || +# fatal 'error: you do not seem to have perl available?' + +# if --tee was passed, write the output not only to the terminal, but +# additionally to the file test-results/$BASENAME.out, too. +case "$TESTLIB_TEST_TEE_STARTED, $* " in +done,*) + # do not redirect again + ;; +*' --tee '*|*' --verbose-log '*) + mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" + BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" + + # Make this filename available to the sub-process in case it is using + # --verbose-log. + TESTLIB_TEST_TEE_OUTPUT_FILE=$BASE.out + export TESTLIB_TEST_TEE_OUTPUT_FILE + + # Truncate before calling "tee -a" to get rid of the results + # from any previous runs. + >"$TESTLIB_TEST_TEE_OUTPUT_FILE" + + (TESTLIB_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; + echo $? >"$BASE.exit") | tee -a "$TESTLIB_TEST_TEE_OUTPUT_FILE" + test "$(cat "$BASE.exit")" = 0 + exit + ;; +esac + +# For repeatability, reset the environment to known value. +# TERM is sanitized below, after saving color control sequences. +LANG=C +LC_ALL=C +PAGER=cat +TZ=UTC +export LANG LC_ALL PAGER TZ +EDITOR=: +# A call to "unset" with no arguments causes at least Solaris 10 +# /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets +# deriving from the command substitution clustered with the other +# ones. +unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' + my @env = keys %ENV; + my $ok = join("|", qw( + TRACE + DEBUG + USE_LOOKUP + TEST + .*_TEST + MINIMUM_VERSION + PATH + PROVE + UNZIP + PERF_ + CURL_VERBOSE + TRACE_CURL + )); + my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); + print join("\n", @vars); +') +unset XDG_CONFIG_HOME +unset GITPERLLIB +GIT_AUTHOR_NAME='Te s t' +GIT_AUTHOR_EMAIL=test@example.net +GIT_COMMITTER_NAME='Fra mewor k' +GIT_COMMITTER_EMAIL=framework@example.org +GIT_MERGE_VERBOSITY=5 +GIT_MERGE_AUTOEDIT=no +GIT_TEMPLATE_DIR="$EMPTY_DIRECTORY" +GIT_CONFIG_NOSYSTEM=1 +GIT_ATTR_NOSYSTEM=1 +export PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_ATTR_NOSYSTEM +export GIT_MERGE_VERBOSITY GIT_MERGE_AUTOEDIT +export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME +export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME +export EDITOR + +# Tests using GIT_TRACE typically don't want : output +GIT_TRACE_BARE=1 +export GIT_TRACE_BARE + +# Protect ourselves from common misconfiguration to export +# CDPATH into the environment +unset CDPATH + +unset GREP_OPTIONS +unset UNZIP + +case $(echo $GIT_TRACE |tr "[A-Z]" "[a-z]") in +1|2|true) + GIT_TRACE=4 + ;; +esac + +# Convenience +# +# A regexp to match 5 and 40 hexdigits +_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' +_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" + +# Zero SHA-1 +_z40=0000000000000000000000000000000000000000 + +EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 + +# Line feed +LF=' +' + +# UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores +# when case-folding filenames +u200c=$(printf '\342\200\214') + +export _x05 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB - of_prereq= - if test "$missing_prereq" != "$test_prereq" - then - of_prereq=" of $test_prereq" - fi - skipped_reason="missing $missing_prereq${of_prereq}" - fi - if test -z "$to_skip" && test -n "$run_list" && - ! match_test_selector_list '--run' $test_count "$run_list" - then - to_skip=t - skipped_reason="--run" - fi +test "x$TERM" != "xdumb" && ( + test -t 1 && + tput bold >/dev/null 2>&1 && + tput setaf 1 >/dev/null 2>&1 && + tput sgr0 >/dev/null 2>&1 + ) && + color=t - case "$to_skip" in - t) - say_color skip >&3 "skipping test: $@" - say_color skip "ok $test_count # skip $1 ($skipped_reason)" - : true - ;; +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) + TESTLIB_TEST_LONG=t; export TESTLIB_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 ;; + --no-color) + color=; shift ;; + --tee) + shift ;; # was handled already + --root=*) + root=${1#--*=} + shift ;; + --chain-lint) + TESTLIB_TEST_CHAIN_LINT=1 + shift ;; + --no-chain-lint) + TESTLIB_TEST_CHAIN_LINT=0 + shift ;; + -x) + trace=t + verbose=t + shift ;; + --verbose-log) + verbose_log=t + shift ;; *) - false - ;; + echo "error: unknown test option '$1'" >&2; exit 1 ;; esac -} +done -# stub; runs at end of each successful test -test_at_end_hook_() { - : -} +if test -n "$color" +then + # Save the color control sequences now rather than run tput + # each time say_color() is called. This is done for two + # reasons: + # * TERM will be changed to dumb + # * HOME will be changed to a temporary directory and tput + # might need to read ~/.terminfo from the original HOME + # directory to get the control sequences + # Note: This approach assumes the control sequences don't end + # in a newline for any terminal of interest (command + # substitutions strip trailing newlines). Given that most + # (all?) terminals in common use are related to ECMA-48, this + # shouldn't be a problem. + say_color_error=$(tput bold; tput setaf 1) # bold red + say_color_skip=$(tput setaf 4) # blue + say_color_warn=$(tput setaf 3) # brown/yellow + say_color_pass=$(tput setaf 2) # green + say_color_info=$(tput setaf 6) # cyan + say_color_reset=$(tput sgr0) + say_color_="" # no formatting for normal text + say_color() { + test -z "$1" && test -n "$quiet" && return + eval "say_color_color=\$say_color_$1" + shift + printf "%s\\n" "$say_color_color$*$say_color_reset" + } +else + say_color() { + test -z "$1" && test -n "$quiet" && return + shift + printf "%s\n" "$*" + } +fi -test_done() { - TESTLIB_EXIT_OK=t +TERM=dumb +export TERM - if test -z "$HARNESS_ACTIVE" +if test -n "$HARNESS_ACTIVE" +then + if test "$verbose" = t || test -n "$verbose_only" 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" - - cat >"$test_results_path" <<-EOF - total $test_count - success $test_success - fixed $test_fixed - broken $test_broken - failed $test_failure - - EOF + printf 'Bail out! %s\n' \ + 'verbose mode forbidden under TAP harness; try --verbose-log' + exit 1 fi +fi - if test "$test_fixed" != 0 - then - say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" - fi - if test "$test_broken" != 0 - then - say_color warn "# still have $test_broken known breakage(s)" - fi - if test "$test_broken" != 0 || test "$test_fixed" != 0 - then - test_remaining=$(( $test_count - $test_broken - $test_fixed )) - msg="remaining $test_remaining test(s)" - else - test_remaining=$test_count - msg="$test_count test(s)" - fi - case "$test_failure" in - 0) - # Maybe print SKIP message - if test -n "$skip_all" && test $test_count -gt 0 - then - error "Can't use skip_all after running some tests" - fi - test -z "$skip_all" || skip_all=" # SKIP $skip_all" +test "${test_description}" != "" || +error "Test script did not set test_description." - if test $test_external_has_tap -eq 0 - then - if test $test_remaining -gt 0 - then - say_color pass "# passed all $msg" - fi - say "1..$test_count$skip_all" - fi +if test "$help" = "t" +then + printf '%s\n' "$test_description" + exit 0 +fi - test -d "$remove_trash" && - cd "$(dirname "$remove_trash")" && - rm -rf "$(basename "$remove_trash")" +exec 5>&1 +exec 6<&0 +if test "$verbose_log" = "t" +then + exec 3>>"$TESTLIB_TEST_TEE_OUTPUT_FILE" 4>&3 +elif test "$verbose" = "t" +then + exec 4>&2 3>&1 +else + exec 4>/dev/null 3>/dev/null +fi - test_at_end_hook_ +# Send any "-x" output directly to stderr to avoid polluting tests +# which capture stderr. We can do this unconditionally since it +# has no effect if tracing isn't turned on. +# +# Note that this sets up the trace fd as soon as we assign the variable, so it +# must come after the creation of descriptor 4 above. Likewise, we must never +# unset this, as it has the side effect of closing descriptor 4, which we +# use to show verbose tests to the user. +# +# Note also that we don't need or want to export it. The tracing is local to +# this shell, and we would not want to influence any shells we exec. +BASH_XTRACEFD=4 - exit 0 ;; +test_failure=0 +test_count=0 +test_fixed=0 +test_broken=0 +test_success=0 - *) - if test $test_external_has_tap -eq 0 - then - say_color error "# failed $test_failure among $msg" - say "1..$test_count" - fi +test_external_has_tap=0 - exit 1 ;; +TESTLIB_EXIT_OK= +trap 'die' EXIT +trap 'exit $?' HUP INT QUIT ABRT PIPE TERM - esac -} +# The user-facing functions are loaded from a separate file +. "$TEST_DIRECTORY/test-lib-functions.sh" +test_lib_functions_init + +last_verbose=t if [ -n "$TG_TEST_INSTALLED" ]; then [ -n "$(cmd_path tg || :)" ] || @@ -863,23 +908,6 @@ then test_done fi -# Provide an implementation of the 'yes' utility -yes() { - if test $# = 0 - then - y=y - else - y="$*" - fi - - i=0 - while test $i -lt 99 - do - echo "$y" - i=$(($i+1)) - done -} - # Fix some commands on Windows case $(uname -s) in *MINGW*) @@ -1012,8 +1040,8 @@ test_lazy_prereq SANITY ' return $status ' -run_with_limited_cmdline() { - (ulimit -s 128 && "$@") -} - test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true' + + +# End test_lib_main_init code +} diff --git a/t/test-lib.sh b/t/test-lib.sh index afee44d..488f124 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1 +1,2 @@ . ./test-lib-main.sh +test_lib_main_init "$@" -- 2.11.4.GIT