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