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