#! /bin/bash
#
# Last modified: April 9, 2021
#
# This script must be executed in the grading/ directory, in the testing
# structure described in the accompanying README file:
#
# README SO READ IT!!
# compare utility used by the grading script
# dev/ empty directory; develop your solution here
# gisdata/ directory holding GIS data files used in grading
# scripts/ directory holding command files used in grading
# logs/ directory holding reference output logs used in grading
# grading/ directory for testing your solution
# gradeC08.sh bash script to automate grading procedure
# <tar file holding your solution goes here>
# suppliedcode/ directory holding supplied code you may use;
# copy files to the dev/ directory as needed;
# include those files in your tar
# StringHashTable.h StringHashTable header/implementation files
# StringHashTable.o
# nextField.h nextField() header/implementation files
# nextField.o
#
# The directory must also contain a tar file containing all of the files that
# are necessary in order to compile your solution.
#
# We recommend that you name that file yourPID.tar, using your VT email PID as
# the first part of the file name, as the Curator will do.
#
# Invocation: ./gradeC08.sh <name of your tar file>
# e.g., ./gradeC08.sh wmcquain.C08.1.tar
#
# If you followed the suggested naming convention, there will be a file named
# yourPID.txt that contains the results of testing. The script will:
#
# - Extract your tar file to ./tempDir, then copy your .c, .h, and supplied
# files you've included to the ./build directory and attempt to compile.
# The command used to compile your submission, and any resulting error or
# warning messages should be echoed to file buildLog.txt.
# - Copy the executable (named c08_yourPID) and the supplied GIS data files
# into the testing directory.
# - Attempt to run your executable on each of the supplied script files,
# producing log files named stulogXX.txt, where XX is the sequence number
# of the script file that was used.
# - Use the compare tool to compare your log files to the supplied reference
# log files and generate a score for each.
# - Run valgrind to check for memory-related errors, using one of the
# supplied script files.
# - Compile results into a single report file, yourPID.txt
#
# Score data should be found in the second section of the final report file,
# looking something like this (the file names and scores may vary):
#
# >>Scores from testing<<
# 1 >> Score from stuLog01.txt: 50 / 50
# 2 >> Score from stuLog02.txt: 180 / 180
# 3 >> Score from stuLog03.txt: 120 / 120
# 4 >> Score from stuLog04.txt: 435 / 435
# . . .
#
# Valgrind results should be summarized in the third section of the final
# report. That should be followed by detailed results from the compare
# tool (valuable in determining why you may have lost points on one of
# the tests), then by the detailed valgrind log, and then by the messages
# from the build process. If errors occur, some of these sections may be
# omitted.
#
# Alternate invocation: ./gradeC08.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.
#
# Name of grading package file and comparison tool
compTool="compare"
# Names for directories
buildDir="build"
tempDir="tempdir"
scriptDir="../scripts"
logDir="../logs"
gisDir="../gisdata"
# Names for log files created by this script:
headerLog="header.txt"
buildLog="buildLog.txt"
testLog="details.txt"
scoreLog="scores.txt"
# Name for the executable
exeName="c08"
# Names for driver-generated test files
testCases="TestData.txt"
results="Results.txt"
# Delimiter to separate sections of report file:
Separator="================================================================================"
############################################# 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 extract token from file name
clean() {
echo "Removing earlier test files..."
rm -Rf *.txt $buildDir $tempDir $exeName $compTool
rm -Rf "script*.txt"
rm -Rf "NM_*.txt" "VA_*.txt" "Mixed*.txt"
}
############################################################ Validate command line
# Verify number of parameters
if [[ $# -ne 1 ]]; then
echo "You must specify the name of your tar file (or -clean)."
exit 1
fi
# See if user selected the cleaning option
if [[ $1 == "-clean" ]]; then
clean
exit 0
fi
# Verify presence of named file and that it's a tar
sourceFile="$1"
sourceFile=${sourceFile##*/}
if [ ! -e $sourceFile ]; then
echo "The file $sourceFile does not exist."
exit 2
fi
isTar "$sourceFile"
if [[ ! $? -eq 0 ]]; then
echo "The file $sourceFile is not a tar file."
exit 2
fi
############################################################ Get student's PID
# Extract first token of submitted file name (student PID when we run this)
getPID $sourceFile
exeName=$exeName"_$sPID"
summaryLog="$sPID.txt"
# Initiate header for grading log
echo "Grading: $1" > $headerLog
echo -n "Time: " >> $headerLog
echo `date` >> $headerLog
echo >> $headerLog
############################################################ Prepare for build
# Create build log file:
echo "Executing gradeC08.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
# Unpack student's tar file to a temp directory
if [[ -d ./$tempDir ]]; then
rm -Rf ./$tempDir
fi
mkdir ./$tempDir
tar xf $sourceFile -C ./$tempDir
echo "Student's tar file contains:" >> $buildLog
tar tvf $sourceFile >> $buildLog
echo >> $buildLog
# Copy student's C source files (.c and .h) into build directory
echo "Copying the student's .c and .h files to the build directory:" >> $buildLog
cp ./$tempDir/*.c $buildDir
if compgen -G "./$tempDir/*.h" >> /dev/null; then
cp ./$tempDir/*.h $buildDir
fi
# If present, copy supplied binaries into build directory
if [[ -e ./$tempDir/StringHashTable.o ]]; then
cp ./$tempDir/StringHashTable.o $buildDir
fi
if [[ -e ./$tempDir/nextField.o ]]; then
cp ./$tempDir/nextField.o $buildDir
fi
echo >> $buildLog
ls -l $buildDir >> $buildLog
echo >> $buildLog
# Move to build directory
cd ./$buildDir
####################################################### Compile student's submission
# Set build command:
buildCMD="gcc -o $exeName -std=c11 -Wall -W -ggdb3 -lm *.c"
if [[ -e StringHashTable.o ]]; then
buildCMD=$buildCMD" StringHashTable.o"
fi
if [[ -e nextField.o ]]; then
buildCMD=$buildCMD" nextField.o"
fi
# Build the driver; save output in build log
echo "Compiling the submitted files:" >> ../$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
cp ../$headerLog ../$sPID.txt
cat ../$buildLog >> ../$sPID.txt
exit 7
fi
if [[ ! -x $exeName ]]; then
echo "Permissions error; the file $exeName is not executable" >> ../$buildLog
echo $Separator >> ../$buildLog
cp ../$headerLog ../$sPID.txt
cat ../$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 .. >> $buildLog
cd .. >> $buildLog
# Delimit this section of the report file:
echo $Separator >> $buildLog
############################################################ Perform tests
# Initiate test Log
echo "Begin testing..." > $testLog
# Copy the comparison tool into the test directory:
if [[ -e ../$compTool ]]; then
cp ../$compTool .
else
echo "ERROR: $compTool not found!"
echo "Please correct test setup as described in spec."
exit 10
fi
# Verify the presence of the script and reference log directories:
if [[ ! -e $scriptDir ]]; then
echo "ERROR: script directory $scriptDir not found!"
echo "Please correct test setup as described in spec."
exit 10
fi
if [[ ! -e $logDir ]]; then
echo "ERROR: log directory $logDir not found!"
echo "Please correct test setup as described in spec."
exit 10
fi
# Copy the GIS data files into the current directory
if [[ -d $gisDir ]]; then
echo "Copying GIS data files to test directory" >> $testLog
cp $gisDir/* .
else
echo "ERROR: GIS data directory $gisDir not found!"
echo "Please correct test setup as described in spec."
exit 10
fi
# Count the test scripts
echo "Running tests using the following scripts:" >> $testLog
echo >> $testLog
ls -l $scriptDir/script*.txt >> $testLog
nCases=`ls $scriptDir/script*.txt | wc -w`
# Perform testing
index=1
############################## run the GIS test cases
while [ "$index" -le "$nCases" ]
do
script="script0$index.txt"
refLog="refLog0$index.txt"
stuLog="stuLog0$index.txt"
echo -n "$Separator " >> $testLog
echo "Testing with $script" >> $testLog
# Write-protect the supplied script and log files
chmod a-w $scriptDir/$script
chmod a-w $logDir/$refLog
# Run student's gis soln on current test file
echo "Running student soln on $scriptDir/$script" >> $testLog
echo >> $testLog
timeout -s SIGKILL 60 ./$exeName $scriptDir/$script $stuLog >> $testLog 2>&1
timeoutStatus="$?"
if [[ $timeoutStatus -eq 124 || $timeoutStatus -eq 137 ]]; then
echo "The test of your solution timed out after 30 seconds." >> $errorLog
elif [[ $timeoutStatus -eq 139 ]]; then
echo "The test of your solution was killed by a SIGSEGV error" >> $errorLog
elif [[ $timeoutStatus -eq 134 ]]; then
echo "The test of your solution was killed by a SIGABRT signal." >> $errorLog
echo "Possible reasons include:" >> $errorLog
echo " - a segfault error" >> $errorLog
echo " - a serious memory access error" >> $errorLog
fi
# Remove write-protection from test files
chmod u+w $scriptDir/$script
chmod u+w $logDir/$refLog
# Verify existence of a nonempty student log file
if [ ! -s $stuLog ]; then
echo "The log file $stuLog was not produced or is empty" >> $testLog
echo >> $testLog
((index +=1))
continue
fi
# Run comparator on current reference and student logs
echo "Running compare on $refLog and $stuLog" >> $testLog
./$compTool $index $logDir/$refLog $stuLog >> $testLog
compLog="compare0$index.txt"
echo "$compLog results:" >> $testLog
cat $compLog >> $testLog
echo >> $testLog
echo $Separator >> $testLog
echo >> $testLog
((index +=1))
done
############################################################ Run valgrind test
echo "Valgrind testing may abort if your program was killed on an earlier test..." >> $testLog
echo "Execution on Valgrind may also mask runtime errors that occurred in earlier test..." >> $testLog
echo >> $testLog
# 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"
tmpVgrind="tmpVgrind.txt"
script="script04.txt"
stuLog="stuvgrindLog.txt"
timeout -s SIGKILL 60 valgrind $vgrindSwitches ./$exeName $scriptDir/$script ./$stuLog >> $tmpVgrind 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." >> $errorLog
elif [[ $timeoutStatus -eq 139 ]]; then
echo "The test of your solution was killed by a SIGSEGV error" >> $errorLog
elif [[ $timeoutStatus -eq 134 ]]; then
echo "The test of your solution was killed by a SIGABRT signal." >> $errorLog
echo "Possible reasons include:" >> $errorLog
echo " - a segfault error" >> $errorLog
echo " - a serious memory access error" >> $errorLog
fi
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
echo >> $vgrindIssues
else
echo "Error running Valgrind test." >> $testLog
fi
############################################################ File report
# Create summary file
# Write header to summary log
cat "$headerLog" > $summaryLog
echo $Separator >> $summaryLog
echo >> $summaryLog
# Gather scores from the test log produced earlier
grep "Score from" $testLog > $scoreLog
echo ">>Scores from testing<<" >> $summaryLog
cat $scoreLog >> $summaryLog
echo >> $summaryLog
echo $Separator >> $summaryLog
echo >> $summaryLog
# Write Valgrind summary into log
if [[ -e $vgrindIssues ]]; then
echo "Summary of valgrind results:" >> $summaryLog
echo >> $summaryLog
cat $vgrindIssues >> $summaryLog
echo $Separator >> $summaryLog
fi
# Write test output to summary log
echo "Detailed results from testing:" >> $summaryLog
echo >> $summaryLog
cat $testLog >> $summaryLog
echo >> $summaryLog
# Write Valgrind details into log
if [[ -e $vgrindLog ]]; then
echo "Details from valgrind check:" >> $summaryLog
echo >> $summaryLog
cat $vgrindLog >> $summaryLog
echo $Separator >> $summaryLog
fi
# Write build log into summary
echo "Results from $buildLog" >> $summaryLog
cat $buildLog >> $summaryLog
echo >> $summaryLog
exit 0