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