Skip to content

Commit 7b174b5

Browse files
committed
Experimental code to filter stack traces
1 parent 9c337dc commit 7b174b5

File tree

7 files changed

+407
-63
lines changed

7 files changed

+407
-63
lines changed

src/main/java/junit/runner/BaseTestRunner.java

+6-42
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package junit.runner;
22

3-
import java.io.BufferedReader;
43
import java.io.File;
54
import java.io.FileInputStream;
65
import java.io.FileOutputStream;
76
import java.io.IOException;
87
import java.io.InputStream;
98
import java.io.PrintWriter;
10-
import java.io.StringReader;
119
import java.io.StringWriter;
1210
import java.lang.reflect.InvocationTargetException;
1311
import java.lang.reflect.Method;
@@ -19,6 +17,7 @@
1917
import junit.framework.Test;
2018
import junit.framework.TestListener;
2119
import junit.framework.TestSuite;
20+
import org.junit.internal.StackTraces;
2221

2322
/**
2423
* Base class for all test runners.
@@ -264,6 +263,10 @@ public static int getPreference(String key, int dflt) {
264263
* Returns a filtered stack trace
265264
*/
266265
public static String getFilteredTrace(Throwable e) {
266+
if (!showStackRaw()) {
267+
return StackTraces.getTrimmedStackTrace(e);
268+
}
269+
267270
StringWriter stringWriter = new StringWriter();
268271
PrintWriter writer = new PrintWriter(stringWriter);
269272
e.printStackTrace(writer);
@@ -275,53 +278,14 @@ public static String getFilteredTrace(Throwable e) {
275278
* Filters stack frames from internal JUnit classes
276279
*/
277280
public static String getFilteredTrace(String stack) {
278-
if (showStackRaw()) {
279-
return stack;
280-
}
281-
282-
StringWriter sw = new StringWriter();
283-
PrintWriter pw = new PrintWriter(sw);
284-
StringReader sr = new StringReader(stack);
285-
BufferedReader br = new BufferedReader(sr);
286-
287-
String line;
288-
try {
289-
while ((line = br.readLine()) != null) {
290-
if (!filterLine(line)) {
291-
pw.println(line);
292-
}
293-
}
294-
} catch (Exception IOException) {
295-
return stack; // return the stack unfiltered
296-
}
297-
return sw.toString();
281+
return showStackRaw() ? stack : StackTraces.trimStackTrace(stack);
298282
}
299283

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

304-
static boolean filterLine(String line) {
305-
String[] patterns = new String[]{
306-
"junit.framework.TestCase",
307-
"junit.framework.TestResult",
308-
"junit.framework.TestSuite",
309-
"junit.framework.Assert.", // don't filter AssertionFailure
310-
"junit.swingui.TestRunner",
311-
"junit.awtui.TestRunner",
312-
"junit.textui.TestRunner",
313-
"java.lang.reflect.Method.invoke("
314-
};
315-
for (int i = 0; i < patterns.length; i++) {
316-
if (line.indexOf(patterns[i]) > 0) {
317-
return true;
318-
}
319-
}
320-
return false;
321-
}
322-
323288
static {
324289
fgMaxMessageLength = getPreference("maxmessage", fgMaxMessageLength);
325290
}
326-
327291
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package org.junit.internal;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.io.PrintWriter;
6+
import java.io.StringReader;
7+
import java.io.StringWriter;
8+
import java.util.AbstractList;
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.List;
12+
13+
/**
14+
* Utility class for working with stack traces.
15+
*/
16+
public class StackTraces {
17+
private StackTraces() {
18+
}
19+
20+
/**
21+
* Gets a trimmed version of the stack trace of the given exception. Stack trace
22+
* elements that are below the test method are filtered out.
23+
*
24+
* @return a trimmed stack trace, or the original trace if trimming wasn't possible
25+
*/
26+
public static String getTrimmedStackTrace(Throwable exception) {
27+
StringWriter stringWriter = new StringWriter();
28+
PrintWriter writer = new PrintWriter(stringWriter);
29+
exception.printStackTrace(writer);
30+
return trimStackTrace(exception.toString(), stringWriter.toString());
31+
}
32+
33+
/**
34+
* Trims the given stack trace. Stack trace elements that are below the test method are
35+
* filtered. out.
36+
*
37+
* @param fullTrace the full stack trace
38+
* @return a trimmed stack trace, or the original trace if trimming wasn't possible
39+
*/
40+
public static String trimStackTrace(String fullTrace) {
41+
return trimStackTrace("", fullTrace);
42+
}
43+
44+
static String trimStackTrace(String extracedExceptionMessage, String fullTrace) {
45+
StringBuilder trimmedTrace = new StringBuilder(extracedExceptionMessage);
46+
BufferedReader reader = new BufferedReader(
47+
new StringReader(fullTrace.substring(extracedExceptionMessage.length())));
48+
49+
try {
50+
// Collect the stack trace lines for "exception" (but not the cause).
51+
List<String> stackTraceLines = new ArrayList<String>();
52+
String line;
53+
boolean hasCause = false;
54+
while ((line = reader.readLine()) != null) {
55+
if (line.startsWith("Caused by: ")) {
56+
hasCause= true;
57+
break;
58+
}
59+
stackTraceLines.add(line);
60+
}
61+
if (stackTraceLines.isEmpty()) {
62+
// No stack trace?
63+
return fullTrace;
64+
}
65+
stackTraceLines = trimStackTraceLines(stackTraceLines, hasCause);
66+
if (stackTraceLines.isEmpty()) {
67+
// Could not trim stack trace lines.
68+
return fullTrace;
69+
}
70+
appendStackTraceLines(stackTraceLines, trimmedTrace);
71+
if (line != null) {
72+
// Print remaining stack trace lines.
73+
do {
74+
trimmedTrace.append(line).append("\n");
75+
line = reader.readLine();
76+
} while (line != null);
77+
}
78+
return trimmedTrace.toString();
79+
} catch (IOException e) {
80+
}
81+
return fullTrace;
82+
}
83+
84+
private static void appendStackTraceLines(
85+
List<String> stackTraceLines, StringBuilder destBuilder) {
86+
for (String stackTraceLine : stackTraceLines) {
87+
destBuilder.append(stackTraceLine).append("\n");
88+
}
89+
}
90+
91+
private static List<String> trimStackTraceLines(
92+
List<String> stackTraceLines, boolean hasCause) {
93+
State state = State.PROCESSING_OTHER_CODE;
94+
int linesToInclude = stackTraceLines.size();
95+
for (String stackTraceLine : asReversedList(stackTraceLines)) {
96+
state = state.processLine(stackTraceLine);
97+
if (state == State.DONE) {
98+
List<String> trimmedLines = stackTraceLines.subList(0, linesToInclude);
99+
if (!hasCause) {
100+
return trimmedLines;
101+
}
102+
List<String> copy = new ArrayList<String>(trimmedLines);
103+
copy.add("\t..." + (stackTraceLines.size() - copy.size()) + "more");
104+
return copy;
105+
}
106+
linesToInclude--;
107+
}
108+
return Collections.emptyList();
109+
}
110+
111+
private static <T> List<T> asReversedList(final List<T> list) {
112+
return new AbstractList<T>() {
113+
114+
@Override
115+
public T get(int index) {
116+
return list.get(list.size() - index - 1);
117+
}
118+
119+
@Override
120+
public int size() {
121+
return list.size();
122+
}
123+
};
124+
}
125+
126+
private enum State {
127+
PROCESSING_OTHER_CODE {
128+
@Override public State processLine(String line) {
129+
if (isTestFrameworkStackTraceLine(line)) {
130+
return PROCESSING_TEST_FRAMEWORK_CODE;
131+
}
132+
return this;
133+
}
134+
},
135+
PROCESSING_TEST_FRAMEWORK_CODE {
136+
@Override public State processLine(String line) {
137+
if (isReflectionStackTraceLine(line)) {
138+
return PROCESSING_REFLECTION_CODE;
139+
} else if (isTestFrameworkStackTraceLine(line)) {
140+
return this;
141+
}
142+
return PROCESSING_OTHER_CODE;
143+
}
144+
},
145+
PROCESSING_REFLECTION_CODE {
146+
@Override public State processLine(String line) {
147+
if (isReflectionStackTraceLine(line)) {
148+
return this;
149+
} else if (isTestFrameworkStackTraceLine(line)) {
150+
/*
151+
* This is here to handle TestCase.runBare() calling TestCase.runTest().
152+
* We could in theory get here if a built-in rule is accessed via
153+
* reflection.
154+
*/
155+
return PROCESSING_TEST_FRAMEWORK_CODE;
156+
}
157+
return DONE;
158+
}
159+
},
160+
DONE {
161+
@Override public State processLine(String line) {
162+
return this;
163+
}
164+
};
165+
166+
/** Processes a stack trace line, possibly moving to a new state. */
167+
public abstract State processLine(String line);
168+
}
169+
170+
private static final String[] TEST_FRAMEWORK_METHOD_NAME_PREFIXES = {
171+
"org.junit.runner.",
172+
"org.junit.runners.",
173+
"org.junit.experimental.runners.",
174+
"org.junit.internal.",
175+
"junit.",
176+
"org.apache.maven.surefire."
177+
};
178+
179+
private static boolean isTestFrameworkStackTraceLine(String line) {
180+
return isMatchingStackTraceLine(line, TEST_FRAMEWORK_METHOD_NAME_PREFIXES);
181+
}
182+
183+
private static final String[] REFLECTION_METHOD_NAME_PREFIXES = {
184+
"sun.reflect.",
185+
"java.lang.reflect.",
186+
"org.junit.rules.RunRules.evaluate(", // get better stack traces for failures in method rules
187+
"junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown()
188+
};
189+
190+
private static boolean isReflectionStackTraceLine(String line) {
191+
return isMatchingStackTraceLine(line, REFLECTION_METHOD_NAME_PREFIXES);
192+
}
193+
194+
private static boolean isMatchingStackTraceLine(String line, String[] packagePrefixes) {
195+
if (!line.startsWith("\tat ")) {
196+
return false;
197+
}
198+
line = line.substring(4);
199+
for (String packagePrefix : packagePrefixes) {
200+
if (line.startsWith(packagePrefix)) {
201+
return true;
202+
}
203+
}
204+
205+
return false;
206+
}
207+
}

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/runner/notification/Failure.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.Serializable;
55
import java.io.StringWriter;
66

7+
import org.junit.internal.StackTraces;
78
import org.junit.runner.Description;
89

910
/**
@@ -65,9 +66,7 @@ public String toString() {
6566
}
6667

6768
/**
68-
* Convenience method
69-
*
70-
* @return the printed form of the exception
69+
* Gets the printed form of the exception and its stack trace.
7170
*/
7271
public String getTrace() {
7372
StringWriter stringWriter = new StringWriter();
@@ -76,6 +75,15 @@ public String getTrace() {
7675
return stringWriter.toString();
7776
}
7877

78+
/**
79+
* Gets a the printed form of the exception, with a trimmed version of the stack trace.
80+
* This method will attempt to filter out frames of the stack trace that are below
81+
* the test method call.
82+
*/
83+
public String getTrimmedTrace() {
84+
return StackTraces.getTrimmedStackTrace(getException());
85+
}
86+
7987
/**
8088
* Convenience method
8189
*

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

+20-17
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,31 @@ 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 junit.framework.Assert.fail(Assert.java:144)");
39+
pwout.println("\tat junit.framework.Assert.assert(Assert.java:19)");
40+
pwout.println("\tat junit.framework.Assert.assert(Assert.java:26)");
41+
pwout.println("\tat MyTest.f(MyTest.java:13)");
42+
pwout.println("\tat MyTest.testStackTrace(MyTest.java:8)");
4043
fFiltered = swout.toString();
4144
}
4245

0 commit comments

Comments
 (0)