Skip to content

Commit

Permalink
Experimental code to filter stack traces
Browse files Browse the repository at this point in the history
  • Loading branch information
kcooney committed Mar 29, 2015
1 parent 9c337dc commit 4930a1e
Show file tree
Hide file tree
Showing 7 changed files with 403 additions and 63 deletions.
48 changes: 6 additions & 42 deletions src/main/java/junit/runner/BaseTestRunner.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package junit.runner;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand All @@ -19,6 +17,7 @@
import junit.framework.Test;
import junit.framework.TestListener;
import junit.framework.TestSuite;
import org.junit.internal.StackTraces;

/**
* Base class for all test runners.
Expand Down Expand Up @@ -264,6 +263,10 @@ public static int getPreference(String key, int dflt) {
* Returns a filtered stack trace
*/
public static String getFilteredTrace(Throwable e) {
if (!showStackRaw()) {
return StackTraces.getTrimmedStackTrace(e);
}

StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
e.printStackTrace(writer);
Expand All @@ -275,53 +278,14 @@ public static String getFilteredTrace(Throwable e) {
* Filters stack frames from internal JUnit classes
*/
public static String getFilteredTrace(String stack) {
if (showStackRaw()) {
return stack;
}

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
StringReader sr = new StringReader(stack);
BufferedReader br = new BufferedReader(sr);

String line;
try {
while ((line = br.readLine()) != null) {
if (!filterLine(line)) {
pw.println(line);
}
}
} catch (Exception IOException) {
return stack; // return the stack unfiltered
}
return sw.toString();
return showStackRaw() ? stack : StackTraces.trimStackTrace(stack);
}

protected static boolean showStackRaw() {
return !getPreference("filterstack").equals("true") || fgFilterStack == false;
}

static boolean filterLine(String line) {
String[] patterns = new String[]{
"junit.framework.TestCase",
"junit.framework.TestResult",
"junit.framework.TestSuite",
"junit.framework.Assert.", // don't filter AssertionFailure
"junit.swingui.TestRunner",
"junit.awtui.TestRunner",
"junit.textui.TestRunner",
"java.lang.reflect.Method.invoke("
};
for (int i = 0; i < patterns.length; i++) {
if (line.indexOf(patterns[i]) > 0) {
return true;
}
}
return false;
}

static {
fgMaxMessageLength = getPreference("maxmessage", fgMaxMessageLength);
}

}
203 changes: 203 additions & 0 deletions src/main/java/org/junit/internal/StackTraces.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package org.junit.internal;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Utility class for working with stack traces.
*/
public class StackTraces {
private StackTraces() {
}

/**
* Gets a trimmed version of the stack trace of the given exception. Stack trace
* elements that are below the test method are filtered out.
*
* @return a trimmed stack trace, or the original trace if trimming wasn't possible
*/
public static String getTrimmedStackTrace(Throwable exception) {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
exception.printStackTrace(writer);
return trimStackTrace(exception.toString(), stringWriter.toString());
}

/**
* Trims the given stack trace. Stack trace elements that are below the test method are
* filtered. out.
*
* @param fullTrace the full stack trace
* @return a trimmed stack trace, or the original trace if trimming wasn't possible
*/
public static String trimStackTrace(String fullTrace) {
return trimStackTrace("", fullTrace);
}

static String trimStackTrace(String extracedExceptionMessage, String fullTrace) {
StringBuilder trimmedTrace = new StringBuilder(extracedExceptionMessage);
BufferedReader reader = new BufferedReader(
new StringReader(fullTrace.substring(extracedExceptionMessage.length())));

try {
// Collect the stack trace lines for "exception" (but not the cause).
List<String> stackTraceLines = new ArrayList<String>();
String line;
boolean hasCause = false;
while ((line = reader.readLine()) != null) {
if (line.startsWith("Caused by: ")) {
hasCause= true;
break;
}
stackTraceLines.add(line);
}
if (stackTraceLines.isEmpty()) {
// No stack trace?
return fullTrace;
}
stackTraceLines = trimStackTraceLines(stackTraceLines, hasCause);
if (stackTraceLines.isEmpty()) {
// Could not trim stack trace lines.
return fullTrace;
}
appendStackTraceLines(stackTraceLines, trimmedTrace);
if (line != null) {
// Print remaining stack trace lines.
do {
trimmedTrace.append(line).append("\n");
line = reader.readLine();
} while (line != null);
}
return trimmedTrace.toString();
} catch (IOException e) {
}
return fullTrace;
}

private static void appendStackTraceLines(
List<String> stackTraceLines, StringBuilder destBuilder) {
for (String stackTraceLine : stackTraceLines) {
destBuilder.append(stackTraceLine).append("\n");
}
}

private static List<String> trimStackTraceLines(
List<String> stackTraceLines, boolean hasCause) {
State state = State.PROCESSING_OTHER_CODE;
int linesToInclude = stackTraceLines.size();
for (String stackTraceLine : asReversedList(stackTraceLines)) {
state = state.processLine(stackTraceLine);
if (state == State.DONE) {
List<String> trimmedLines = stackTraceLines.subList(0, linesToInclude);
if (!hasCause) {
return trimmedLines;
}
List<String> copy = new ArrayList<String>(trimmedLines);
copy.add("\t..." + (stackTraceLines.size() - copy.size()) + "more");
return copy;
}
linesToInclude--;
}
return Collections.emptyList();
}

private static <T> List<T> asReversedList(final List<T> list) {
return new AbstractList<T>() {

@Override
public T get(int index) {
return list.get(list.size() - index - 1);
}

@Override
public int size() {
return list.size();
}
};
}

private enum State {
PROCESSING_OTHER_CODE {
@Override public State processLine(String line) {
if (isTestFrameworkStackTraceLine(line)) {
return PROCESSING_TEST_FRAMEWORK_CODE;
}
return this;
}
},
PROCESSING_TEST_FRAMEWORK_CODE {
@Override public State processLine(String line) {
if (isReflectionStackTraceLine(line)) {
return PROCESSING_REFLECTION_CODE;
} else if (isTestFrameworkStackTraceLine(line)) {
return this;
}
return PROCESSING_OTHER_CODE;
}
},
PROCESSING_REFLECTION_CODE {
@Override public State processLine(String line) {
if (isReflectionStackTraceLine(line)) {
return this;
} else if (isTestFrameworkStackTraceLine(line)) {
// This is here to handle TestCase.runBare() calling TestCase.runTest().
return PROCESSING_TEST_FRAMEWORK_CODE;
}
return DONE;
}
},
DONE {
@Override public State processLine(String line) {
return this;
}
};

/** Processes a stack trace line, possibly moving to a new state. */
public abstract State processLine(String line);
}

private static final String[] TEST_FRAMEWORK_METHOD_NAME_PREFIXES = {
"org.junit.runner.",
"org.junit.runners.",
"org.junit.experimental.runners.",
"org.junit.internal.",
"junit.",
"org.apache.maven.surefire."
};

private static boolean isTestFrameworkStackTraceLine(String line) {
return isMatchingStackTraceLine(line, TEST_FRAMEWORK_METHOD_NAME_PREFIXES);
}

private static final String[] REFLECTION_METHOD_NAME_PREFIXES = {
"sun.reflect.",
"java.lang.reflect.",
"org.junit.rules.RunRules.evaluate(", // get better stack traces for failures in method rules
"junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown()
};

private static boolean isReflectionStackTraceLine(String line) {
return isMatchingStackTraceLine(line, REFLECTION_METHOD_NAME_PREFIXES);
}

private static boolean isMatchingStackTraceLine(String line, String[] packagePrefixes) {
if (!line.startsWith("\tat ")) {
return false;
}
line = line.substring(4);
for (String packagePrefix : packagePrefixes) {
if (line.startsWith(packagePrefix)) {
return true;
}
}

return false;
}
}
2 changes: 1 addition & 1 deletion src/main/java/org/junit/internal/TextListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected void printFailures(Result result) {

protected void printFailure(Failure each, String prefix) {
getWriter().println(prefix + ") " + each.getTestHeader());
getWriter().print(each.getTrace());
getWriter().print(each.getTrimmedTrace());
}

protected void printFooter(Result result) {
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/org/junit/runner/notification/Failure.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.io.Serializable;
import java.io.StringWriter;

import org.junit.internal.StackTraces;
import org.junit.runner.Description;

/**
Expand Down Expand Up @@ -65,9 +66,7 @@ public String toString() {
}

/**
* Convenience method
*
* @return the printed form of the exception
* Gets the printed form of the exception and its stack trace.
*/
public String getTrace() {
StringWriter stringWriter = new StringWriter();
Expand All @@ -76,6 +75,15 @@ public String getTrace() {
return stringWriter.toString();
}

/**
* Gets a the printed form of the exception, with a trimmed version of the stack trace.
* This method will attempt to filter out frames of the stack trace that are below
* the test method call.
*/
public String getTrimmedTrace() {
return StackTraces.getTrimmedStackTrace(getException());
}

/**
* Convenience method
*
Expand Down
37 changes: 20 additions & 17 deletions src/test/java/junit/tests/runner/StackFilterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,31 @@ protected void setUp() {
StringWriter swin = new StringWriter();
PrintWriter pwin = new PrintWriter(swin);
pwin.println("junit.framework.AssertionFailedError");
pwin.println(" at junit.framework.Assert.fail(Assert.java:144)");
pwin.println(" at junit.framework.Assert.assert(Assert.java:19)");
pwin.println(" at junit.framework.Assert.assert(Assert.java:26)");
pwin.println(" at MyTest.f(MyTest.java:13)");
pwin.println(" at MyTest.testStackTrace(MyTest.java:8)");
pwin.println(" at java.lang.reflect.Method.invoke(Native Method)");
pwin.println(" at junit.framework.TestCase.runTest(TestCase.java:156)");
pwin.println(" at junit.framework.TestCase.runBare(TestCase.java:130)");
pwin.println(" at junit.framework.TestResult$1.protect(TestResult.java:100)");
pwin.println(" at junit.framework.TestResult.runProtected(TestResult.java:118)");
pwin.println(" at junit.framework.TestResult.run(TestResult.java:103)");
pwin.println(" at junit.framework.TestCase.run(TestCase.java:121)");
pwin.println(" at junit.framework.TestSuite.runTest(TestSuite.java:157)");
pwin.println(" at junit.framework.TestSuite.run(TestSuite.java, Compiled Code)");
pwin.println(" at junit.swingui.TestRunner$17.run(TestRunner.java:669)");
pwin.println("\tat junit.framework.Assert.fail(Assert.java:144)");
pwin.println("\tat junit.framework.Assert.assert(Assert.java:19)");
pwin.println("\tat junit.framework.Assert.assert(Assert.java:26)");
pwin.println("\tat MyTest.f(MyTest.java:13)");
pwin.println("\tat MyTest.testStackTrace(MyTest.java:8)");
pwin.println("\tat java.lang.reflect.Method.invoke(Native Method)");
pwin.println("\tat junit.framework.TestCase.runTest(TestCase.java:156)");
pwin.println("\tat junit.framework.TestCase.runBare(TestCase.java:130)");
pwin.println("\tat junit.framework.TestResult$1.protect(TestResult.java:100)");
pwin.println("\tat junit.framework.TestResult.runProtected(TestResult.java:118)");
pwin.println("\tat junit.framework.TestResult.run(TestResult.java:103)");
pwin.println("\tat junit.framework.TestCase.run(TestCase.java:121)");
pwin.println("\tat junit.framework.TestSuite.runTest(TestSuite.java:157)");
pwin.println("\tat junit.framework.TestSuite.run(TestSuite.java, Compiled Code)");
pwin.println("\tat junit.swingui.TestRunner$17.run(TestRunner.java:669)");
fUnfiltered = swin.toString();

StringWriter swout = new StringWriter();
PrintWriter pwout = new PrintWriter(swout);
pwout.println("junit.framework.AssertionFailedError");
pwout.println(" at MyTest.f(MyTest.java:13)");
pwout.println(" at MyTest.testStackTrace(MyTest.java:8)");
pwout.println("\tat junit.framework.Assert.fail(Assert.java:144)");
pwout.println("\tat junit.framework.Assert.assert(Assert.java:19)");
pwout.println("\tat junit.framework.Assert.assert(Assert.java:26)");
pwout.println("\tat MyTest.f(MyTest.java:13)");
pwout.println("\tat MyTest.testStackTrace(MyTest.java:8)");
fFiltered = swout.toString();
}

Expand Down
Loading

0 comments on commit 4930a1e

Please sign in to comment.