toybox/scripts/runtest.sh
<<
>>
Prefs
   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