org.testng.reporters.EmailableReporter2 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of testng Show documentation
Show all versions of testng Show documentation
A testing framework for the JVM
package org.testng.reporters;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.testng.IReporter;
import org.testng.ISuite;
import org.testng.ISuiteResult;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.collections.Lists;
import org.testng.internal.Utils;
import org.testng.log4testng.Logger;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlSuite.ParallelMode;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.newBufferedWriter;
/** Reporter that generates a single-page HTML report of the test results. */
public class EmailableReporter2 implements IReporter {
private static final Logger LOG = Logger.getLogger(EmailableReporter2.class);
protected PrintWriter writer;
protected final List suiteResults = Lists.newArrayList();
// Reusable buffer
private final StringBuilder buffer = new StringBuilder();
private String fileName = "emailable-report.html";
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
@Override
public void generateReport(
List xmlSuites, List suites, String outputDirectory) {
try {
writer = createWriter(outputDirectory);
} catch (IOException e) {
LOG.error("Unable to create output file", e);
return;
}
for (ISuite suite : suites) {
suiteResults.add(new SuiteResult(suite));
}
writeDocumentStart();
writeHead();
writeBody();
writeDocumentEnd();
writer.close();
}
protected PrintWriter createWriter(String outdir) throws IOException {
new File(outdir).mkdirs();
String jvmArg = RuntimeBehavior.getDefaultEmailableReport2Name();
if (jvmArg != null && !jvmArg.trim().isEmpty()) {
fileName = jvmArg;
}
return new PrintWriter(newBufferedWriter(new File(outdir, fileName).toPath(), UTF_8));
}
protected void writeDocumentStart() {
writer.println(
"");
writer.println("");
}
protected void writeHead() {
writer.println("");
writer.println("");
writer.println("TestNG Report ");
writeStylesheet();
writer.println("");
}
protected void writeStylesheet() {
writer.print("");
}
protected void writeBody() {
writer.println("");
writeSuiteSummary();
writeScenarioSummary();
writeScenarioDetails();
writer.println("");
}
protected void writeDocumentEnd() {
writer.println("");
}
protected void writeSuiteSummary() {
NumberFormat integerFormat = NumberFormat.getIntegerInstance();
NumberFormat decimalFormat = NumberFormat.getNumberInstance();
int totalPassedTests = 0;
int totalSkippedTests = 0;
int totalFailedTests = 0;
int totalRetriedTests = 0;
long totalDuration = 0;
writer.println("");
writer.print("");
writer.print("Test ");
writer.print("# Passed ");
writer.print("# Skipped ");
writer.print("# Retried ");
writer.print("# Failed ");
writer.print("Time (ms) ");
writer.print("Included Groups ");
writer.print("Excluded Groups ");
writer.println(" ");
int testIndex = 0;
for (SuiteResult suiteResult : suiteResults) {
writer.print("");
writer.print(Utils.escapeHtml(suiteResult.getSuiteName()));
writer.println(" ");
for (TestResult testResult : suiteResult.getTestResults()) {
int passedTests = testResult.getPassedTestCount();
int skippedTests = testResult.getSkippedTestCount();
int failedTests = testResult.getFailedTestCount();
int retriedTests = testResult.getRetriedTestCount();
long duration = testResult.getDuration();
writer.print("");
buffer.setLength(0);
writeTableData(
buffer
.append("")
.append(Utils.escapeHtml(testResult.getTestName()))
.append("")
.toString());
writeTableData(integerFormat.format(passedTests), "num");
writeTableData(integerFormat.format(skippedTests), (skippedTests > 0 ? "num attn" : "num"));
writeTableData(integerFormat.format(retriedTests), (retriedTests > 0 ? "num attn" : "num"));
writeTableData(integerFormat.format(failedTests), (failedTests > 0 ? "num attn" : "num"));
writeTableData(decimalFormat.format(duration), "num");
writeTableData(testResult.getIncludedGroups());
writeTableData(testResult.getExcludedGroups());
writer.println(" ");
totalPassedTests += passedTests;
totalSkippedTests += skippedTests;
totalFailedTests += failedTests;
totalRetriedTests += retriedTests;
totalDuration += duration;
testIndex++;
}
boolean testsInParallel = XmlSuite.ParallelMode.TESTS.equals(suiteResult.getParallelMode());
if (testsInParallel) {
Optional maxValue = suiteResult.testResults.stream()
.max(Comparator.comparing(TestResult::getDuration));
if (maxValue.isPresent()) {
totalDuration = Math.max(totalDuration, maxValue.get().duration);
}
}
}
// Print totals if there was more than one test
if (testIndex > 1) {
writer.print("");
writer.print("Total ");
writeTableHeader(integerFormat.format(totalPassedTests), "num");
writeTableHeader(
integerFormat.format(totalSkippedTests), (totalSkippedTests > 0 ? "num attn" : "num"));
writeTableHeader(
integerFormat.format(totalRetriedTests), (totalRetriedTests > 0 ? "num attn" : "num"));
writeTableHeader(
integerFormat.format(totalFailedTests), (totalFailedTests > 0 ? "num attn" : "num"));
writeTableHeader(decimalFormat.format(totalDuration), "num");
writer.print(" ");
writer.println(" ");
}
writer.println("
");
}
/** Writes a summary of all the test scenarios. */
protected void writeScenarioSummary() {
writer.print("");
writer.print("");
writer.print("");
writer.print("Class ");
writer.print("Method ");
writer.print("Start ");
writer.print("Time (ms) ");
writer.print(" ");
writer.print("");
int testIndex = 0;
int scenarioIndex = 0;
for (SuiteResult suiteResult : suiteResults) {
writer.print("");
writer.print(Utils.escapeHtml(suiteResult.getSuiteName()));
writer.print(" ");
for (TestResult testResult : suiteResult.getTestResults()) {
writer.printf("", testIndex);
String testName = Utils.escapeHtml(testResult.getTestName());
int startIndex = scenarioIndex;
scenarioIndex +=
writeScenarioSummary(
testName + " — failed (configuration methods)",
testResult.getFailedConfigurationResults(),
"failed",
scenarioIndex);
scenarioIndex +=
writeScenarioSummary(
testName + " — failed",
testResult.getFailedTestResults(),
"failed",
scenarioIndex);
scenarioIndex +=
writeScenarioSummary(
testName + " — skipped (configuration methods)",
testResult.getSkippedConfigurationResults(),
"skipped",
scenarioIndex);
scenarioIndex +=
writeScenarioSummary(
testName + " — skipped",
testResult.getSkippedTestResults(),
"skipped",
scenarioIndex);
scenarioIndex +=
writeScenarioSummary(
testName + " — retried",
testResult.getRetriedTestResults(),
"retried",
scenarioIndex);
scenarioIndex +=
writeScenarioSummary(
testName + " — passed",
testResult.getPassedTestResults(),
"passed",
scenarioIndex);
if (scenarioIndex == startIndex) {
writer.print(" ");
}
writer.println("");
testIndex++;
}
}
writer.println("
");
}
/** Writes the scenario summary for the results of a given state for a single test. */
private int writeScenarioSummary(
String description,
List classResults,
String cssClassPrefix,
int startingScenarioIndex) {
int scenarioCount = 0;
if (!classResults.isEmpty()) {
writer.print("");
writer.print(description);
writer.print(" ");
int scenarioIndex = startingScenarioIndex;
int classIndex = 0;
for (ClassResult classResult : classResults) {
String cssClass = cssClassPrefix + ((classIndex % 2) == 0 ? "even" : "odd");
buffer.setLength(0);
int scenariosPerClass = 0;
int methodIndex = 0;
for (MethodResult methodResult : classResult.getMethodResults()) {
List results = methodResult.getResults();
int resultsCount = results.size();
assert resultsCount > 0;
ITestResult firstResult = results.iterator().next();
String methodName = Utils.escapeHtml(firstResult.getMethod().getMethodName());
long start = firstResult.getStartMillis();
long duration = firstResult.getEndMillis() - start;
// The first method per class shares a row with the class
// header
if (methodIndex > 0) {
buffer.append("");
}
// Write the timing information with the first scenario per
// method
buffer
.append("")
.append(methodName)
.append(" ")
.append("")
.append(start)
.append(" ")
.append("")
.append(duration)
.append(" ");
scenarioIndex++;
// Write the remaining scenarios for the method
for (int i = 1; i < resultsCount; i++) {
buffer
.append("")
.append("")
.append(methodName)
.append(" ");
scenarioIndex++;
}
scenariosPerClass += resultsCount;
methodIndex++;
}
// Write the test results for the class
writer.print("");
writer.print("");
writer.print(Utils.escapeHtml(classResult.getClassName()));
writer.print(" ");
writer.print(buffer);
classIndex++;
}
scenarioCount = scenarioIndex - startingScenarioIndex;
}
return scenarioCount;
}
/** Writes the details for all test scenarios. */
protected void writeScenarioDetails() {
int scenarioIndex = 0;
for (SuiteResult suiteResult : suiteResults) {
for (TestResult testResult : suiteResult.getTestResults()) {
writer.print("");
writer.print(Utils.escapeHtml(testResult.getTestName()));
writer.print("
");
scenarioIndex +=
writeScenarioDetails(testResult.getFailedConfigurationResults(), scenarioIndex);
scenarioIndex += writeScenarioDetails(testResult.getFailedTestResults(), scenarioIndex);
scenarioIndex +=
writeScenarioDetails(testResult.getSkippedConfigurationResults(), scenarioIndex);
scenarioIndex += writeScenarioDetails(testResult.getSkippedTestResults(), scenarioIndex);
scenarioIndex += writeScenarioDetails(testResult.getPassedTestResults(), scenarioIndex);
}
}
}
/** Writes the scenario details for the results of a given state for a single test. */
private int writeScenarioDetails(List classResults, int startingScenarioIndex) {
int scenarioIndex = startingScenarioIndex;
for (ClassResult classResult : classResults) {
String className = classResult.getClassName();
for (MethodResult methodResult : classResult.getMethodResults()) {
List results = methodResult.getResults();
assert !results.isEmpty();
String label =
Utils.escapeHtml(
className + "#" + results.iterator().next().getMethod().getMethodName());
for (ITestResult result : results) {
writeScenario(scenarioIndex, label, result);
scenarioIndex++;
}
}
}
return scenarioIndex - startingScenarioIndex;
}
/** Writes the details for an individual test scenario. */
private void writeScenario(int scenarioIndex, String label, ITestResult result) {
writer.print("");
writer.print(label);
writer.print("
");
writer.print("");
boolean hasRows = false;
// Write test parameters (if any)
Object[] parameters = result.getParameters();
int parameterCount = (parameters == null ? 0 : parameters.length);
hasRows = dumpParametersInfo("Factory Parameter", result.getFactoryParameters());
parameters = result.getParameters();
parameterCount = (parameters == null ? 0 : parameters.length);
hasRows = dumpParametersInfo("Parameter", result.getParameters());
// Write reporter messages (if any)
List reporterMessages = Reporter.getOutput(result);
if (!reporterMessages.isEmpty()) {
writer.print(" 1) {
writer.printf(" colspan=\"%d\"", parameterCount);
}
writer.print(">Messages ");
writer.print(" 1) {
writer.printf(" colspan=\"%d\"", parameterCount);
}
writer.print(">");
writeReporterMessages(reporterMessages);
writer.print(" ");
hasRows = true;
}
// Write exception (if any)
Throwable throwable = result.getThrowable();
if (throwable != null) {
writer.print(" 1) {
writer.printf(" colspan=\"%d\"", parameterCount);
}
writer.print(">");
writer.print(
(result.getStatus() == ITestResult.SUCCESS ? "Expected Exception" : "Exception"));
writer.print(" ");
writer.print(" 1) {
writer.printf(" colspan=\"%d\"", parameterCount);
}
writer.print(">");
writeStackTrace(throwable);
writer.print(" ");
hasRows = true;
}
if (!hasRows) {
writer.print(" 1) {
writer.printf(" colspan=\"%d\"", parameterCount);
}
writer.print(" class=\"invisible\"/> ");
}
writer.print("
");
writer.println("");
}
private boolean dumpParametersInfo(String prefix, Object[] parameters) {
int parameterCount = (parameters == null ? 0 : parameters.length);
if (parameterCount == 0) {
return false;
}
writer.print("");
for (int i = 1; i <= parameterCount; i++) {
writer.print(String.format("%s #", prefix));
writer.print(i);
writer.print(" ");
}
writer.print(" ");
for (Object parameter : parameters) {
writer.print("");
writer.print(Utils.escapeHtml(Utils.toString(parameter)));
writer.print(" ");
}
writer.print(" ");
return true;
}
protected void writeReporterMessages(List reporterMessages) {
writer.print("");
writer.print(Utils.shortStackTrace(throwable, true));
writer.print("");
}
/**
* Writes a TH element with the specified contents and CSS class names.
*
* @param html the HTML contents
* @param cssClasses the space-delimited CSS classes or null if there are no classes to apply
*/
protected void writeTableHeader(String html, String cssClasses) {
writeTag("th", html, cssClasses);
}
/**
* Writes a TD element with the specified contents.
*
* @param html the HTML contents
*/
protected void writeTableData(String html) {
writeTableData(html, null);
}
/**
* Writes a TD element with the specified contents and CSS class names.
*
* @param html the HTML contents
* @param cssClasses the space-delimited CSS classes or null if there are no classes to apply
*/
protected void writeTableData(String html, String cssClasses) {
writeTag("td", html, cssClasses);
}
/**
* Writes an arbitrary HTML element with the specified contents and CSS class names.
*
* @param tag the tag name
* @param html the HTML contents
* @param cssClasses the space-delimited CSS classes or null if there are no classes to apply
*/
protected void writeTag(String tag, String html, String cssClasses) {
writer.print("<");
writer.print(tag);
if (cssClasses != null) {
writer.print(" class=\"");
writer.print(cssClasses);
writer.print("\"");
}
writer.print(">");
writer.print(html);
writer.print("");
}
/** Groups {@link TestResult}s by suite. */
protected static class SuiteResult {
private final String suiteName;
private final List testResults = Lists.newArrayList();
private final ParallelMode mode;
public SuiteResult(ISuite suite) {
suiteName = suite.getName();
mode = suite.getXmlSuite().getParallel();
for (ISuiteResult suiteResult : suite.getResults().values()) {
testResults.add(new TestResult(suiteResult.getTestContext()));
}
}
public String getSuiteName() {
return suiteName;
}
/** @return the test results (possibly empty) */
public List getTestResults() {
return testResults;
}
public ParallelMode getParallelMode() {
return mode;
}
}
/** Groups {@link ClassResult}s by test, type (configuration or test), and status. */
protected static class TestResult {
/** Orders test results by class name and then by method name (in lexicographic order). */
protected static final Comparator RESULT_COMPARATOR =
Comparator.comparing((ITestResult o) -> o.getTestClass().getName())
.thenComparing(o -> o.getMethod().getMethodName());
private final String testName;
private final List failedConfigurationResults;
private final List failedTestResults;
private final List skippedConfigurationResults;
private final List skippedTestResults;
private final List retriedTestResults;
private final List passedTestResults;
private final int failedTestCount;
private final int retriedTestCount;
private final int skippedTestCount;
private final int passedTestCount;
private final long duration;
private final String includedGroups;
private final String excludedGroups;
public TestResult(ITestContext context) {
testName = context.getName();
Set failedConfigurations = context.getFailedConfigurations().getAllResults();
Set failedTests = context.getFailedTests().getAllResults();
Set skippedConfigurations = context.getSkippedConfigurations().getAllResults();
Set rawSkipped = context.getSkippedTests().getAllResults();
Set skippedTests = pruneSkipped(rawSkipped);
Set retriedTests = pruneRetried(rawSkipped);
Set passedTests = context.getPassedTests().getAllResults();
failedConfigurationResults = groupResults(failedConfigurations);
failedTestResults = groupResults(failedTests);
skippedConfigurationResults = groupResults(skippedConfigurations);
skippedTestResults = groupResults(skippedTests);
retriedTestResults = groupResults(retriedTests);
passedTestResults = groupResults(passedTests);
failedTestCount = failedTests.size();
retriedTestCount = retriedTests.size();
skippedTestCount = skippedTests.size();
passedTestCount = passedTests.size();
duration = context.getEndDate().getTime() - context.getStartDate().getTime();
includedGroups = formatGroups(context.getIncludedGroups());
excludedGroups = formatGroups(context.getExcludedGroups());
}
private static Set pruneSkipped(Set results) {
return results.stream().filter(result -> !result.wasRetried()).collect(Collectors.toSet());
}
private static Set pruneRetried(Set results) {
return results.stream().filter(ITestResult::wasRetried).collect(Collectors.toSet());
}
/** Groups test results by method and then by class. */
protected List groupResults(Set results) {
List classResults = Lists.newArrayList();
if (!results.isEmpty()) {
List resultsPerClass = Lists.newArrayList();
List resultsPerMethod = Lists.newArrayList();
List resultsList = Lists.newArrayList(results);
resultsList.sort(RESULT_COMPARATOR);
Iterator resultsIterator = resultsList.iterator();
assert resultsIterator.hasNext();
ITestResult result = resultsIterator.next();
resultsPerMethod.add(result);
String previousClassName = result.getTestClass().getName();
String previousMethodName = result.getMethod().getMethodName();
while (resultsIterator.hasNext()) {
result = resultsIterator.next();
String className = result.getTestClass().getName();
if (!previousClassName.equals(className)) {
// Different class implies different method
assert !resultsPerMethod.isEmpty();
resultsPerClass.add(new MethodResult(resultsPerMethod));
resultsPerMethod = Lists.newArrayList();
assert !resultsPerClass.isEmpty();
classResults.add(new ClassResult(previousClassName, resultsPerClass));
resultsPerClass = Lists.newArrayList();
previousClassName = className;
previousMethodName = result.getMethod().getMethodName();
} else {
String methodName = result.getMethod().getMethodName();
if (!previousMethodName.equals(methodName)) {
assert !resultsPerMethod.isEmpty();
resultsPerClass.add(new MethodResult(resultsPerMethod));
resultsPerMethod = Lists.newArrayList();
previousMethodName = methodName;
}
}
resultsPerMethod.add(result);
}
assert !resultsPerMethod.isEmpty();
resultsPerClass.add(new MethodResult(resultsPerMethod));
assert !resultsPerClass.isEmpty();
classResults.add(new ClassResult(previousClassName, resultsPerClass));
}
return classResults;
}
public String getTestName() {
return testName;
}
/** @return the results for failed configurations (possibly empty) */
public List getFailedConfigurationResults() {
return failedConfigurationResults;
}
/** @return the results for failed tests (possibly empty) */
public List getFailedTestResults() {
return failedTestResults;
}
/** @return the results for skipped configurations (possibly empty) */
public List getSkippedConfigurationResults() {
return skippedConfigurationResults;
}
/** @return the results for skipped tests (possibly empty) */
public List getSkippedTestResults() {
return skippedTestResults;
}
public List getRetriedTestResults() {
return retriedTestResults;
}
/** @return the results for passed tests (possibly empty) */
public List getPassedTestResults() {
return passedTestResults;
}
public int getFailedTestCount() {
return failedTestCount;
}
public int getSkippedTestCount() {
return skippedTestCount;
}
public int getRetriedTestCount() {
return retriedTestCount;
}
public int getPassedTestCount() {
return passedTestCount;
}
public long getDuration() {
return duration;
}
public String getIncludedGroups() {
return includedGroups;
}
public String getExcludedGroups() {
return excludedGroups;
}
/** Formats an array of groups for display. */
protected String formatGroups(String[] groups) {
if (groups.length == 0) {
return "";
}
StringBuilder builder = new StringBuilder();
builder.append(groups[0]);
for (int i = 1; i < groups.length; i++) {
builder.append(", ").append(groups[i]);
}
return builder.toString();
}
}
/** Groups {@link MethodResult}s by class. */
protected static class ClassResult {
private final String className;
private final List methodResults;
/**
* @param className the class name
* @param methodResults the non-null, non-empty {@link MethodResult} list
*/
public ClassResult(String className, List methodResults) {
this.className = className;
this.methodResults = methodResults;
}
public String getClassName() {
return className;
}
/** @return the non-null, non-empty {@link MethodResult} list */
public List getMethodResults() {
return methodResults;
}
}
/** Groups test results by method. */
protected static class MethodResult {
private final List results;
/** @param results the non-null, non-empty result list */
public MethodResult(List results) {
this.results = results;
}
/** @return the non-null, non-empty result list */
public List getResults() {
return results;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy
");
}
protected void writeStackTrace(Throwable throwable) {
writer.print("