5 echo [STEP] Initialize test library
7 # Paths to compiled executables
9 # realpath on FreeBSD fails if the path does not exist.
11 [ -e "$1" ] || mkdir -p "$1"
12 if type realpath >/dev/null; then
19 tincd_path=$(realdir "../src/tincd@EXEEXT@")
20 tinc_path=$(realdir "../src/tinc@EXEEXT@")
22 # shellcheck disable=SC2034
23 SPTPS_TEST=$(realdir "../src/sptps_test@EXEEXT@")
24 # shellcheck disable=SC2034
25 SPTPS_KEYPAIR=$(realdir "../src/sptps_keypair@EXEEXT@")
28 # shellcheck disable=SC2034
30 # shellcheck disable=SC2034
33 # The list of the environment variables that tinc injects into the scripts it calls.
34 # shellcheck disable=SC2016
35 TINC_SCRIPT_VARS='$NETNAME,$NAME,$DEVICE,$IFACE,$NODE,$REMOTEADDRESS,$REMOTEPORT,$SUBNET,$WEIGHT,$INVITATION_FILE,$INVITATION_URL,$DEBUG'
39 # Reuse script name if it was passed in an env var (when imported from tinc scripts).
40 if [ -z "$SCRIPTNAME" ]; then
41 SCRIPTNAME=$(basename "$0")
44 # Network names for tincd daemons.
49 # Configuration/pidfile directories for tincd daemons.
50 DIR_FOO=$(realdir "$PWD/$net1")
51 DIR_BAR=$(realdir "$PWD/$net2")
52 DIR_BAZ=$(realdir "$PWD/$net3")
54 # Register helper functions
56 # Alias gtimeout to timeout if it exists.
57 if type gtimeout >/dev/null; then
58 timeout() { gtimeout "$@"; }
61 # Are the shell tools provided by busybox?
63 timeout --help 2>&1 | grep -q -i busybox
66 # busybox timeout returns 128 + signal number (which is TERM by default)
68 # shellcheck disable=SC2034
69 EXIT_TIMEOUT=$((128 + 15))
71 # shellcheck disable=SC2034
77 test "$(uname -o)" = Msys
80 # Are we running on a CI server?
85 # Dump error message and exit with an error.
91 # Remove carriage returns to normalize strings on Windows for easier comparisons.
96 # Executes whatever is passed to it, checking that the resulting exit code is non-zero.
99 bail "expected a non-zero exit code"
103 # Executes whatever is passed to it, checking that the resulting exit code is equal to the first argument.
111 if [ $code != "$expected" ]; then
112 bail "wrong exit code $code, expected $expected"
116 # Runs its arguments with timeout(1) or gtimeout(1) if either are installed.
117 # Usage: try_limit_time 10 command --with --args
118 if type timeout >/dev/null; then
120 # busybox does not support --foreground
127 # BSD and GNU timeout do not require special handling
131 timeout --foreground "$time" "$@"
136 echo >&2 "timeout was not found, running without time limits!"
142 # wc -l on mac prints whitespace before the actual number.
143 # This is simplest cross-platform alternative without that behavior.
145 awk 'END{ print NR }'
148 # Calls compiled tinc, passing any supplied arguments.
149 # Usage: tinc { foo | bar | baz } --arg1 val1 "$args"
155 foo) try_limit_time 30 "$tinc_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" "$@" ;;
156 bar) try_limit_time 30 "$tinc_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" "$@" ;;
157 baz) try_limit_time 30 "$tinc_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" "$@" ;;
158 *) bail "invalid command [[$peer $*]]" ;;
162 # Calls compiled tincd, passing any supplied arguments.
163 # Usage: tincd { foo | bar | baz } --arg1 val1 "$args"
169 foo) try_limit_time 30 "$tincd_path" -n "$net1" --config="$DIR_FOO" --pidfile="$DIR_FOO/pid" --logfile="$DIR_FOO/log" -d5 "$@" ;;
170 bar) try_limit_time 30 "$tincd_path" -n "$net2" --config="$DIR_BAR" --pidfile="$DIR_BAR/pid" --logfile="$DIR_BAR/log" -d5 "$@" ;;
171 baz) try_limit_time 30 "$tincd_path" -n "$net3" --config="$DIR_BAZ" --pidfile="$DIR_BAZ/pid" --logfile="$DIR_BAZ/log" -d5 "$@" ;;
172 *) bail "invalid command [[$peer $*]]" ;;
176 # Start the specified tinc daemon.
177 # usage: start_tinc { foo | bar | baz }
183 foo) tinc "$peer" start --logfile="$DIR_FOO/log" -d5 "$@" ;;
184 bar) tinc "$peer" start --logfile="$DIR_BAR/log" -d5 "$@" ;;
185 baz) tinc "$peer" start --logfile="$DIR_BAZ/log" -d5 "$@" ;;
186 *) bail "invalid peer $peer" ;;
190 # Stop all tinc clients.
193 # In case these pid files are mangled.
195 [ -f "$DIR_FOO/pid" ] && tinc foo stop
196 [ -f "$DIR_BAR/pid" ] && tinc bar stop
197 [ -f "$DIR_BAZ/pid" ] && tinc baz stop
202 # Checks that the number of reachable nodes matches what is expected.
203 # usage: require_nodes node_name expected_number
205 echo >&2 "Check that we're able to reach tincd"
206 test "$(tinc "$1" pid | count_lines)" = 1
208 echo >&2 "Check the number of reachable nodes for $1 (expecting $2)"
209 actual="$(tinc "$1" dump reachable nodes | count_lines)"
211 if [ "$actual" != "$2" ]; then
212 echo >&2 "tinc $1: expected $2 reachable nodes, got $actual"
219 foo) echo "$DIR_FOO" ;;
220 bar) echo "$DIR_BAR" ;;
221 baz) echo "$DIR_BAZ" ;;
222 *) bail "invalid peer $peer" ;;
226 # This is an append-only log of all scripts executed by all peers.
228 echo "$(peer_directory "$1")/script-runs.log"
231 # Create tincd script. If it fails, it kills the test script with SIGTERM.
232 # usage: create_script { foo | bar | baz } { tinc-up | host-down | ... } 'script content'
238 # This is the line that we should start from when reading the script execution log while waiting
239 # for $script from $peer. It is a poor man's hash map to avoid polluting tinc's home directory with
240 # "last seen" files. There seem to be no good solutions to this that are compatible with all shells.
241 line_var=$(next_line_var "$peer" "$script")
243 # We must reassign it here in case the script is recreated.
244 # shellcheck disable=SC2229
245 read -r "$line_var" <<EOF
249 # Full path to the script.
250 script_path=$(peer_directory "$peer")/$script
252 # Full path to the script execution log (one for each peer).
253 script_log=$(script_runs_log "$peer")
254 printf '' >"$script_log"
256 # Script output is redirected into /dev/null. Otherwise, it ends up
257 # in tinc's output and breaks things like 'tinc invite'.
258 cat >"$script_path" <<EOF
262 SCRIPTNAME="$SCRIPTNAME" . ./testlib.sh
264 echo "$script,\$$,$TINC_SCRIPT_VARS" >>"$script_log"
265 ) >/dev/null 2>&1 || kill -TERM $$
268 chmod u+x "$script_path"
271 echo "@$MINGW_SHELL '$script_path'" >"$script_path.cmd"
275 # Returns the name of the variable that contains the line number
276 # we should read next when waiting on $script from $peer.
277 # usage: next_line_var foo host-up
280 script=$(echo "$2" | sed 's/[^a-zA-Z0-9]/_/g')
281 printf "%s" "next_line_${peer}_${script}"
284 # Waits for `peer`'s script `script` to finish `count` number of times.
285 # usage: wait_script { foo | bar | baz } { tinc-up | host-up | ... } [count=1]
291 if [ -z "$count" ] || [ "$count" -lt 1 ]; then
295 # Find out the location of the log and how many lines we should skip
296 # (because we've already seen them in previous invocations of wait_script
297 # for current $peer and $script).
298 line_var=$(next_line_var "$peer" "$script")
300 # eval is the only solution supported by POSIX shells.
301 # https://github.com/koalaman/shellcheck/wiki/SC3053
302 # 1. $line_var expands into 'next_line_foo_hosts_bar_up'
303 # 2. the name is substituted and the command becomes 'echo "$next_line_foo_hosts_bar_up"'
304 # 3. the command is evaluated and the line number is assigned to $line
305 line=$(eval "echo \"\$$line_var\"")
307 # This is the file that we monitor for script execution records.
308 script_log=$(script_runs_log "$peer")
310 # Starting from $line, read until $count matches are found.
311 # Print the number of the last matching line and exit.
312 # GNU tail 2.82 and newer terminates by itself when the pipe breaks.
313 # To support other tails we do an explicit `kill`.
314 # FIFO is useful here because otherwise it's difficult to determine
315 # which tail process should be killed. We could stick them in a process
316 # group by enabling job control, but this results in weird behavior when
317 # running tests in parallel on some interactive shells
318 # (e.g. when /bin/sh is symlinked to dash).
320 try_limit_time 60 sh -c "
322 cleanup() { rm -f \$fifo; }
323 cleanup && trap cleanup EXIT
326 tail -n '+$line' -f '$script_log' >\$fifo &
327 grep -n -m '$count' '^$script,' <\$fifo
329 " | awk -F: 'END { print $1 }'
332 # Remember the next line number for future reference. We'll use it if
333 # wait_script is called again with same $peer and $script.
334 read -r "${line_var?}" <<EOF
339 # Are we running tests in parallel?
341 # Grep the make flags for any of: '-j', '-j5', '-j 42', but not 'n-j', '-junk'.
342 echo "$MAKEFLAGS" | grep -E -q '(^|[[:space:]])-j[[:digit:]]*([[:space:]]|$)'
345 # Cleanup after running each script.
350 if command -v cleanup_hook 2>/dev/null; then
351 echo >&2 "Cleanup hook found, calling..."
357 # Ask nicely, then kill anything that's left.
358 if is_ci && ! is_parallel; then
362 for process in "$@"; do
363 pkill -"SIG$signal" -x -u "$(id -u)" "$process"
366 echo >&2 "CI server detected, performing aggressive cleanup"
367 kill_processes TERM tinc tincd
368 kill_processes KILL tinc tincd
373 # Generate path to current shell which can be used from Windows applications.
375 MINGW_SHELL=$(cygpath --mixed -- "$SHELL")
378 # This was called from a tincd script. Skip executing commands with side effects.
379 [ -n "$NAME" ] && return
381 echo [STEP] Check for leftover tinc daemons and test directories
383 # Cleanup leftovers from previous runs.
386 # On Windows this can actually fail. We don't want to suppress possible failure with -f.
387 if [ -d "$DIR_FOO" ]; then rm -r "$DIR_FOO"; fi
388 if [ -d "$DIR_BAR" ]; then rm -r "$DIR_BAR"; fi
389 if [ -d "$DIR_BAZ" ]; then rm -r "$DIR_BAZ"; fi
391 # Register cleanup function so we don't have to call it everywhere
392 # (and failed scripts do not leave stray tincd running).
393 trap cleanup EXIT INT TERM