
org.provatesting.ProvaRunner Maven / Gradle / Ivy
/*
Copyright 2020 Stefan Maucher
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.provatesting;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.provatesting.actuals.ActualColumn;
import org.provatesting.actuals.ActualRow;
import org.provatesting.annotations.MultiRowAction;
import org.provatesting.annotations.MultiRowValidation;
import org.provatesting.annotations.SingleRowAction;
import org.provatesting.annotations.SingleRowValidation;
import org.provatesting.expectations.ExpectedColumn;
import org.provatesting.expectations.ExpectedRow;
import org.provatesting.parsers.ScenarioParser;
import org.provatesting.printers.PrinterEngine;
import org.provatesting.printers.ResultPrinter;
import org.provatesting.scenario.Scenario;
import org.provatesting.scenario.Step;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProvaRunner {
private static final Logger LOG = LoggerFactory.getLogger(ProvaRunner.class);
private static final Map, AnnotationProcessor> supportedAnnotations = new HashMap<>();
static {
supportedAnnotations.put(
SingleRowAction.class,
(s, r, a, an) -> processSingleRowAction(s, r, a, ((SingleRowAction) an).init())
);
supportedAnnotations.put(
MultiRowAction.class,
(s, r, a, an) -> processMultiRowAction(s, r, a, ((MultiRowAction) an).init())
);
supportedAnnotations.put(
SingleRowValidation.class,
(s, r, a, an) -> processSingleRowValidation(s, r, a, ((SingleRowValidation) an).init())
);
supportedAnnotations.put(
MultiRowValidation.class,
(s, r, a, an) -> processMultiRowValidation(s, r, a, ((MultiRowValidation) an).init())
);
}
private final Contexts contexts;
private final ScenarioParser parser;
private final PrinterEngine printerEngine;
private final String resultOutputPath;
/**
* Constructor to instantiate a new ProvaRunner.
* @param parser - the parser to read the scenarios
* @param printerEngine - the engine to print the results
* @param resultOutputPath - the location where to print the results
*/
public ProvaRunner(ScenarioParser parser, PrinterEngine printerEngine, String resultOutputPath) {
this.contexts = new Contexts();
this.parser = parser;
this.printerEngine = printerEngine;
this.resultOutputPath = resultOutputPath;
}
private static boolean processSingleRowAction(Step step,
Object resource,
Method action,
String init
) throws Exception {
runInit(step, resource, init);
for (ExpectedRow row : step.getExpectedRows()) {
action.invoke(resource, row);
step.addActualRow(parseExpectedRowToActualRow(row));
}
return true;
}
private static boolean processMultiRowAction(Step step,
Object resource,
Method action,
String init
) throws Exception {
runInit(step, resource, init);
action.invoke(resource, step.getExpectedRows());
step.getExpectedRows().parallelStream().forEach(
row -> step.addActualRow(parseExpectedRowToActualRow(row))
);
return true;
}
private static boolean processSingleRowValidation(Step step,
Object resource,
Method action,
String init
) throws Exception {
runInit(step, resource, init);
for (ExpectedRow expectedRow : step.getExpectedRows()) {
if (action.getParameterCount() > 0) {
step.addActualRow((ActualRow) action.invoke(resource, expectedRow));
} else {
step.addActualRow((ActualRow) action.invoke(resource));
}
}
return true;
}
private static boolean processMultiRowValidation(Step step,
Object resource,
Method action,
String init
) throws Exception {
runInit(step, resource, init);
Set actualRows;
if (action.getParameterCount() > 0) {
actualRows = (Set) action.invoke(resource, step.getExpectedRows());
} else {
actualRows = (Set) action.invoke(resource);
}
actualRows.parallelStream().forEach(step::addActualRow);
return true;
}
private static Method findMethod(String type, Object resource, String name) {
Optional first = Arrays.stream(resource.getClass().getMethods())
.filter((m) -> m.getName().equalsIgnoreCase(name))
.findFirst();
if (first.isPresent()) {
return first.get();
} else {
throw new RuntimeException(type + " not found: " + name);
}
}
private static ActualRow parseExpectedRowToActualRow(ExpectedRow row) {
ActualRow actualRow = new ActualRow();
for (ExpectedColumn col : row.getExpectedColumns()) {
actualRow.addActualColumn(new ActualColumn(col.getKey(), col.getValue()));
}
return actualRow;
}
private static void runInit(Step step, Object resource, String init) throws Exception {
if (init == null || init.trim().equals("")) {
return;
}
Method method = findMethod("Init", resource, init);
method.invoke(resource, step.getInitParams());
}
/**
* The "main method" used to run PROVA for a given list of tests.
* @param testResources - the source "files" to use for generating the scenarios
* @return true if, and only if, all tests run successfully
*/
public boolean run(List testResources) {
LOG.info("Running with {} testResources.", testResources.size());
executeBeforeSuite();
Set status = testResources.stream()
.map(this::run)
.collect(Collectors.toSet());
executeAfterSuite();
printerEngine.finalizePrinting(resultOutputPath);
boolean success = !status.contains(Boolean.FALSE);
return success;
}
private boolean run(File testResource) {
List scenarios = getScenarios(testResource);
Set statuses = scenarios.stream()
.map(this::run)
.collect(Collectors.toSet());
boolean success = !statuses.contains(Boolean.FALSE);
return success;
}
private boolean run(Scenario scenario) {
LOG.info("Running scenario '{}/{}'", scenario.getFolderName(), scenario.getName());
executeBeforeScenario();
for (Step step : scenario.getSteps()) {
try {
run(step);
} catch (Throwable t) {
if (step.getExceptionMessage() == null || step.getExceptionMessage().equals("")) {
step.setExceptionMessage(extractErrorMessage(t));
}
break;
}
}
executeAfterScenario();
String directory;
if (resultOutputPath != null && !"".equals(resultOutputPath.trim())) {
directory = resultOutputPath;
} else {
directory = scenario.getFolderName();
}
LOG.debug("directory to write to results to: {}", directory);
File file = new File(directory);
if (!file.exists() || !file.isDirectory() || !file.canWrite()) {
throw new RuntimeException("Cannot write result to directory " + directory);
}
try (ResultPrinter printerInstance = printerEngine.prepareForScenario(directory, scenario.getName(), scenario.isValid())) {
printerInstance.print(scenario);
} catch (IOException e) {
return false;
}
return scenario.isValid();
}
private void run(Step step) {
Object resource = findResource(step.getResource());
Method action = findMethod("Action", resource, step.getAction());
if (!processBasedOnAnnotation(step, resource, action)) {
throw new RuntimeException("Action was not marked runnable with Prova annotation.");
}
}
//TODO: add to summary result?
private void executeBeforeSuite() {
executePreOrPostStep("BeforeSuite", "run");
}
private void executeAfterSuite() {
executePreOrPostStep("AfterSuite", "run");
}
private void executeBeforeScenario() {
executePreOrPostStep("BeforeScenario", "run");
}
private void executeAfterScenario() {
executePreOrPostStep("AfterScenario", "run");
}
private void executePreOrPostStep(String resourceName, String actionName) {
try {
Object resource = findResource(resourceName);
Method action = findMethod("Action", resource, actionName);
action.invoke(resource);
} catch (RuntimeException | IllegalAccessException | InvocationTargetException e) {
LOG.warn("{}", e.getMessage());
}
}
private List getScenarios(File testResource) {
final List scenarios = new ArrayList<>();
if (testResource.isDirectory()) {
Arrays.stream(testResource.listFiles()).forEach(
file -> scenarios.addAll(getScenarios(file))
);
} else {
//TODO: should probably not hard code it to FIS here? Each parser should know how to load?
try (InputStream is = new FileInputStream(testResource)) {
scenarios.add(parser.parse(
testResource.getParentFile().getAbsolutePath(),
testResource.getName(),
is)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return scenarios;
}
private String extractErrorMessage(Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
if (t instanceof InvocationTargetException) {
((InvocationTargetException) t).getTargetException().printStackTrace(pw);
} else {
t.printStackTrace(pw);
}
return sw.toString();
}
private Object findResource(String resourceName) {
Object resource = contexts.get(resourceName);
if (resource == null) {
throw new RuntimeException("Resource not found: " + resourceName);
}
return resource;
}
private boolean processBasedOnAnnotation(Step step, Object resource, Method action) {
try {
return tryToProcessBasedOnAnnotation(step, resource, action);
} catch (Throwable t) {
step.setExceptionMessage(extractErrorMessage(t));
return false;
}
}
private boolean tryToProcessBasedOnAnnotation(Step step,
Object resource,
Method action
) throws Exception {
for (Annotation annotation : action.getDeclaredAnnotations()) {
if (supportedAnnotations.containsKey(annotation.annotationType())) {
return supportedAnnotations
.get(annotation.annotationType())
.process(step, resource, action, annotation);
}
}
return false;
}
private interface AnnotationProcessor {
boolean process(Step step,
Object resource,
Method action,
Annotation annotation
) throws Exception;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy