All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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