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