• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                         + " &#8212; failed (configuration methods)",
225                         testResult.getFailedConfigurationResults(), "failed",
226                         scenarioIndex);
227                 scenarioIndex += writeScenarioSummary(testName
228                         + " &#8212; failed", testResult.getFailedTestResults(),
229                         "failed", scenarioIndex);
230                 scenarioIndex += writeScenarioSummary(testName
231                         + " &#8212; skipped (configuration methods)",
232                         testResult.getSkippedConfigurationResults(), "skipped",
233                         scenarioIndex);
234                 scenarioIndex += writeScenarioSummary(testName
235                         + " &#8212; skipped",
236                         testResult.getSkippedTestResults(), "skipped",
237                         scenarioIndex);
238                 scenarioIndex += writeScenarioSummary(testName
239                         + " &#8212; 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