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
Testing framework for Java
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("");
writer.print(tag);
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("