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

com.ocadotechnology.scenario.AbstractThenSteps Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 2017-2024 Ocado (Ocava)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ocadotechnology.scenario;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import javax.annotation.ParametersAreNonnullByDefault;

import com.google.common.collect.ImmutableSet;
import com.ocadotechnology.notification.Notification;
import com.ocadotechnology.simulation.Simulation;

/**
 * An abstract class which should be extended by each distinct set of then conditions that need to be implemented as
 * part of the testing package.  Each implementation should be generic on itself so that it can be correctly modified by
 * the decorator methods {@link AbstractThenSteps#unordered}, {@link AbstractThenSteps#never} etc
 */
@ParametersAreNonnullByDefault
public abstract class AbstractThenSteps> {
    private final StepManager stepManager;
    private final CheckStepExecutionType checkStepExecutionType;
    private final NotificationCache notificationCache;

    protected AbstractThenSteps(StepManager stepManager, NotificationCache notificationCache, CheckStepExecutionType checkStepExecutionType) {
        this.stepManager = stepManager;
        this.notificationCache = notificationCache;
        this.checkStepExecutionType = checkStepExecutionType;
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates will use a
     *          CheckStepExecutionType object created from the supplied NamedStepExecutionType object. Used in composite
     *          steps which contain an {@link AbstractThenSteps} instance.
     */
    public T modify(NamedStepExecutionType executionType) {
        return modify(executionType.getCheckStepExecutionType());
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates will use the
     *          supplied CheckStepExecutionType object. Used in composite steps which contain an
     *          {@link AbstractThenSteps} instance.
     */
    public T modify(CheckStepExecutionType executionType) {
        return create(stepManager, notificationCache, executionType);
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates can occur in any
     * order.  They will not block execution of other steps, but must complete for the test to pass.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T unordered() {
        return create(stepManager, notificationCache, CheckStepExecutionType.unordered().merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates can occur in any
     * order.  They will not block execution of other steps, but must complete for the test to pass.  The steps are
     * associated with the given name, which may be used to block and wait for them or remove them.  See {@link
     * UnorderedSteps}
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T unordered(String name) {
        return create(stepManager, notificationCache, CheckStepExecutionType.unordered(name).merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must never occur.
     * They will not block execution of other steps.  If the steps are ever completed, the test will fail.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T never() {
        return create(stepManager, notificationCache, CheckStepExecutionType.never().merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must never occur.
     * They will not block execution of other steps.  The steps are associated with the given name, which may be used to
     * remove them if the test requires that they only hold for a portion of the scenario.  See {@link UnorderedSteps}
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T never(String name) {
        return create(stepManager, notificationCache, CheckStepExecutionType.never(name).merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * within the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T within(Duration duration) {
        return within(duration.toNanos(), TimeUnit.NANOSECONDS);
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * within the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T within(double magnitude, TimeUnit timeUnit) {
        double timeLimit = TimeThenSteps.convertToUnit(magnitude, timeUnit, stepManager.getTimeUnit());
        return create(stepManager, notificationCache,
                CheckStepExecutionType.within(stepManager.simulation::getEventScheduler, timeLimit).merge(checkStepExecutionType));
    }

    /**
     * @param timeLimit - the time limit specified in the simulation scheduler's time units
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * within the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T within(StepFuture timeLimit) {
        return create(stepManager, notificationCache,
                CheckStepExecutionType.within(stepManager.simulation::getEventScheduler, timeLimit).merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * after exactly the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T afterExactly(double magnitude, TimeUnit timeUnit) {
        double delay = TimeThenSteps.convertToUnit(magnitude, timeUnit, stepManager.getTimeUnit());
        return afterExactly(StepFuture.of(delay));
    }

    /**
     * @param delay - the delay specified in the simulation scheduler's time units
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * after exactly the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T afterExactly(StepFuture delay) {
        return create(stepManager, notificationCache,
                CheckStepExecutionType.afterExactly(stepManager.simulation::getEventScheduler, delay).merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * after exactly the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T afterExactly(Duration duration) {
        return afterExactly(duration.toNanos(), TimeUnit.NANOSECONDS);
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * after at least the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T afterAtLeast(double magnitude, TimeUnit timeUnit) {
        double delay = TimeThenSteps.convertToUnit(magnitude, timeUnit, stepManager.getTimeUnit());
        return afterAtLeast(StepFuture.of(delay));
    }

    /**
     * @param delay - the delay specified in the simulation scheduler's time units
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * after at least the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T afterAtLeast(StepFuture delay) {
        return create(stepManager, notificationCache,
                CheckStepExecutionType.afterAtLeast(stepManager.simulation::getEventScheduler, delay).merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates must complete
     * after at least the specified duration from the time this step is executed.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     */
    public T afterAtLeast(Duration duration) {
        return afterAtLeast(duration.toNanos(), TimeUnit.NANOSECONDS);
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates are linked to
     * create an ordered sub-sequence with other steps of the same name.
     *
     * See OcavaScenarioTest/README.md file for explanation of what notifications the created step will receive.
     *
     * @throws IllegalStateException if called after an incompatible modifier
     * @throws NullPointerException if the name is null
     */
    public T sequenced(String name) {
        return create(
                stepManager,
                notificationCache,
                CheckStepExecutionType.sequenced(name).merge(checkStepExecutionType));
    }

    /**
     * @return an instance of the concrete sub-class of AbstractThenSteps where the steps it creates has the
     * {@code CheckStepExecutionType.isFailingStep} flag set to true. The failingStep flag is checked after the scenario test has completed
     * successfully or exceptionally and should be used in conjunction with {@link FixRequired}
     *
     * @throws IllegalStateException if called after a previous invocation of this method
     */
    public T failingStep() {
        return create(stepManager, notificationCache, checkStepExecutionType.merge(CheckStepExecutionType.failing()));
    }

    protected abstract T create(StepManager stepManager, NotificationCache notificationCache, CheckStepExecutionType executionType);

    protected  void addCheckStep(Class notificationType, Predicate predicate) {
        addCheckStep(new CheckStep<>(notificationType, notificationCache, predicate));
    }

     void addCheckStep(CheckStep checkStep) {
        stepManager.add(checkStep, checkStepExecutionType);
    }

    protected void addExecuteStep(Runnable runnable) {
        stepManager.add(new SimpleExecuteStep(runnable), checkStepExecutionType.getNamedStepExecutionType());
    }

    public void notificationsReceived(ImmutableSet> notifications) {
        notifications.forEach(this::notificationReceived);
    }

    public void notificationReceived(Class notificationClass) {
        addCheckStep(notificationClass, n -> true);
    }

    protected S getSimulation() {
        return stepManager.getSimulation();
    }
}