test/splice.c: reformat with astyle
[tinc] / test / testlib.sh.in
1 #!/bin/sh
2
3 set -ex
4
5 echo [STEP] Initialize test library
6
7 # Paths to compiled executables
8
9 # realpath on FreeBSD fails if the path does not exist.
10 realdir() {
11   [ -e "$1" ] || mkdir -p "$1"
12   if type realpath >/dev/null; then
13     realpath "$1"
14   else
15     readlink -f "$1"
16   fi
17 }
18
19 tincd_path=$(realdir "../src/tincd@EXEEXT@")
20 tinc_path=$(realdir "../src/tinc@EXEEXT@")
21
22 SPTPS_TEST=$(realdir "../src/sptps_test@EXEEXT@")
23 SPTPS_KEYPAIR=$(realdir "../src/sptps_keypair@EXEEXT@")
24
25 # Exit status list
26 EXIT_SKIP_TEST=77
27
28 # The list of the environment variables that tinc injects into the scripts it calls.
29 # shellcheck disable=SC2016
30 TINC_SCRIPT_VARS='$NETNAME,$NAME,$DEVICE,$IFACE,$NODE,$REMOTEADDRESS,$REMOTEPORT,$SUBNET,$WEIGHT,$INVITATION_FILE,$INVITATION_URL,$DEBUG'
31
32 # Test directories
33
34 # Reuse script name if it was passed in an env var (when imported from tinc scripts).
35 if [ -z "$SCRIPTNAME" ]; then
36   SCRIPTNAME=$(basename "$0")
37 fi
38
39 # Network names for tincd daemons.
40 net1=$SCRIPTNAME.1
41 net2=$SCRIPTNAME.2
42 net3=$SCRIPTNAME.3
43
44 # Configuration/pidfile directories for tincd daemons.
45 DIR_FOO=$(realdir "$PWD/$net1")
46 DIR_BAR=$(realdir "$PWD/$net2")
47 DIR_BAZ=$(realdir "$PWD/$net3")
48
49 # Register helper functions
50
51 # Alias gtimeout to timeout if it exists.
52 if type gtimeout >/dev/null; then
53   timeout() { gtimeout "$@"; }
54 fi
55
56 # Are the shell tools provided by busybox?
57 is_busybox() {
58   timeout --help 2>&1 | grep -q -i busybox
59 }
60
61 # busybox timeout returns 128 + signal number (which is TERM by default)
62 if is_busybox; then
63   EXIT_TIMEOUT=$((128 + 15))
64 else
65   EXIT_TIMEOUT=124
66 fi
67
68 # Is this msys2?
69 is_windows() {
70   test "$(uname -o)" = Msys
71 }
72
73 # Are we running on a CI server?
74 is_ci() {
75   test "$CI"
76 }
77
78 # Dump error message and exit with an error.
79 bail() {
80   echo >&2 "$@"
81   exit 1
82 }
83
84 # Remove carriage returns to normalize strings on Windows for easier comparisons.
85 rm_cr() {
86   tr -d '\r'
87 }
88
89 # Executes whatever is passed to it, checking that the resulting exit code is non-zero.
90 must_fail() {
91   if "$@"; then
92     bail "expected a non-zero exit code"
93   fi
94 }
95
96 # Runs its arguments with timeout(1) or gtimeout(1) if either are installed.
97 # Usage: try_limit_time 10 command --with --args
98 if type timeout >/dev/null; then
99   if is_busybox; then
100     # busybox does not support --foreground
101     try_limit_time() {
102       time=$1
103       shift
104       timeout "$time" "$@"
105     }
106   else
107     # BSD and GNU timeout do not require special handling
108     try_limit_time() {
109       time=$1
110       shift
111       timeout --foreground "$time" "$@"
112     }
113   fi
114 else
115   try_limit_time() {
116     echo >&2 "timeout was not found, running without time limits!"
117     shift
118     "$@"
119   }
120 fi
121
122 # wc -l on mac prints whitespace before the actual number.
123 # This is simplest cross-platform alternative without that behavior.
124 count_lines() {
125   awk 'END{ print NR }'
126 }
127
128 # Calls compiled tinc, passing any supplied arguments.
129 # Usage: tinc { foo | bar | baz } --arg1 val1 "$args"
130 tinc() {
131   peer=$1
132   shift
133
134   case "$peer" in
135   foo) try_limit_time 30 "$tinc_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" "$@" ;;
136   bar) try_limit_time 30 "$tinc_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" "$@" ;;
137   baz) try_limit_time 30 "$tinc_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" "$@" ;;
138   *) bail "invalid command [[$peer $*]]" ;;
139   esac
140 }
141
142 # Calls compiled tincd, passing any supplied arguments.
143 # Usage: tincd { foo | bar | baz } --arg1 val1 "$args"
144 tincd() {
145   peer=$1
146   shift
147
148   case "$peer" in
149   foo) try_limit_time 30 "$tincd_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" --logfile="$DIR_FOO/log" -d5 "$@" ;;
150   bar) try_limit_time 30 "$tincd_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" --logfile="$DIR_BAR/log" -d5 "$@" ;;
151   baz) try_limit_time 30 "$tincd_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" --logfile="$DIR_BAZ/log" -d5 "$@" ;;
152   *) bail "invalid command [[$peer $*]]" ;;
153   esac
154 }
155
156 # Start the specified tinc daemon.
157 # usage: start_tinc { foo | bar | baz }
158 start_tinc() {
159   peer=$1
160   shift
161
162   case "$peer" in
163   foo) tinc "$peer" start --logfile="$DIR_FOO/log" -d5 "$@" ;;
164   bar) tinc "$peer" start --logfile="$DIR_BAR/log" -d5 "$@" ;;
165   baz) tinc "$peer" start --logfile="$DIR_BAZ/log" -d5 "$@" ;;
166   *) bail "invalid peer $peer" ;;
167   esac
168 }
169
170 # Stop all tinc clients.
171 stop_all_tincs() {
172   (
173     # In case these pid files are mangled.
174     set +e
175     [ -f "$DIR_FOO/pid" ] && tinc foo stop
176     [ -f "$DIR_BAR/pid" ] && tinc bar stop
177     [ -f "$DIR_BAZ/pid" ] && tinc baz stop
178     true
179   )
180 }
181
182 # Checks that the number of reachable nodes matches what is expected.
183 # usage: require_nodes node_name expected_number
184 require_nodes() {
185   echo >&2 "Check that we're able to reach tincd"
186   test "$(tinc "$1" pid | count_lines)" = 1
187
188   echo >&2 "Check the number of reachable nodes for $1 (expecting $2)"
189   actual="$(tinc "$1" dump reachable nodes | count_lines)"
190
191   if [ "$actual" != "$2" ]; then
192     echo >&2 "tinc $1: expected $2 reachable nodes, got $actual"
193     exit 1
194   fi
195 }
196
197 peer_directory() {
198   case "$peer" in
199   foo) echo "$DIR_FOO" ;;
200   bar) echo "$DIR_BAR" ;;
201   baz) echo "$DIR_BAZ" ;;
202   *) bail "invalid peer $peer" ;;
203   esac
204 }
205
206 # This is an append-only log of all scripts executed by all peers.
207 script_runs_log() {
208   echo "$(peer_directory "$1")/script-runs.log"
209 }
210
211 # Create tincd script. If it fails, it kills the test script with SIGTERM.
212 # usage: create_script { foo | bar | baz } { tinc-up | host-down | ... } 'script content'
213 create_script() {
214   peer=$1
215   script=$2
216   shift 2
217
218   # This is the line that we should start from when reading the script execution log while waiting
219   # for $script from $peer. It is a poor man's hash map to avoid polluting tinc's home directory with
220   # "last seen" files. There seem to be no good solutions to this that are compatible with all shells.
221   line_var=$(next_line_var "$peer" "$script")
222
223   # We must reassign it here in case the script is recreated.
224   # shellcheck disable=SC2229
225   read -r "$line_var" <<EOF
226 1
227 EOF
228
229   # Full path to the script.
230   script_path=$(peer_directory "$peer")/$script
231
232   # Full path to the script execution log (one for each peer).
233   script_log=$(script_runs_log "$peer")
234   printf '' >"$script_log"
235
236   # Script output is redirected into /dev/null. Otherwise, it ends up
237   # in tinc's output and breaks things like 'tinc invite'.
238   cat >"$script_path" <<EOF
239 #!/bin/sh
240 (
241   cd "$PWD" || exit 1
242   SCRIPTNAME="$SCRIPTNAME" . ./testlib.sh
243   $@
244   echo "$script,\$$,$TINC_SCRIPT_VARS" >>"$script_log"
245 ) >/dev/null 2>&1 || kill -TERM $$
246 EOF
247
248   chmod u+x "$script_path"
249
250   if is_windows; then
251     echo "@$MINGW_SHELL '$script_path'" >"$script_path.cmd"
252   fi
253 }
254
255 # Returns the name of the variable that contains the line number
256 # we should read next when waiting on $script from $peer.
257 # usage: next_line_var foo host-up
258 next_line_var() {
259   peer=$1
260   script=$(echo "$2" | sed 's/[^a-zA-Z0-9]/_/g')
261   printf "%s" "next_line_${peer}_${script}"
262 }
263
264 # Waits for `peer`'s script `script` to finish `count` number of times.
265 # usage: wait_script { foo | bar | baz } { tinc-up | host-up | ... } [count=1]
266 wait_script() {
267   peer=$1
268   script=$2
269   count=$3
270
271   if [ -z "$count" ] || [ "$count" -lt 1 ]; then
272     count=1
273   fi
274
275   # Find out the location of the log and how many lines we should skip
276   # (because we've already seen them in previous invocations of wait_script
277   # for current $peer and $script).
278   line_var=$(next_line_var "$peer" "$script")
279
280   # eval is the only solution supported by POSIX shells.
281   # https://github.com/koalaman/shellcheck/wiki/SC3053
282   #   1. $line_var expands into 'next_line_foo_hosts_bar_up'
283   #   2. the name is substituted and the command becomes 'echo "$next_line_foo_hosts_bar_up"'
284   #   3. the command is evaluated and the line number is assigned to $line
285   line=$(eval "echo \"\$$line_var\"")
286
287   # This is the file that we monitor for script execution records.
288   script_log=$(script_runs_log "$peer")
289
290   # Starting from $line, read until $count matches are found.
291   # Print the number of the last matching line and exit.
292   # GNU tail 2.82 and newer terminates by itself when the pipe breaks.
293   # To support other tails we do an explicit `kill`.
294   # FIFO is useful here because otherwise it's difficult to determine
295   # which tail process should be killed. We could stick them in a process
296   # group by enabling job control, but this results in weird behavior when
297   # running tests in parallel on some interactive shells
298   # (e.g. when /bin/sh is symlinked to dash).
299   new_line=$(
300     try_limit_time 60 sh -c "
301       fifo=\$$.fifo
302       cleanup() { rm -f \$fifo; }
303       cleanup && trap cleanup EXIT
304
305       mkfifo \$$.fifo
306       tail -n '+$line' -f '$script_log' >\$fifo &
307       grep -n -m '$count' '^$script,'   <\$fifo
308       kill \$!
309     " | awk -F: 'END { print $1 }'
310   )
311
312   # Remember the next line number for future reference. We'll use it if
313   # wait_script is called again with same $peer and $script.
314   read -r "${line_var?}" <<EOF
315 $((line + new_line))
316 EOF
317 }
318
319 # Are we running tests in parallel?
320 is_parallel() {
321   # Grep the make flags for any of: '-j', '-j5', '-j 42', but not 'n-j', '-junk'.
322   echo "$MAKEFLAGS" | grep -E -q '(^|[[:space:]])-j[[:digit:]]*([[:space:]]|$)'
323 }
324
325 # Cleanup after running each script.
326 cleanup() {
327   (
328     set +ex
329
330     stop_all_tincs
331
332     # Ask nicely, then kill anything that's left.
333     if is_ci && ! is_parallel; then
334       kill_processes() {
335         signal=$1
336         shift
337         for process in "$@"; do
338           pkill -"SIG$signal" -x -u "$(id -u)" "$process"
339         done
340       }
341       echo >&2 "CI server detected, performing aggressive cleanup"
342       kill_processes TERM tinc tincd
343       kill_processes KILL tinc tincd
344     fi
345   ) || true
346 }
347
348 # Generate path to current shell which can be used from Windows applications.
349 if is_windows; then
350   MINGW_SHELL=$(cygpath --mixed -- "$SHELL")
351 fi
352
353 # This was called from a tincd script. Skip executing commands with side effects.
354 [ -n "$NAME" ] && return
355
356 echo [STEP] Check for leftover tinc daemons and test directories
357
358 # Cleanup leftovers from previous runs.
359 stop_all_tincs
360
361 # On Windows this can actually fail. We don't want to suppress possible failure with -f.
362 if [ -d "$DIR_FOO" ]; then rm -r "$DIR_FOO"; fi
363 if [ -d "$DIR_BAR" ]; then rm -r "$DIR_BAR"; fi
364 if [ -d "$DIR_BAZ" ]; then rm -r "$DIR_BAZ"; fi
365
366 # Register cleanup function so we don't have to call it everywhere
367 # (and failed scripts do not leave stray tincd running).
368 trap cleanup EXIT INT TERM