auth: Fix CID 1615191 Uninitialized scalar variable
[vlendec/samba-autobuild/.git] / ctdb / tests / run_tests.sh
index c39b92dcd85dccc2ace64a282fe55dc6bf1d2247..dfe2a9ab36437f43247e07b2e5c545644437e92c 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 usage() {
     cat <<EOF
@@ -7,16 +7,18 @@ Usage: $0 [OPTIONS] [TESTS]
 Options:
   -A           Use "cat -A" to print test output (only some tests)
   -c           Run integration tests on a cluster
-  -C           Clean up - kill daemons and remove TEST_VAR_DIR when done
-  -d           Print descriptions of tests instead of filenames (dodgy!)
+  -C           Clean up when done by removing test state directory (see -V)
   -D           Show diff between failed/expected test output (some tests only)
   -e           Exit on the first test failure
   -H           No headers - for running single test with other wrapper
+  -I <count>    Iterate tests <count> times, exiting on failure (implies -e, -N)
+  -l <count>    Use <count> daemons for local daemon integration tests
+  -L            Print daemon logs on test failure (only some tests)
   -N           Don't print summary of tests results after running all tests
-  -q           Quiet - don't show tests being run (hint: use with -s)
-  -S            Enable socket wrapper
+  -q           Quiet - don't show tests being run (still displays summary)
+  -S <lib>      Use socket wrapper library <lib> for local integration tests
   -v           Verbose - print test output for non-failures (only some tests)
-  -V <dir>     Use <dir> as TEST_VAR_DIR
+  -V <dir>     Use <dir> as test state directory
   -x           Trace this script with the -x option
   -X           Trace certain scripts run by tests using -x (only some tests)
 EOF
@@ -26,58 +28,56 @@ EOF
 # Print a message and exit.
 die ()
 {
-    echo "$1" >&2 ; exit ${2:-1}
+    echo "$1" >&2 ; exit "${2:-1}"
 }
 
 ######################################################################
 
 with_summary=true
-with_desc=false
 quiet=false
 exit_on_fail=false
+max_iterations=1
 no_header=false
-socket_wrapper=false
-
-export TEST_VERBOSE=false
-export TEST_COMMAND_TRACE=false
-export TEST_CAT_RESULTS_OPTS=""
-export TEST_DIFF_RESULTS=false
-export TEST_LOCAL_DAEMONS
-[ -n "$TEST_LOCAL_DAEMONS" ] || TEST_LOCAL_DAEMONS=3
-export TEST_VAR_DIR=""
-export TEST_CLEANUP=false
-
-temp=$(getopt -n "$prog" -o "AcCdDehHNqSvV:xX" -l help -- "$@")
-
-[ $? != 0 ] && usage
-
-eval set -- "$temp"
-
-while true ; do
-    case "$1" in
-       -A) TEST_CAT_RESULTS_OPTS="-A" ; shift ;;
-       -c) TEST_LOCAL_DAEMONS="" ; shift ;;
-       -C) TEST_CLEANUP=true ; shift ;;
-       -d) with_desc=true ; shift ;;  # 4th line of output is description
-       -D) TEST_DIFF_RESULTS=true ; shift ;;
-       -e) exit_on_fail=true ; shift ;;
-       -H) no_header=true ; shift ;;
-       -N) with_summary=false ; shift ;;
-       -q) quiet=true ; shift ;;
-       -S) socket_wrapper=true ; shift ;;
-       -v) TEST_VERBOSE=true ; shift ;;
-       -V) TEST_VAR_DIR="$2" ; shift 2 ;;
-       -x) set -x; shift ;;
-       -X) TEST_COMMAND_TRACE=true ; shift ;;
-       --) shift ; break ;;
-       *) usage ;;
-    esac
+test_state_dir=""
+cleanup=false
+test_time_limit=3600
+
+export CTDB_TEST_VERBOSE=false
+export CTDB_TEST_COMMAND_TRACE=false
+export CTDB_TEST_CAT_RESULTS_OPTS=""
+export CTDB_TEST_DIFF_RESULTS=false
+export CTDB_TEST_PRINT_LOGS_ON_ERROR=false
+export CTDB_TEST_LOCAL_DAEMONS=3
+export CTDB_TEST_SWRAP_SO_PATH=""
+
+while getopts "AcCDehHI:l:LNqS:T:vV:xX?" opt ; do
+       case "$opt" in
+       A) CTDB_TEST_CAT_RESULTS_OPTS="-A" ;;
+       c) CTDB_TEST_LOCAL_DAEMONS="" ;;
+       C) cleanup=true ;;
+       D) CTDB_TEST_DIFF_RESULTS=true ;;
+       e) exit_on_fail=true ;;
+       H) no_header=true ;;
+       I) max_iterations="$OPTARG" ; exit_on_fail=true ; with_summary=false ;;
+       l) CTDB_TEST_LOCAL_DAEMONS="$OPTARG" ;;
+       L) CTDB_TEST_PRINT_LOGS_ON_ERROR=true ;;
+       N) with_summary=false ;;
+       q) quiet=true ;;
+       S) CTDB_TEST_SWRAP_SO_PATH="$OPTARG" ;;
+       T) test_time_limit="$OPTARG" ;;
+       v) CTDB_TEST_VERBOSE=true ;;
+       V) test_state_dir="$OPTARG" ;;
+       x) set -x ;;
+       X) CTDB_TEST_COMMAND_TRACE=true ;;
+       \?|h) usage ;;
+       esac
 done
+shift $((OPTIND - 1))
 
 case $(basename "$0") in
     *run_cluster_tests*)
        # Running on a cluster...  same as -c
-       TEST_LOCAL_DAEMONS=""
+       CTDB_TEST_LOCAL_DAEMONS=""
        ;;
 esac
 
@@ -89,69 +89,111 @@ fi
 
 ######################################################################
 
-ctdb_test_begin ()
+test_header ()
 {
-    local name="$1"
+       local name="$1"
 
-    teststarttime=$(date '+%s')
-    testduration=0
-
-    echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
-    echo "Running test $name ($(date '+%T'))"
-    echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
+       echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
+       echo "Running test $name ($(date '+%T'))"
+       echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
 }
 
-ctdb_test_end ()
+test_footer ()
 {
-    local name="$1" ; shift
-    local status="$1" ; shift
-    # "$@" is command-line
-
-    local interp="SKIPPED"
-    local statstr=" (reason $*)"
-    if [ -n "$status" ] ; then
-       if [ $status -eq 0 ] ; then
-           interp="PASSED"
-           statstr=""
-           echo "ALL OK: $*"
+       local f="$1"
+       local status="$2"
+       local interp="$3"
+       local duration="$4"
+
+       local statstr=""
+       if [ "$status" -eq 0 ] ; then
+               statstr=""
        else
-           interp="FAILED"
-           statstr=" (status $status)"
+               statstr=" (status $status)"
        fi
-    fi
-
-    testduration=$(($(date +%s)-$teststarttime))
-
-    echo "=========================================================================="
-    echo "TEST ${interp}: ${name}${statstr} (duration: ${testduration}s)"
-    echo "=========================================================================="
 
+       echo "=========================================================================="
+       echo "TEST ${interp}: ${f}${statstr} (duration: ${duration}s)"
+       echo "=========================================================================="
 }
 
 ctdb_test_run ()
 {
-    local name="$1" ; shift
+       local f="$1"
 
-    [ -n "$1" ] || set -- "$name"
+       $no_header || test_header "$f"
 
-    $no_header || ctdb_test_begin "$name"
+       local status=0
+       local start_time
 
-    local status=0
-    "$@" || status=$?
+       start_time=$(date '+%s')
 
-    $no_header || ctdb_test_end "$name" "$status" "$*"
+       if [ -x "$f" ] ; then
+               timeout "$test_time_limit" "$f" </dev/null | show_progress
+               status=$?
+       else
+               echo "TEST IS NOT EXECUTABLE"
+               status=99
+       fi
 
-    return $status
+       local duration=$(($(date +%s) - start_time))
+
+       tests_total=$((tests_total + 1))
+
+       local interp
+       case "$status" in
+       0)
+               interp="PASSED"
+               tests_passed=$((tests_passed + 1))
+               ;;
+       77)
+               interp="SKIPPED"
+               tests_skipped=$((tests_skipped + 1))
+               ;;
+       99)
+               interp="ERROR"
+               tests_failed=$((tests_failed + 1))
+               ;;
+       124)
+               interp="TIMEDOUT"
+               tests_failed=$((tests_failed + 1))
+               ;;
+       *)
+               interp="FAILED"
+               tests_failed=$((tests_failed + 1))
+               ;;
+       esac
+
+       $no_header || test_footer "$f" "$status" "$interp" "$duration"
+
+       if $with_summary ; then
+               local t
+               if [ $status -eq 0 ] ; then
+                       t=" ${interp}"
+               else
+                       t="*${interp}*"
+               fi
+               printf '%-10s %s\n' "$t" "$f" >>"$summary_file"
+       fi
+
+       # Skipped tests should not cause failure
+       case "$status" in
+       77)
+               status=0
+               ;;
+       esac
+
+       return $status
 }
 
 ######################################################################
 
 tests_total=0
 tests_passed=0
+tests_skipped=0
 tests_failed=0
-summary=""
 
-if ! which mktemp >/dev/null 2>&1 ; then
+if ! type mktemp >/dev/null 2>&1 ; then
     # Not perfect, but it will do...
     mktemp ()
     {
@@ -165,165 +207,191 @@ if ! which mktemp >/dev/null 2>&1 ; then
            if $dir ; then
                mkdir "$t"
            else
-               >"$t"
+               >"$t"
            fi
        )
        echo "$t"
     }
 fi
 
-tf=$(mktemp)
-sf=$(mktemp)
-
 set -o pipefail
 
 run_one_test ()
 {
     local f="$1"
 
-    [ -x "$f" ] || die "test \"$f\" is not executable"
-    tests_total=$(($tests_total + 1))
+    CTDB_TEST_SUITE_DIR=$(dirname "$f")
+    export CTDB_TEST_SUITE_DIR
+    # This expands the most probable problem cases like "." and "..".
+    if [ "$(dirname "$CTDB_TEST_SUITE_DIR")" = "." ] ; then
+           CTDB_TEST_SUITE_DIR=$(cd "$CTDB_TEST_SUITE_DIR" && pwd)
+    fi
 
-    ctdb_test_run "$f" | tee "$tf" | show_progress
+    # Set CTDB_TEST_TMP_DIR
+    #
+    # Determine the relative test suite subdirectory.  The top-level
+    # test directory needs to be a prefix of the test suite directory,
+    # so make absolute versions of both.
+    local test_dir test_suite_dir reldir
+    test_dir=$(cd "$CTDB_TEST_DIR" && pwd)
+    test_suite_dir=$(cd "$CTDB_TEST_SUITE_DIR" && pwd)
+    reldir="${test_suite_dir#"${test_dir}"/}"
+
+    export CTDB_TEST_TMP_DIR="${test_state_dir}/${reldir}"
+    rm -rf "$CTDB_TEST_TMP_DIR"
+    mkdir -p "$CTDB_TEST_TMP_DIR"
+
+    ctdb_test_run "$f"
     status=$?
-    if [ $status -eq 0 ] ; then
-       tests_passed=$(($tests_passed + 1))
-    else
-       tests_failed=$(($tests_failed + 1))
-    fi
-    if $with_summary ; then
-       local t
-       if [ $status -eq 0 ] ; then
-           t=" PASSED "
-       else
-           t="*FAILED*"
-       fi
-       if $with_desc ; then
-           desc=$(tail -n +4 $tf | head -n 1)
-           f="$desc"
-       fi
-       echo "$t $f" >>"$sf"
-    fi
 }
 
-find_and_run_one_test ()
+run_tests ()
 {
-    local t="$1"
-    local dir="$2"
-
-    local f="${dir}${dir:+/}${t}"
-
-    if [ -d "$f" ] ; then
-       local i
-       for i in $(ls "${f%/}/"*".sh" 2>/dev/null) ; do
-           run_one_test "$i"
-           if $exit_on_fail && [ $status -ne 0 ] ; then
-               break
-           fi
+       local f
+
+       for f ; do
+               case "$f" in
+               */README|*/README.md)
+                       continue
+                       ;;
+               esac
+
+               if [ ! -e "$f" ] ; then
+                       # Can't find it?  Check relative to CTDB_TEST_DIR.
+                       # Strip off current directory from beginning,
+                       # if there, just to make paths more friendly.
+                       f="${CTDB_TEST_DIR#"${PWD}"/}/${f}"
+               fi
+
+               if [ -d "$f" ] ; then
+                       local test_dir dir reldir subtests
+
+                       test_dir=$(cd "$CTDB_TEST_DIR" && pwd)
+                       dir=$(cd "$f" && pwd)
+                       reldir="${dir#"${test_dir}"/}"
+
+                       case "$reldir" in
+                       */*/*)
+                               die "test \"$f\" is not recognised"
+                               ;;
+                       */*)
+                               # This is a test suite
+                               subtests=$(echo "${f%/}/"*".sh")
+                               if [ "$subtests" = "${f%/}/*.sh" ] ; then
+                                       # Probably empty directory
+                                       die "test \"$f\" is not recognised"
+                               fi
+                               ;;
+                       CLUSTER|INTEGRATION|UNIT)
+                               # A collection of test suites
+                               subtests=$(echo "${f%/}/"*)
+                               ;;
+                       *)
+                               die "test \"$f\" is not recognised"
+                       esac
+
+                       # Recurse - word-splitting wanted
+                       # shellcheck disable=SC2086
+                       run_tests $subtests
+               elif [ -f "$f" ] ; then
+                       run_one_test "$f"
+               else
+                       # Time to give up
+                       die "test \"$f\" is not recognised"
+               fi
+
+               if $exit_on_fail && [ "$status" -ne 0 ] ; then
+                       return "$status"
+               fi
        done
-       # No tests found?  Not a tests directory!  Not found...
-       [ -n "$status" ] || status=127
-    elif [ -f "$f" ] ; then
-       run_one_test "$f"
-    else
-       status=127
-    fi
 }
 
+export CTDB_TEST_MODE="yes"
+
 # Following 2 lines may be modified by installation script
-export CTDB_TESTS_ARE_INSTALLED=false
-test_dir=$(dirname "$0")
+CTDB_TESTS_ARE_INSTALLED=false
+CTDB_TEST_DIR=$(dirname "$0")
+export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR
 
-if [ -z "$TEST_VAR_DIR" ] ; then
+if [ -z "$test_state_dir" ] ; then
     if $CTDB_TESTS_ARE_INSTALLED ; then
-       TEST_VAR_DIR=$(mktemp -d)
+       test_state_dir=$(mktemp -d)
     else
-       TEST_VAR_DIR="${test_dir}/var"
+       test_state_dir="${CTDB_TEST_DIR}/var"
     fi
 fi
-mkdir -p "$TEST_VAR_DIR"
+mkdir -p "$test_state_dir"
 
-# Must be absolute
-TEST_VAR_DIR=$(cd "$TEST_VAR_DIR"; echo "$PWD")
-echo "TEST_VAR_DIR=$TEST_VAR_DIR"
+summary_file="${test_state_dir}/.summary"
+: >"$summary_file"
 
-if $socket_wrapper ; then
-    export SOCKET_WRAPPER_DIR="${TEST_VAR_DIR}/sw"
-    mkdir -p "$SOCKET_WRAPPER_DIR"
-fi
-
-export TEST_SCRIPTS_DIR="${test_dir}/scripts"
+export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts"
 
 # If no tests specified then run some defaults
 if [ -z "$1" ] ; then
-    if [ -n "$TEST_LOCAL_DAEMONS" ] ; then
-       set -- onnode takeover tool eventscripts cunit eventd shellcheck simple
-    else
-       set -- simple complex
+       if [ -n "$CTDB_TEST_LOCAL_DAEMONS" ] ; then
+               set -- UNIT INTEGRATION
+       else
+               set -- INTEGRATION CLUSTER
     fi
 fi
 
 do_cleanup ()
 {
-    if $TEST_CLEANUP ; then
-       echo "Removing TEST_VAR_DIR=$TEST_VAR_DIR"
-       rm -rf "$TEST_VAR_DIR"
+    if $cleanup ; then
+       echo "Removing test state directory: ${test_state_dir}"
+       rm -rf "$test_state_dir"
     else
-       echo "Not cleaning up TEST_VAR_DIR=$TEST_VAR_DIR"
+       echo "Not cleaning up test state directory: ${test_state_dir}"
     fi
 }
 
-cleanup_handler ()
-{
-    if $TEST_CLEANUP ; then
-       if [ -n "$TEST_LOCAL_DAEMONS" -a "$f" = "simple" ] ; then
-           echo "***** shutting down daemons *****"
-           find_and_run_one_test simple/99_daemons_shutdown.sh "$tests_dir"
-       fi
-    fi
-    do_cleanup
-}
+trap "do_cleanup ; exit 130" SIGINT
+trap "do_cleanup ; exit 143" SIGTERM
 
-trap cleanup_handler SIGINT SIGTERM
+iterations=0
+# Special case: -I 0 means iterate forever (until failure)
+while [ "$max_iterations" -eq 0 ] || [ $iterations -lt "$max_iterations" ] ; do
+       iterations=$((iterations + 1))
 
-for f ; do
-    find_and_run_one_test "$f"
+       if [ "$max_iterations" -ne 1 ] ; then
+               echo
+               echo "##################################################"
+               echo "ITERATION ${iterations}"
+               echo "##################################################"
+               echo
+       fi
 
-    if [ $status -eq 127 ] ; then
-       # Find the the top-level tests directory
-       tests_dir=$(dirname $(cd $TEST_SCRIPTS_DIR; echo $PWD))
-       # Strip off current directory from beginning, if there, just
-       # to make paths more friendly.
-       tests_dir=${tests_dir#$PWD/}
-       find_and_run_one_test "$f" "$tests_dir"
-    fi
+       run_tests "$@"
+       status=$?
 
-    if [ $status -eq 127 ] ; then
-           die "test \"$f\" is not recognised"
-    fi
-
-    if $exit_on_fail && [ $status -ne 0 ] ; then
-           break
-    fi
+       if [ $status -ne 0 ] ; then
+               break
+       fi
 done
 
-rm -f "$tf"
-
 if $with_summary ; then
-    echo
-    cat "$sf"
-    echo
-    echo "${tests_passed}/${tests_total} tests passed"
+       if [ "$status" -eq 0 ] || ! $exit_on_fail ; then
+               echo
+               cat "$summary_file"
+
+               echo
+               tests_run=$((tests_total - tests_skipped))
+               printf '%d/%d tests passed' $tests_passed $tests_run
+               if [ $tests_skipped -gt 0 ] ; then
+                       printf ' (%d skipped)' $tests_skipped
+               fi
+               printf '\n'
+       fi
 fi
-
-rm -f "$sf"
+rm -f "$summary_file"
 
 echo
 
 do_cleanup
 
 if $no_header || $exit_on_fail ; then
-    exit $status
+    exit "$status"
 elif [ $tests_failed -gt 0 ] ; then
     exit 1
 else