• 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 - "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