X-Git-Url: https://www.tinc-vpn.org/git/browse?a=blobdiff_plain;f=test%2Ftestlib.sh.in;h=c63d27d13858ae659cf6f8fc7bd66138bb8a2717;hb=77cd819058de43a5fcea54300dde50e03088c318;hp=185aec0b135531d3d94370ebcbc879bc78729adb;hpb=046a10d692d1ac22de4daf783ee4fe025c4eb6ec;p=tinc diff --git a/test/testlib.sh.in b/test/testlib.sh.in index 185aec0b..c63d27d1 100644 --- a/test/testlib.sh.in +++ b/test/testlib.sh.in @@ -19,10 +19,15 @@ realdir() { tincd_path=$(realdir "../src/tincd@EXEEXT@") tinc_path=$(realdir "../src/tinc@EXEEXT@") +# shellcheck disable=SC2034 SPTPS_TEST=$(realdir "../src/sptps_test@EXEEXT@") +# shellcheck disable=SC2034 SPTPS_KEYPAIR=$(realdir "../src/sptps_keypair@EXEEXT@") # Exit status list +# shellcheck disable=SC2034 +EXIT_FAILURE=1 +# shellcheck disable=SC2034 EXIT_SKIP_TEST=77 # The list of the environment variables that tinc injects into the scripts it calls. @@ -53,6 +58,12 @@ if type gtimeout >/dev/null; then timeout() { gtimeout "$@"; } fi +# As usual, BSD tools require special handling, as they do not support -i without a suffix. +# Note that there must be no space after -i, or it won't work on GNU sed. +sed_cmd() { + sed -i.orig "$@" +} + # Are the shell tools provided by busybox? is_busybox() { timeout --help 2>&1 | grep -q -i busybox @@ -60,8 +71,10 @@ is_busybox() { # busybox timeout returns 128 + signal number (which is TERM by default) if is_busybox; then + # shellcheck disable=SC2034 EXIT_TIMEOUT=$((128 + 15)) else + # shellcheck disable=SC2034 EXIT_TIMEOUT=124 fi @@ -86,6 +99,12 @@ rm_cr() { tr -d '\r' } +if is_windows; then + normalize_path() { cygpath --mixed -- "$@"; } +else + normalize_path() { echo "$@"; } +fi + # Executes whatever is passed to it, checking that the resulting exit code is non-zero. must_fail() { if "$@"; then @@ -93,24 +112,84 @@ must_fail() { fi } +# Executes the passed command and checks two conditions: +# 1. it must exit successfully (with code 0) +# 2. its output (stdout + stderr) must include the substring from the first argument (ignoring case) +# usage: expect_msg 'expected message' command --with --args +expect_msg() { + message=$1 + shift + + if ! output=$("$@" 2>&1); then + bail 'expected 0 exit code' + fi + + if ! echo "$output" | grep -q -i "$message"; then + bail "expected message '$message'" + fi +} + +# The reverse of expect_msg. We cannot simply wrap expect_msg with must_fail +# because there should be a separate check for tinc exit code. +fail_on_msg() { + message=$1 + shift + + if ! output=$("$@" 2>&1); then + bail 'expected 0 exit code' + fi + + if echo "$output" | grep -q -i "$message"; then + bail "unexpected message '$message'" + fi +} + +# Like expect_msg, but the command must fail with a non-zero exit code. +# usage: must_fail_with_msg 'expected message' command --with --args +must_fail_with_msg() { + message=$1 + shift + + if output=$("$@" 2>&1); then + bail "expected a non-zero exit code" + fi + + if ! echo "$output" | grep -i -q "$message"; then + bail "expected message '$message'" + fi +} + +# Is the legacy protocol enabled? +with_legacy() { + tincd foo --version | grep -q legacy_protocol +} + +# Are we running with EUID 0? +is_root() { + test "$(id -u)" = 0 +} + +# Executes whatever is passed to it, checking that the resulting exit code is equal to the first argument. +expect_code() { + expected=$1 + shift + + code=0 + "$@" || code=$? + + if [ $code != "$expected" ]; then + bail "wrong exit code $code, expected $expected" + fi +} + # Runs its arguments with timeout(1) or gtimeout(1) if either are installed. # Usage: try_limit_time 10 command --with --args if type timeout >/dev/null; then - if is_busybox; then - # busybox does not support --foreground - try_limit_time() { - time=$1 - shift - timeout "$time" "$@" - } - else - # BSD and GNU timeout do not require special handling - try_limit_time() { - time=$1 - shift - timeout --foreground "$time" "$@" - } - fi + try_limit_time() { + time=$1 + shift + timeout "$time" "$@" + } else try_limit_time() { echo >&2 "timeout was not found, running without time limits!" @@ -195,6 +274,7 @@ require_nodes() { } peer_directory() { + peer=$1 case "$peer" in foo) echo "$DIR_FOO" ;; bar) echo "$DIR_BAR" ;; @@ -296,19 +376,26 @@ wait_script() { # group by enabling job control, but this results in weird behavior when # running tests in parallel on some interactive shells # (e.g. when /bin/sh is symlinked to dash). + fifo=$(mktemp) + rm -f "$fifo" + mkfifo "$fifo" + + # This weird thing is required to support old versions of ksh on NetBSD 8.2 and the like. + (tail -n +"$line" -f "$script_log" >"$fifo") & + new_line=$( try_limit_time 60 sh -c " - fifo=\$$.fifo - cleanup() { rm -f \$fifo; } - cleanup && trap cleanup EXIT - - mkfifo \$$.fifo - tail -n '+$line' -f '$script_log' >\$fifo & - grep -n -m '$count' '^$script,' <\$fifo - kill \$! + grep -n -m $count '^$script,' <'$fifo' " | awk -F: 'END { print $1 }' ) + # Try to stop the background tail, ignoring possible failure (some tails + # detect EOF, some don't, so it may have already exited), but do wait on + # it (which is required at least by old ksh). + kill $! || true + wait || true + rm -f "$fifo" + # Remember the next line number for future reference. We'll use it if # wait_script is called again with same $peer and $script. read -r "${line_var?}" </dev/null; then + echo >&2 "Cleanup hook found, calling..." + cleanup_hook + fi + stop_all_tincs # Ask nicely, then kill anything that's left. @@ -345,9 +437,32 @@ cleanup() { ) || true } +# If we're on a CI server, the test requires superuser privileges to run, and we're not +# currently a superuser, try running the test as one and fail if it doesn't work (the +# system must be configured to provide passwordless sudo for our user). +require_root() { + if is_root; then + return + fi + if is_ci; then + echo "root is required for test $SCRIPTNAME, but we're a regular user; elevating privileges..." + if ! command -v sudo 2>/dev/null; then + bail "please install sudo and configure passwordless auth for user $USER" + fi + if ! sudo --preserve-env --non-interactive true; then + bail "sudo is not allowed or requires a password for user $USER" + fi + exec sudo --preserve-env "$@" + else + # Avoid these kinds of surprises outside CI. Just skip the test. + echo "root is required for test $SCRIPTNAME, but we're a regular user; skipping" + exit $EXIT_SKIP_TEST + fi +} + # Generate path to current shell which can be used from Windows applications. if is_windows; then - MINGW_SHELL=$(cygpath --mixed -- "$SHELL") + MINGW_SHELL=$(normalize_path "$SHELL") fi # This was called from a tincd script. Skip executing commands with side effects. @@ -358,10 +473,9 @@ echo [STEP] Check for leftover tinc daemons and test directories # Cleanup leftovers from previous runs. stop_all_tincs -# On Windows this can actually fail. We don't want to suppress possible failure with -f. -if [ -d "$DIR_FOO" ]; then rm -r "$DIR_FOO"; fi -if [ -d "$DIR_BAR" ]; then rm -r "$DIR_BAR"; fi -if [ -d "$DIR_BAZ" ]; then rm -r "$DIR_BAZ"; fi +if [ -d "$DIR_FOO" ]; then rm -rf "$DIR_FOO"; fi +if [ -d "$DIR_BAR" ]; then rm -rf "$DIR_BAR"; fi +if [ -d "$DIR_BAZ" ]; then rm -rf "$DIR_BAZ"; fi # Register cleanup function so we don't have to call it everywhere # (and failed scripts do not leave stray tincd running).