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 [ -z "$TEST_HOST" ] && IS_TOYBOX=toybox 106 : ${IS_TOYBOX:=$("$C" --version 2>/dev/null | grep -o toybox)} \ 107 ${IS_TOYBOX:="$(basename $(readlink -f "$C"))"} 108 # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf 109 # happy, so we have at least two things to check for. 110 case "$IS_TOYBOX" in 111 toybox*) ;; 112 This\ is\ not\ GNU*) ;; 113 *) [ $SKIP -eq 0 ] && ((++SKIP)) ;; 114 esac 115 116 "$@" 117} 118 119# Takes five arguments: "name" "command" "result" "infile" "stdin" 120testing() 121{ 122 wrong_args "$@" 123 124 [ -z "$1" ] && NAME="$2" || NAME="$1" 125 [ "${NAME#$CMDNAME }" == "$NAME" ] && NAME="$CMDNAME $1" 126 127 [ -n "$DEBUG" ] && set -x 128 129 if [ "$SKIP" -gt 0 ] 130 then 131 verbose_has quiet || printf "%s\n" "$SHOWSKIP: $NAME" 132 ((--SKIP)) 133 134 return 0 135 fi 136 137 echo -ne "$3" > "$TESTDIR"/expected 138 [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input 139 echo -ne "$5" | ${EVAL:-eval --} "$2" > "$TESTDIR"/actual 140 RETVAL=$? 141 142 # Catch segfaults 143 [ $RETVAL -gt 128 ] && 144 echo "exited with signal (or returned $RETVAL)" >> actual 145 DIFF="$(cd "$TESTDIR"; diff -au${NOSPACE:+w} expected actual 2>&1)" 146 [ -z "$DIFF" ] && do_pass || VERBOSE=all do_fail 147 if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; } 148 then 149 [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input" 150 printf "%s\n" "echo -ne '$5' |$EVAL ${2@Q}" 151 [ -n "$DIFF" ] && printf "%s\n" "$DIFF" 152 fi 153 154 [ -n "$DIFF" ] && ! verbose_has all && exit 1 155 rm -f input ../expected ../actual 156 157 [ -n "$DEBUG" ] && set +x 158 159 return 0 160} 161 162# Wrapper for "testing", adds command name being tested to start of command line 163testcmd() 164{ 165 wrong_args "$@" 166 167 testing "${1:-$CMDNAME $2}" "\"$C\" $2" "$3" "$4" "$5" 168} 169 170utf8locale() 171{ 172 local i 173 174 for i in $LC_ALL C.UTF-8 en_US.UTF-8 175 do 176 [ "$(LC_ALL=$i locale charmap 2>/dev/null)" == UTF-8 ] && LC_ALL=$i && break 177 done 178} 179 180# Simple implementation of "expect" written in shell. 181 182# txpect NAME COMMAND [I/O/E/X/R[OE]string]... 183# Run COMMAND and interact with it: 184# I send string to input 185# OE read exactly this string from stdout or stderr (bare = read+discard line) 186# note: non-bare does not read \n unless you include it with O$'blah\n' 187# R prefix means O or E is regex match (read line, must contain substring) 188# X close stdin/stdout/stderr and match return code (blank means nonzero) 189txpect() 190{ 191 local NAME CASE VERBOSITY IN OUT ERR LEN PID A B X O 192 193 # Run command with redirection through fifos 194 NAME="$CMDNAME $1" 195 CASE= 196 VERBOSITY= 197 198 if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ 199 then 200 do_fail 201 return 202 fi 203 eval "$2" <in-$$ >out-$$ 2>err-$$ & 204 PID=$! 205 shift 2 206 : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$ 207 208 [ $? -ne 0 ] && { do_fail;return;} 209 210 # Loop through challenge/response pairs, with 2 second timeout 211 while [ $# -gt 0 -a -n "$PID" ] 212 do 213 VERBOSITY="$VERBOSITY"$'\n'"$1" LEN=$((${#1}-1)) CASE="$1" A= B= 214 215 verbose_has spam && echo "txpect $CASE" 216 case ${1::1} in 217 218 # send input to child 219 I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;; 220 221 R) LEN=0; B=1; ;& 222 # check output from child 223 [OE]) 224 [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" 225 O=$OUT A= 226 [ "${1:$B:1}" == 'E' ] && O=$ERR 227 read -t2 $LARG A <&$O 228 X=$? 229 verbose_has spam && echo "txgot $X '$A'" 230 VERBOSITY="$VERBOSITY"$'\n'"$A" 231 if [ $LEN -eq 0 ] 232 then 233 [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;} 234 else 235 if [ ${1::1} == 'R' ] && grep -q "${1:2}" <<< "$A"; then true 236 elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true 237 else 238 # Append the rest of the output if there is any. 239 read -t.1 B <&$O 240 A="$A$B" 241 read -t.1 -rN 9999 B<&$ERR 242 do_fail 243 break 244 fi 245 fi 246 ;; 247 248 # close I/O and wait for exit 249 X) 250 exec {IN}<&- 251 wait $PID 252 A=$? 253 exec {OUT}<&- {ERR}<&- 254 if [ "$LEN" -eq 0 ] 255 then 256 [ $A -eq 0 ] && { do_fail;break;} # any error 257 else 258 [ $A != "${1:1}" ] && { do_fail;break;} # specific value 259 fi 260 do_pass 261 return 262 ;; 263 *) do_fail; break ;; 264 esac 265 shift 266 done 267 # In case we already closed it 268 exec {IN}<&- {OUT}<&- {ERR}<&- 269 270 if [ $# -eq 0 ] 271 then 272 do_pass 273 else 274 ! verbose_has quiet && echo "$VERBOSITY" >&2 275 do_fail 276 fi 277} 278