Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trim stack trace. #1028

Merged
merged 9 commits into from
Feb 6, 2017
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
189 changes: 189 additions & 0 deletions src/main/java/org/junit/internal/Throwables.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
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.Arrays;
import java.util.Collections;
import java.util.List;

/**
* Miscellaneous functions dealing with {@code Throwable}.
Expand Down Expand Up @@ -54,4 +62,185 @@ public static String getStacktrace(Throwable exception) {
exception.printStackTrace(writer);
return stringWriter.toString();
}

/**
* 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) {
List<String> trimmedStackTraceLines = getTrimmedStackTraceLines(exception);
if (trimmedStackTraceLines.isEmpty()) {
return getFullStackTrace(exception);
}

StringBuilder result = new StringBuilder(exception.toString());
appendStackTraceLines(trimmedStackTraceLines, result);
appendStackTraceLines(getCauseStackTraceLines(exception), result);
return result.toString();
}

private static List<String> getTrimmedStackTraceLines(Throwable exception) {
List<StackTraceElement> stackTraceElements = Arrays.asList(exception.getStackTrace());
int linesToInclude = stackTraceElements.size();

State state = State.PROCESSING_OTHER_CODE;
for (StackTraceElement stackTraceElement : asReversedList(stackTraceElements)) {
state = state.processStackTraceElement(stackTraceElement);
if (state == State.DONE) {
List<String> trimmedLines = new ArrayList<String>(linesToInclude + 2);
trimmedLines.add("");
for (StackTraceElement each : stackTraceElements.subList(0, linesToInclude)) {
trimmedLines.add("\tat " + each);
}
if (exception.getCause() != null) {
trimmedLines.add("\t... " + (stackTraceElements.size() - trimmedLines.size()) + " trimmed");
}
return trimmedLines;
}
linesToInclude--;
}
return Collections.emptyList();
}

private static List<String> getCauseStackTraceLines(Throwable exception) {
if (exception.getCause() != null) {
String fullTrace = getFullStackTrace(exception);
BufferedReader reader = new BufferedReader(
new StringReader(fullTrace.substring(exception.toString().length())));
List<String> causedByLines = new ArrayList<String>();

try {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("Caused by: ")) {
causedByLines.add(line);
while ((line = reader.readLine()) != null) {
causedByLines.add(line);
}
return causedByLines;
}
}
} catch (IOException e) {
// We should never get here, because we are reading from a StringReader
}
}

return Collections.emptyList();
}

private static String getFullStackTrace(Throwable exception) {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
exception.printStackTrace(writer);
return stringWriter.toString();
}

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

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 methodName) {
if (isTestFrameworkMethod(methodName)) {
return PROCESSING_TEST_FRAMEWORK_CODE;
}
return this;
}
},
PROCESSING_TEST_FRAMEWORK_CODE {
@Override public State processLine(String methodName) {
if (isReflectionMethod(methodName)) {
return PROCESSING_REFLECTION_CODE;
} else if (isTestFrameworkMethod(methodName)) {
return this;
}
return PROCESSING_OTHER_CODE;
}
},
PROCESSING_REFLECTION_CODE {
@Override public State processLine(String methodName) {
if (isReflectionMethod(methodName)) {
return this;
} else if (isTestFrameworkMethod(methodName)) {
// This is here to handle TestCase.runBare() calling TestCase.runTest().
return PROCESSING_TEST_FRAMEWORK_CODE;
}
return DONE;
}
},
DONE {
@Override public State processLine(String methodName) {
return this;
}
};

/** Processes a stack trace element method name, possibly moving to a new state. */
protected abstract State processLine(String methodName);

/** Processes a stack trace element, possibly moving to a new state. */
public final State processStackTraceElement(StackTraceElement element) {
return processLine(element.getClassName() + "." + element.getMethodName() + "()");
}
}

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

private static final String[] TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES = {
"org.junit.internal.StackTracesTest",
};

private static boolean isTestFrameworkMethod(String methodName) {
return isMatchingMethod(methodName, TEST_FRAMEWORK_METHOD_NAME_PREFIXES) &&
!isMatchingMethod(methodName, TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES);
}

private static final String[] REFLECTION_METHOD_NAME_PREFIXES = {
"sun.reflect.",
"java.lang.reflect.",
"org.junit.rules.RunRules.<init>(",
"org.junit.rules.RunRules.applyAll(", // calls TestRules
"org.junit.runners.BlockJUnit4ClassRunner.withMethodRules(", // calls MethodRules
"junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown()
};

private static boolean isReflectionMethod(String methodName) {
return isMatchingMethod(methodName, REFLECTION_METHOD_NAME_PREFIXES);
}

private static boolean isMatchingMethod(String methodName, String[] methodNamePrefixes) {
for (String methodNamePrefix : methodNamePrefixes) {
if (methodName.startsWith(methodNamePrefix)) {
return true;
}
}

return false;
}
}
13 changes: 10 additions & 3 deletions src/main/java/org/junit/runner/notification/Failure.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,21 @@ 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() {
return Throwables.getStacktrace(getException());
}

/**
* 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 Throwables.getTrimmedStackTrace(getException());
}

/**
* Convenience method
*
Expand Down
34 changes: 17 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,28 @@ 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 MyTest.f(MyTest.java:13)");
pwout.println("\tat MyTest.testStackTrace(MyTest.java:8)");
fFiltered = swout.toString();
}

Expand Down
1 change: 1 addition & 0 deletions src/test/java/org/junit/internal/AllInternalTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
FailOnTimeoutTest.class,
MethodSorterTest.class,
StacktracePrintingMatcherTest.class,
StackTracesTest.class,
ThrowableCauseMatcherTest.class,
ArrayComparisonFailureTest.class
})
Expand Down
Loading