#! /bin/bash
################################################################################
#
#  test.sh
#  calling interface to testing routines
#
#  copyright (c)
#    Fritz Lipmann Institute Jena, CF Bioinformatics, 2018
#    Fritz Lipmann Institute Jena, Genome Analysis Group, 2005, 2016
#    Karol Szafranski, 2005, 2009
#    Karol Szafranski at UPenn Philadelphia, Center for Bioinformatics, 2004
#    IMB Jena, Dept. Genome Analysis, 2004
#  author
#    Karol Szafranski, karol.szafranski@leibniz-fli.de
#
################################################################################
#
#  DESCRIPTION
#
# - see function usage for a description of the command line syntax
#
# - development history
#   - Essential ideas stem from ~/code/PWMsuite/test/test.sh (CVS PWMsuite).
#   - An enhanced version was developed for the sequence C++ library project
#     "clib_seq" - dropped since.
#
################################################################################


##### environment and constants

function realpath { perl -MCwd -e 'print Cwd::realpath(shift()),qq(\n)' "$1"; }

# environment variables
# do never modify $PERLPATH! Some downstream code may expect an unaffected
# definition.
pprefix=                                                    #LLL#

# default program directives
dirbase=./
dirbase=$(realpath $dirbase)
dircode="$dirbase"
direxec=./bin
direxec=$(realpath $direxec)
dirobj=./obj
dirobj=$(realpath $dirobj)
dirtest=./test
dirtest=$(realpath $dirtest)

# command line syntax
function usage {
  if [ ! -z "$1" ] ; then echo "$1" >&2 ; fi

  echo ''
  cat <<END_USAGE
PURPOSE
 perform test calls, i.e. find test sets, perform the calls provided with
 each set, and perform diffing between test call output and the provided
 expected output.

COMMAND LINE SYNTAX
 $(basename $0) [-h]
 $(basename $0) [options]

options:
 -c dir     directory path of code/script files, default ./
 -e dir     directory path of the executables, default ./bin
 -h         output command line syntax description and exit
 -o dir     directory path of object files, default ./obj
 -t dir     directory path of the test sets, default ./test
END_USAGE
  echo ''
  exit 1
}

# command line options
while getopts 'c:e:ho:t:' OptCurr ; do
  case $OptCurr in
    c) if [ "${OPTARG:0:1}" == "/" ] ; then
         dircode="$OPTARG"
       else
         dircode=$(realpath "$OPTARG")
       fi
       ;;
    e) if [ "${OPTARG:0:1}" == "/" ] ; then
         direxec="$OPTARG"
       else
         direxec=$(realpath "$OPTARG")
       fi
       ;;
    o) if [ "${OPTARG:0:1}" == "/" ] ; then
         dirobj="$OPTARG"
       else
         dirobj=$(realpath "$OPTARG")
       fi
       ;;
    h) usage ;;
    t) if [ "${OPTARG:0:1}" == "/" ] ; then
         dirtest="$OPTARG"
       else
         dirtest=$(realpath "$OPTARG")
       fi
       ;;
  esac
done
shift $(expr $OPTIND - 1)


################################################################################
# run the tests


# diffing and messaging for test set
#
# INTERFACE
# - arg1: file containing expected output
# - arg2: file containing newly generated output
#
function TestDiff {
  pshould="$1"
  pis="$2"
  ptmp="$dirtest/test_diff.tmp"
  diff $pshould $pis  > $ptmp 2>&1
  if [ "$?" == "0" ] && [ ! -s $ptmp ] ; then
    echo " OK"
    \rm -f $pis
    ret=0
  else
    echo " ERROR"
    echo "  see output in $pis"
    ret=1
  fi
  \rm -f $ptmp
  return $ret
}

# delete former test results
\rm -f $dirtest/test_*_rsltchk


# output report header
echo $(basename $0)
echo "  date/time:" $(date '+%Y-%m-%d %H:%M:%S %z(%Z)')
echo "  host:" $(hostname)
echo "  pwd:" $(pwd)
echo "  code directory: $dircode"
echo "  execute directory: $direxec"
echo "  object directory: $dirobj"
echo "  test data directory: $dirtest"
echo ''

# check of global prerequisites, prior to running the actual tests
fprereq=$dirtest/test_prerequisites
if [ -e "$fprereq" ] ; then
  echo "checking prerequisites"
  # run code fragment from the test package
  . $fprereq
  # failed checks are answered by an "exit"
  echo OK
fi

# counter variables
cttest=0
ctskip=0
ctfail=0

# test set convention
#
# Every test set is defined by a file named $dirtest/test_<set>_call and
# $dirtest/test_<set>_rslt. File $dirtest/test_<set>_call contains the
# bash code snipped for definition of the command line, omitting the
# redirection for result output. File $dirtest/test_<set>_rslt contains
# the expected output for the test call.
#
# Additionally, variables mediate exchange between test script (this file)
# and the test set code snippets:
# cwd         the sourced bash code snippet should not rely on any certain
#             current working directory, nor should they modify it.
# local_call  function name that's reserved for test set code snippets.
# $dirbase    package base directory, absolute path
# $dircode    where to find package-specific code/script files, absolute path
# $direxec    where to find package-specific executable files, absolute path
# $dirobj     where to find object files, absolute path
# $dirtest    where to find the test data directory, absolute path
# $setid      ID of the test set, as determined from matching the code file
#             with "m/test_(.+)_call/"
# $setstamp   file prefix ("test_${setid}") of the test set

# do test batch
# loop over *_call files of the test sets, sorted case-insensitive
fll=$( ( ( ls -d "$dirtest"/test_*_call | sort -f >&3 ) 2>&1 | grep -vi 'no such file' > $dirtest/test_ls.err ) 3>&1)
if [ -s "$dirtest/test_ls.err" ] ; then
  echo "ERROR in ls to retrieve test calls" >&2
  cat "$dirtest/test_ls.err" >&2
  exit 1
fi
rm "$dirtest/test_ls.err"
for fcall in $fll ; do

  # determine ID of the test set
  setid=$(perl -e '$ARGV[0]=~m/test_(.+)_call/; print $1,"\n"' $fcall)
  if [ "$setid" == "*" ] ; then break ; fi
  setstamp="test_$setid"
  setstump="$setstamp"

  # derive result file of the test set
  setrslt=$(perl -e '$ARGV[0]=~s/_call$/_rslt/; print $ARGV[0],"\n"' $fcall)
  # log
  echo -n "testing set $setid ..."

  # check fulfilment of requirements
  setrequ=$(perl -e '$ARGV[0]=~s/_call$/_requ/; print $ARGV[0],"\n"' $fcall)
  if [ -e "$setrequ" ] ; then
    . $setrequ >/dev/null 2>&1
    if [ "$?" != "0" ] ; then
      echo " requirements not met"
      let ctskip="$ctskip + 1"
      continue
    fi
  fi

  # execute test call(s)
  ( . $fcall ) > ${setrslt}chk 2>&1
  # perform diff in function
  TestDiff ${setrslt} ${setrslt}chk
  if [ "$?" != "0" ] ; then let ctfail="$ctfail + 1" ; fi
  let cttest="$cttest + 1"
done

# summary of test results
if [ "$cttest" != "0" ] ; then echo '' ; fi
phraseskip=
if [ "$ctskip" != "0" ] ; then
  echo "some tests skipped ($ctskip)"
  phraseskip=' remaining'
fi
if [ "$ctfail" == "0" ] ; then
  echo "all$phraseskip tests passed ($cttest/$cttest)"
else
  echo "some$phraseskip tests failed ($ctfail/$cttest)"
fi
