-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
Trim stack trace. #1028
Changes from 1 commit
4930a1e
170d509
858c057
2d754f3
8846d73
3488c76
3c6be5a
3b45708
21c24f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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()))); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. extraced -> extracted - typo |
||
|
||
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." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If including Maven, should it also include Gradle? http://gradle.org/docs/current/dsl/org.gradle.api.tasks.testing.Test.html There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ashleyfrieze Good idea. Would you please send me an example stack trace from a failing JUnit test running on Gradle? I can add test cases for Gradle and SureFire. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is the output from a Gradle test failure. I don't use Gradle myself, so I asked a colleague.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks; I'll try to find time to add this to the test cases this weekend. It's possible that Gradle doesn't use the code I'm changing when it prints out test failures or produces test XML. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out, I don't need to special case Maven Surefire or Gradle. I added testcases to verify that |
||
}; | ||
|
||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is quite long. I think it is separated into different activities which could be "extractMethod" refactored. Example, collecting the original set of stack trace lines could be a method of its own, as could be the loop on line 71 below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ashleyfrieze I tried to extract a method here, but I couldn't find a clean way to to it. Extracting a method at line 71 won't shorten the method by much, and might even hurt readability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kcooney if you tell me how to do it, I could fork your code and show you a refactoring via pull request. In general, my rule is this - if you've put a comment on top of a block of code, then there's probably a method waiting to be extracted. So lines 71-77 look like a method to me, as do lines 50-60.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ashleyfrieze I'm a fan of the Hub tool for tasks like this (see http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#). That being said, for breaking up private or back-scoped methods, I think it would be simpler for me to push this and for you to send a new pull.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kcooney just let me know where it is and I'll have a look and send you a pull request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ashleyfrieze let you know where what is? The blog post explains how to take a github pull request and get it into your local repo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kcooney - aha! (GitHub rookie). I'll look!