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 }