001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.geronimo.mavenplugins.testsuite.report;
019
020 import org.apache.maven.reporting.MavenReportException;
021 import org.codehaus.doxia.sink.Sink;
022
023 import java.io.File;
024 import java.text.NumberFormat;
025 import java.util.Iterator;
026 import java.util.List;
027 import java.util.ListIterator;
028 import java.util.Locale;
029 import java.util.Map;
030 import java.util.ResourceBundle;
031 import java.util.StringTokenizer;
032
033 public class SurefireReportGenerator
034 {
035 private SurefireReportParser report;
036
037 private List testSuites;
038
039 private boolean showSuccess;
040
041 private String xrefLocation;
042
043 public SurefireReportGenerator( File reportsDirectory, Locale locale, boolean showSuccess, String xrefLocation )
044 {
045 report = new SurefireReportParser( reportsDirectory, locale );
046
047 this.xrefLocation = xrefLocation;
048
049 this.showSuccess = showSuccess;
050 }
051
052 public void doGenerateReport( ResourceBundle bundle, Sink sink )
053 throws MavenReportException
054 {
055 testSuites = report.parseXMLReportFiles();
056
057 sink.head();
058
059 sink.text( bundle.getString( "report.surefire.description" ) );
060
061 StringBuffer str = new StringBuffer();
062 str.append( "<script type=\"text/javascript\">\n" );
063 str.append( "function toggleDisplay(elementId) {\n" );
064 str.append( " var elm = document.getElementById(elementId + 'error');\n" );
065 str.append( " if (elm && typeof elm.style != \"undefined\") {\n" );
066 str.append( " if (elm.style.display == \"none\") {\n" );
067 str.append( " elm.style.display = \"\";\n" );
068 str.append( " document.getElementById(elementId + 'off').style.display = \"none\";\n" );
069 str.append( " document.getElementById(elementId + 'on').style.display = \"inline\";\n" );
070 str.append( " }" );
071 str.append( " else if (elm.style.display == \"\") {" );
072 str.append( " elm.style.display = \"none\";\n" );
073 str.append( " document.getElementById(elementId + 'off').style.display = \"inline\";\n" );
074 str.append( " document.getElementById(elementId + 'on').style.display = \"none\";\n" );
075 str.append( " } \n" );
076 str.append( " } \n" );
077 str.append( " }\n" );
078 str.append( "</script>" );
079 sink.rawText( str.toString() );
080
081 sink.head_();
082
083 sink.body();
084
085 constructSummarySection( bundle, sink );
086
087 Map suitePackages = report.getSuitesGroupByPackage( testSuites );
088 if ( !suitePackages.isEmpty() )
089 {
090 constructPackagesSection( bundle, sink, suitePackages );
091 }
092
093 if ( !testSuites.isEmpty() )
094 {
095 constructTestCasesSection( bundle, sink );
096 }
097
098 List failureList = report.getFailureDetails( testSuites );
099 if ( !failureList.isEmpty() )
100 {
101 constructFailureDetails( sink, bundle, failureList );
102 }
103
104 sinkLineBreak( sink );
105
106 sink.body_();
107
108 sink.flush();
109
110 sink.close();
111 }
112
113 private void constructSummarySection( ResourceBundle bundle, Sink sink )
114 {
115 Map summary = report.getSummary( testSuites );
116
117 sink.sectionTitle1();
118
119 sinkAnchor( sink, "Summary" );
120
121 sink.text( bundle.getString( "report.surefire.label.summary" ) );
122
123 sink.sectionTitle1_();
124
125 constructHotLinks( sink, bundle );
126
127 sinkLineBreak( sink );
128
129 sink.table();
130
131 sink.tableRow();
132
133 sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
134
135 sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
136
137 sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
138
139 sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
140
141 sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
142
143 sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
144
145 sink.tableRow_();
146
147 sink.tableRow();
148
149 sinkCell( sink, (String) summary.get( "totalTests" ) );
150
151 sinkCell( sink, (String) summary.get( "totalErrors" ) );
152
153 sinkCell( sink, (String) summary.get( "totalFailures" ) );
154
155 sinkCell( sink, (String) summary.get( "totalSkipped" ) );
156
157 sinkCell( sink, summary.get( "totalPercentage" ) + "%" );
158
159 sinkCell( sink, (String) summary.get( "totalElapsedTime" ) );
160
161 sink.tableRow_();
162
163 sink.table_();
164
165 sink.lineBreak();
166
167 sink.rawText( bundle.getString( "report.surefire.text.note1" ) );
168
169 sinkLineBreak( sink );
170 }
171
172 private void constructPackagesSection( ResourceBundle bundle, Sink sink, Map suitePackages )
173 {
174 NumberFormat numberFormat = report.getNumberFormat();
175
176 sink.sectionTitle1();
177
178 sinkAnchor( sink, "Package_List" );
179
180 sink.text( bundle.getString( "report.surefire.label.packagelist" ) );
181
182 sink.sectionTitle1_();
183
184 constructHotLinks( sink, bundle );
185
186 sinkLineBreak( sink );
187
188 sink.table();
189
190 sink.tableRow();
191
192 sinkHeader( sink, bundle.getString( "report.surefire.label.package" ) );
193
194 sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
195
196 sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
197
198 sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
199
200 sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
201
202 sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
203
204 sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
205
206 sink.tableRow_();
207
208 Iterator packIter = suitePackages.keySet().iterator();
209
210 while ( packIter.hasNext() )
211 {
212 sink.tableRow();
213
214 String packageName = (String) packIter.next();
215
216 List testSuiteList = (List) suitePackages.get( packageName );
217
218 Map packageSummary = report.getSummary( testSuiteList );
219
220 sinkCellLink( sink, packageName, getLink(packageName) );
221
222 sinkCell( sink, (String) packageSummary.get( "totalTests" ) );
223
224 sinkCell( sink, (String) packageSummary.get( "totalErrors" ) );
225
226 sinkCell( sink, (String) packageSummary.get( "totalFailures" ) );
227
228 sinkCell( sink, (String) packageSummary.get( "totalSkipped" ) );
229
230 sinkCell( sink, packageSummary.get( "totalPercentage" ) + "%" );
231
232 sinkCell( sink, (String) packageSummary.get( "totalElapsedTime" ) );
233
234 sink.tableRow_();
235 }
236
237 sink.table_();
238
239 sink.lineBreak();
240
241 sink.rawText( bundle.getString( "report.surefire.text.note2" ) );
242
243 packIter = suitePackages.keySet().iterator();
244
245 while ( packIter.hasNext() )
246 {
247 String packageName = (String) packIter.next();
248
249 if (packageName.endsWith("#")) {
250 continue;
251 }
252
253 List testSuiteList = (List) suitePackages.get( packageName );
254
255 Iterator suiteIterator = testSuiteList.iterator();
256
257 sink.sectionTitle2();
258
259 sinkAnchor( sink, packageName );
260
261 sink.text( packageName );
262
263 sink.sectionTitle2_();
264
265 sink.table();
266
267 sink.tableRow();
268
269 sinkHeader( sink, "" );
270
271 sinkHeader( sink, bundle.getString( "report.surefire.label.class" ) );
272
273 sinkHeader( sink, bundle.getString( "report.surefire.label.tests" ) );
274
275 sinkHeader( sink, bundle.getString( "report.surefire.label.errors" ) );
276
277 sinkHeader( sink, bundle.getString( "report.surefire.label.failures" ) );
278
279 sinkHeader( sink, bundle.getString( "report.surefire.label.skipped" ) );
280
281 sinkHeader( sink, bundle.getString( "report.surefire.label.successrate" ) );
282
283 sinkHeader( sink, bundle.getString( "report.surefire.label.time" ) );
284
285 sink.tableRow_();
286
287 while ( suiteIterator.hasNext() )
288 {
289 ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
290
291 if ( showSuccess || suite.getNumberOfErrors() != 0 || suite.getNumberOfFailures() != 0 )
292 {
293
294 sink.tableRow();
295
296 sink.tableCell();
297
298 sink.link( "#" + suite.getPackageName() + suite.getName() );
299
300 if ( suite.getNumberOfErrors() > 0 )
301 {
302 sinkIcon( "error", sink );
303 }
304 else if ( suite.getNumberOfFailures() > 0 )
305 {
306 sinkIcon( "junit.framework", sink );
307 }
308 else
309 {
310 sinkIcon( "success", sink );
311 }
312
313 sink.link_();
314
315 sink.tableCell_();
316
317 sinkCellLink( sink, suite.getName(), "#" + suite.getPackageName() + suite.getName() );
318
319 sinkCell( sink, Integer.toString( suite.getNumberOfTests() ) );
320
321 sinkCell( sink, Integer.toString( suite.getNumberOfErrors() ) );
322
323 sinkCell( sink, Integer.toString( suite.getNumberOfFailures() ) );
324
325 sinkCell( sink, Integer.toString( suite.getNumberOfSkipped() ) );
326
327 String percentage = report.computePercentage( suite.getNumberOfTests(), suite.getNumberOfErrors(),
328 suite.getNumberOfFailures(), suite
329 .getNumberOfSkipped() );
330 sinkCell( sink, percentage + "%" );
331
332 sinkCell( sink, numberFormat.format( suite.getTimeElapsed() ) );
333
334 sink.tableRow_();
335 }
336 }
337
338 sink.table_();
339 }
340
341 sinkLineBreak( sink );
342 }
343
344 private void constructTestCasesSection( ResourceBundle bundle, Sink sink )
345 {
346 NumberFormat numberFormat = report.getNumberFormat();
347
348 sink.sectionTitle1();
349
350 sinkAnchor( sink, "Test_Cases" );
351
352 sink.text( bundle.getString( "report.surefire.label.testcases" ) );
353
354 sink.sectionTitle1_();
355
356 constructHotLinks( sink, bundle );
357
358 ListIterator suiteIterator = testSuites.listIterator();
359
360 while ( suiteIterator.hasNext() )
361 {
362 ReportTestSuite suite = (ReportTestSuite) suiteIterator.next();
363 if ( suite.getPackageName().endsWith("#") ) {
364 continue;
365 }
366
367 List testCases = suite.getTestCases();
368
369 if ( testCases != null )
370 {
371 ListIterator caseIterator = testCases.listIterator();
372
373 sink.sectionTitle2();
374
375 sinkAnchor( sink, suite.getPackageName() + suite.getName() );
376
377 sink.text( suite.getName() );
378
379 sink.sectionTitle2_();
380
381 sink.table();
382
383 while ( caseIterator.hasNext() )
384 {
385 ReportTestCase testCase = (ReportTestCase) caseIterator.next();
386
387 if ( testCase.getFailure() != null || showSuccess )
388 {
389 sink.tableRow();
390
391 sink.tableCell();
392
393 Map failure = testCase.getFailure();
394
395 if ( failure != null )
396 {
397 sink.link( "#" + testCase.getFullName() );
398
399 sinkIcon( (String) failure.get( "type" ), sink );
400
401 sink.link_();
402 }
403 else
404 {
405 sinkIcon( "success", sink );
406 }
407
408 sink.tableCell_();
409
410 if ( failure != null )
411 {
412 sink.tableCell();
413
414 sinkLink( sink, testCase.getName(), "#" + testCase.getFullName() );
415
416 sink.rawText( " <div class=\"detailToggle\" style=\"display:inline\">" );
417
418 sink.link( "javascript:toggleDisplay('" + testCase.getName() + "');" );
419
420 sink.rawText( "<span style=\"display: inline;\" " + "id=\"" + testCase.getName() +
421 "off\">+</span><span id=\"" + testCase.getName() + "on\" " +
422 "style=\"display: none;\">-</span> " );
423 sink.text( "[ Detail ]" );
424 sink.link_();
425
426 sink.rawText( "</div>" );
427
428 sink.tableCell_();
429 }
430 else
431 {
432 sinkCell( sink, testCase.getName() );
433 }
434
435 sinkCell( sink, numberFormat.format( testCase.getTime() ) );
436
437 sink.tableRow_();
438
439 if ( failure != null )
440 {
441 sink.tableRow();
442
443 sinkCell( sink, "" );
444 sinkCell( sink, (String) failure.get( "message" ) );
445 sinkCell( sink, "" );
446 sink.tableRow_();
447
448 List detail = (List) failure.get( "detail" );
449 if ( detail != null )
450 {
451
452 sink.tableRow();
453 sinkCell( sink, "" );
454
455 sink.tableCell();
456 sink.rawText(
457 " <div id=\"" + testCase.getName() + "error\" style=\"display:none;\">" );
458
459 Iterator it = detail.iterator();
460
461 sink.verbatim( true );
462 while ( it.hasNext() )
463 {
464 sink.text( it.next().toString() );
465 sink.lineBreak();
466 }
467 sink.verbatim_();
468
469 sink.rawText( "</div>" );
470 sink.tableCell_();
471
472 sinkCell( sink, "" );
473
474 sink.tableRow_();
475 }
476 }
477 }
478 }
479
480 sink.table_();
481 }
482 }
483
484 sinkLineBreak( sink );
485 }
486
487 private void constructFailureDetails( Sink sink, ResourceBundle bundle, List failureList )
488 {
489 Iterator failIter = failureList.iterator();
490
491 if ( failIter != null )
492 {
493 sink.sectionTitle1();
494
495 sinkAnchor( sink, "Failure_Details" );
496
497 sink.text( bundle.getString( "report.surefire.label.failuredetails" ) );
498
499 sink.sectionTitle1_();
500
501 constructHotLinks( sink, bundle );
502
503 sinkLineBreak( sink );
504
505 sink.table();
506
507 while ( failIter.hasNext() )
508 {
509 ReportTestCase tCase = (ReportTestCase) failIter.next();
510
511 Map failure = tCase.getFailure();
512
513 sink.tableRow();
514
515 sink.tableCell();
516
517 String type = (String) failure.get( "type" );
518 sinkIcon( type, sink );
519
520 sink.tableCell_();
521
522 sinkCellAnchor( sink, tCase.getName(), tCase.getFullName() );
523
524 sink.tableRow_();
525
526 String message = (String) failure.get( "message" );
527
528 sink.tableRow();
529
530 sinkCell( sink, "" );
531
532 StringBuffer sb = new StringBuffer();
533 sb.append( type );
534
535 if ( message != null )
536 {
537 sb.append( ": " );
538 sb.append( message );
539 }
540
541 sinkCell( sink, sb.toString() );
542
543 sink.tableRow_();
544
545 List detail = (List) failure.get( "detail" );
546 if ( detail != null )
547 {
548 Iterator it = detail.iterator();
549
550 boolean firstLine = true;
551
552 String techMessage = "";
553 while ( it.hasNext() )
554 {
555 techMessage = it.next().toString();
556 if ( firstLine )
557 {
558 firstLine = false;
559 }
560 else
561 {
562 sink.text( " " );
563 }
564 }
565
566 sink.tableRow();
567
568 sinkCell( sink, "" );
569
570 sink.tableCell();
571 sink.rawText( " <div id=\"" + tCase.getName() + "error\" >" );
572
573 if ( xrefLocation != null )
574 {
575 String path = tCase.getFullClassName().replace( '.', '/' );
576
577 sink.link( xrefLocation + "/" + path + ".html#" +
578 getErrorLineNumber( tCase.getFullName(), techMessage ) );
579 }
580 sink.text(
581 tCase.getFullClassName() + ":" + getErrorLineNumber( tCase.getFullName(), techMessage ) );
582
583 if ( xrefLocation != null )
584 {
585 sink.link_();
586 }
587 sink.rawText( "</div>" );
588
589 sink.tableCell_();
590
591 sink.tableRow_();
592 }
593 }
594
595 sink.table_();
596 }
597
598 sinkLineBreak( sink );
599 }
600
601 private String getErrorLineNumber( String className, String source )
602 {
603 StringTokenizer tokenizer = new StringTokenizer( source );
604
605 String lineNo = "";
606
607 while ( tokenizer.hasMoreTokens() )
608 {
609 String token = tokenizer.nextToken();
610 if ( token.startsWith( className ) )
611 {
612 int idx = token.indexOf( ":" );
613 lineNo = token.substring( idx + 1, token.indexOf( ")" ) );
614 break;
615 }
616 }
617 return lineNo;
618 }
619
620 private void constructHotLinks( Sink sink, ResourceBundle bundle )
621 {
622 if ( !testSuites.isEmpty() )
623 {
624 sink.section2();
625
626 sink.rawText( "[" );
627 sinkLink( sink, bundle.getString( "report.surefire.label.summary" ), "#Summary" );
628 sink.rawText( "]" );
629
630 sink.rawText( "[" );
631 sinkLink( sink, bundle.getString( "report.surefire.label.packagelist" ), "#Package_List" );
632 sink.rawText( "]" );
633
634 sink.rawText( "[" );
635 sinkLink( sink, bundle.getString( "report.surefire.label.testcases" ), "#Test_Cases" );
636 sink.rawText( "]" );
637 sink.section2_();
638 }
639 }
640
641 private void sinkLineBreak( Sink sink )
642 {
643 sink.table();
644 sink.tableRow();
645 sink.tableRow_();
646 sink.tableRow();
647 sink.tableRow_();
648 sink.table_();
649 }
650
651 private void sinkIcon( String type, Sink sink )
652 {
653 sink.figure();
654
655 if ( type.startsWith( "junit.framework" ) )
656 {
657 sink.figureGraphics( "images/icon_warning_sml.gif" );
658 }
659 else if ( type.startsWith( "success" ) )
660 {
661 sink.figureGraphics( "images/icon_success_sml.gif" );
662 }
663 else
664 {
665 sink.figureGraphics( "images/icon_error_sml.gif" );
666 }
667
668 sink.figure_();
669 }
670
671 private void sinkHeader( Sink sink, String header )
672 {
673 sink.tableHeaderCell();
674 sink.text( header );
675 sink.tableHeaderCell_();
676 }
677
678 private void sinkCell( Sink sink, String text )
679 {
680 sink.tableCell();
681 sink.text( text );
682 sink.tableCell_();
683 }
684
685 private void sinkLink( Sink sink, String text, String link )
686 {
687 sink.link( link );
688 sink.text( text );
689 sink.link_();
690 }
691
692 private void sinkCellLink( Sink sink, String text, String link )
693 {
694 sink.tableCell();
695 sinkLink( sink, text, link );
696 sink.tableCell_();
697 }
698
699 private void sinkCellAnchor( Sink sink, String text, String anchor )
700 {
701 sink.tableCell();
702 sinkAnchor( sink, anchor );
703 sink.text( text );
704 sink.tableCell_();
705 }
706
707 private void sinkAnchor( Sink sink, String anchor )
708 {
709 sink.anchor( anchor );
710 sink.anchor_();
711 }
712
713 private String getLink(String link) {
714 if ( link.endsWith("#") ) {
715 link = link.replace("#", "/");
716 link = link.replace("@", "/");
717 return link + "surefire-report.html";
718 }
719 return "#" + link;
720 }
721 }