1 package org.apache.maven.plugins.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.doxia.sink.Sink;
23 import org.apache.maven.reporting.MavenReportException;
24
25 import java.io.File;
26 import java.text.NumberFormat;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.ListIterator;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.ResourceBundle;
33 import java.util.StringTokenizer;
34
35
36
37
38 public class SurefireReportGenerator
39 {
40 private SurefireReportParser report;
41
42 private List testSuites;
43
44 private boolean showSuccess;
45
46 private String xrefLocation;
47
48 public SurefireReportGenerator( File[] reportsDirectories, Locale locale, boolean showSuccess, String xrefLocation )
49 {
50 report = new SurefireReportParser( reportsDirectories, locale );
51
52 this.xrefLocation = xrefLocation;
53
54 this.showSuccess = showSuccess;
55 }
56
57 public void doGenerateReport( ResourceBundle bundle, Sink sink )
58 throws MavenReportException
59 {
60 testSuites = report.parseXMLReportFiles();
61
62 sink.head();
63
64 sink.title();
65 sink.text( bundle.getString( "report.surefire.header" ) );
66 sink.title_();
67
68 StringBuffer str = new StringBuffer();
69 str.append( "<script type=\"text/javascript\">\n" );
70 str.append( "function toggleDisplay(elementId) {\n" );
71 str.append( " var elm = document.getElementById(elementId + 'error');\n" );
72 str.append( " if (elm && typeof elm.style != \"undefined\") {\n" );
73 str.append( " if (elm.style.display == \"none\") {\n" );
74 str.append( " elm.style.display = \"\";\n" );
75 str.append( " document.getElementById(elementId + 'off').style.display = \"none\";\n" );
76 str.append( " document.getElementById(elementId + 'on').style.display = \"inline\";\n" );
77 str.append( " }" );
78 str.append( " else if (elm.style.display == \"\") {" );
79 str.append( " elm.style.display = \"none\";\n" );
80 str.append( " document.getElementById(elementId + 'off').style.display = \"inline\";\n" );
81 str.append( " document.getElementById(elementId + 'on').style.display = \"none\";\n" );
82 str.append( " } \n" );
83 str.append( " } \n" );
84 str.append( " }\n" );
85 str.append( "</script>" );
86 sink.rawText( str.toString() );
87
88 sink.head_();
89
90 sink.body();
91
92 sink.section1();
93 sink.sectionTitle1();
94 sink.text( bundle.getString( "report.surefire.header" ) );
95 sink.sectionTitle1_();
96 sink.section1_();
97
98 constructSummarySection( bundle, sink );
99
100 Map suitePackages = report.getSuitesGroupByPackage( testSuites );
101 if ( !suitePackages.isEmpty() )
102 {
103 constructPackagesSection( bundle, sink, suitePackages );
104 }
105
106 if ( !testSuites.isEmpty() )
107 {
108 constructTestCasesSection( bundle, sink );
109 }
110
111 List failureList = report.getFailureDetails( testSuites );
112 if ( !failureList.isEmpty() )
113 {
114 constructFailureDetails( sink, bundle, failureList );
115 }
116
117 sink.body_();
118
119 sink.flush();
120
121 sink.close();
122 }
123
124 private void constructSummarySection( ResourceBundle bundle, Sink sink )
125 {
126 Map summary = report.getSummary( testSuites );
127
128 sink.section1();
129 sink.sectionTitle1();
130 sink.text( bundle.getString( "report.surefire.label.summary" ) );
131 sink.sectionTitle1_();
132
133 sinkAnchor( sink, "Summary" );
134
135 constructHotLinks( sink, bundle );
136
137 sinkLineBreak( sink );
138
139 sink.table();
140
141 sink.tableRow();
142
143 sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
144
145 sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
146
147 sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
148
149 sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
150
151 sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
152
153 sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
154
155 sink.tableRow_();
156
157 sink.tableRow();
158
159 sinkCell( sink, (String) summary.get( "totalTests" ) );
160
161 sinkCell( sink, (String) summary.get( "totalErrors" ) );
162
163 sinkCell( sink, (String) summary.get( "totalFailures" ) );
164
165 sinkCell( sink, (String) summary.get( "totalSkipped" ) );
166
167 sinkCell( sink, summary.get( "totalPercentage" ) + "%" );
168
169 sinkCell( sink, (String) summary.get( "totalElapsedTime" ) );
170
171 sink.tableRow_();
172
173 sink.table_();
174
175 sink.lineBreak();
176
177 sink.paragraph();
178 sink.rawText( bundle.getString( "report.surefire.text.note1" ) );
179 sink.paragraph_();
180
181 sinkLineBreak( sink );
182
183 sink.section1_();
184 }
185
186 private void constructPackagesSection( ResourceBundle bundle, Sink sink, Map suitePackages )
187 {
188 NumberFormat numberFormat = report.getNumberFormat();
189
190 sink.section1();
191 sink.sectionTitle1();
192 sink.text( bundle.getString( "report.surefire.label.packagelist" ) );
193 sink.sectionTitle1_();
194
195 sinkAnchor( sink, "Package_List" );
196
197 constructHotLinks( sink, bundle );
198
199 sinkLineBreak( sink );
200
201 sink.table();
202
203 sink.tableRow();
204
205 sinkHeader( sink, bundle.getString( "report.surefire.label.package" ) );
206
207 sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
208
209 sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
210
211 sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
212
213 sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
214
215 sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
216
217 sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
218
219 sink.tableRow_();
220
221 Iterator packIter = suitePackages.keySet().iterator();
222
223 while ( packIter.hasNext() )
224 {
225 sink.tableRow();
226
227 String packageName = (String) packIter.next();
228
229 List testSuiteList = (List) suitePackages.get( packageName );
230
231 Map packageSummary = report.getSummary( testSuiteList );
232
233 sinkCellLink( sink, packageName, "#" + packageName );
234
235 sinkCell( sink, (String) packageSummary.get( "totalTests" ) );
236
237 sinkCell( sink, (String) packageSummary.get( "totalErrors" ) );
238
239 sinkCell( sink, (String) packageSummary.get( "totalFailures" ) );
240
241 sinkCell( sink, (String) packageSummary.get( "totalSkipped" ) );
242
243 sinkCell( sink, packageSummary.get( "totalPercentage" ) + "%" );
244
245 sinkCell( sink, (String) packageSummary.get( "totalElapsedTime" ) );
246
247 sink.tableRow_();
248 }
249
250 sink.table_();
251
252 sink.lineBreak();
253
254 sink.paragraph();
255 sink.rawText( bundle.getString( "report.surefire.text.note2" ) );
256 sink.paragraph_();
257
258 packIter = suitePackages.keySet().iterator();
259
260 while ( packIter.hasNext() )
261 {
262 String packageName = (String) packIter.next();
263
264 List testSuiteList = (List) suitePackages.get( packageName );
265
266 Iterator suiteIterator = testSuiteList.iterator();
267
268 sink.section2();
269 sink.sectionTitle2();
270 sink.text( packageName );
271 sink.sectionTitle2_();
272
273 sinkAnchor( sink, packageName );
274
275 sink.table();
276
277 sink.tableRow();
278
279 sinkHeader( sink, "" );
280
281 sinkHeader( sink, bundle.getString( "report.surefire.label.class" ) );
282
283 sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
284
285 sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
286
287 sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
288
289 sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
290
291 sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
292
293 sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
294
295 sink.tableRow_();
296
297 while ( suiteIterator.hasNext() )
298 {
299 ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
300
301 if ( showSuccess || suite.getNumberOfErrors() != 0 || suite.getNumberOfFailures() != 0 )
302 {
303
304 sink.tableRow();
305
306 sink.tableCell();
307
308 sink.link( "#" + suite.getPackageName() + suite.getName() );
309
310 if ( suite.getNumberOfErrors() > 0 )
311 {
312 sinkIcon( "error", sink );
313 }
314 else if ( suite.getNumberOfFailures() > 0 )
315 {
316 sinkIcon( "junit.framework", sink );
317 }
318 else if ( suite.getNumberOfSkipped() > 0 )
319 {
320 sinkIcon( "skipped", sink );
321 }
322 else
323 {
324 sinkIcon( "success", sink );
325 }
326
327 sink.link_();
328
329 sink.tableCell_();
330
331 sinkCellLink( sink, suite.getName(), "#" + suite.getPackageName() + suite.getName() );
332
333 sinkCell( sink, Integer.toString( suite.getNumberOfTests() ) );
334
335 sinkCell( sink, Integer.toString( suite.getNumberOfErrors() ) );
336
337 sinkCell( sink, Integer.toString( suite.getNumberOfFailures() ) );
338
339 sinkCell( sink, Integer.toString( suite.getNumberOfSkipped() ) );
340
341 String percentage = report.computePercentage( suite.getNumberOfTests(), suite.getNumberOfErrors(),
342 suite.getNumberOfFailures(), suite
343 .getNumberOfSkipped() );
344 sinkCell( sink, percentage + "%" );
345
346 sinkCell( sink, numberFormat.format( suite.getTimeElapsed() ) );
347
348 sink.tableRow_();
349 }
350 }
351
352 sink.table_();
353
354 sink.section2_();
355 }
356
357 sinkLineBreak( sink );
358
359 sink.section1_();
360 }
361
362 private void constructTestCasesSection( ResourceBundle bundle, Sink sink )
363 {
364 NumberFormat numberFormat = report.getNumberFormat();
365
366 sink.section1();
367 sink.sectionTitle1();
368 sink.text( bundle.getString( "report.surefire.label.testcases" ) );
369 sink.sectionTitle1_();
370
371 sinkAnchor( sink, "Test_Cases" );
372
373 constructHotLinks( sink, bundle );
374
375 ListIterator suiteIterator = testSuites.listIterator();
376
377 while ( suiteIterator.hasNext() )
378 {
379 ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
380
381 List testCases = suite.getTestCases();
382
383 if ( testCases != null && !testCases.isEmpty() )
384 {
385 ListIterator caseIterator = testCases.listIterator();
386
387 sink.section2();
388 sink.sectionTitle2();
389 sink.text( suite.getName() );
390 sink.sectionTitle2_();
391
392 sinkAnchor( sink, suite.getPackageName() + suite.getName() );
393
394 sink.table();
395
396 while ( caseIterator.hasNext() )
397 {
398 ReportTestCase testCase = (ReportTestCase) caseIterator.next();
399
400 if ( testCase.getFailure() != null || showSuccess )
401 {
402 sink.tableRow();
403
404 sink.tableCell();
405
406 Map failure = testCase.getFailure();
407
408 if ( failure != null )
409 {
410 sink.link( "#" + testCase.getFullName() );
411
412 sinkIcon( (String) failure.get( "type" ), sink );
413
414 sink.link_();
415 }
416 else
417 {
418 sinkIcon( "success", sink );
419 }
420
421 sink.tableCell_();
422
423 if ( failure != null )
424 {
425 sink.tableCell();
426
427 sinkLink( sink, testCase.getName(), "#" + testCase.getFullName() );
428
429 sink.rawText( " <div class=\"detailToggle\" style=\"display:inline\">" );
430
431 sink.link( "javascript:toggleDisplay('" + testCase.getName() + "');" );
432
433 sink.rawText( "<span style=\"display: inline;\" " + "id=\"" + testCase.getName()
434 + "off\">+</span><span id=\"" + testCase.getName() + "on\" "
435 + "style=\"display: none;\">-</span> " );
436 sink.text( "[ Detail ]" );
437 sink.link_();
438
439 sink.rawText( "</div>" );
440
441 sink.tableCell_();
442 }
443 else
444 {
445 sinkCell( sink, testCase.getName() );
446 }
447
448 sinkCell( sink, numberFormat.format( testCase.getTime() ) );
449
450 sink.tableRow_();
451
452 if ( failure != null )
453 {
454 sink.tableRow();
455
456 sinkCell( sink, "" );
457 sinkCell( sink, (String) failure.get( "message" ) );
458 sinkCell( sink, "" );
459 sink.tableRow_();
460
461 List detail = (List) failure.get( "detail" );
462 if ( detail != null )
463 {
464
465 sink.tableRow();
466 sinkCell( sink, "" );
467
468 sink.tableCell();
469 sink.rawText(
470 " <div id=\"" + testCase.getName() + "error\" style=\"display:none;\">" );
471
472 Iterator it = detail.iterator();
473
474 sink.verbatim( true );
475 while ( it.hasNext() )
476 {
477 sink.text( it.next().toString() );
478 sink.lineBreak();
479 }
480 sink.verbatim_();
481
482 sink.rawText( "</div>" );
483 sink.tableCell_();
484
485 sinkCell( sink, "" );
486
487 sink.tableRow_();
488 }
489 }
490 }
491 }
492
493 sink.table_();
494
495 sink.section2_();
496 }
497 }
498
499 sinkLineBreak( sink );
500
501 sink.section1_();
502 }
503
504 private void constructFailureDetails( Sink sink, ResourceBundle bundle, List failureList )
505 {
506 Iterator failIter = failureList.iterator();
507
508 if ( failIter != null )
509 {
510 sink.section1();
511 sink.sectionTitle1();
512 sink.text( bundle.getString( "report.surefire.label.failuredetails" ) );
513 sink.sectionTitle1_();
514
515 sinkAnchor( sink, "Failure_Details" );
516
517 constructHotLinks( sink, bundle );
518
519 sinkLineBreak( sink );
520
521 sink.table();
522
523 while ( failIter.hasNext() )
524 {
525 ReportTestCase tCase = (ReportTestCase) failIter.next();
526
527 Map failure = tCase.getFailure();
528
529 sink.tableRow();
530
531 sink.tableCell();
532
533 String type = (String) failure.get( "type" );
534 sinkIcon( type, sink );
535
536 sink.tableCell_();
537
538 sinkCellAnchor( sink, tCase.getName(), tCase.getFullName() );
539
540 sink.tableRow_();
541
542 String message = (String) failure.get( "message" );
543
544 sink.tableRow();
545
546 sinkCell( sink, "" );
547
548 StringBuffer sb = new StringBuffer();
549 sb.append( type );
550
551 if ( message != null )
552 {
553 sb.append( ": " );
554 sb.append( message );
555 }
556
557 sinkCell( sink, sb.toString() );
558
559 sink.tableRow_();
560
561 List detail = (List) failure.get( "detail" );
562 if ( detail != null )
563 {
564 Iterator it = detail.iterator();
565
566 boolean firstLine = true;
567
568 String techMessage = "";
569 while ( it.hasNext() )
570 {
571 techMessage = it.next().toString();
572 if ( firstLine )
573 {
574 firstLine = false;
575 }
576 else
577 {
578 sink.text( " " );
579 }
580 }
581
582 sink.tableRow();
583
584 sinkCell( sink, "" );
585
586 sink.tableCell();
587 sink.rawText( " <div id=\"" + tCase.getName() + "error\" >" );
588
589 if ( xrefLocation != null )
590 {
591 String path = tCase.getFullClassName().replace( '.', '/' );
592
593 sink.link( xrefLocation + "/" + path + ".html#" +
594 getErrorLineNumber( tCase.getFullName(), techMessage ) );
595 }
596 sink.text(
597 tCase.getFullClassName() + ":" + getErrorLineNumber( tCase.getFullName(), techMessage ) );
598
599 if ( xrefLocation != null )
600 {
601 sink.link_();
602 }
603 sink.rawText( "</div>" );
604
605 sink.tableCell_();
606
607 sink.tableRow_();
608 }
609 }
610
611 sink.table_();
612 }
613
614 sinkLineBreak( sink );
615
616 sink.section1_();
617 }
618
619 private String getErrorLineNumber( String className, String source )
620 {
621 StringTokenizer tokenizer = new StringTokenizer( source );
622
623 String lineNo = "";
624
625 while ( tokenizer.hasMoreTokens() )
626 {
627 String token = tokenizer.nextToken();
628 if ( token.startsWith( className ) )
629 {
630 int idx = token.indexOf( ":" );
631 lineNo = token.substring( idx + 1, token.indexOf( ")" ) );
632 break;
633 }
634 }
635 return lineNo;
636 }
637
638 private void constructHotLinks( Sink sink, ResourceBundle bundle )
639 {
640 if ( !testSuites.isEmpty() )
641 {
642 sink.paragraph();
643
644 sink.text( "[" );
645 sinkLink( sink, bundle.getString( "report.surefire.label.summary" ), "#Summary" );
646 sink.text( "]" );
647
648 sink.text( " [" );
649 sinkLink( sink, bundle.getString( "report.surefire.label.packagelist" ), "#Package_List" );
650 sink.text( "]" );
651
652 sink.text( " [" );
653 sinkLink( sink, bundle.getString( "report.surefire.label.testcases" ), "#Test_Cases" );
654 sink.text( "]" );
655
656 sink.paragraph_();
657 }
658 }
659
660 private void sinkLineBreak( Sink sink )
661 {
662 sink.lineBreak();
663 }
664
665 private void sinkIcon( String type, Sink sink )
666 {
667 sink.figure();
668
669 if ( type.startsWith( "junit.framework" ) || "skipped".equals( type ) )
670 {
671 sink.figureGraphics( "images/icon_warning_sml.gif" );
672 }
673 else if ( type.startsWith( "success" ) )
674 {
675 sink.figureGraphics( "images/icon_success_sml.gif" );
676 }
677 else
678 {
679 sink.figureGraphics( "images/icon_error_sml.gif" );
680 }
681
682 sink.figure_();
683 }
684
685 private void sinkHeader( Sink sink, String header )
686 {
687 sink.tableHeaderCell();
688 sink.text( header );
689 sink.tableHeaderCell_();
690 }
691
692 private void sinkCell( Sink sink, String text )
693 {
694 sink.tableCell();
695 sink.text( text );
696 sink.tableCell_();
697 }
698
699 private void sinkLink( Sink sink, String text, String link )
700 {
701 sink.link( link );
702 sink.text( text );
703 sink.link_();
704 }
705
706 private void sinkCellLink( Sink sink, String text, String link )
707 {
708 sink.tableCell();
709 sinkLink( sink, text, link );
710 sink.tableCell_();
711 }
712
713 private void sinkCellAnchor( Sink sink, String text, String anchor )
714 {
715 sink.tableCell();
716 sinkAnchor( sink, anchor );
717 sink.text( text );
718 sink.tableCell_();
719 }
720
721 private void sinkAnchor( Sink sink, String anchor )
722 {
723 sink.anchor( anchor );
724 sink.anchor_();
725 }
726 }