• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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