• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.lang.reflect.Method;
9 import java.util.AbstractList;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Collections;
13 import java.util.List;
14 
15 /**
16  * Miscellaneous functions dealing with {@code Throwable}.
17  *
18  * @author kcooney@google.com (Kevin Cooney)
19  * @since 4.12
20  */
21 public final class Throwables {
22 
Throwables()23     private Throwables() {
24     }
25 
26     /**
27      * Rethrows the given {@code Throwable}, allowing the caller to
28      * declare that it throws {@code Exception}. This is useful when
29      * your callers have nothing reasonable they can do when a
30      * {@code Throwable} is thrown. This is declared to return {@code Exception}
31      * so it can be used in a {@code throw} clause:
32      * <pre>
33      * try {
34      *   doSomething();
35      * } catch (Throwable e} {
36      *   throw Throwables.rethrowAsException(e);
37      * }
38      * doSomethingLater();
39      * </pre>
40      *
41      * @param e exception to rethrow
42      * @return does not return anything
43      * @since 4.12
44      */
rethrowAsException(Throwable e)45     public static Exception rethrowAsException(Throwable e) throws Exception {
46         Throwables.<Exception>rethrow(e);
47         return null; // we never get here
48     }
49 
50     @SuppressWarnings("unchecked")
rethrow(Throwable e)51     private static <T extends Throwable> void rethrow(Throwable e) throws T {
52         throw (T) e;
53     }
54 
55     /**
56      * Returns the stacktrace of the given Throwable as a String.
57      *
58      * @since 4.13
59      */
getStacktrace(Throwable exception)60     public static String getStacktrace(Throwable exception) {
61         StringWriter stringWriter = new StringWriter();
62         PrintWriter writer = new PrintWriter(stringWriter);
63         exception.printStackTrace(writer);
64         return stringWriter.toString();
65     }
66 
67     /**
68      * Gets a trimmed version of the stack trace of the given exception. Stack trace
69      * elements that are below the test method are filtered out.
70      *
71      * @return a trimmed stack trace, or the original trace if trimming wasn't possible
72      */
getTrimmedStackTrace(Throwable exception)73     public static String getTrimmedStackTrace(Throwable exception) {
74         List<String> trimmedStackTraceLines = getTrimmedStackTraceLines(exception);
75         if (trimmedStackTraceLines.isEmpty()) {
76             return getFullStackTrace(exception);
77         }
78 
79         StringBuilder result = new StringBuilder(exception.toString());
80         appendStackTraceLines(trimmedStackTraceLines, result);
81         appendStackTraceLines(getCauseStackTraceLines(exception), result);
82         return result.toString();
83     }
84 
getTrimmedStackTraceLines(Throwable exception)85     private static List<String> getTrimmedStackTraceLines(Throwable exception) {
86         List<StackTraceElement> stackTraceElements = Arrays.asList(exception.getStackTrace());
87         int linesToInclude = stackTraceElements.size();
88 
89         State state = State.PROCESSING_OTHER_CODE;
90         for (StackTraceElement stackTraceElement : asReversedList(stackTraceElements)) {
91             state = state.processStackTraceElement(stackTraceElement);
92             if (state == State.DONE) {
93                 List<String> trimmedLines = new ArrayList<String>(linesToInclude + 2);
94                 trimmedLines.add("");
95                 for (StackTraceElement each : stackTraceElements.subList(0, linesToInclude)) {
96                     trimmedLines.add("\tat " + each);
97                 }
98                 if (exception.getCause() != null) {
99                     trimmedLines.add("\t... " + (stackTraceElements.size() - trimmedLines.size()) + " trimmed");
100                 }
101                 return trimmedLines;
102             }
103             linesToInclude--;
104         }
105         return Collections.emptyList();
106     }
107 
108     private static final Method getSuppressed = initGetSuppressed();
109 
initGetSuppressed()110     private static Method initGetSuppressed() {
111         try {
112             return Throwable.class.getMethod("getSuppressed");
113         } catch (Throwable e) {
114             return null;
115         }
116     }
117 
hasSuppressed(Throwable exception)118     private static boolean hasSuppressed(Throwable exception) {
119         if (getSuppressed == null) {
120             return false;
121         }
122         try {
123             Throwable[] suppressed = (Throwable[]) getSuppressed.invoke(exception);
124             return suppressed.length != 0;
125         } catch (Throwable e) {
126             return false;
127         }
128     }
129 
getCauseStackTraceLines(Throwable exception)130     private static List<String> getCauseStackTraceLines(Throwable exception) {
131         if (exception.getCause() != null || hasSuppressed(exception)) {
132             String fullTrace = getFullStackTrace(exception);
133             BufferedReader reader = new BufferedReader(
134                     new StringReader(fullTrace.substring(exception.toString().length())));
135             List<String> causedByLines = new ArrayList<String>();
136 
137             try {
138                 String line;
139                 while ((line = reader.readLine()) != null) {
140                     if (line.startsWith("Caused by: ") || line.trim().startsWith("Suppressed: ")) {
141                         causedByLines.add(line);
142                         while ((line = reader.readLine()) != null) {
143                             causedByLines.add(line);
144                         }
145                         return causedByLines;
146                     }
147                 }
148             } catch (IOException e) {
149                 // We should never get here, because we are reading from a StringReader
150             }
151         }
152 
153         return Collections.emptyList();
154     }
155 
getFullStackTrace(Throwable exception)156     private static String getFullStackTrace(Throwable exception) {
157         StringWriter stringWriter = new StringWriter();
158         PrintWriter writer = new PrintWriter(stringWriter);
159         exception.printStackTrace(writer);
160         return stringWriter.toString();
161     }
162 
appendStackTraceLines( List<String> stackTraceLines, StringBuilder destBuilder)163     private static void appendStackTraceLines(
164             List<String> stackTraceLines, StringBuilder destBuilder) {
165         for (String stackTraceLine : stackTraceLines) {
166             destBuilder.append(String.format("%s%n", stackTraceLine));
167         }
168     }
169 
asReversedList(final List<T> list)170     private static <T> List<T> asReversedList(final List<T> list) {
171         return new AbstractList<T>() {
172 
173             @Override
174             public T get(int index) {
175                 return list.get(list.size() - index - 1);
176             }
177 
178             @Override
179             public int size() {
180                 return list.size();
181             }
182         };
183     }
184 
185     private enum State {
186         PROCESSING_OTHER_CODE {
187             @Override public State processLine(String methodName) {
188                 if (isTestFrameworkMethod(methodName)) {
189                     return PROCESSING_TEST_FRAMEWORK_CODE;
190                 }
191                 return this;
192             }
193         },
194         PROCESSING_TEST_FRAMEWORK_CODE {
195             @Override public State processLine(String methodName) {
196                 if (isReflectionMethod(methodName)) {
197                     return PROCESSING_REFLECTION_CODE;
198                 } else if (isTestFrameworkMethod(methodName)) {
199                     return this;
200                 }
201                 return PROCESSING_OTHER_CODE;
202             }
203         },
204         PROCESSING_REFLECTION_CODE {
205             @Override public State processLine(String methodName) {
206                 if (isReflectionMethod(methodName)) {
207                     return this;
208                 } else if (isTestFrameworkMethod(methodName)) {
209                     // This is here to handle TestCase.runBare() calling TestCase.runTest().
210                     return PROCESSING_TEST_FRAMEWORK_CODE;
211                 }
212                 return DONE;
213             }
214         },
215         DONE {
216             @Override public State processLine(String methodName) {
217                 return this;
218             }
219         };
220 
221         /** Processes a stack trace element method name, possibly moving to a new state. */
222         protected abstract State processLine(String methodName);
223 
224         /** Processes a stack trace element, possibly moving to a new state. */
225         public final State processStackTraceElement(StackTraceElement element) {
226             return processLine(element.getClassName() + "." + element.getMethodName() + "()");
227         }
228     }
229 
230     private static final String[] TEST_FRAMEWORK_METHOD_NAME_PREFIXES = {
231         "org.junit.runner.",
232         "org.junit.runners.",
233         "org.junit.experimental.runners.",
234         "org.junit.internal.",
235         "junit.extensions",
236         "junit.framework",
237         "junit.runner",
238         "junit.textui",
239     };
240 
241     private static final String[] TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES = {
242         "org.junit.internal.StackTracesTest",
243     };
244 
245     private static boolean isTestFrameworkMethod(String methodName) {
246         return isMatchingMethod(methodName, TEST_FRAMEWORK_METHOD_NAME_PREFIXES) &&
247                 !isMatchingMethod(methodName, TEST_FRAMEWORK_TEST_METHOD_NAME_PREFIXES);
248     }
249 
250     private static final String[] REFLECTION_METHOD_NAME_PREFIXES = {
251         "sun.reflect.",
252         "java.lang.reflect.",
253         "jdk.internal.reflect.",
254         "org.junit.rules.RunRules.<init>(",
255         "org.junit.rules.RunRules.applyAll(", // calls TestRules
256         "org.junit.runners.RuleContainer.apply(", // calls MethodRules & TestRules
257         "junit.framework.TestCase.runBare(", // runBare() directly calls setUp() and tearDown()
258    };
259 
260     private static boolean isReflectionMethod(String methodName) {
261         return isMatchingMethod(methodName, REFLECTION_METHOD_NAME_PREFIXES);
262     }
263 
264     private static boolean isMatchingMethod(String methodName, String[] methodNamePrefixes) {
265         for (String methodNamePrefix : methodNamePrefixes) {
266             if (methodName.startsWith(methodNamePrefix)) {
267                 return true;
268             }
269         }
270 
271         return false;
272     }
273 }
274