Skip to content

Commit

Permalink
v1.1 Redirect stdout and stderr to NULL
Browse files Browse the repository at this point in the history
  • Loading branch information
superfashi committed Dec 27, 2019
1 parent f78ff28 commit b33f46b
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 73 deletions.
Binary file removed jh61b-1.0.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>com.gradescope</groupId>
<artifactId>jh61b</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<version>1.1</version>
<name>jh61b</name>
<url>http://maven.apache.org</url>
<dependencies>
Expand Down
163 changes: 91 additions & 72 deletions src/main/java/com/gradescope/jh61b/grader/GradedTestListenerJSON.java
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
//adapted from http://memorynotfound.com/add-junit-listener-example/
package com.gradescope.jh61b.grader;

import java.util.List;
import java.util.ArrayList;

import com.gradescope.jh61b.junit.JUnitUtilities;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import java.util.Collection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

import com.gradescope.jh61b.junit.JUnitUtilities;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class GradedTestListenerJSON extends RunListener {

private static final int MAX_OUTPUT_LENGTH = 8192;

/** Storage of print output that has been intercepted. */
private static ByteArrayOutputStream capturedData = new ByteArrayOutputStream();
/** Tracks original StdOut for when capturing end */
/**
* Tracks original StdOut and StdErr for when capturing end
*/
private static final PrintStream STDOUT = System.out;

private static final PrintStream STDERR = System.err;
/* Current test result. Created at the beginning of every test, completed at the
end of every test. */
private static TestResult currentTestResult;
Expand All @@ -39,90 +34,106 @@ public class GradedTestListenerJSON extends RunListener {
/* Test run start time. */
private static long startTime;

/** Returns the name of a test as stored in an annotation.
* TODO: Is there a more elegant way to do this? */
/**
* Returns the name of a test as stored in an annotation.
* TODO: Is there a more elegant way to do this?
*/
private static String getAnnotationString(Annotation x, String annotationStringName) throws
IllegalAccessException, InvocationTargetException {
IllegalAccessException, InvocationTargetException {
Method[] methods = x.getClass().getDeclaredMethods();
/** If the annotation has a method name() that returns
* a String, invoke that method and return the result.
*/
/* If the annotation has a method name() that returns
a String, invoke that method and return the result.
*/

for (Method m : methods) {
if (m.getName().equals(annotationStringName) &&
m.getReturnType().getCanonicalName().equals("java.lang.String")) {
m.getReturnType().getCanonicalName().equals("java.lang.String")) {
return (String) m.invoke(x);
}
}
return "Uh-oh, getAnnotationString failed to get test String. This should never happen!";
}
}

/** Returns the name of a test as stored in an annotation.
* TODO: Is there a more elegant way to do this? */
/**
* Returns the name of a test as stored in an annotation.
* TODO: Is there a more elegant way to do this?
*/
private static double getAnnotationDouble(Annotation x, String annotationDoubleName) throws
IllegalAccessException, InvocationTargetException {
IllegalAccessException, InvocationTargetException {
Method[] methods = x.getClass().getDeclaredMethods();
/** If the annotation has a method name() that returns
* a String, invoke that method and return the result.
*/
/* If the annotation has a method name() that returns
a String, invoke that method and return the result.
*/

for (Method m : methods) {
if (m.getName().equals(annotationDoubleName) &&
m.getReturnType().getCanonicalName().equals("double")) {
m.getReturnType().getCanonicalName().equals("double")) {
return (double) m.invoke(x);
}
}
return -31337;
}
}

/** Gets test name of the given test. */
/**
* Gets test name of the given test.
*/
private static String getTestName(GradedTest x) throws
IllegalAccessException, InvocationTargetException {
IllegalAccessException, InvocationTargetException {
return getAnnotationString(x, "name");
}

/** Gets test number of the given test. */
/**
* Gets test number of the given test.
*/
private static String getTestNumber(GradedTest x) throws
IllegalAccessException, InvocationTargetException {
return getAnnotationString(x, "number");
IllegalAccessException, InvocationTargetException {
return getAnnotationString(x, "number");
}

/** Gets test weight of the given test. */
/**
* Gets test weight of the given test.
*/
private static double getTestMaxScore(GradedTest x) throws
IllegalAccessException, InvocationTargetException {
return getAnnotationDouble(x, "max_score");
IllegalAccessException, InvocationTargetException {
return getAnnotationDouble(x, "max_score");
}

private static String getTestVisibility(GradedTest x) throws
IllegalAccessException, InvocationTargetException {
IllegalAccessException, InvocationTargetException {
return getAnnotationString(x, "visibility");
}


/** Returns the name of a test as stored in an annotation.
* TODO: Is there a more elegant way to do this? */
/**
* Returns the name of a test as stored in an annotation.
* TODO: Is there a more elegant way to do this?
*/


/* Code to run at the beginning of a test run. */
public void testRunStarted(Description description) throws Exception {
allTestResults = new ArrayList<TestResult>();
public void testRunStarted(Description description) {
System.setOut(new NullPrintStream());
System.setErr(new NullPrintStream());
allTestResults = new ArrayList<>();
startTime = System.currentTimeMillis();
}

/* Code to run at the end of test run. */
public void testRunFinished(Result result) throws Exception {
public void testRunFinished(Result result) {
/* Dump allTestResults to StdOut in JSON format. */
long elapsed = System.currentTimeMillis() - startTime;
System.setOut(STDOUT);
System.setErr(STDERR);

ArrayList<String> objects = new ArrayList<String>();
ArrayList<String> objects = new ArrayList<>();
for (TestResult tr : allTestResults) {
objects.add(tr.toJSON());
}
String testsJSON = String.join(",", objects);

System.out.println("{" + String.join(",", new String[] {
String.format("\"execution_time\": %d", elapsed),
String.format("\"tests\": [%s]", testsJSON)
System.out.println("{" + String.join(",", new String[]{
String.format("\"execution_time\": %d", elapsed),
String.format("\"tests\": [%s]", testsJSON)
}) + "}");
}

Expand All @@ -148,44 +159,52 @@ public void testStarted(Description description) throws Exception {

/* By default every test passes. */
currentTestResult.setScore(testMaxScore);

capturedData = new ByteArrayOutputStream();
System.setOut(new PrintStream(capturedData));
}

/** When a test completes, add the test output at the bottom. Then stop capturing
* StdOut. Open question: Is putting the captured output at the end clear? Or is that
* possibly confusing? We'll see... */
public void testFinished(Description description) throws Exception {
String capturedDataString = capturedData.toString();
if (capturedDataString.length() > 0) {
// currentTestResult.addOutput("Captured Test Output: \n");
if (capturedDataString.length() > MAX_OUTPUT_LENGTH) {
capturedDataString = capturedDataString.substring(0, MAX_OUTPUT_LENGTH) +
"... truncated due to excessive output!";
}
currentTestResult.addOutput(capturedDataString);
}
System.setOut(STDOUT);

/**
* When a test completes, add the test output at the bottom. Then stop capturing
* StdOut. Open question: Is putting the captured output at the end clear? Or is that
* possibly confusing? We'll see...
*/
public void testFinished(Description description) {
/* For Debugging. */
if (false) {
System.out.println(currentTestResult);
STDOUT.println(currentTestResult);
}

allTestResults.add(currentTestResult);
}

/** Sets score to 0 and appends reason for failure and dumps a stack trace.
* Other possible things we might want to consider including: http://junit.sourceforge.net/javadoc/org/junit/runner/notification/Failure.html.
*/
public void testFailure(Failure failure) throws Exception {
/**
* Sets score to 0 and appends reason for failure and dumps a stack trace.
* Other possible things we might want to consider including: http://junit.sourceforge.net/javadoc/org/junit/runner/notification/Failure.html.
*/
public void testFailure(Failure failure) {
currentTestResult.setScore(0);
currentTestResult.addOutput("Test Failed!\n");
System.out.println(JUnitUtilities.failureToString(failure));
currentTestResult.addOutput(JUnitUtilities.failureToString(failure));
//currentTestResult.addOutput(failure.getTrace());
}

private static class NullPrintStream extends PrintStream {
public NullPrintStream() {
super(new NullByteArrayOutputStream());
}

private static class NullByteArrayOutputStream extends ByteArrayOutputStream {
@Override
public void write(int b) {
}

@Override
public void write(byte[] b, int off, int len) {
}

@Override
public void writeTo(OutputStream out) {
}
}
}
}


Expand Down

0 comments on commit b33f46b

Please sign in to comment.