Restore libgcrypt support.
[tinc] / test / testlib.sh.in
index 185aec0..c63d27d 100644 (file)
@@ -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?}" <<EOF
@@ -327,6 +414,11 @@ cleanup() {
   (
     set +ex
 
+    if command -v cleanup_hook 2>/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).