-#!/bin/bash
+#!/usr/bin/env bash
usage() {
cat <<EOF
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
# 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
######################################################################
-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 ()
{
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