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 - "all" continue after failed test 12# "fail" show diff and stop at first failed test 13# "nopass" don't show successful tests 14# "quiet" don't show diff -u for failures 15# "spam" show passing test command lines 16# 17# The "testcmd" function takes five arguments: 18# $1) Description to display when running command 19# $2) Command line arguments to command 20# $3) Expected result (on stdout) 21# $4) Data written to file "input" 22# $5) Data written to stdin 23# 24# The "testing" function is like testcmd but takes a complete command line 25# (I.E. you have to include the command name.) The variable $C is an absolute 26# path to the command being tested, which can bypass shell builtins. 27# 28# The exit value of testcmd is the exit value of the command it ran. 29# 30# The environment variable "FAILCOUNT" contains a cumulative total of the 31# number of failed tests. 32# 33# The "optional" function is used to skip certain tests (by setting the 34# environment variable SKIP), ala: 35# optional CFG_THINGY 36# 37# The "optional" function checks the environment variable "OPTIONFLAGS", 38# which is either empty (in which case it always clears SKIP) or 39# else contains a colon-separated list of features (in which case the function 40# clears SKIP if the flag was found, or sets it to 1 if the flag was not found). 41 42export FAILCOUNT=0 43export SKIP= 44 45# Helper functions 46 47# Check config to see if option is enabled, set SKIP if not. 48 49SHOWPASS=PASS 50SHOWFAIL=FAIL 51SHOWSKIP=SKIP 52 53if tty -s <&1 54then 55 SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")" 56 SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")" 57 SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")" 58fi 59 60optional() 61{ 62 option=`printf %s "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` 63 # Not set? 64 if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] 65 then 66 unset SKIP 67 return 68 fi 69 SKIP=1 70} 71 72verbose_has() 73{ 74 [ "${VERBOSE/$1/}" != "$VERBOSE" ] 75} 76 77skipnot() 78{ 79 if verbose_has quiet 80 then 81 eval "$@" 2>/dev/null 82 else 83 eval "$@" 84 fi 85 [ $? -eq 0 ] || SKIPNEXT=1 86} 87 88toyonly() 89{ 90 IS_TOYBOX="$("$C" --version 2>/dev/null)" 91 # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf 92 # happy, so we have at least two things to check for. 93 case "$IS_TOYBOX" in 94 toybox*) ;; 95 This\ is\ not\ GNU*) ;; 96 *) SKIPNEXT=1 ;; 97 esac 98 99 "$@" 100} 101 102wrong_args() 103{ 104 if [ $# -ne 5 ] 105 then 106 printf "%s\n" "Test $NAME has the wrong number of arguments ($# $*)" >&2 107 exit 108 fi 109} 110 111# Announce success 112do_pass() 113{ 114 ! verbose_has nopass && printf "%s\n" "$SHOWPASS: $NAME" 115} 116 117# The testing function 118 119testing() 120{ 121 NAME="$CMDNAME $1" 122 wrong_args "$@" 123 124 [ -z "$1" ] && NAME=$2 125 126 [ -n "$DEBUG" ] && set -x 127 128 if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ] 129 then 130 verbose_has quiet && printf "%s\n" "$SHOWSKIP: $NAME" 131 unset SKIPNEXT 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 ] && [ $RETVAL -lt 255 ] && 142 echo "exited with signal (or returned $RETVAL)" >> actual 143 DIFF="$(diff -au${NOSPACE:+w} expected actual)" 144 if [ -n "$DIFF" ] 145 then 146 FAILCOUNT=$(($FAILCOUNT+1)) 147 printf "%s\n" "$SHOWFAIL: $NAME" 148 else 149 ! verbose_has nopass && printf "%s\n" "$SHOWPASS: $NAME" 150 fi 151 if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; } 152 then 153 [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input" 154 printf "%s\n" "echo -ne '$5' |$EVAL $2" 155 [ -n "$DIFF" ] && printf "%s\n" "$DIFF" 156 fi 157 158 [ -n "$DIFF" ] && ! verbose_has all && exit 1 159 rm -f input expected actual 160 161 [ -n "$DEBUG" ] && set +x 162 163 return 0 164} 165 166testcmd() 167{ 168 wrong_args "$@" 169 170 X="$1" 171 [ -z "$X" ] && X="$CMDNAME $2" 172 testing "$X" "\"$C\" $2" "$3" "$4" "$5" 173} 174 175# Announce failure and handle fallout for txpect 176do_fail() 177{ 178 FAILCOUNT=$(($FAILCOUNT+1)) 179 printf "%s\n" "$SHOWFAIL: $NAME" 180 if [ ! -z "$CASE" ] 181 then 182 echo "Expected '$CASE'" 183 echo "Got '$A'" 184 fi 185 ! verbose_has all && exit 1 186} 187 188# txpect NAME COMMAND [I/O/E/Xstring]... 189# Run COMMAND and interact with it: send I strings to input, read O or E 190# strings from stdout or stderr (empty string is "read line of input here"), 191# X means close stdin/stdout/stderr and match return code (blank means nonzero) 192txpect() 193{ 194 local NAME CASE VERBOSITY LEN A B X O 195 196 # Run command with redirection through fifos 197 NAME="$CMDNAME $1" 198 CASE= 199 VERBOSITY= 200 201 if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ 202 then 203 do_fail 204 return 205 fi 206 eval "$2" <in-$$ >out-$$ 2>err-$$ & 207 shift 2 208 : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$ 209 210 [ $? -ne 0 ] && { do_fail;return;} 211 212 # Loop through challenge/response pairs, with 2 second timeout 213 while [ $# -gt 0 ] 214 do 215 VERBOSITY="$VERBOSITY"$'\n'"$1" LEN=$((${#1}-1)) CASE="$1" A= B= 216 217 verbose_has spam && echo "txpect $CASE" 218 case ${1::1} in 219 220 # send input to child 221 I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;; 222 223 R) LEN=0; B=1; ;& 224 # check output from child 225 [OE]) 226 [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" 227 O=$OUT A= 228 [ "${1:$B:1}" == 'E' ] && O=$ERR 229 read -t2 $LARG A <&$O 230 X=$? 231 verbose_has spam && echo "txgot $X '$A'" 232 VERBOSITY="$VERBOSITY"$'\n'"$A" 233 if [ $LEN -eq 0 ] 234 then 235 [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;} 236 else 237 if [ ${1::1} == 'R' ] && [[ "$A" =~ "${1:2}" ]]; then true 238 elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true 239 else 240 # Append the rest of the output if there is any. 241 read -t.1 B <&$O 242 A="$A$B" 243 read -t.1 -rN 9999 B<&$ERR 244 do_fail;break; 245 fi 246 fi 247 ;; 248 249 # close I/O and wait for exit 250 X) 251 exec {IN}<&- {OUT}<&- {ERR}<&- 252 wait 253 A=$? 254 if [ -z "$LEN" ] 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 ;; 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 fi 274} 275 276# Recursively grab an executable and all the libraries needed to run it. 277# Source paths beginning with / will be copied into destpath, otherwise 278# the file is assumed to already be there and only its library dependencies 279# are copied. 280 281mkchroot() 282{ 283 [ $# -lt 2 ] && return 284 285 echo -n . 286 287 dest=$1 288 shift 289 for i in "$@" 290 do 291 [ "${i:0:1}" == "/" ] || i=$(which $i) 292 [ -f "$dest/$i" ] && continue 293 if [ -e "$i" ] 294 then 295 d=`echo "$i" | grep -o '.*/'` && 296 mkdir -p "$dest/$d" && 297 cat "$i" > "$dest/$i" && 298 chmod +x "$dest/$i" 299 else 300 echo "Not found: $i" 301 fi 302 mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') 303 done 304} 305 306# Set up a chroot environment and run commands within it. 307# Needed commands listed on command line 308# Script fed to stdin. 309 310dochroot() 311{ 312 mkdir tmpdir4chroot 313 mount -t ramfs tmpdir4chroot tmpdir4chroot 314 mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} 315 cp -L testing.sh tmpdir4chroot 316 317 # Copy utilities from command line arguments 318 319 echo -n "Setup chroot" 320 mkchroot tmpdir4chroot $* 321 echo 322 323 mknod tmpdir4chroot/dev/tty c 5 0 324 mknod tmpdir4chroot/dev/null c 1 3 325 mknod tmpdir4chroot/dev/zero c 1 5 326 327 # Copy script from stdin 328 329 cat > tmpdir4chroot/test.sh 330 chmod +x tmpdir4chroot/test.sh 331 chroot tmpdir4chroot /test.sh 332 umount -l tmpdir4chroot 333 rmdir tmpdir4chroot 334} 335