1 package org.apache.maven.plugin.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.commons.io.output.DeferredFileOutputStream;
23 import org.apache.maven.shared.utils.io.IOUtil;
24 import org.apache.maven.shared.utils.xml.XMLWriter;
25 import org.apache.maven.surefire.report.ReportEntry;
26 import org.apache.maven.surefire.report.ReporterException;
27 import org.apache.maven.surefire.report.SafeThrowable;
28
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.FilterOutputStream;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.io.OutputStreamWriter;
35 import java.util.Enumeration;
36 import java.util.Properties;
37 import java.util.StringTokenizer;
38
39 import static org.apache.maven.plugin.surefire.report.FileReporterUtils.stripIllegalFilenameChars;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public class StatelessXmlReporter
74 {
75
76 private static final byte[] ampBytes = "&#".getBytes();
77
78 private final File reportsDirectory;
79
80 private final String reportNameSuffix;
81
82 private final boolean trimStackTrace;
83
84 private final String encoding = "UTF-8";
85
86 public StatelessXmlReporter( File reportsDirectory, String reportNameSuffix, boolean trimStackTrace )
87 {
88 this.reportsDirectory = reportsDirectory;
89 this.reportNameSuffix = reportNameSuffix;
90 this.trimStackTrace = trimStackTrace;
91 }
92
93 public void testSetCompleted( WrappedReportEntry testSetReportEntry, TestSetStats testSetStats )
94 throws ReporterException
95 {
96
97 FileOutputStream outputStream = getOutputStream( testSetReportEntry );
98 OutputStreamWriter fw = getWriter( outputStream );
99 try
100 {
101
102 org.apache.maven.shared.utils.xml.XMLWriter ppw =
103 new org.apache.maven.shared.utils.xml.PrettyPrintXMLWriter( fw );
104 ppw.setEncoding( encoding );
105
106 createTestSuiteElement( ppw, testSetReportEntry, testSetStats, reportNameSuffix );
107
108 showProperties( ppw );
109
110 for ( WrappedReportEntry entry : testSetStats.getReportEntries() )
111 {
112 if ( ReportEntryType.success.equals( entry.getReportEntryType() ) )
113 {
114 startTestElement( ppw, entry, reportNameSuffix );
115 ppw.endElement();
116 }
117 else
118 {
119 getTestProblems( fw, ppw, entry, trimStackTrace, reportNameSuffix, outputStream );
120 }
121
122 }
123 ppw.endElement();
124
125 }
126 finally
127 {
128 IOUtil.close( fw );
129 }
130 }
131
132 private OutputStreamWriter getWriter( FileOutputStream fos )
133 {
134 try
135 {
136
137 return new OutputStreamWriter( fos, encoding );
138 }
139 catch ( IOException e )
140 {
141 throw new ReporterException( "When writing report", e );
142 }
143 }
144
145 private FileOutputStream getOutputStream( WrappedReportEntry testSetReportEntry )
146 {
147 File reportFile = getReportFile( testSetReportEntry, reportsDirectory, reportNameSuffix );
148
149 File reportDir = reportFile.getParentFile();
150
151
152 reportDir.mkdirs();
153
154 try
155 {
156
157 return new FileOutputStream( reportFile );
158 }
159 catch ( Exception e )
160 {
161 throw new ReporterException( "When writing report", e );
162 }
163 }
164
165 private File getReportFile( ReportEntry report, File reportsDirectory, String reportNameSuffix )
166 {
167 File reportFile;
168
169 if ( reportNameSuffix != null && reportNameSuffix.length() > 0 )
170 {
171 reportFile = new File( reportsDirectory, stripIllegalFilenameChars(
172 "TEST-" + report.getName() + "-" + reportNameSuffix + ".xml" ) );
173 }
174 else
175 {
176 reportFile = new File( reportsDirectory, stripIllegalFilenameChars( "TEST-" + report.getName() + ".xml" ) );
177 }
178
179 return reportFile;
180 }
181
182 private static void startTestElement( XMLWriter ppw, WrappedReportEntry report, String reportNameSuffix )
183 {
184 ppw.startElement( "testcase" );
185 ppw.addAttribute( "name", report.getReportName() );
186 if ( report.getGroup() != null )
187 {
188 ppw.addAttribute( "group", report.getGroup() );
189 }
190 if ( report.getSourceName() != null )
191 {
192 if ( reportNameSuffix != null && reportNameSuffix.length() > 0 )
193 {
194 ppw.addAttribute( "classname", report.getSourceName() + "(" + reportNameSuffix + ")" );
195 }
196 else
197 {
198 ppw.addAttribute( "classname", report.getSourceName() );
199 }
200 }
201 ppw.addAttribute( "time", report.elapsedTimeAsString() );
202 }
203
204 private static void createTestSuiteElement( XMLWriter ppw, WrappedReportEntry report, TestSetStats testSetStats,
205 String reportNameSuffix1 )
206 {
207 ppw.startElement( "testsuite" );
208
209 ppw.addAttribute( "name", report.getReportName( reportNameSuffix1 ) );
210
211 if ( report.getGroup() != null )
212 {
213 ppw.addAttribute( "group", report.getGroup() );
214 }
215
216 ppw.addAttribute( "time", testSetStats.getElapsedForTestSet() );
217
218 ppw.addAttribute( "tests", String.valueOf( testSetStats.getCompletedCount() ) );
219
220 ppw.addAttribute( "errors", String.valueOf( testSetStats.getErrors() ) );
221
222 ppw.addAttribute( "skipped", String.valueOf( testSetStats.getSkipped() ) );
223
224 ppw.addAttribute( "failures", String.valueOf( testSetStats.getFailures() ) );
225
226 }
227
228
229 private void getTestProblems( OutputStreamWriter outputStreamWriter, XMLWriter ppw, WrappedReportEntry report,
230 boolean trimStackTrace, String reportNameSuffix, FileOutputStream fw )
231 {
232
233 startTestElement( ppw, report, reportNameSuffix );
234
235 ppw.startElement( report.getReportEntryType().name() );
236
237 String stackTrace = report.getStackTrace( trimStackTrace );
238
239 if ( report.getMessage() != null && report.getMessage().length() > 0 )
240 {
241 ppw.addAttribute( "message", extraEscape( report.getMessage(), true ) );
242 }
243
244 if ( report.getStackTraceWriter() != null )
245 {
246
247 SafeThrowable t = report.getStackTraceWriter().getThrowable();
248 if ( t != null )
249 {
250 if ( t.getMessage() != null )
251 {
252 ppw.addAttribute( "type", ( stackTrace.contains( ":" )
253 ? stackTrace.substring( 0, stackTrace.indexOf( ":" ) )
254 : stackTrace ) );
255 }
256 else
257 {
258 ppw.addAttribute( "type", new StringTokenizer( stackTrace ).nextToken() );
259 }
260 }
261 }
262
263 if ( stackTrace != null )
264 {
265 ppw.writeText( extraEscape( stackTrace, false ) );
266 }
267
268 ppw.endElement();
269
270 EncodingOutputStream eos = new EncodingOutputStream( fw );
271
272 addOutputStreamElement( outputStreamWriter, fw, eos, ppw, report.getStdout(), "system-out" );
273
274 addOutputStreamElement( outputStreamWriter, fw, eos, ppw, report.getStdErr(), "system-err" );
275
276 ppw.endElement();
277 }
278
279 private void addOutputStreamElement( OutputStreamWriter outputStreamWriter, OutputStream fw,
280 EncodingOutputStream eos, XMLWriter xmlWriter, DeferredFileOutputStream stdOut,
281 String name )
282 {
283 if ( stdOut != null && stdOut.getByteCount() > 0 )
284 {
285
286 xmlWriter.startElement( name );
287
288 try
289 {
290 xmlWriter.writeText( "" );
291 outputStreamWriter.flush();
292 stdOut.close();
293 stdOut.writeTo( eos );
294 eos.flush();
295 }
296 catch ( IOException e )
297 {
298 throw new ReporterException( "When writing xml report stdout/stderr", e );
299 }
300 xmlWriter.endElement();
301 }
302 }
303
304
305
306
307
308
309
310 private void showProperties( XMLWriter xmlWriter )
311 {
312 xmlWriter.startElement( "properties" );
313
314 Properties systemProperties = System.getProperties();
315
316 if ( systemProperties != null )
317 {
318 Enumeration<?> propertyKeys = systemProperties.propertyNames();
319
320 while ( propertyKeys.hasMoreElements() )
321 {
322 String key = (String) propertyKeys.nextElement();
323
324 String value = systemProperties.getProperty( key );
325
326 if ( value == null )
327 {
328 value = "null";
329 }
330
331 xmlWriter.startElement( "property" );
332
333 xmlWriter.addAttribute( "name", key );
334
335 xmlWriter.addAttribute( "value", extraEscape( value, true ) );
336
337 xmlWriter.endElement();
338
339 }
340 }
341 xmlWriter.endElement();
342 }
343
344
345
346
347
348
349
350
351 private static String extraEscape( String message, boolean attribute )
352 {
353
354 if ( !containsEscapesIllegalnXml10( message ) )
355 {
356 return message;
357 }
358 return escapeXml( message, attribute );
359 }
360
361 private static class EncodingOutputStream
362 extends FilterOutputStream
363 {
364
365 public EncodingOutputStream( OutputStream out )
366 {
367 super( out );
368 }
369
370 @Override
371 public void write( int b )
372 throws IOException
373 {
374 if ( isIllegalEscape( b ) )
375 {
376
377
378
379
380
381 out.write( ampBytes );
382 out.write( b );
383 out.write( ';' );
384 }
385 else if ( '<' == b )
386 {
387 out.write( '&' );
388 out.write( 'l' );
389 out.write( 't' );
390 out.write( ';' );
391 }
392 else if ( '>' == b )
393 {
394 out.write( '&' );
395 out.write( 'g' );
396 out.write( 't' );
397 out.write( ';' );
398 }
399 else if ( '&' == b )
400 {
401 out.write( '&' );
402 out.write( 'a' );
403 out.write( 'm' );
404 out.write( 'p' );
405 out.write( ';' );
406 }
407 else
408 {
409 out.write( b );
410 }
411 }
412 }
413
414 private static boolean containsEscapesIllegalnXml10( String message )
415 {
416 int size = message.length();
417 for ( int i = 0; i < size; i++ )
418 {
419 if ( isIllegalEscape( message.charAt( i ) ) )
420 {
421 return true;
422 }
423
424 }
425 return false;
426 }
427
428 private static boolean isIllegalEscape( char c )
429 {
430 return c >= 0 && c < 32 && c != '\n' && c != '\r' && c != '\t';
431 }
432
433 private static boolean isIllegalEscape( int c )
434 {
435 return c >= 0 && c < 32 && c != '\n' && c != '\r' && c != '\t';
436 }
437
438 private static String escapeXml( String text, boolean attribute )
439 {
440 StringBuilder sb = new StringBuilder( text.length() * 2 );
441 for ( int i = 0; i < text.length(); i++ )
442 {
443 char c = text.charAt( i );
444 if ( isIllegalEscape( c ) )
445 {
446
447
448
449
450
451 sb.append( attribute ? "&#" : "&#" ).append( (int) c ).append(
452 ';' );
453 }
454 else
455 {
456 sb.append( c );
457 }
458 }
459 return sb.toString();
460 }
461
462 }