#!/bin/bash
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   Unit testing library for BeakerLib
#   Author: Ales Zelinka <azelinka@redhat.com>
#   Author: Petr Splichal <psplicha@redhat.com>
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   Copyright (c) 2010 Red Hat, Inc. All rights reserved.
#
#   This copyrighted material is made available to anyone wishing
#   to use, modify, copy, or redistribute it subject to the terms
#   and conditions of the GNU General Public License version 2.
#
#   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., 51 Franklin Street, Fifth Floor,
#   Boston, MA 02110-1301, USA.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#   This is a simple unit testing library for BeakerLib.
#   Have a look at the README file to learn more about it.


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   Global variables
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

TotalFailed="0"
TotalPassed="0"
FileList=""
TestList=""

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertLog comment [result] --- log a comment (with optional result)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertLog() {
    local comment="$1"
    local result="${2:-INFO}"

    # colorify known results if run on terminal
    if [ -t 1 ]; then
        case $result in
            INFO) result="\033[0;34mINFO\033[0m";;
            PASS) result="\033[0;32mPASS\033[0m";;
            FAIL) result="\033[0;31mFAIL\033[0m";;
            WARN) result="\033[0;33mWARN\033[0m";;
            SKIP) result="\033[0;37mSKIP\033[0m"
                  ((__INTERNAL_ASSERT_SKIPPED++))
                  ((TotalSkipped++))
                ;;
        esac
    fi

    # echo!
    echo -e " [ $result ] $comment"
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertRun command [status] [comment] --- run command, check status, log
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertRun() {
    local command="$1"
    local expected="${2:-0}"
    local comment="${3:-Running $command}"

    # no output unless in debug mode
    if [ "$DEBUG" == "1" ]; then
        eval "$command"
    else
        eval "$command" &> /dev/null
    fi
    local status=$?

    # check status
    if [[ "$status" =~ ^$expected$ ]]; then
        assertLog "$comment" 'PASS'
        ((__INTERNAL_ASSERT_PASSED++))
        ((TotalPassed++))
    else
        assertLog "$comment" 'FAIL'
        ((__INTERNAL_ASSERT_FAILED++))
        ((TotalFailed++))
        [ "$DEBUG" == "1" ] && assertLog "Expected $expected, got $status"
    fi
}

silentIfNotDebug() {
    local command="$1"
    if [ "$DEBUG" == "1" ]
    then
      eval "$command"
    else
      eval "$command" &> /dev/null
    fi
}

journalReset() {
  rm -f $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL $__INTERNAL___INTERNAL_BEAKERLIB_METAFILE $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_TXT $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_COLORED $__INTERNAL_PRESISTENT_DATA
  [ -e "$BEAKERLIB_DIR" ] && ( chmod -R 777 $BEAKERLIB_DIR ; rm -rf $BEAKERLIB_DIR; )
  unset __INTERNAL_RPM_ASSERTED_PACKAGES
  silentIfNotDebug 'rlJournalStart'
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertStart name --- start an assert phase
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertStart() {
    local phase="$1"
    echo
    assertLog "Testing $phase"
    __INTERNAL_ASSERT_PHASE="$phase"
    __INTERNAL_ASSERT_PASSED="0"
    __INTERNAL_ASSERT_FAILED="0"
    __INTERNAL_ASSERT_SKIPPED="0"
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertEnd --- short phase summary (returns number of failed asserts)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertEnd() {
    local failed="$__INTERNAL_ASSERT_FAILED"
    local passed="$__INTERNAL_ASSERT_PASSED"
    local skipped="$__INTERNAL_ASSERT_SKIPPED"
    local name="$__INTERNAL_ASSERT_PHASE"

    if [ "$failed" -gt "0" ]; then
        assertLog "Testing $name finished: $passed passed, $failed failed, $skipped skipped" "FAIL"
    elif [ "$passed" -gt "0" ]; then
        assertLog "Testing $name finished: $passed passed, $failed failed, $skipped skipped" "PASS"
    else
        assertLog "Testing $name finished: No assserts run" "WARN"
    fi

    printf "%i:%i:%i\n" $__INTERNAL_ASSERT_PASSED $__INTERNAL_ASSERT_FAILED $__INTERNAL_ASSERT_SKIPPED>> $SCOREFILE
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertTrue comment command --- check that command succeeded
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertTrue() {
    local comment="$1"
    local command="$2"

    assertRun "$command" 0 "$comment"
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertFalse comment command --- check that command failed
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertFalse() {
    local comment="$1"
    local command="$2"
    local expects="${3:-1}"

    assertRun "$command" "$expects" "$comment"
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertGoodBad command good bad --- check for good/bad asserts in journal
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertGoodBad() {
    local command="$1"
    local good="$2"
    local bad="$3"

    if [[ -n "$good" ]]; then
        rm -f $__INTERNAL_BEAKERLIB_JOURNAL $__INTERNAL_BEAKERLIB_METAFILE $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_TXT $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_COLORED $__INTERNAL_PRESISTENT_DATA; rlJournalStart
        assertTrue "$good good logged for '$command'" \
                "rlPhaseStart FAIL; $command; rlPhaseEnd;
                rlJournalPrintText | egrep 'Assertions: *$good *good, *[0-9]+ *bad'"
    fi

    if [[ -n "$bad" ]]; then
        rm -f $__INTERNAL_BEAKERLIB_JOURNAL $__INTERNAL_BEAKERLIB_METAFILE $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_TXT $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_COLORED $__INTERNAL_PRESISTENT_DATA; rlJournalStart
        assertTrue "$bad bad logged for '$command'" \
                "rlPhaseStart FAIL; $command; rlPhaseEnd;
                rlJournalPrintText | egrep 'Assertions: *[0-9]+ *good, *$bad *bad'"
    fi
    rm -f $__INTERNAL_BEAKERLIB_JOURNAL $__INTERNAL_BEAKERLIB_METAFILE $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_TXT $__INTERNAL___INTERNAL_BEAKERLIB_JOURNAL_COLORED $__INTERNAL_PRESISTENT_DATA
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   assertParameters assert --- check missing parameters
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

assertParameters() {
	journalReset
	assertTrue "running '$1' (all parameters) must succeed" \
	"rlPhaseStart FAIL; $1 ; rlPhaseEnd ;  rlJournalPrintText |grep '1 *good'"
	local CMD=""
	for i in $1 ; do
		CMD="${CMD}${i} "
		if [ "x$CMD" == "x$1 " ] ; then break ; fi
		#echo "--$1-- --$CMD--"
		journalReset
		assertFalse "running just '$CMD' (missing parameters) must not succeed" \
	    "rlPhaseStart FAIL; $CMD ; rlPhaseEnd ;  rlJournalPrintText |grep '1 *good'"
	done
}


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   Fake rhts-report-result
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

rhts-report-result(){
  echo -e "ANCHOR NAME: $1\nRESULT: $2\nLOGFILE: $3\nSCORE: $4"
}



# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   Self test --- run a simple self test if called as 'test.sh test'
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if [ "$1" == "test" ]; then
    assertStart "logging"
        assertLog "Some comment with a pass" "PASS"
        assertLog "Some comment with a fail" "FAIL"
    assertEnd

    assertStart "passing asserts"
        assertRun "true"
        assertRun "true" 0
        assertRun "true" 0 "Checking true with assertRun"
        assertRun "false" 1
        assertRun "false" 1 "Checking false with assertRun"
        assertTrue "Checking true with assertTrue" "true"
        assertFalse "Checking false with assertFalse" "false"
    assertEnd

    assertStart "failing asserts"
        assertRun "false"
        assertRun "false" 0
        assertRun "false" 0 "Checking false with assertRun"
        assertRun "true" 1
        assertRun "true" 1 "Checking true with assertRun"
        assertTrue "Checking false with assertTrue" "false"
        assertFalse "Checking true with assertFalse" "true"
    assertEnd

    [ $TotalPassed == 7 -a $TotalFailed == 7 ] && exit 0 || exit 1
fi



# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#   Run the tests
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# set important variables & start journal
export BEAKERLIB="$PWD/.."
export TESTID='123456'
export TEST='beakerlib-unit-tests'
. ../beakerlib.sh
export __INTERNAL_JOURNALIST="$BEAKERLIB/python/journalling.py"
export OUTPUTFILE=$(mktemp) # no-reboot
export SCOREFILE=$(mktemp) # no-reboot
rlJournalStart

# check parameters for test list
for arg in "$@"; do
    # selected test function
    if [[ "$arg" =~ 'test_' ]]; then
        TestList="$TestList $arg"
    # test file
    elif [[ "$arg" =~ 'Test.sh' ]]; then
        FileList="$FileList $arg"
    else
        echo "What do you mean by $arg?"
        exit 1
    fi
done

# unless test files specified run all available
[[ -z "$FileList" ]] && FileList="$(ls *Test.sh)"

# load all test functions
for file in $FileList; do
    . $file || { echo "Could not load $file"; exit 1; }
done

assessFile(){
    local file="$1"
    assertStart ${file%Test.sh}
    for test in $(grep --text -o '^test_[^ (]*' $file); do
        assertLog "Running $test"
        silentIfNotDebug "journalReset"
        $test
    done
    assertEnd
}

export TIMEFORMAT="System: %S seconds; User: %U seconds"
TIMEFILE=$( mktemp -u ) # no-reboot
# run all tests
if [[ -z "$TestList" ]]; then
    for file in $FileList; do
      (time ( { assessFile $file; } 2>&3 ) ) 3>&2 2>>$TIMEFILE.$( basename $file )
      OLDTIMEFILE=".$( basename $file)-perf.old"
      if [ -e $OLDTIMEFILE ]
      then
        OLDPERF="$( cat $OLDTIMEFILE )"
      fi
      assertLog "Measurement: $( cat $TIMEFILE.$( basename $file ) )"
      if [ -n "$OLDPERF" ]
      then
        assertLog "        Was: $OLDPERF"
      fi
    done
# run selected tests only
else
    for test in $TestList; do
        assertStart "$test"
        silentIfNotDebug "journalReset"
        $test
        assertEnd
    done
fi

# clean up
rm -rf $BEAKERLIB_DIR

# print summary
echo
for file in $( ls ${TIMEFILE}* 2>/dev/null )
do
    OLDTIMEFILE=".${file#$TIMEFILE.}-perf.old"
    assertLog "${file#$TIMEFILE.} performance: $( cat $file )"
    if [ -e $OLDTIMEFILE ]
    then
      assertLog "${file#$TIMEFILE.}   Was:       $( cat $OLDTIMEFILE )"
    fi
    cat $file > $OLDTIMEFILE
done

while read line
do
  PASS=$( echo $line | cut -d ':' -f 1)
  FAIL=$( echo $line | cut -d ':' -f 2 )
  SKIP=$( echo $line | cut -d ':' -f 3 )
  TotalPassed=$(( $TotalPassed+$PASS ))
  TotalFailed=$(( $TotalFailed+$FAIL ))
  TotalSkipped=$(( $TotalSkipped+$SKIP ))
done < $SCOREFILE

rm -rf $TIMEFILE* $SCOREFILE

if [ $TotalPassed -gt 0 -a $TotalFailed == 0 ]; then
    assertLog "Total summary: $TotalPassed passed, $TotalFailed failed, $TotalSkipped skipped\n" "PASS"
    exit 0
else
    assertLog "Total summary: $TotalPassed passed, $TotalFailed failed, $TotalSkipped skipped\n" "FAIL"
    exit 1
fi
