Skip to content

Commit ead2fe7

Browse files
authored
Trim stack trace. (#1028)
Update TextListener to trim stack traces. Fixes #669
1 parent a03e02a commit ead2fe7

File tree

7 files changed

+678
-21
lines changed

7 files changed

+678
-21
lines changed

src/main/java/org/junit/internal/TextListener.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ protected void printFailures(Result result) {
7474

7575
protected void printFailure(Failure each, String prefix) {
7676
getWriter().println(prefix + ") " + each.getTestHeader());
77-
getWriter().print(each.getTrace());
77+
getWriter().print(each.getTrimmedTrace());
7878
}
7979

8080
protected void printFooter(Result result) {

src/main/java/org/junit/internal/Throwables.java

+189
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package org.junit.internal;
22

3+
import java.io.BufferedReader;
4+
import java.io.IOException;
35
import java.io.PrintWriter;
6+
import java.io.StringReader;
47
import java.io.StringWriter;
8+
import java.util.AbstractList;
9+
import java.util.ArrayList;
10+
import java.util.Arrays;
11+
import java.util.Collections;
12+
import java.util.List;
513

614
/**
715
* Miscellaneous functions dealing with {@code Throwable}.
@@ -54,4 +62,185 @@ public static String getStacktrace(Throwable exception) {
5462
exception.printStackTrace(writer);
5563
return stringWriter.toString();
5664
}
65+
66+
/**
67+
* Gets a trimmed version of the stack trace of the given exception. Stack trace
68+
* elements that are below the test method are filtered out.
69+
*
70+
* @return a trimmed stack trace, or the original trace if trimming wasn't possible
71+
*/
72+
public static String getTrimmedStackTrace(Throwable exception) {
73+
List<String> trimmedStackTraceLines = getTrimmedStackTraceLines(exception);
74+
if (trimmedStackTraceLines.isEmpty()) {
75+
return getFullStackTrace(exception);
76+
}
77+
78+
StringBuilder result = new StringBuilder(exception.toString());
79+
appendStackTraceLines(trimmedStackTraceLines, result);
80+
appendStackTraceLines(getCauseStackTraceLines(exception), result);
81+
return result.toString();
82+
}
83+
84+
private static List<String> getTrimmedStackTraceLines(Throwable exception) {
85+
List<StackTraceElement> stackTraceElements = Arrays.asList(exception.getStackTrace());
86+
int linesToInclude = stackTraceElements.size();
87+
88+
State state = State.PROCESSING_OTHER_CODE;
89+
for (StackTraceElement stackTraceElement : asReversedList(stackTraceElements)) {
90+
state = state.processStackTraceElement(stackTraceElement);
91+
if (state == State.DONE) {
92+
List<String> trimmedLines = new ArrayList<String>(linesToInclude + 2);
93+
trimmedLines.add("");
94+
for (StackTraceElement each : stackTraceElements.subList(0, linesToInclude)) {
95+
trimmedLines.add("\tat " + each);
96+
}
97+
if (exception.getCause() != null) {
98+
trimmedLines.add("\t... " + (stackTraceElements.size() - trimmedLines.size()) + " trimmed");
99+
}
100+
return trimmedLines;
101+
}
102+
linesToInclude--;
103+
}
104+
return Collections.emptyList();
105+
}
106+
107+
private static List<String> getCauseStackTraceLines(Throwable exception) {
108+
if (exception.getCause() != null) {
109+
String fullTrace = getFullStackTrace(exception);
110+
BufferedReader reader = new BufferedReader(
111+
new StringReader(fullTrace.substring(exception.toString().length())));
112+
List<String> causedByLines = new ArrayList<String>();
113+
114+
try {
115+
String line;
116+
while ((line = reader.readLine()) != null) {
117+
if (line.startsWith("Caused by: ")) {
118+
causedByLines.add(line);
119+
while ((line = reader.readLine()) != null) {
120+
causedByLines.add(line);
121+
}
122+
return causedByLines;
123+
}
124+
}
125+
} catch (IOException e) {
126+
// We should never get here, because we are reading from a StringReader
127+
}
128+
}
129+
130+
return Collections.emptyList();
131+
}
132+
133+
private static String getFullStackTrace(Throwable exception) {
134+
StringWriter stringWriter = new StringWriter();
135+
PrintWriter writer = new PrintWriter(stringWriter);
136+
exception.printStackTrace(writer);
137+
return stringWriter.toString();
138+
}
139+
140+
private static void appendStackTraceLines(
141+
List<String> stackTraceLines, StringBuilder destBuilder) {
142+
for (String stackTraceLine : stackTraceLines) {
143+
destBuilder.append(String.format("%s%n", stackTraceLine));
144+
}
145+
}
146+
147+
private static <T> List<T> asReversedList(final List<T> list) {
148+
return new AbstractList<T>() {
149+
150+
@Override
151+
public T get(int index) {
152+
return list.get(list.size() - index - 1);
153+
}
154+
155+
@Override
156+
public int size() {
157+
return list.size();
158+
}
159+
};
160+
}
161+
162+
private enum State {
163+
PROCESSING_OTHER_CODE {
164+
@Override public State processLine(String methodName) {
165+
if (isTestFrameworkMethod(methodName)) {
166+
return PROCESSING_TEST_FRAMEWORK_CODE;
167+
}
168+
return this;
169+
}
170+
},
171+
PROCESSING_TEST_FRAMEWORK_CODE {
172+
@Override public State processLine(String methodName) {
173+
if (isReflectionMethod(methodName)) {
174+
return PROCESSING_REFLECTION_CODE;
175+
} else if (isTestFrameworkMethod(methodName)) {
176+
return this;
177+
}
178+
return PROCESSING_OTHER_CODE;
179+
}
180+
},
181+
PROCESSING_REFLECTION_CODE {
182+
@Override public State processLine(String methodName) {
183+
if (isReflectionMethod(methodName)) {
184+
return this;
185+
} else if (isTestFrameworkMethod(methodName)) {
186+
// This is here to handle TestCase.runBare() calling TestCase.runTest().
187+
return PROCESSING_TEST_FRAMEWORK_CODE;
188+
}
189+
return DONE;
190+
}
191+
},
192+
DONE {
193+
@Override public State processLine(String methodName) {
194+
return this;
195+
}
196+
};
197+
198+
/** Processes a stack trace element method name, possibly moving to a new state. */
199+
protected abstract State processLine(String methodName);
200+
201+
/** Processes a stack trace element, possibly moving to a new state. */
202+
public final State processStackTraceElement(StackTraceElement element) {
203+
return processLine(element.getClassName() + "." + element.getMethodName() + "()");
204+
}
205+
}
206+
207+
private static final String[] TEST_FRAMEWORK_METHOD_NAME_PREFIXES = {
208+
"org.junit.runner.",
209+
"org.junit.runners.",
210+
"org.junit.experimental.runners.",
211+
"org.junit.internal.",
212+
"junit.",
213+
};
214+
215+
private static final String[] TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES = {
216+
"org.junit.internal.StackTracesTest",
217+
};
218+
219+
private static boolean isTestFrameworkMethod(String methodName) {
220+
return isMatchingMethod(methodName, TEST_FRAMEWORK_METHOD_NAME_PREFIXES) &&
221+
!isMatchingMethod(methodName, TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES);
222+
}
223+
224+
private static final String[] REFLECTION_METHOD_NAME_PREFIXES = {
225+
"sun.reflect.",
226+
"java.lang.reflect.",
227+
"org.junit.rules.RunRules.<init>(",
228+
"org.junit.rules.RunRules.applyAll(", // calls TestRules
229+
"org.junit.runners.BlockJUnit4ClassRunner.withMethodRules(", // calls MethodRules
230+
"junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown()
231+
};
232+
233+
private static boolean isReflectionMethod(String methodName) {
234+
return isMatchingMethod(methodName, REFLECTION_METHOD_NAME_PREFIXES);
235+
}
236+
237+
private static boolean isMatchingMethod(String methodName, String[] methodNamePrefixes) {
238+
for (String methodNamePrefix : methodNamePrefixes) {
239+
if (methodName.startsWith(methodNamePrefix)) {
240+
return true;
241+
}
242+
}
243+
244+
return false;
245+
}
57246
}

src/main/java/org/junit/runner/notification/Failure.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,21 @@ public String toString() {
6464
}
6565

6666
/**
67-
* Convenience method
68-
*
69-
* @return the printed form of the exception
67+
* Gets the printed form of the exception and its stack trace.
7068
*/
7169
public String getTrace() {
7270
return Throwables.getStacktrace(getException());
7371
}
7472

73+
/**
74+
* Gets a the printed form of the exception, with a trimmed version of the stack trace.
75+
* This method will attempt to filter out frames of the stack trace that are below
76+
* the test method call.
77+
*/
78+
public String getTrimmedTrace() {
79+
return Throwables.getTrimmedStackTrace(getException());
80+
}
81+
7582
/**
7683
* Convenience method
7784
*

src/test/java/junit/tests/runner/StackFilterTest.java

+17-17
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,28 @@ protected void setUp() {
1515
StringWriter swin = new StringWriter();
1616
PrintWriter pwin = new PrintWriter(swin);
1717
pwin.println("junit.framework.AssertionFailedError");
18-
pwin.println(" at junit.framework.Assert.fail(Assert.java:144)");
19-
pwin.println(" at junit.framework.Assert.assert(Assert.java:19)");
20-
pwin.println(" at junit.framework.Assert.assert(Assert.java:26)");
21-
pwin.println(" at MyTest.f(MyTest.java:13)");
22-
pwin.println(" at MyTest.testStackTrace(MyTest.java:8)");
23-
pwin.println(" at java.lang.reflect.Method.invoke(Native Method)");
24-
pwin.println(" at junit.framework.TestCase.runTest(TestCase.java:156)");
25-
pwin.println(" at junit.framework.TestCase.runBare(TestCase.java:130)");
26-
pwin.println(" at junit.framework.TestResult$1.protect(TestResult.java:100)");
27-
pwin.println(" at junit.framework.TestResult.runProtected(TestResult.java:118)");
28-
pwin.println(" at junit.framework.TestResult.run(TestResult.java:103)");
29-
pwin.println(" at junit.framework.TestCase.run(TestCase.java:121)");
30-
pwin.println(" at junit.framework.TestSuite.runTest(TestSuite.java:157)");
31-
pwin.println(" at junit.framework.TestSuite.run(TestSuite.java, Compiled Code)");
32-
pwin.println(" at junit.swingui.TestRunner$17.run(TestRunner.java:669)");
18+
pwin.println("\tat junit.framework.Assert.fail(Assert.java:144)");
19+
pwin.println("\tat junit.framework.Assert.assert(Assert.java:19)");
20+
pwin.println("\tat junit.framework.Assert.assert(Assert.java:26)");
21+
pwin.println("\tat MyTest.f(MyTest.java:13)");
22+
pwin.println("\tat MyTest.testStackTrace(MyTest.java:8)");
23+
pwin.println("\tat java.lang.reflect.Method.invoke(Native Method)");
24+
pwin.println("\tat junit.framework.TestCase.runTest(TestCase.java:156)");
25+
pwin.println("\tat junit.framework.TestCase.runBare(TestCase.java:130)");
26+
pwin.println("\tat junit.framework.TestResult$1.protect(TestResult.java:100)");
27+
pwin.println("\tat junit.framework.TestResult.runProtected(TestResult.java:118)");
28+
pwin.println("\tat junit.framework.TestResult.run(TestResult.java:103)");
29+
pwin.println("\tat junit.framework.TestCase.run(TestCase.java:121)");
30+
pwin.println("\tat junit.framework.TestSuite.runTest(TestSuite.java:157)");
31+
pwin.println("\tat junit.framework.TestSuite.run(TestSuite.java, Compiled Code)");
32+
pwin.println("\tat junit.swingui.TestRunner$17.run(TestRunner.java:669)");
3333
fUnfiltered = swin.toString();
3434

3535
StringWriter swout = new StringWriter();
3636
PrintWriter pwout = new PrintWriter(swout);
3737
pwout.println("junit.framework.AssertionFailedError");
38-
pwout.println(" at MyTest.f(MyTest.java:13)");
39-
pwout.println(" at MyTest.testStackTrace(MyTest.java:8)");
38+
pwout.println("\tat MyTest.f(MyTest.java:13)");
39+
pwout.println("\tat MyTest.testStackTrace(MyTest.java:8)");
4040
fFiltered = swout.toString();
4141
}
4242

src/test/java/org/junit/internal/AllInternalTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
FailOnTimeoutTest.class,
1717
MethodSorterTest.class,
1818
StacktracePrintingMatcherTest.class,
19+
StackTracesTest.class,
1920
ThrowableCauseMatcherTest.class,
2021
ArrayComparisonFailureTest.class
2122
})

0 commit comments

Comments
 (0)