serval-dna/testdefs.sh
Andrew Bettison 5e164607aa Expand output of rhizome add and import
Rename 'secret:' field to '.secret:' (non-manifest-fields start with '.'
using the same convention as rhizome list)

Add '.author' and 'BK' fields to "rhizome add"

Add 'BK' field to "rhizome import"

Fix 'rhizomeops' tests to assert no 'author' and 'BK' output fields from
"rhizome add" with no author

Fiz testdefs.sh and testdefs_rhizome.sh to support new output fields
2013-09-27 22:34:22 +09:30

735 lines
25 KiB
Bash

# Common definitions for all test suites.
# Copyright 2012 The Serval Project, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
shopt -s extglob
testdefs_sh=$(abspath "${BASH_SOURCE[0]}")
servald_source_root="${testdefs_sh%/*}"
servald_build_root="$servald_source_root"
servald_basename="servald"
servald_build_executable="$servald_build_root/$servald_basename"
export TFW_LOGDIR="${TFW_LOGDIR:-$servald_build_root/testlog}"
addr_localhost="127.0.0.1"
declare -a instance_stack=()
# Some useful regular expressions. These must work in grep(1) as basic
# expressions, and also in sed(1).
rexp_sid='[0-9a-fA-F]\{64\}'
rexp_did='[0-9+#]\{5,\}'
# Utility function for extracting information from the output of servald
# commands that return "key:value\n" pairs.
#
# extract_stdout_keyvalue_optional <varname> <key> [<delimiter>] <regular-expression>
#
# Examines the standard output of the last command executed using "execute" or
# any of its variants. If there is a line matching
# "<key><delimiter><regular-expression>\n" then assigns the part matched by
# <regular-expression> into the shell variable called <varname> and returns 0.
# Otherwise, leaves <varname> unchanged and returns 1.
#
# The default <delimimter> is ':'.
#
extract_stdout_keyvalue_optional() {
local _var="$1"
local _label="$2"
local _delim=':'
local _rexp="$3"
case $# in
3) ;;
4) _delim="$3"; _rexp="$4";;
*) error "invalid number of args";;
esac
local _label_re=$(escape_grep_basic "$_label")
local _delim_re=$(escape_grep_basic "$_delim")
local _line=$(replayStdout | $GREP "^$_label_re$_delim_re")
local _value=
local _return=1
if [ -n "$_line" ]; then
_value="${_line#*$_delim}"
_return=0
fi
[ -n "$_var" ] && eval $_var="\$_value"
return $_return
}
# Utility function for extracting information from the output of servald
# commands that return "key:value\n" pairs.
extract_stdout_keyvalue() {
local _label="$2"
assert --message="stdout of ($executed) contains valid '$_label:' line" --stdout extract_stdout_keyvalue_optional "$@"
}
# Utility function for creating servald fixtures:
# - set $servald variable (executable under test)
# - set the current instance to be "Z"
setup_servald() {
if ! [ -x "$servald_build_executable" ]; then
error "servald executable not present: $servald"
return 1
fi
export SERVALD_VAR="$TFWVAR/servald"
mkdir "$SERVALD_VAR"
servald="$SERVALD_VAR/$servald_basename" # The servald executable under test
cp -f "$servald_build_executable" "$servald"
unset SERVALD_OUTPUT_DELIMITER
unset SERVALD_SERVER_START_DELAY
unset SERVALD_SERVER_CHDIR
unset SERVALD_START_POST_SLEEP
unset SERVALD_LOG_FILE
unset SERVALD_KEYRING_PATH
unset SERVALD_KEYRING_READONLY
servald_instances_dir="$SERVALD_VAR/instance"
set_instance +Z
}
# Utility function for running servald and asserting no errors:
# - executes $servald with the given arguments
# - asserts that standard error contains no error messages
executeOk_servald() {
executeOk --core-backtrace --executable=$servald "$@"
assertStderrGrep --matches=0 --message="stderr of ($executed) contains no error messages" '^ERROR:'
}
# Utility function:
# - if the argument is a prefixed instance name "+X", then call set_instance
# "X" and return 0, otherwise leave the current instance unchanged and return 1
# Designed for use in functions that take an optional instance name as their
# first argument:
# func() {
# push_instance
# set_instance_fromarg "$1" && shift
# ...
# pop_instance
# }
# would be invoked as:
# func +A blah blah
# func +B wow wow
# func foo bar
set_instance_fromarg() {
case "$1" in
+[A-Z]) set_instance "$1"; return 0;;
esac
return 1
}
# Utility function:
# - push the current instance on the instance stack
push_instance() {
instance_stack+=("$instance_name")
}
# Utility function:
# - pop an instance off the instance stack
pop_instance() {
local n=${#instance_stack[*]}
[ $n -eq 0 ] && error "instance stack underflow"
let --n
unset instance_stack[$n]
}
# Utility function:
# - create a temporary directory to contain all per-instance test files
# - set SERVALINSTANCE_PATH environment variable to name a directory within
# the per-instance test directory (but do not create it)
# - set other environment variables to support other functions defined in this
# script
set_instance() {
case "$1" in
'')
error "missing instance name argument"
;;
+[A-Z])
instance_arg="${1}"
instance_name="${instance_arg#+}"
instance_number=$((36#$instance_name - 9))
local servald_binary_var=servald${instance_name}
if [ -z "${!servald_binary_var}" ]; then
servald="$SERVALD_VAR/$servald_basename"
else
servald="$SERVALD_VAR/${!servald_binary_var}"
fi
if ! [ -x "$servald" ]; then
error "servald executable not present: $servald"
return 1
fi
tfw_log "# set instance = $instance_name, number = $instance_number"
export instance_dir="${servald_instances_dir?:}/$instance_name"
mkdir -p "$instance_dir"
export SERVALINSTANCE_PATH="$instance_dir/servald"
instance_servald_log="$instance_dir/servald.log"
instance_servald_pidfile="$SERVALINSTANCE_PATH/servald.pid"
;;
*)
error "malformed instance name argument, must be in form +[A-Z]"
;;
esac
}
# Composition function:
# - invoke a command once in many instances
# - in "--all" mode (default), returns the count of commands that returned
# nonzero (ie, failure count); this returns zero only if all commands in all
# instances return zero, ie, is an AND relation on success; this is
# guaranteed to invoke the command in all instances (unless terminated
# by a failed assertion)
# - in "--any" mode, returns zero as soon as any command returns zero; ie, is
# an OR relation on success; N.B. this may not invoke the command in all
# instances
foreach_instance() {
mode=all
case "$1" in
--any) mode=any; shift;;
--all) mode=all; shift;;
esac
local -a instances=()
while [ $# -ne 0 ]; do
case "$1" in
+[A-Z]) instances+=("$1"); shift;;
*) break;;
esac
done
push_instance
local ret=0
local I
for I in ${instances[*]}; do
set_instance $I
if "$@"; then
case $mode in
any) break;;
esac
else
case $mode in
all) let ++ret;;
esac
fi
done
pop_instance
return $ret
}
# Composition function:
# - invoke a command once in every instance that contains a server pidfile
# - takes the same options as foreach_instance()
foreach_instance_with_pidfile() {
push_instance
local -a instances=()
if pushd "${servald_instances_dir?:}" >/dev/null; then
for name in *; do
set_instance "+$name"
get_servald_server_pidfile && instances+=("+$name")
done
popd >/dev/null
fi
pop_instance
foreach_instance "${instances[@]}" "$@"
}
# Utility function for setting up servald JNI fixtures:
# - check that libservald.so is present
# - set LD_LIBRARY_PATH so that libservald.so can be found
setup_servald_so() {
assert [ -r "$servald_build_root/libservald.so" ]
export LD_LIBRARY_PATH="$servald_build_root"
}
# Utility function for setting up a fixture with a servald server process:
# - start a servald server process
# - assert that the pidfile is created and correct
# - set $servald_pid to the PID of the started server process
# - assert that the reported PID is actually a running servald process
start_servald_server() {
push_instance
set_instance_fromarg "$1" && shift
# Start servald server
local -a before_pids
local -a after_pids
get_servald_pids before_pids
tfw_log "# before_pids=$before_pids"
executeOk --core-backtrace servald_start "$@"
extract_stdout_keyvalue start_instance_path instancepath '.*'
extract_stdout_keyvalue start_pid pid '[0-9]\+'
assert [ "$start_instance_path" = "$SERVALINSTANCE_PATH" ]
get_servald_pids after_pids
tfw_log "# after_pids=$after_pids"
assert_servald_server_pidfile servald_pid
# Assert that the servald pid file is present.
assert --message="servald pidfile was created" [ -s "$instance_servald_pidfile" ]
assert --message="servald pidfile contains a valid pid" --dump-on-fail="$instance_servald_log" kill -0 "$servald_pid"
assert --message="servald start command returned correct pid" [ "$start_pid" -eq "$servald_pid" ]
# Assert that there is at least one new servald process running.
local apid bpid
local new_pids=
local pidfile_running=false
for apid in ${after_pids[*]}; do
local isnew=true
for bpid in ${before_pids[*]}; do
if [ "$apid" -eq "$bpid" ]; then
isnew=false
break
fi
done
if [ "$apid" -eq "$servald_pid" ]; then
tfw_log "# started servald process: pid=$servald_pid"
new_pids="$new_pids $apid"
pidfile_running=true
elif $isnew; then
tfw_log "# unknown new servald process: pid=$apid"
new_pids="$new_pids $apid"
fi
done
eval LOG$instance_name=$instance_servald_log
assert --message="a new servald process is running" --dump-on-fail="$instance_servald_log" [ -n "$new_pids" ]
assert --message="servald pidfile process is running" --dump-on-fail="$instance_servald_log" $pidfile_running
assert --message="servald log file $instance_servald_log is present" [ -r "$instance_servald_log" ]
wait_until grep -q "Server started" "$instance_servald_log"
tfw_log "# Started servald server process $instance_name, pid=$servald_pid"
pop_instance
}
# Utility function:
# - invoke "servald start" command with given args and suitable environment
servald_start() {
rm -r -f "$instance_dir/cache"
mkdir "$instance_dir/cache" || error "mkdir failed"
SERVALD_SERVER_CHDIR="$instance_dir" SERVALD_LOG_FILE="$instance_servald_log" $servald start "$@"
}
# Utility function:
# - stop a servald server process instance in an orderly fashion
# - cat its log file into the test log
stop_servald_server() {
push_instance
set_instance_fromarg "$1" && shift
# Stop servald server
get_servald_server_pidfile servald_pid
local -a before_pids
local -a after_pids
get_servald_pids before_pids
tfw_log "# before_pids=$before_pids"
execute --core-backtrace $servald stop "$@"
extract_stdout_keyvalue stop_instance_path instancepath '.*'
assert [ "$stop_instance_path" = "$SERVALINSTANCE_PATH" ]
if [ -n "$servald_pid" ]; then
assertExitStatus '==' 0
extract_stdout_keyvalue stop_pid pid '[0-9]\+'
assert [ "$stop_pid" = "$servald_pid" ]
fi
tfw_log "# Stopped servald server process $instance_name, pid=${servald_pid:-unknown}"
get_servald_pids after_pids
tfw_log "# after_pids=$after_pids"
# Assert that the servald pid file is gone.
assert --message="servald pidfile was removed" [ ! -e "$instance_servald_pidfile" ]
# Assert that the servald process identified by the pidfile is no longer running.
local apid bpid
if [ -n "$servald_pid" ]; then
for apid in ${after_pids[*]}; do
assert --message="servald process still running" [ "$apid" -ne "$servald_pid" ]
done
fi
# Check there is at least one fewer servald processes running.
for bpid in ${before_pids[*]}; do
local isgone=true
for apid in ${after_pids[*]}; do
if [ "$apid" -eq "$bpid" ]; then
isgone=false
break
fi
done
if $isgone; then
tfw_log "# ended servald process: pid=$bpid"
fi
done
pop_instance
}
# Utility function:
# - cat a servald server log file and core dump information into the test log
report_servald_server() {
# Append the server log file to the test log.
[ -s "$instance_servald_log" ] && tfw_cat "$instance_servald_log"
# Append a core dump backtrace to the test log.
if [ -s "$instance_dir/core" ]; then
tfw_core_backtrace "$servald" "$instance_dir/core"
rm -f "$instance_dir/core"
fi
}
# Utility function:
# - test whether the pidfile for a given server instance exists and is valid
# - if it exists and is valid, set named variable to PID (and second named
# variable to path of pidfile) and return 0
# - otherwise return 1
get_servald_server_pidfile() {
local _pidvar="$1"
local _pidfilevar="$2"
push_instance
set_instance_fromarg "$1" && shift
local _pidfile="$instance_servald_pidfile"
pop_instance
[ -n "$_pidfilevar" ] && eval $_pidfilevar="\$_pidfile"
local _pid=$(cat "$_pidfile" 2>/dev/null)
case "$_pid" in
+([0-9]))
[ -n "$_pidvar" ] && eval $_pidvar="\$_pid"
return 0
;;
'')
if [ -e "$_pidfile" ]; then
tfw_log "# empty pidfile $_pidfile"
else
tfw_log "# missing pidfile $_pidfile"
fi
;;
*)
tfw_log "# invalid pidfile $_pidfile"
tfw_cat "$_pidfile"
;;
esac
return 1
}
# Assertion function:
# - asserts that the servald server pidfile exists and contains a valid PID
# - does NOT check whether a process with that PID exists or whether that
# process is a servald process
assert_servald_server_pidfile() {
assert get_servald_server_pidfile "$@"
}
# Assertion function:
# - assert that the given instance's server has the given status ('running' or 'stopped')
assert_servald_server_status() {
push_instance
set_instance_fromarg "$1" && shift
[ $# -eq 1 ] || error "invalid arguments"
executeOk_servald status
local status
extract_stdout_keyvalue status status '.*'
assert --message="instance +$instance_name servald server status is '$1'" [ "$status" = "$1" ]
pop_instance
}
# Assertion function:
# - asserts that all servald instances with a pidfile have a server in a given
# state
assert_status_all_servald_servers() {
foreach_instance_with_pidfile assert_servald_server_status "$@"
}
# Utility function for tearing down servald fixtures:
# - stop all servald server process instances in an orderly fashion
stop_all_servald_servers() {
foreach_instance_with_pidfile stop_servald_server
}
# Utility function for tearing down servald fixtures:
# - log a report of the execution of all servald server process instances
report_all_servald_servers() {
foreach_instance +{A..Z} report_servald_server
}
# Utility function for tearing down servald fixtures:
# - send a given signal to all running servald processes, identified by name
# - return 1 if no processes were present, 0 if any signal was sent
signal_all_servald_processes() {
local sig="$1"
local servald_pids
get_servald_pids servald_pids
local pid
local ret=1
for pid in $servald_pids; do
if kill -$sig "$pid"; then
tfw_log "# Sent SIG$sig to servald process pid=$pid"
ret=0
else
tfw_log "# servald process pid=$pid not running -- SIG$sig not sent"
fi
done
return $ret
}
# Utility function for tearing down servald fixtures:
# - wait while any servald processes remain
# - return 0 if no processes are present
# - 1 if the timeout elapses first
wait_all_servald_processes() {
local timeout="${1:-1000000}"
sleep $timeout &
sleep_pid=$!
while get_servald_pids; do
kill -0 $sleep_pid 2>/dev/null || return 1
sleep 0.1
done
kill -TERM $sleep_pid 2>/dev/null
return 0
}
# Utility function for tearing down servald fixtures:
# - terminate all running servald processes, identified by name, by sending
# two SIGHUPs 100ms apart, then another SIGHUP after 2 seconds, finally
# SIGKILL after 2 seconds
# - return 0 if no more processes are running, nonzero otherwise
kill_all_servald_processes() {
for delay in 0.1 2 2; do
signal_all_servald_processes HUP || return 0
wait_all_servald_processes $delay && return 0
done
signal_all_servald_processes KILL || return 0
return 1
}
# Utility function:
# - return the PIDs of all servald processes the current test is running, by
# assigning to the named array variable if given
# - return 0 if there are any servald processes running, 1 if not
get_servald_pids() {
local var="$1"
if [ -z "$servald" ]; then
error "\$servald is not set"
return 1
fi
local mypid=$$
# XXX The following line will not find any PIDs if there are spaces in "$servald".
local pids=$(ps -u$UID -o pid,args | $AWK -v mypid="$mypid" -v servald="$servald" '$1 != mypid && $2 == servald {print $1}')
[ -n "$var" ] && eval "$var=(\$pids)"
[ -n "$pids" ]
}
# Assertion function:
# - assert there are no existing servald server processes
assert_no_servald_processes() {
local pids
get_servald_pids pids
assert --message="no $servald_basename process(es) running" [ -z "$pids" ]
return 0
}
# Assertion function:
# - assert the given instance's servald server log contains no errors
assert_servald_server_no_errors() {
push_instance
set_instance_fromarg "$1" && shift
assertGrep --matches=0 --message="stderr of $servald_basename $instance_name contains no error messages" "$instance_servald_log" '^ERROR:'
pop_instance
}
# Assertion function:
# - assert that all instances of servald server logs contain no errors
assert_all_servald_servers_no_errors() {
push_instance
if pushd "${servald_instances_dir?:}" >/dev/null; then
for name in *; do
set_instance "+$name"
assertGrep --matches=0 --message="stderr of $servald_basename $instance_name contains no error messages" "$instance_servald_log" '^ERROR:'
done
popd >/dev/null
fi
pop_instance
}
# Utility function
#
# create_single_identity [--option]... [ DID [ Name ]]
#
# - create an identity in the current instance {I} by invoking the command:
# servald keyring add [--option]...
# - assign a phone number (DID) and name to the new identity, use defaults
# if not specified by arg1 and arg2
# - assert the new identity is the only one in this instance
# - set the SID{I} variable, eg SIDA, to the SID of the new identity
# - set the DID{I} variable, eg DIDA, to the phone number of the new identity
# - set the NAME{I} variable, eg NAMEA, to the name of the new identity
create_single_identity() {
local servald_options=()
while [ $# -gt 0 ]; do
case "$1" in
--*) servald_options+=("$1"); shift;;
*) break;;
esac
done
local sidvar=SID${instance_name}1
local didvar=DID${instance_name}1
local namevar=NAME${instance_name}1
eval "$didvar=\"\${1-\$((5550000 + \$instance_number))}\""
eval "$namevar=\"\${2-Agent \$instance_name Smith}\""
create_identities "${servald_options[@]}" 1
eval "SID$instance_name=\"\${!sidvar}\""
eval "DID$instance_name=\"\${!didvar}\""
eval "NAME$instance_name=\"\${!namevar}\""
sidvar=SID${instance_name}
didvar=DID${instance_name}
namevar=NAME${instance_name}
tfw_log "SID$instance_name=$(shellarg "${!sidvar}")"
tfw_log "DID$instance_name=$(shellarg "${!didvar}")"
tfw_log "NAME$instance_name=$(shellarg "${!namevar}")"
}
# Utility function:
#
# create_identities [--option]... N
#
# - create N identities in the current instance {I} using N consecutive
# invocations of: servald keyring add [--option]...
# - pass [args...] to the keyring add
# - if variables DID{I}{1..N} and/or NAME{I}{1..N} are already set, then use
# them to set the DIDs and names of each identity
# - assert that all SIDs are unique
# - assert that all SIDs appear in keyring list
# - set variables SID{I}{1..N} to SIDs of identities, eg, SIDA1, SIDA2...
# - set variables DID{I}{1..N} to DIDs of identities, eg, DIDA1, DIDA2...
# - set variables NAME{I}{1..N} to names of identities, eg, NAMEA1, NAMEA2...
create_identities() {
local servald_options=()
while [ $# -gt 0 ]; do
case "$1" in
--*) servald_options+=("$1"); shift;;
*) break;;
esac
done
local N="$1"
case "$N" in
+([0-9]));;
*) error "invalid arg1: $N";;
esac
shift
local i j
for ((i = 1; i <= N; ++i)); do
executeOk_servald keyring add "${servald_options[@]}"
assert [ -e "$SERVALINSTANCE_PATH/serval.keyring" ]
local sidvar=SID$instance_name$i
local didvar=DID$instance_name$i
local namevar=NAME$instance_name$i
extract_stdout_keyvalue $sidvar sid "$rexp_sid"
tfw_log "$sidvar=${!sidvar}"
# If the DID and/or NAME is already specified in the variables, then use
# them, otherwise extract the DID and NAME automatically generated by
# servald.
if [ -n "${!didvar}" -o -n "${!namevar}" ]; then
executeOk_servald keyring set did "${servald_options[@]}" "${!sidvar}" "${!didvar}" "${!namevar}"
eval "$didvar=\${!didvar}"
eval "$namevar=\${!namevar}"
tfw_log "$didvar=$(shellarg "${!didvar}")"
tfw_log "$namevar=$(shellarg "${!namevar}")"
else
extract_stdout_keyvalue_optional $didvar did "$rexp_did" && tfw_log "$didvar=$(shellarg "${!didvar}")"
extract_stdout_keyvalue_optional $namevar name ".*" && tfw_log "$namevar=$(shellarg "${!namevar}")"
fi
done
for ((i = 1; i <= N; ++i)); do
for ((j = 1; j <= N; ++j)); do
[ $i -ne $j ] && eval assert [ "\$SID$instance_name$i" != "\$SID$instance_name$j" ]
done
done
executeOk_servald keyring list "${servald_options[@]}"
assertStdoutLineCount '==' $N
for ((i = 1; i <= N; ++i)); do
local sidvar=SID$instance_name$i
local didvar=DID$instance_name$i
local namevar=NAME$instance_name$i
local re_name=$(escape_grep_basic "${!namevar}")
assertStdoutGrep --matches=1 "^${!sidvar}:${!didvar}:${re_name}\$"
done
}
# Utility function, to be overridden as needed:
# - set up the configuration immediately prior to starting a servald server process
# - called by start_servald_instances
configure_servald_server() {
:
}
# Utility function:
# - start a set of servald server processes running on a shared file interface
# and with its own private monitor and MDP abstract socket names
# - set variables DUMMYx to the full path name of shared dummy interface
# - set variables LOGx to the full path of server log file for instance x: LOGA,
# LOGB, etc,
# - wait for all instances to detect each other
# - assert that all instances are in each others' peer lists
start_servald_instances() {
local DUMMY=dummy
case "$1" in
dummy*) DUMMY="$1"; shift;;
esac
push_instance
tfw_log "# start servald instances DUMMY=$DUMMY $*"
local DUMMYNET="$SERVALD_VAR/$DUMMY"
>$DUMMYNET
local I
for I; do
set_instance $I
# These config settings can be overridden in a caller-supplied configure_servald_server().
# They are extremely useful for the majority of fixtures.
executeOk_servald config \
set interfaces.1.file "$DUMMYNET"
configure_servald_server
start_servald_server
eval DUMMY$instance_name="\$DUMMYNET"
done
# Now wait until they see each other.
foreach_instance "$@" \
wait_until --sleep=0.25 has_seen_instances "$@"
tfw_log "# dummynet file:" $(ls -l $DUMMYNET)
pop_instance
}
# Assertion function:
# - asserts that the current instance reports a peer list that contains all the
# SIDs of all the other instances
# - uses the SID{I}{1..N} variables set by create_instances()
assert_peers_are_instances() {
local I N
executeOk_servald id allpeers
for I; do
for ((N=1; 1; ++N)); do
local sidvar=SID${I#+}$N
[ -n "${!sidvar}" ] || break
assertStdoutGrep "${!sidvar}"
done
done
}
# Predicate function:
# - useful in combination with assert() and wait_until()
# - return true if the current instance has logged that it has seen all other instances via the
# selfannounce mechanism
has_seen_instances() {
local I N
executeOk_servald route print
for I; do
[ $I = $instance_arg ] && continue
for ((N=1; 1; ++N)); do
local sidvar=SID${I#+}$N
[ -n "${!sidvar}" ] || break
if ! grep "^${!sidvar}" $_tfw_tmp/stdout; then
return 1
fi
done
done
return 0
}
# Predicate function:
# - useful in combination with assert() and wait_until()
# - return true if all instances have logged that they have seen all other instances via the
# selfannounce mechanism
instances_see_each_other() {
foreach_instance "$@" has_seen_instances "$@"
}