1# Simple test harness infrastructure 2# 3# Copyright 2005 by Rob Landley 4 5# This file defines three main functions: "testing", "testcmd", and "txpect". 6 7# The following environment variables enable optional behavior in "testing": 8# DEBUG - Show every command run by test script. 9# VERBOSE - "all" continue after failed test 10# "fail" show diff and stop at first failed test 11# "nopass" don't show successful tests 12# "quiet" don't show diff -u for failures 13# "spam" show passing test command lines 14# 15# The "testcmd" function takes five arguments: 16# $1) Description to display when running command 17# $2) Command line arguments to command 18# $3) Expected result (on stdout) 19# $4) Data written to file "input" 20# $5) Data written to stdin 21# 22# The "testing" function is like testcmd but takes a complete command line 23# (I.E. you have to include the command name.) The variable $C is an absolute 24# path to the command being tested, which can bypass shell builtins. 25# 26# The exit value of testcmd is the exit value of the command it ran. 27# 28# The environment variable "FAILCOUNT" contains a cumulative total of the 29# number of failed tests. 30# 31# The environment variable "SKIP" says how many upcoming tests to skip, 32# defaulting to 0 and counting down when set to a higher number. 33# 34# Function "optional" enables/disables tests based on configuration options. 35 36export FAILCOUNT=0 SKIP=0 37: ${SHOWPASS:=PASS} ${SHOWFAIL:=FAIL} ${SHOWSKIP:=SKIP} 38if tty -s <&1 39then 40 SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")" 41 SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")" 42 SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")" 43fi 44 45# Helper functions 46 47# Check if VERBOSE= contains a given string. (This allows combining.) 48verbose_has() 49{ 50 [ "${VERBOSE/$1/}" != "$VERBOSE" ] 51} 52 53wrong_args() 54{ 55 if [ $# -ne 5 ] 56 then 57 printf "%s\n" "Test $NAME has the wrong number of arguments ($# $*)" >&2 58 exit 59 fi 60} 61 62# Announce success 63do_pass() 64{ 65 verbose_has nopass || printf "%s\n" "$SHOWPASS: $NAME" 66} 67 68# Announce failure and handle fallout for txpect 69do_fail() 70{ 71 FAILCOUNT=$(($FAILCOUNT+1)) 72 printf "%s\n" "$SHOWFAIL: $NAME" 73 if [ ! -z "$CASE" ] 74 then 75 echo "Expected '$CASE'" 76 echo "Got '$A'" 77 fi 78 ! verbose_has all && exit 1 79} 80 81# Functions test files call directly 82 83# Set SKIP high if option not enabled in $OPTIONFLAGS (unless OPTIONFLAGS blank) 84optional() 85{ 86 [ -n "$OPTIONFLAGS" ] && [ "$OPTIONFLAGS" == "${OPTIONFLAGS/:$1:/}" ] && 87 SKIP=99999 || SKIP=0 88} 89 90# Evalute command line and skip next test when false 91skipnot() 92{ 93 if verbose_has quiet 94 then 95 eval "$@" >/dev/null 2>&1 96 else 97 eval "$@" 98 fi 99 [ $? -eq 0 ] || { ((++SKIP)); return 1; } 100} 101 102# Skip this test (rest of command line) when not running toybox. 103toyonly() 104{ 105 IS_TOYBOX="$("$C" --version 2>/dev/null)" 106 # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf 107 # happy, so we have at least two things to check for. 108 case "$IS_TOYBOX" in 109 toybox*) ;; 110 This\ is\ not\ GNU*) ;; 111 *) [ $SKIP -eq 0 ] && ((++SKIP)) ;; 112 esac 113 114 "$@" 115} 116 117# Takes five arguments: "name" "command" "result" "infile" "stdin" 118testing() 119{ 120 wrong_args "$@" 121 122 [ -z "$1" ] && NAME="$2" || NAME="$1" 123 [ "${NAME#$CMDNAME }" == "$NAME" ] && NAME="$CMDNAME $1" 124 125 [ -n "$DEBUG" ] && set -x 126 127 if [ "$SKIP" -gt 0 ] 128 then 129 verbose_has quiet || printf "%s\n" "$SHOWSKIP: $NAME" 130 ((--SKIP)) 131 132 return 0 133 fi 134 135 echo -ne "$3" > ../expected 136 [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input 137 echo -ne "$5" | ${EVAL:-eval --} "$2" > ../actual 138 RETVAL=$? 139 140 # Catch segfaults 141 [ $RETVAL -gt 128 ] && 142 echo "exited with signal (or returned $RETVAL)" >> actual 143 DIFF="$(cd ..; diff -au${NOSPACE:+w} expected actual)" 144 [ -z "$DIFF" ] && do_pass || VERBOSE=all do_fail 145 if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; } 146 then 147 [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input" 148 printf "%s\n" "echo -ne '$5' |$EVAL $2" 149 [ -n "$DIFF" ] && printf "%s\n" "$DIFF" 150 fi 151 152 [ -n "$DIFF" ] && ! verbose_has all && exit 1 153 rm -f input ../expected ../actual 154 155 [ -n "$DEBUG" ] && set +x 156 157 return 0 158} 159 160# Wrapper for "testing", adds command name being tested to start of command line 161testcmd() 162{ 163 wrong_args "$@" 164 165 testing "${1:-$CMDNAME $2}" "\"$C\" $2" "$3" "$4" "$5" 166} 167 168# Simple implementation of "expect" written in shell. 169 170# txpect NAME COMMAND [I/O/E/Xstring]... 171# Run COMMAND and interact with it: send I strings to input, read O or E 172# strings from stdout or stderr (empty string is "read line of input here"), 173# X means close stdin/stdout/stderr and match return code (blank means nonzero) 174txpect() 175{ 176 local NAME CASE VERBOSITY LEN PID A B X O 177 178 # Run command with redirection through fifos 179 NAME="$CMDNAME $1" 180 CASE= 181 VERBOSITY= 182 183 if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ 184 then 185 do_fail 186 return 187 fi 188 eval "$2" <in-$$ >out-$$ 2>err-$$ & 189 PID=$! 190 shift 2 191 : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$ 192 193 [ $? -ne 0 ] && { do_fail;return;} 194 195 # Loop through challenge/response pairs, with 2 second timeout 196 while [ $# -gt 0 -a -n "$PID" ] 197 do 198 VERBOSITY="$VERBOSITY"$'\n'"$1" LEN=$((${#1}-1)) CASE="$1" A= B= 199 200 verbose_has spam && echo "txpect $CASE" 201 case ${1::1} in 202 203 # send input to child 204 I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;; 205 206 R) LEN=0; B=1; ;& 207 # check output from child 208 [OE]) 209 [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" 210 O=$OUT A= 211 [ "${1:$B:1}" == 'E' ] && O=$ERR 212 read -t2 $LARG A <&$O 213 X=$? 214 verbose_has spam && echo "txgot $X '$A'" 215 VERBOSITY="$VERBOSITY"$'\n'"$A" 216 if [ $LEN -eq 0 ] 217 then 218 [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;} 219 else 220 if [ ${1::1} == 'R' ] && grep -q "${1:2}" <<< "$A"; then true 221 elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true 222 else 223 # Append the rest of the output if there is any. 224 read -t.1 B <&$O 225 A="$A$B" 226 read -t.1 -rN 9999 B<&$ERR 227 do_fail 228 break 229 fi 230 fi 231 ;; 232 233 # close I/O and wait for exit 234 X) 235 exec {IN}<&- 236 wait $PID 237 A=$? 238 exec {OUT}<&- {ERR}<&- 239 if [ -z "$LEN" ] 240 then 241 [ $A -eq 0 ] && { do_fail;break;} # any error 242 else 243 [ $A != "${1:1}" ] && { do_fail;break;} # specific value 244 fi 245 do_pass 246 return 247 ;; 248 *) do_fail; break ;; 249 esac 250 shift 251 done 252 # In case we already closed it 253 exec {IN}<&- {OUT}<&- {ERR}<&- 254 255 if [ $# -eq 0 ] 256 then 257 do_pass 258 else 259 ! verbose_has quiet && echo "$VERBOSITY" >&2 260 do_fail 261 fi 262} 263