#!/usr/bin/env bash

#constants
readonly bets_version=0.9.11

#color constants

output_filter=""

define_colors()
{
  if [ "$use_color" = "yes" ]; then
    GREEN="\033[1;32m"
    RED="\033[1;31m"
    BLUE="\033[1;34m"
    YELLOW="\033[1;33m"
    NORMAL="\033[0m"
  else
    GREEN=
    RED=
    BLUE=
    YELLOW=
    NORMAL=
  fi
}

#global counters
pass=0
fail=0
faulty=0

export junit_log_OUT=
export junit_log_ERR=

#utility functions
log ()
{
    local n
    if [ "$1" = "-n" ]; then
      shift; n=-n
    fi
    echo -e $n $* >> $logfile
}

junit_log()
{
    echo $* >> $junit_logfile
}

debug ()
{
    log $*
    [ -n "$junit_log_ERR" ] && (echo "$*" >> $junit_log_ERR)
}

logcmd ()
{
    log $*
    [ -n "$junit_log_ERR" ] && (echo "$*" >> $junit_log_ERR)
    $* 
}

buffer=
filtered_message ()
{
    local n
    if [ "$1" = "-n" ]; then
      shift; n=-n
      buffer="$buffer $*"
    else
      echo -e $buffer "$*" | grep -e "$output_filter"
      buffer=""
    fi
}

message ()
{
    local n
    if [ "$1" = "-n" ]; then
      shift; n=-n
      buffer="$buffer $*"
    else
      echo -e $buffer "$*" 
      buffer=""
    fi
}

escape_xml ()
{
    # Got from http://www.daemonforums.org/showthread.php?t=4054
    cat $1 | sed -e 's~&~\&amp;~g' -e 's~<~\&lt;~g'  -e  's~>~\&gt;~g' 1> $2
}

utility=
for u in bc awk perl ; do
    hash $u && utility=$u && break
done

arithmetic ()
{
    case "$utility" in
        bc)
            echo $* | bc -l
            ;;
        awk)
            awk "BEGIN{print $* }"
            ;;
        perl)
            echo print $* | perl
            ;;
        *)
            echo 0
            ;;
    esac
}

# action functions

passfail ()
{
   local reverse
   local ret

   local full_test_name=$1
   shift

   export junit_log_OUT=$(mktemp)
   export junit_log_ERR=$(mktemp)

   # Note that >> is right here since $* may have appended stuff already
   START=$(date "+%s.%N")
   $* 1>> $junit_log_OUT 2>> $junit_log_ERR
   ret=$?
   END=$(date "+%s.%N")

   ELAPSED=$(arithmetic $END - $START)

   cat $junit_log_ERR $junit_log_OUT >> $logfile

   # test_fail negates the return code check,
   # unless the test was killed (128 + 9(SIGKILL))
   if [ "$test_fail" -a "$ret" -ne 137 ]; then
     reverse='!'
   fi

   junit_log "<testcase name=\"${full_test_name}\" time=\"$ELAPSED\">"

   if [ $reverse $ret -eq 0 ]; then
     if [ ! "$test_faulty" ];
     then
       filtered_message "${GREEN}passed${NORMAL}"
       log "passed"
     else
       filtered_message "${YELLOW}passed (faulty)${NORMAL}"
       log "passed (faulty)"
       let run_faulty=run_faulty+1
       # Note in jUnit output these tests are not flagged in any special way!
     fi
     let run_pass=run_pass+1
   else
     if [ ! "$test_ignore_fail" ]; then
       filtered_message "${RED}failed${NORMAL}!"
       log "failed!"
       junit_log "<error />"
       let run_fail=run_fail+1
     else
       # Note in jUnit output these tests are not flagged in any special way!
       filtered_message "${YELLOW}failed (ignored)${NORMAL}!"
       log "failed (ignored)"
     fi
   fi

   escaped_xml_OUT=$(mktemp)
   escaped_xml_ERR=$(mktemp)

   junit_log "<system-out>"
   escape_xml $junit_log_OUT $escaped_xml_OUT
   cat $escaped_xml_OUT >> $junit_logfile
   junit_log "</system-out>"

   junit_log "<system-err>"
   escape_xml $junit_log_ERR $escaped_xml_ERR
   cat $escaped_xml_ERR >> $junit_logfile
   junit_log "</system-err>"

   junit_log "</testcase>"

   rm -f $escaped_xml_OUT
   rm -f $escaped_xml_ERR

   rm -f $junit_log_OUT
   rm -f $junit_log_ERR

   # Not sure if needed
   junit_log_OUT=""
   junit_log_ERR=""
   unset junit_log_OUT
   unset junit_log_ERR

   return $ret
}

compile_c ()
{
   local version=$1
   local source=$2
   local exec=$3
   local srcdir=$4

   local cc
   local cflags
   local cppflags
   local ldflags
   local tmp

   #defaults
   cc=${CC:=cc}

   #test values
   cc=${test_CC:-$cc}
   cflags=${test_CFLAGS:-$CFLAGS}
   cppflags=${test_CPPFLAGS:-$CPPFLAGS}
   ldflags=${test_LDFLAGS:-$LDFLAGS}

   #version values
   eval tmp=\${test_CC_$version}
   cc=${tmp:-$cc}
   eval tmp=\"\${test_CPPFLAGS_$version}\"
   cppflags="$tmp $cppflags"
   eval tmp=\"\${test_CFLAGS_$version}\"
   cflags="$tmp $cflags"
   out_param=""
   eval current_test_nolink=\${test_nolink:-\${test_nolink_$version}}
   if [ "$current_test_nolink" ]; then
      cflags="-c $cflags"
      ldflags=""
   else
     eval tmp=\"\${test_LDFLAGS_$version}\"
     out_param="-o $exec"
     ldflags="$tmp $ldflags"
   fi
   unset current_test_nolink

   pushd $tmpdir
   logcmd $cc $cppflags $cflags $out_param $srcdir/$source $ldflags 
   local ret=$?
   popd

   return $ret
}

compile_cpp ()
{
   local version=$1
   local source=$2
   local exec=$3
   local srcdir=$4

   local cxx
   local cppflags
   local cxxflags
   local ldflags
   local tmp

   #defaults
   cxx=${CXX:=c++}

   #test values
   cxx=${test_CXX:-$cxx}
   cppflags=${test_CPPFLAGS:-$CPPFLAGS}
   cxxflags=${test_CXXFLAGS:-$CXXFLAGS}
   ldflags=${test_LDFLAGS:-$LDFLAGS}

   #version values
   eval tmp=\${test_CXX_$version}
   cxx=${tmp:-$cxx}
   eval tmp=\"\${test_CPPFLAGS_$version}\"
   cppflags="$tmp $cppflags"
   eval tmp=\"\${test_CXXFLAGS_$version}\"
   cxxflags="$tmp $cxxflags"
   out_param=""
   eval current_test_nolink=\${test_nolink:-\${test_nolink_$version}}
   if [ "$current_test_nolink" ]; then
      cxxflags="-c $cxxflags"
      ldflags=""
   else
     eval tmp=\"\${test_LDFLAGS_$version}\"
     out_param="-o $exec"
     ldflags="$tmp $ldflags"
   fi
   unset current_test_nolink

   pushd $tmpdir
   logcmd $cxx $cppflags $cxxflags $out_param $srcdir/$source $ldflags 
   local ret=$?
   popd

   return $ret
}

compile_fortran ()
{
   local version=$1
   local source=$2
   local exec=$3
   local srcdir=$4

   # Not using fc as it is a shell command
   local fc_
   local fflags
   local ldflags
   local tmp

   #defaults
   fc_=${FC:=f95}

   #test values
   fc_=${test_FC:-$fc_}
   fflags=${test_FFLAGS:-$FFLAGS}
   ldflags=${test_LDFLAGS:-$LDFLAGS}

   #version values
   eval tmp=\${test_FC_$version}
   fc_=${tmp:-$fc_}
   eval tmp=\"\${test_FFLAGS_$version}\"
   fflags="$tmp $fflags"
   out_param=""
   eval current_test_nolink=\${test_nolink:-\${test_nolink_$version}}
   if [ "$current_test_nolink" ]; then
      fflags="-c $fflags"
      ldflags=""
   else
     eval tmp=\"\${test_LDFLAGS_$version}\"
     out_param="-o $exec"
     ldflags="$tmp $ldflags"
   fi
   unset current_test_nolink

   pushd $tmpdir
   logcmd $fc_ $fflags $out_param $srcdir/$source $ldflags
   local ret=$?
   popd

   return $ret
}

runner_local ()
{
   local tmpdir
   local exe
   local args
   local env

   tmpdir="$1"
   exe="$2"
   args="$3"
   env="$4"

   echo > $tmpfile
   echo $env $test_exec_command $tmpdir/$exec $args >> $tmpfile

   log sh $tmpfile
   sh $tmpfile >> $logfile
   local ret=$?

   return $ret
}

runner_ssh()
{
   local tmpdir
   local exe
   local args
   local env

   tmpdir="$1"
   exe="$2"
   args="$3"
   env="$4"


   echo > $tmpfile

   echo $ssh_remote_commands >> $tmpfile
   echo $env $test_exec_command $tmpdir/$exec $args >> $tmpfile

   log ssh -oBatchMode=yes ${ssh_user:-$(whoami)}@$ssh_host "sh $tmpfile"
   ssh -oBatchMode=yes ${ssh_user:-$(whoami)}@$ssh_host "sh $tmpfile" >> $logfile

   local ret=$?

   return $ret
}

runner_sde ()
{
   local tmpdir
   local exe
   local args
   local env

   tmpdir="$1"
   exe="$2"
   args="$3"
   env="$4"

   echo > $tmpfile
   echo $env sde64 -- $tmpdir/$exec $args >> $tmpfile

   log sh $tmpfile
   sh $tmpfile >> $logfile
   local ret=$?

   return $ret
}


# Given a process mask and a number of requested cores, this function computes
# a mask that exaclty enables that number of cores.
compute_mask ()
{
    local hex_mask=`taskset -p $$ | cut -d':' -f 2 | cut -d' ' -f 2`

    local dec_mask=$((0x${hex_mask}))

    local req_cores=$1
    local ena_cores=0

    local res_mask=0
    local num_bit=0
    while [ $dec_mask -ne 0 -a $ena_cores -lt $req_cores ];
    do
        current_bit=$(($dec_mask & 1))
        if [ $current_bit -eq 1 ];
        then
            res_mask=$(($res_mask | (1 << $num_bit)))
            ena_cores=$(($ena_cores + 1))
        fi
        dec_mask=$(($dec_mask >> 1))
        num_bit=$(($num_bit + 1))
    done

    if [ $ena_cores -ne $req_cores ];
    then
        echo REQUESTING MORE CORES THAN AVAILABLE RESOURCES
        exit -1
    fi

    local res_hex_mask=`echo "obase=16; ibase=10; $res_mask" | bc`
    echo 0x${res_hex_mask}
}

runner_taskset ()
{
   local tmpdir
   local exe
   local args
   local env

   tmpdir="$1"
   exe="$2"
   args="$3"
   env="$4"

   echo "ENV ==> " $env >> $logfile

   # Truncate the file
   cat /dev/null > $tmpfile
   (
      # We need to evaluate ENV to pass ${TASKSET_NUM_CPUS}
      echo "set -x" >> $tmpfile
      eval $env;
      local hex_mask=$(compute_mask ${TASKSET_NUM_CPUS})
      echo $env taskset ${hex_mask} $tmpdir/$exec $args >> $tmpfile
      echo "ret=\$?" >> $tmpfile
      echo "set +x" >> $tmpfile
      echo "exit \$ret" >> $tmpfile
   )

   echo "=== TMPFILE ========" >> $logfile
   cat $tmpfile >> $logfile
   echo "=== End of TMPFILE ========" >> $logfile

   log sh $tmpfile
   sh $tmpfile >> $logfile
   local ret=$?

   return $ret
}

execute ()
{
   local version=$1
   local exec=$2
   local cversion=$3
   local env
   local args

   eval env=\"$test_ENV \${test_ENV_$version} \${test_ENV_$cversion}\"
   eval args=\"$test_ARGS \${test_ARGS_$version}\"

   log $env ./$exec $args

   eval local runner_used=\${runner_$cversion:-\${runner:-runner_local}}

   ${runner_used} "$tmpdir" "$exe" "$args" "$env"
   local ret=$?

   return $ret
}

#variable management functions

merge_common_compile_vars ()
{
   eval test_nolink_$1="\${test_nolink_$2:-\$test_nolink_$3}"
}

merge_c_compile_vars ()
{
   eval test_CC_$1="\${test_CC_$2:-\$test_CC_$3}"
   eval test_CPPFLAGS_$1=\"\${test_CPPFLAGS_$2} \${test_CPPFLAGS_$3}\"
   eval test_CFLAGS_$1=\"\${test_CFLAGS_$2} \${test_CFLAGS_$3}\"
   eval test_LDFLAGS_$1=\"\${test_LDFLAGS_$2} \${test_LDFLAGS_$3}\"
}

merge_fortran_compile_vars ()
{
   eval test_FC_$1="\${test_FC_$2:-\$test_FC_$3}"
   eval test_FFLAGS_$1=\"\${test_FFLAGS_$2} \${test_FFLAGS_$3}\"
   eval test_LDFLAGS_$1=\"\${test_LDFLAGS_$2} \${test_LDFLAGS_$3}\"
}

merge_cpp_compile_vars ()
{
   eval test_CXX_$1="\${test_CXX_$2:-\$test_CXX_$3}"
   eval test_CPPFLAGS_$1=\"\${test_CPPFLAGS_$2} \${test_CPPFLAGS_$3}\"
   eval test_CXXFLAGS_$1=\"\${test_CXXFLAGS_$2} \${test_CXXFLAGS_$3}\"
   eval test_LDFLAGS_$1=\"\${test_LDFLAGS_$2} \${test_LDFLAGS_$3}\"
}

merge_exec_vars ()
{
   eval test_noexec_$1=\"\${test_noexec_$2} \${test_noexec_$3}\"
   eval test_ENV_$1=\"\${test_ENV_$2} \${test_ENV_$3}\"
   eval test_ARGS_$1=\"\${test_ARGS_$2} \${test_ARGS_$3}\"
}

allow_all_versions()
{
    return 0
}

mark_test_as_ignored()
{
    local name=$1
    local test_ignore_reason=$2
    junit_log "<testcase name=\"${name}\"><skipped>Ignored test</skipped></testcase>"
    local test_ignore_message="${YELLOW}ignored${NORMAL}"
    if [ "$test_ignore_reason" ]; then
        test_ignore_message="$test_ignore_message (${test_ignore_reason})"
    fi
    filtered_message "Test $name $test_ignore_message"
}


# run functions

run_test ()
(
   local source=$1
   local type=$2
   local compressed=$3
   local name=$(basename ${source%.*})
   local srcdir=$(pwd)

   run_pass=0
   run_fail=0
   run_faulty=0

   if [ "$compressed" = compressed ];
   then
       local uncompressed_name=${source%.*}
       log "***Uncompressing file '$source' to '$tmpdir/$uncompressed_name'"
       bzip2 -c -d $source > $tmpdir/$uncompressed_name
       source=${uncompressed_name}
       name=$(basename ${uncompressed_name%.*})
       srcdir=$tmpdir
   fi

   debug "Starting $name test"

   # init
   local all_compile_versions=
   local all_exec_versions=
   local exec_versions=
   local compile_versions=

   SED_SCRIPT='/<testinfo>/,/<\/testinfo>/{
       /<testinfo>/d
       /<\/testinfo>/d
       # Remove Fortran/C++ comments if any
       s/[[:blank:]]*\(!\|\/\/\)[[:blank:]]*\(.*\)/\2/
       # Remove DOS CR characters if any
       s/\r//g
       p
   }'

   sed -e "${SED_SCRIPT}" -n $srcdir/$source > $tmpfile

   ## Executing the embedded script at the beginning of each test
   source $tmpfile >> $logfile

   ## Saving some variables of the embedded script
   test_generator=${test_generator:-$TEST_GENERATOR}
   test_compile_versions=$compile_versions
   test_exec_versions=$exec_versions
   compile_versions=
   exec_versions=

   local test_generator_env=:
   if [ -n "$test_generator_ENV" ];
   then
          test_generator_env="declare -x \"\${test_generator_ENV[@]}\"";
   fi

   if [ "$test_ignore" ]; then
       mark_test_as_ignored $name "$test_ignore_reason"
       return
   fi

   if [ ! "$test_generator" ]; then
      echo "Invalid $name test: missing test_generator"
      exit -1
   fi

   for gen in "${test_generator[@]}"
   do
       debug "Using version generator $gen"

       export TEST_LANGUAGE=$type
       export TEST_SOURCE=$source
       ( eval "$test_generator_env" ; $basedir/$gen > $tmpfile )
       unset TEST_LANGUAGE
       unset TEST_SOURCE

       (
           ## Executing the output of each test generator
           source $tmpfile >> $logfile

           if [ "$test_ignore" ]; then
              mark_test_as_ignored $name "$test_ignore_reason"
              exit 0
           fi

           ## By default, all_compile_versions = test_compile_versions(embedded script)
           local all_compile_versions=$test_compile_versions
           if [ "$compile_versions" ]; then
             if [ "$test_compile_versions" ]; then
                ## all_compile_versions =
                ##        compile_versions(test_generator) x test_compile_versions(embedded script)
                all_compile_versions=
                for v1 in $compile_versions; do
                  for v2 in $test_compile_versions; do
                     v="$v1"_"$v2"
                     all_compile_versions="$all_compile_versions $v"

                     merge_common_compile_vars $v $v1 $v2
                     merge_${type}_compile_vars $v $v1 $v2
                     merge_exec_vars $v $v1 $v2
                  done
               done
             else
               all_compile_versions=$compile_versions
             fi
           fi

           ## By default, all_exec_versions = test_exec_versions(embedded script)
           local all_exec_versions=$test_exec_versions
           if [ "$exec_versions" ]; then
             if [ "$test_exec_versions" ]; then
               ## all_exec_versions =
               ##        compile_versions(test_generator) x test_compile_versions(embedded script)
               all_exec_versions=
               for v1 in $test_exec_versions; do
                  for v2 in $exec_versions; do
                     v="$v1"_"$v2"
                     all_exec_versions="$all_exec_versions $v"
                     merge_exec_vars $v $v1 $v2
                  done
               done
             else
               all_exec_versions=$exec_versions
             fi
           fi

           if [ ! "$all_compile_versions" ]; then
               all_compile_versions="default"
           fi

           if [ ! "$all_exec_versions" ]; then
               all_exec_versions="default"
           fi

           filter_test_versions=${filter_test_versions:-allow_all_versions}

           for v in $all_compile_versions; do
              filtered_message -n "Compile test $name-$v "
              log "***Start compile test $name-$v"
              eval test_fail=\${test_compile_fail_$v}
              test_fail=${test_fail:-$test_compile_fail}
              eval test_faulty=\${test_compile_faulty_$v}
              test_faulty=${test_faulty:-$test_compile_faulty}
              passfail "$name-$v" compile_$type $v $source $name $srcdir
              local ok=$?

              log "***End compile test $name-$v"
              if [ "$ok" -eq "0" ]; then
                for e in $all_exec_versions; do

                   if ! $filter_test_versions $v $e; then
                       continue
                   fi

                   current_test_noexec=${test_noexec:-${test_nolink}}
                   eval current_test_noexec=\${current_test_noexec:-\${test_noexec_$e}}
                   eval current_test_noexec=\${current_test_noexec:-\${test_noexec_${v}_${e}}}
                   eval current_test_noexec=\${current_test_noexec:-\${test_nolink_$v}}

                   if [ -z "$current_test_noexec" ]; then
                       filtered_message -n "Execute test $name-$v-$e "
                       log "***Start execute test $name-$v-$e"
                       eval test_fail=\${test_exec_fail_${v}_${e}}
                       eval test_fail=\${test_fail:-\$test_exec_fail_${e}}
                       test_fail=${test_fail:-$test_exec_fail}
                       eval test_faulty=\${test_exec_faulty_${v}_${e}}
                       eval test_faulty=\${test_faulty:-\$test_exec_faulty_${e}}
                       test_faulty=${test_faulty:-$test_exec_faulty}
                       eval test_ignore_fail=\${test_ignore_fail}
                       eval test_exec_command=\${test_exec_command}
                       passfail "$name-$v-$e" execute $e $name $v
                       log "***End execute test $name-$v-$e"
                   fi

                   unset current_test_noexec
                done
              fi
           done

           # Allow results to go out the subshell
           output_results
       )
   done

   if [ "$compressed" = compressed ];
   then
       log "***Removing uncompressed file $uncompressed_name"
       rm -f $tmpdir/$uncompressed_name
   fi
)

run_package()
{
    local name=$1
    local compressed=$2

    run_pass=0
    run_fail=0
    run_faulty=0

    if [ "$compressed" = compressed ]; then
        local file=$name
        local name=$(basename ${file%.pkg.bz2})
        log "***Uncompressing package '$file' to '$tmpdir/$name'"
        tar -C "$tmpdir" -xJf "$file"
        pushd "$tmpdir/$name" > /dev/null
    fi

    debug "Starting $name test"
    filtered_message -n "Compile test $name"
    log "***Start compile test $name"
    logcmd "make -s clean"
    passfail "$name" logcmd "make"
    local ok=$?
    log "***End compile test $name"
    if [ "$ok" -eq "0" ]; then
        filtered_message -n "Execute test $name"
        log "***Start execute test $name"
        passfail "$name" logcmd "make check"
        logcmd "make -s clean"
        log "***End execute test $name"
    fi

    if [ "$compressed" = compressed ]; then
        popd > /dev/null
        log "***Removing uncompressed package directory '$tmpdir/$name'"
        rm -rf "$tmpdir/$name"
    fi

    # This is not a subshell but it should behave like run_test
    output_results
    unset run_pass
    unset run_fail
    unset run_faulty
}

init_results()
{
    cat /dev/null > $runresults
}

output_results()
{
    echo $run_pass $run_fail $run_faulty >> $runresults
}

update_results()
{
   # Read output values and update the global counters
   local run_pass=0
   local run_fail=0
   local run_faulty=0
   while read run_pass run_fail run_faulty;
   do
     let pass=pass+run_pass
     let fail=fail+run_fail
     let faulty=faulty+run_faulty
   done < $runresults
}

run_file()
{
   local file=$1

   init_results

   case $file in
     *.c)
       run_test $file c
       ;;
     *.f|*.F|*.f77|*.F77|*.f90|*.F90|*.f95|*.F95)
       run_test $file fortran
       ;;
     *.c.bz2)
       run_test $file c compressed
       ;;
     *.cpp|*.cu)
       run_test $file cpp
       ;;
     *.cpp.bz2)
       run_test $file cpp compressed
       ;;
     *.pkg.bz2)
       run_package $file compressed
       ;;
   esac

   update_results
}

run_dir()
{
  local base=$1

  debug "Testing $base"
  message "===Entering directory $base==="

  #test subdirs
  local dirs=$(find . -maxdepth 1 -type d -path "./*" | cut -c3- | sort)
  for d in $dirs; do
     cd $d
     if [ -f Makefile -o -f makefile -o -f GNUmakefile ] ; then
         run_package $d
     else
        run_dir $base/$d
     fi
     cd ..
  done

  testsuite_name=$(echo $base | tr . _)
  junit_log "<testsuite name=\"$testsuite_name\" tests=\"\">"

  #run test
  for file in $(find . -maxdepth 1 -type f | sort ); do
     run_file $file
  done

  junit_log "</testsuite>"
}

# mode functions

do_help ()
{
   cat <<EOF
$0 [options] [tests]

Options:
-h                  Print this help
-nocolor            Do not use ANSI colors in output
-o logfile          Redirect run output to logfile (default=test.log)
-run                run tests (default) 
-only-fails         Show only failures
-version            Show version

In run mode specific directory or file tests can be specified in the command line
EOF
   return 0
}

do_version ()
{
   echo "Build and Execute Test Suites v$bets_version"
   return 0
}

do_run ()
{
   #startup
   declare -r tmpdir=$(mktemp -d)
   export TMPDIR=$tmpdir
   declare -r tmpfile=$(mktemp)
   declare -r runresults=$(mktemp)
   local old_dir=""
   local ret=0

   if [ "${logfile:0:1}" != "/" ]; then
     logfile=$basedir/$logfile
     junit_logfile=${logfile}.xml
   else
     junit_logfile=/dev/null
   fi
   readonly logfile
   readonly junit_logfile
   echo > $logfile

   # Start junit log
   cat > $junit_logfile << EOF
<?xml version="1.0" encoding="UTF-8" ?>
<testsuites>
EOF

   message "===Running tests==="
   
   #run tests
   if [ "$#" -ge 1 ]; then
     for t in $*; do
        if [ -d $t ]; then
          old_dir=$(pwd)
          cd $t
          run_dir $t
          cd "$old_dir" 
        fi
        if [ -f $t ]; then
          old_dir=$(pwd)
          cd $(dirname $t)
          junit_log "<testsuite name=\"singleton\" tests=\"\">"
          run_file $(basename $t)
          junit_log "</testsuite>"
          cd "$old_dir"
        fi
     done
   else
     run_dir .
   fi
   
   #clean-up
   rm -fr $tmpdir
   rm -f $runresults

   # End junit log
   cat >> $junit_logfile << EOF
</testsuites>
EOF
   
   if [ $fail -eq 0 -a $pass -eq 0 ]; then
     message "No test run. Any files in here? :-)"
   else
     message
     message "===Summary==="
     if [ $fail -eq 0 -a $faulty -eq 0 ]; then
       message "ALL $pass tests passed! Congratulations! :-)"
     elif [ $fail -eq 0 -a $faulty -ne 0 ]; then
       message "ALL $pass tests passed! But there are faulty ones :-/"
       message "Pass tests: $pass"
       message "${YELLOW}Faulty tests: $faulty${NORMAL}"
     else
       message "Some tests failed! :("
       message "Pass tests: $pass"
       message "${YELLOW}Faulty tests: $faulty${NORMAL}"
       message "${RED}Failed tests: $fail${NORMAL}"
       ret=1
     fi
     message
   fi

   return $ret
}

do_list()
{
   local items=
   #run tests
   if [ "$#" -ge 1 ]; then
     items=$1/*
   else
     items=*
   fi

   for t in ${items};
   do
      if [ -d $t ]; then
        echo $t
      fi
   done
}

#default values

declare -r basedir=$(pwd)
export basedir
logfile="test.log"
mode=run
use_color=yes

#read options

done=
while [ -z $done ]; do
case $1 in
    -h)
       mode=help; shift
       ;;
    --list)
       mode=list; shift
       ;;
    -nocolor)
       use_color=no; shift
       ;;
    -o)
       shift
       logfile=$1; 
       shift
       ;;
    -only-fails)
       output_filter="failed";
       shift;
       ;;
    -run) 
       mode=run; shift
       ;;
    -version)
       mode=version; shift
       ;;
    *) done=true;;
esac
done

define_colors

START_TIME=$SECONDS

do_$mode $*
bets_exit_code=$?

END_TIME=$SECONDS
ELAPSED_TIME=$(($END_TIME - $START_TIME))

echo Duration: $(($ELAPSED_TIME / 60))min $(($ELAPSED_TIME % 60))sec

exit $bets_exit_code
