package autograder;
import java.io.PrintStream;
import java.util.*;
import java.util.regex.Pattern;
import org.junit.runner.*;
import org.junit.runner.notification.*;
import autograder.AGCategories.AGTestDetails;
public class SuccessListener extends RunListener {
private static final int MAX_CAUSE_DEPTH = 5;
private final PrintStream psLogPrivate, psLogPublic;
private final Map<Description, Failure> tests;
private Description activeTest;
private Failure activeFailure;
public float pointsPossible = 0;
public float pointsLost = 0;
public float pointsEarned = 0;
public int testsPossible = 0;
public int testsEarned = 0;
public int testsLost = 0; // Silly, but maybe handy for redundancy?
public int extraPossible = 0;
public int extraEarned = 0;
public SuccessListener(Request request, PrintStream psPriv, PrintStream psPub) {
psLogPrivate = psPriv;
psLogPublic = psPub;
// psLogPrivate.format("LISTENER: %d %s%n", request.getRunner()
// .testCount(), request.getRunner().getDescription());
// psLogPrivate.format("LISTENER: %s%n", request.getRunner().getDescription());
tests = Collections
.synchronizedMap(new LinkedHashMap<Description, Failure>(
request.getRunner().testCount()));
}
public static final Pattern[] classesAllowed = {
Pattern.compile("kvstore.*"),
Pattern.compile("java.*"), Pattern.compile("javax.*"),
Pattern.compile("com.sun.org.*"),
}; // Must match one of the Allowed but none of the Banned
public static final Pattern[] classesBanned = {
Pattern.compile("autograder.*"),
Pattern.compile("java.security.*"),
Pattern.compile("junit.*"), Pattern.compile("org.junit.*"),
Pattern.compile("sun.reflect.*"), Pattern.compile("java.lang.reflect.*"),
};
public static boolean isVisibleClass(String fullName) {
boolean maybe = false;
for (Pattern pat: classesAllowed) {
if (pat.matcher(fullName).matches()) {
maybe = true;
break;
}
}
if (!maybe) return false;
for (Pattern pat: classesBanned) {
if (pat.matcher(fullName).matches()) return false;
}
return true;
}
public static String standardKVMessageFields(kvstore.KVMessage kve) {
return String.format("[KVMessage: T=%s M=%s K=%s V=%s]",
kve.getMsgType(), kve.getMessage(),
kve.getKey(), kve.getValue());
}
public static void streamFlush(PrintStream ps) {
ps.flush();
try {
Thread.sleep(1); // Workaround for eclipse output blending err/out
} catch (InterruptedException e1) {}
}
public static void logError(PrintStream ps, boolean fullTrace, Throwable ex) {
logError(ps, fullTrace, ex, 0);
}
protected static void logError(PrintStream ps, boolean fullTrace,
Throwable ex, int causeDepth) {
if ((ps != null) && (ex != null)) {
if (ex instanceof AGException) {
// Custom failure shows friendly name (perhaps more stuff later)
ps.format("%s%n", ex);
} else if (ex instanceof java.lang.AssertionError) {
// A regular style JUnit error, show friendly message
ps.format("ASSERT-FAILED: %s%n", ex.getMessage());
} else {
// Log exception basics, plus message in case toString() doesn't have it
ps.format("EXCEPTION: %s [%s]%n", ex, ex.getMessage());
}
try { // Dump the visible pieces of the stack trace
for (StackTraceElement ste: ex.getStackTrace()) {
if (fullTrace) {
ps.format("\t%s%n", ste);
} else if (isVisibleClass(ste.getClassName())) {
ps.format(" %s.%s(%s:%d)%n",
ste.getClassName(), ste.getMethodName(), ste.getFileName(),
ste.getLineNumber());
}
ps.flush();
}
} catch (Exception exIgnore) {}
try { // Trace back the cause chain
Throwable ecause = ex.getCause();
if (ecause != null) {
if (causeDepth < MAX_CAUSE_DEPTH) {
ps.format(" CAUSE-%d: ", causeDepth + 1);
logError(ps, fullTrace, ecause, causeDepth + 1);
} else {
ps.println(" CAUSE CHAIN ABORTED.");
}
}
} catch (Exception exIgnore) {}
}
streamFlush(ps);
}
public static void logResult(PrintStream ps, boolean fullTrace, Description d, Failure f) {
AGTestDetails details = d.getAnnotation(AGTestDetails.class);
if (details == null) {
if (fullTrace) {
ps.format("%nUNRECOGNIZED-%s: %s%n", (f == null)
? "PASS"
: "FAIL", d.getDisplayName());
}
return;
}
if (f == null) {
return;
}
ps.flush();
ps.format("\n===> [FAILED] %s\n", d.getDisplayName());
ps.flush();
ps.format("TEST-DESCRIPTION: %s%n", details.desc());
ps.flush();
if (!(details.points() > 0)) {
ps.format("*** This is an INFORMATIONAL test only; not included in points *** %n");
}
if (f != null) { // Was it a failed test???
logError(ps, fullTrace, f.getException());
}
}
public void logSummary(PrintStream ps, boolean fullTrace) {
psLogPublic.flush();
psLogPrivate.flush();
String stats = String.format("Tests passed: %s", getStats(false));
stats += String.format(" | Score: %s", getStats(true));
for (Map.Entry<Description, Failure> e: tests.entrySet()) {
Description d = e.getKey();
Failure f = e.getValue();
logResult(ps, fullTrace, d, f);
}
ps.println("\n=============================================================================\n\n" +
"SUMMARY: " + stats + "\n");
}
@Override
public void testRunStarted(Description d) {
psLogPublic.flush();
psLogPrivate.flush();
activeTest = null;
}
@Override
public void testRunFinished(Result result) {
psLogPublic.flush();
psLogPrivate.flush();
try { Thread.sleep(100); } catch (InterruptedException ie) { }
logSummary(psLogPublic, false);
}
@Override
public void testIgnored(Description d) {
psLogPublic.flush();
psLogPrivate.flush();
psLogPrivate.format("TEST-SKIP: %s%n", d);
}
@Override
public void testStarted(Description d) {
psLogPublic.flush();
psLogPrivate.flush();
psLogPublic.format("Running test %s\n", d.getDisplayName());
activeTest = d;
activeFailure = null;
}
@Override
public void testFailure(Failure f) {
psLogPublic.flush();
psLogPrivate.flush();
activeFailure = f;
}
@Override
public void testFinished(Description d) {
psLogPublic.flush();
psLogPrivate.flush();
AGTestDetails details = d.getAnnotation(AGTestDetails.class);
Float points = (details == null) ? null : details.points();
if ((points != null) && !points.isNaN()) {
if (points == 0.0f) { // TODO: Compare after rounding
extraPossible++;
if (activeFailure == null) {
extraEarned++;
}
} else {
pointsPossible += points;
testsPossible++;
if (activeFailure == null) {
pointsEarned += points;
testsEarned++;
} else {
pointsLost += points;
testsLost++;
}
}
}
if (activeFailure != null) {
if (activeTest != activeFailure.getDescription()) {
psLogPrivate.println("WARNING: CRAZY FAILURE MISMATCH");
}
}
tests.put(activeTest, activeFailure);
activeTest = null;
activeFailure = null;
}
public String getStats(boolean getPoints) {
if (getPoints) return String.format("%.2f/%.2f",
pointsEarned, pointsPossible);
return String.format("%d/%d", testsEarned, testsPossible);
}
}