#! /bin/bash
#
#  Last modified:  March 28, 2022
#
#  This script must be executed in a directory that contains the grading harness
#  tar file (c05Grader.tar) posted on the course website; that tar file must
#  contain the files:
#
#    c05driver.c
#    String.h
#    testString.h
#    testString.o
#
#  The directory must also contain your completed String.c file; we recommend
#  that you name that file yourPID.c, using your VT email PID as the first part
#  of the file name; e.g., "wmcquain.c05.c".
#
#  Invocation:  ./gradeC05.sh <name of your .c file> <grading code tar file> [-repeat]
#               e.g., ./gradeC05.sh wmcquain.c
#
#  A summary of the test results can be found in the file summary.txt.  There
#  will be a separate log file for each test phase that was completed, and an
#  overall report, named PID.txt if you followed the suggested naming convention.
#
#  Alternate invocation:  ./gradeC05.sh -clean
#
#  This will remove the files created by an earlier run of the grading script;
#  it's useful if you just want to start with a pristine environment.
#

#  Rename for student file
renameC="String.c"

#  Names for directories
buildDir="build"

#   Names for log files created by this script:
buildLog="buildLog.txt"
testLog="testLog.txt"
logFiles="logs.txt"

#   Name for the executable
exeName="c05"

# Set command-line switches for testing:
cmdSwitches=("-create" "-compare" "-copy" "-cat" "-strstr" "-substring" )

#  Names for driver-generated test results files
scoresFile="scoresLog.txt"
testFiles=("TestCreateLog.txt" "TestCompareLog.txt" "TestCopyLog.txt" \
           "TestCatLog.txt" "TestStrstrLog.txt" "TestSubstringLog.txt")

#   Delimiter to separate sections of report file:
Separator="============================================================"

Separator="######################################################################"

# Configuration variables for late penalty calculation
latePenaltyDataFile="../../Laggards.txt"
latePenaltyReport="latePenalty.txt"
perDiemPenalty="10"

############################################################## functions
#  Remove files created during test;
#   Names for log files created by this script:
clean() {
   rm -Rf $seedFile
   rm -Rf *.txt ./$buildDir *_c05
   #exit 0
}

############################################# fn to check for tar file
#                 param1:  name of file to be checked
isTar() {

   mimeType=`file -b --mime-type $1`
   if [[ $mimeType == "application/x-tar" ]]; then
     return 0
   fi
   if [[ $mimeType == "application/x-gzip" ]]; then
     return 0
   fi
   return 1
}

##################################### fn to extract token from file name
#                 param1: (possibly fully-qualified) name of file
#  Note:  in production use, the first part of the file name will be the
#         student's PID
#
getPID() { 

   fname=$1
   # strip off any leading path info
   fname=${fname##*/}
   # extract first token of file name
   sPID=${fname%%.*}
}

###################################### fn to compute late penalty
#                   param1:  PID of studentresultsFile
#  The penalty is based on the presence of an entry for the given PID
#  in the file specified by the config variable latePenaltyDataFile;
#  that file must be produced by the LateDaysCalculator.jar utility.
#
#  The amount of the penalty is determined by the config variable
#  perDiemPenalty (which must usually be reset based on the maximum
#  score for the assignment in question).
#
#  The penalty, if any, is logged to the file specified by the config
#  variable latePenaltyReport,
#
computeLatePenalty() {     

   if [[ -e $latePenaltyReport ]]; then
      rm -rf $latePenaltyReport
   fi
   
   targetPID=$1
   daysLate=0
   
   # see if there's a line corresponding to the given student
   studentLine=`grep $targetPID $latePenaltyDataFile`
   if [[ $? -eq 0 ]]; then
      prefix="*"$'\t'
      daysLate=${studentLine##$prefix}
      latePenalty=$(( daysLate * perDiemPenalty ))
      echo "Late penalty for this submission:"  > $latePenaltyReport
      echo >> $latePenaltyReport
      echo "Number of days late: $daysLate" >> $latePenaltyReport
      echo "Penalty (percentage): $latePenalty" >> $latePenaltyReport
   fi
}

############################################# echo invocation instructions
#
showInstructions() {
   
   echo "Error:  incorrect command-line parameters."
   echo "   gradeC05.sh <student submission> <grading code tar file> [-repeat]"
   echo "     or"
   echo "   gradeC05.sh -clean"
   echo "Read the header comment for more information."
}

############################################################ Validate command line
   
   if [[ $# -eq 0 ]]; then
      showInstructions
      exit -1
   fi
   
   if [[ $# -eq 1 ]]; then
      if [ $1 == "-clean" ]; then
         clean
         exit 0
      else
         echo "Unrecognized option: $1"
         showInstructions
         exit -1
      fi
   fi
   
   if [[ $# -ge 2 ]]; then
      stuSolnFile="$1"
      gradingTarFile="$2"
   fi
   
   repeatOn="no"
   if [[ $# -eq 3 ]]; then
      if [[ $3 == "-repeat" ]]; then
         if [[ -e "seed.txt" ]]; then
            repeatOn="yes"
         else
            echo "Cannot repeat:  no seed file is present"
         fi
      else 
         echo "Unrecognized switch: $3"
         exit -2
      fi
   fi

   
   if [[ ! -e $stuSolnFile ]]; then
      echo "Error:  $stuSolnFile is missing."
      exit -3
   fi
   
   if [[ ! -e $gradingTarFile ]]; then
      echo "Error:  $gradingTarFile is missing."
      exit -4
   else
      isTar $gradingTarFile
      if [[ $? -ne 0 ]]; then
         echo "Error:  $gradingTarFile is not a tar file."
         exit -4
      fi
   fi

############################################################ Get student's PID
   
   # Extract first token of C file name (student PID when we run this)
   getPID $stuSolnFile
   
   # Initiate header for grading log
   headerLog="header.txt"
   echo "Grading:  $1" > $headerLog
   echo -n "Time:     " >> $headerLog
   echo `date` >> $headerLog
   echo >> $headerLog
   
############################################################ Prepare for build
  
   # Create log file:
   echo "Executing gradeC05.sh..." > $buildLog
   echo >> $buildLog
   
   # Create build directory:
   echo "Creating build subdirectory" >> $buildLog
   echo >> $buildLog
   # Create build directory if needed; empty it if it already exists
   if [[ -d $buildDir ]]; then
      rm -Rf ./$buildDir/*
   else
      mkdir $buildDir
   fi
   
   # Copy student's C file to the build directory
   echo "Copying student source file to the build directory:" >> $buildLog
   cp $stuSolnFile ./$buildDir/$renameC >> $buildLog
   echo >> $buildLog
   
   # Unpack the test source files into the build directory
   echo "Unpacking test code into build directory:" >> $buildLog
   tar xf $gradingTarFile -C ./$buildDir >> $buildLog
      
   if [[ $? -ne 0 ]]; then
      echo "Error:  tar did not exit cleanly."
      exit -5
   fi
   
   echo "Unpacked files:" >> $buildLog
   ls -l ./$buildDir >> $buildLog
   echo >> $buildLog

   # Move to build directory
   cd ./$buildDir
   
############################################################ Build the test driver

   #   Build command:  
   exeName=$sPID"_c05"
   # echo "exeName:  $exeName"
   buildCMD="gcc -o $exeName -std=c11 -Wall -ggdb3 c05driver.c $renameC testString.o"
   
   # build the driver; save output in build log
   echo "Compiling test code and submission" >> ../$buildLog
   echo "$buildCMD" >> ../$buildLog
   $buildCMD >> ../$buildLog 2>&1
   echo >> ../$buildLog

   # Verify existence of executable
   if [[ ! -e $exeName ]]; then
      echo "Build failed; the file $exeName does not exist" >> ../$buildLog
      echo $Separator >> ../$buildLog
      mv ../$buildLog ../$sPID.txt
      exit 7
   fi
   if [[ ! -x $exeName ]]; then
      echo "Permissions error; the file $exeName is not executable" >> ../$buildLog
      echo $Separator >> ../$buildLog
      mv ../$buildLog ../$sPID.txt
      exit 8
   fi

   echo "Build succeeded..." >> ../$buildLog
   
   # Move executable up to test directory and return there
   echo "Moving the executable $exeName to the test directory." >> ../$buildLog
   mv ./$exeName ..
   cd ..

############################################################ Perform testing

   # Initiate test Log
   echo "Begin testing using seed value $seed" > $testLog
   
   # Initiate score summary file:
   echo ">>Scores from testing<<" > $scoresFile
   
   # Test each of the specified functions:
   testIdx=0
   numTests=6
   while [ $testIdx -lt $numTests ]; do  
      # get current switch
      option=${cmdSwitches[$testIdx]}
      
      echo "$Separator $option" >> $testLog
      if [[ $repeatOn == "yes" ]]; then
         executeCmd="$exeName $option -repeat"
      else
         executeCmd="$exeName $option"
      fi
      echo "Executing program: $executeCmd" >> $testLog
      junkLog="$sPID_junk.txt"
      timeout -s SIGKILL 15 $executeCmd > $junkLog 2>&1
      timeoutStatus="$?"
      # echo "timeout said: $timeoutStatus"
      if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
         echo "The test of your solution timed out after 30 seconds." >> $testLog
      elif [[ $timeoutStatus -eq 134 ]]; then
         echo "The test of your solution was killed by a SIGABRT signal." >> $testLog
         echo "Possible reasons include:" >> $testLog
         echo "    - a segfault error" >> $testLog
         echo "    - a serious memory access error" >> $testLog
      fi
      
      # Check for existence of test output file
      testFile=${testFiles[$testIdx]}
      ((testIdx++))
      if [[ ! -e $testFile ]]; then
         echo "Test output file $testFile was not found!" >> $testLog
      else
         echo "$testFile" >> $testLog
         echo >> $testLog
         cat $testFile >> $testLog
         cat "Log.txt" >> $scoresFile
      fi
   done
   echo "$Separator end of testing" >> $testLog
   
############################################################ Run valgrind check

   #   full valgrind output is in $vgrindLog
   #   extracted counts are in $vgrindIssues
   vgrindLog="vgrindLog.txt"
   vgrindSwitches=" --leak-check=full --show-leak-kinds=all --log-file=$vgrindLog --track-origins=yes -v"
   #scoreResultsLog2="ScoresValgrind.txt"
   tmpVgrind="tmpVgrind.txt"
   executeCmd="$exeName -all -repeat"
   echo -n "Running valgrind test..." >> $vgrindLog
   echo $executeCmd >> $vgrindLog
   timeout -s SIGKILL 30 valgrind $vgrindSwitches ./$executeCmd >> $tmpVgrind 2>&1
   if [[ -s $tmpVgrind ]]; then
      cat $tmpVgrind >> $vgrindLog
   fi
	   
   # accumulate valgrind error counts
   if [[ -e $vgrindLog ]]; then
      vgrindIssues="vgrind_issues.txt"
      echo "Valgrind issues:" > $vgrindIssues
      grep "in use at exit" $vgrindLog >> $vgrindIssues
      grep "total heap usage" $vgrindLog >> $vgrindIssues
      grep "definitely lost" $vgrindLog >> $vgrindIssues
      echo "Invalid reads: `grep -c "Invalid read" $vgrindLog`" >> $vgrindIssues
      echo "Invalid writes: `grep -c "Invalid write" $vgrindLog`" >> $vgrindIssues
      echo "Uses of uninitialized values: `grep -c "uninitialised" $vgrindLog`" >> $vgrindIssues
   else
      echo "Error running Valgrind test." >> $testLog
   fi


############################################################ File report
# complete summary file

   summaryLog="$sPID.txt"
   
   # write header to summary log
   cat "$headerLog" > $summaryLog
   echo $Separator >> $summaryLog
   
   # write score summary to log
   cat "$scoresFile" >> $summaryLog
   echo $Separator >> $summaryLog
   
   # Compute and log late penalty, if the needed log file exists
   if [[ -e $latePenaltyDataFile ]]; then
      computeLatePenalty $sPID
      if [[ -e $latePenaltyReport ]]; then
         cat $latePenaltyReport >> $summaryLog
         echo >> $summaryLog
      fi
      echo $Separator >> $summaryLog
   fi
   
   # write Valgrind summary into log
   echo "Summary of valgrind results:" >> $summaryLog
   echo >> $summaryLog
   cat $vgrindIssues >> $summaryLog
   #echo $Separator >> $summaryLog
   
   # write test output to summary log
   echo >> $summaryLog
   echo $Separator >> $summaryLog
   echo "Test details follow below..." >> $summaryLog
   echo >> $summaryLog
   cat $testLog >> $summaryLog
   echo >> $summaryLog
   
   # write Valgrind details into log
   echo "Details from valgrind check:" >> $summaryLog
   echo >> $summaryLog
   cat $vgrindLog >> $summaryLog
   echo $Separator >> $summaryLog
   
   # write build log into summary
   echo "Results from $buildLog" >> $summaryLog
   cat $buildLog >> $summaryLog
   echo >> $summaryLog

exit 0
