ctdb-scripts: Protect against races when starting grace period
[vlendec/samba-autobuild/.git] / ctdb / tests / run_tests.sh
1 #!/usr/bin/env bash
2
3 usage() {
4     cat <<EOF
5 Usage: $0 [OPTIONS] [TESTS]
6
7 Options:
8   -A            Use "cat -A" to print test output (only some tests)
9   -c            Run integration tests on a cluster
10   -C            Clean up when done by removing test state directory (see -V)
11   -D            Show diff between failed/expected test output (some tests only)
12   -e            Exit on the first test failure
13   -H            No headers - for running single test with other wrapper
14   -I <count>    Iterate tests <count> times, exiting on failure (implies -e, -N)
15   -l <count>    Use <count> daemons for local daemon integration tests
16   -L            Print daemon logs on test failure (only some tests)
17   -N            Don't print summary of tests results after running all tests
18   -q            Quiet - don't show tests being run (still displays summary)
19   -S <lib>      Use socket wrapper library <lib> for local integration tests
20   -v            Verbose - print test output for non-failures (only some tests)
21   -V <dir>      Use <dir> as test state directory
22   -x            Trace this script with the -x option
23   -X            Trace certain scripts run by tests using -x (only some tests)
24 EOF
25     exit 1
26 }
27
28 # Print a message and exit.
29 die ()
30 {
31     echo "$1" >&2 ; exit "${2:-1}"
32 }
33
34 ######################################################################
35
36 with_summary=true
37 quiet=false
38 exit_on_fail=false
39 max_iterations=1
40 no_header=false
41 test_state_dir=""
42 cleanup=false
43 test_time_limit=3600
44
45 export CTDB_TEST_VERBOSE=false
46 export CTDB_TEST_COMMAND_TRACE=false
47 export CTDB_TEST_CAT_RESULTS_OPTS=""
48 export CTDB_TEST_DIFF_RESULTS=false
49 export CTDB_TEST_PRINT_LOGS_ON_ERROR=false
50 export CTDB_TEST_LOCAL_DAEMONS=3
51 export CTDB_TEST_SWRAP_SO_PATH=""
52
53 while getopts "AcCDehHI:l:LNqS:T:vV:xX?" opt ; do
54         case "$opt" in
55         A) CTDB_TEST_CAT_RESULTS_OPTS="-A" ;;
56         c) CTDB_TEST_LOCAL_DAEMONS="" ;;
57         C) cleanup=true ;;
58         D) CTDB_TEST_DIFF_RESULTS=true ;;
59         e) exit_on_fail=true ;;
60         H) no_header=true ;;
61         I) max_iterations="$OPTARG" ; exit_on_fail=true ; with_summary=false ;;
62         l) CTDB_TEST_LOCAL_DAEMONS="$OPTARG" ;;
63         L) CTDB_TEST_PRINT_LOGS_ON_ERROR=true ;;
64         N) with_summary=false ;;
65         q) quiet=true ;;
66         S) CTDB_TEST_SWRAP_SO_PATH="$OPTARG" ;;
67         T) test_time_limit="$OPTARG" ;;
68         v) CTDB_TEST_VERBOSE=true ;;
69         V) test_state_dir="$OPTARG" ;;
70         x) set -x ;;
71         X) CTDB_TEST_COMMAND_TRACE=true ;;
72         \?|h) usage ;;
73         esac
74 done
75 shift $((OPTIND - 1))
76
77 case $(basename "$0") in
78     *run_cluster_tests*)
79         # Running on a cluster...  same as -c
80         CTDB_TEST_LOCAL_DAEMONS=""
81         ;;
82 esac
83
84 if $quiet ; then
85     show_progress() { cat >/dev/null ; }
86 else
87     show_progress() { cat ; }
88 fi
89
90 ######################################################################
91
92 test_header ()
93 {
94         local name="$1"
95
96         echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
97         echo "Running test $name ($(date '+%T'))"
98         echo "--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--"
99 }
100
101 test_footer ()
102 {
103         local f="$1"
104         local status="$2"
105         local interp="$3"
106         local duration="$4"
107
108         local statstr=""
109         if [ "$status" -eq 0 ] ; then
110                 statstr=""
111         else
112                 statstr=" (status $status)"
113         fi
114
115         echo "=========================================================================="
116         echo "TEST ${interp}: ${f}${statstr} (duration: ${duration}s)"
117         echo "=========================================================================="
118 }
119
120 ctdb_test_run ()
121 {
122         local f="$1"
123
124         $no_header || test_header "$f"
125
126         local status=0
127         local start_time
128
129         start_time=$(date '+%s')
130
131         if [ -x "$f" ] ; then
132                 timeout "$test_time_limit" "$f" </dev/null | show_progress
133                 status=$?
134         else
135                 echo "TEST IS NOT EXECUTABLE"
136                 status=99
137         fi
138
139         local duration=$(($(date +%s) - start_time))
140
141         tests_total=$((tests_total + 1))
142
143         local interp
144         case "$status" in
145         0)
146                 interp="PASSED"
147                 tests_passed=$((tests_passed + 1))
148                 ;;
149         77)
150                 interp="SKIPPED"
151                 tests_skipped=$((tests_skipped + 1))
152                 ;;
153         99)
154                 interp="ERROR"
155                 tests_failed=$((tests_failed + 1))
156                 ;;
157         124)
158                 interp="TIMEDOUT"
159                 tests_failed=$((tests_failed + 1))
160                 ;;
161         *)
162                 interp="FAILED"
163                 tests_failed=$((tests_failed + 1))
164                 ;;
165         esac
166
167         $no_header || test_footer "$f" "$status" "$interp" "$duration"
168
169         if $with_summary ; then
170                 local t
171                 if [ $status -eq 0 ] ; then
172                         t=" ${interp}"
173                 else
174                         t="*${interp}*"
175                 fi
176                 printf '%-10s %s\n' "$t" "$f" >>"$summary_file"
177         fi
178
179         # Skipped tests should not cause failure
180         case "$status" in
181         77)
182                 status=0
183                 ;;
184         esac
185
186         return $status
187 }
188
189 ######################################################################
190
191 tests_total=0
192 tests_passed=0
193 tests_skipped=0
194 tests_failed=0
195
196 if ! type mktemp >/dev/null 2>&1 ; then
197     # Not perfect, but it will do...
198     mktemp ()
199     {
200         local dir=false
201         if [ "$1" = "-d" ] ; then
202             dir=true
203         fi
204         local t="${TMPDIR:-/tmp}/tmp.$$.$RANDOM"
205         (
206             umask 077
207             if $dir ; then
208                 mkdir "$t"
209             else
210                 : >"$t"
211             fi
212         )
213         echo "$t"
214     }
215 fi
216
217 set -o pipefail
218
219 run_one_test ()
220 {
221     local f="$1"
222
223     CTDB_TEST_SUITE_DIR=$(dirname "$f")
224     export CTDB_TEST_SUITE_DIR
225     # This expands the most probable problem cases like "." and "..".
226     if [ "$(dirname "$CTDB_TEST_SUITE_DIR")" = "." ] ; then
227             CTDB_TEST_SUITE_DIR=$(cd "$CTDB_TEST_SUITE_DIR" && pwd)
228     fi
229
230     # Set CTDB_TEST_TMP_DIR
231     #
232     # Determine the relative test suite subdirectory.  The top-level
233     # test directory needs to be a prefix of the test suite directory,
234     # so make absolute versions of both.
235     local test_dir test_suite_dir reldir
236     test_dir=$(cd "$CTDB_TEST_DIR" && pwd)
237     test_suite_dir=$(cd "$CTDB_TEST_SUITE_DIR" && pwd)
238     reldir="${test_suite_dir#"${test_dir}"/}"
239
240     export CTDB_TEST_TMP_DIR="${test_state_dir}/${reldir}"
241     rm -rf "$CTDB_TEST_TMP_DIR"
242     mkdir -p "$CTDB_TEST_TMP_DIR"
243
244     ctdb_test_run "$f"
245     status=$?
246 }
247
248 run_tests ()
249 {
250         local f
251
252         for f ; do
253                 case "$f" in
254                 */README|*/README.md)
255                         continue
256                         ;;
257                 esac
258
259                 if [ ! -e "$f" ] ; then
260                         # Can't find it?  Check relative to CTDB_TEST_DIR.
261                         # Strip off current directory from beginning,
262                         # if there, just to make paths more friendly.
263                         f="${CTDB_TEST_DIR#"${PWD}"/}/${f}"
264                 fi
265
266                 if [ -d "$f" ] ; then
267                         local test_dir dir reldir subtests
268
269                         test_dir=$(cd "$CTDB_TEST_DIR" && pwd)
270                         dir=$(cd "$f" && pwd)
271                         reldir="${dir#"${test_dir}"/}"
272
273                         case "$reldir" in
274                         */*/*)
275                                 die "test \"$f\" is not recognised"
276                                 ;;
277                         */*)
278                                 # This is a test suite
279                                 subtests=$(echo "${f%/}/"*".sh")
280                                 if [ "$subtests" = "${f%/}/*.sh" ] ; then
281                                         # Probably empty directory
282                                         die "test \"$f\" is not recognised"
283                                 fi
284                                 ;;
285                         CLUSTER|INTEGRATION|UNIT)
286                                 # A collection of test suites
287                                 subtests=$(echo "${f%/}/"*)
288                                 ;;
289                         *)
290                                 die "test \"$f\" is not recognised"
291                         esac
292
293                         # Recurse - word-splitting wanted
294                         # shellcheck disable=SC2086
295                         run_tests $subtests
296                 elif [ -f "$f" ] ; then
297                         run_one_test "$f"
298                 else
299                         # Time to give up
300                         die "test \"$f\" is not recognised"
301                 fi
302
303                 if $exit_on_fail && [ "$status" -ne 0 ] ; then
304                         return "$status"
305                 fi
306         done
307 }
308
309 export CTDB_TEST_MODE="yes"
310
311 # Following 2 lines may be modified by installation script
312 CTDB_TESTS_ARE_INSTALLED=false
313 CTDB_TEST_DIR=$(dirname "$0")
314 export CTDB_TESTS_ARE_INSTALLED CTDB_TEST_DIR
315
316 if [ -z "$test_state_dir" ] ; then
317     if $CTDB_TESTS_ARE_INSTALLED ; then
318         test_state_dir=$(mktemp -d)
319     else
320         test_state_dir="${CTDB_TEST_DIR}/var"
321     fi
322 fi
323 mkdir -p "$test_state_dir"
324
325 summary_file="${test_state_dir}/.summary"
326 : >"$summary_file"
327
328 export TEST_SCRIPTS_DIR="${CTDB_TEST_DIR}/scripts"
329
330 # If no tests specified then run some defaults
331 if [ -z "$1" ] ; then
332         if [ -n "$CTDB_TEST_LOCAL_DAEMONS" ] ; then
333                 set -- UNIT INTEGRATION
334         else
335                 set -- INTEGRATION CLUSTER
336     fi
337 fi
338
339 do_cleanup ()
340 {
341     if $cleanup ; then
342         echo "Removing test state directory: ${test_state_dir}"
343         rm -rf "$test_state_dir"
344     else
345         echo "Not cleaning up test state directory: ${test_state_dir}"
346     fi
347 }
348
349 trap "do_cleanup ; exit 130" SIGINT
350 trap "do_cleanup ; exit 143" SIGTERM
351
352 iterations=0
353 # Special case: -I 0 means iterate forever (until failure)
354 while [ "$max_iterations" -eq 0 ] || [ $iterations -lt "$max_iterations" ] ; do
355         iterations=$((iterations + 1))
356
357         if [ "$max_iterations" -ne 1 ] ; then
358                 echo
359                 echo "##################################################"
360                 echo "ITERATION ${iterations}"
361                 echo "##################################################"
362                 echo
363         fi
364
365         run_tests "$@"
366         status=$?
367
368         if [ $status -ne 0 ] ; then
369                 break
370         fi
371 done
372
373 if $with_summary ; then
374         if [ "$status" -eq 0 ] || ! $exit_on_fail ; then
375                 echo
376                 cat "$summary_file"
377
378                 echo
379                 tests_run=$((tests_total - tests_skipped))
380                 printf '%d/%d tests passed' $tests_passed $tests_run
381                 if [ $tests_skipped -gt 0 ] ; then
382                         printf ' (%d skipped)' $tests_skipped
383                 fi
384                 printf '\n'
385         fi
386 fi
387 rm -f "$summary_file"
388
389 echo
390
391 do_cleanup
392
393 if $no_header || $exit_on_fail ; then
394     exit "$status"
395 elif [ $tests_failed -gt 0 ] ; then
396     exit 1
397 else
398     exit 0
399 fi