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