#!/bin/bash
set -e
APP_SVNID='$HeadURL: http://dione.no-ip.org/svn/ade/tags/1.7.6/bin/adetest.sh $ $LastChangedRevision: 5290 $'

. $(ade-config ade_include_prefix)/ade.sh


ADETEST_DEFINED_ERRORS=(
    "KEY=ADETEST_ERR_MISC; FMT=\"%s\""
    "KEY=ADETEST_ERR_ACCESS; FMT=\"%s: can't %s\""
    "KEY=ADETEST_ERR_OS; FMT=\"%s: failed\""
)

adetest()
{
    local OPTVAL LISTPATHS RC TEMP ERRSTACK_REF
    local ISRELABELABLE_FLAG SANDPIT_DIR REF_FILE OUT_FILE DIR TEST_CMDS
    local VERBOSELEVEL
    local -a DOLLARAT

    ERRSTACK_REF="$1"
    shift 

    ###########################################################################
    #
    #  SET ADE OPTIONS
    #
    ###########################################################################

    #  Register application-specific errors
    ade_err_registerdefderrs ADETEST_DEFINED_ERRORS

    ###########################################################################
    #
    #  PROCESS OPTIONS
    #
    ###########################################################################

    #  Defaults for options
    OPT_ARGSARELISTGENERATORS_FLAG=false
    OPT_MODE=test
    OPT_KEEPOUTFILES_FLAG=false
    OPT_MAKE_CMD=make

    #  Register adetest's options
    ade_opt_register "$ERRSTACK_REF" -o gtrk --longoptions=generate,args-are-generators,test,run,keep-output-files --callback-template="adetest_opt_handler_%s" || return $?
    ade_msg_register "$ERRSTACK_REF" adetest_usage adetest_version adetest_listpaths || return $?

    #  Process options
    ade_opt_process "$ERRSTACK_REF" NEW_DOLLAR_AT "$@" || return $?
    set -- "${NEW_DOLLAR_AT[@]}"

    ##########################################################################
    #
    #  ARGUMENT PROCESSING
    #
    ##########################################################################

    [ "X$1" = X ] && ade_msg_usage "$ERRSTACK_REF"
    #  The exact interpreation of the arguments (whether as test generators
    #  or actual tests is made a little bir further below)
    
    ##########################################################################
    #
    #  FORKING AND LOCKING
    #
    ##########################################################################

    #ade_lck_lock "$ERRSTACK_REF" DUMMY ade_lck_getgenericlockfilename || {
    #    RC=$?
    #    ade_err_error "$ERRSTACK_REF" ADETEST_ERR_SEEABOVE
    #    return $RC
    #}

    ##########################################################################
    #
    #	GUTS START HERE
    #
    ##########################################################################

    #
    #  There is a good chance this program is running under make and make
    #  will have contaminated the environment. Amongst other things this
    #  leads to make saying 'Entering <directory>' because it sees a non-empty
    #  MAKELEVEL environment variable. We want the behaviour to be the same
    #  when run directly and when run from a Makefile. So we we unset all the
    #  infectious settings.
    #

    unset MAKEFLAGS
    unset MAKEOVERRIDES
    unset MAKELEVEL

    #
    #  Find the top of the module
    #

    MODULE_ROOT_DIR=
    DIR=$(pwd)
    while [ "X$DIR" != X ]; do
        [ ! -d $DIR/tests/ref ] || { MODULE_ROOT_DIR=$DIR; break; }
        DIR=${DIR%/*}
    done
    [ "X$MODULE_ROOT_DIR" != X ] || {
        ade_err_error "$ERRSTACK_REF" ADETEST_ERR_MISC "failed to find module root"
        return $ADE_ERR_FAIL
    }

    #
    #  For each test ...
    #

    ALL_TESTS_PASSED=true
    #  Probably the test generator is specified as just 'testlist' and 
    #  that won't be found since it mentions no directories and '.' is
    #  probably not in $PATH; so we need to fix that by adding it.
    local -a TEST_CMDS
    ade_err_debug "$ERRSTACK_REF" 5 "adetest: collecting test commands ..."
    if $OPT_ARGSARELISTGENERATORS_FLAG; then
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: arguments are supposed to to be test list generators; calling ..."
        TEST_CMDS=($(eval "PATH=$PATH:.; \"\$@\"")) || {
            ade_err_error "$ERRSTACK_REF" ADETEST_ERR_MISC "test list generator failed"
            return $ADE_ERR_FAIL
        }
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: TEST_CMDS (generated)=${TEST_CMDS[@]}"
    else
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: arguments are supposed to to be tests"
        TEST_CMDS=("$@");
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: TEST_CMDS (listed)=${TEST_CMDS[@]}"
    fi
    ade_err_debug "$ERRSTACK_REF" 5 "adetest: TEST_CMDS=\"$TEST_CMDS\""

    for TEST_CMD in ${TEST_CMDS[@]}; do
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: TEST_CMD=$TEST_CMD ..."

        #  Check for is '..../bin/something'
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: validating filename pattern ..."
        expr "$TEST_CMD" : 'bin/[^/][^/]*$' > /dev/null || expr "$TEST_CMD" : '.*/bin/[^/][^/]*$' > /dev/null || {
            ade_err_error "$ERRSTACK_REF" ADETEST_ERR_MISC "$TEST_CMD: isn't in a 'bin' directory"
            return $ADE_ERR_FAIL
        }

        #  Make the test script
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: making the test script with \"cd $(dirname $TEST_CMD) \&\& $OPT_MAKE_CMD -s $(basename $TEST_CMD)\" ..."
        [ -d $(dirname $TEST_CMD) ] || {
            ade_err_error "$ERRSTACK_REF" ADETEST_ERR_ACCESS "$(dirname $TEST_CMD) (test's directory)" "access"
            return $ADE_ERR_FAIL
        }
        ( cd $(dirname $TEST_CMD) && $OPT_MAKE_CMD -s $(basename $TEST_CMD) ) || {
            ade_err_error "$ERRSTACK_REF" ADETEST_ERR_OS $OPT_MAKE_CMD
            return $ADE_ERR_FAIL
        }

        #  Check test exists (don't check before the 'make' paragraph above)
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: checking test accessibility ..."
        [ -x "$TEST_CMD" ] || {
            ade_err_error "$ERRSTACK_REF" ADETEST_ERR_ACCESS $TEST_CMD "execute"
            return $ADE_ERR_FAIL
        }

        #  Derive name of reference file and output file
	REF_FILE=`echo $TEST_CMD | sed 's/bin\/\([^\/][^\/]*\)$/ref\/\1/'`
	OUT_FILE=$ADE_TMP_DIR/$ADE_APP_PROGNAME.$$.$(basename $TEST_CMD).out
        SANDPIT_DIR=$ADE_TMP_DIR/$ADE_APP_PROGNAME.$$.$(basename $TEST_CMD).sandpit

        #  Prepare sandbox
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: registering sandbox (SANDPIT_DIR=$SANDPIT_DIR) ..."
        ade_tmp_registerfile "$ERRSTACK_REF" $SANDPIT_DIR || return $?
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: creating sandbox (SANDPIT_DIR=$SANDPIT_DIR) ..."
        mkdir -p "$SANDPIT_DIR"
        ade_err_debug "$ERRSTACK_REF" 5 "adetest: created sandbox (SANDPIT_DIR=$SANDPIT_DIR)"
    
        #  This program needs to know the verbosity as it displays some messages not in
        #  standard format. But $ADE_MSG_VERBOSELEVEL is to be considered private to ade.sh
        #  so we use a function to retrieve it into a local variable.
        ade_msg_getverboselevel "$ERRSTACK_REF" VERBOSELEVEL || return $?
       
        #  Generate reference file
        if [ $OPT_MODE = generate ]; then
            ade_err_debug "$ERRSTACK_REF" 10 "adetest/generate: REF_FILE=$REF_FILE, PWD=$PWD"
            [ $VERBOSELEVEL -ge 3 ] && printf "%-60s" "`basename $TEST_CMD`:" >&2
            ade_tmp_registerfile "$ERRSTACK_REF" $REF_FILE || return $?
            touch $REF_FILE 2> /dev/null || {
                ade_err_error "$ERRSTACK_REF" ADETEST_ERR_ACCESS $REF_FILE "create"
                return $ADE_ERR_FAIL
            }
            execute_test "$ERRSTACK_REF" "$TEST_CMD" $SANDPIT_DIR RC > $REF_FILE || return $?
            if [ $RC != 0 ]; then
                [ $VERBOSELEVEL -ge 3 ] && echo "failed (exit code $RC)"
                ALL_TESTS_PASSED=false
            else
                [ $VERBOSELEVEL -ge 3 ] && echo "generated"
            fi
            ade_err_debug "$ERRSTACK_REF" 10 "adetest/generate: about to deregister $REF_FILE ..."
            ade_tmp_deregisterfile "$ERRSTACK_REF" $REF_FILE || return $?

        #  Make the test
        elif [ $OPT_MODE = test ]; then
            ade_err_debug "$ERRSTACK_REF" 10 "adetest/test: checking reference file accessibility ..."
	    [ -r $REF_FILE ] || {
                ade_err_error "$ERRSTACK_REF" ADETEST_ERR_ACCESS $REF_FILE "access"
                return $ADE_ERR_FAIL
            }
            [ $VERBOSELEVEL -ge 3 ] && printf "%-70s" "`basename $TEST_CMD`:" >&2
            ade_tmp_registerfile "$ERRSTACK_REF" $OUT_FILE || return $?
            touch $OUT_FILE 2> /dev/null || {
                ade_err_error "$ERRSTACK_REF" ADETEST_ERR_ACCESS $OUT_FILE "create"
                return $ADE_ERR_FAIL
            }
            execute_test "$ERRSTACK_REF" "$TEST_CMD" $SANDPIT_DIR RC > $OUT_FILE || return $?
            if [ $RC != 0 ]; then
                [ $VERBOSELEVEL -ge 3 ] && echo "failed (exit code $RC)" >&2
                ALL_TESTS_PASSED=false
            elif diff $REF_FILE $OUT_FILE > /dev/null; then
                [ $VERBOSELEVEL -ge 3 ] && echo "passed" >&2
            else
                [ $VERBOSELEVEL -ge 3 ] && echo "failed (differs from ref)" >&2
                ALL_TESTS_PASSED=false
            fi
            if $OPT_KEEPOUTFILES_FLAG; then
                [ $VERBOSELEVEL -ge 3 ] && { 
                    echo "    reference: $REF_FILE" >&2
                    echo "    output: $OUT_FILE" >&2
                }
            else
                rm -f $OUT_FILE
            fi
            ade_tmp_deregisterfile "$ERRSTACK_REF" $OUT_FILE || return $?

        elif [ $OPT_MODE = run ]; then
            ade_err_debug "$ERRSTACK_REF" 10 "adetest/run: "
            execute_test "$ERRSTACK_REF" "$TEST_CMD" $SANDPIT_DIR RC || return $?
            if [ $RC != 0 ]; then
                [ $VERBOSELEVEL -ge 3 ] && echo "failed (exit code $RC)" >&2
                ALL_TESTS_PASSED=false
            fi
        else
            ade_err_internal "$ERRSTACK_REF" "adetest: illegal OPT_MODE=$OPT_MODE"
        fi

        #  Purge sandbox
        if $OPT_KEEPOUTFILES_FLAG; then
            [ $VERBOSELEVEL -ge 3 ] && echo "    sandpit: $SANDPIT_DIR" >&2
        else
            rm -fr $SANDPIT_DIR
        fi
        ade_tmp_deregisterfile "$ERRSTACK_REF" $SANDPIT_DIR || return $?
    done
    [ $ALL_TESTS_PASSED = true ] || {
        ade_err_error "$ERRSTACK_REF" ADETEST_ERR_MISC "at least one test failed"
        return $ADE_ERR_FAIL
    }

    return $ADE_ERR_OK
}

adetest_opt_handler_g()
{
    adetest_opt_handler_generate "$@"
}

adetest_opt_handler_t()
{
    adetest_opt_handler_test "$@"
}

adetest_opt_handler_r()
{
    adetest_opt_handler_run "$@"
}

adetest_opt_handler_k()
{
    adetest_opt_handler_keep_output_files "$@"
}

adetest_opt_handler_generate()
{
    OPT_MODE=generate
}

adetest_opt_handler_args_are_generators()
{
    OPT_ARGSARELISTGENERATORS_FLAG=true
}

adetest_opt_handler_test()
{
    OPT_MODE=test
}

adetest_opt_handler_run()
{
    OPT_MODE=run
}

adetest_opt_handler_keep_output_files()
{
    OPT_KEEPOUTFILES_FLAG=true
}

execute_test()
{
    local ERRSTACK_REF="$1"; shift
    local TEST_CMD="$1"; shift
    local SANDPIT_DIR="$1"; shift
    local RC_REF="$1"; shift
    local PWD OLD_PWD DIR ABS_DIR

    PWD=`pwd`

    #  Make the test command absolute so we can run it from the sandpit.
    ade_err_debug "$ERRSTACK_REF" 10 "execute_test: making \"$TEST_CMD\" absolute ..."
    ade_fnm_makeabsolute "$ERRSTACK_REF" "$TEST_CMD" TEST_CMD || {
        ade_err_internal "$ERRSTACK_REF" "execute_test: ade_fnm_makeabsolute() failed"
        return $ADE_ERR_FAIL
    }
    ade_err_debug "$ERRSTACK_REF" 10 "execute_test: TEST_CMD=$TEST_CMD"

    #  Make the test configuration directory absolute
    ade_err_debug "$ERRSTACK_REF" 10 "execute_test: making test configuration directory absolute ..."

    #  Prepare the test environment
    ade_err_debug "$ERRSTACK_REF" 10 "execute_test: preparing the test environment ..."
    #  Add the module's bin and sbin to the $PATH that will be used for the test
    TEST_PATH=$PATH
    for DIR in sbin bin; do
        TEST_PATH="$MODULE_ROOT_DIR/$DIR:$TEST_PATH"
    done

    #  Add the module's include directory to PERL5LIB (so mkfad can find FAD.pm, etc)
    TEST_PERL5LIB=$PERL5LIB
    for DIR in include; do
        TEST_PERL5LIB="$MODULE_ROOT_DIR/$DIR:$TEST_PERL5LIB"
    done

    #  some module include tests running dynamically linked commands with libraries provided by the module
    TEST_LD_LIBRARY_PATH=$(ade-config ade_lib_prefix):$LD_LIBRARY_PATH

    #  Determine verbosity level for test
    #  Some of the tests will invoke 'make'; we need to make sure that they invoke the right make.
    TEST_MAKE_CMD=$OPT_MAKE_CMD

    #  Create the sandpit in which to execute the test
    ade_err_debug "$ERRSTACK_REF" 10 "execute_test: creating a sandpit ..."
    OLD_PWD=`pwd`
    cd $SANDPIT_DIR
    mkdir $SANDPIT_DIR/tmpdir.$$

    #  Call the test
    ade_err_debug "$ERRSTACK_REF" 10 "execute_test: calling \"ADETEST_MODROOT=$MODULE_ROOT_DIR MAKE=$TEST_MAKE_CMD LD_LIBRARY_PATH=$TEST_LD_LIBRARY_PATH PATH=$TEST_PATH PERL5LIB=$TEST_PERL5LIB TMP_DIR=$SANDPIT_DIR/tmpdir.$$ $TEST_CMD 2>&1\" ..."
    ADETEST_MODROOT=$MODULE_ROOT_DIR MAKE=$TEST_MAKE_CMD LD_LIBRARY_PATH=$TEST_LD_LIBRARY_PATH PATH=$TEST_PATH PERL5LIB=$TEST_PERL5LIB TMP_DIR=$SANDPIT_DIR/tmpdir.$$ $TEST_CMD 2>&1
    eval "$RC_REF=$?"
    cd $OLD_PWD
    
    return $ADE_ERR_OK
}

adetest_usage()
{
    local ERRSTACK_REF="$1"; shift 
    local USAGETEXT_REF="$1"; shift
    local PASSNO=$1; shift

    if [ $PASSNO = 1 ]; then
        eval "$USAGETEXT_REF=\"<testfile>\""
    elif [ $PASSNO = 2 ]; then
        eval "$USAGETEXT_REF=\"\
         -g        | --generate              generate mode
         -t        | --test                  test mode (default)
         -r        | --run                   run mode
         -k        | --keep-output-files     don't delete test output
                     --args-are-generators   args are generators\""
    fi

    return $ADE_ERR_OK
}

adetest_listpaths()
{
    local ERRSTACK_REF="$1"; shift
    local PATHLIST_REF=$1; shift

    eval "$PATHLIST_REF=\"\
    \""

    return $ADE_ERR_OK
} 

adetest_version() 
{
    local ERRSTACK_REF="$1"; shift
    local VERSION_REF=$1; shift

    ade_smf_extractversionfromsvnstring "$ERRSTACK_REF" "$APP_SVNID" "$VERSION_REF"

    return $ADE_ERR_OK
} 

ade_gep_main adetest "$@"
