#!/bin/bash -e

# The linuxcnc starter script sometimes tries to display X windows if
# DISPLAY is set.  We never want that while running tests, so unset it.
unset DISPLAY

# Some of our tests emit locale-sensitive strings, so reset the locale
# to a sane default.
export LC_ALL=C


case "$0" in
	*/*) MYDIR="${0%/*}" ;;
	*) MYDIR="`type -path $0`"; MYDIR="${MYDIR%/*}"
esac
MYDIR=$(cd $MYDIR; pwd);
TOPDIR=$(cd $MYDIR/..; pwd)

. $TOPDIR/scripts/rip-environment >/dev/null

NUM=0
FAIL=0; FAIL_NAMES=""
XFAIL=0
VERBOSE=0

clean () {
    find $* \( -name "stderr" -or -name "result" \
	-or -name "*.var" -or -name "*.var.bak" \) \
	-print0 | xargs -0 rm -f
}

run_and_log() {
    # Execute command line args with stderr teed to ./stderr and stdout
    # teed to ./result, ensuring that 'tee' subprocesses are finished
    # writing before exiting
    #
    # http://mywiki.wooledge.org/BashFAQ/106

    errpipe=pipe-stderr-$$
    outpipe=pipe-stdout-$$

    # create two named pipes (and ensure they're cleaned up)
    trap "rm -f $errpipe $outpipe; exit" EXIT
    mkfifo $errpipe $outpipe

    # start a 'tee' reading from each named pipe and echoing to stdout and
    # stderr; note PIDs
    tee stderr < $errpipe >&2 & errpid=$!
    tee result < $outpipe & outpid=$!

    # redirect this process's stdout and stderr to the named pipes
    exec 1>$outpipe 2>$errpipe

    # run the command given on the command line, capturing exit status
    exitcode=0
    $@ || exitcode=$?

    # close this process's stdout and stderr FDs so the 'tee' processes
    # get EOF on the named pipes
    exec >&- 2>&-

    # wait for the 'tee' processes to finish writing
    wait $errpid $outpid

    # return captured status
    return $exitcode
}

run_test () {
    case $1 in
	-s) INTERP="bash -x"; shift ;;
	-h) INTERP="halrun -f"; shift ;;
	*) INTERP="" ;;
    esac

    testname=$(basename $1)
    testdir=$(dirname $1)

    pushd $testdir > /dev/null
    exitcode=0
    if [ $VERBOSE -eq 1 ]; then
	$0 -l $INTERP $testname || exitcode=$?
    else
	$0 -l $INTERP $testname >&/dev/null || exitcode=$?
    fi
    popd > /dev/null
    return $exitcode
}

run_without_overruns () {
    test=$1
    testname=$(basename $test)
    testdir=$(dirname $test)
    exitcode=0
    for i in $(seq 10); do
        if [ $i != 1 ]; then
	    echo "--- $testdir: overrun detected in sampler, " \
		"re-running test" >&2
	fi

	run_test -h $test || exitcode=$?

        if ! grep -q '^overrun$' $testdir/result; then return $exitcode; fi
    done
    echo "--- $testdir: $i overruns detected, giving up" 1>&2
    return 1
}

run_tests () {
    alltests="$(find $* -name test.hal -or -name test.sh -or -name test \
        | sort)"

    for testname in $alltests; do
	testdir=$(dirname $testname)
	if [ -e $testdir/skip ]; then
	    if ! [ -x $testdir/skip ] || ! $testdir/skip; then
		echo "Skipping test: $testdir" 1>&2
		continue
	    fi
	fi
	NUM=$(($NUM+1))
	logger -p local1.info "Running test: $testdir"
	echo "Running test: $testdir" 1>&2
	exitcode=0
        case $testname in
        *.hal) run_without_overruns $testname || exitcode=$? ;;
        *.sh) run_test -s $testname || exitcode=$? ;;
        *) run_test $testname || exitcode=$? ;;
        esac
	if [ $exitcode -ne 0 ]; then
	    reason="test run exited with $exitcode"
	else
	    if [ -e $testdir/checkresult ]; then
		exitcode=0
		$testdir/checkresult $testdir/result || exitcode=$?
		reason="checkresult exited with $exitcode"
	    elif [ -f $testdir/expected ]; then
		exitcode=0
		if ! diff --ignore-trailing-space -u $testdir/expected $testdir/result > \
		    $TMPDIR/diff; then
		    exitcode=1
		    reason="result differed from expected"
		    SIZE=$(wc -l < $TMPDIR/diff)
		    if [ $SIZE -lt 40 ]; then
			cat $TMPDIR/diff
		    else
			OMIT=$((SIZE-40))
			head -40 $TMPDIR/diff
			echo "($OMIT more lines omitted)"
		    fi
		fi
	    else
		exitcode=1
		reason="Neither expected nor checkresult existed"
	    fi
	fi
	if [ $exitcode -ne 0 ]; then
	    if [ -f $testdir/xfail ]; then
		XFAIL=$(($XFAIL+1))
		echo "*** $testdir: XFAIL: $reason"
		if [ $NOCLEAN -eq 0 ]; then
		    rm -f $testdir/stderr $testdir/result \
			$testdir/*.var $testdir/*.var.bak
		fi
	    else
		FAIL=$(($FAIL+1))
		FAIL_NAMES="$FAIL_NAMES
	$testdir"
		echo "*** $testdir: FAIL: $reason"
	    fi
            if test $STOP = 1; then
	        break
	    fi
	else
	    if [ -f $testdir/xfail ]; then
		echo "*** $testdir: XPASS: Passed, but was expected to fail"
	    else
		if [ $NOCLEAN -eq 0 ]; then
		    rm -f $testdir/stderr $testdir/result \
			$testdir/*.var $testdir/*.var.bak
		fi
	    fi
	fi
    done

    SUCC=$((NUM-FAIL-XFAIL))
    echo "Runtest: $NUM tests run, $SUCC successful, $FAIL failed + $XFAIL expected"
    if [ $FAIL -ne 0 ]; then
	echo "Failed: $FAIL_NAMES"
	exit 1;
    else
	exit 0;
    fi
}

usage () {
    P=${0##*/}
    cat <<EOF
$P: Run HAL test suite items

Usage:
    $P [-n] [-s] tests
	Run tests.  With '-n', do not remove temporary files for successful
	tests.  With '-s', stop after any failed test.

    $P -c tests
	Remove temporary files from an earlier test run.

    $P -v
        Show stdout and stderr (normally it's hidden).
EOF
}

# Special case:  we called ourself to log a run
if test "$1" = "-l"; then
    shift
    run_and_log "$@"
    exit $?
fi

TMPDIR=`mktemp -d /tmp/runtest.XXXXXX`
trap "rm -rf $TMPDIR" 0 1 2 3 9 15

CLEAN_ONLY=0
NOCLEAN=0
STOP=0
while getopts cnvsh opt; do
    case "$opt" in
    c) CLEAN_ONLY=1 ;;
    n) NOCLEAN=1 ;;
    v) VERBOSE=1 ;;
    s) STOP=1 ;;
    h|?) usage; exit 0 ;;
    *) usage; exit 1 ;;
    esac
done
shift $((OPTIND-1))

if [ $# -eq 0 ]; then
    if [ -f test.hal -o -f test.sh ]; then
        set -- .
    else
        set -- $TOPDIR/tests
    fi
fi

if [ $CLEAN_ONLY -eq 1 ]; then
    clean "$@"
else
    run_tests "$@"
fi
