#!/bin/bash
#
# Time-stamp: <Monday 22 January 2001 / ipp>
#
# CS3 Individual Programming Project 1999-2000
#
# Shell script for students to exercise their µOCCAM recogniser/compiler
#
# Ian Stark
# Niall Tracey
##############################################################################

### Set parameters ###########################################################

## Establish version of this program

version='$Revision: 1.6 $'
version=${version/\$Revision: /}
version=${version/ \$/}

## Check invocation name

command=$(basename $0)

case $command in
  ipp1) IPPSTAGE=1;;
  ipp2) IPPSTAGE=2;;
esac

## Parse arguments

while [ $# -gt 0 ]
do
  case "$1" in
    -1 ) IPPSTAGE=1; shift;;
    -2 ) IPPSTAGE=2; shift;;

    --stage) case "$2" in
		1) IPPSTAGE="1";;
		2) IPPSTAGE="2";;
		*) echo "$command: Stage \"$2\" invalid -- must be 1 or 2."
		   exit 1;;
	     esac; shift 2;;

    -d|--directory) IPPDIR=$2;   shift 2;;
    -t|--tests)     IPPTESTS=$2; shift 2;;
    -l|--log)       IPPLOG=$2;   shift 2;;
    -s|--limit)     IPPLIMIT=$2; shift 2;;
    *)  echo "$command: Unrecognized option \"$1\"."; exit 1;;
  esac
done

if [ -z "$IPPSTAGE" ]
then
  echo "$command: Could not determine stage 1 or 2."
  exit 1
fi

## Set any remaining variables to defaults

IPPDIR=${IPPDIR:-ipp${IPPSTAGE}}
IPPTESTS=${IPPTESTS:-ipptests}
IPPLOG=${IPPLOG:-ipplog}
IPPLIMIT=${IPPLIMIT:-2}


### Write initial log and perform preliminary testing ########################

function log ()
{ echo >>$IPPLOG "$@" 
}

function report()
{ echo >>$IPPLOG "$@"
  echo "$@" 
}

divider="---------------------------------------"
divider=${divider}${divider}

rm -f $IPPLOG

## Create new log file with current user, date and time,
log "IPP log for $USER at $(date)"

## Output summary of command-line options to log file.
log $divider
log "IPP test program version: $version"
log "Phase: $IPPSTAGE"
log "Program directory: $IPPDIR"
log "Test directory: $IPPTESTS"
log "Log file: $IPPLOG"
log "Time limit: $IPPLIMIT seconds"
log $divider

log Initial processing
log $divider

## Check that directories do exist
if [ ! -d $IPPDIR -o ! -r $IPPDIR ]
then
  report "Source directory $IPPDIR does not exist or is not readable."
  report "Testing halted."
  exit 1
elif [ ! -d $IPPTESTS -o ! -r $IPPTESTS ]
then
  report "Test directory $IPPTESTS does not exist or is not readable."
  report "Testing halted."
  exit 1
fi

##Check permissions on IPPDIR
if [ $(ls -ld $IPPDIR | cut -b 5-10) != "------" ]
then
  report "Directory $IPPDIR not protected from access by group or other." 
  log $(ls -ld $IPPDIR)
  report "Testing halted."
  exit 1
else
  log "Permissions on $IPPDIR set correctly."
fi

## Execute make in IPPDIR.

#If an error occurs during the execution of make, return, giving a
#suitable error message to stdout and the log file.

log "Running make in $IPPDIR."
make -C $IPPDIR >> $IPPLOG
if [ $? -ne 0 ]
then
  report "Error while running make in $IPPDIR."
  exit 1
fi
echo "Make executed without errors." >> $IPPLOG

## Check for presence of executable file 'occam'

#If this file does not exist, or is not executable, output a suitable
#error message to stdout and the log file

if [ ! -x "$IPPDIR/occam" ]
then
  report "No executable file 'occam'."
  exit 1
fi
log "Executable file 'occam' found." >> $IPPLOG


### Run occam on the given test files ########################################

## First declare a bunch of functions

# Run occam compiler on program given as input
function compile()
{
  case $IPPSTAGE in
    1) (ulimit -t $IPPLIMIT; $IPPDIR/occam   <$1 >>$IPPLOG 2>&1) 2>>$IPPLOG;;
    2) (ulimit -t $IPPLIMIT; $IPPDIR/occam -r $1 >>$IPPLOG 2>&1) 2>>$IPPLOG;;
  esac
  
  local result=$?
  log
  return $result
}

# Run all tests on correct programs.  These are given as a list of valid
# muOccam program files each with a .accept extension.
function accept_test()
{
  local accepted=0 rejected=0 aborted=0

  log $divider
  log "Checking with valid programs"
  log $divider
 
  for filename
  do
      testname=$(basename $filename .accept)
      comments=${filename/.accept/.comment} 
      log "Test: $testname"
      [ -f $comments ] && cat $comments >> $IPPLOG
 
      compile $filename 
 
      case $? in
       1) # Tag incorrect rejection
          echo -n "*"
          rejected=$[rejected + 1]
          log "Valid program incorrectly rejected.";;
       0) # Tag correct acceptance
          echo -n "+"
          accepted=$[accepted + 1]
          log "Valid program correctly accepted.";;
       *) # Tag abnormal termination
          echo -n "^"
          aborted=$[aborted + 1]
          log "Compiler aborted.";;
      esac
      log $divider
  done
  log "Number of valid programs correctly accepted: $accepted" 
  log "Number of valid programs incorrectly rejected: $rejected"
  log "Program 'occam' exited abnormally $aborted times"
 
  correct=$[correct+accepted]
  incorrect=$[incorrect+rejected+aborted]
}

# Run all tests on incorrect programs.  These are given as a list of invalid
# muOccam program files each with a .reject extension.
function reject_test()
{
  local accepted=0 rejected=0 aborted=0

  log $divider
  log "Checking with invalid programs"
  log $divider

  for filename
  do
      testname=$(basename $filename .reject)
      comments=${filename/.reject/.comment}
      log "Test: $testname"
      [ -f $comments ] && cat $comments >> $IPPLOG

      compile $filename

      case $? in
        1) # Tag correct reject
           echo -n "-"
           rejected=$[rejected + 1]
           log "Invalid program correctly rejected.";;
        0) # Tag incorrect acceptance
           echo -n "/"
           accepted=$[accepted + 1]
           log "Invalid program incorrectly accepted.";;
        *) # Tag abnormal termination
           echo -n "^"
           aborted=$[aborted + 1]
           log "Compiler aborted."
      esac
      log $divider
  done
  log "Number of invalid programs correctly rejected: $rejected"
  log "Number of invalid programs incorrectly accepted: $accepted"
  log "Program 'occam' exited abnormally $aborted times"

  correct=$[correct + rejected]
  incorrect=$[incorrect + accepted + aborted]
 }


# Run all IO tests.  The argument to the function is a list of valid muOccam
# program files each with a .io extension.  The corresponding input and
# desired output are in files with .in and .out extensions.
function io_test()
{
  local aborted=0 rejected=0 right=0 wrong=0

  log $divider
  log "Checking with I/O programs"
  log $divider

  for filename
  do
      testname=$(basename $filename .io)
      comments=${filename/.io/.comment}
      input=${filename/.io/.in}
      output=${filename/.io/.out}

      log "Test: $testname"
      [ -f $comments ] && cat $comments >> $IPPLOG

      if [ ! -r $input -o ! -r $output ]
      then
        log "Test does not have accompanying $input and $output,"
        log "or they are not readable."
        log $divider
        continue
      fi

      actual=`(ulimit -t $IPPLIMIT; 
               $IPPDIR/occam $filename <$input 2>>$IPPLOG) 2>>$IPPLOG`

      case $? in
        1) # Tag incorrect rejection
           echo -n "%"
           rejected=$[rejected + 1]
           log "Valid program incorrectly rejected.";;
        0) # Correct acceptance; now check output
           log "Valid program accepted and run."
           log $divider; log "Input was:";  log "$(<$input)"
           log $divider; log "Output was:"; log "$actual"; log $divider
           if [ "$actual" == "$(<$output)" ] 
           then
               echo -n "!"
               right=$[right + 1]
               log "Correct output."
           else
               echo -n "?"
               wrong=$[wrong + 1]
               log "Incorrect output - expected this:"; log "$(<$output)"
           fi ;;
        *) # Tag abnormal termination
           echo -n "^"
           aborted=$[aborted + 1]
           log "Program aborted or time expired."
      esac
      log $divider
  done
  log "Number of programs accepted and run with correct output: $right"
  log "Number of programs accepted and run but output incorrect: $wrong"
  log "Number of valid programs incorrectly rejected: $rejected"
  log "Program exited abnormally or overran time limit $aborted times"

  correct=$[correct + right ]
  incorrect=$[incorrect + rejected + wrong + aborted]
 }



## End of function definitions

## Now to actually run the tests

correct=0
incorrect=0

# Say what is being tested
echo -n "$(basename $IPPDIR): "

# Make *.accept *.reject vanish when there are no suitable files
shopt -s nullglob

# Now perform the three kinds of test (only two in stage 1)
accept_test $IPPTESTS/*.accept 
reject_test $IPPTESTS/*.reject
[ $IPPSTAGE -eq 2 ] && io_test $IPPTESTS/*.io
echo
log $divider

# Display statistics.
report "$(basename $IPPDIR): Passed $correct of $[correct+incorrect] tests"

# End of file ################################################################