CS-PROJECTS / c02_assembler / test / gradeC02.sh
gradeC02.sh
Raw
#! /bin/bash
#
#  Warning:  modifying this script would very likely defeat the purpose of having
#            it supplied to you.
#
#  This script can be used to test and grade your solution for the MIPS assembler
#  assignment.  This is the script we will use to do that, so if your tar file
#  does not work for you with this script, it won't work for us either.
#  That would be unfortunate.
#
#  Invoke as:  ./gradeC02.sh [ -m1 | -m2 | -final ] <name of submission tar file>
#              -m1 selects the tests for milestone 1
#              -m2 selects the tests for milestone 2
#              -final selects the tests for the final submission
#              -ec selects the tests for the extra-credit option (only) 
#              We suggest you name your tar file PID.tar, where PID is
#              your VT email pid (e.g., wmcquain.tar).
#
#
#  Last modified:  Sept 26, 2021
#
#  To use this test script:
#     - Untar the distributed testing tar file into a directory; that should
#       create the following directory structure:
#
#          ./gradeC02.sh              - this script file
#          ./compare                  - 64-bit CentOS 8 utility for checking results
#          ./milestone1/m1testxx.asm  - MIPS files to be assembled
#          ./milestone1/m1testxx.o    - reference translations 
#          ./milestone2/m2testxx.asm  - MIPS files to be assembled
#          ./milestone2/m2testxx.o    - reference translations 
#          ./final/ftestxx.asm        - MIPS files to be assembled
#          ./final/ftestxx.o          - reference translations
#
#       We will refer to the extraction directory as the test directory.
#     - Set execute permissions for grade.sh.
#     - Prepare the tar file you intend to submit and place it into the same
#       directory as gradeC02.sh.
#     - Execute the grading script command as described above.
#
#     gradeC03.sh will create a subdirectory, ./build, unpack your submission tar
#     file into it, and use your makefile to build the specified target,
#     "assemble".
#
#     If there is no makefile, the script will exit with an error message.
#
#     The script will then check for the existence of the specified executable,
#     "assemble".  If there is no such file, the script will exit with an error
#     message.  Otherwise, the  script will move the executable "assemble" into
#     the test directory, and execute it on each of the asm files in the
#     appropriate directory.  For each test case, the script will use the compare
#     utility to compare your translation to the reference translation, producing
#     a score for each test case.
#
#     If -final is selected, there is additional testing:
#        -  symbol table generation is tested
#        -  Valgrind analysis is run on a single test case
#        -  a test is run for the extra-credit option
#
#  Exit codes:
#     0   -  normal execution; no major errors were detected
#     1   -  wrong number of command-line parameters
#     2   -  invalid switch used on command-line
#     3   -  something is missing from test file directory
#     4   -  file named on command line does not exist
#     5   -  file named on command line is not a tar file
#     6   -  tar file does not contain a makefile
#     7   -  build failed
#     8   -  build produced nonexecutable file (unlikely)
#
# Configuration variables:
#
#   Names for directories
buildDir="build"

#   Flag for testing control
runEC="no"

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

#   Name for the specified make target and executable
makeTarget="assembler"
exeName="assemble"

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

#   Set results file names
testLog=testing_details.txt
summaryLog=testing_summary.txt

##################################### fn to check existence of test data
#                 param1:  -m1 or -m2 or -final or -ec
testsOK() {
	
	if [[ $1 == "-m1" ]]; then
	   nCases=3
	   testDataDir="milestone1"
      #   Set array of names of test input files
      asmFileNames=("m1test01.asm" "m1test02.asm" "m1test03.asm")
      #   Set array of names of reference object files
      refFileNames=("m1test01.o" "m1test02.o" "m1test03.o")
      #    Set array of names of assembler-produced student object files
      stuFileNames=("m1stu01.o" "m1stu02.o" "m1stu03.o")
	elif [[ $1 == "-m2" ]]; then
	   nCases=5
	   testDataDir="milestone2"
      #   Set array of names of test input files
      asmFileNames=("m2test01.asm" "m2test02.asm" "m2test03.asm" "m2test04.asm" "m2test05.asm")
      #   Set array of names of reference object files
      refFileNames=("m2test01.o" "m2test02.o" "m2test03.o" "m2test04.o" "m2test05.o")
      #    Set array of names of assembler-produced student object files
      stuFileNames=("m2stu01.o" "m2stu02.o" "m2stu03.o" "m2stu04.o" "m2stu05.o")
	elif [[ $1 == "-final" ]]; then
	   nCases=10
	   testDataDir="final"
      #   Set array of names of test input files
      asmFileNames=("ftest01.asm" "ftest02.asm" "ftest03.asm" "ftest04.asm" "ftest05.asm" "ftest06.asm" "ftest07.asm" "ftest08.asm" "ftest09.asm" "ftest10.asm")
      #   Set array of names of reference object files
      refFileNames=("ftest01.o" "ftest02.o" "ftest03.o" "ftest04.o" "ftest05.o" "ftest06.o" "ftest07.o" "ftest08.o" "ftest09.o" "ftest10.o")
      #    Set array of names of assembler-produced student object files
      stuFileNames=("fstu01.o" "fstu02.o" "fstu03.o" "fstu04.o" "fstu05.o" "fstu06.o" "fstu07.o" "fstu08.o" "fstu09.o" "fstu10.o")
   else
      echo "Unknown option: $1"
      return 1 
   fi
	
	if [[ ! -d ./$testDataDir ]]; then
	   echo "Test data directory does not exist!"
	   echo "Download a fresh copy of the tar file and try again."
	   return 1
	fi
	
	for fName in ${asmFileNames[@]}
	do
	   if [[ ! -e "./$testDataDir/$fName" ]]; then
	      echo "$fName is not in the test data directory!"
	      echo "Download a fresh copy of the tar file and try again."
	      return 2
	   fi
	done
	
	for fName in ${refFileNames[@]}
	do
	   if [[ ! -e "./$testDataDir/$fName" ]]; then
	      echo "$fName is not in the test data directory!"
	      echo "Download a fresh copy of the tar file and try again."
	      return 2
	   fi
	done
	return 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%%.*}
}

###################################### clean up old test files
clean() {
   rm -Rf build         # delete build directory
   rm -f *.o            # delete old student object files, or stray ones
   rm -f *.txt          # delete any old report files
   rm -f assemble       # delete old assembler executable
   
   exit 0
}

############################################################ Choose test data directory
   
   # Check number of command-line parameters
   if [[ $# -eq 1 && $1 == "-clean" ]]; then
      clean
   fi
   
   if [[ $# -ne 2 ]]; then
      echo "Invocations:  gradeC02.sh [-m1 | -m2 | -final] [name of submitted tar file]"
      echo "              gradeC02.sh -clean"
      exit 1
   fi
   
   if [[ $1 == "-m1" ]]; then
      echo "Running tests for milestone 1."
      testDataDir="./milestone1"
   elif [[ $1 == "-m2" ]]; then
      echo "Running tests for milestone 2."
      testDataDir="./milestone2"
   elif [[ $1 == "-final" ]]; then
      echo "Running tests for final submission."
      testDataDir="./final"
   else
      echo "Unknown option: $1"
      exit 2
   fi

############################################################ Validate environment

   # Check for valid test data subdirectory:
   testsOK $1
   if [[ ! $? -eq 0 ]]; then
      echo "The test data directory does not exist, or its contents are not valid."
      echo "Fix this error."
      exit 3
   fi
   
############################################################ Validate command line

   # Verify presence of named file
   tarFile="$2"
   tarFile=${tarFile##*/}
   if [ ! -e $tarFile ]; then
      echo "The file $tarFile does not exist."      exit 4
   fi

   # Verify parameter is really a tar file
   isTar "$tarFile"
   if [[ ! $? -eq 0 ]]; then
      echo "The file $tarFile is not a valid tar file."
      exit 5
   fi
   
############################################################ Get student's PID
   
   # Extract first token of tar file name (student PID when we run this)
   getPID $tarFile
   
   # Initiate header for grading log
   headerLog="header.txt"
   echo "Grading:  $2" > $headerLog
   echo -n "Time:     " >> $headerLog
   echo `date` >> $headerLog
   echo "Option:   $1" >> $headerLog
   echo >> $headerLog
   
################################################### Clean up the test directory

   # Remove pre-existing assembler executable from test directory
   if [[ -e assemble ]]; then
      rm -f assemble
   fi
   
   # Remove any old student object files and assembler executable
   rm -f ./*.o
   rm -f ./$exeName
   
############################################################ Prepare for build
  
   # Create log file:
   echo "Executing grade.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
   
   # Extract student's tar file to the build directory
   echo "Extracting student's files to the build directory:" >> $buildLog
   tar xvf $tarFile -C ./$buildDir >> $buildLog
   echo >> $buildLog

   # Move to build directory
   cd ./build
   # Verify we have a makefile
   gotMakefile="yes"
   if [[ ! -e makefile ]] && [[ ! -e Makefile ]] && [[ ! -e GNUmakefile ]]; then
      echo "There is no makefile in the build directory" >> ../$buildLog
      gotMakefile="no"
      #echo $Separator >> ../$buildLog
      #mv ../$buildLog ../$spid.txt
      #exit 6
   fi
   # Verify we have a pledge file
   if [[ ! -e pledge.txt ]]; then
      echo "Missing pledge file" >> ../$buildLog
   fi
   # Remove extraneous files from build directory
   if [[ -e assemble ]]; then
      echo "Deleting prebuilt assembler" >> ../$buildLog
      rm -f assemble
   fi
   if [[ -e *.o ]]; then
      echo "Deleting extraneous object files" >> ../$buildLog
      rm -f *.o
   fi
   
############################################################ Build the assembler

   # build the assembler; save output in build log
   if [[ $gotMakefile == "yes" ]]; then
      echo "Invoking:  make $makeTarget" >> ../$buildLog
      make $makeTarget >> ../$buildLog 2>&1
      echo >> ../$buildLog
      if [[ ! -e $exeName ]]; then
         echo "$exeName was not found... trying make with default target..." >> ../$buildLog
         # try to make a default target
         make
         if [[ ! -e $exeName ]]; then
            echo "That failed; the file $exeName does not exist" >> ../$buildLog
            echo $Separator >> ../$buildLog
         else
            echo "Attempting to build w/o a makefile; penalty will be 20%." >> ../$buildLog
            gcc -o $exeName -std=c99 -Wall -ggdb3 *.c >> ../$buildLog 2>&1
         fi
      fi
   else
      echo "ERROR:  no makefile found! " >> ../$buildLog
      echo "Attempting to build w/o a makefile; penalty will be 20%." >> ../$buildLog
      gcc -o $exeName -std=c11 -Wall -ggdb3 *.c >> ../$buildLog 2>&1
   fi

   # Verify existence of executable
   if [[ ! -e $exeName ]]; then
      echo "I give up... cannot build $exeName..." >> ../$buildLog
      mv ../$buildLog ../$spid.txt
      exit 7
   fi
   
   if [[ ! -x assemble ]]; 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
   
   # Check for 32-bit executable and issue warning
   file ./$exeName | grep "32-bit"
   if [[ $? -eq 0 ]]; then
      warningLog="warningLog.txt"
      echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" >> $warningLog
      echo "Warning:  $exeName apears to be a 32-bit executable." >> $warningLog
      echo "This will interfere with the valgrind checks, and affect your score." >> $warningLog
      echo "We strongly suggest you remove -m32 from your makefile." >> $warningLog
      echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" >> $warningLog
      echo >> $warningLog
      cat $warningLog >> ../$headerLog
      echo
      cat $warningLog
      rm -f $warningLog
   fi
   
   # Move executable up to test directory and return there
   echo "Moving the executable $exeName to the test directory." >> $buildLog
   mv ./$exeName .. >> $buildLog
   cd .. >> $buildLog
   
   # Delimit this section of the report file:
   echo $Separator >> $buildLog

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

   # Initiate test Log
   echo "Begin running test cases..." > $testLog
   echo >> $testLog

   # perform testing
   index=0
   
   ############################## run the assemble test cases
   while [ "$index" -lt "$nCases" ]
   do
      asmToProcess=$testDataDir/${asmFileNames[$index]}
      refObjectFile=$testDataDir/${refFileNames[$index]}
      stuObjectFile=${stuFileNames[$index]}

      ((index +=1))
      echo "Beginning test case $index" >> $testLog

      # verify existence of current test file and reference file
      if [ ! -e $asmToProcess ]; then
         echo "The test file $asmToProcess is not present" >> $testLog
         continue
      fi
      if [ ! -e $refObjectFile ]; then
         echo "The reference file $refObjectFile is not present" >> $testLog
         continue
      fi
      # write-protect test files
      chmod a-w $asmToProcess
      chmod a-w $refObjectFile

      # run assembler on current test file
      echo "Running assembler on $asmToProcess" >> $testLog
      timeout -s SIGKILL 30 ./$exeName $asmToProcess $stuObjectFile >> $testLog
		if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
			echo "The test of your solution timed out after 30 seconds." >> $testLog
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		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
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		fi

      # remove write-protection from test files
      chmod u+w $asmToProcess
      chmod u+w $refObjectFile

      # verify existence of a nonempty student object file
      if [ ! -s $stuObjectFile ]; then
         echo "The object file $stuObjectFile was not produced or is empty" >> $testLog
         echo >> $testLog
         continue
      fi

      # run comparator
      echo "Running compare on $stuObjectFile and $refObjectFile" >> $testLog
      ./compare $index $stuObjectFile $refObjectFile >> $testLog
      echo >> $testLog
   done

############################################################ Run valgrind check

   if [[ $1 == "-final" ]]; then
      
      #echo "Running valgrind test"
      
	   # run test 7 on valgrind
	   #   full valgrind output is in $vgrindLog
	   #   extracted counts are in $vgrindIssues
	   vgrindLog="vgrindLog.txt"
	   echo "Running valgrind test..." >> $vgrindLog
	   vgrindSwitches=" --leak-check=full --show-leak-kinds=all --log-file=$vgrindLog --track-origins=yes -v"
	   scoreResultsLog2="ScoresValgrind.txt"
	   timeout -s SIGKILL 30 valgrind $vgrindSwitches ./assemble ./final/ftest07.asm ./fstu07.o
		if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
			echo "The test of your solution timed out after 30 seconds." >> $testLog
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		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
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		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

   fi
   
############################################################ Run symbol table tests

   if [[ $1 == "-final" ]]; then
	   symbolsLog="symbolsLog.txt"

      echo "Details from symbol table generation tests:" > $symbolsLog
      echo >> $symbolsLog
      
	   timeout -s SIGKILL 30 ./assemble ./final/ftest01.asm ./symstu01.txt -symbols
		if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
			echo "The test of your solution timed out after 30 seconds." >> $testLog
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		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
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		fi
	   if [[ -e "./symstu01.txt" ]]; then
	      sort -k2 ./symstu01.txt > sortedsyms01.txt
	      echo "Sorted symbol table for ftest01.asm:" >> $symbolsLog
	      cat sortedsyms01.txt >> $symbolsLog
	      echo >> $symbolsLog
	      ./symcompare 11 sortedsyms01.txt ./symtabs/refsyms01.txt >> $symbolsLog
	      echo >> $symbolsLog
	   else
	      echo "   File not found:  symstu01.txt" >> $symbolsLog
	   fi

	   timeout -s SIGKILL 30 ./assemble ./final/ftest04.asm ./symstu04.txt -symbols
		if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
			echo "The test of your solution timed out after 30 seconds." >> $testLog
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		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
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		fi
	   if [[ -e "./symstu04.txt" ]]; then
	      sort -k2 ./symstu04.txt > sortedsyms04.txt
	      echo "Sorted symbol table for ftest04.asm:" >> $symbolsLog
	      cat sortedsyms04.txt >> $symbolsLog
	      echo >> $symbolsLog
	      ./symcompare 12 sortedsyms04.txt ./symtabs/refsyms04.txt >> $symbolsLog
	      echo >> $symbolsLog
	   else
	      echo "   File not found:  symstu04.txt" >> $symbolsLog
	   fi

	   timeout -s SIGKILL 30 ./assemble ./final/ftest07.asm ./symstu07.txt -symbols
		if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
			echo "The test of your solution timed out after 30 seconds." >> $testLog
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		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
			echo "Valgrind testing will NOT be done." >> $testLog
			killed="yes"
		fi
	   if [[ -e "./symstu07.txt" ]]; then
	      sort -k2 ./symstu07.txt > sortedsyms07.txt
	      echo "Sorted symbol table for ftest07.asm:" >> $symbolsLog
	      cat sortedsyms07.txt >> $symbolsLog
	      echo >> $symbolsLog
	      ./symcompare 13 sortedsyms07.txt ./symtabs/refsyms07.txt >> $symbolsLog
         echo $Separator >> $summaryLog
	   else
	      echo "   File not found:  symstu07.txt" >> $symbolsLog
	   fi
	   echo >> $symbolsLog
   fi
   
############################################################ File report
# complete summary file

   # write header to summary log
   cat "$headerLog" > $summaryLog
   
   echo ">>Scores from testing<<" >> $summaryLog
   # write scores to summary file
   #echo >> $summaryLog
   grep Score $testLog >> $summaryLog
   
   if [[ $1 == "-final" ]]; then
      grep symbols $symbolsLog >> $summaryLog
   fi
      
   echo $Separator >> $summaryLog
   echo >> $summaryLog
   
   # write Valgrind summary into log
   if [[ $1 == "-final" ]]; then
      cat $vgrindIssues >> $summaryLog
      echo $Separator >> $summaryLog
   fi
   
   # write symbol tables test results into log
   if [[ $1 == "-final" ]]; then
      cat $symbolsLog >> $summaryLog
      echo $Separator >> $summaryLog
   fi
   
   # write testing details into summary
   cat $testLog >> $summaryLog
   echo $Separator >> $summaryLog
   
   # write Valgrind details into log
   if [[ $1 == "-final" ]]; then
      cat $vgrindLog >> $summaryLog
      echo $Separator >> $summaryLog
   fi

   # write build log into summary
   echo "Results from $buildLog" >> $summaryLog
   cat $buildLog >> $summaryLog
   echo >> $summaryLog

   # rename summary log using student's PID
   mv $summaryLog $spid.txt

exit 0