1 package org.testng.reporters; 2 3 import java.io.BufferedWriter; 4 import java.io.File; 5 import java.io.FileWriter; 6 import java.io.IOException; 7 import java.io.PrintWriter; 8 import java.text.NumberFormat; 9 import java.util.Collections; 10 import java.util.Comparator; 11 import java.util.Iterator; 12 import java.util.List; 13 import java.util.Set; 14 15 import org.testng.IReporter; 16 import org.testng.ISuite; 17 import org.testng.ISuiteResult; 18 import org.testng.ITestContext; 19 import org.testng.ITestResult; 20 import org.testng.Reporter; 21 import org.testng.collections.Lists; 22 import org.testng.internal.Utils; 23 import org.testng.log4testng.Logger; 24 import org.testng.xml.XmlSuite; 25 26 /** 27 * Reporter that generates a single-page HTML report of the test results. 28 * <p> 29 * Based on an earlier implementation by Paul Mendelson. 30 * </p> 31 * 32 * @author Abraham Lin 33 */ 34 public class EmailableReporter2 implements IReporter { 35 private static final Logger LOG = Logger.getLogger(EmailableReporter.class); 36 37 protected PrintWriter writer; 38 39 protected List<SuiteResult> suiteResults = Lists.newArrayList(); 40 41 // Reusable buffer 42 private StringBuilder buffer = new StringBuilder(); 43 44 @Override generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory)45 public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, 46 String outputDirectory) { 47 try { 48 writer = createWriter(outputDirectory); 49 } catch (IOException e) { 50 LOG.error("Unable to create output file", e); 51 return; 52 } 53 for (ISuite suite : suites) { 54 suiteResults.add(new SuiteResult(suite)); 55 } 56 57 writeDocumentStart(); 58 writeHead(); 59 writeBody(); 60 writeDocumentEnd(); 61 62 writer.close(); 63 } 64 createWriter(String outdir)65 protected PrintWriter createWriter(String outdir) throws IOException { 66 new File(outdir).mkdirs(); 67 return new PrintWriter(new BufferedWriter(new FileWriter(new File( 68 outdir, "emailable-report.html")))); 69 } 70 writeDocumentStart()71 protected void writeDocumentStart() { 72 writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">"); 73 writer.print("<html xmlns=\"http://www.w3.org/1999/xhtml\">"); 74 } 75 writeHead()76 protected void writeHead() { 77 writer.print("<head>"); 78 writer.print("<title>TestNG Report</title>"); 79 writeStylesheet(); 80 writer.print("</head>"); 81 } 82 writeStylesheet()83 protected void writeStylesheet() { 84 writer.print("<style type=\"text/css\">"); 85 writer.print("table {margin-bottom:10px;border-collapse:collapse;empty-cells:show}"); 86 writer.print("th,td {border:1px solid #009;padding:.25em .5em}"); 87 writer.print("th {vertical-align:bottom}"); 88 writer.print("td {vertical-align:top}"); 89 writer.print("table a {font-weight:bold}"); 90 writer.print(".stripe td {background-color: #E6EBF9}"); 91 writer.print(".num {text-align:right}"); 92 writer.print(".passedodd td {background-color: #3F3}"); 93 writer.print(".passedeven td {background-color: #0A0}"); 94 writer.print(".skippedodd td {background-color: #DDD}"); 95 writer.print(".skippedeven td {background-color: #CCC}"); 96 writer.print(".failedodd td,.attn {background-color: #F33}"); 97 writer.print(".failedeven td,.stripe .attn {background-color: #D00}"); 98 writer.print(".stacktrace {white-space:pre;font-family:monospace}"); 99 writer.print(".totop {font-size:85%;text-align:center;border-bottom:2px solid #000}"); 100 writer.print("</style>"); 101 } 102 writeBody()103 protected void writeBody() { 104 writer.print("<body>"); 105 writeSuiteSummary(); 106 writeScenarioSummary(); 107 writeScenarioDetails(); 108 writer.print("</body>"); 109 } 110 writeDocumentEnd()111 protected void writeDocumentEnd() { 112 writer.print("</html>"); 113 } 114 writeSuiteSummary()115 protected void writeSuiteSummary() { 116 NumberFormat integerFormat = NumberFormat.getIntegerInstance(); 117 NumberFormat decimalFormat = NumberFormat.getNumberInstance(); 118 119 int totalPassedTests = 0; 120 int totalSkippedTests = 0; 121 int totalFailedTests = 0; 122 long totalDuration = 0; 123 124 writer.print("<table>"); 125 writer.print("<tr>"); 126 writer.print("<th>Test</th>"); 127 writer.print("<th># Passed</th>"); 128 writer.print("<th># Skipped</th>"); 129 writer.print("<th># Failed</th>"); 130 writer.print("<th>Time (ms)</th>"); 131 writer.print("<th>Included Groups</th>"); 132 writer.print("<th>Excluded Groups</th>"); 133 writer.print("</tr>"); 134 135 int testIndex = 0; 136 for (SuiteResult suiteResult : suiteResults) { 137 writer.print("<tr><th colspan=\"7\">"); 138 writer.print(Utils.escapeHtml(suiteResult.getSuiteName())); 139 writer.print("</th></tr>"); 140 141 for (TestResult testResult : suiteResult.getTestResults()) { 142 int passedTests = testResult.getPassedTestCount(); 143 int skippedTests = testResult.getSkippedTestCount(); 144 int failedTests = testResult.getFailedTestCount(); 145 long duration = testResult.getDuration(); 146 147 writer.print("<tr"); 148 if ((testIndex % 2) == 1) { 149 writer.print(" class=\"stripe\""); 150 } 151 writer.print(">"); 152 153 buffer.setLength(0); 154 writeTableData(buffer.append("<a href=\"#t").append(testIndex) 155 .append("\">") 156 .append(Utils.escapeHtml(testResult.getTestName())) 157 .append("</a>").toString()); 158 writeTableData(integerFormat.format(passedTests), "num"); 159 writeTableData(integerFormat.format(skippedTests), 160 (skippedTests > 0 ? "num attn" : "num")); 161 writeTableData(integerFormat.format(failedTests), 162 (failedTests > 0 ? "num attn" : "num")); 163 writeTableData(decimalFormat.format(duration), "num"); 164 writeTableData(testResult.getIncludedGroups()); 165 writeTableData(testResult.getExcludedGroups()); 166 167 writer.print("</tr>"); 168 169 totalPassedTests += passedTests; 170 totalSkippedTests += skippedTests; 171 totalFailedTests += failedTests; 172 totalDuration += duration; 173 174 testIndex++; 175 } 176 } 177 178 // Print totals if there was more than one test 179 if (testIndex > 1) { 180 writer.print("<tr>"); 181 writer.print("<th>Total</th>"); 182 writeTableHeader(integerFormat.format(totalPassedTests), "num"); 183 writeTableHeader(integerFormat.format(totalSkippedTests), 184 (totalSkippedTests > 0 ? "num attn" : "num")); 185 writeTableHeader(integerFormat.format(totalFailedTests), 186 (totalFailedTests > 0 ? "num attn" : "num")); 187 writeTableHeader(decimalFormat.format(totalDuration), "num"); 188 writer.print("<th colspan=\"2\"></th>"); 189 writer.print("</tr>"); 190 } 191 192 writer.print("</table>"); 193 } 194 195 /** 196 * Writes a summary of all the test scenarios. 197 */ writeScenarioSummary()198 protected void writeScenarioSummary() { 199 writer.print("<table>"); 200 writer.print("<thead>"); 201 writer.print("<tr>"); 202 writer.print("<th>Class</th>"); 203 writer.print("<th>Method</th>"); 204 writer.print("<th>Start</th>"); 205 writer.print("<th>Time (ms)</th>"); 206 writer.print("</tr>"); 207 writer.print("</thead>"); 208 209 int testIndex = 0; 210 int scenarioIndex = 0; 211 for (SuiteResult suiteResult : suiteResults) { 212 writer.print("<tbody><tr><th colspan=\"4\">"); 213 writer.print(Utils.escapeHtml(suiteResult.getSuiteName())); 214 writer.print("</th></tr></tbody>"); 215 216 for (TestResult testResult : suiteResult.getTestResults()) { 217 writer.print("<tbody id=\"t"); 218 writer.print(testIndex); 219 writer.print("\">"); 220 221 String testName = Utils.escapeHtml(testResult.getTestName()); 222 223 scenarioIndex += writeScenarioSummary(testName 224 + " — failed (configuration methods)", 225 testResult.getFailedConfigurationResults(), "failed", 226 scenarioIndex); 227 scenarioIndex += writeScenarioSummary(testName 228 + " — failed", testResult.getFailedTestResults(), 229 "failed", scenarioIndex); 230 scenarioIndex += writeScenarioSummary(testName 231 + " — skipped (configuration methods)", 232 testResult.getSkippedConfigurationResults(), "skipped", 233 scenarioIndex); 234 scenarioIndex += writeScenarioSummary(testName 235 + " — skipped", 236 testResult.getSkippedTestResults(), "skipped", 237 scenarioIndex); 238 scenarioIndex += writeScenarioSummary(testName 239 + " — passed", testResult.getPassedTestResults(), 240 "passed", scenarioIndex); 241 242 writer.print("</tbody>"); 243 244 testIndex++; 245 } 246 } 247 248 writer.print("</table>"); 249 } 250 251 /** 252 * Writes the scenario summary for the results of a given state for a single 253 * test. 254 */ writeScenarioSummary(String description, List<ClassResult> classResults, String cssClassPrefix, int startingScenarioIndex)255 private int writeScenarioSummary(String description, 256 List<ClassResult> classResults, String cssClassPrefix, 257 int startingScenarioIndex) { 258 int scenarioCount = 0; 259 if (!classResults.isEmpty()) { 260 writer.print("<tr><th colspan=\"4\">"); 261 writer.print(description); 262 writer.print("</th></tr>"); 263 264 int scenarioIndex = startingScenarioIndex; 265 int classIndex = 0; 266 for (ClassResult classResult : classResults) { 267 String cssClass = cssClassPrefix 268 + ((classIndex % 2) == 0 ? "even" : "odd"); 269 270 buffer.setLength(0); 271 272 int scenariosPerClass = 0; 273 int methodIndex = 0; 274 for (MethodResult methodResult : classResult.getMethodResults()) { 275 List<ITestResult> results = methodResult.getResults(); 276 int resultsCount = results.size(); 277 assert resultsCount > 0; 278 279 ITestResult firstResult = results.iterator().next(); 280 String methodName = Utils.escapeHtml(firstResult 281 .getMethod().getMethodName()); 282 long start = firstResult.getStartMillis(); 283 long duration = firstResult.getEndMillis() - start; 284 285 // The first method per class shares a row with the class 286 // header 287 if (methodIndex > 0) { 288 buffer.append("<tr class=\"").append(cssClass) 289 .append("\">"); 290 291 } 292 293 // Write the timing information with the first scenario per 294 // method 295 buffer.append("<td><a href=\"#m").append(scenarioIndex) 296 .append("\">").append(methodName) 297 .append("</a></td>").append("<td rowspan=\"") 298 .append(resultsCount).append("\">").append(start) 299 .append("</td>").append("<td rowspan=\"") 300 .append(resultsCount).append("\">") 301 .append(duration).append("</td></tr>"); 302 scenarioIndex++; 303 304 // Write the remaining scenarios for the method 305 for (int i = 1; i < resultsCount; i++) { 306 buffer.append("<tr class=\"").append(cssClass) 307 .append("\">").append("<td><a href=\"#m") 308 .append(scenarioIndex).append("\">") 309 .append(methodName).append("</a></td></tr>"); 310 scenarioIndex++; 311 } 312 313 scenariosPerClass += resultsCount; 314 methodIndex++; 315 } 316 317 // Write the test results for the class 318 writer.print("<tr class=\""); 319 writer.print(cssClass); 320 writer.print("\">"); 321 writer.print("<td rowspan=\""); 322 writer.print(scenariosPerClass); 323 writer.print("\">"); 324 writer.print(Utils.escapeHtml(classResult.getClassName())); 325 writer.print("</td>"); 326 writer.print(buffer); 327 328 classIndex++; 329 } 330 scenarioCount = scenarioIndex - startingScenarioIndex; 331 } 332 return scenarioCount; 333 } 334 335 /** 336 * Writes the details for all test scenarios. 337 */ writeScenarioDetails()338 protected void writeScenarioDetails() { 339 int scenarioIndex = 0; 340 for (SuiteResult suiteResult : suiteResults) { 341 for (TestResult testResult : suiteResult.getTestResults()) { 342 writer.print("<h2>"); 343 writer.print(Utils.escapeHtml(testResult.getTestName())); 344 writer.print("</h2>"); 345 346 scenarioIndex += writeScenarioDetails( 347 testResult.getFailedConfigurationResults(), 348 scenarioIndex); 349 scenarioIndex += writeScenarioDetails( 350 testResult.getFailedTestResults(), scenarioIndex); 351 scenarioIndex += writeScenarioDetails( 352 testResult.getSkippedConfigurationResults(), 353 scenarioIndex); 354 scenarioIndex += writeScenarioDetails( 355 testResult.getSkippedTestResults(), scenarioIndex); 356 scenarioIndex += writeScenarioDetails( 357 testResult.getPassedTestResults(), scenarioIndex); 358 } 359 } 360 } 361 362 /** 363 * Writes the scenario details for the results of a given state for a single 364 * test. 365 */ writeScenarioDetails(List<ClassResult> classResults, int startingScenarioIndex)366 private int writeScenarioDetails(List<ClassResult> classResults, 367 int startingScenarioIndex) { 368 int scenarioIndex = startingScenarioIndex; 369 for (ClassResult classResult : classResults) { 370 String className = classResult.getClassName(); 371 for (MethodResult methodResult : classResult.getMethodResults()) { 372 List<ITestResult> results = methodResult.getResults(); 373 assert !results.isEmpty(); 374 375 String label = Utils 376 .escapeHtml(className 377 + "#" 378 + results.iterator().next().getMethod() 379 .getMethodName()); 380 for (ITestResult result : results) { 381 writeScenario(scenarioIndex, label, result); 382 scenarioIndex++; 383 } 384 } 385 } 386 387 return scenarioIndex - startingScenarioIndex; 388 } 389 390 /** 391 * Writes the details for an individual test scenario. 392 */ writeScenario(int scenarioIndex, String label, ITestResult result)393 private void writeScenario(int scenarioIndex, String label, 394 ITestResult result) { 395 writer.print("<h3 id=\"m"); 396 writer.print(scenarioIndex); 397 writer.print("\">"); 398 writer.print(label); 399 writer.print("</h3>"); 400 401 writer.print("<table class=\"result\">"); 402 403 // Write test parameters (if any) 404 Object[] parameters = result.getParameters(); 405 int parameterCount = (parameters == null ? 0 : parameters.length); 406 if (parameterCount > 0) { 407 writer.print("<tr class=\"param\">"); 408 for (int i = 1; i <= parameterCount; i++) { 409 writer.print("<th>Parameter #"); 410 writer.print(i); 411 writer.print("</th>"); 412 } 413 writer.print("</tr><tr class=\"param stripe\">"); 414 for (Object parameter : parameters) { 415 writer.print("<td>"); 416 writer.print(Utils.escapeHtml(Utils.toString(parameter))); 417 writer.print("</td>"); 418 } 419 writer.print("</tr>"); 420 } 421 422 // Write reporter messages (if any) 423 List<String> reporterMessages = Reporter.getOutput(result); 424 if (!reporterMessages.isEmpty()) { 425 writer.print("<tr><th"); 426 if (parameterCount > 1) { 427 writer.print(" colspan=\""); 428 writer.print(parameterCount); 429 writer.print("\""); 430 } 431 writer.print(">Messages</th></tr>"); 432 433 writer.print("<tr><td"); 434 if (parameterCount > 1) { 435 writer.print(" colspan=\""); 436 writer.print(parameterCount); 437 writer.print("\""); 438 } 439 writer.print(">"); 440 writeReporterMessages(reporterMessages); 441 writer.print("</td></tr>"); 442 } 443 444 // Write exception (if any) 445 Throwable throwable = result.getThrowable(); 446 if (throwable != null) { 447 writer.print("<tr><th"); 448 if (parameterCount > 1) { 449 writer.print(" colspan=\""); 450 writer.print(parameterCount); 451 writer.print("\""); 452 } 453 writer.print(">"); 454 writer.print((result.getStatus() == ITestResult.SUCCESS ? "Expected Exception" 455 : "Exception")); 456 writer.print("</th></tr>"); 457 458 writer.print("<tr><td"); 459 if (parameterCount > 1) { 460 writer.print(" colspan=\""); 461 writer.print(parameterCount); 462 writer.print("\""); 463 } 464 writer.print(">"); 465 writeStackTrace(throwable); 466 writer.print("</td></tr>"); 467 } 468 469 writer.print("</table>"); 470 writer.print("<p class=\"totop\"><a href=\"#summary\">back to summary</a></p>"); 471 } 472 writeReporterMessages(List<String> reporterMessages)473 protected void writeReporterMessages(List<String> reporterMessages) { 474 writer.print("<div class=\"messages\">"); 475 Iterator<String> iterator = reporterMessages.iterator(); 476 assert iterator.hasNext(); 477 writer.print(Utils.escapeHtml(iterator.next())); 478 while (iterator.hasNext()) { 479 writer.print("<br/>"); 480 writer.print(Utils.escapeHtml(iterator.next())); 481 } 482 writer.print("</div>"); 483 } 484 writeStackTrace(Throwable throwable)485 protected void writeStackTrace(Throwable throwable) { 486 writer.print("<div class=\"stacktrace\">"); 487 writer.print(Utils.stackTrace(throwable, true)[0]); 488 writer.print("</div>"); 489 } 490 491 /** 492 * Writes a TH element with the specified contents and CSS class names. 493 * 494 * @param html 495 * the HTML contents 496 * @param cssClasses 497 * the space-delimited CSS classes or null if there are no 498 * classes to apply 499 */ writeTableHeader(String html, String cssClasses)500 protected void writeTableHeader(String html, String cssClasses) { 501 writeTag("th", html, cssClasses); 502 } 503 504 /** 505 * Writes a TD element with the specified contents. 506 * 507 * @param html 508 * the HTML contents 509 */ writeTableData(String html)510 protected void writeTableData(String html) { 511 writeTableData(html, null); 512 } 513 514 /** 515 * Writes a TD element with the specified contents and CSS class names. 516 * 517 * @param html 518 * the HTML contents 519 * @param cssClasses 520 * the space-delimited CSS classes or null if there are no 521 * classes to apply 522 */ writeTableData(String html, String cssClasses)523 protected void writeTableData(String html, String cssClasses) { 524 writeTag("td", html, cssClasses); 525 } 526 527 /** 528 * Writes an arbitrary HTML element with the specified contents and CSS 529 * class names. 530 * 531 * @param tag 532 * the tag name 533 * @param html 534 * the HTML contents 535 * @param cssClasses 536 * the space-delimited CSS classes or null if there are no 537 * classes to apply 538 */ writeTag(String tag, String html, String cssClasses)539 protected void writeTag(String tag, String html, String cssClasses) { 540 writer.print("<"); 541 writer.print(tag); 542 if (cssClasses != null) { 543 writer.print(" class=\""); 544 writer.print(cssClasses); 545 writer.print("\""); 546 } 547 writer.print(">"); 548 writer.print(html); 549 writer.print("</"); 550 writer.print(tag); 551 writer.print(">"); 552 } 553 554 /** 555 * Groups {@link TestResult}s by suite. 556 */ 557 protected static class SuiteResult { 558 private final String suiteName; 559 private final List<TestResult> testResults = Lists.newArrayList(); 560 SuiteResult(ISuite suite)561 public SuiteResult(ISuite suite) { 562 suiteName = suite.getName(); 563 for (ISuiteResult suiteResult : suite.getResults().values()) { 564 testResults.add(new TestResult(suiteResult.getTestContext())); 565 } 566 } 567 getSuiteName()568 public String getSuiteName() { 569 return suiteName; 570 } 571 572 /** 573 * @return the test results (possibly empty) 574 */ getTestResults()575 public List<TestResult> getTestResults() { 576 return testResults; 577 } 578 } 579 580 /** 581 * Groups {@link ClassResult}s by test, type (configuration or test), and 582 * status. 583 */ 584 protected static class TestResult { 585 /** 586 * Orders test results by class name and then by method name (in 587 * lexicographic order). 588 */ 589 protected static final Comparator<ITestResult> RESULT_COMPARATOR = new Comparator<ITestResult>() { 590 @Override 591 public int compare(ITestResult o1, ITestResult o2) { 592 int result = o1.getTestClass().getName() 593 .compareTo(o2.getTestClass().getName()); 594 if (result == 0) { 595 result = o1.getMethod().getMethodName() 596 .compareTo(o2.getMethod().getMethodName()); 597 } 598 return result; 599 } 600 }; 601 602 private final String testName; 603 private final List<ClassResult> failedConfigurationResults; 604 private final List<ClassResult> failedTestResults; 605 private final List<ClassResult> skippedConfigurationResults; 606 private final List<ClassResult> skippedTestResults; 607 private final List<ClassResult> passedTestResults; 608 private final int failedTestCount; 609 private final int skippedTestCount; 610 private final int passedTestCount; 611 private final long duration; 612 private final String includedGroups; 613 private final String excludedGroups; 614 TestResult(ITestContext context)615 public TestResult(ITestContext context) { 616 testName = context.getName(); 617 618 Set<ITestResult> failedConfigurations = context 619 .getFailedConfigurations().getAllResults(); 620 Set<ITestResult> failedTests = context.getFailedTests() 621 .getAllResults(); 622 Set<ITestResult> skippedConfigurations = context 623 .getSkippedConfigurations().getAllResults(); 624 Set<ITestResult> skippedTests = context.getSkippedTests() 625 .getAllResults(); 626 Set<ITestResult> passedTests = context.getPassedTests() 627 .getAllResults(); 628 629 failedConfigurationResults = groupResults(failedConfigurations); 630 failedTestResults = groupResults(failedTests); 631 skippedConfigurationResults = groupResults(skippedConfigurations); 632 skippedTestResults = groupResults(skippedTests); 633 passedTestResults = groupResults(passedTests); 634 635 failedTestCount = failedTests.size(); 636 skippedTestCount = skippedTests.size(); 637 passedTestCount = passedTests.size(); 638 639 duration = context.getEndDate().getTime() 640 - context.getStartDate().getTime(); 641 642 includedGroups = formatGroups(context.getIncludedGroups()); 643 excludedGroups = formatGroups(context.getExcludedGroups()); 644 } 645 646 /** 647 * Groups test results by method and then by class. 648 */ groupResults(Set<ITestResult> results)649 protected List<ClassResult> groupResults(Set<ITestResult> results) { 650 List<ClassResult> classResults = Lists.newArrayList(); 651 if (!results.isEmpty()) { 652 List<MethodResult> resultsPerClass = Lists.newArrayList(); 653 List<ITestResult> resultsPerMethod = Lists.newArrayList(); 654 655 List<ITestResult> resultsList = Lists.newArrayList(results); 656 Collections.sort(resultsList, RESULT_COMPARATOR); 657 Iterator<ITestResult> resultsIterator = resultsList.iterator(); 658 assert resultsIterator.hasNext(); 659 660 ITestResult result = resultsIterator.next(); 661 resultsPerMethod.add(result); 662 663 String previousClassName = result.getTestClass().getName(); 664 String previousMethodName = result.getMethod().getMethodName(); 665 while (resultsIterator.hasNext()) { 666 result = resultsIterator.next(); 667 668 String className = result.getTestClass().getName(); 669 if (!previousClassName.equals(className)) { 670 // Different class implies different method 671 assert !resultsPerMethod.isEmpty(); 672 resultsPerClass.add(new MethodResult(resultsPerMethod)); 673 resultsPerMethod = Lists.newArrayList(); 674 675 assert !resultsPerClass.isEmpty(); 676 classResults.add(new ClassResult(previousClassName, 677 resultsPerClass)); 678 resultsPerClass = Lists.newArrayList(); 679 680 previousClassName = className; 681 previousMethodName = result.getMethod().getMethodName(); 682 } else { 683 String methodName = result.getMethod().getMethodName(); 684 if (!previousMethodName.equals(methodName)) { 685 assert !resultsPerMethod.isEmpty(); 686 resultsPerClass.add(new MethodResult(resultsPerMethod)); 687 resultsPerMethod = Lists.newArrayList(); 688 689 previousMethodName = methodName; 690 } 691 } 692 resultsPerMethod.add(result); 693 } 694 assert !resultsPerMethod.isEmpty(); 695 resultsPerClass.add(new MethodResult(resultsPerMethod)); 696 assert !resultsPerClass.isEmpty(); 697 classResults.add(new ClassResult(previousClassName, 698 resultsPerClass)); 699 } 700 return classResults; 701 } 702 getTestName()703 public String getTestName() { 704 return testName; 705 } 706 707 /** 708 * @return the results for failed configurations (possibly empty) 709 */ getFailedConfigurationResults()710 public List<ClassResult> getFailedConfigurationResults() { 711 return failedConfigurationResults; 712 } 713 714 /** 715 * @return the results for failed tests (possibly empty) 716 */ getFailedTestResults()717 public List<ClassResult> getFailedTestResults() { 718 return failedTestResults; 719 } 720 721 /** 722 * @return the results for skipped configurations (possibly empty) 723 */ getSkippedConfigurationResults()724 public List<ClassResult> getSkippedConfigurationResults() { 725 return skippedConfigurationResults; 726 } 727 728 /** 729 * @return the results for skipped tests (possibly empty) 730 */ getSkippedTestResults()731 public List<ClassResult> getSkippedTestResults() { 732 return skippedTestResults; 733 } 734 735 /** 736 * @return the results for passed tests (possibly empty) 737 */ getPassedTestResults()738 public List<ClassResult> getPassedTestResults() { 739 return passedTestResults; 740 } 741 getFailedTestCount()742 public int getFailedTestCount() { 743 return failedTestCount; 744 } 745 getSkippedTestCount()746 public int getSkippedTestCount() { 747 return skippedTestCount; 748 } 749 getPassedTestCount()750 public int getPassedTestCount() { 751 return passedTestCount; 752 } 753 getDuration()754 public long getDuration() { 755 return duration; 756 } 757 getIncludedGroups()758 public String getIncludedGroups() { 759 return includedGroups; 760 } 761 getExcludedGroups()762 public String getExcludedGroups() { 763 return excludedGroups; 764 } 765 766 /** 767 * Formats an array of groups for display. 768 */ formatGroups(String[] groups)769 protected String formatGroups(String[] groups) { 770 if (groups.length == 0) { 771 return ""; 772 } 773 774 StringBuilder builder = new StringBuilder(); 775 builder.append(groups[0]); 776 for (int i = 1; i < groups.length; i++) { 777 builder.append(", ").append(groups[i]); 778 } 779 return builder.toString(); 780 } 781 } 782 783 /** 784 * Groups {@link MethodResult}s by class. 785 */ 786 protected static class ClassResult { 787 private final String className; 788 private final List<MethodResult> methodResults; 789 790 /** 791 * @param className 792 * the class name 793 * @param methodResults 794 * the non-null, non-empty {@link MethodResult} list 795 */ ClassResult(String className, List<MethodResult> methodResults)796 public ClassResult(String className, List<MethodResult> methodResults) { 797 this.className = className; 798 this.methodResults = methodResults; 799 } 800 getClassName()801 public String getClassName() { 802 return className; 803 } 804 805 /** 806 * @return the non-null, non-empty {@link MethodResult} list 807 */ getMethodResults()808 public List<MethodResult> getMethodResults() { 809 return methodResults; 810 } 811 } 812 813 /** 814 * Groups test results by method. 815 */ 816 protected static class MethodResult { 817 private final List<ITestResult> results; 818 819 /** 820 * @param results 821 * the non-null, non-empty result list 822 */ MethodResult(List<ITestResult> results)823 public MethodResult(List<ITestResult> results) { 824 this.results = results; 825 } 826 827 /** 828 * @return the non-null, non-empty result list 829 */ getResults()830 public List<ITestResult> getResults() { 831 return results; 832 } 833 } 834 } 835