# Simple test harness infrastructure # # Copyright 2005 by Rob Landley # This file defines two main functions, "testcmd" and "optional". The # first performs a test, the second enables/disables tests based on # configuration options. # The following environment variables enable optional behavior in "testing": # DEBUG - Show every command run by test script. # VERBOSE - "all" continue after failed test # "quiet" like all but just print FAIL (no diff -u). # "nopass" don't show successful tests # # The "testcmd" function takes five arguments: # $1) Description to display when running command # $2) Command line arguments to command # $3) Expected result (on stdout) # $4) Data written to file "input" # $5) Data written to stdin # # The "testing" function is like testcmd but takes a complete command line # (I.E. you have to include the command name.) The variable $C is an absolute # path to the command being tested, which can bypass shell builtins. # # The exit value of testcmd is the exit value of the command it ran. # # The environment variable "FAILCOUNT" contains a cumulative total of the # number of failed tests. # # The "optional" function is used to skip certain tests (by setting the # environment variable SKIP), ala: # optional CFG_THINGY # # The "optional" function checks the environment variable "OPTIONFLAGS", # which is either empty (in which case it always clears SKIP) or # else contains a colon-separated list of features (in which case the function # clears SKIP if the flag was found, or sets it to 1 if the flag was not found). export FAILCOUNT=0 export SKIP= # Helper functions # Check config to see if option is enabled, set SKIP if not. SHOWPASS=PASS SHOWFAIL=FAIL SHOWSKIP=SKIP if tty -s <&1 then SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")" SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")" SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")" fi optional() { option=`printf %s "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` # Not set? if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] then unset SKIP return fi SKIP=1 } skipnot() { if [ "$VERBOSE" == quiet ] then eval "$@" 2>/dev/null else eval "$@" fi [ $? -eq 0 ] || SKIPNEXT=1 } toyonly() { IS_TOYBOX="$("$C" --version 2>/dev/null)" # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf # happy, so we have at least two things to check for. case "$IS_TOYBOX" in toybox*) ;; This\ is\ not\ GNU*) ;; *) SKIPNEXT=1 ;; esac "$@" } wrong_args() { if [ $# -ne 5 ] then printf "%s\n" "Test $NAME has the wrong number of arguments ($# $*)" >&2 exit fi } # Announce success do_pass() { [ "$VERBOSE" != "nopass" ] && printf "%s\n" "$SHOWPASS: $NAME" } # The testing function testing() { NAME="$CMDNAME $1" wrong_args "$@" [ -z "$1" ] && NAME=$2 [ -n "$DEBUG" ] && set -x if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ] then [ "$VERBOSE" != quiet ] && printf "%s\n" "$SHOWSKIP: $NAME" unset SKIPNEXT return 0 fi echo -ne "$3" > expected [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input echo -ne "$5" | ${EVAL:-eval --} "$2" > actual RETVAL=$? # Catch segfaults [ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] && echo "exited with signal (or returned $RETVAL)" >> actual DIFF="$(diff -au${NOSPACE:+w} expected actual)" if [ ! -z "$DIFF" ] then FAILCOUNT=$(($FAILCOUNT+1)) printf "%s\n" "$SHOWFAIL: $NAME" if [ "$VERBOSE" != quiet ] then [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input" printf "%s\n" "echo -ne '$5' |$EVAL $2" printf "%s\n" "$DIFF" [ "$VERBOSE" != all ] && exit 1 fi else [ "$VERBOSE" != "nopass" ] && printf "%s\n" "$SHOWPASS: $NAME" fi rm -f input expected actual [ -n "$DEBUG" ] && set +x return 0 } testcmd() { wrong_args "$@" X="$1" [ -z "$X" ] && X="$CMDNAME $2" testing "$X" "\"$C\" $2" "$3" "$4" "$5" } # Announce failure and handle fallout for txpect do_fail() { FAILCOUNT=$(($FAILCOUNT+1)) printf "%s\n" "$SHOWFAIL: $NAME" if [ ! -z "$CASE" ] then echo "Expected '$CASE'" echo "Got '$A'" fi [ "$VERBOSE" != all ] && [ "$VERBOSE" != quiet ] && exit 1 } # txpect NAME COMMAND [I/O/E/Xstring]... # Run COMMAND and interact with it: send I strings to input, read O or E # strings from stdout or stderr (empty string is "read line of input here"), # X means close stdin/stdout/stderr and match return code (blank means nonzero) txpect() { local NAME CASE VERBOSITY LEN A B # Run command with redirection through fifos NAME="$CMDNAME $1" CASE= VERBOSITY= if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ then do_fail return fi eval "$2" out-$$ 2>err-$$ & shift 2 : {IN}>in-$$ {OUT}&$IN || { do_fail;break;} ;; R) LEN=0; B=1; ;& # check output from child [OE]) [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" O=$OUT [ "${1:$B:1}" == 'E' ] && O=$ERR A= read -t2 $LARG A <&$O VERBOSITY="$VERBOSITY"$'\n'"$A" if [ $LEN -eq 0 ] then [ -z "$A" ] && { do_fail;break;} else if [ ${1::1} == 'R' ] && [[ "$A" =~ "${1:2}" ]]; then true elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true else # Append the rest of the output if there is any. read -t.1 B <&$O A="$A$B" read -t.1 -rN 9999 B<&$ERR do_fail;break; fi fi ;; # close I/O and wait for exit X) exec {IN}<&- {OUT}<&- {ERR}<&- wait A=$? if [ -z "$LEN" ] then [ $A -eq 0 ] && { do_fail;break;} # any error else [ $A != "${1:1}" ] && { do_fail;break;} # specific value fi ;; *) do_fail; break ;; esac shift done # In case we already closed it exec {IN}<&- {OUT}<&- {ERR}<&- if [ $# -eq 0 ] then do_pass else [ "$VERBOSE" != quiet ] && echo "$VERBOSITY" >&2 fi } # Recursively grab an executable and all the libraries needed to run it. # Source paths beginning with / will be copied into destpath, otherwise # the file is assumed to already be there and only its library dependencies # are copied. mkchroot() { [ $# -lt 2 ] && return echo -n . dest=$1 shift for i in "$@" do [ "${i:0:1}" == "/" ] || i=$(which $i) [ -f "$dest/$i" ] && continue if [ -e "$i" ] then d=`echo "$i" | grep -o '.*/'` && mkdir -p "$dest/$d" && cat "$i" > "$dest/$i" && chmod +x "$dest/$i" else echo "Not found: $i" fi mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') done } # Set up a chroot environment and run commands within it. # Needed commands listed on command line # Script fed to stdin. dochroot() { mkdir tmpdir4chroot mount -t ramfs tmpdir4chroot tmpdir4chroot mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} cp -L testing.sh tmpdir4chroot # Copy utilities from command line arguments echo -n "Setup chroot" mkchroot tmpdir4chroot $* echo mknod tmpdir4chroot/dev/tty c 5 0 mknod tmpdir4chroot/dev/null c 1 3 mknod tmpdir4chroot/dev/zero c 1 5 # Copy script from stdin cat > tmpdir4chroot/test.sh chmod +x tmpdir4chroot/test.sh chroot tmpdir4chroot /test.sh umount -l tmpdir4chroot rmdir tmpdir4chroot }