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# Announce failure and handle fallout for txpect
 118do_fail()
 119{
 120  FAILCOUNT=$(($FAILCOUNT+1))
 121  printf "%s\n" "$SHOWFAIL: $NAME"
 122  if [ ! -z "$CASE" ]
 123  then
 124    echo "Expected '$CASE'"
 125    echo "Got '$A'"
 126  fi
 127  ! verbose_has all && exit 1
 128}
 129
 130# The testing function
 131
 132testing()
 133{
 134  NAME="$CMDNAME $1"
 135  wrong_args "$@"
 136
 137  [ -z "$1" ] && NAME=$2
 138
 139  [ -n "$DEBUG" ] && set -x
 140
 141  if [ -n "$SKIP" -o -n "$SKIP_HOST" -a -n "$TEST_HOST" -o -n "$SKIPNEXT" ]
 142  then
 143    verbose_has quiet && printf "%s\n" "$SHOWSKIP: $NAME"
 144    unset SKIPNEXT
 145    return 0
 146  fi
 147
 148  echo -ne "$3" > expected
 149  [ ! -z "$4" ] && echo -ne "$4" > input || rm -f input
 150  echo -ne "$5" | ${EVAL:-eval --} "$2" > actual
 151  RETVAL=$?
 152
 153  # Catch segfaults
 154  [ $RETVAL -gt 128 ] &&
 155    echo "exited with signal (or returned $RETVAL)" >> actual
 156  DIFF="$(diff -au${NOSPACE:+w} expected actual)"
 157  [ -z "$DIFF" ] && do_pass || VERBOSE=all do_fail
 158  if ! verbose_has quiet && { [ -n "$DIFF" ] || verbose_has spam; }
 159  then
 160    [ ! -z "$4" ] && printf "%s\n" "echo -ne \"$4\" > input"
 161    printf "%s\n" "echo -ne '$5' |$EVAL $2"
 162    [ -n "$DIFF" ] && printf "%s\n" "$DIFF"
 163  fi
 164
 165  [ -n "$DIFF" ] && ! verbose_has all && exit 1
 166  rm -f input expected actual
 167
 168  [ -n "$DEBUG" ] && set +x
 169
 170  return 0
 171}
 172
 173testcmd()
 174{
 175  wrong_args "$@"
 176
 177  X="$1"
 178  [ -z "$X" ] && X="$CMDNAME $2"
 179  testing "$X" "\"$C\" $2" "$3" "$4" "$5"
 180}
 181
 182# txpect NAME COMMAND [I/O/E/Xstring]...
 183# Run COMMAND and interact with it: send I strings to input, read O or E
 184# strings from stdout or stderr (empty string is "read line of input here"),
 185# X means close stdin/stdout/stderr and match return code (blank means nonzero)
 186txpect()
 187{
 188  local NAME CASE VERBOSITY LEN A B X O
 189
 190  # Run command with redirection through fifos
 191  NAME="$CMDNAME $1"
 192  CASE=
 193  VERBOSITY=
 194
 195  if [ $# -lt 2 ] || ! mkfifo in-$$ out-$$ err-$$
 196  then
 197    do_fail
 198    return
 199  fi
 200  eval "$2" <in-$$ >out-$$ 2>err-$$ &
 201  shift 2
 202  : {IN}>in-$$ {OUT}<out-$$ {ERR}<err-$$ && rm in-$$ out-$$ err-$$
 203
 204  [ $? -ne 0 ] && { do_fail;return;}
 205
 206  # Loop through challenge/response pairs, with 2 second timeout
 207  while [ $# -gt 0 ]
 208  do
 209    VERBOSITY="$VERBOSITY"$'\n'"$1"  LEN=$((${#1}-1))  CASE="$1"  A=  B=
 210
 211    verbose_has spam && echo "txpect $CASE"
 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  A=
 222        [ "${1:$B:1}" == 'E' ] && O=$ERR
 223        read -t2 $LARG A <&$O
 224        X=$?
 225        verbose_has spam && echo "txgot $X '$A'"
 226        VERBOSITY="$VERBOSITY"$'\n'"$A"
 227        if [ $LEN -eq 0 ]
 228        then
 229          [ -z "$A" -o "$X" -ne 0 ] && { do_fail;break;}
 230        else
 231          if [ ${1::1} == 'R' ] && [[ "$A" =~ ${1:2} ]]; then true
 232          elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true
 233          else
 234            # Append the rest of the output if there is any.
 235            read -t.1 B <&$O
 236            A="$A$B"
 237            read -t.1 -rN 9999 B<&$ERR
 238            do_fail;break;
 239          fi
 240        fi
 241        ;;
 242
 243      # close I/O and wait for exit
 244      X)
 245        exec {IN}<&- {OUT}<&- {ERR}<&-
 246        wait
 247        A=$?
 248        if [ -z "$LEN" ]
 249        then
 250          [ $A -eq 0 ] && { do_fail;break;}        # any error
 251        else
 252          [ $A != "${1:1}" ] && { do_fail;break;}  # specific value
 253        fi
 254        ;;
 255      *) do_fail; break ;;
 256    esac
 257    shift
 258  done
 259  # In case we already closed it
 260  exec {IN}<&- {OUT}<&- {ERR}<&-
 261
 262  if [ $# -eq 0 ]
 263  then
 264    do_pass
 265  else
 266    ! verbose_has quiet && echo "$VERBOSITY" >&2
 267  fi
 268}
 269
 270# Recursively grab an executable and all the libraries needed to run it.
 271# Source paths beginning with / will be copied into destpath, otherwise
 272# the file is assumed to already be there and only its library dependencies
 273# are copied.
 274
 275mkchroot()
 276{
 277  [ $# -lt 2 ] && return
 278
 279  echo -n .
 280
 281  dest=$1
 282  shift
 283  for i in "$@"
 284  do
 285    [ "${i:0:1}" == "/" ] || i=$(which $i)
 286    [ -f "$dest/$i" ] && continue
 287    if [ -e "$i" ]
 288    then
 289      d=`echo "$i" | grep -o '.*/'` &&
 290      mkdir -p "$dest/$d" &&
 291      cat "$i" > "$dest/$i" &&
 292      chmod +x "$dest/$i"
 293    else
 294      echo "Not found: $i"
 295    fi
 296    mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ')
 297  done
 298}
 299
 300# Set up a chroot environment and run commands within it.
 301# Needed commands listed on command line
 302# Script fed to stdin.
 303
 304dochroot()
 305{
 306  mkdir tmpdir4chroot
 307  mount -t ramfs tmpdir4chroot tmpdir4chroot
 308  mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev}
 309  cp -L testing.sh tmpdir4chroot
 310
 311  # Copy utilities from command line arguments
 312
 313  echo -n "Setup chroot"
 314  mkchroot tmpdir4chroot $*
 315  echo
 316
 317  mknod tmpdir4chroot/dev/tty c 5 0
 318  mknod tmpdir4chroot/dev/null c 1 3
 319  mknod tmpdir4chroot/dev/zero c 1 5
 320
 321  # Copy script from stdin
 322
 323  cat > tmpdir4chroot/test.sh
 324  chmod +x tmpdir4chroot/test.sh
 325  chroot tmpdir4chroot /test.sh
 326  umount -l tmpdir4chroot
 327  rmdir tmpdir4chroot
 328}
 329