Skip to content
Snippets Groups Projects
Commit 26795cf8 authored by Hague Matthew UXAC009's avatar Hague Matthew UXAC009
Browse files
parents 59524cf8 1e34aaf5
Branches
No related tags found
No related merge requests found
Showing
with 280 additions and 46 deletions
......@@ -7,9 +7,7 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
......@@ -28,6 +26,7 @@ public abstract class BaseTester {
* For custom error messages for tests failing by exception
*/
public static class FailedTestException extends Exception {
private static final long serialVersionUID = -7087131188058589135L;
public FailedTestException(String msg) { super(msg); }
}
......@@ -259,7 +258,7 @@ public abstract class BaseTester {
*/
protected boolean outputResult(List<String> failedMessages,
SubmissionWrapper wrapper) {
return outputResult(failedMessages, wrapper, false);
return outputResult(failedMessages, wrapper, false, null);
}
/**
......@@ -272,17 +271,23 @@ public abstract class BaseTester {
* scriptedInteraction containing at least one error message
* @param wrapper the submission wrapper used
* @param outputCommandLine include the command line in the output
* @param description a description of the test to show before
* failing interaction (or null if non to show)
* @return true if failedMessage is null (i.e. test passed)
*/
protected boolean outputResult(List<String> failedMessages,
SubmissionWrapper wrapper,
boolean outputCommandLine) {
boolean outputCommandLine,
String description) {
if (failedMessages != null) {
out.printCodeLine("Test failed!\n");
if (description != null)
out.printCodeLine(description);
out.printCodeLine("Failing interaction:");
if (outputCommandLine) {
String[] args = wrapper.getCommandLineArgs();
out.printCodeLine("$ " + wrapper.getRunCommand());
}
......
......@@ -5,6 +5,7 @@ import java.io.File;
import java.lang.ClassLoader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
......@@ -98,6 +99,7 @@ public class CodeTester {
*/
public static class StudentCodeException
extends BaseTester.FailedTestException {
private static final long serialVersionUID = -4498256171743133274L;
public StudentCodeException(String msg) { super(msg); }
}
......@@ -253,9 +255,11 @@ public class CodeTester {
String className,
Object... params) throws
BaseTester.FailedTestException {
Class<?>[] parameterTypes
= Stream.of(params).map(p -> p.getClass())
.toArray(Class<?>[]::new);
Class<?>[] parameterTypes = Stream.of(params).map(
p -> p != null ? p.getClass() : NullType.class
).toArray(Class<?>[]::new);
try {
Class<?> klass = loadClass(className);
Constructor<?> constructor
......@@ -279,6 +283,25 @@ public class CodeTester {
throw new BaseTester.FailedTestException(expandMsg(msg));
}
/**
* Attempts to construct an array and log it
*
* @param klass the type of the array (can be e.g. int.class for
* primitives)
* @param values the values to fill array with
* @return the array
*/
public Object constructArray(Class<?> klass, Object[] values) {
Object array = Array.newInstance(klass, values.length);
for (int i = 0; i < values.length; i++) {
Array.set(array, i, values[i]);
}
recordConstructArray(array, values);
return array;
}
/**
* Convenience for constructMsg with default messages
*/
......@@ -301,6 +324,23 @@ public class CodeTester {
return constructMsg(msg, invokeMsg, className, params);
}
/**
* Convenience for callMsgAcceptNull with acceptNull set to true
*/
public Object callMsg(String msg,
String invocationMsg,
Object o,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
return callMsgAcceptNull(msg,
invocationMsg,
true,
o,
methodName,
params);
}
/**
* Attempts to call an object method
*
......@@ -308,20 +348,24 @@ public class CodeTester {
* msg and invocationMsg can contain LOG_EXPAND which will be replaced by
* getIndentedLinesString in the exception.
*
* Can specify whether to cause a failure if the method returns null
*
* @param msg the failure message
* @param invocationMsg failure message prefix in case method call
* throws exception (student code fails)
* @param acceptNull whether the result of the call can be null
* @param o the object to call the method on
* @param String name method name
* @param parameterTypes the arguments (cannot be null else type
* can't be inferred, with throw NPE)
* @return the result object
*/
public Object callMsg(String msg,
String invocationMsg,
Object o,
String methodName,
Object... params) throws
public Object callMsgAcceptNull(String msg,
String invocationMsg,
boolean acceptNull,
Object o,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
Class<?>[] parameterTypes
= Stream.of(params).map(p -> p.getClass())
......@@ -331,6 +375,13 @@ public class CodeTester {
Object r = invokeMethod(invocationMsg, m, o, params);
if (r == null && !acceptNull) {
throw new BaseTester.FailedTestException(
expandMsg(invocationMsg) + "\n\n" +
"The method call returned null but a non-null value was expected."
);
}
recordCall(r, o, m, params);
return r;
......@@ -349,13 +400,27 @@ public class CodeTester {
throw new BaseTester.FailedTestException(expandMsg(msg));
}
/**
* Convenience for callMsg with default messages.
*
* Will allow method to return null.
*/
public Object call(Object o,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
return callAcceptNull(true, o, methodName, params);
}
/**
* Convenience for callMsgAcceptNull with default messages.
*/
public Object callAcceptNull(boolean acceptNull,
Object o,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
String base;
if (getLines().size() > 0) {
......@@ -370,7 +435,30 @@ public class CodeTester {
String msg = base + "could not be made.";
String invokeMsg = base + "went wrong.";
return callMsg(msg, invokeMsg, o, methodName, params);
return callMsgAcceptNull(msg,
invokeMsg,
acceptNull,
o,
methodName,
params);
}
/**
* Convenience for callStaticMsgAcceptNull with acceptNull as true
*/
public Object callStaticMsg(String msg,
String invocationMsg,
Class<?> klass,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
return callStaticMsgAcceptNull(msg,
invocationMsg,
true,
klass,
methodName,
params);
}
/**
......@@ -380,19 +468,23 @@ public class CodeTester {
* msg and invocationMsg can contain LOG_EXPAND which will be replaced by
* getIndentedLinesString in the exception.
*
* Can specify whether a null return value is an error.
*
* @param msg the failure message
* @param invocationMsg failure message prefix in case method call
* throws exception (student code fails)
* @param acceptNull whether null is an ok return value
* @param klass the class having the method
* @param String name method name
* @param parameterTypes the arguments
* @return the result object
*/
public Object callStaticMsg(String msg,
String invocationMsg,
Class<?> klass,
String methodName,
Object... params) throws
public Object callStaticMsgAcceptNull(String msg,
String invocationMsg,
boolean acceptNull,
Class<?> klass,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
Class<?>[] parameterTypes
= Stream.of(params).map(p -> p.getClass())
......@@ -409,6 +501,13 @@ public class CodeTester {
Object r = invokeMethod(invocationMsg, m, null, params);
if (r == null && !acceptNull) {
throw new BaseTester.FailedTestException(
expandMsg(invocationMsg) + "\n\n" +
"The method call returned null but a non-null value was expected."
);
}
recordStaticCall(r, klass, m, params);
return r;
......@@ -419,21 +518,40 @@ public class CodeTester {
Utils.makeThrowableMsg(e.getCause())
);
}
catch (BaseTester.FailedTestException e) {
throw e;
}
catch (Throwable e) {
/* fall through */
System.out.println(e);
}
// failures fall through here
throw new BaseTester.FailedTestException(expandMsg(msg));
}
/**
* Convenience for callMsg with default messages.
* Convenience for callMsgAcceptNull with acceptNull as true
*/
public Object callStatic(Class<?> klass,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
return callStaticAcceptNull(true,
klass,
methodName,
params);
}
/**
* Convenience for callStaticMsgAcceptNull with default messages.
*/
public Object callStaticAcceptNull(boolean acceptNull,
Class<?> klass,
String methodName,
Object... params) throws
BaseTester.FailedTestException {
String base;
if (getLines().size() > 0) {
......@@ -448,10 +566,14 @@ public class CodeTester {
String msg = base + "could not be made.";
String invokeMsg = base + "went wrong.";
return callStaticMsg(msg, invokeMsg, klass, methodName, params);
return callStaticMsgAcceptNull(msg,
invokeMsg,
acceptNull,
klass,
methodName,
params);
}
/**
* If a name is recorded for the object, return it, else null
*/
......@@ -459,7 +581,6 @@ public class CodeTester {
return objectNames.get(System.identityHashCode(o));
}
/**
* Expand a string with log and arguments.
*
......@@ -615,7 +736,10 @@ public class CodeTester {
* dependencies..
*/
private boolean isAssignable(Class<?> k1, Class<?> k2) {
return boxClass(k1).isAssignableFrom(boxClass(k2));
if (k2 == NullType.class)
return true;
else
return boxClass(k1).isAssignableFrom(boxClass(k2));
}
/**
......@@ -654,6 +778,28 @@ public class CodeTester {
setLastCall("new " + className + "(" + paramsString + ")");
}
/**
* Record construction of an array with given values
*
* The values will be converted to (internal) variable names if they
* are object already constructed by this object. Else we rely on
* the toString method returning something useful.
*/
private void recordConstructArray(Object array, Object[] values) {
String name = getNewVariableName(array);
String className = getClassString(array.getClass());
String valuesString = getParamsString(values);
if (getIsRecording()) {
lines.add(className + " " + name +
" = new " + className +
" { " + valuesString + " }");
}
setLastCall("new " + className + " { " + valuesString + " }");
}
/**
* Record a call method object with given params.
*/
......@@ -747,6 +893,8 @@ public class CodeTester {
if (name == null) {
if (p instanceof String)
name = "\"" + p + "\"";
else if (p instanceof Character)
name = "'" + p + "'";
else
name = p.toString();
}
......@@ -786,7 +934,14 @@ public class CodeTester {
if (Character.isUpperCase(c))
sb.append(Character.toLowerCase(c));
}
return sb.toString();
String base = sb.toString();
// primitive arrays will be empty this way
if (base.isEmpty())
return Character.toString(className.charAt(0));
else
return base;
}
/**
......@@ -941,4 +1096,9 @@ public class CodeTester {
else
return logIdealizer.apply(output);
}
/**
* A class to represent a null argument which can be any object type
*/
private class NullType { }
}
......@@ -4,10 +4,8 @@ package uk.ac.rhul.cs.javatester;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Class for handling command line parameters.
......
......@@ -174,6 +174,8 @@ public class SubmissionWrapper implements AutoCloseable {
* Close the streams and kill the submission
*/
public void close() {
waitFor();
flushOutputStreams();
try {
......@@ -496,6 +498,30 @@ public class SubmissionWrapper implements AutoCloseable {
.toArray(String[]::new);
}
/**
* Construct a default interaction with default fail response for an output
*/
public static String[] expectedOutput(String output) {
return new String[] {
"> " + output,
"Expected the output '" + output + "'."
};
}
/**
* Construct a default interaction with fail response for an output
*/
public static String[] expectedOutput(String output, String failMsg) {
return new String[] { "> " + output, failMsg };
}
/**
* Construct an interaction for an input
*/
public static String[] input(String input) {
return new String[] { "< " + input };
}
private void recordInteractionOutputLine(String line) {
interactionBuffer.append("> " + line + "\n");
outputLines.add(line);
......@@ -527,6 +553,9 @@ public class SubmissionWrapper implements AutoCloseable {
try {
process.waitFor(TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// no nothing
} finally {
// make sure it's really shutdown
process.destroyForcibly();
}
}
......
......@@ -43,11 +43,13 @@ public class TesterOutput {
if (!isHTML) {
// will work for \r\n and \n
indented = CODE_INDENT;
indented = "";
if (code == null) {
indented += code;
} else {
indented += code.replaceAll("\n", "\n" + CODE_INDENT);
indented = CODE_INDENT + code;
} else if (code.length() > 0) {
indented
= CODE_INDENT +
code.replaceAll("\n(?!($|\n))", "\n" + CODE_INDENT);
}
}
......
......@@ -14,11 +14,9 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class UnitTests {
private static final String THING_RE = ".*XYZ\\s*";
private static final String OUT_THING_RE = "> .*XYZ\\s*";
private static final String THING_FAIL_MSG
= "Expected a thing ending in XYX";
......@@ -37,14 +35,13 @@ public class UnitTests {
private static final String NULL_PTR_CLASS
= "uk.ac.rhul.cs.javatester.solution.NullPtrMethod";
// TODO: import some of the non-fail tests from Assignment3Tester
@Rule
public Timeout globalTimeout = Timeout.seconds(10);
@Test
public void testMainClassFail() throws IOException {
BaseTester base = new BaseTester("Not a class") {
@SuppressWarnings("unused")
public void testMainClass()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -151,6 +148,7 @@ public class UnitTests {
@Test
public void testLoadClass() {
BaseTester tester = new BaseTester(STORAGE) {
@SuppressWarnings("unused")
public boolean testLoadClass()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -164,6 +162,7 @@ public class UnitTests {
@Test
public void testLoadClassFail() {
BaseTester tester = new BaseTester(STORAGE) {
@SuppressWarnings("unused")
public boolean testLoadClass()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -177,6 +176,7 @@ public class UnitTests {
@Test
public void testConstruct() {
BaseTester tester = new BaseTester(STORAGE) {
@SuppressWarnings("unused")
public boolean testConstruct()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -190,6 +190,7 @@ public class UnitTests {
@Test
public void testGetConstructFail() {
BaseTester tester = new BaseTester(STORAGE) {
@SuppressWarnings("unused")
public boolean testGetConstruct()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -203,6 +204,7 @@ public class UnitTests {
@Test
public void testCallMethod() {
BaseTester tester = new BaseTester(STORAGE) {
@SuppressWarnings("unused")
public boolean testCallMethod()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -217,6 +219,7 @@ public class UnitTests {
@Test
public void testCallMethodFail() {
BaseTester tester = new BaseTester(STORAGE) {
@SuppressWarnings("unused")
public boolean testCallMethod()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
......@@ -231,6 +234,7 @@ public class UnitTests {
@Test
public void testOptInOutHasNot() {
BaseTester tester = new BaseTester(SIMPLE_IN_OUT) {
@SuppressWarnings("unused")
public boolean testOptInOut()
throws BaseTester.FailedTestException, IOException {
return scriptedInteraction(
......@@ -250,6 +254,7 @@ public class UnitTests {
@Test
public void testOptInOutHasSo() {
BaseTester tester = new BaseTester(LONGER_IN_OUT) {
@SuppressWarnings("unused")
public boolean testOptInOut()
throws BaseTester.FailedTestException, IOException {
return scriptedInteraction(
......@@ -269,6 +274,7 @@ public class UnitTests {
@Test
public void testPrivate() {
BaseTester tester = new BaseTester(PRIVATE_CLASS) {
@SuppressWarnings("unused")
public boolean testAccess() throws FailedTestException {
CodeTester ct = new CodeTester();
Object priv = ct.construct(PRIVATE_CLASS);
......@@ -286,6 +292,7 @@ public class UnitTests {
@Test
public void testNullPtrInCall() {
BaseTester tester = new BaseTester(NULL_PTR_CLASS) {
@SuppressWarnings("unused")
public boolean testNullPtr() throws FailedTestException {
CodeTester ct = new CodeTester();
Object np = ct.construct(NULL_PTR_CLASS);
......@@ -299,9 +306,40 @@ public class UnitTests {
@Test
public void testNullPtrInConstructor() {
BaseTester tester = new BaseTester(NULL_PTR_CLASS) {
@SuppressWarnings("unused")
public boolean testNullPtr() throws FailedTestException {
CodeTester ct = new CodeTester();
Object np = ct.construct(NULL_PTR_CLASS, "x");
ct.construct(NULL_PTR_CLASS, "x");
return true;
}
};
assertFalse(tester.runTests());
}
@Test
public void testCallMethodNoNull() {
BaseTester tester = new BaseTester(NULL_PTR_CLASS) {
@SuppressWarnings("unused")
public boolean testCallMethod()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
Object o = ct.construct(NULL_PTR_CLASS);
ct.callAcceptNull(false, o, "getNull");
return true;
}
};
assertFalse(tester.runTests());
}
@Test
public void testCallMethodStaticNoNull() {
BaseTester tester = new BaseTester(NULL_PTR_CLASS) {
@SuppressWarnings("unused")
public boolean testCallMethod()
throws BaseTester.FailedTestException {
CodeTester ct = new CodeTester();
Class<?> klass = ct.loadClass(NULL_PTR_CLASS);
ct.callStaticAcceptNull(false, klass, "getNull");
return true;
}
};
......
......@@ -2,11 +2,6 @@
package uk.ac.rhul.cs.javatester.solution;
import java.util.Scanner;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Assignment {
public static void main(String[] args) {
......@@ -14,9 +9,10 @@ public class Assignment {
doThing("This is a question");
}
@SuppressWarnings("resource")
private static void doThing(String question) {
System.out.println(question + " XYZ");
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine().trim();
scanner.nextLine().trim();
}
}
......@@ -4,6 +4,7 @@ package uk.ac.rhul.cs.javatester.solution;
import java.util.Scanner;
public class LongerInOut {
@SuppressWarnings("resource")
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
......
......@@ -5,13 +5,19 @@ public class NullPtrMethod {
public NullPtrMethod() { }
@SuppressWarnings("null")
public NullPtrMethod(String x) {
String n = null;
n.length();
}
@SuppressWarnings("null")
public void nullPtr() {
String n = null;
n.length();
}
public static String getNull() {
return null;
}
}
......@@ -8,8 +8,10 @@ package uk.ac.rhul.cs.javatester.solution;
class PrivateClass {
PrivateClass() { }
@SuppressWarnings("unused")
private PrivateClass(String isPrivate) { }
@SuppressWarnings("unused")
private void privateMethod() { }
protected void protectedMethod() { }
void packageMethod() { }
......
......@@ -4,6 +4,7 @@ package uk.ac.rhul.cs.javatester.solution;
import java.util.Scanner;
public class SimpleInOut {
@SuppressWarnings("resource")
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
......
package uk.ac.rhul.cs.javatester.solution;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
public class Storage {
String value;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment