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