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