package au.edu.mq.comp.junitGrading.grader;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import au.edu.mq.comp.common.*;
import au.edu.mq.comp.junitGrading.Assignment;
import au.edu.mq.comp.junitGrading.GlobalSetting;
import au.edu.mq.comp.junitGrading.GraderWorker;
import au.edu.mq.comp.junitGrading.TestResult;
/**
*
* @author psksvp@gmail.com
* a GraderWorker for a java assignment
*/
public class JavaGrader extends GraderWorker
{
private String GradingRunListenerDotjava = "";
private String GradedDotJava = "";
private String ResourceDotJava = "";
//////////////////////////////////////////////
private String unitTestClassName;
private java.util.HashMap<String, String> junitTestSourceMapCollection = new java.util.HashMap<String, String>();
private java.util.ArrayList<String> junitTestSourceFullPathCollection = new java.util.ArrayList<String>();
private String junitTestSourceZipFilePath = null;
private java.util.ArrayList<String> libraryCollection = new java.util.ArrayList<String>();
private java.util.ArrayList<String> testDataZipCollection = new java.util.ArrayList<String>();
//////////////////////////////////////////////
public JavaGrader clone()
{
try
{
JavaGrader twin = new JavaGrader(this.unitTestClassName);
if(null != this.junitTestSourceZipFilePath)
twin.addJUnitTestSource(this.junitTestSourceZipFilePath);
for(String path : this.junitTestSourceFullPathCollection)
twin.addJUnitTestSource(path);
for(String path : this.libraryCollection)
twin.addLibrary(path);
for(String path : this.testDataZipCollection)
twin.addTestDataZipFile(path);
return twin;
}
catch(Exception ex)
{
Log.error("Exception thown at JavaGrader.clone() with message ->" + ex.toString());
return null;
}
}
/**
* constructor
* @param unitTestClassName - <p>must be a fully qualified class name. This class(with junit @Test) is usually provided by instructor to test target class which is written by a student</p>
* @throws IOException - if CodeTemplate is not found.
*/
public JavaGrader(String unitTestClassName) throws IOException
{
this.unitTestClassName = new String(unitTestClassName);
// all path name in JAR and out size of JAR is case sensitive
this.GradingRunListenerDotjava = SimpleFileIO.readTextFromSelfJarBundleFile(this, GlobalSetting.programResourcePath() + "/CodeTemplate/GradingRunListenerDotJava");
this.GradedDotJava = SimpleFileIO.readTextFromSelfJarBundleFile(this, GlobalSetting.programResourcePath() + "/CodeTemplate/GradedDotJava");
this.ResourceDotJava = SimpleFileIO.readTextFromSelfJarBundleFile(this, GlobalSetting.programResourcePath() + "/CodeTemplate/ResourceDotJava");
if(0 == this.GradingRunListenerDotjava.length() ||
0 == this.GradedDotJava.length() ||
0 == this.ResourceDotJava.length())
{
Log.error("at class JavaGrader, Fail to read code template from /Resources/CodeTemplate/");
throw new IOException("at class JavaGrader, Fail to read code template from /Resources/CodeTemplate/");
}
}
/**
*
* @param path - to just unit test source <p>If the test has no package name(default package), the junit test class source file does not need
* to be in a zip archive and can be specified like the example below. If the test needs more java files,
* it can be specified using the same key name. Or all the files can be in one zip file, but the zip
* file must not have directory structure.
* if the test has package name, all junit test class source files must be in a zip file with correct
* directory structure according to the package name. </p>
* @throws IOException
*/
public void addJUnitTestSource(String path) throws IOException
{
String extension = SimpleFileIO.extensionOfFileName(path);
if(true == extension.equalsIgnoreCase("java"))
{
String sourceContent = SimpleFileIO.readTextFromFile(path);
this.junitTestSourceMapCollection.put((new java.io.File(path)).getName(), sourceContent);
this.junitTestSourceFullPathCollection.add(path);
}
else if(true == extension.equalsIgnoreCase("zip"))
{
this.junitTestSourceZipFilePath = new String(path);
}
else
{
Log.message("edu.macquarie.computing.junitGrading.grader.Automark::addJUnitTestSource is ignoring file -->" + path);
}
}
/**
*
* @param pathToJAR - path to external library JAR file needed by this test
*/
public void addLibrary(String pathToJAR)
{
this.libraryCollection.add(pathToJAR);
}
/**
*
* @param pathToZipOfData - path to data zip file if this test needed external data. It must be in a zip file
*/
public void addTestDataZipFile(String pathToZipOfData)
{
this.testDataZipCollection.add(pathToZipOfData);
}
/**
* compile java source in the Assignment being graded
* @param assignment
* @param runResult
* @return
* @throws Exception
*/
private boolean compile(Assignment assignment, TestResult runResult) throws Exception
{
JavaCodeCompiler compiler = new JavaCodeCompiler();
this.copyJUnitTestSource(assignment.containerDirectory().getAbsolutePath());
compiler.appendLibraryClassPath(assignment.containerDirectory().getAbsolutePath());
compiler.appendLibraryClassPath(OS.pathToRunningJAR());
for(String libraryPath : this.libraryCollection)
{
compiler.appendLibraryClassPath(libraryPath);
}
this.copyJUnitDriverSource(assignment.containerDirectory().getAbsolutePath());
java.util.List<java.io.File> listOfJavaFiles = SimpleFileIO.traverseDirectoryForFiles(assignment.containerDirectory(), "java");
if(0 != compiler.compileJavaFilesInList(listOfJavaFiles))
{
runResult.appendResult("RunResult", "CompileFail");
runResult.setErrorMessage("compiler process returned with error " + compiler.errorString());
return false;
}
else
{
return true;
}
}
/**
* run the test
* @param assignment
* @param runResult
* @throws Exception
*/
private void run(Assignment assignment, TestResult runResult) throws Exception
{
JavaClassRunner jcr = new JavaClassRunner(assignment.containerDirectory().getAbsolutePath());
jcr.appendLibraryClassPath(OS.pathToRunningJAR());
for(String libraryPath : this.libraryCollection)
{
jcr.appendLibraryClassPath(libraryPath);
}
for(String pathToZip : this.testDataZipCollection)
{
SimpleFileIO.unzipFile(new java.io.File(pathToZip),
new java.io.File(assignment.containerDirectory().getAbsolutePath() +
File.separator + "Resources"));
}
String packageName = this.packageNameOfUnitTestClass().length() > 0 ? this.packageNameOfUnitTestClass() + "." : "";
String classNameToRun = packageName + "GradingRunListener";
int exitCode = jcr.runAndWait(classNameToRun, TimeUnit.SECONDS.toMillis(GlobalSetting.processWaitTimeout()));
if(0 == exitCode)
{
int startIndex = jcr.outputString().indexOf("<dataStart>");
int endIndex = jcr.outputString().indexOf("<dataEnd>");
if(startIndex >= 0 && endIndex >= 0)
{
String data = jcr.outputString().substring(startIndex + "<dataStart>".length(), endIndex);
runResult.appendResult("RunResult", "success");
runResult.appendResultFromMapInString(data);
}
else
{
runResult.appendResult("RunResult", "DataError");
runResult.setErrorMessage("there is no <dataStart> or <dataEnd> marker in the output from GradingRunListener.java");
}
}
else if(JavaClassRunner.kWaitForProcessTimeout == exitCode)
{
runResult.appendResult("RunResult", "RunTimeout");
runResult.setErrorMessage("junittest process returned with error: possible runnaway process");
}
else
{
runResult.appendResult("RunResult", "RunFail");
runResult.setErrorMessage("junittest process returned with error" + jcr.errorString());
}
}
@Override
public TestResult gradeAnAssignment(Assignment assignment)
{
TestResult runResult = new TestResult(assignment.ownerIdentification());
try
{
if(true == this.compile(assignment, runResult))
this.run(assignment, runResult);
}
catch(Exception e)
{
runResult.appendResult("RunResult", "ExceptionThrown");
runResult.setErrorMessage("exception raised -->" + e.toString());
Log.error(runResult.errorMessage());
e.printStackTrace();
}
try
{
SimpleFileIO.writeStringToTextFile(runResult.toString(), assignment.containerDirectory().getAbsolutePath() + File.separator + "runResult.txt");
}
catch(Exception e)
{
Log.error("Fail to write run result to " + assignment.containerDirectory().getAbsolutePath());
}
return runResult;
}
/**
*
* @param destinationPath
* @throws IOException
*/
private void copyJUnitTestSource(String destinationPath) throws IOException
{
for(String sourceName : junitTestSourceMapCollection.keySet())
{
String pathToWriteUnitTestJavaSourceFile = destinationPath + File.separator + sourceName;
SimpleFileIO.writeStringToTextFile(this.junitTestSourceMapCollection.get(sourceName), pathToWriteUnitTestJavaSourceFile);
}
if(null != this.junitTestSourceZipFilePath)
SimpleFileIO.unzipFile(new java.io.File(this.junitTestSourceZipFilePath), new java.io.File(destinationPath));
}
/**
*
* @param destinationPath
* @throws IOException
*/
private void copyJUnitDriverSource(String destinationPath) throws IOException
{
String packageName = this.makePackageNameOfUnitTestClass();
//--------------------------
//GradingRunListener.java
String source = this.GradingRunListenerDotjava.replace(GlobalSetting.codeTemplatePackageName(), packageName);
source = source.replace("/*%%%(junitTest.class)%%%*/", "core.run(" + this.unitTestClassName + ".class);");
String packagePath = this.classNameWithPackageToPath(this.unitTestClassName);
String finalPath = destinationPath + File.separator + packagePath + "GradingRunListener.java";
SimpleFileIO.writeStringToTextFile(source, finalPath);
//--------------------------
//Graded.java
source = this.GradedDotJava.replace(GlobalSetting.codeTemplatePackageName(), packageName);
finalPath = destinationPath + File.separator + packagePath + "Graded.java";
SimpleFileIO.writeStringToTextFile(source, finalPath);
//--------------------------
//Resource.java
source = this.ResourceDotJava.replace(GlobalSetting.codeTemplatePackageName(), packageName);
if(true == OS.isAnyWindows())
source = source.replace("$(path)$", (destinationPath + File.separator + "Resources").replace("\\", "\\\\"));
else
source = source.replace("$(path)$", destinationPath + File.separator + "Resources");
finalPath = destinationPath + File.separator + packagePath + "Resource.java";
SimpleFileIO.writeStringToTextFile(source, finalPath);
}
/**
*
* @return
*/
private String makePackageNameOfUnitTestClass()
{
int index = this.unitTestClassName.lastIndexOf('.');
if(index <= 0)
return "";
else
{
String packageName = this.unitTestClassName.substring(0, index);
return "package " + packageName + ";";
}
}
/**
*
* @return
*/
private String packageNameOfUnitTestClass()
{
int index = this.unitTestClassName.lastIndexOf('.');
if(index <= 0)
return "";
else
return this.unitTestClassName.substring(0, index);
}
/**
*
* @param packageName
* @return
*/
private String classNameWithPackageToPath(String packageName)
{
int index = packageName.lastIndexOf('.');
if(index <= 0)
return File.separator;
else
return packageName.substring(0, index).replace('.', OS.fileSeparator().charAt(0)) + OS.fileSeparator();
}
}
////////////////////////
/*
String pathToUnitTestJavaClassFile = assignment.containerDirectory() + "/" + this.unitTestClassName + ".class";
FileClassLoader classLoader = new FileClassLoader();
Class<?> junitTestClass = classLoader.createClass(new java.io.File(pathToUnitTestJavaClassFile));
Result result = JUnitCore.runClasses(junitTestClass.newInstance().getClass());
int failures = 0;
StringBuffer output = new StringBuffer();
for (Failure failure : result.getFailures())
{
output.append(failure.toString());
output.append(",\t");
failures++;
}
output.append("There are "+failures+" failures.");
int mark = (4-failures)*25;
output.append("Your mark is "+mark+" out of 100");
return assignment.ownerIdentification() + ",\t" + output.toString(); */
/*
* @Override
public RunResult gradeAnAssignment(Assignment assignment)
{
RunResult runResult = new RunResult();
runResult.appendResult("identification", assignment.ownerIdentification());
try
{
JavaCodeCompiler compiler = new JavaCodeCompiler();
this.copyJUnitTestSource(assignment.containerDirectory().getAbsolutePath());
compiler.appendLibraryClassPath(assignment.containerDirectory().getAbsolutePath());
for(String libraryPath : this.libraryCollection)
{
compiler.appendLibraryClassPath(libraryPath);
}
this.copyJUnitDriverSource(assignment.containerDirectory().getAbsolutePath());
java.util.List<java.io.File> listOfJavaFiles = SimpleFileIO.traverseDirectoryForFiles(assignment.containerDirectory(), "java");
if(0 == compiler.compileJavaFilesInList(listOfJavaFiles))
{
JavaClassRunner jcr = new JavaClassRunner(assignment.containerDirectory().getAbsolutePath());
for(String libraryPath : this.libraryCollection)
{
jcr.appendLibraryClassPath(libraryPath);
}
for(String pathToZip : this.testDataZipCollection)
{
SimpleFileIO.unzipFile(new java.io.File(pathToZip),
new java.io.File(assignment.containerDirectory().getAbsolutePath() +
OS.fileSeparator() + "Resources"));
}
String packageName = this.packageNameOfUnitTestClass().length() > 0 ? this.packageNameOfUnitTestClass() + "." : "";
String classNameToRun = packageName + "GradingRunListener";
int exitCode = jcr.runAndWait(classNameToRun, TimeUnit.SECONDS.toMillis(GlobalSetting.processTimeout()));
if(0 == exitCode)
{
int startIndex = jcr.outputString().indexOf("<dataStart>");
int endIndex = jcr.outputString().indexOf("<dataEnd>");
if(startIndex >= 0 && endIndex >= 0)
{
String data = jcr.outputString().substring(startIndex + "<dataStart>".length(), endIndex);
runResult.appendResult("RunResult", "success");
runResult.appendResultFromMapInString(data);
}
else
{
runResult.appendResult("RunResult", "DataError");
runResult.setErrorMessage("there is no <dataStart> or <dataEnd> marker in the output from GradingRunListener.java");
}
}
else if(JavaClassRunner.kWaitForProcessTimeout == exitCode)
{
runResult.appendResult("RunResult", "RunTimeout");
runResult.setErrorMessage("junittest process returned with error: possible runnaway process");
}
else
{
runResult.appendResult("RunResult", "RunFail");
runResult.setErrorMessage("junittest process returned with error" + jcr.errorString());
}
}
else
{
runResult.appendResult("RunResult", "CompileFail");
runResult.setErrorMessage("compiler process returned with error " + compiler.errorString());
}
}
catch(Exception e)
{
runResult.appendResult("RunResult", "ExceptionThrown");
runResult.setErrorMessage("exception raised -->" + e.toString());
Log.error(runResult.errorMessage());
e.printStackTrace();
}
return runResult;
}
*/