xref: /aosp_15_r20/external/toybox/scripts/runtest.sh (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
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" > "$TESTDIR"/expected
136  [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input
137  echo -ne "$5" | ${EVAL:-eval --} "$2" > "$TESTDIR"/actual
138  RETVAL=$?
139
140  # Catch segfaults
141  [ $RETVAL -gt 128 ] &&
142    echo "exited with signal (or returned $RETVAL)" >> actual
143  DIFF="$(cd "$TESTDIR"; diff -au${NOSPACE:+w} expected actual 2>&1)"
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
168utf8locale()
169{
170  local i
171
172  for i in $LC_ALL C.UTF-8 en_US.UTF-8
173  do
174    [ "$(LC_ALL=$i locale charmap 2>/dev/null)" == UTF-8 ] && LC_ALL=$i && break
175  done
176}
177
178# Simple implementation of "expect" written in shell.
179
180# txpect NAME COMMAND [I/O/E/X/R[OE]string]...
181# Run COMMAND and interact with it:
182# I send string to input
183# OE read exactly this string from stdout or stderr (bare = read+discard line)
184#    note: non-bare does not read \n unless you include it with O$'blah\n'
185# R prefix means O or E is regex match (read line, must contain substring)
186# X close stdin/stdout/stderr and match return code (blank means nonzero)
187txpect()
188{
189  local NAME CASE VERBOSITY IN OUT ERR LEN PID A B X O
190
191  # Run command with redirection through fifos
192  NAME="$CMDNAME $1"
193  CASE=
194  VERBOSITY=
195
196  if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$
197  then
198    do_fail
199    return
200  fi
201  eval "$2" <in-$$ >out-$$ 2>err-$$ &
202  PID=$!
203  shift 2
204  : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$
205
206  [ $? -ne 0 ] && { do_fail;return;}
207
208  # Loop through challenge/response pairs, with 2 second timeout
209  while [ $# -gt 0 -a -n "$PID" ]
210  do
211    VERBOSITY="$VERBOSITY"$'\n'"$1"  LEN=$((${#1}-1))  CASE="$1"  A=  B=
212
213    verbose_has spam && echo "txpect $CASE"
214    case ${1::1} in
215
216      # send input to child
217      I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;;
218
219      R) LEN=0; B=1; ;&
220      # check output from child
221      [OE])
222        [ $LEN == 0 ] && LARG="" || LARG="-rN $LEN"
223        O=$OUT  A=
224        [ "${1:$B:1}" == 'E' ] && O=$ERR
225        read -t2 $LARG A <&$O
226        X=$?
227        verbose_has spam && echo "txgot $X '$A'"
228        VERBOSITY="$VERBOSITY"$'\n'"$A"
229        if [ $LEN -eq 0 ]
230        then
231          [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;}
232        else
233          if [ ${1::1} == 'R' ] && grep -q "${1:2}" <<< "$A"; then true
234          elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true
235          else
236            # Append the rest of the output if there is any.
237            read -t.1 B <&$O
238            A="$A$B"
239            read -t.1 -rN 9999 B<&$ERR
240            do_fail
241            break
242          fi
243        fi
244        ;;
245
246      # close I/O and wait for exit
247      X)
248        exec {IN}<&-
249        wait $PID
250        A=$?
251        exec {OUT}<&- {ERR}<&-
252        if [ "$LEN" -eq 0 ]
253        then
254          [ $A -eq 0 ] && { do_fail;break;}        # any error
255        else
256          [ $A != "${1:1}" ] && { do_fail;break;}  # specific value
257        fi
258        do_pass
259        return
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    do_fail
274  fi
275}
276