1# Simple test harness infrastructure 2# 3# Copyright 2005 by Rob Landley 4 5# This file defines two main functions, "testcmd" and "optional". The 6# first performs a test, the second enables/disables tests based on 7# configuration options. 8 9# The following environment variables enable optional behavior in "testing": 10# DEBUG - Show every command run by test script. 11# VERBOSE - Print the diff -u of each failed test case. 12# If equal to "fail", stop after first failed test. 13# "nopass" to not show successful tests 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 "optional" function is used to skip certain tests (by setting the 32# environment variable SKIP), ala: 33# optional CFG_THINGY 34# 35# The "optional" function checks the environment variable "OPTIONFLAGS", 36# which is either empty (in which case it always clears SKIP) or 37# else contains a colon-separated list of features (in which case the function 38# clears SKIP if the flag was found, or sets it to 1 if the flag was not found). 39 40export FAILCOUNT=0 41export SKIP= 42 43# Helper functions 44 45# Check config to see if option is enabled, set SKIP if not. 46 47SHOWPASS=PASS 48SHOWFAIL=FAIL 49SHOWSKIP=SKIP 50 51if tty -s <&1 52then 53 SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")" 54 SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")" 55 SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")" 56fi 57 58optional() 59{ 60 option=`printf %s "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` 61 # Not set? 62 if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] 63 then 64 SKIP="" 65 return 66 fi 67 SKIP=1 68} 69 70skipnot() 71{ 72 if [ -z "$VERBOSE" ] 73 then 74 eval "$@" 2>/dev/null 75 else 76 eval "$@" 77 fi 78 [ $? -eq 0 ] || SKIPNEXT=1 79} 80 81toyonly() 82{ 83 IS_TOYBOX="$("$C" --version 2>/dev/null)" 84 [ "${IS_TOYBOX/toybox/}" == "$IS_TOYBOX" ] && SKIPNEXT=1 85 86 "$@" 87} 88 89wrong_args() 90{ 91 if [ $# -ne 5 ] 92 then 93 printf "%s\n" "Test $NAME has the wrong number of arguments ($# $*)" >&2 94 exit 95 fi 96} 97 98# Announce success 99do_pass() 100{ 101 [ "$VERBOSE" != "nopass" ] && printf "%s\n" "$SHOWPASS: $NAME" 102} 103 104# The testing function 105 106testing() 107{ 108 NAME="$CMDNAME $1" 109 wrong_args "$@" 110 111 [ -z "$1" ] && NAME=$2 112 113 [ -n "$DEBUG" ] && set -x 114 115 if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ] 116 then 117 [ ! -z "$VERBOSE" ] && printf "%s\n" "$SHOWSKIP: $NAME" 118 unset SKIPNEXT 119 return 0 120 fi 121 122 echo -ne "$3" > expected 123 [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input 124 echo -ne "$5" | ${EVAL:-eval --} "$2" > actual 125 RETVAL=$? 126 127 # Catch segfaults 128 [ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] && 129 echo "exited with signal (or returned $RETVAL)" >> actual 130 DIFF="$(diff -au${NOSPACE:+w} expected actual)" 131 if [ ! -z "$DIFF" ] 132 then 133 FAILCOUNT=$(($FAILCOUNT+1)) 134 printf "%s\n" "$SHOWFAIL: $NAME" 135 if [ -n "$VERBOSE" ] 136 then 137 [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input" 138 printf "%s\n" "echo -ne '$5' |$EVAL $2" 139 printf "%s\n" "$DIFF" 140 [ "$VERBOSE" == fail ] && exit 1 141 fi 142 else 143 [ "$VERBOSE" != "nopass" ] && printf "%s\n" "$SHOWPASS: $NAME" 144 fi 145 rm -f input expected actual 146 147 [ -n "$DEBUG" ] && set +x 148 149 return 0 150} 151 152testcmd() 153{ 154 wrong_args "$@" 155 156 X="$1" 157 [ -z "$X" ] && X="$CMDNAME $2" 158 testing "$X" "\"$C\" $2" "$3" "$4" "$5" 159} 160 161# Announce failure and handle fallout for txpect 162do_fail() 163{ 164 FAILCOUNT=$(($FAILCOUNT+1)) 165 printf "%s\n" "$SHOWFAIL: $NAME" 166 if [ ! -z "$CASE" ] 167 then 168 echo "Expected '$CASE'" 169 echo "Got '$A'" 170 fi 171 [ "$VERBOSE" == fail ] && exit 1 172} 173 174# txpect NAME COMMAND [I/O/E/Xstring]... 175# Run COMMAND and interact with it: send I strings to input, read O or E 176# strings from stdout or stderr (empty string is "read line of input here"), 177# X means close stdin/stdout/stderr and match return code (blank means nonzero) 178txpect() 179{ 180 # Run command with redirection through fifos 181 NAME="$1" 182 CASE= 183 184 if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ 185 then 186 do_fail 187 return 188 fi 189 eval "$2" <in-$$ >out-$$ 2>err-$$ & 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 ] 197 do 198 [ "$VERBOSE" == xpect ] && echo "$1" >&2 199 LEN=$((${#1}-1)) 200 CASE="$1" 201 A= 202 case ${1::1} in 203 204 # send input to child 205 I) echo -en "${1:1}" >&$IN || { do_fail;break;} ;; 206 207 # check output from child 208 [OE]) 209 [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" 210 O=$OUT 211 [ ${1::1} == 'E' ] && O=$ERR 212 A= 213 read -t2 $LARG A <&$O 214 [ "$VERBOSE" == xpect ] && echo "$A" >&2 215 if [ $LEN -eq 0 ] 216 then 217 [ -z "$A" ] && { do_fail;break;} 218 else 219 if [ "$A" != "${1:1}" ] 220 then 221 # Append the rest of the output if there is any. 222 read -t.1 B <&$O 223 A="$A$B" 224 read -t.1 -rN 9999 B<&$ERR 225 do_fail;break; 226 fi 227 fi 228 ;; 229 230 # close I/O and wait for exit 231 X) 232 exec {IN}<&- {OUT}<&- {ERR}<&- 233 wait 234 A=$? 235 if [ -z "$LEN" ] 236 then 237 [ $A -eq 0 ] && { do_fail;break;} # any error 238 else 239 [ $A != "${1:1}" ] && { do_fail;break;} # specific value 240 fi 241 ;; 242 *) do_fail; break ;; 243 esac 244 shift 245 done 246 # In case we already closed it 247 exec {IN}<&- {OUT}<&- {ERR}<&- 248 249 [ $# -eq 0 ] && do_pass 250} 251 252# Recursively grab an executable and all the libraries needed to run it. 253# Source paths beginning with / will be copied into destpath, otherwise 254# the file is assumed to already be there and only its library dependencies 255# are copied. 256 257mkchroot() 258{ 259 [ $# -lt 2 ] && return 260 261 echo -n . 262 263 dest=$1 264 shift 265 for i in "$@" 266 do 267 [ "${i:0:1}" == "/" ] || i=$(which $i) 268 [ -f "$dest/$i" ] && continue 269 if [ -e "$i" ] 270 then 271 d=`echo "$i" | grep -o '.*/'` && 272 mkdir -p "$dest/$d" && 273 cat "$i" > "$dest/$i" && 274 chmod +x "$dest/$i" 275 else 276 echo "Not found: $i" 277 fi 278 mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') 279 done 280} 281 282# Set up a chroot environment and run commands within it. 283# Needed commands listed on command line 284# Script fed to stdin. 285 286dochroot() 287{ 288 mkdir tmpdir4chroot 289 mount -t ramfs tmpdir4chroot tmpdir4chroot 290 mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} 291 cp -L testing.sh tmpdir4chroot 292 293 # Copy utilities from command line arguments 294 295 echo -n "Setup chroot" 296 mkchroot tmpdir4chroot $* 297 echo 298 299 mknod tmpdir4chroot/dev/tty c 5 0 300 mknod tmpdir4chroot/dev/null c 1 3 301 mknod tmpdir4chroot/dev/zero c 1 5 302 303 # Copy script from stdin 304 305 cat > tmpdir4chroot/test.sh 306 chmod +x tmpdir4chroot/test.sh 307 chroot tmpdir4chroot /test.sh 308 umount -l tmpdir4chroot 309 rmdir tmpdir4chroot 310} 311