tw.teddysoft.ezspec.keyword.RuntimeScenario Maven / Gradle / Ivy
package tw.teddysoft.ezspec.keyword;
import org.junit.jupiter.api.DynamicNode;
import tw.teddysoft.ezspec.exception.EzSpecError;
import tw.teddysoft.ezspec.exception.PendingException;
import tw.teddysoft.ezspec.keyword.table.Table;
import tw.teddysoft.ezspec.keyword.visitor.SpecificationElementVisitor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import static java.lang.String.format;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
/**
* {@code RuntimeScenario} is a class for representing Gherkin scenario.
*
* @author Teddy Chen
* @since 1.0
*/
public class RuntimeScenario extends Scenario {
protected ScenarioOutline from;
/**
* Instantiates a new Runtime scenario.
*
* @param name the name
*/
public RuntimeScenario(String name, Rule rule){
super(name, rule);
}
/**
* Instantiates a new Runtime scenario.
*
* @param name the name
* @param table the table
* @param outline the outline
* @param index the index of this RuntimeScenario in the ScenarioOutline
* @param backgroundRuntime the ScenarioEnvironment of this RuntimeScenario in the ScenarioOutline
*/
public RuntimeScenario(String name, Table table, ScenarioOutline outline, int index, ScenarioEnvironment backgroundRuntime, Rule rule){
super(name, table, rule);
var temp = ScenarioEnvironment.clone(backgroundRuntime);
temp.addContext(runtime);
runtime = temp;
from = outline;
this.index = index;
getEnvironment().setExecutionCount(index+1);
}
/**
* Instantiates a new Runtime scenario
*
* @param name the name
* @param background the background
*/
public RuntimeScenario(String name, Background background, Rule rule){
super(name, background, rule);
}
public int getIndex() {
return index;
}
@Override
public Scenario Given(String description, Consumer callback){
return this.Given(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario Given(String description, boolean continuous, Consumer callback){
var step = new Given(description, continuous, callback);
getSteps().add(step);
return this;
}
@Override
public Scenario When(String description, Consumer callback){
return this.When(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario When(String description, boolean continuous, Consumer callback){
var step = new When(description, continuous, callback);
getSteps().add(step);
return this;
}
@Override
public Scenario Then(String description, Consumer callback){
return this.Then(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario Then(String description, boolean continuous, Consumer callback){
var step = new Then(description, continuous, callback);
getSteps().add(step);
return this;
}
@Override
public Scenario ThenSuccess(boolean continuous, Consumer callback) {
return this.ThenSuccess("", continuous, callback);
}
@Override
public Scenario ThenSuccess(Consumer callback) {
return this.ThenSuccess("", Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenSuccess(String description, Consumer callback) {
return this.ThenSuccess(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenSuccess(String description, boolean continuous, Consumer callback) {
var step = new ThenSuccess(description, continuous, callback);
getSteps().add(step);
return this;
}
@Override
public Scenario ThenFailure(Consumer callback) {
return this.ThenFailure("", Step.TerminateAfterFailure, callback);
}
@Override
public Scenario ThenFailure(boolean continuous, Consumer callback) {
return this.ThenFailure("", continuous, callback);
}
@Override
public Scenario ThenFailure(String description, boolean continuous, Consumer callback) {
var step = new ThenFailure(description, continuous, callback);
getSteps().add(step);
return this;
}
@Override
public Scenario ThenFailure(String description, Consumer callback) {
return this.ThenFailure(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario And(String description, Consumer callback){
return this.And(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario And(String description, boolean continuous, Consumer callback){
var step = new And(description, continuous, callback);
getSteps().add(step);
return this;
}
@Override
public Scenario But(String description, Consumer callback){
return this.But(description, Step.TerminateAfterFailure, callback);
}
@Override
public Scenario But(String description, boolean continuous, Consumer callback){
var step = new But(description, continuous, callback);
getSteps().add(step);
return this;
}
public boolean isFromScenarioOutline(){
return null != from;
}
public ScenarioOutline getScenarioOutline(){
return from;
}
@Override
public DynamicNode DynamicExecute() {
return dynamicContainer("Scenario: " + this.getReplacedUnderscoresName(), dynamicExecuteImpl());
}
/**
* Creates dynamic nodes for each step of this RuntimeScenario.
*
* @return the list of dynamic nodes
*/
List dynamicExecuteImpl() {
this.preExecuteScenario();
List dynamicNodes = new ArrayList<>();
for(int i = 0; i < getSteps().size(); i++) {
var step = getSteps().get(i);
dynamicNodes.add(dynamicTest(format("[%s] %s %s", step.getResult().getExecutionOutcome().name(), step.getName(), step.description()), () -> dynamicExecuteStep(step)));
}
return dynamicNodes;
}
@Override
public void Execute() {
doExecute(true);
Optional ezSpecError = buildSpecError(getSteps());
if (ezSpecError.isPresent())
throw ezSpecError.get();
}
/**
* Executes all steps in this RuntimeScenario.
*
* @param throwException the throw exception
*/
void doExecute(boolean throwException) {
for (int i = 0; i < this.getSteps().size(); i++) {
try {
this.executeStep(this.getSteps().get(i));
} catch (Throwable e) {
if (!this.getSteps().get(i).isContinuousAfterFailure()) {
for (int k = i + 1; k < this.getSteps().size(); k++) {
this.getSteps().get(k).setResult(Result.Skip());
}
if(throwException){
throw e;
}
break;
}
}
}
}
@Override
public void accept(SpecificationElementVisitor visitor) {
if (!toString().isEmpty())
visitor.visit(this);
getSteps().forEach( step -> {
step.accept(visitor);
});
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(KEYWORD).append(": ").append(replaceName(getName())).append("\n");
for(var each : getSteps()){
sb.append(each.getName());
if (!each.description().isEmpty()){
sb.append(" ").append(Step.eraseReservedWords(each.description()));
}
sb.append("\n");
}
return sb.toString();
}
@Override
public void ExecuteConcurrently() {
int currentStep = doExecuteConcurrently();
Optional ezSpecError = buildSpecError(steps().subList(0, currentStep));
if (ezSpecError.isPresent()){
throw ezSpecError.get();
}
}
/**
* Executes steps in this RuntimeScenario concurrently.
*
* @return the current execution step.
*/
int doExecuteConcurrently() {
int nextGroupIndex = 0;
boolean isTerminated = false;
int currentStep;
for (currentStep = 0; currentStep < steps().size() && !isTerminated; ) {
switch (steps().get(currentStep)) {
case ConcurrentGroup givenWhenThen -> {
List> futures = new ArrayList<>();
List concurrentSteps = new ArrayList<>();
for (nextGroupIndex = currentStep + 1; nextGroupIndex < steps().size(); nextGroupIndex++) {
if (steps().get(nextGroupIndex) instanceof ConcurrentGroup) break;
}
for (int k = currentStep; k < nextGroupIndex; k++) {
concurrentSteps.add(steps().get(k));
}
currentStep = nextGroupIndex;
concurrentSteps.parallelStream().forEach(step -> {
CompletableFuture future =
CompletableFuture.runAsync(() -> executeStep(step));
futures.add(future);
});
futures.parallelStream().forEach(x -> {
try { x.get(); }
catch (Throwable e) {} // ignored, continue to execute
});
for (var step : concurrentSteps) {
if (!step.isContinuousAfterFailure() && step.getResult().isFailure()) {
isTerminated = true;
break;
}
}
}
default -> {throw new RuntimeException("A concurrent scenario must start with a Given, When, or Then");}
}
}
for(int k = nextGroupIndex; k < steps().size(); k++){
steps().get(k).setResult(Result.Skip());
}
return currentStep;
}
protected void invokeStep(Step step, String description, Consumer callback){
List arguments = Step.parseArguments(description);
this.runtime.setArguments(arguments);
if (Table.containsTable(description)){
this.lookupTable = new Table(description);
this.runtime.setAnonymousTable(lookupTable);
}
callback.accept(this.getEnvironment());
step.setResult(Result.Success());
}
protected void executeStep(Step step){
try {
invokeStep(step, step.description(), step.getCallback());
}
catch (PendingException e){
step.setResult(Result.Pending(e));
}
catch (Throwable e) {
step.setResult(Result.Failure(e));
throw e;
}
}
void preExecuteScenario() {
boolean continueExecute = true;
var steps = this.getSteps();
for(int i = 0; i < steps.size() && continueExecute; i++){
try{
executeStep(steps.get(i));
} catch (Throwable e) {
if (!steps.get(i).isContinuousAfterFailure()){
for(int k = i+1; k < steps.size(); k++){
steps.get(k).setResult(Result.Skip());
}
continueExecute = false;
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy