#!/bin/sh
#
#  Copyright (C) CFEngine AS
#
#  This file is part of CFEngine 3 - written and maintained by CFEngine AS.
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; version 3.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
#
# To the extent this program is licensed as part of the Enterprise
# versions of CFEngine, the applicable Commercial Open Source License
# (COSL) may apply to this file if you as a licensee so wish it. See
# included file COSL.txt.
#

#
# Detect and replace non-POSIX shell
#
try_exec() {
  type "$1" > /dev/null 2>&1 && exec "$@"
}

unset foo
(: ${foo%%bar}) 2> /dev/null
T1="$?"

if test "$T1" != 0; then
  try_exec /usr/xpg4/bin/sh "$0" "$@"
  echo "No compatible shell script interpreter found."
  echo "Please find a POSIX shell for your system."
  exit 42
fi

#
# Explicitly use POSIX tools if needed
#
if [ -f /usr/xpg4/bin/grep ]; then
  PATH=/usr/xpg4/bin:$PATH
  export PATH
fi

#
# Unset environment variables which might break runinng acceptance tests
#
GREP_OPTIONS=
export GREP_OPTIONS

#
# Defaults (overridden by command-line arguments)
#
LOG=test.log
SUMMARY=summary.log
XML=test.xml
XMLTMP=xml.tmp
WHOAMI=/c/Windows/System32/whoami.exe
BASE_WORKDIR="$(pwd)/workdir"
QUIET=
TIMED_TESTS=${TIMED_TESTS:-1}
CRASHING_TESTS=${CRASHING_TESTS:-1}
STAGING_TESTS=${STAGING_TESTS:-0}
NETWORK_TESTS=${NETWORK_TESTS:-1}
UNSAFE_TESTS=${UNSAFE_TESTS:-0}
LIBXML2_TESTS=${LIBXML2_TESTS:-1}
NO_CLEAN=0
BASECLASSES="AUTO"
EXTRACLASSES="DEBUG"
VALGRIND_OPTS="--leak-check=full --show-reachable=yes --suppressions=valgrind-suppressions"

# Use TEST_INDEX for indexing a poor man's array. Basically the subscript is
# just appended to the variable name.
TEST_INDEX=0
TEST_TIMED_INDEX=0
#TESTS_TIMED_<index>=0
#TESTS_TIMEOUT_<index>=0
#TESTS_PASSES_<index>=0
TESTS_COUNT=0
TESTS_NORMAL_COUNT=0
TESTS_TIMED_COUNT=0
TESTS_TIMED_REMAINING=0

case "$OSTYPE" in
  msys)
    if "$WHOAMI" -priv | grep SeTakeOwnershipPrivilege > /dev/null; then
      # Don't use elevate if we already have the privileges. It is slower and
      # pops up a flashing window for every single test.
      DEFAULT_GAINROOT=
    else
      DEFAULT_GAINROOT="`dirname $0`/tool_wrappers/elevate.sh"
    fi
    # Use hardlinks on Windows. Using symbolic links will work, but Msys creates a
    # real copy, which eats disk space very quickly when you multiply with the number
    # of tests.
    LN_CMD="ln -f"
    ;;
  *)
    DEFAULT_GAINROOT=fakeroot
    LN_CMD="ln -sf"
    ;;
esac

GAINROOT=${GAINROOT:-$DEFAULT_GAINROOT}

PASSED_TESTS=0
FAILED_TESTS=0
SUPPRESSED_FAILURES=0
SKIPPED_TESTS=0

#
# Many older platforms don't support date +%s, so check for compatibility
# and find Perl for the unix_seconds() routine below. (Mantis #1254)
#
HAVE_DATE_PCT_S=
date +%s | grep %s >/dev/null 2>&1
if [ $? -ne 0 ] ; then
  HAVE_DATE_PCT_S=1
fi
PERL=`which perl 2>/dev/null`

# color!
if [ "${CFENGINE_COLOR}" = "1" ]; then
    COLOR_SUCCESS="\\033[1;32m"
    COLOR_FAILURE="\\033[1;31m"
    COLOR_WARNING="\\033[1;33m"
    COLOR_NORMAL="\\033[0;39m"
else
    COLOR_SUCCESS=
    COLOR_FAILURE=
    COLOR_WARNING=
    COLOR_NORMAL=
fi

#
# Obtain UNIX time(), using date +%s, Perl, or POSIX-compatible approach.
#
unix_seconds() {
  if [ "$HAVE_DATE_PCT_S" ]; then
    date +%s
    return 0
  fi

  if [ "$PERL" ]; then
    $PERL -e 'print time() . "\n"' 2>/dev/null
    if [ $? -eq 0 ] ; then
      return 0
    fi
  fi

  # Last resort if Perl fails - the extended cpio interchange format has
  # the file modification timestamp in columns 48-59, in octal.
  : > $BASE_WORKDIR/x
  echo "ibase=8;$(pax -wx cpio $BASE_WORKDIR/$$.seconds | cut -c 48-59)" | bc 2>/dev/null
  rm $BASE_WORKDIR/x
}

# echo
# echo === Test environment: ===
# echo AGENT=$AGENT
# echo CF_PROMISES=$CF_PROMISES
# echo CF_SERVERD=$CF_SERVERD
# echo CF_KEY=$CF_KEY
# echo =========================
# echo

usage() {
  echo "testall [-h|--help] [-q|--quiet] [--gainroot=<command>] [--agent=<agent>] [--cfpromises=<cf-promises>] [--cfserverd=<cf-serverd>] [--cfkey=<cf-key>] [--bindir=<bindir>] [--staging] [--unsafe] [--no-network] [--gdb] [--printlog] [<test> <test>...]"
  echo
  echo "If no test is given, all standard tests are run:"
  echo "  Tests with names of form <file>.cf are expected to run succesfully"
  echo "  Tests with names of form <file>.x.cf are expected to crash"
  echo "Set ${COLOR_SUCCESS}CFENGINE_COLOR=1${COLOR_NORMAL} to get ANSI color markers where appropriate."
  echo
  echo "If arguments are given, those are executed as tests"
  echo
  echo " -h"
  echo " --help    prints usage"
  echo " -q"
  echo " --quiet   makes script much quieter"
  echo " --gainroot=<command> forces use of command to gain root privileges,"
  echo "           otherwise fakeroot is used.  Use --gainroot=env to make this"
  echo "           option a no-op (e.g. you're running inside fakeroot already)"
  echo " --agent   provides a way to specify non-default cf-agent location,"
  echo "           and defaults to $DEFAGENT."
  echo " --baseclasses provides a way to override the default cf-agent classes,"
  echo "           and defaults to ${BASECLASSES}.  Also can use --bc"
  echo " --extraclasses provides a way to append to the default cf-agent classes,"
  echo "           and defaults to ${EXTRACLASSES}.  Also can use --ec"
  echo " --cfpromises  provides a way to specify non-default cf-promises location,"
  echo "               and defaults to $DEFCF_PROMISES."
  echo " --cfserverd  provides a way to specify non-default cf-serverd location,"
  echo "               and defaults to $DEFCF_SERVERD."
  echo " --cfkey  provides a way to specify non-default cf-key location,"
  echo "               and defaults to $DEFCF_KEY."
  echo " --bindir  specifies the directory containing all the binaries."
  echo "           Mutually exclusive with --agent and --cf* arguments."
  echo " --libtool specify non-default libtool location (only needed for --gdb)."
  echo "               defaults to $DEFLIBTOOL."
  echo " --staging enable tests in staging directories. They are not expected to pass."
  echo " --unsafe  enable tests in unsafe directories. WARNING! These tests modify the"
  echo "           system they're running on and can DAMAGE YOUR SYSTEM! DO NOT use"
  echo "           this option without a backup."
  echo "           If you use this option you should also use --gainroot=sudo,"
  echo "           otherwise you will get incorrect results."
  echo " --no-network disable tests in network directories."
  echo " --no-libxml2 disable tests involving xml file editing."
  echo " --no-crashing disable tests that are expected to crash (for use with valgrind)."
  echo " --no-timed disable timed tests that may introduce wait times."
  echo " --printlog   print the full test.log output immediately.  Override with $PRINTLOG"
  echo " --gdb         Run test under GDB"
  echo " --valgrind    Run test under Valgrind"
  echo " --callgrind   Run test under valgrind --tool=callgrind"
  echo " --no-clean does not clean workdir after test finishes"
  echo "            (by default it gets cleaned only if test passed)."
}

# Takes the following arguments:
# 1. Agent - The agent to execute.
# 2. Test - The test to execute.
# 3. Pass number - [Optional] The current pass number. Used by timed tests.
# 4. Timeout variable - [Optional] The name of the variable to put the next
#        timeout into. Used by timed tests.
runtest() {
  # Clear local variables
  unset AGENT TEST EXPECTED_CRASH SKIP SKIPREASON RESULT RESULT_MSG TEST_START_TIME FLATNAME WORKDIR OUTFILE

  AGENT="$1"
  TEST="$2"
  PASS_NUM="$3"
  NEXT_TIMEOUT_VAR="$4"
  if [ -z "$QUIET" ]; then
    printf "$TEST "
  fi

  if echo "$TEST" | fgrep -e .x.cf > /dev/null; then
    EXPECTED_CRASH=1
  else
    EXPECTED_CRASH=
  fi

  if [ "x$CRASHING_TESTS" = "x0" ] && [ "x$EXPECTED_CRASH" = "x1" ]; then
    SKIP=1
    SKIPREASON="${COLOR_WARNING}Crashing tests are disabled${COLOR_NORMAL}"
  elif [ "x$STAGING_TESTS" = "x0" ] && echo "$TEST" | grep '/staging/' > /dev/null; then
    SKIP=1
    SKIPREASON="${COLOR_WARNING}Staging tests are disabled${COLOR_NORMAL}"
  elif [ "x$UNSAFE_TESTS" != "x1" ] && echo "$TEST" | grep '/unsafe/' > /dev/null; then
    SKIP=1
    SKIPREASON="${COLOR_WARNING}Unsafe tests are disabled${COLOR_NORMAL}"
  elif [ "x$NETWORK_TESTS" = "x0" ] && echo "$TEST" | grep '/network/' > /dev/null; then
    SKIP=1
    SKIPREASON="${COLOR_WARNING}Network-dependent tests are disabled${COLOR_NORMAL}"
  elif [ "x$LIBXML2_TESTS" = "x0" ] && echo "$TEST" | grep '/11_xml_edits/' > /dev/null; then
    SKIP=1
    SKIPREASON="XML file editing tests are disabled"
  else
    SKIP=
    SKIPREASON=
  fi

  ( echo ----------------------------------------------------------------------
    echo "$TEST"${EXPECTED_CRASH:+ \(expected to crash\)}${SKIPREASON:+ \($SKIPREASON\)}
    echo ----------------------------------------------------------------------
  ) >> "$LOG"

  TEST_START_TIME=$(unix_seconds)

  if [ -n "$NEXT_TIMEOUT_VAR" ]; then
    eval $NEXT_TIMEOUT_VAR=
  fi

  if [ -n "$SKIP" ]; then
    TEST_END_TIME=$TEST_START_TIME
    RESULT=Skip
    RESULT_MSG="${COLOR_WARNING}Skipped ($SKIPREASON)${COLOR_NORMAL}"
  else
    FLATNAME="$(echo "$TEST" | sed 's,[./],_,g')"

    # Prepare workdir
    WORKDIR="$BASE_WORKDIR/$FLATNAME"
    OUTFILE=$WORKDIR/test.log
    if [ -z "$PASS_NUM" ] || [ "$PASS_NUM" -eq 1 ]; then
      # Don't reset workdir if this is a subsequent pass.
      $GAINROOT rm -rf "$WORKDIR"
      mkdir -p "$WORKDIR/bin" "$WORKDIR/tmp"
      chmod ugo+rwxt "$WORKDIR/tmp"
      if [ -n "$BINDIR" ]; then
        # Copy everything, because Windows depends on DLLs.
        $LN_CMD "$BINDIR"/* "$WORKDIR/bin"
      else
        $LN_CMD "$AGENT" "$WORKDIR/bin"
        $LN_CMD "$CF_PROMISES" "$WORKDIR/bin"
        $LN_CMD "$CF_SERVERD" "$WORKDIR/bin"
        $LN_CMD "$CF_KEY" "$WORKDIR/bin"
      fi
    fi
    if uname | grep MINGW > /dev/null; then
        PLATFORM_WORKDIR="$(echo $WORKDIR | sed -e 's%^/\([a-cA-Z]\)/%\1:/%' | sed -e 's%/%\\%g')"
        DS="\\"
    else
        PLATFORM_WORKDIR="$WORKDIR"
        DS="/"
    fi

    echo "#!/bin/sh
CFENGINE_TEST_OVERRIDE_WORKDIR=\"$PLATFORM_WORKDIR\"
TEMP=\"$PLATFORM_WORKDIR${DS}tmp\"
CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR=\"$CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR\"
export CFENGINE_TEST_OVERRIDE_WORKDIR TEMP CFENGINE_TEST_OVERRIDE_EXTENSION_LIBRARY_DIR

" > "$WORKDIR/runtest"

    if [ "$GDB" = 1 ]; then
      if grep libtool < "$AGENT" > /dev/null; then
        printf "\"$LIBTOOL\" --mode=execute " >> "$WORKDIR/runtest"
      fi
      printf "gdb --args " >> "$WORKDIR/runtest"
    fi

    if [ -n "$USE_VALGRIND" ] && [ x"$EXPECTED_CRASH" = "x" ]; then
      if grep libtool < "$AGENT" > /dev/null; then
        printf "\"$LIBTOOL\" --mode=execute " >> "$WORKDIR/runtest"
      fi
      printf "valgrind ${VALGRIND_OPTS} \"$AGENT\" $VERBOSE -Kf \"$TEST\" -D ${PASS_NUM:+test_pass_$PASS_NUM,}${BASECLASSES},${EXTRACLASSES} 2>&1\n" >> "$WORKDIR/runtest"
    else
      printf "\"$AGENT\" $VERBOSE -Kf \"$TEST\" -D ${PASS_NUM:+test_pass_$PASS_NUM,}${BASECLASSES},${EXTRACLASSES}\n" >> "$WORKDIR/runtest"
    fi

    chmod +x "$WORKDIR/runtest"

    if [ "$GDB" = 1 ]; then
      $GAINROOT "$WORKDIR/runtest"
    else
      eval $GAINROOT "$WORKDIR/runtest" >$OUTFILE 2>&1
    fi
    RETVAL=$?
    cat $OUTFILE >> "$LOG"
    echo >> "$LOG"
    echo "Return code is $RETVAL." >> "$LOG"

    TEST_END_TIME=$(unix_seconds)

    RESULT_MSG=
    if [ -z "$EXPECTED_CRASH" ] && [ $RETVAL -ne 0 ]; then
      RESULT=FAIL
    elif [ -z "$EXPECTED_CRASH" ]; then
      # We need to be careful when matching test outcomes. Because of convergence
      # passes, a test may output FAIL before it outputs Pass; in this case the
      # latter trumps the former. Also be careful when matching an XFAIL; the
      # test should not be allowed to output any other outcome in the same run,
      # except for FAIL.

      # Some states are output by dcs.cf.sub, therefore check for both TEST
      # prefix and dcs.cf.sub prefix.
      ESCAPED_TEST="$(echo "($TEST|dcs.cf.sub)" | sed -e 's/\./\\./g')"
      if egrep -e "R: .*$ESCAPED_TEST XFAIL" $OUTFILE > /dev/null; then
        # Check for other test case outcomes than fail. Should not happen.
        if egrep -e "R: .*$ESCAPED_TEST " $OUTFILE | egrep -v -e "R: .*$ESCAPED_TEST X?FAIL" > /dev/null; then
          RESULT=FAIL
          RESULT_MSG="${COLOR_FAILURE}FAIL (Tried to suppress nonexistent failure)${COLOR_NORMAL}"
        else
          REDMINE="$(egrep -e "R: .*$ESCAPED_TEST XFAIL" $OUTFILE | sed -e "s,.*XFAIL/redmine\([0-9][0-9]*\).*,\1,")"
          RESULT=XFAIL
          RESULT_MSG="${COLOR_WARNING}FAIL (Suppressed, Redmine #$REDMINE)${COLOR_NORMAL}"
        fi
      elif egrep -e "R: .*$ESCAPED_TEST FAIL/no_redmine_number" $OUTFILE > /dev/null; then
        RESULT=FAIL
        RESULT_MSG="${COLOR_FAILURE}FAIL (Tried to suppress failure, but no Redmine issue number is provided)${COLOR_NORMAL}"
      elif egrep -e "R: .*$ESCAPED_TEST Wait/[0-9]+" $OUTFILE > /dev/null; then
        if [ -z "$NEXT_TIMEOUT_VAR" ]; then
          RESULT=FAIL
          RESULT_MSG="${COLOR_FAILURE}FAIL (Test tried to wait but is not in \"timed\" directory)${COLOR_NORMAL}"
        else
          WAIT_TIME=$(egrep -e "R: .*$ESCAPED_TEST Wait/[0-9]+" $OUTFILE | sed -e 's,.*Wait/\([0-9][0-9]*\).*,\1,')
          eval $NEXT_TIMEOUT_VAR=$(($TEST_END_TIME+$WAIT_TIME))
          RESULT=Wait
          RESULT_MSG="Awaiting ($WAIT_TIME seconds)..."
        fi
      elif egrep -e "R: .*$ESCAPED_TEST Pass" $OUTFILE > /dev/null; then
        RESULT=Pass
        RESULT_MSG="${COLOR_SUCCESS}Pass${COLOR_NORMAL}"
      elif egrep -e "R: .*$ESCAPED_TEST Skip/unsupported" $OUTFILE > /dev/null; then
        RESULT=Skip
        RESULT_MSG="${COLOR_WARNING}Skipped (No platform support)${COLOR_NORMAL}"
      elif egrep -e "R: .*$ESCAPED_TEST Skip/needs_work" $OUTFILE > /dev/null; then
        RESULT=Skip
        RESULT_MSG="${COLOR_WARNING}Skipped (Test needs work)${COLOR_NORMAL}"
      else
        RESULT=FAIL
        RESULT_MSG="${COLOR_FAILURE}FAIL${COLOR_NORMAL}"
      fi
    else
      if [ $RETVAL -ne 0 ]; then
        RESULT=Pass
        RESULT_MSG="${COLOR_SUCCESS}Pass${COLOR_NORMAL}"
      else
        RESULT=FAIL
        RESULT_MSG="${COLOR_FAILURE}FAILed to crash${COLOR_NORMAL}"
      fi
    fi

    if [ "x$RESULT_MSG" = "x" ]; then
      RESULT_MSG=$RESULT
    fi

    if [ "$RESULT" = "FAIL" ] && [ -e .succeeded/"$FLATNAME" ]; then
      RESULT_MSG="${COLOR_FAILURE}$RESULT_MSG (UNEXPECTED FAILURE)${COLOR_NORMAL}"
    fi
  fi


  if [ "$RESULT" = "XFAIL" ]; then
    echo "    <testcase name=\"$(basename $TEST)\"" >> "$XMLTMP"
    echo "              classname=\"$TEST $RESULT_MSG\"" >> "$XMLTMP"
    echo "              time=\"$(($TEST_END_TIME - $TEST_START_TIME)) seconds\">" >> "$XMLTMP"
  elif [ "$RESULT" != Wait ]; then
    echo "    <testcase name=\"$(basename $TEST)\"" >> "$XMLTMP"
    echo "              classname=\"$TEST\"" >> "$XMLTMP"
    echo "              time=\"$(($TEST_END_TIME - $TEST_START_TIME)) seconds\">" >> "$XMLTMP"
  fi
  

  case "$RESULT" in
    Pass)
      echo $RESULT $TEST >> "$SUMMARY"
      PASSED_TESTS=$(($PASSED_TESTS + 1))

      mkdir -p '.succeeded'
      touch .succeeded/"$FLATNAME"
      ;;

    FAIL|XFAIL)
      echo $RESULT $TEST >> "$SUMMARY"
      ( echo "        <failure type=\"$RESULT\""
        echo "                 message=\"$RESULT_MSG $TEST\">"
        cat $OUTFILE | sed -e "s/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/'/\&quot;/g"
        echo "        </failure>"
      ) >> "$XMLTMP"
      FAILED_TESTS=$(($FAILED_TESTS + 1))
      if [ "$RESULT" = "XFAIL" ]; then
        SUPPRESSED_FAILURES=$(($SUPPRESSED_FAILURES + 1))
      fi
      ;;

    Skip)
      echo $RESULT $TEST >> "$SUMMARY"
      ( echo "        <skipped type=\"$RESULT_MSG\">"
        echo "        </skipped>"
      ) >> "$XMLTMP"
      SKIPPED_TESTS=$(($SKIPPED_TESTS + 1))
      ;;
  esac

  if [ "$RESULT" != Wait ]; then
    echo "    </testcase>" >> "$XMLTMP"
  fi

  if [ "$RESULT" != "FAIL" -a "$RESULT" != "Wait" -a "$NO_CLEAN" = "0" ]; then
    $GAINROOT rm -rf "$WORKDIR"
  fi

  if [ -z "$QUIET" ]; then
    echo $RESULT_MSG
  else
    if [ "$RESULT" = Pass ]; then
      printf '.'
    elif [ "$RESULT" = Skip ]; then
      printf '-'
    elif [ "$RESULT" = FAIL ]; then
      if [ -n "$EXPECTED_CRASH" ]; then
        printf '!'
      else
        printf 'x'
      fi
    fi
  fi

  (
    echo
    echo '  ==>' "$RESULT_MSG"
    echo
  ) >> "$LOG"
}

ORIG_ARGS=
while true; do
  case "$1" in
    -h|--help)
      usage
      exit;;
    -q|--quiet)
      QUIET=1;;
    --gainroot=*)
      GAINROOT=${1#--gainroot=};;
    --valgrind)
      USE_VALGRIND=1;;
    --callgrind)
      USE_VALGRIND=1;
      VALGRIND_OPTS="--suppressions=valgrind-suppressions --tool=callgrind";;
    --staging)
      STAGING_TESTS=1;;
    --unsafe)
      UNSAFE_TESTS=1;;
    --no-network)
      NETWORK_TESTS=0;;
    --no-libxml2)
      LIBXML2_TESTS=0;;
    --no-crashing)
      CRASHING_TESTS=0;;
    --no-timed)
      TIMED_TESTS=0;;
    --agent=*)
      AGENT=${1#--agent=};;
    --baseclasses=*)
      BASECLASSES=${1#--baseclasses=};;
    --bc=*)
      BASECLASSES=${1#--bc=};;
    --extraclasses=*)
      EXTRACLASSES=${1#--extraclasses=};;
    --ec=*)
      EXTRACLASSES=${1#--ec=};;
    --cfpromises=*)
      CF_PROMISES=${1#--cfpromises=};;
    --cfserverd=*)
      CF_SERVERD=${1#--cfserverd=};;
    --cfkey=*)
      CF_KEY=${1#--cfkey=};;
    --bindir=*)
      BINDIR=${1#--bindir=};;
    --libtool=*)
      LIBTOOL=${1#--libtool=};;
    --printlog)
      PRINTLOG=1;;
    --gdb)
      GDB=1;;
    --no-clean)
      NO_CLEAN=1;;
    -*)
      echo "Unknown option: $1"
      exit 1;;
    *)
      break;;
  esac
  # Make sure spaces are preserved by escaping them.
  ORIG_ARGS="$ORIG_ARGS $(echo "$1" | sed -e 's/ /\\ /g')"
  shift
done

if [ "$OSTYPE" = "msys" ] && [ "$TESTALL_DO_NOT_RECURSE" != 1 ] &&
    ! "$WHOAMI" -priv | grep SeTakeOwnershipPrivilege > /dev/null; then
  # On Windows we run the entire test run under GAINROOT, because doing it for
  # each test is horribly slow.
  echo "export GAINROOT=" > runtests.sh
  echo "export TESTALL_DO_NOT_RECURSE=1" >> runtests.sh
  echo "export VERBOSE='$VERBOSE'" >> runtests.sh
  echo "$0 $ORIG_ARGS $@ &" >> runtests.sh
  # Note quote change. We want to keep below variables unexpanded.
  echo 'trap "kill $!" INT' >> runtests.sh
  echo 'trap "kill $!" TERM' >> runtests.sh
  # Traps do not fire during commands, but *do* fire during wait.
  echo 'wait $!' >> runtests.sh
  $GAINROOT "./runtests.sh"
  exit $?
fi

if [ -n "$AGENT" -o -n "$CF_PROMISES" -o -n "$CF_SERVERD" -o -n "$CF_KEY" ]; then
  if [ -n "$BINDIR" ]; then
    echo "--bindir is mutually exclusive with --agent and --cf* arguments"
    exit 2
  fi
fi

# We assume we're running this script from $objdir, $objdir/tests/acceptance,
# or /var/cfengine/tests/acceptance.
find_default_binary()
{
    [ -n "$BINDIR" -a -x "$BINDIR/$2" ] && eval $1=\""$BINDIR/$2"\"
    [ -x "`pwd`/$2/$2" ] && eval $1=\""`pwd`/$2/$2"\"
    [ -x "`pwd`/../../$2/$2" ] && eval $1=\""`pwd`/../../$2/$2"\"
    [ -x "`pwd`/../../bin/$2" ] && eval $1=\""`pwd`/../../bin/$2"\"
}
find_default_binary DEFAGENT cf-agent
find_default_binary DEFCF_PROMISES cf-promises
find_default_binary DEFCF_SERVERD cf-serverd
find_default_binary DEFCF_KEY cf-key

[ -x "`pwd`/libtool" ] && DEFLIBTOOL="`pwd`/libtool"
[ -x "`pwd`/../../libtool" ] && DEFLIBTOOL="`pwd`/../../libtool"

AGENT=${AGENT:-${DEFAGENT}}
CF_PROMISES=${CF_PROMISES:-${DEFCF_PROMISES}}
CF_SERVERD=${CF_SERVERD:-${DEFCF_SERVERD}}
CF_KEY=${CF_KEY:-${DEFCF_KEY}}
LIBTOOL=${LIBTOOL:-${DEFLIBTOOL}}

if [ ! -x "$AGENT" -o ! -x "$CF_PROMISES" -o ! -x "$CF_SERVERD" -o ! -x "$CF_KEY" ]
then
	echo "ERROR can't find cf-agent or other binary. Are you sure you're running this from OBJDIR or OBJDIR/tests/acceptance?  Check '$AGENT', '$CF_PROMISES', '$CF_SERVERD', and '$CF_KEY'"
	exit 1
fi

if [ "$UNSAFE_TESTS" = "1" ]; then
    if [ "$GAINROOT" = "fakeroot" ]; then
        echo "Unsafe tests do not play well together with fakeroot. Please use a different"
        echo "--gainroot (like \"sudo\"), or you will get incorrect results."
        exit 1
    fi

    # Make sure test dir is accessible to everyone, because unsafe tests may
    # switch user during the test (users promises do this).
    DIR="$(cd "$(dirname "$0")"; pwd)"
    while [ "$DIR" != "/" -a "$DIR" != "" ]; do
        $GAINROOT chmod go+rx "$DIR"
        DIR="$(dirname "$DIR")"
    done
fi

if [ $# -gt 0 ]; then
  # We run all specified tests according to the defaults (no unsafe ones).
  for test in "$@"; do
    if ! expr "$test" : '[/.]' >/dev/null; then
      test="./$test"
    fi

    if [ -f $test ]; then
      ADDTESTS="$ADDTESTS${ADDTESTS:+ }$test"
    elif [ -d $test ]; then
      ADDTESTS="$ADDTESTS${ADDTESTS:+ }$(find "$test" -name '*.cf' | sort)"
    else
      echo "Unable to open test file/directory: $test"
    fi
  done
else
  ADDTESTS="$ADDTESTS${ADDTESTS:+ }$(find . -name '*.cf' | sort)"
fi

for addtest in $ADDTESTS; do
  if echo "$addtest" | fgrep "/timed/" > /dev/null; then
    if [ "$TIMED_TESTS" = 1 ]; then
      eval TESTS_TIMED_$TEST_TIMED_INDEX="$addtest"
      eval TESTS_TIMEOUT_$TEST_TIMED_INDEX=0
      eval TESTS_PASSES_$TEST_TIMED_INDEX=0
      TEST_TIMED_INDEX=$(($TEST_TIMED_INDEX+1))
    fi
  else
    eval TESTS_$TEST_INDEX="$addtest"
    TEST_INDEX=$(($TEST_INDEX+1))
  fi

done

#
# fd 7 is a /dev/null for quiet execution and stdout for default one
#
if [ -z "$QUIET" ]; then
  exec 7>&1
else
  exec 7>/dev/null
fi

#
# Now run the tests
#

TESTS_NORMAL_COUNT=$TEST_INDEX
TESTS_TIMED_COUNT=$TEST_TIMED_INDEX
TESTS_COUNT=$(($TESTS_NORMAL_COUNT + $TESTS_TIMED_COUNT))
TESTS_TIMED_REMAINING=$TEST_TIMED_INDEX
START_TIME=$(unix_seconds)

( echo ======================================================================
  echo Testsuite started at $(date "+%Y-%m-%d %T")
  echo ----------------------------------------------------------------------
  echo Total tests: $TESTS_COUNT
  echo
  if [ ${CRASHING_TESTS} -ne 0 ]; then
    echo CRASHING_TESTS: enabled
  else
    echo CRASHING_TESTS: disabled
  fi
  if [ ${NETWORK_TESTS} -ne 0 ]; then
    echo NETWORK_TESTS: enabled
  else
    echo NETWORK_TESTS: disabled
  fi
  if [ ${STAGING_TESTS} -ne 0 ]; then
    echo STAGING_TESTS: enabled
  else
    echo STAGING_TESTS: disabled
  fi
  if [ ${UNSAFE_TESTS} -ne 0 ]; then
    echo UNSAFE_TESTS: enabled
  else
    echo UNSAFE_TESTS: disabled
  fi
  if [ ${LIBXML2_TESTS} -ne 0 ]; then
    echo LIBXML2_TESTS: enabled
  else
    echo LIBXML2_TESTS: disabled
  fi
  echo
) | tee "$LOG" | tee "$SUMMARY" >&7


( echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
  echo "<testsuite name=\"$(pwd)\""
  echo "           timestamp=\"$(date "+%F %T")\""
  echo "           hostname=\"localhost\""
  echo "           tests=\"$TESTS_COUNT\""
) > "$XML"

  echo -n "" > "$XMLTMP"

check_and_run_timed_tests() {
  TEST_TIMED_INDEX=0
  time=$(unix_seconds)
  # Run timed tests if any deadlines have expired.
  while [ $TEST_TIMED_INDEX -lt $TESTS_TIMED_COUNT ]; do
    eval test=\$TESTS_TIMED_$TEST_TIMED_INDEX
    eval timeout=\$TESTS_TIMEOUT_$TEST_TIMED_INDEX
    if [ -n "$timeout" ] && [ "$time" -ge "$timeout" ]; then
      eval TESTS_PASSES_$TEST_TIMED_INDEX="\$((\$TESTS_PASSES_$TEST_TIMED_INDEX+1))"
      eval pass=\$TESTS_PASSES_$TEST_TIMED_INDEX
      runtest "$AGENT" "$test" "$pass" "TESTS_TIMEOUT_$TEST_TIMED_INDEX"
      eval timeout=\$TESTS_TIMEOUT_$TEST_TIMED_INDEX
      if [ -z "$timeout" ]; then
        TESTS_TIMED_REMAINING=$(($TESTS_TIMED_REMAINING - 1))
      fi
    fi
    TEST_TIMED_INDEX=$(($TEST_TIMED_INDEX+1))
  done
}

TEST_INDEX=0
while [ $TEST_INDEX -lt $TESTS_NORMAL_COUNT -o $TESTS_TIMED_REMAINING -gt 0 ]; do
  check_and_run_timed_tests

  # Run normal test.
  if [ $TEST_INDEX -lt $TESTS_NORMAL_COUNT ]; then
    eval test=\$TESTS_$TEST_INDEX
    runtest "$AGENT" "$test"
    TEST_INDEX=$(($TEST_INDEX+1))
  elif [ $TESTS_TIMED_REMAINING -gt 0 ]; then
    sleep 1
  fi
done

END_TIME=$(unix_seconds)

( echo
  echo ======================================================================
  echo "Testsuite finished at $(date  "+%F %T") ($(($END_TIME - $START_TIME)) seconds)"
) | tee -a "$LOG" | tee -a "$SUMMARY" >&7

( echo
  echo Passed tests: $PASSED_TESTS
  echo -n Failed tests: $FAILED_TESTS
  if [ $SUPPRESSED_FAILURES -gt 0 ]; then
    echo " ($SUPPRESSED_FAILURES are known and suppressed)"
  else
    echo
  fi
  echo Skipped tests: $SKIPPED_TESTS
  echo Total tests: $TESTS_COUNT
) | tee -a "$LOG" | tee -a "$SUMMARY"

(  echo "           failures=\"$FAILED_TESTS\""
   echo "           skipped=\"$SKIPPED_TESTS\""
   echo "           time=\"$(($END_TIME - $START_TIME)) seconds\">"
) >> "$XML"

  cat "$XMLTMP" >> "$XML"

  echo "</testsuite>" >> "$XML"

if [ -n "$PRINTLOG" ]; then
  cat "$LOG"
fi

if [ "$FAILED_TESTS" -gt "$SUPPRESSED_FAILURES" ]; then
  exit 1
else
  exit 0
fi

