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" > "$TESTDIR"/expected 136 [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input 137 echo -ne "$5" | ${EVAL:-eval --} "$2" > "$TESTDIR"/actual 138 RETVAL=$? 139 140 # Catch segfaults 141 [ $RETVAL -gt 128 ] && 142 echo "exited with signal (or returned $RETVAL)" >> actual 143 DIFF="$(cd "$TESTDIR"; diff -au${NOSPACE:+w} expected actual 2>&1)" 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 168utf8locale() 169{ 170 local i 171 172 for i in $LC_ALL C.UTF-8 en_US.UTF-8 173 do 174 [ "$(LC_ALL=$i locale charmap 2>/dev/null)" == UTF-8 ] && LC_ALL=$i && break 175 done 176} 177 178# Simple implementation of "expect" written in shell. 179 180# txpect NAME COMMAND [I/O/E/X/R[OE]string]... 181# Run COMMAND and interact with it: 182# I send string to input 183# OE read exactly this string from stdout or stderr (bare = read+discard line) 184# note: non-bare does not read \n unless you include it with O$'blah\n' 185# R prefix means O or E is regex match (read line, must contain substring) 186# X close stdin/stdout/stderr and match return code (blank means nonzero) 187txpect() 188{ 189 local NAME CASE VERBOSITY IN OUT ERR LEN PID A B X O 190 191 # Run command with redirection through fifos 192 NAME="$CMDNAME $1" 193 CASE= 194 VERBOSITY= 195 196 if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ 197 then 198 do_fail 199 return 200 fi 201 eval "$2" <in-$$ >out-$$ 2>err-$$ & 202 PID=$! 203 shift 2 204 : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$ 205 206 [ $? -ne 0 ] && { do_fail;return;} 207 208 # Loop through challenge/response pairs, with 2 second timeout 209 while [ $# -gt 0 -a -n "$PID" ] 210 do 211 VERBOSITY="$VERBOSITY"$'\n'"$1" LEN=$((${#1}-1)) CASE="$1" A= B= 212 213 verbose_has spam && echo "txpect $CASE" 214 case ${1::1} in 215 216 # send input to child 217 I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;; 218 219 R) LEN=0; B=1; ;& 220 # check output from child 221 [OE]) 222 [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" 223 O=$OUT A= 224 [ "${1:$B:1}" == 'E' ] && O=$ERR 225 read -t2 $LARG A <&$O 226 X=$? 227 verbose_has spam && echo "txgot $X '$A'" 228 VERBOSITY="$VERBOSITY"$'\n'"$A" 229 if [ $LEN -eq 0 ] 230 then 231 [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;} 232 else 233 if [ ${1::1} == 'R' ] && grep -q "${1:2}" <<< "$A"; then true 234 elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true 235 else 236 # Append the rest of the output if there is any. 237 read -t.1 B <&$O 238 A="$A$B" 239 read -t.1 -rN 9999 B<&$ERR 240 do_fail 241 break 242 fi 243 fi 244 ;; 245 246 # close I/O and wait for exit 247 X) 248 exec {IN}<&- 249 wait $PID 250 A=$? 251 exec {OUT}<&- {ERR}<&- 252 if [ "$LEN" -eq 0 ] 253 then 254 [ $A -eq 0 ] && { do_fail;break;} # any error 255 else 256 [ $A != "${1:1}" ] && { do_fail;break;} # specific value 257 fi 258 do_pass 259 return 260 ;; 261 *) do_fail; break ;; 262 esac 263 shift 264 done 265 # In case we already closed it 266 exec {IN}<&- {OUT}<&- {ERR}<&- 267 268 if [ $# -eq 0 ] 269 then 270 do_pass 271 else 272 ! verbose_has quiet && echo "$VERBOSITY" >&2 273 do_fail 274 fi 275} 276