com.github.robindevilliers.cascade.modules.reporter.HtmlReporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cascade Show documentation
Show all versions of cascade Show documentation
Cascade is a blackbox testing framework
The newest version!
package com.github.robindevilliers.cascade.modules.reporter;
import com.github.robindevilliers.cascade.Scope;
import junit.framework.AssertionFailedError;
import com.github.robindevilliers.cascade.Completeness;
import com.github.robindevilliers.cascade.Scenario;
import com.github.robindevilliers.cascade.annotations.Narrative;
import com.github.robindevilliers.cascade.annotations.Step;
import com.github.robindevilliers.cascade.exception.CascadeException;
import com.github.robindevilliers.cascade.model.Journey;
import com.github.robindevilliers.cascade.modules.Reporter;
import com.github.robindevilliers.cascade.modules.TestReport;
import com.github.robindevilliers.cascade.utils.Reference;
import javax.json.*;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Paths;
import java.util.*;
import static java.lang.String.format;
import static java.nio.file.Files.copy;
import static java.util.Collections.emptyMap;
import static javax.json.stream.JsonGenerator.PRETTY_PRINTING;
import static com.github.robindevilliers.cascade.utils.Utils.map;
public class HtmlReporter implements Reporter {
private JsonBuilderFactory builderFactory = Json.createBuilderFactory(emptyMap());
private JsonWriterFactory writerFactory = Json.createWriterFactory(map(PRETTY_PRINTING, true));
private File dataDirectory;
private JsonArrayBuilder directoryItemsJson;
private long startTime;
private JsonObjectBuilder directoryJson;
private Completeness completeness;
private RenderingSystem renderingSystem;
private Map, Integer> stateHistogram = new HashMap<>();
private Map, Integer> scenarioHistogram = new HashMap<>();
@Override
public void init(Class> controlClass, List scenarios, Map globalScope, Completeness completeness, RenderingSystem renderingSystem) {
this.completeness = completeness;
this.renderingSystem = renderingSystem;
this.directoryJson = builderFactory.createObjectBuilder();
this.directoryItemsJson = builderFactory.createArrayBuilder();
this.startTime = System.currentTimeMillis();
globalScope.computeIfAbsent("REPORTS_BASE_DIRECTORY", k -> new Scope("./build/reports/tests/cascade"));
File reportsDirectory = createDestinationDirectories(globalScope.get("REPORTS_BASE_DIRECTORY").getValue().toString());
globalScope.put("REPORTS_DIRECTORY", new Scope(reportsDirectory));
copyTemplateFiles(reportsDirectory);
writeStateMachineToJson(scenarios);
}
private File createDestinationDirectories(String reportsBaseDir) {
File testDirectory = new File(reportsBaseDir);
if (!testDirectory.exists()) {
if (!testDirectory.mkdirs()) {
throw new CascadeException("Unable to create test directory for cascade reports at location " + reportsBaseDir);
}
}
File reportsDirectory = new File(testDirectory, Long.toString(System.currentTimeMillis()));
if (!reportsDirectory.mkdir()) {
throw new CascadeException("Unable to create cascade directory for cascade reports at location " + reportsDirectory);
}
dataDirectory = new File(reportsDirectory, "data");
if (!dataDirectory.mkdir()) {
throw new CascadeException("Unable to create cascade directory for cascade reports at location " + reportsBaseDir + "/cascade/data");
}
return reportsDirectory;
}
private void copyTemplateFiles(File reportsDirectory) {
copyFileFromTemplate("index.html", reportsDirectory);
copyFileFromTemplate("bootstrap.min.css", reportsDirectory);
copyFileFromTemplate("bootstrap.css.map", reportsDirectory);
copyFileFromTemplate("jquery.min.js", reportsDirectory);
copyFileFromTemplate("bootstrap.min.js", reportsDirectory);
copyFileFromTemplate("lodash.js", reportsDirectory);
copyFileFromTemplate("index.js", reportsDirectory);
copyFileFromTemplate("journey.js", reportsDirectory);
copyFileFromTemplate("journey.html", reportsDirectory);
copyFileFromTemplate("style.css", reportsDirectory);
copyFileFromTemplate("state.html", reportsDirectory);
copyFileFromTemplate("state.js", reportsDirectory);
new File(reportsDirectory, "fonts").mkdir();
copyFileFromTemplate("fonts/glyphicons-halflings-regular.eot", reportsDirectory);
copyFileFromTemplate("fonts/glyphicons-halflings-regular.svg", reportsDirectory);
copyFileFromTemplate("fonts/glyphicons-halflings-regular.ttf", reportsDirectory);
copyFileFromTemplate("fonts/glyphicons-halflings-regular.woff", reportsDirectory);
copyFileFromTemplate("fonts/glyphicons-halflings-regular.woff2", reportsDirectory);
new File(reportsDirectory, "lib").mkdir();
copyFileFromTemplate("lib/coordinate.js", reportsDirectory);
copyFileFromTemplate("lib/directory.js", reportsDirectory);
copyFileFromTemplate("lib/enum.js", reportsDirectory);
copyFileFromTemplate("lib/leg.js", reportsDirectory);
copyFileFromTemplate("lib/path.js", reportsDirectory);
copyFileFromTemplate("lib/plot.js", reportsDirectory);
copyFileFromTemplate("lib/state.js", reportsDirectory);
copyFileFromTemplate("lib/utils.js", reportsDirectory);
copyFileFromTemplate("lib/breadcrumb.js", reportsDirectory);
}
private void writeStateMachineToJson(List scenarios) {
Map, TreeSet> states = new HashMap<>();
JsonArrayBuilder statesJson = builderFactory.createArrayBuilder();
JsonArrayBuilder scenariosJson = builderFactory.createArrayBuilder();
for (Scenario scenario : scenarios) {
JsonObjectBuilder scenarioJson = builderFactory.createObjectBuilder();
scenarioJson.add("name", scenario.getName());
Optional.ofNullable(scenario.getClazz().getAnnotation(Narrative.class)).ifPresent((n) -> scenarioJson.add("narrative", n.value()));
scenarioJson.add("state", scenario.getStateClazz().getName());
scenarioJson.add("terminator", scenario.isTerminator());
scenarioJson.add("reEntrantTerminator", scenario.isReEntrantTerminator());
scenariosJson.add(scenarioJson);
states.computeIfAbsent(scenario.getStateClazz(), (c) -> new TreeSet<>(Comparator.comparing(Class::getCanonicalName))).addAll(Arrays.asList(scenario.getSteps()));
}
directoryJson.add("scenarios", scenariosJson);
for (Map.Entry, TreeSet> entry : states.entrySet()) {
JsonObjectBuilder stateJson = builderFactory.createObjectBuilder();
stateJson.add("name", entry.getKey().getCanonicalName());
Optional.ofNullable(entry.getKey().getAnnotation(Narrative.class)).ifPresent((n) -> stateJson.add("narrative", n.value()));
JsonArrayBuilder precedentsJson = builderFactory.createArrayBuilder();
for (Class> cls : entry.getValue()) {
precedentsJson.add(cls.getName());
}
stateJson.add("precedents", precedentsJson);
statesJson.add(stateJson);
}
directoryJson.add("states", statesJson);
directoryJson.add("completeness", completeness.toString());
}
@Override
public void start() {
}
@Override
public TestReport createTestReport() {
return new HtmlTestReport(renderingSystem, builderFactory, this);
}
@Override
public void finish() {
directoryJson.add("duration", System.currentTimeMillis() - startTime);
directoryJson.add("items", directoryItemsJson.build());
Map> stateAccumulator = new TreeMap<>();
stateHistogram.forEach((key, value) -> stateAccumulator.computeIfAbsent(value, (k) -> new ArrayList<>()).add(key));
JsonObjectBuilder stateOrderJson = builderFactory.createObjectBuilder();
int stateIndex = 1;
for (Map.Entry> entry : stateAccumulator.entrySet()) {
for (Class clz : entry.getValue()) {
stateOrderJson.add(clz.getName(), stateIndex);
}
stateIndex++;
}
directoryJson.add("stateOrder", stateOrderJson);
Map> scenarioAccumulator = new TreeMap<>();
scenarioHistogram.forEach((key, value) -> scenarioAccumulator.computeIfAbsent(value, (k) -> new ArrayList<>()).add(key));
JsonObjectBuilder scenarioOrderJson = builderFactory.createObjectBuilder();
int scenarioIndex = 1;
for (Map.Entry> entry : scenarioAccumulator.entrySet()) {
for (Class clz : entry.getValue()) {
scenarioOrderJson.add(clz.getName(), scenarioIndex);
}
scenarioIndex++;
}
directoryJson.add("scenarioOrder", scenarioOrderJson);
writeVariableAsFile(dataDirectory, "directoryData", directoryJson.build());
}
private void merge(Journey journey, JsonObjectBuilder directoryItemJson) {
for (Scenario scenario : journey.getSteps()) {
stateHistogram.compute(scenario.getStateClazz(), (clz, count) -> count == null ? 1 : count + 1);
scenarioHistogram.compute(scenario.getClazz(), (clz, count) -> count == null ? 1 : count + 1);
}
directoryItemsJson.add(directoryItemJson);
}
private void copyFileFromTemplate(String fileName, File baseDirectory) {
try {
InputStream is = HtmlReporter.class.getResourceAsStream("/template/" + fileName);
copy(is, Paths.get(format("%s/%s", baseDirectory.getAbsolutePath(), fileName)));
} catch (IOException e) {
throw new CascadeException("io error copying file " + fileName, e);
}
}
private void writeVariableAsFile(File reportsDirectory, String variableName, String fileName, JsonStructure data) {
try {
PrintWriter printWriter = new PrintWriter(new File(reportsDirectory, fileName + ".js"));
printWriter.print("var " + variableName + " = ");
JsonWriter jsonWriter = writerFactory.createWriter(printWriter);
jsonWriter.write(data);
jsonWriter.close();
} catch (IOException e) {
throw new CascadeException("io error writing file " + reportsDirectory + "/" + variableName + ".js", e);
}
}
private void writeVariableAsFile(File reportsDirectory, String variableName, JsonStructure data) {
writeVariableAsFile(reportsDirectory, variableName, variableName, data);
}
private enum TestResult {
SUCCESS, FAILED, ERROR
}
private Class> findClassWithAnnotation(Class annotationClass, Class> subject) {
if (subject.equals(Step.Null.class)) {
return subject;
}
T step = subject.getAnnotation(annotationClass);
if (step != null) {
return subject;
}
for (Class> i : subject.getInterfaces()) {
step = i.getAnnotation(annotationClass);
if (step != null) {
return i;
}
}
Class superClass = subject.getSuperclass();
if (superClass != null) {
return findClassWithAnnotation(annotationClass, superClass);
}
return null;
}
public static class HtmlTestReport implements TestReport {
private Map scope;
private List steps;
private JsonObjectBuilder directoryItemJson;
private int index;
private TestResult testResult;
private RenderingSystem renderingSystem;
private JsonBuilderFactory builderFactory;
private HtmlReporter htmlReporter;
private Journey journey;
HtmlTestReport(RenderingSystem renderingSystem, JsonBuilderFactory builderFactory, HtmlReporter htmlReporter) {
this.renderingSystem = renderingSystem;
this.builderFactory = builderFactory;
this.htmlReporter = htmlReporter;
}
@Override
public void setupTest(Journey journey, Map scope) {
this.journey = journey;
this.index = 0;
this.scope = scope;
directoryItemJson = builderFactory.createObjectBuilder();
directoryItemJson.add("journeyId", UUID.randomUUID().toString().replaceAll("-", ""));
directoryItemJson.add("name", journey.getName());
directoryItemJson.add("id", journey.getId());
this.steps = new ArrayList<>();
for (Scenario scenario : journey.getSteps()) {
JsonObjectBuilder stepJson = builderFactory.createObjectBuilder();
stepJson.add("name", scenario.getName());
stepJson.add("narrative", scenario.getNarrative());
this.steps.add(stepJson);
}
StringBuilder filter = new StringBuilder();
filter.append("@FilterTests
").append("Predicate filter = and(");
boolean comma = false;
int index = 0;
for (Scenario scenario : journey.getSteps()) {
if (comma) {
filter.append(",");
}
filter.append("
stepAt(");
filter.append(index++);
filter.append(",");
filter.append(scenario.getClazz().getCanonicalName());
filter.append(".class)");
comma = true;
}
filter.append("
);");
directoryItemJson.add("filter", filter.toString());
}
@Override
public void startTest(Journey journey, Reference