• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Simple test harness infrastructure
2#
3# Copyright 2005 by Rob Landley
4
5# This file defines three main functions: "testing", "testcmd", and "txpect".
6
7# The following environment variables enable optional behavior in "testing":
8#    DEBUG - Show every command run by test script.
9#    VERBOSE - "all"    continue after failed test
10#              "fail"   show diff and stop at first failed test
11#              "nopass" don't show successful tests
12#              "quiet"  don't show diff -u for failures
13#              "spam"   show passing test command lines
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 environment variable "SKIP" says how many upcoming tests to skip,
32# defaulting to 0 and counting down when set to a higher number.
33#
34# Function "optional" enables/disables tests based on configuration options.
35
36export FAILCOUNT=0 SKIP=0
37: ${SHOWPASS:=PASS} ${SHOWFAIL:=FAIL} ${SHOWSKIP:=SKIP}
38if tty -s <&1
39then
40  SHOWPASS="$(echo -e "\033[1;32m${SHOWPASS}\033[0m")"
41  SHOWFAIL="$(echo -e "\033[1;31m${SHOWFAIL}\033[0m")"
42  SHOWSKIP="$(echo -e "\033[1;33m${SHOWSKIP}\033[0m")"
43fi
44
45# Helper functions
46
47# Check if VERBOSE= contains a given string. (This allows combining.)
48verbose_has()
49{
50  [ "${VERBOSE/$1/}" != "$VERBOSE" ]
51}
52
53wrong_args()
54{
55  if [ $# -ne 5 ]
56  then
57    printf "%s\n" "Test $NAME has the wrong number of arguments ($# $*)" >&2
58    exit
59  fi
60}
61
62# Announce success
63do_pass()
64{
65  verbose_has nopass || printf "%s\n" "$SHOWPASS: $NAME"
66}
67
68# Announce failure and handle fallout for txpect
69do_fail()
70{
71  FAILCOUNT=$(($FAILCOUNT+1))
72  printf "%s\n" "$SHOWFAIL: $NAME"
73  if [ ! -z "$CASE" ]
74  then
75    echo "Expected '$CASE'"
76    echo "Got '$A'"
77  fi
78  ! verbose_has all && exit 1
79}
80
81# Functions test files call directly
82
83# Set SKIP high if option not enabled in $OPTIONFLAGS (unless OPTIONFLAGS blank)
84optional()
85{
86  [ -n "$OPTIONFLAGS" ] && [ "$OPTIONFLAGS" == "${OPTIONFLAGS/:$1:/}" ] &&
87    SKIP=99999 || SKIP=0
88}
89
90# Evalute command line and skip next test when false
91skipnot()
92{
93  if verbose_has quiet
94  then
95    eval "$@" >/dev/null 2>&1
96  else
97    eval "$@"
98  fi
99  [ $? -eq 0 ] || { ((++SKIP)); return 1; }
100}
101
102# Skip this test (rest of command line) when not running toybox.
103toyonly()
104{
105  IS_TOYBOX="$("$C" --version 2>/dev/null)"
106  # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf
107  # happy, so we have at least two things to check for.
108  case "$IS_TOYBOX" in
109    toybox*) ;;
110    This\ is\ not\ GNU*) ;;
111    *) [ $SKIP -eq 0 ] && ((++SKIP)) ;;
112  esac
113
114  "$@"
115}
116
117# Takes five arguments: "name" "command" "result" "infile" "stdin"
118testing()
119{
120  wrong_args "$@"
121
122  [ -z "$1" ] && NAME="$2" || NAME="$1"
123  [ "${NAME#$CMDNAME }" == "$NAME" ] && NAME="$CMDNAME $1"
124
125  [ -n "$DEBUG" ] && set -x
126
127  if [ "$SKIP" -gt 0 ]
128  then
129    verbose_has quiet || printf "%s\n" "$SHOWSKIP: $NAME"
130    ((--SKIP))
131
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 ] &&
142    echo "exited with signal (or returned $RETVAL)" >> actual
143  DIFF="$(cd ..; diff -au${NOSPACE:+w} expected actual)"
144  [ -z "$DIFF" ] && do_pass || VERBOSE=all do_fail
145  if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; }
146  then
147    [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input"
148    printf "%s\n" "echo -ne '$5' |$EVAL $2"
149    [ -n "$DIFF" ] && printf "%s\n" "$DIFF"
150  fi
151
152  [ -n "$DIFF" ] && ! verbose_has all && exit 1
153  rm -f input ../expected ../actual
154
155  [ -n "$DEBUG" ] && set +x
156
157  return 0
158}
159
160# Wrapper for "testing", adds command name being tested to start of command line
161testcmd()
162{
163  wrong_args "$@"
164
165  testing "${1:-$CMDNAME $2}" "\"$C\" $2" "$3" "$4" "$5"
166}
167
168# Simple implementation of "expect" written in shell.
169
170# txpect NAME COMMAND [I/O/E/Xstring]...
171# Run COMMAND and interact with it: send I strings to input, read O or E
172# strings from stdout or stderr (empty string is "read line of input here"),
173# X means close stdin/stdout/stderr and match return code (blank means nonzero)
174txpect()
175{
176  local NAME CASE VERBOSITY LEN PID A B X O
177
178  # Run command with redirection through fifos
179  NAME="$CMDNAME $1"
180  CASE=
181  VERBOSITY=
182
183  if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$
184  then
185    do_fail
186    return
187  fi
188  eval "$2" <in-$$ >out-$$ 2>err-$$ &
189  PID=$!
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 -a -n "$PID" ]
197  do
198    VERBOSITY="$VERBOSITY"$'\n'"$1"  LEN=$((${#1}-1))  CASE="$1"  A=  B=
199
200    verbose_has spam && echo "txpect $CASE"
201    case ${1::1} in
202
203      # send input to child
204      I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;;
205
206      R) LEN=0; B=1; ;&
207      # check output from child
208      [OE])
209        [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN"
210        O=$OUT  A=
211        [ "${1:$B:1}" == 'E' ] && O=$ERR
212        read -t2 $LARG A <&$O
213        X=$?
214        verbose_has spam && echo "txgot $X '$A'"
215        VERBOSITY="$VERBOSITY"$'\n'"$A"
216        if [ $LEN -eq 0 ]
217        then
218          [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;}
219        else
220          if [ ${1::1} == 'R' ] && grep -q "${1:2}" <<< "$A"; then true
221          elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true
222          else
223            # Append the rest of the output if there is any.
224            read -t.1 B <&$O
225            A="$A$B"
226            read -t.1 -rN 9999 B<&$ERR
227            do_fail
228            break
229          fi
230        fi
231        ;;
232
233      # close I/O and wait for exit
234      X)
235        exec {IN}<&-
236        wait $PID
237        A=$?
238        exec {OUT}<&- {ERR}<&-
239        if [ -z "$LEN" ]
240        then
241          [ $A -eq 0 ] && { do_fail;break;}        # any error
242        else
243          [ $A != "${1:1}" ] && { do_fail;break;}  # specific value
244        fi
245        do_pass
246        return
247        ;;
248      *) do_fail; break ;;
249    esac
250    shift
251  done
252  # In case we already closed it
253  exec {IN}<&- {OUT}<&- {ERR}<&-
254
255  if [ $# -eq 0 ]
256  then
257    do_pass
258  else
259    ! verbose_has quiet && echo "$VERBOSITY" >&2
260    do_fail
261  fi
262}
263