PerformableTree.java
- package org.jbehave.core.embedder;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.EnumMap;
- import java.util.HashMap;
- import java.util.LinkedHashMap;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.commons.lang3.builder.ToStringBuilder;
- import org.apache.commons.lang3.builder.ToStringStyle;
- import org.apache.commons.lang3.function.FailableRunnable;
- import org.jbehave.core.annotations.ScenarioType;
- import org.jbehave.core.annotations.Scope;
- import org.jbehave.core.configuration.Configuration;
- import org.jbehave.core.configuration.Keywords;
- import org.jbehave.core.embedder.MatchingStepMonitor.StepMatch;
- import org.jbehave.core.failures.BatchFailures;
- import org.jbehave.core.failures.FailingUponPendingStep;
- import org.jbehave.core.failures.IgnoringStepsFailure;
- import org.jbehave.core.failures.PendingStepFound;
- import org.jbehave.core.failures.PendingStepsFound;
- import org.jbehave.core.failures.RestartingScenarioFailure;
- import org.jbehave.core.failures.RestartingStoryFailure;
- import org.jbehave.core.failures.UUIDExceptionWrapper;
- import org.jbehave.core.model.ExamplesTable;
- import org.jbehave.core.model.GivenStories;
- import org.jbehave.core.model.GivenStory;
- import org.jbehave.core.model.Lifecycle;
- import org.jbehave.core.model.Lifecycle.ExecutionType;
- import org.jbehave.core.model.Meta;
- import org.jbehave.core.model.Scenario;
- import org.jbehave.core.model.Story;
- import org.jbehave.core.model.StoryDuration;
- import org.jbehave.core.reporters.ConcurrentStoryReporter;
- import org.jbehave.core.reporters.DelegatingStoryReporter;
- import org.jbehave.core.reporters.StoryReporter;
- import org.jbehave.core.steps.AbstractStepResult;
- import org.jbehave.core.steps.PendingStepMethodGenerator;
- import org.jbehave.core.steps.Step;
- import org.jbehave.core.steps.StepCollector;
- import org.jbehave.core.steps.StepCollector.Stage;
- import org.jbehave.core.steps.StepCreator.PendingStep;
- import org.jbehave.core.steps.StepCreator.StepExecutionType;
- import org.jbehave.core.steps.StepResult;
- import org.jbehave.core.steps.Timer;
- import org.jbehave.core.steps.Timing;
- import org.jbehave.core.steps.context.StepsContext;
- /**
- * Creates a tree of {@link Performable} objects for a set of stories, grouping
- * sets of performable steps for each story and scenario, and adding before and
- * after stories steps. The process has two phases:
- * <ol>
- * <li>The tree is populated with groups of performable steps when the stories
- * are added via the {@link #addStories(RunContext, List)} method.</li>
- * <li>The performable steps are then populated with the results when the
- * {@link #performBeforeOrAfterStories(RunContext, Stage)} and
- * {@link #perform(RunContext, Story)} methods are executed.</li>
- * </ol>
- * The tree is created per {@link RunContext} for the set of stories being run
- * but the individual stories can be performed concurrently.
- */
- public class PerformableTree {
- private static final Map<String, String> NO_PARAMETERS = new HashMap<>();
- private PerformableRoot root = new PerformableRoot();
- public PerformableRoot getRoot() {
- return root;
- }
- public void addStories(RunContext context, List<Story> stories) {
- root.addBeforeSteps(context.beforeStoriesSteps());
- for (Story story : stories) {
- root.add(performableStory(context, story, NO_PARAMETERS));
- }
- root.addAfterSteps(context.afterStoriesSteps());
- }
- private PerformableStory performableStory(RunContext context, Story story, Map<String, String> storyParameters) {
- PerformableStory performableStory = new PerformableStory(story, context.configuration().keywords(),
- context.givenStory());
- FilteredStory filteredStory = context.filter(story);
- Meta storyMeta = story.getMeta();
- boolean storyExcluded = filteredStory.excluded();
- performableStory.excluded(storyExcluded);
- if (!storyExcluded) {
- Map<Stage, PerformableSteps> lifecycleSteps = context.lifecycleSteps(story.getLifecycle(), storyMeta,
- Scope.STORY, new HashMap<>()); // TODO: Rework to use ExamplesTable row here
- performableStory.addBeforeSteps(ExecutionType.SYSTEM, context.beforeStorySteps(storyMeta));
- performableStory.addBeforeSteps(ExecutionType.USER, lifecycleSteps.get(Stage.BEFORE));
- performableStory.addAll(performableScenarios(context, story, storyParameters, filteredStory));
- // Add Given stories only if story contains non-filtered scenarios
- if (performableStory.hasIncludedScenarios()) {
- Map<String, String> givenStoryParameters = new HashMap<>(storyParameters);
- addMetaParameters(givenStoryParameters, storyMeta);
- performableStory.setGivenStories(performableGivenStories(context, story.getGivenStories(),
- givenStoryParameters));
- }
- performableStory.addAfterSteps(ExecutionType.USER, lifecycleSteps.get(Stage.AFTER));
- performableStory.addAfterSteps(ExecutionType.SYSTEM, context.afterStorySteps(storyMeta));
- }
- return performableStory;
- }
- private List<PerformableScenario> performableScenarios(RunContext context, Story story,
- Map<String, String> storyParameters, FilteredStory filterContext) {
- List<PerformableScenario> performableScenarios = new ArrayList<PerformableScenario>();
- ExamplesTable storyExamplesTable = story.getLifecycle().getExamplesTable();
- List<Map<String, String>> storyExamplesTableRows;
- if (storyExamplesTable.isEmpty()) {
- storyExamplesTableRows = new ArrayList<Map<String, String>>();
- storyExamplesTableRows.add(new HashMap<String, String>());
- } else {
- storyExamplesTableRows = storyExamplesTable.getRows();
- }
- // determine if before and after scenario steps should be run
- boolean runBeforeAndAfterScenarioSteps = shouldRunBeforeOrAfterScenarioSteps(context);
- for (Map<String, String> storyExamplesTableRow : storyExamplesTableRows) {
- for (Map.Entry<String, String> entry : storyExamplesTableRow.entrySet()) {
- entry.setValue((String)
- context.configuration().parameterConverters().convert(entry.getValue(), String.class));
- }
- }
- for (int i = 0; i < storyExamplesTableRows.size(); i++) {
- Map<String, String> storyExamplesTableRow = storyExamplesTableRows.get(i);
- for (Scenario scenario : story.getScenarios()) {
- Map<String, String> scenarioParameters = new HashMap<String, String>(storyParameters);
- PerformableScenario performableScenario = performableScenario(context, story, scenarioParameters,
- filterContext, runBeforeAndAfterScenarioSteps, scenario, storyExamplesTableRow,
- storyExamplesTable.isEmpty() ? -1 : i);
- if (performableScenario.isPerformable()) {
- performableScenarios.add(performableScenario);
- }
- }
- }
- return performableScenarios;
- }
- private PerformableScenario performableScenario(RunContext context, Story story,
- Map<String, String> storyParameters, FilteredStory filterContext, boolean runBeforeAndAfterScenarioSteps,
- Scenario originalScenario, Map<String, String> storyExamplesTableRow, int storyExamplesTableRowIndex) {
- Scenario scenario = originalScenario;
- if (storyExamplesTableRowIndex != -1) {
- scenario = new Scenario(scenario.getTitle() + " [" + (storyExamplesTableRowIndex + 1) + "]",
- scenario.getMeta(), scenario.getGivenStories(), scenario.getExamplesTable(), scenario.getSteps());
- }
- PerformableScenario performableScenario = new PerformableScenario(scenario, story.getPath());
- if (context.failureOccurred() && context.configuration().storyControls().skipScenariosAfterFailure()) {
- return performableScenario;
- }
- boolean scenarioExcluded = filterContext.excluded(originalScenario);
- performableScenario.excluded(scenarioExcluded);
- if (!scenarioExcluded) {
- Meta storyAndScenarioMeta = scenario.getMeta().inheritFrom(story.getMeta());
- if (isParameterisedByExamples(scenario)) {
- ExamplesTable table = scenario.getExamplesTable();
- List<Map<String, String>> tableRows = table.getRows();
- for (int exampleIndex = 0; exampleIndex < tableRows.size(); exampleIndex++) {
- Map<String, String> scenarioParameters = tableRows.get(exampleIndex);
- Map<String, String> scenarioParametersCopy = new HashMap<String, String>(storyParameters);
- scenarioParametersCopy.putAll(storyExamplesTableRow);
- scenarioParametersCopy.putAll(scenarioParameters);
- for (Map.Entry<String, String> scenarioParameterEntry : scenarioParametersCopy.entrySet()) {
- String value = context.configuration().parameterControls().replaceAllDelimitedNames(
- scenarioParameterEntry.getValue(), storyExamplesTableRow);
- scenarioParameterEntry.setValue((String) context.configuration().parameterConverters()
- .convert(value, String.class));
- }
- Map<String, String> parameters = new LinkedHashMap<String, String>(scenarioParametersCopy);
- for (Map.Entry<String, String> storyExamplesTableRowEntry: storyExamplesTableRow.entrySet()) {
- String key = storyExamplesTableRowEntry.getKey();
- if (!parameters.containsKey(key)) {
- parameters.put(key, storyExamplesTableRowEntry.getValue());
- }
- }
- addExampleScenario(context, scenario, performableScenario, story, storyAndScenarioMeta,
- parameters, exampleIndex);
- }
- } else if (!storyExamplesTableRow.isEmpty()) {
- addExampleScenario(context, scenario, performableScenario, story, storyAndScenarioMeta,
- new HashMap<>(storyExamplesTableRow), -1);
- } else { // plain old scenario
- NormalPerformableScenario normalScenario = normalScenario(context, story, scenario,
- storyAndScenarioMeta, storyParameters);
- // run before scenario steps, if allowed
- if (runBeforeAndAfterScenarioSteps) {
- normalScenario.addBeforeSteps(ExecutionType.SYSTEM,
- context.beforeScenarioSteps(storyAndScenarioMeta, ScenarioType.NORMAL));
- }
- performableScenario.useNormalScenario(normalScenario);
- // after scenario steps, if allowed
- if (runBeforeAndAfterScenarioSteps) {
- normalScenario.addAfterSteps(ExecutionType.SYSTEM,
- context.afterScenarioSteps(storyAndScenarioMeta, ScenarioType.NORMAL));
- }
- }
- }
- return performableScenario;
- }
- private void addExampleScenario(RunContext context, Scenario scenario, PerformableScenario performableScenario,
- Story story, Meta storyAndScenarioMeta, Map<String, String> parameters, int exampleIndex) {
- Meta exampleScenarioMeta = parameterMeta(context, parameters).inheritFrom(storyAndScenarioMeta);
- if (!context.filter().excluded(exampleScenarioMeta)) {
- ExamplePerformableScenario exampleScenario = exampleScenario(context, story, scenario,
- storyAndScenarioMeta, parameters, exampleIndex);
- performableScenario.addExampleScenario(exampleScenario);
- }
- }
- private NormalPerformableScenario normalScenario(RunContext context, Story story, Scenario scenario,
- Meta storyAndScenarioMeta, Map<String, String> storyParameters) {
- NormalPerformableScenario normalScenario = new NormalPerformableScenario(story);
- normalScenario.setStoryAndScenarioMeta(storyAndScenarioMeta);
- addStepsWithLifecycle(normalScenario, context, story.getLifecycle(), storyParameters,
- scenario, storyAndScenarioMeta);
- return normalScenario;
- }
- private ExamplePerformableScenario exampleScenario(RunContext context, Story story, Scenario scenario,
- Meta storyAndScenarioMeta, Map<String, String> parameters, int exampleIndex) {
- ExamplePerformableScenario exampleScenario = new ExamplePerformableScenario(story, parameters, exampleIndex);
- exampleScenario.setStoryAndScenarioMeta(storyAndScenarioMeta);
- exampleScenario.addBeforeSteps(ExecutionType.SYSTEM,
- context.beforeScenarioSteps(storyAndScenarioMeta, ScenarioType.EXAMPLE));
- addStepsWithLifecycle(exampleScenario, context, story.getLifecycle(), parameters, scenario,
- storyAndScenarioMeta);
- exampleScenario.addAfterSteps(ExecutionType.SYSTEM,
- context.afterScenarioSteps(storyAndScenarioMeta, ScenarioType.EXAMPLE));
- return exampleScenario;
- }
- private Meta parameterMeta(RunContext context, Map<String, String> parameters) {
- Meta meta = Meta.EMPTY;
- Keywords keywords = context.configuration().keywords();
- String metaText = keywords.meta();
- if (parameters.containsKey(metaText)) {
- meta = Meta.createMeta(parameters.get(metaText), keywords);
- }
- return meta;
- }
- private void addStepsWithLifecycle(AbstractPerformableScenario performableScenario, RunContext context,
- Lifecycle lifecycle, Map<String, String> parameters, Scenario scenario, Meta storyAndScenarioMeta) {
- Map<Stage, PerformableSteps> lifecycleSteps = context.lifecycleSteps(lifecycle, storyAndScenarioMeta,
- Scope.SCENARIO, parameters);
- performableScenario.addBeforeSteps(ExecutionType.SYSTEM,
- context.beforeScenarioSteps(storyAndScenarioMeta, ScenarioType.ANY));
- performableScenario.addBeforeSteps(ExecutionType.USER, lifecycleSteps.get(Stage.BEFORE));
- addMetaParameters(parameters, storyAndScenarioMeta);
- performableScenario.setGivenStories(performableGivenStories(context, scenario.getGivenStories(), parameters));
- performableScenario.addSteps(context.scenarioSteps(lifecycle, storyAndScenarioMeta, scenario, parameters));
- performableScenario.addAfterSteps(ExecutionType.USER, lifecycleSteps.get(Stage.AFTER));
- performableScenario.addAfterSteps(ExecutionType.SYSTEM,
- context.afterScenarioSteps(storyAndScenarioMeta, ScenarioType.ANY));
- }
- private PerformableGivenStories performableGivenStories(RunContext context, GivenStories givenStories,
- Map<String, String> parameters) {
- List<PerformableStory> stories = new ArrayList<>();
- if (givenStories.getPaths().size() > 0) {
- for (GivenStory givenStory : givenStories.getStories()) {
- RunContext childContext = context.childContextFor(givenStory);
- // run given story, using any parameters provided
- Story story = storyOfPath(context.configuration(), childContext.path());
- if (givenStory.hasAnchorParameters()) {
- story = storyWithMatchingScenarios(story, givenStory.getAnchorParameters());
- }
- parameters.putAll(givenStory.getParameters());
- stories.add(performableStory(childContext, story, parameters));
- }
- }
- return new PerformableGivenStories(stories, givenStories);
- }
- private Story storyWithMatchingScenarios(Story story, Map<String,String> parameters) {
- if (parameters.isEmpty()) {
- return story;
- }
- List<Scenario> scenarios = new ArrayList<>();
- for (Scenario scenario : story.getScenarios()) {
- if (matchesParameters(scenario, parameters)) {
- scenarios.add(scenario);
- }
- }
- return story.cloneWithScenarios(scenarios);
- }
- private boolean matchesParameters(Scenario scenario, Map<String, String> parameters) {
- Meta meta = scenario.getMeta();
- for (String name : parameters.keySet()) {
- if (meta.hasProperty(name)) {
- return meta.getProperty(name).equals(parameters.get(name));
- }
- }
- return false;
- }
- /**
- * Returns the parsed story from the given path
- *
- * @param configuration the Configuration used to run story
- * @param storyPath the story path
- * @return The parsed Story
- */
- public Story storyOfPath(Configuration configuration, String storyPath) {
- String storyAsText = configuration.storyLoader().loadStoryAsText(storyPath);
- return configuration.storyParser().parseStory(storyAsText, storyPath);
- }
- /**
- * Returns the parsed story from the given text
- *
- * @param configuration the Configuration used to run story
- * @param storyAsText the story text
- * @param storyId the story Id, which will be returned as story path
- * @return The parsed Story
- */
- public Story storyOfText(Configuration configuration, String storyAsText, String storyId) {
- return configuration.storyParser().parseStory(storyAsText, storyId);
- }
- private void addMetaParameters(Map<String, String> storyParameters, Meta meta) {
- for (String name : meta.getPropertyNames()) {
- if (!storyParameters.containsKey(name)) {
- storyParameters.put(name, meta.getProperty(name));
- }
- }
- }
- private boolean shouldRunBeforeOrAfterScenarioSteps(RunContext context) {
- return !context.configuration().storyControls().skipBeforeAndAfterScenarioStepsIfGivenStory()
- || !context.givenStory();
- }
- private boolean isParameterisedByExamples(Scenario scenario) {
- return !scenario.getExamplesTable().isEmpty() && !scenario.getGivenStories().requireParameters();
- }
- public interface State {
- State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter);
- RuntimeException getFailure();
- }
- private static final class FineSoFar implements State {
- @Override
- public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
- State state;
- StepResult result;
- int indexOfResult;
- try {
- result = step.perform(reporter, getFailure());
- UUIDExceptionWrapper stepFailure = result.getFailure();
- state = stepFailure == null ? this : new SomethingHappened(stepFailure);
- } catch (IgnoringStepsFailure e) {
- result = AbstractStepResult.ignorable(step.asString(keywords));
- state = new Ignoring(e);
- } catch (PendingStepFound e) {
- result = AbstractStepResult.pending((PendingStep) step);
- state = new Pending(e);
- }
- indexOfResult = results.size();
- results.add(result);
- List<Step> composedSteps = step.getComposedSteps();
- if (!composedSteps.isEmpty()) {
- reporter.beforeComposedSteps();
- for (Step composedStep : composedSteps) {
- state = state.run(composedStep, results, keywords, reporter);
- }
- reporter.afterComposedSteps();
- }
- if (state instanceof Ignoring) {
- result = AbstractStepResult.ignorable(step.asString(keywords));
- results.set(indexOfResult, result);
- }
- result.describeTo(reporter);
- return state;
- }
- @Override
- public UUIDExceptionWrapper getFailure() {
- return null;
- }
- }
- private static final class SomethingHappened implements State {
- private UUIDExceptionWrapper failure;
- public SomethingHappened(UUIDExceptionWrapper failure) {
- this.failure = failure;
- }
- @Override
- public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
- StepResult result = step.doNotPerform(reporter, getFailure());
- results.add(result);
- result.describeTo(reporter);
- return this;
- }
- @Override
- public UUIDExceptionWrapper getFailure() {
- return failure;
- }
- }
- private static final class Ignoring implements State {
- private final IgnoringStepsFailure failure;
- private Ignoring(IgnoringStepsFailure failure) {
- this.failure = failure;
- }
- @Override
- public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
- String stepAsString = step.asString(keywords);
- reporter.beforeStep(new org.jbehave.core.model.Step(StepExecutionType.IGNORABLE, stepAsString));
- StepResult result = AbstractStepResult.ignorable(stepAsString);
- results.add(result);
- result.describeTo(reporter);
- return this;
- }
- @Override
- public IgnoringStepsFailure getFailure() {
- return failure;
- }
- }
- private static final class Pending implements State {
- private final PendingStepFound failure;
- private Pending(PendingStepFound failure) {
- this.failure = failure;
- }
- @Override
- public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
- StepResult result = AbstractStepResult.pending((PendingStep) step);
- results.add(result);
- result.describeTo(reporter);
- return this;
- }
- @Override
- public PendingStepFound getFailure() {
- return failure;
- }
- }
- public void perform(RunContext context, Story story) {
- boolean restartingStory = false;
- try {
- performCancellable(context, story);
- if (context.restartStory()) {
- context.reporter().restartedStory(story, context.failure(context.state()));
- restartingStory = true;
- perform(context, story);
- }
- } catch (InterruptedException e) {
- if (context.isCancelled(story)) {
- context.reporter().storyCancelled(story, context.storyDuration(story));
- context.reporter().afterStory(context.givenStory);
- }
- throw new UUIDExceptionWrapper(e);
- } finally {
- if (!context.givenStory() && !restartingStory) {
- invokeDelayedReporters(context.reporter());
- }
- }
- }
- private void performCancellable(RunContext context, Story story) throws InterruptedException {
- if (context.configuration().storyControls().resetStateBeforeStory()) {
- context.resetState();
- context.resetFailures(story);
- }
- if (!story.getPath().equals(context.path())) {
- context.currentPath(story.getPath());
- }
- if (context.configuration.dryRun()) {
- context.reporter().dryRun();
- }
- root.get(story).perform(context);
- if (context.failureOccurred()) {
- context.addFailure(story);
- }
- }
- public void performBeforeOrAfterStories(RunContext context, Stage stage) {
- String storyPath = StringUtils.capitalize(stage.name().toLowerCase()) + "Stories";
- context.currentPath(storyPath);
- context.reporter().beforeStoriesSteps(stage);
- try {
- (stage == Stage.BEFORE ? root.beforeSteps : root.afterSteps).perform(context, false);
- } catch (InterruptedException e) {
- throw new UUIDExceptionWrapper(e);
- } finally {
- context.reporter().afterStoriesSteps(stage);
- invokeDelayedReporters(context.reporter());
- }
- }
- private void invokeDelayedReporters(StoryReporter reporter) {
- if (reporter instanceof ConcurrentStoryReporter) {
- ((ConcurrentStoryReporter) reporter).invokeDelayed();
- } else if (reporter instanceof DelegatingStoryReporter) {
- for (StoryReporter delegate : ((DelegatingStoryReporter) reporter).getDelegates()) {
- invokeDelayedReporters(delegate);
- }
- }
- }
- @Override
- public String toString() {
- return this.getClass().getSimpleName();
- }
- /**
- * The context for running a story.
- */
- public static class RunContext {
- private final Configuration configuration;
- private final boolean givenStory;
- private final AllStepCandidates allStepCandidates;
- private final EmbedderMonitor embedderMonitor;
- private final MetaFilter filter;
- private final BatchFailures failures;
- private final StepsContext stepsContext;
- private final Map<Story, StoryDuration> cancelledStories = new HashMap<>();
- private final Map<String, List<PendingStep>> pendingStories = new HashMap<>();
- private final ThreadLocal<StoryRunContext> storyRunContext = ThreadLocal.withInitial(StoryRunContext::new);
- public RunContext(Configuration configuration, AllStepCandidates allStepCandidates,
- EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) {
- this(configuration, allStepCandidates, embedderMonitor, filter, failures, false);
- }
- private RunContext(Configuration configuration, AllStepCandidates allStepCandidates,
- EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures, boolean givenStory) {
- this.configuration = configuration;
- this.givenStory = givenStory;
- this.allStepCandidates = allStepCandidates;
- this.embedderMonitor = embedderMonitor;
- this.filter = filter;
- this.failures = failures;
- this.stepsContext = configuration.stepsContext();
- resetState();
- }
- public StepsContext stepsContext() {
- return stepsContext;
- }
- public boolean restartScenario() {
- Throwable cause = failure(state());
- while (cause != null) {
- if (cause instanceof RestartingScenarioFailure) {
- return true;
- }
- cause = cause.getCause();
- }
- return false;
- }
- public boolean restartStory() {
- Throwable cause = failure(state());
- while (cause != null) {
- if (cause instanceof RestartingStoryFailure) {
- return true;
- }
- cause = cause.getCause();
- }
- return false;
- }
- public void currentPath(String path) {
- currentRunContext().pathIs(path);
- currentRunContext().reporterIs(configuration.storyReporter(path));
- }
- public void interruptIfCancelled() throws InterruptedException {
- for (Story story : cancelledStories.keySet()) {
- if (path().equals(story.getPath())) {
- throw new InterruptedException(path());
- }
- }
- }
- public boolean dryRun() {
- return configuration.storyControls().dryRun();
- }
- public Configuration configuration() {
- return configuration;
- }
- public boolean givenStory() {
- return givenStory;
- }
- public String path() {
- return currentRunContext().path();
- }
- public FilteredStory filter(Story story) {
- return new FilteredStory(filter, story, configuration.storyControls(), givenStory());
- }
- public MetaFilter filter() {
- return filter;
- }
- public PerformableSteps beforeStoriesSteps() {
- return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(
- allStepCandidates.getBeforeStoriesSteps()));
- }
- public PerformableSteps afterStoriesSteps() {
- return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(
- allStepCandidates.getAfterStoriesSteps()));
- }
- public PerformableSteps beforeStorySteps(Meta storyMeta) {
- return new PerformableSteps(configuration.stepCollector()
- .collectBeforeOrAfterStorySteps(allStepCandidates.getBeforeStorySteps(givenStory), storyMeta));
- }
- public PerformableSteps afterStorySteps(Meta storyMeta) {
- return new PerformableSteps(configuration.stepCollector()
- .collectBeforeOrAfterStorySteps(allStepCandidates.getAfterStorySteps(givenStory), storyMeta));
- }
- public PerformableSteps beforeScenarioSteps(Meta storyAndScenarioMeta, ScenarioType type) {
- return new PerformableSteps(configuration.stepCollector()
- .collectBeforeScenarioSteps(allStepCandidates.getBeforeScenarioSteps(type), storyAndScenarioMeta));
- }
- public PerformableSteps afterScenarioSteps(Meta storyAndScenarioMeta, ScenarioType type) {
- return new PerformableSteps(configuration.stepCollector()
- .collectAfterScenarioSteps(allStepCandidates.getAfterScenarioSteps(type), storyAndScenarioMeta));
- }
- private Map<Stage, PerformableSteps> lifecycleSteps(Lifecycle lifecycle, Meta meta, Scope scope,
- Map<String, String> parameters) {
- MatchingStepMonitor monitor = new MatchingStepMonitor(configuration.stepMonitor());
- Map<Stage, List<Step>> steps = configuration.stepCollector().collectLifecycleSteps(
- allStepCandidates.getRegularSteps(), lifecycle, meta, scope, parameters, monitor);
- Map<Stage, PerformableSteps> performableSteps = new EnumMap<>(Stage.class);
- for (Map.Entry<Stage, List<Step>> entry : steps.entrySet()) {
- performableSteps.put(entry.getKey(), new PerformableSteps(entry.getValue(), monitor.matched()));
- }
- return performableSteps;
- }
- private PerformableSteps scenarioSteps(Lifecycle lifecycle, Meta meta, Scenario scenario,
- Map<String, String> parameters) {
- MatchingStepMonitor monitor = new MatchingStepMonitor(configuration.stepMonitor());
- StepCollector stepCollector = configuration.stepCollector();
- Map<Stage, List<Step>> beforeOrAfterStepSteps = stepCollector.collectLifecycleSteps(
- allStepCandidates.getRegularSteps(), lifecycle, meta, Scope.STEP, parameters, monitor);
- List<Step> steps = new LinkedList<>();
- for (Step step : stepCollector.collectScenarioSteps(allStepCandidates.getRegularSteps(), scenario,
- parameters, monitor)) {
- steps.addAll(beforeOrAfterStepSteps.get(Stage.BEFORE));
- steps.add(step);
- steps.addAll(beforeOrAfterStepSteps.get(Stage.AFTER));
- }
- return new PerformableSteps(steps, monitor.matched());
- }
- public RunContext childContextFor(GivenStory givenStory) {
- RunContext child = new RunContext(configuration, allStepCandidates, embedderMonitor, filter,
- failures, true);
- child.currentRunContext().pathIs(configuration.pathCalculator().calculate(path(), givenStory.getPath()));
- return child;
- }
- public void cancelStory(Story story, StoryDuration storyDuration) {
- cancelledStories.put(story, storyDuration);
- }
- public boolean isCancelled(Story story) {
- return cancelledStories.containsKey(story);
- }
- public StoryDuration storyDuration(Story story) {
- return cancelledStories.get(story);
- }
- public State state() {
- return currentRunContext().state();
- }
- public void stateIs(State state) {
- currentRunContext().stateIs(state);
- }
- public boolean failureOccurred() {
- return failed(state());
- }
- public void resetState() {
- currentRunContext().resetState();
- }
- /**
- * Reset all the existing failures.
- */
- public void resetFailures() {
- this.failures.clear();
- }
- /**
- * Resets only the failures corresponding to the given story.
- * @param story the story for which we want to remove the failures.
- */
- public void resetFailures(Story story) {
- this.failures.entrySet()
- .removeIf(entry -> entry.getKey().equals(toBatchFailuresKey(story, entry.getValue())));
- }
- public StoryReporter reporter() {
- return currentRunContext().reporter();
- }
- public boolean failed(State state) {
- return !state.getClass().equals(FineSoFar.class);
- }
- public Throwable failure(State state) {
- if (failed(state)) {
- return state.getFailure().getCause();
- }
- return null;
- }
- public void addFailure(Story story) {
- addFailure(story, failure(state()));
- }
- public void addFailure(Story story, Throwable cause) {
- if (cause != null) {
- failures.put(toBatchFailuresKey(story, cause), cause);
- }
- }
- public void pendingSteps(List<PendingStep> pendingSteps) {
- if (!pendingSteps.isEmpty()) {
- pendingStories.put(path(), pendingSteps);
- }
- }
- public boolean hasPendingSteps() {
- return pendingStories.containsKey(path());
- }
- public boolean isStoryPending() {
- return pendingStories.containsKey(path());
- }
- public boolean hasFailed() {
- return failed(state());
- }
- public Status status(State initial) {
- if (isStoryPending()) {
- return Status.PENDING;
- } else if (failed(initial)) {
- return Status.NOT_PERFORMED;
- } else {
- return (hasFailed() ? Status.FAILED : Status.SUCCESSFUL);
- }
- }
- public MetaFilter getFilter() {
- return filter;
- }
- public BatchFailures getFailures() {
- return failures;
- }
-
- public EmbedderMonitor embedderMonitor() {
- return embedderMonitor;
- }
- private StoryRunContext currentRunContext() {
- return storyRunContext.get();
- }
- /**
- * Converts the given story and failure to the key to use to store the failure.
- * @param story the story where the failure occurred.
- * @param cause the failure that occurred.
- * @return the key to use to store the failure into the {@code BatchFailures}.
- */
- private String toBatchFailuresKey(Story story, Throwable cause) {
- return String.format("%s@%s", story.getPath(), Integer.toHexString(cause.hashCode()));
- }
- }
- private static class StoryRunContext {
- private State state;
- private String path;
- private StoryReporter reporter;
- private State state() {
- return state;
- }
- private void stateIs(State state) {
- this.state = state;
- }
- private void resetState() {
- this.state = new FineSoFar();
- }
- private String path() {
- return path;
- }
- private void pathIs(String path) {
- this.path = path;
- }
- public StoryReporter reporter() {
- return this.reporter;
- }
- private void reporterIs(StoryReporter reporter) {
- this.reporter = reporter;
- }
- }
- public static class FailureContext {
- List<Throwable> failures = new ArrayList<>();
- public void addFailure(Throwable failure) {
- failures.add(failure);
- }
- public List<Throwable> getFailures() {
- return failures;
- }
- }
- public interface ReportingFailures {
- void reportFailures(FailureContext context);
- }
- public interface Performable extends ReportingFailures {
- void perform(RunContext context) throws InterruptedException;
- }
- public static class PerformableRoot {
- private PerformableSteps beforeSteps = new PerformableSteps();
- private Map<String, PerformableStory> stories = new LinkedHashMap<>();
- private PerformableSteps afterSteps = new PerformableSteps();
- public void addBeforeSteps(PerformableSteps beforeSteps) {
- this.beforeSteps = beforeSteps;
- }
- public void add(PerformableStory performableStory) {
- stories.put(performableStory.getStory().getPath(), performableStory);
- }
- public void addAfterSteps(PerformableSteps afterSteps) {
- this.afterSteps = afterSteps;
- }
- public PerformableStory get(Story story) {
- PerformableStory performableStory = stories.get(story.getPath());
- if (performableStory != null) {
- return performableStory;
- }
- throw new RuntimeException("No performable story for path " + story.getPath());
- }
- public List<PerformableStory> getStories() {
- return new ArrayList<>(stories.values());
- }
- }
- public static enum Status {
- SUCCESSFUL, FAILED, PENDING, NOT_PERFORMED, EXCLUDED;
- }
- public abstract static class PerformableEntity implements Performable {
- private PerformableGivenStories givenStories;
- private final Map<Stage, Map<ExecutionType, PerformableSteps>> stageSteps;
- private final LifecycleStepsExecutionHook beforeHook;
- private final LifecycleStepsExecutionHook afterHook;
- public PerformableEntity(LifecycleStepsExecutionHook beforeHook, LifecycleStepsExecutionHook afterHook) {
- this.givenStories = new PerformableGivenStories(Collections.emptyList(), null);
- this.stageSteps = new EnumMap<>(Stage.class);
- this.beforeHook = beforeHook;
- this.afterHook = afterHook;
- }
- public void setGivenStories(PerformableGivenStories givenStories) {
- this.givenStories = givenStories;
- }
- public void addBeforeSteps(Lifecycle.ExecutionType type, PerformableSteps beforeSteps) {
- getBeforeSteps(type).add(beforeSteps);
- }
- public void addAfterSteps(Lifecycle.ExecutionType type, PerformableSteps afterSteps) {
- getAfterSteps(type).add(afterSteps);
- }
- protected void performWithStopExecutionExceptionHandling(FailableRunnable<InterruptedException> action,
- FailableRunnable<InterruptedException> afterHook) throws InterruptedException {
- try {
- action.run();
- } finally {
- afterHook.run();
- }
- }
- protected void performBeforeSteps(RunContext context) throws InterruptedException {
- performHookSteps(context, Stage.BEFORE, ExecutionType.SYSTEM);
- performHookSteps(context, Stage.BEFORE, ExecutionType.USER);
- }
- protected void performAfterSteps(RunContext context) throws InterruptedException {
- performHookSteps(context, Stage.AFTER, ExecutionType.USER);
- performHookSteps(context, Stage.AFTER, ExecutionType.SYSTEM);
- }
- protected PerformableSteps getBeforeSteps(Lifecycle.ExecutionType type) {
- return getPerformableSteps(Stage.BEFORE, type);
- }
- protected PerformableSteps getAfterSteps(Lifecycle.ExecutionType type) {
- return getPerformableSteps(Stage.AFTER, type);
- }
- private PerformableSteps getPerformableSteps(Stage stage, Lifecycle.ExecutionType type) {
- Map<ExecutionType, PerformableSteps> steps = stageSteps.computeIfAbsent(stage,
- t -> new EnumMap<>(ExecutionType.class));
- return steps.computeIfAbsent(type, s -> new PerformableSteps());
- }
- private void performHookSteps(RunContext context, Stage stage, Lifecycle.ExecutionType type)
- throws InterruptedException {
- StoryReporter reporter = context.reporter();
- this.beforeHook.perform(reporter, stage, type);
- getPerformableSteps(stage, type).perform(context, false);
- this.afterHook.perform(reporter, stage, type);
- }
- public PerformableGivenStories getGivenStories() {
- return givenStories;
- }
- }
- private interface LifecycleStepsExecutionHook {
- void perform(StoryReporter reporter, Stage stage, Lifecycle.ExecutionType type);
- }
- public static class PerformableStory extends PerformableEntity {
- private final Story story;
- private final transient Keywords keywords;
- private final boolean givenStory;
- private boolean excluded;
- private Status status;
- private Timing timing = new Timing();
- private List<PerformableScenario> scenarios = new ArrayList<>();
- public PerformableStory(Story story, Keywords keywords, boolean givenStory) {
- super(StoryReporter::beforeStorySteps, StoryReporter::afterStorySteps);
- this.story = story;
- this.keywords = keywords;
- this.givenStory = givenStory;
- }
- public void excluded(boolean excluded) {
- this.excluded = excluded;
- }
- public boolean isExcluded() {
- return excluded;
- }
- public Story getStory() {
- return story;
- }
- public Keywords getKeywords() {
- return keywords;
- }
- public boolean givenStory() {
- return givenStory;
- }
- public Status getStatus() {
- return status;
- }
- public Timing getTiming() {
- return timing;
- }
- public void add(PerformableScenario performableScenario) {
- scenarios.add(performableScenario);
- }
- public void addAll(List<PerformableScenario> performableScenarios) {
- scenarios.addAll(performableScenarios);
- }
- @Override
- public void perform(RunContext context) throws InterruptedException {
- if (isExcluded()) {
- context.reporter().storyExcluded(story, context.filter.asString());
- this.status = Status.EXCLUDED;
- }
- Timer timer = new Timer().start();
- try {
- context.stepsContext().resetStory();
- context.reporter().beforeStory(story, givenStory);
- context.reporter().narrative(story.getNarrative());
- context.reporter().lifecycle(story.getLifecycle());
- State state = context.state();
- performBeforeSteps(context);
- getGivenStories().perform(context);
- performWithStopExecutionExceptionHandling(
- () -> {
- if (!context.failureOccurred() || !context.configuration().storyControls()
- .skipStoryIfGivenStoryFailed()) {
- context.reporter().beforeScenarios();
- for (PerformableScenario scenario : scenarios) {
- scenario.perform(context);
- }
- }
- },
- () -> {
- context.reporter().afterScenarios();
- performAfterSteps(context);
- }
- );
- context.configuration().storyControls().resetCurrentStoryControls();
- if (context.restartStory()) {
- context.reporter().afterStory(true);
- } else {
- context.reporter().afterStory(givenStory);
- }
- this.status = context.status(state);
- } finally {
- timing = new Timing(timer.stop());
- }
- }
- @Override
- public void reportFailures(FailureContext context) {
- for (PerformableScenario scenario : scenarios) {
- scenario.reportFailures(context);
- }
- }
- public List<PerformableScenario> getScenarios() {
- return scenarios;
- }
- public boolean hasIncludedScenarios() {
- return getScenarios().stream().anyMatch(scenario -> !scenario.isExcluded());
- }
- }
- public static class PerformableScenario implements Performable {
- private final Scenario scenario;
- private final String storyPath;
- private boolean excluded;
- @SuppressWarnings("unused")
- private Status status;
- private Timing timing = new Timing();
- private NormalPerformableScenario normalScenario;
- private List<ExamplePerformableScenario> exampleScenarios;
- public PerformableScenario(Scenario scenario, String storyPath) {
- this.scenario = scenario;
- this.storyPath = storyPath;
- }
- public void useNormalScenario(NormalPerformableScenario normalScenario) {
- this.normalScenario = normalScenario;
- }
- public void addExampleScenario(ExamplePerformableScenario exampleScenario) {
- if (exampleScenarios == null) {
- exampleScenarios = new ArrayList<>();
- }
- exampleScenarios.add(exampleScenario);
- }
- public void excluded(boolean excluded) {
- this.excluded = excluded;
- }
- public boolean isExcluded() {
- return excluded;
- }
- public Status getStatus() {
- return status;
- }
- public Timing getTiming() {
- return timing;
- }
- public Scenario getScenario() {
- return scenario;
- }
- public String getStoryPath() {
- return storyPath;
- }
- public Throwable getFailure() {
- FailureContext context = new FailureContext();
- reportFailures(context);
- List<Throwable> failures = context.getFailures();
- if (failures.size() > 0) {
- return failures.get(0);
- }
- return null;
- }
- public boolean hasNormalScenario() {
- return normalScenario != null;
- }
- public boolean hasExamples() {
- return exampleScenarios != null && exampleScenarios.size() > 0;
- }
- public boolean isPerformable() {
- return hasNormalScenario() || hasExamples() || isExcluded();
- }
- public List<ExamplePerformableScenario> getExamples() {
- return exampleScenarios;
- }
- @Override
- public void perform(RunContext context) throws InterruptedException {
- if (isExcluded()) {
- context.embedderMonitor().scenarioExcluded(scenario, context.filter());
- return;
- }
- Timer timer = new Timer().start();
- try {
- context.stepsContext().resetScenario();
- context.reporter().beforeScenario(scenario);
- State state = context.state();
- if (hasExamples()) {
- context.reporter().beforeExamples(scenario.getSteps(),
- scenario.getExamplesTable());
- for (ExamplePerformableScenario exampleScenario : exampleScenarios) {
- exampleScenario.perform(context);
- }
- context.reporter().afterExamples();
- } else {
- context.stepsContext().resetExample();
- normalScenario.perform(context);
- }
- this.status = context.status(state);
- } finally {
- timing = new Timing(timer.stop());
- context.reporter().afterScenario(timing);
- }
- }
- @Override
- public void reportFailures(FailureContext context) {
- if (hasExamples()) {
- for (ExamplePerformableScenario exampleScenario : exampleScenarios) {
- exampleScenario.reportFailures(context);
- }
- } else {
- normalScenario.reportFailures(context);
- }
- }
- }
- public abstract static class AbstractPerformableScenario extends PerformableEntity {
- private transient Story story;
- protected final Map<String, String> parameters;
- protected final PerformableSteps steps = new PerformableSteps();
- private Meta storyAndScenarioMeta = new Meta();
- protected AbstractPerformableScenario(Story story) {
- this(story, new HashMap<>());
- }
- protected AbstractPerformableScenario(Story story, Map<String, String> parameters) {
- super(StoryReporter::beforeScenarioSteps, StoryReporter::afterScenarioSteps);
- this.story = story;
- this.parameters = parameters;
- }
- public void addSteps(PerformableSteps steps) {
- this.steps.add(steps);
- }
- public Map<String, String> getParameters() {
- return parameters;
- }
- protected void performScenario(RunContext context) throws InterruptedException {
- performBeforeSteps(context);
- getGivenStories().perform(context);
- performWithStopExecutionExceptionHandling(
- () -> {
- boolean restart = true;
- while (restart) {
- restart = false;
- try {
- context.reporter().beforeScenarioSteps(null, null);
- steps.perform(context, true);
- } catch (RestartingScenarioFailure e) {
- restart = true;
- }
- }
- },
- () -> {
- context.reporter().afterScenarioSteps(null, null);
- performAfterSteps(context);
- }
- );
- }
- @Override
- public void reportFailures(FailureContext context) {
- getBeforeSteps(ExecutionType.SYSTEM).reportFailures(context);
- getBeforeSteps(ExecutionType.USER).reportFailures(context);
- steps.reportFailures(context);
- getAfterSteps(ExecutionType.USER).reportFailures(context);
- getAfterSteps(ExecutionType.SYSTEM).reportFailures(context);
- }
- protected void resetStateIfConfigured(RunContext context) {
- if (context.configuration().storyControls().resetStateBeforeScenario()) {
- if (context.failureOccurred()) {
- context.addFailure(story);
- }
- context.resetState();
- }
- }
- public Meta getStoryAndScenarioMeta() {
- return storyAndScenarioMeta;
- }
- public void setStoryAndScenarioMeta(Meta storyAndScenarioMeta) {
- this.storyAndScenarioMeta = storyAndScenarioMeta;
- }
- }
- public static class NormalPerformableScenario extends AbstractPerformableScenario {
- public NormalPerformableScenario(Story story) {
- super(story);
- }
- @Override
- public void perform(RunContext context) throws InterruptedException {
- resetStateIfConfigured(context);
- performScenario(context);
- }
- }
- public static class ExamplePerformableScenario extends AbstractPerformableScenario {
- private final int exampleIndex;
- public ExamplePerformableScenario(Story story, Map<String, String> exampleParameters, int exampleIndex) {
- super(story, exampleParameters);
- this.exampleIndex = exampleIndex;
- }
- @Override
- public void perform(RunContext context) throws InterruptedException {
- Meta parameterMeta = parameterMeta(context.configuration().keywords(), parameters).inheritFrom(
- getStoryAndScenarioMeta());
- if (parameterMeta.isEmpty() || !context.filter().excluded(parameterMeta)) {
- resetStateIfConfigured(context);
- context.stepsContext().resetExample();
- context.reporter().example(parameters, exampleIndex);
- performScenario(context);
- }
- }
- private Meta parameterMeta(Keywords keywords, Map<String, String> parameters) {
- String meta = keywords.meta();
- if (parameters.containsKey(meta)) {
- return Meta.createMeta(parameters.get(meta), keywords);
- }
- return Meta.EMPTY;
- }
- }
- public static class PerformableGivenStories implements Performable {
- private final List<PerformableStory> performableGivenStories;
- private final GivenStories givenStories;
- public PerformableGivenStories(List<PerformableStory> performableGivenStories, GivenStories givenStories) {
- this.performableGivenStories = performableGivenStories;
- this.givenStories = givenStories;
- }
- @Override
- public void perform(RunContext context) throws InterruptedException {
- if (performableGivenStories.size() > 0) {
- StoryReporter storyReporter = context.reporter();
- storyReporter.beforeGivenStories();
- storyReporter.givenStories(givenStories);
- for (PerformableStory story : performableGivenStories) {
- story.perform(context);
- }
- storyReporter.afterGivenStories();
- }
- }
- @Override
- public void reportFailures(FailureContext context) {
- }
- }
- public static class PerformableSteps implements ReportingFailures {
- private final transient List<Step> steps;
- private final transient List<PendingStep> pendingSteps;
- private List<StepMatch> matches;
- private List<StepResult> results;
- public PerformableSteps() {
- this(null);
- }
- public PerformableSteps(List<Step> steps) {
- this(steps, null);
- }
- public PerformableSteps(List<Step> steps, List<StepMatch> stepMatches) {
- this.steps = steps != null ? steps : new ArrayList<Step>();
- this.pendingSteps = pendingSteps();
- this.matches = stepMatches;
- }
- public void add(PerformableSteps performableSteps) {
- this.steps.addAll(performableSteps.steps);
- this.pendingSteps.addAll(performableSteps.pendingSteps);
- if (performableSteps.matches != null) {
- if (this.matches == null) {
- this.matches = new ArrayList<>();
- }
- this.matches.addAll(performableSteps.matches);
- }
- }
-
- public void perform(RunContext context, boolean interruptIfCancelled) throws InterruptedException {
- if (steps.size() == 0) {
- return;
- }
- Keywords keywords = context.configuration().keywords();
- State state = context.state();
- State originalState = state;
- StoryReporter reporter = context.reporter();
- results = new ArrayList<>();
- for (Step step : steps) {
- try {
- if (interruptIfCancelled) {
- context.interruptIfCancelled();
- }
- state = state.run(step, results, keywords, reporter);
- } catch (RestartingScenarioFailure e) {
- reporter.restarted(step.asString(keywords), e);
- throw e;
- }
- }
- context.stateIs(state instanceof Ignoring ? originalState : state);
- context.pendingSteps(pendingSteps);
- generatePendingStepMethods(context, pendingSteps);
- }
- @Override
- public void reportFailures(FailureContext context) {
- // Results can be null if the steps are not executed
- if (results == null) {
- return;
- }
- for (StepResult result : results) {
- if (result instanceof AbstractStepResult.Failed) {
- context.addFailure(result.getFailure());
- }
- }
- }
- private List<PendingStep> pendingSteps() {
- List<PendingStep> pending = new ArrayList<>();
- for (Step step : steps) {
- if (step instanceof PendingStep) {
- pending.add((PendingStep) step);
- }
- }
- return pending;
- }
- private void generatePendingStepMethods(RunContext context, List<PendingStep> pendingSteps) {
- if (!pendingSteps.isEmpty()) {
- PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration()
- .keywords());
- List<String> methods = new ArrayList<>();
- for (PendingStep pendingStep : pendingSteps) {
- if (!pendingStep.annotated()) {
- String generatedMethod = generator.generateMethod(pendingStep);
- pendingStep.setPendingMethod(generatedMethod);
- methods.add(generatedMethod);
- }
- }
- context.reporter().pendingMethods(methods);
- if (context.configuration().pendingStepStrategy() instanceof FailingUponPendingStep) {
- throw new PendingStepsFound(pendingSteps);
- }
- }
- }
- @Override
- public String toString() {
- return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
- }
- }
- public RunContext newRunContext(Configuration configuration, AllStepCandidates allStepCandidates,
- EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) {
- return new RunContext(configuration, allStepCandidates, embedderMonitor, filter, failures);
- }
- }