TestBook.java
package io.github.giulong.spectrum.utils.testbook;
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import io.github.giulong.spectrum.enums.Result;
import io.github.giulong.spectrum.interfaces.SessionHook;
import io.github.giulong.spectrum.interfaces.reports.CanReportTestBook;
import io.github.giulong.spectrum.interfaces.reports.Reportable;
import io.github.giulong.spectrum.pojos.testbook.QualityGate;
import io.github.giulong.spectrum.pojos.testbook.TestBookStatistics;
import io.github.giulong.spectrum.pojos.testbook.TestBookStatistics.Statistics;
import io.github.giulong.spectrum.pojos.testbook.TestBookTest;
import io.github.giulong.spectrum.utils.FileUtils;
import io.github.giulong.spectrum.utils.FreeMarkerWrapper;
import io.github.giulong.spectrum.utils.Vars;
import io.github.giulong.spectrum.utils.reporters.FileReporter;
import io.github.giulong.spectrum.utils.testbook.parsers.TestBookParser;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import static io.github.giulong.spectrum.enums.Result.*;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
@Getter
@Slf4j
public class TestBook implements SessionHook, Reportable {
@JsonIgnore
private final FileUtils fileUtils = FileUtils.getInstance();
@JsonIgnore
@JacksonInject("enabledFromClient")
@SuppressWarnings("unused")
private boolean enabled;
@JsonPropertyDescription("Quality Gate to be evaluated to consider the execution successful")
@SuppressWarnings("unused")
private QualityGate qualityGate;
@JsonPropertyDescription("Object specifying the kind of testBook provided")
@SuppressWarnings("unused")
private TestBookParser parser;
@JsonPropertyDescription("List of testBook reporters that will produce the execution report in specific formats")
@SuppressWarnings("unused")
private List<CanReportTestBook> reporters;
@JsonIgnore
private final Map<String, TestBookTest> mappedTests = new HashMap<>();
@JsonIgnore
private final Map<String, TestBookTest> unmappedTests = new HashMap<>();
@JsonIgnore
private final Map<String, Set<TestBookTest>> groupedMappedTests = new HashMap<>();
@JsonIgnore
private final Map<String, Set<TestBookTest>> groupedUnmappedTests = new HashMap<>();
@JsonIgnore
private final TestBookStatistics statistics = new TestBookStatistics();
@JsonIgnore
private final Map<String, Object> vars = new HashMap<>();
public TestBook() {
final Map<Result, Statistics> totalCount = statistics.getTotalCount();
final Map<Result, Statistics> grandTotalCount = statistics.getGrandTotalCount();
final Map<Result, Statistics> totalWeightedCount = statistics.getTotalWeightedCount();
final Map<Result, Statistics> grandTotalWeightedCount = statistics.getGrandTotalWeightedCount();
Arrays
.stream(Result.values())
.forEach(result -> {
totalCount.put(result, new Statistics());
grandTotalCount.put(result, new Statistics());
totalWeightedCount.put(result, new Statistics());
grandTotalWeightedCount.put(result, new Statistics());
});
}
@Override
public void sessionOpened() {
if (!enabled) {
log.debug("TestBook disabled. Skipping parse");
return;
}
reporters
.stream()
.filter(canReportTestBook -> canReportTestBook instanceof FileReporter)
.map(FileReporter.class::cast)
.map(FileReporter::getOutput)
.forEach(output -> {
final String reportPath = Path.of(output).toAbsolutePath().toString().replace("\\", "/");
log.info("After the execution, you'll find the {} testBook at file:///{}", fileUtils.getExtensionOf(output), reportPath);
});
final List<TestBookTest> tests = parser.parse();
mappedTests.putAll(tests
.stream()
.collect(toMap(test -> String.format("%s %s", test.getClassName(), test.getTestName()), identity())));
tests.forEach(test -> updateGroupedTests(groupedMappedTests, test.getClassName(), test));
}
@Override
public void sessionClosed() {
if (!enabled) {
log.debug("Testbook disabled. Skipping flush");
return;
}
log.debug("Updating testBook percentages");
final int testsTotal = mappedTests.size();
final int unmappedTestsTotal = unmappedTests.size();
final int weightedTestsTotal = getWeightedTotalOf(mappedTests);
final int weightedTestsGrandTotal = weightedTestsTotal + getWeightedTotalOf(unmappedTests);
statistics.getGrandTotal().set(testsTotal + unmappedTestsTotal);
statistics.getTotalWeighted().set(weightedTestsTotal);
statistics.getGrandTotalWeighted().set(weightedTestsGrandTotal);
flush(testsTotal, statistics.getTotalCount());
flush(testsTotal + unmappedTestsTotal, statistics.getGrandTotalCount());
flush(weightedTestsTotal, statistics.getTotalWeightedCount());
flush(weightedTestsGrandTotal, statistics.getGrandTotalWeightedCount());
final Map<Result, Statistics> totalCount = statistics.getTotalCount();
final Map<Result, Statistics> grandTotalCount = statistics.getGrandTotalCount();
final Map<Result, Statistics> totalWeightedCount = statistics.getTotalWeightedCount();
final Map<Result, Statistics> grandTotalWeightedCount = statistics.getGrandTotalWeightedCount();
vars.putAll(Vars.getInstance());
vars.put("mappedTests", mappedTests);
vars.put("unmappedTests", unmappedTests);
vars.put("groupedMappedTests", groupedMappedTests);
vars.put("groupedUnmappedTests", groupedUnmappedTests);
vars.put("statistics", statistics);
vars.put("qg", qualityGate);
vars.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")));
vars.put("successful", totalCount.get(SUCCESSFUL));
vars.put("failed", totalCount.get(FAILED));
vars.put("aborted", totalCount.get(ABORTED));
vars.put("disabled", totalCount.get(DISABLED));
vars.put("notRun", totalCount.get(NOT_RUN));
vars.put("grandSuccessful", grandTotalCount.get(SUCCESSFUL));
vars.put("grandFailed", grandTotalCount.get(FAILED));
vars.put("grandAborted", grandTotalCount.get(ABORTED));
vars.put("grandDisabled", grandTotalCount.get(DISABLED));
vars.put("grandNotRun", grandTotalCount.get(NOT_RUN));
vars.put("weightedSuccessful", totalWeightedCount.get(SUCCESSFUL));
vars.put("weightedFailed", totalWeightedCount.get(FAILED));
vars.put("weightedAborted", totalWeightedCount.get(ABORTED));
vars.put("weightedDisabled", totalWeightedCount.get(DISABLED));
vars.put("weightedNotRun", totalWeightedCount.get(NOT_RUN));
vars.put("grandWeightedSuccessful", grandTotalWeightedCount.get(SUCCESSFUL));
vars.put("grandWeightedFailed", grandTotalWeightedCount.get(FAILED));
vars.put("grandWeightedAborted", grandTotalWeightedCount.get(ABORTED));
vars.put("grandWeightedDisabled", grandTotalWeightedCount.get(DISABLED));
vars.put("grandWeightedNotRun", grandTotalWeightedCount.get(NOT_RUN));
final String qgStatus = "qgStatus";
final String interpolatedQgStatus = FreeMarkerWrapper.getInstance().interpolate(qualityGate.getCondition(), vars);
vars.put(qgStatus, interpolatedQgStatus);
Vars.getInstance().put(qgStatus, interpolatedQgStatus);
reporters.forEach(reporter -> reporter.flush(this));
}
public void updateWithResult(final String className, final String testName, final Result result) {
if (!enabled) {
log.debug("TestBook disabled. Skipping consumer");
return;
}
final String fullName = String.format("%s %s", className, testName);
statistics.getGrandTotalCount().get(result).getTotal().incrementAndGet();
if (mappedTests.containsKey(fullName)) {
log.debug("Setting TestBook result {} for test '{}'", result, fullName);
final TestBookTest actualTest = mappedTests.get(fullName);
final int weight = actualTest.getWeight();
actualTest.setResult(result);
statistics.getTotalCount().get(result).getTotal().incrementAndGet();
statistics.getTotalWeightedCount().get(result).getTotal().addAndGet(weight);
statistics.getGrandTotalWeightedCount().get(result).getTotal().addAndGet(weight);
updateGroupedTests(groupedMappedTests, className, actualTest);
} else {
final TestBookTest unmappedTest = TestBookTest.builder()
.className(className)
.testName(testName)
.result(result)
.build();
log.debug("Setting TestBook result {} for unmapped test '{}'", result, unmappedTest);
unmappedTests.put(fullName, unmappedTest);
statistics.getGrandTotalWeightedCount().get(result).getTotal().incrementAndGet();
updateGroupedTests(groupedUnmappedTests, className, unmappedTest);
}
}
void updateGroupedTests(final Map<String, Set<TestBookTest>> groupedTests, final String className, final TestBookTest test) {
final Set<TestBookTest> tests = groupedTests.getOrDefault(className, new HashSet<>());
tests.add(test);
groupedTests.put(className, tests);
}
int getWeightedTotalOf(final Map<String, TestBookTest> tests) {
return tests
.values()
.stream()
.map(TestBookTest::getWeight)
.reduce(0, Integer::sum);
}
void flush(final int total, final Map<Result, Statistics> map) {
final Statistics successful = map.get(SUCCESSFUL);
final Statistics failed = map.get(FAILED);
final Statistics aborted = map.get(ABORTED);
final Statistics disabled = map.get(DISABLED);
final Statistics notRun = map.get(NOT_RUN);
final double totalSuccessful = successful.getTotal().doubleValue();
final double totalFailed = failed.getTotal().doubleValue();
final double totalAborted = aborted.getTotal().doubleValue();
final double totalDisabled = disabled.getTotal().doubleValue();
final double totalNotRun = total - totalSuccessful - totalFailed - totalAborted - totalDisabled;
successful.getPercentage().set(totalSuccessful / total * 100);
failed.getPercentage().set(totalFailed / total * 100);
aborted.getPercentage().set(totalAborted / total * 100);
disabled.getPercentage().set(totalDisabled / total * 100);
notRun.getPercentage().set(totalNotRun / total * 100);
notRun.getTotal().set((int) totalNotRun);
}
}