1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.surefire.report;
20
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.List;
24
25 import org.apache.maven.surefire.api.report.SafeThrowable;
26
27 import static java.util.Arrays.asList;
28 import static java.util.Collections.reverse;
29 import static org.apache.maven.surefire.shared.utils.StringUtils.chompLast;
30 import static org.apache.maven.surefire.shared.utils.StringUtils.isNotEmpty;
31
32
33
34
35 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
36 public class SmartStackTraceParser {
37 private final SafeThrowable throwable;
38
39 private final StackTraceElement[] stackTrace;
40
41 private final String testClassName;
42
43 private final Class<?> testClass;
44
45 private final String testMethodName;
46
47 public SmartStackTraceParser(String testClassName, Throwable throwable, String testMethodName) {
48 this.testMethodName = testMethodName;
49 this.testClassName = testClassName;
50 testClass = toClass(testClassName);
51 this.throwable = new SafeThrowable(throwable);
52 stackTrace = throwable.getStackTrace();
53 }
54
55 private static Class<?> toClass(String name) {
56 try {
57 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
58 return classLoader == null ? null : classLoader.loadClass(name);
59 } catch (ClassNotFoundException e) {
60 return null;
61 }
62 }
63
64 private static String toSimpleClassName(String className) {
65 int i = className.lastIndexOf(".");
66 return className.substring(i + 1);
67 }
68
69 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
70 public String getString() {
71 if (testClass == null) {
72 return throwable.getLocalizedMessage();
73 }
74
75 final StringBuilder result = new StringBuilder();
76 final List<StackTraceElement> stackTraceElements = focusOnClass(stackTrace, testClass);
77 reverse(stackTraceElements);
78 final String testClassSimpleName = toSimpleClassName(testClassName);
79 if (stackTraceElements.isEmpty()) {
80 result.append(testClassSimpleName);
81 if (isNotEmpty(testMethodName)) {
82 result.append(".").append(testMethodName);
83 }
84 } else {
85 for (int i = 0, size = stackTraceElements.size(); i < size; i++) {
86 final StackTraceElement stackTraceElement = stackTraceElements.get(i);
87 final boolean isTestClassName = stackTraceElement.getClassName().equals(testClassName);
88 if (i == 0) {
89 result.append(testClassSimpleName).append(isTestClassName ? '.' : '>');
90 }
91
92 if (!isTestClassName) {
93 result.append(toSimpleClassName(stackTraceElement.getClassName()))
94 .append('.');
95 }
96
97 result.append(stackTraceElement.getMethodName())
98 .append(':')
99 .append(stackTraceElement.getLineNumber())
100 .append("->");
101 }
102
103 if (result.length() >= 2) {
104 result.setLength(result.length() - 2);
105 }
106 }
107
108 final Throwable target = throwable.getTarget();
109 final Class<?> excType = target.getClass();
110 final String excClassName = excType.getName();
111 final String msg = throwable.getMessage();
112
113 if (!(target instanceof AssertionError
114 || "junit.framework.AssertionFailedError".equals(excClassName)
115 || "junit.framework.ComparisonFailure".equals(excClassName)
116 || excClassName.startsWith("org.opentest4j."))) {
117 result.append(rootIsInclass() ? " " : " ยป ").append(toMinimalThrowableMiniMessage(excType));
118 }
119
120 if (isNotEmpty(msg)) {
121 result.append(' ').append(msg);
122 }
123 return result.toString();
124 }
125
126 private static String toMinimalThrowableMiniMessage(Class<?> excType) {
127 String name = excType.getSimpleName();
128 if (name.endsWith("Exception")) {
129 return chompLast(name, "Exception");
130 }
131 if (name.endsWith("Error")) {
132 return chompLast(name, "Error");
133 }
134 return name;
135 }
136
137 private boolean rootIsInclass() {
138 return stackTrace != null
139 && stackTrace.length > 0
140 && stackTrace[0].getClassName().equals(testClassName);
141 }
142
143 private static List<StackTraceElement> focusOnClass(StackTraceElement[] stackTrace, Class<?> clazz) {
144 if (stackTrace == null) {
145 return Collections.emptyList();
146 }
147 List<StackTraceElement> result = new ArrayList<>();
148 for (StackTraceElement element : stackTrace) {
149 if (element != null && isInSupers(clazz, element.getClassName())) {
150 result.add(element);
151 }
152 }
153 return result;
154 }
155
156 private static boolean isInSupers(Class<?> testClass, String lookFor) {
157 if (lookFor.startsWith("junit.framework.")) {
158 return false;
159 }
160 while (!testClass.getName().equals(lookFor) && testClass.getSuperclass() != null) {
161 testClass = testClass.getSuperclass();
162 }
163 return testClass.getName().equals(lookFor);
164 }
165
166 static Throwable findTopmostWithClass(final Throwable t, StackTraceFilter filter) {
167 Throwable n = t;
168 do {
169 if (containsClassName(n.getStackTrace(), filter)) {
170 return n;
171 }
172
173 n = n.getCause();
174 } while (n != null);
175 return t;
176 }
177
178 public static String stackTraceWithFocusOnClassAsString(Throwable t, String className) {
179 StackTraceFilter filter = new ClassNameStackTraceFilter(className);
180 Throwable topmost = findTopmostWithClass(t, filter);
181 List<StackTraceElement> stackTraceElements = focusInsideClass(topmost.getStackTrace(), filter);
182 String s = causeToString(topmost.getCause(), filter);
183 return toString(t, stackTraceElements, filter) + s;
184 }
185
186 static List<StackTraceElement> focusInsideClass(StackTraceElement[] stackTrace, StackTraceFilter filter) {
187 List<StackTraceElement> result = new ArrayList<>();
188 for (StackTraceElement element : stackTrace) {
189 if (filter.matches(element)) {
190 result.add(element);
191 }
192 }
193 return result;
194 }
195
196 private static boolean containsClassName(StackTraceElement[] stackTrace, StackTraceFilter filter) {
197 for (StackTraceElement element : stackTrace) {
198 if (filter.matches(element)) {
199 return true;
200 }
201 }
202 return false;
203 }
204
205 private static String causeToString(Throwable cause, StackTraceFilter filter) {
206 StringBuilder resp = new StringBuilder();
207 while (cause != null) {
208 resp.append("Caused by: ");
209 resp.append(toString(cause, asList(cause.getStackTrace()), filter));
210 cause = cause.getCause();
211 }
212 return resp.toString();
213 }
214
215 private static String toString(Throwable t, Iterable<StackTraceElement> elements, StackTraceFilter filter) {
216 StringBuilder result = new StringBuilder();
217 if (t != null) {
218 result.append(t.getClass().getName());
219 String msg = t.getMessage();
220 if (msg != null) {
221 result.append(": ");
222 if (isMultiLine(msg)) {
223
224 result.append('\n');
225 }
226 result.append(msg);
227 }
228 result.append('\n');
229 }
230
231 for (StackTraceElement element : elements) {
232 if (filter.matches(element)) {
233 result.append("\tat ").append(element).append('\n');
234 }
235 }
236 return result.toString();
237 }
238
239 private static boolean isMultiLine(String msg) {
240 int countNewLines = 0;
241 for (int i = 0, length = msg.length(); i < length; i++) {
242 if (msg.charAt(i) == '\n') {
243 if (++countNewLines == 2) {
244 break;
245 }
246 }
247 }
248 return countNewLines > 1 || countNewLines == 1 && !msg.trim().endsWith("\n");
249 }
250 }