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# Announce failure and handle fallout for txpect 118do_fail() 119{ 120 FAILCOUNT=$(($FAILCOUNT+1)) 121 printf "%s\n" "$SHOWFAIL: $NAME" 122 if [ ! -z "$CASE" ] 123 then 124 echo "Expected '$CASE'" 125 echo "Got '$A'" 126 fi 127 ! verbose_has all && exit 1 128} 129 130# The testing function 131 132testing() 133{ 134 NAME="$CMDNAME $1" 135 wrong_args "$@" 136 137 [ -z "$1" ] && NAME=$2 138 139 [ -n "$DEBUG" ] && set -x 140 141 if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ] 142 then 143 verbose_has quiet && printf "%s\n" "$SHOWSKIP: $NAME" 144 unset SKIPNEXT 145 return 0 146 fi 147 148 echo -ne "$3" > expected 149 [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input 150 echo -ne "$5" | ${EVAL:-eval --} "$2" > actual 151 RETVAL=$? 152 153 # Catch segfaults 154 [ $RETVAL -gt 128 ] && 155 echo "exited with signal (or returned $RETVAL)" >> actual 156 DIFF="$(diff -au${NOSPACE:+w} expected actual)" 157 [ -z "$DIFF" ] && do_pass || VERBOSE=all do_fail 158 if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; } 159 then 160 [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input" 161 printf "%s\n" "echo -ne '$5' |$EVAL $2" 162 [ -n "$DIFF" ] && printf "%s\n" "$DIFF" 163 fi 164 165 [ -n "$DIFF" ] && ! verbose_has all && exit 1 166 rm -f input expected actual 167 168 [ -n "$DEBUG" ] && set +x 169 170 return 0 171} 172 173testcmd() 174{ 175 wrong_args "$@" 176 177 X="$1" 178 [ -z "$X" ] && X="$CMDNAME $2" 179 testing "$X" "\"$C\" $2" "$3" "$4" "$5" 180} 181 182# txpect NAME COMMAND [I/O/E/Xstring]... 183# Run COMMAND and interact with it: send I strings to input, read O or E 184# strings from stdout or stderr (empty string is "read line of input here"), 185# X means close stdin/stdout/stderr and match return code (blank means nonzero) 186txpect() 187{ 188 local NAME CASE VERBOSITY LEN A B X O 189 190 # Run command with redirection through fifos 191 NAME="$CMDNAME $1" 192 CASE= 193 VERBOSITY= 194 195 if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$ 196 then 197 do_fail 198 return 199 fi 200 eval "$2" <in-$$ >out-$$ 2>err-$$ & 201 shift 2 202 : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$ 203 204 [ $? -ne 0 ] && { do_fail;return;} 205 206 # Loop through challenge/response pairs, with 2 second timeout 207 while [ $# -gt 0 ] 208 do 209 VERBOSITY="$VERBOSITY"$'\n'"$1" LEN=$((${#1}-1)) CASE="$1" A= B= 210 211 verbose_has spam && echo "txpect $CASE" 212 case ${1::1} in 213 214 # send input to child 215 I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;; 216 217 R) LEN=0; B=1; ;& 218 # check output from child 219 [OE]) 220 [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN" 221 O=$OUT A= 222 [ "${1:$B:1}" == 'E' ] && O=$ERR 223 read -t2 $LARG A <&$O 224 X=$? 225 verbose_has spam && echo "txgot $X '$A'" 226 VERBOSITY="$VERBOSITY"$'\n'"$A" 227 if [ $LEN -eq 0 ] 228 then 229 [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;} 230 else 231 if [ ${1::1} == 'R' ] && [[ "$A" =~ ${1:2} ]]; then true 232 elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true 233 else 234 # Append the rest of the output if there is any. 235 read -t.1 B <&$O 236 A="$A$B" 237 read -t.1 -rN 9999 B<&$ERR 238 do_fail;break; 239 fi 240 fi 241 ;; 242 243 # close I/O and wait for exit 244 X) 245 exec {IN}<&- {OUT}<&- {ERR}<&- 246 wait 247 A=$? 248 if [ -z "$LEN" ] 249 then 250 [ $A -eq 0 ] && { do_fail;break;} # any error 251 else 252 [ $A != "${1:1}" ] && { do_fail;break;} # specific value 253 fi 254 ;; 255 *) do_fail; break ;; 256 esac 257 shift 258 done 259 # In case we already closed it 260 exec {IN}<&- {OUT}<&- {ERR}<&- 261 262 if [ $# -eq 0 ] 263 then 264 do_pass 265 else 266 ! verbose_has quiet && echo "$VERBOSITY" >&2 267 fi 268} 269 270# Recursively grab an executable and all the libraries needed to run it. 271# Source paths beginning with / will be copied into destpath, otherwise 272# the file is assumed to already be there and only its library dependencies 273# are copied. 274 275mkchroot() 276{ 277 [ $# -lt 2 ] && return 278 279 echo -n . 280 281 dest=$1 282 shift 283 for i in "$@" 284 do 285 [ "${i:0:1}" == "/" ] || i=$(which $i) 286 [ -f "$dest/$i" ] && continue 287 if [ -e "$i" ] 288 then 289 d=`echo "$i" | grep -o '.*/'` && 290 mkdir -p "$dest/$d" && 291 cat "$i" > "$dest/$i" && 292 chmod +x "$dest/$i" 293 else 294 echo "Not found: $i" 295 fi 296 mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') 297 done 298} 299 300# Set up a chroot environment and run commands within it. 301# Needed commands listed on command line 302# Script fed to stdin. 303 304dochroot() 305{ 306 mkdir tmpdir4chroot 307 mount -t ramfs tmpdir4chroot tmpdir4chroot 308 mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} 309 cp -L testing.sh tmpdir4chroot 310 311 # Copy utilities from command line arguments 312 313 echo -n "Setup chroot" 314 mkchroot tmpdir4chroot $* 315 echo 316 317 mknod tmpdir4chroot/dev/tty c 5 0 318 mknod tmpdir4chroot/dev/null c 1 3 319 mknod tmpdir4chroot/dev/zero c 1 5 320 321 # Copy script from stdin 322 323 cat > tmpdir4chroot/test.sh 324 chmod +x tmpdir4chroot/test.sh 325 chroot tmpdir4chroot /test.sh 326 umount -l tmpdir4chroot 327 rmdir tmpdir4chroot 328} 329