• 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  [ -z "$TEST_HOST" ] && IS_TOYBOX=toybox
106  : ${IS_TOYBOX:=$("$C" --version 2>/dev/null | grep -o toybox)} \
107    ${IS_TOYBOX:="$(basename $(readlink -f "$C"))"}
108  # Ideally we'd just check for "toybox", but toybox sed lies to make autoconf
109  # happy, so we have at least two things to check for.
110  case "$IS_TOYBOX" in
111    toybox*) ;;
112    This\ is\ not\ GNU*) ;;
113    *) [ $SKIP -eq 0 ] && ((++SKIP)) ;;
114  esac
115
116  "$@"
117}
118
119# Takes five arguments: "name" "command" "result" "infile" "stdin"
120testing()
121{
122  wrong_args "$@"
123
124  [ -z "$1" ] && NAME="$2" || NAME="$1"
125  [ "${NAME#$CMDNAME }" == "$NAME" ] && NAME="$CMDNAME $1"
126
127  [ -n "$DEBUG" ] && set -x
128
129  if [ "$SKIP" -gt 0 ]
130  then
131    verbose_has quiet || printf "%s\n" "$SHOWSKIP: $NAME"
132    ((--SKIP))
133
134    return 0
135  fi
136
137  echo -ne "$3" > "$TESTDIR"/expected
138  [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input
139  echo -ne "$5" | ${EVAL:-eval --} "$2" > "$TESTDIR"/actual
140  RETVAL=$?
141
142  # Catch segfaults
143  [ $RETVAL -gt 128 ] &&
144    echo "exited with signal (or returned $RETVAL)" >> actual
145  DIFF="$(cd "$TESTDIR"; diff -au${NOSPACE:+w} expected actual 2>&1)"
146  [ -z "$DIFF" ] && do_pass || VERBOSE=all do_fail
147  if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; }
148  then
149    [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input"
150    printf "%s\n" "echo -ne '$5' |$EVAL ${2@Q}"
151    [ -n "$DIFF" ] && printf "%s\n" "$DIFF"
152  fi
153
154  [ -n "$DIFF" ] && ! verbose_has all && exit 1
155  rm -f input ../expected ../actual
156
157  [ -n "$DEBUG" ] && set +x
158
159  return 0
160}
161
162# Wrapper for "testing", adds command name being tested to start of command line
163testcmd()
164{
165  wrong_args "$@"
166
167  testing "${1:-$CMDNAME $2}" "\"$C\" $2" "$3" "$4" "$5"
168}
169
170utf8locale()
171{
172  local i
173
174  for i in $LC_ALL C.UTF-8 en_US.UTF-8
175  do
176    [ "$(LC_ALL=$i locale charmap 2>/dev/null)" == UTF-8 ] && LC_ALL=$i && break
177  done
178}
179
180# Simple implementation of "expect" written in shell.
181
182# txpect NAME COMMAND [I/O/E/X/R[OE]string]...
183# Run COMMAND and interact with it:
184# I send string to input
185# OE read exactly this string from stdout or stderr (bare = read+discard line)
186#    note: non-bare does not read \n unless you include it with O$'blah\n'
187# R prefix means O or E is regex match (read line, must contain substring)
188# X close stdin/stdout/stderr and match return code (blank means nonzero)
189txpect()
190{
191  local NAME CASE VERBOSITY IN OUT ERR LEN PID A B X O
192
193  # Run command with redirection through fifos
194  NAME="$CMDNAME $1"
195  CASE=
196  VERBOSITY=
197
198  if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$
199  then
200    do_fail
201    return
202  fi
203  eval "$2" <in-$$ >out-$$ 2>err-$$ &
204  PID=$!
205  shift 2
206  : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$
207
208  [ $? -ne 0 ] && { do_fail;return;}
209
210  # Loop through challenge/response pairs, with 2 second timeout
211  while [ $# -gt 0 -a -n "$PID" ]
212  do
213    VERBOSITY="$VERBOSITY"$'\n'"$1"  LEN=$((${#1}-1))  CASE="$1"  A=  B=
214
215    verbose_has spam && echo "txpect $CASE"
216    case ${1::1} in
217
218      # send input to child
219      I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;;
220
221      R) LEN=0; B=1; ;&
222      # check output from child
223      [OE])
224        [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN"
225        O=$OUT  A=
226        [ "${1:$B:1}" == 'E' ] && O=$ERR
227        read -t2 $LARG A <&$O
228        X=$?
229        verbose_has spam && echo "txgot $X '$A'"
230        VERBOSITY="$VERBOSITY"$'\n'"$A"
231        if [ $LEN -eq 0 ]
232        then
233          [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;}
234        else
235          if [ ${1::1} == 'R' ] && grep -q "${1:2}" <<< "$A"; then true
236          elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true
237          else
238            # Append the rest of the output if there is any.
239            read -t.1 B <&$O
240            A="$A$B"
241            read -t.1 -rN 9999 B<&$ERR
242            do_fail
243            break
244          fi
245        fi
246        ;;
247
248      # close I/O and wait for exit
249      X)
250        exec {IN}<&-
251        wait $PID
252        A=$?
253        exec {OUT}<&- {ERR}<&-
254        if [ "$LEN" -eq 0 ]
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        do_pass
261        return
262        ;;
263      *) do_fail; break ;;
264    esac
265    shift
266  done
267  # In case we already closed it
268  exec {IN}<&- {OUT}<&- {ERR}<&-
269
270  if [ $# -eq 0 ]
271  then
272    do_pass
273  else
274    ! verbose_has quiet && echo "$VERBOSITY" >&2
275    do_fail
276  fi
277}
278