#!/bin/sh # Fritz Sieker # get, possibly extract, compile, and test an assignment # ASSUMPTION: submission consists of a single file (could be a tar/zip etc see # extractFile() for types of files handled.) # set -x #---------------------------------------------------------------------------- # get the testCases file, preferring one with the suffix getTestCases() { if [ -f testCases.${assignName} ] then name=$topDir/testCases.${assignName} elif [ -f testCases ] then name=$topDir/testCases else name= fi if [ -z "$name" ] then echo "Error: can not find testCases file" >&2 fi echo $name } #---------------------------------------------------------------------------- # extract field delimited by %, trim leading/trailing whitespace getPfield() { echo $2 | cut -d'%' -f$1 | sed -e 's/^[ \t]*//; s/[ \t]*$//' } #---------------------------------------------------------------------------- # get the ith parameter of the remaining parameters extractField() { field=$1 shift 1 cmd="echo \$$field" eval $cmd } #---------------------------------------------------------------------------- countLines() { linesAndWords=`wc -l $1` extractField 1 $linesAndWords } #---------------------------------------------------------------------------- # copy data files (if any) to output copyDataFiles() { while [ $# -gt 0 ] do df=$1 shift 1 echo "File: $df" eval cat $df echo done } #---------------------------------------------------------------------------- # extract file(s) from tar/zip etc, if present extractFiles() { fileName=$1 cd $workDir verbose=v removeWrapper=1 suffix=`echo $fileName | sed 's/[^\.]*//'` case $suffix in .bz2) bunzip2 $fileName ;; .-gz) gunzip $fileName ;; .jar) jar -x${verbose}f $fileName ;; .gz) gunzip $fileName ;; .tar) tar -x${verbose}f $fileName ;; .tar.gz) tar -xz${verbose}f $fileName ;; .taz) tar -xZ${verbose}f $fileName ;; .tgz) tar -xz${verbose}f $fileName ;; .zip) unzip $fileName ;; .z) gunzip $fileName ;; .Z) gunzip $fileName ;; .-z) gunzip $fileName ;; *) removeWrapper=0 ;; esac if [ $removeWrapper -eq 1 ] then rm -f $fileName fi cd $topDir } #---------------------------------------------------------------------------- # flatten directory structure (if it exists) flatten() { # move all regular files to top level directory find $workDir -mindepth 2 -type f -exec mv '{}' $workDir \; # now delete all subdirectories (should be empty) find $workDir -mindepth 1 -depth -type d -exec rmdir '{}' \; } #---------------------------------------------------------------------------- # simple attempt to remove package statement from .java files fixPackage() { cd $workDir ls *.java 2> /dev/null if [ $? -eq 0 ] then for file in `ls *.java` do fgrep -v package $file > tmp mv -f tmp $file done fi cd $topDir } #---------------------------------------------------------------------------- checkLate() { # space in "$assignName " to assure PA1 does not match PA1-R or PA10 dueStr=`fgrep "$assignName " $CheckinDir/assignments` dueStr=`extractField 3 $dueStr | sed -s 's/@/ /'` dueSec=`date -d "$dueStr" +%s` # look at date --uc -r $1 +%s stats=`ls -l $1` dmy=`extractField 6 $stats | sed -s 's:-:/:g'` hms=`extractField 7 $stats` submitSec=`date -d "$dmy $hms" +%s` secLate=`expr $submitSec - $dueSec` if [ $secLate -gt 0 ] then hrLate=`expr $secLate / 3600` prod=`expr $hrLate \* 3600` if [ $prod -lt $secLate ] then hrLate=`expr $hrLate + 1` fi fi echo $hrLate } #---------------------------------------------------------------------------- # remove any provided files from submission, so they aren't sent to MOSS removeProvided() { if [ $hasProvided -eq 0 ] then cd provided for file in `ls` do rm -f $workDir/$file done cd $topDir fi } #---------------------------------------------------------------------------- # copy students files to Moss if directory specified copyToMoss() { if [ -n "$mossDir" ] then if [ ! -d $mossDir/$login ] then mkdir -p $mossDir/$login fi cp -f $workDir/* $mossDir/$login fi } #---------------------------------------------------------------------------- # add provided files, overwriting those that exist in $workDir addProvided() { if [ $hasProvided -eq 0 ] then cp -f provided/* $workDir fi } #---------------------------------------------------------------------------- getSubmission() { srcDir=$CheckinDir/$assignName/$login if [ ! -d $srcDir ] then echo "$login: NO submission" >> $gradeDir/ERRORS echo "Nothing checked in" echo "Score: 0 / 0" else # get the files and set up for build # recreate work/output directories rm -rf $outputDir $workDir mkdir $outputDir $workDir #get the newest file (might include LATE_ prefix) # since only newest file copied, it MUST be a tar/zip/etc or a single file submittedFile=`ls -c $srcDir | head -n1` srcFile=$srcDir/$submittedFile finalName=`echo $submittedFile | sed -s 's/LATE_//g'` cp $srcFile $workDir/$finalName echo echo "grading based on file: $submittedFile" echo echo $srcFile | fgrep -q LATE_ > /dev/null if [ $? -eq 0 ] then hoursLate=`checkLate $srcFile` echo "Hours Late: $hoursLate" echo "" fi extractFiles $finalName flatten rm -f work/*~ # remove backup files fixPackage removeProvided copyToMoss fi # else $srcDir exists } #---------------------------------------------------------------------------- # put results and master output in grade file showResults() { fullName=$1 touch $fullName # guarantee file exists (even if empty) if [ $hasStdFilter -eq 0 ] # replace file by filtered one then mv -f $fullName rawOut cat rawOut | stdFilter > $fullName rm -f rawOut fi # allow 60 lines or length of master output, whichever is greater maxLines=$masterLines if [ $maxLines -lt 60 ] then maxLines=60 fi # grab top N lines (avoid results of infinite recursion, large stack trace) head -n$maxLines $fullName > top echo # now produce the ouput student sees in grade file if [ $sideBySide -eq 1 ] then echo "Your Output/Master Output" echo diff $stdDiffFlags $sideBySideFlags top $masterFile else # ? use std diff here, or it is too hard to read? echo "Your Output:" cat top echo echo "Master output:" cat $masterFile fi echo rm top } #---------------------------------------------------------------------------- checkDiff() { diffFile=$1 max=$2 # produce a diff file with only unchanges lines diff --unchanged-group-format="%dn%c'\012'" --old-group-format='' --new-group-format='' $diffFile $masterDir > junk diffRC=$? if [ $diffRC -eq 0 ] then score=$max else # award point on number of "good lines" (lines in common) - future score=0 fi rm junk echo "$score" } #---------------------------------------------------------------------------- generateScore() { echoScore=1 marks=`checkDiff $fileToGrade $points` if [ "$marks" -ne "$points" ] # see if there is a regrade then regradeScript=`getPfield 6 "$testCaseLine"` if [ -n "$regradeScript" ] # try alternate grading then regradePoints=`getPfield 7 "$testCaseLine"` if [ -z "$regradePoints" ] # not specified, default to full value then regradePoints=$points fi cooked=$outputDir/${fileBname}.cooked eval regrade/$regradeScript $testName $regradePoints $fileToGrade $cooked if [ -f $cooked ] then # determine grade by diffing marks=`checkDiff $cooked $regradePoints` else # assume script echoed grade, so supress output of score echoScore=0 fi fi # a regradeScript exists fi if [ $echoScore -eq 1 ] then echo "$testName Score: $marks / $points" fi } #---------------------------------------------------------------------------- gradeSubmission() { FS= rm -rf $outputDir mkdir $outputDir # del everything after #; del leading/trailng whitespace; del blank lines sedCmd='s/#.*// ; s/^[ \t]*// ; s/[ \t]*$// ; /^$/d' sed -e "$sedCmd" testCases.PAx | while read testCaseLine do # extract the various components of the line (use bash [] ?) testName=`getPfield 1 "$testCaseLine"` points=`getPfield 2 "$testCaseLine"` cmdLine=`getPfield 3 "$testCaseLine"` dataFiles=`getPfield 4 "$testCaseLine"` resultFile=`getPfield 5 "$testCaseLine"` # initialize std names of files inputFile=$inputDir/$testName outputFile=$outputDir/$testName rm -f $outputFile touch $outputFile echo "TEST: $testName CMD LINE: $cmdLine" echo if [ -n "$cmdLine" ] # execute the test then cd $workDir eval $cmdLine > $outputFile 2>&1 cd $topDir fi copyDataFiles $dataFiles if [ -n "$resultFile" ] then fileToGrade=`eval echo $resultFile` fileBname=`basename $fileToGrade` masterFile=$masterDir/$fileBname masterLines=`countLines $masterFile` showResults $fileToGrade generateScore $fileToGrade fi echo "--------------------------------------------------------" done } #---------------------------------------------------------------------------- getBuildAndRun() { getSubmission echo "--------------------------------------------------------" addProvided make -s -C $workDir clean # first make sure no binaries left around make -s -C $workDir # now try to build it makeResult=$? if [ $makeResult -eq 0 ] then # run test cases gradeSubmission else echo "$login: make failed" >> $gradeDir/ERRORS echo "make failed" echo "make Score: 0 / 0" fi } #---------------------------------------------------------------------------- if [ $# -ne 2 ] then echo "Usage: gradeAssignment " echo echo "Ex: gradeAssignment PA1 fsieker" exit 1 fi if [ -z "$CheckinDir" ] then CheckinDir=~/Checkin fi topDir=`pwd` assignName=$1 login=$2 workDir=$topDir/work inputDir=$topDir/input outputDir=$topDir/output masterDir=$topDir/master # std settings, can be overridden by .ini file sideBySide=1 #sideBySideFlags="-y --left-column --width=200" sideBySideFlags="-y --width=200" stdDiffFlags="-B" skipGrading=0 mossDir= testCases=`getTestCases` if [ -z "$testCases" ] then exit 1 fi # look for .ini file and source it in to override std settings iniFile=grade.ini if [ -f $iniFile ] then . $iniFile elif [ -f ~/$iniFile ] then . ~/$iniFile fi gradeDir=$topDir/grades.${assignName} gradeFile=$gradeDir/${login}.grade rm -rf $workDir mkdir $workDir if [ $skipGrading -eq 1 ] then echo "fetching: $login" getSubmission > /dev/null 2>&1 exit 0 fi ls provided/* > /dev/null 2>&1 hasProvided=$? which stdFilter > /dev/null 2>&1 hasStdFilter=$? echo "grading: $login" if [ ! -d $gradeDir ] then mkdir $gradeDir fi rm -f $gradeFile touch $gradeFile getBuildAndRun >> $gradeFile 2>&1