PerformableTree.java

  1. package org.jbehave.core.embedder;

  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.EnumMap;
  5. import java.util.HashMap;
  6. import java.util.LinkedHashMap;
  7. import java.util.LinkedList;
  8. import java.util.List;
  9. import java.util.Map;

  10. import org.apache.commons.lang3.StringUtils;
  11. import org.apache.commons.lang3.builder.ToStringBuilder;
  12. import org.apache.commons.lang3.builder.ToStringStyle;
  13. import org.apache.commons.lang3.function.FailableRunnable;
  14. import org.jbehave.core.annotations.ScenarioType;
  15. import org.jbehave.core.annotations.Scope;
  16. import org.jbehave.core.configuration.Configuration;
  17. import org.jbehave.core.configuration.Keywords;
  18. import org.jbehave.core.embedder.MatchingStepMonitor.StepMatch;
  19. import org.jbehave.core.failures.BatchFailures;
  20. import org.jbehave.core.failures.FailingUponPendingStep;
  21. import org.jbehave.core.failures.IgnoringStepsFailure;
  22. import org.jbehave.core.failures.PendingStepFound;
  23. import org.jbehave.core.failures.PendingStepsFound;
  24. import org.jbehave.core.failures.RestartingScenarioFailure;
  25. import org.jbehave.core.failures.RestartingStoryFailure;
  26. import org.jbehave.core.failures.UUIDExceptionWrapper;
  27. import org.jbehave.core.model.ExamplesTable;
  28. import org.jbehave.core.model.GivenStories;
  29. import org.jbehave.core.model.GivenStory;
  30. import org.jbehave.core.model.Lifecycle;
  31. import org.jbehave.core.model.Lifecycle.ExecutionType;
  32. import org.jbehave.core.model.Meta;
  33. import org.jbehave.core.model.Scenario;
  34. import org.jbehave.core.model.Story;
  35. import org.jbehave.core.model.StoryDuration;
  36. import org.jbehave.core.reporters.ConcurrentStoryReporter;
  37. import org.jbehave.core.reporters.DelegatingStoryReporter;
  38. import org.jbehave.core.reporters.StoryReporter;
  39. import org.jbehave.core.steps.AbstractStepResult;
  40. import org.jbehave.core.steps.PendingStepMethodGenerator;
  41. import org.jbehave.core.steps.Step;
  42. import org.jbehave.core.steps.StepCollector;
  43. import org.jbehave.core.steps.StepCollector.Stage;
  44. import org.jbehave.core.steps.StepCreator.PendingStep;
  45. import org.jbehave.core.steps.StepCreator.StepExecutionType;
  46. import org.jbehave.core.steps.StepResult;
  47. import org.jbehave.core.steps.Timer;
  48. import org.jbehave.core.steps.Timing;
  49. import org.jbehave.core.steps.context.StepsContext;

  50. /**
  51.  * Creates a tree of {@link Performable} objects for a set of stories, grouping
  52.  * sets of performable steps for each story and scenario, and adding before and
  53.  * after stories steps. The process has two phases:
  54.  * <ol>
  55.  * <li>The tree is populated with groups of performable steps when the stories
  56.  * are added via the {@link #addStories(RunContext, List)} method.</li>
  57.  * <li>The performable steps are then populated with the results when the
  58.  * {@link #performBeforeOrAfterStories(RunContext, Stage)} and
  59.  * {@link #perform(RunContext, Story)} methods are executed.</li>
  60.  * </ol>
  61.  * The tree is created per {@link RunContext} for the set of stories being run
  62.  * but the individual stories can be performed concurrently.
  63.  */
  64. public class PerformableTree {

  65.     private static final Map<String, String> NO_PARAMETERS = new HashMap<>();

  66.     private PerformableRoot root = new PerformableRoot();

  67.     public PerformableRoot getRoot() {
  68.         return root;
  69.     }

  70.     public void addStories(RunContext context, List<Story> stories) {
  71.         root.addBeforeSteps(context.beforeStoriesSteps());
  72.         for (Story story : stories) {
  73.             root.add(performableStory(context, story, NO_PARAMETERS));
  74.         }
  75.         root.addAfterSteps(context.afterStoriesSteps());
  76.     }

  77.     private PerformableStory performableStory(RunContext context, Story story, Map<String, String> storyParameters) {
  78.         PerformableStory performableStory = new PerformableStory(story, context.configuration().keywords(),
  79.                 context.givenStory());

  80.         FilteredStory filteredStory = context.filter(story);
  81.         Meta storyMeta = story.getMeta();
  82.         boolean storyExcluded = filteredStory.excluded();

  83.         performableStory.excluded(storyExcluded);

  84.         if (!storyExcluded) {

  85.             Map<Stage, PerformableSteps> lifecycleSteps = context.lifecycleSteps(story.getLifecycle(), storyMeta,
  86.                     Scope.STORY, new HashMap<>()); // TODO: Rework to use ExamplesTable row here

  87.             performableStory.addBeforeSteps(ExecutionType.SYSTEM, context.beforeStorySteps(storyMeta));
  88.             performableStory.addBeforeSteps(ExecutionType.USER, lifecycleSteps.get(Stage.BEFORE));
  89.             performableStory.addAll(performableScenarios(context, story, storyParameters, filteredStory));

  90.             // Add Given stories only if story contains non-filtered scenarios
  91.             if (performableStory.hasIncludedScenarios()) {
  92.                 Map<String, String> givenStoryParameters = new HashMap<>(storyParameters);
  93.                 addMetaParameters(givenStoryParameters, storyMeta);
  94.                 performableStory.setGivenStories(performableGivenStories(context, story.getGivenStories(),
  95.                         givenStoryParameters));
  96.             }

  97.             performableStory.addAfterSteps(ExecutionType.USER, lifecycleSteps.get(Stage.AFTER));
  98.             performableStory.addAfterSteps(ExecutionType.SYSTEM, context.afterStorySteps(storyMeta));

  99.         }

  100.         return performableStory;
  101.     }

  102.     private List<PerformableScenario> performableScenarios(RunContext context, Story story,
  103.             Map<String, String> storyParameters, FilteredStory filterContext) {
  104.         List<PerformableScenario> performableScenarios = new ArrayList<PerformableScenario>();

  105.         ExamplesTable storyExamplesTable = story.getLifecycle().getExamplesTable();
  106.         List<Map<String, String>> storyExamplesTableRows;
  107.         if (storyExamplesTable.isEmpty()) {
  108.             storyExamplesTableRows = new ArrayList<Map<String, String>>();
  109.             storyExamplesTableRows.add(new HashMap<String, String>());
  110.         } else {
  111.             storyExamplesTableRows = storyExamplesTable.getRows();
  112.         }

  113.         // determine if before and after scenario steps should be run
  114.         boolean runBeforeAndAfterScenarioSteps = shouldRunBeforeOrAfterScenarioSteps(context);
  115.         for (Map<String, String> storyExamplesTableRow : storyExamplesTableRows) {
  116.             for (Map.Entry<String, String> entry : storyExamplesTableRow.entrySet()) {
  117.                 entry.setValue((String)
  118.                         context.configuration().parameterConverters().convert(entry.getValue(), String.class));
  119.             }
  120.         }
  121.         for (int i = 0; i < storyExamplesTableRows.size(); i++) {
  122.             Map<String, String> storyExamplesTableRow = storyExamplesTableRows.get(i);
  123.             for (Scenario scenario : story.getScenarios()) {
  124.                 Map<String, String> scenarioParameters = new HashMap<String, String>(storyParameters);
  125.                 PerformableScenario performableScenario = performableScenario(context, story, scenarioParameters,
  126.                         filterContext, runBeforeAndAfterScenarioSteps, scenario, storyExamplesTableRow,
  127.                         storyExamplesTable.isEmpty() ? -1 : i);
  128.                 if (performableScenario.isPerformable()) {
  129.                     performableScenarios.add(performableScenario);
  130.                 }
  131.             }
  132.         }
  133.         return performableScenarios;
  134.     }

  135.     private PerformableScenario performableScenario(RunContext context, Story story,
  136.             Map<String, String> storyParameters, FilteredStory filterContext, boolean runBeforeAndAfterScenarioSteps,
  137.             Scenario originalScenario, Map<String, String> storyExamplesTableRow, int storyExamplesTableRowIndex) {
  138.         Scenario scenario = originalScenario;
  139.         if (storyExamplesTableRowIndex != -1) {
  140.             scenario = new Scenario(scenario.getTitle() + " [" + (storyExamplesTableRowIndex + 1) + "]",
  141.                 scenario.getMeta(),  scenario.getGivenStories(), scenario.getExamplesTable(), scenario.getSteps());
  142.         }
  143.         PerformableScenario performableScenario = new PerformableScenario(scenario, story.getPath());
  144.         if (context.failureOccurred() && context.configuration().storyControls().skipScenariosAfterFailure()) {
  145.             return performableScenario;
  146.         }

  147.         boolean scenarioExcluded = filterContext.excluded(originalScenario);

  148.         performableScenario.excluded(scenarioExcluded);

  149.         if (!scenarioExcluded) {
  150.             Meta storyAndScenarioMeta = scenario.getMeta().inheritFrom(story.getMeta());

  151.             if (isParameterisedByExamples(scenario)) {
  152.                 ExamplesTable table = scenario.getExamplesTable();
  153.                 List<Map<String, String>> tableRows = table.getRows();
  154.                 for (int exampleIndex = 0; exampleIndex < tableRows.size(); exampleIndex++) {
  155.                     Map<String, String> scenarioParameters = tableRows.get(exampleIndex);
  156.                     Map<String, String> scenarioParametersCopy = new HashMap<String, String>(storyParameters);
  157.                     scenarioParametersCopy.putAll(storyExamplesTableRow);
  158.                     scenarioParametersCopy.putAll(scenarioParameters);
  159.                     for (Map.Entry<String, String> scenarioParameterEntry : scenarioParametersCopy.entrySet()) {
  160.                         String value = context.configuration().parameterControls().replaceAllDelimitedNames(
  161.                                 scenarioParameterEntry.getValue(), storyExamplesTableRow);
  162.                         scenarioParameterEntry.setValue((String) context.configuration().parameterConverters()
  163.                                  .convert(value, String.class));
  164.                     }
  165.                     Map<String, String> parameters = new LinkedHashMap<String, String>(scenarioParametersCopy);
  166.                     for (Map.Entry<String, String> storyExamplesTableRowEntry: storyExamplesTableRow.entrySet()) {
  167.                         String key = storyExamplesTableRowEntry.getKey();
  168.                         if (!parameters.containsKey(key)) {
  169.                             parameters.put(key, storyExamplesTableRowEntry.getValue());
  170.                         }
  171.                     }
  172.                     addExampleScenario(context, scenario, performableScenario, story, storyAndScenarioMeta,
  173.                             parameters, exampleIndex);
  174.                 }
  175.             } else if (!storyExamplesTableRow.isEmpty()) {
  176.                 addExampleScenario(context, scenario, performableScenario, story, storyAndScenarioMeta,
  177.                         new HashMap<>(storyExamplesTableRow), -1);
  178.             } else { // plain old scenario
  179.                 NormalPerformableScenario normalScenario = normalScenario(context, story, scenario,
  180.                         storyAndScenarioMeta, storyParameters);

  181.                 // run before scenario steps, if allowed
  182.                 if (runBeforeAndAfterScenarioSteps) {
  183.                     normalScenario.addBeforeSteps(ExecutionType.SYSTEM,
  184.                             context.beforeScenarioSteps(storyAndScenarioMeta, ScenarioType.NORMAL));
  185.                 }
  186.                 performableScenario.useNormalScenario(normalScenario);
  187.                 // after scenario steps, if allowed
  188.                 if (runBeforeAndAfterScenarioSteps) {
  189.                     normalScenario.addAfterSteps(ExecutionType.SYSTEM,
  190.                             context.afterScenarioSteps(storyAndScenarioMeta, ScenarioType.NORMAL));
  191.                 }
  192.             }
  193.         }
  194.         return performableScenario;
  195.     }

  196.     private void addExampleScenario(RunContext context, Scenario scenario, PerformableScenario performableScenario,
  197.             Story story, Meta storyAndScenarioMeta, Map<String, String> parameters, int exampleIndex) {
  198.         Meta exampleScenarioMeta = parameterMeta(context, parameters).inheritFrom(storyAndScenarioMeta);

  199.         if (!context.filter().excluded(exampleScenarioMeta)) {
  200.             ExamplePerformableScenario exampleScenario = exampleScenario(context, story, scenario,
  201.                     storyAndScenarioMeta, parameters, exampleIndex);
  202.             performableScenario.addExampleScenario(exampleScenario);
  203.         }
  204.     }

  205.     private NormalPerformableScenario normalScenario(RunContext context, Story story, Scenario scenario,
  206.             Meta storyAndScenarioMeta, Map<String, String> storyParameters) {
  207.         NormalPerformableScenario normalScenario = new NormalPerformableScenario(story);
  208.         normalScenario.setStoryAndScenarioMeta(storyAndScenarioMeta);
  209.         addStepsWithLifecycle(normalScenario, context, story.getLifecycle(), storyParameters,
  210.                 scenario, storyAndScenarioMeta);
  211.         return normalScenario;
  212.     }

  213.     private ExamplePerformableScenario exampleScenario(RunContext context, Story story, Scenario scenario,
  214.             Meta storyAndScenarioMeta, Map<String, String> parameters, int exampleIndex) {
  215.         ExamplePerformableScenario exampleScenario = new ExamplePerformableScenario(story, parameters, exampleIndex);
  216.         exampleScenario.setStoryAndScenarioMeta(storyAndScenarioMeta);
  217.         exampleScenario.addBeforeSteps(ExecutionType.SYSTEM,
  218.                 context.beforeScenarioSteps(storyAndScenarioMeta, ScenarioType.EXAMPLE));
  219.         addStepsWithLifecycle(exampleScenario, context, story.getLifecycle(), parameters, scenario,
  220.                 storyAndScenarioMeta);
  221.         exampleScenario.addAfterSteps(ExecutionType.SYSTEM,
  222.                 context.afterScenarioSteps(storyAndScenarioMeta, ScenarioType.EXAMPLE));
  223.         return exampleScenario;
  224.     }

  225.     private Meta parameterMeta(RunContext context, Map<String, String> parameters) {
  226.         Meta meta = Meta.EMPTY;
  227.         Keywords keywords = context.configuration().keywords();
  228.         String metaText = keywords.meta();
  229.         if (parameters.containsKey(metaText)) {
  230.             meta = Meta.createMeta(parameters.get(metaText), keywords);
  231.         }
  232.         return meta;
  233.     }

  234.     private void addStepsWithLifecycle(AbstractPerformableScenario performableScenario, RunContext context,
  235.             Lifecycle lifecycle, Map<String, String> parameters, Scenario scenario, Meta storyAndScenarioMeta) {
  236.         Map<Stage, PerformableSteps> lifecycleSteps = context.lifecycleSteps(lifecycle, storyAndScenarioMeta,
  237.                 Scope.SCENARIO, parameters);

  238.         performableScenario.addBeforeSteps(ExecutionType.SYSTEM,
  239.                 context.beforeScenarioSteps(storyAndScenarioMeta, ScenarioType.ANY));
  240.         performableScenario.addBeforeSteps(ExecutionType.USER, lifecycleSteps.get(Stage.BEFORE));
  241.         addMetaParameters(parameters, storyAndScenarioMeta);
  242.         performableScenario.setGivenStories(performableGivenStories(context, scenario.getGivenStories(), parameters));
  243.         performableScenario.addSteps(context.scenarioSteps(lifecycle, storyAndScenarioMeta, scenario, parameters));
  244.         performableScenario.addAfterSteps(ExecutionType.USER, lifecycleSteps.get(Stage.AFTER));
  245.         performableScenario.addAfterSteps(ExecutionType.SYSTEM,
  246.                 context.afterScenarioSteps(storyAndScenarioMeta, ScenarioType.ANY));
  247.     }

  248.     private PerformableGivenStories performableGivenStories(RunContext context, GivenStories givenStories,
  249.             Map<String, String> parameters) {
  250.         List<PerformableStory> stories = new ArrayList<>();
  251.         if (givenStories.getPaths().size() > 0) {
  252.             for (GivenStory givenStory : givenStories.getStories()) {
  253.                 RunContext childContext = context.childContextFor(givenStory);
  254.                 // run given story, using any parameters provided
  255.                 Story story = storyOfPath(context.configuration(), childContext.path());                
  256.                 if (givenStory.hasAnchorParameters()) {
  257.                     story = storyWithMatchingScenarios(story, givenStory.getAnchorParameters());
  258.                 }
  259.                 parameters.putAll(givenStory.getParameters());
  260.                 stories.add(performableStory(childContext, story, parameters));
  261.             }
  262.         }
  263.         return new PerformableGivenStories(stories, givenStories);
  264.     }

  265.     private Story storyWithMatchingScenarios(Story story, Map<String,String> parameters) {
  266.         if (parameters.isEmpty()) {
  267.             return story;
  268.         }
  269.         List<Scenario> scenarios = new ArrayList<>();
  270.         for (Scenario scenario : story.getScenarios()) {
  271.             if (matchesParameters(scenario, parameters)) {
  272.                 scenarios.add(scenario);
  273.             }
  274.         }
  275.         return story.cloneWithScenarios(scenarios);
  276.     }

  277.     private boolean matchesParameters(Scenario scenario, Map<String, String> parameters) {
  278.         Meta meta = scenario.getMeta();
  279.         for (String name : parameters.keySet()) {
  280.             if (meta.hasProperty(name)) {
  281.                 return meta.getProperty(name).equals(parameters.get(name));
  282.             }
  283.         }
  284.         return false;
  285.     }

  286.     /**
  287.      * Returns the parsed story from the given path
  288.      *
  289.      * @param configuration the Configuration used to run story
  290.      * @param storyPath the story path
  291.      * @return The parsed Story
  292.      */
  293.     public Story storyOfPath(Configuration configuration, String storyPath) {
  294.         String storyAsText = configuration.storyLoader().loadStoryAsText(storyPath);
  295.         return configuration.storyParser().parseStory(storyAsText, storyPath);
  296.     }

  297.     /**
  298.      * Returns the parsed story from the given text
  299.      *
  300.      * @param configuration the Configuration used to run story
  301.      * @param storyAsText the story text
  302.      * @param storyId the story Id, which will be returned as story path
  303.      * @return The parsed Story
  304.      */
  305.     public Story storyOfText(Configuration configuration, String storyAsText, String storyId) {
  306.         return configuration.storyParser().parseStory(storyAsText, storyId);
  307.     }

  308.     private void addMetaParameters(Map<String, String> storyParameters, Meta meta) {
  309.         for (String name : meta.getPropertyNames()) {
  310.             if (!storyParameters.containsKey(name)) {
  311.                 storyParameters.put(name, meta.getProperty(name));
  312.             }
  313.         }
  314.     }

  315.     private boolean shouldRunBeforeOrAfterScenarioSteps(RunContext context) {
  316.         return !context.configuration().storyControls().skipBeforeAndAfterScenarioStepsIfGivenStory()
  317.                 || !context.givenStory();
  318.     }

  319.     private boolean isParameterisedByExamples(Scenario scenario) {
  320.         return !scenario.getExamplesTable().isEmpty() && !scenario.getGivenStories().requireParameters();
  321.     }

  322.     public interface State {

  323.         State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter);

  324.         RuntimeException getFailure();
  325.     }

  326.     private static final class FineSoFar implements State {

  327.         @Override
  328.         public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
  329.             State state;
  330.             StepResult result;
  331.             int indexOfResult;
  332.             try {
  333.                 result = step.perform(reporter, getFailure());

  334.                 UUIDExceptionWrapper stepFailure = result.getFailure();
  335.                 state = stepFailure == null ? this : new SomethingHappened(stepFailure);
  336.             } catch (IgnoringStepsFailure e) {
  337.                 result = AbstractStepResult.ignorable(step.asString(keywords));
  338.                 state = new Ignoring(e);
  339.             } catch (PendingStepFound e) {
  340.                 result = AbstractStepResult.pending((PendingStep) step);
  341.                 state = new Pending(e);
  342.             }
  343.             indexOfResult = results.size();
  344.             results.add(result);

  345.             List<Step> composedSteps = step.getComposedSteps();
  346.             if (!composedSteps.isEmpty()) {
  347.                 reporter.beforeComposedSteps();
  348.                 for (Step composedStep : composedSteps) {
  349.                     state = state.run(composedStep, results, keywords, reporter);
  350.                 }
  351.                 reporter.afterComposedSteps();
  352.             }

  353.             if (state instanceof Ignoring) {
  354.                 result = AbstractStepResult.ignorable(step.asString(keywords));
  355.                 results.set(indexOfResult, result);
  356.             }
  357.             result.describeTo(reporter);
  358.             return state;
  359.         }

  360.         @Override
  361.         public UUIDExceptionWrapper getFailure() {
  362.             return null;
  363.         }

  364.     }

  365.     private static final class SomethingHappened implements State {
  366.         private UUIDExceptionWrapper failure;

  367.         public SomethingHappened(UUIDExceptionWrapper failure) {
  368.             this.failure = failure;
  369.         }

  370.         @Override
  371.         public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
  372.             StepResult result = step.doNotPerform(reporter, getFailure());
  373.             results.add(result);
  374.             result.describeTo(reporter);
  375.             return this;
  376.         }

  377.         @Override
  378.         public UUIDExceptionWrapper getFailure() {
  379.             return failure;
  380.         }
  381.     }

  382.     private static final class Ignoring implements State {

  383.         private final IgnoringStepsFailure failure;

  384.         private Ignoring(IgnoringStepsFailure failure) {
  385.             this.failure = failure;
  386.         }

  387.         @Override
  388.         public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
  389.             String stepAsString = step.asString(keywords);
  390.             reporter.beforeStep(new org.jbehave.core.model.Step(StepExecutionType.IGNORABLE, stepAsString));
  391.             StepResult result = AbstractStepResult.ignorable(stepAsString);
  392.             results.add(result);
  393.             result.describeTo(reporter);
  394.             return this;
  395.         }

  396.         @Override
  397.         public IgnoringStepsFailure getFailure() {
  398.             return failure;
  399.         }
  400.     }

  401.     private static final class Pending implements State {

  402.         private final PendingStepFound failure;

  403.         private Pending(PendingStepFound failure) {
  404.             this.failure = failure;
  405.         }

  406.         @Override
  407.         public State run(Step step, List<StepResult> results, Keywords keywords, StoryReporter reporter) {
  408.             StepResult result = AbstractStepResult.pending((PendingStep) step);
  409.             results.add(result);
  410.             result.describeTo(reporter);
  411.             return this;
  412.         }

  413.         @Override
  414.         public PendingStepFound getFailure() {
  415.             return failure;
  416.         }
  417.     }

  418.     public void perform(RunContext context, Story story) {
  419.         boolean restartingStory = false;

  420.         try {
  421.             performCancellable(context, story);
  422.             if (context.restartStory()) {
  423.                 context.reporter().restartedStory(story, context.failure(context.state()));
  424.                 restartingStory = true;
  425.                 perform(context, story);
  426.             }
  427.         } catch (InterruptedException e) {
  428.             if (context.isCancelled(story)) {
  429.                 context.reporter().storyCancelled(story, context.storyDuration(story));
  430.                 context.reporter().afterStory(context.givenStory);
  431.             }
  432.             throw new UUIDExceptionWrapper(e);
  433.         } finally {
  434.             if (!context.givenStory() && !restartingStory) {
  435.                 invokeDelayedReporters(context.reporter());
  436.             }
  437.         }
  438.     }

  439.     private void performCancellable(RunContext context, Story story) throws InterruptedException {
  440.         if (context.configuration().storyControls().resetStateBeforeStory()) {
  441.             context.resetState();
  442.             context.resetFailures(story);
  443.         }

  444.         if (!story.getPath().equals(context.path())) {
  445.             context.currentPath(story.getPath());
  446.         }

  447.         if (context.configuration.dryRun()) {
  448.             context.reporter().dryRun();
  449.         }

  450.         root.get(story).perform(context);
  451.         if (context.failureOccurred()) {
  452.             context.addFailure(story);
  453.         }
  454.     }

  455.     public void performBeforeOrAfterStories(RunContext context, Stage stage) {
  456.         String storyPath = StringUtils.capitalize(stage.name().toLowerCase()) + "Stories";
  457.         context.currentPath(storyPath);
  458.         context.reporter().beforeStoriesSteps(stage);
  459.         try {
  460.             (stage == Stage.BEFORE ? root.beforeSteps : root.afterSteps).perform(context, false);
  461.         } catch (InterruptedException e) {
  462.             throw new UUIDExceptionWrapper(e);
  463.         } finally {
  464.             context.reporter().afterStoriesSteps(stage);
  465.             invokeDelayedReporters(context.reporter());
  466.         }
  467.     }

  468.     private void invokeDelayedReporters(StoryReporter reporter) {
  469.         if (reporter instanceof ConcurrentStoryReporter) {
  470.             ((ConcurrentStoryReporter) reporter).invokeDelayed();
  471.         } else if (reporter instanceof DelegatingStoryReporter) {
  472.             for (StoryReporter delegate : ((DelegatingStoryReporter) reporter).getDelegates()) {
  473.                 invokeDelayedReporters(delegate);
  474.             }
  475.         }
  476.     }

  477.     @Override
  478.     public String toString() {
  479.         return this.getClass().getSimpleName();
  480.     }

  481.     /**
  482.      * The context for running a story.
  483.      */
  484.     public static class RunContext {
  485.         private final Configuration configuration;
  486.         private final boolean givenStory;

  487.         private final AllStepCandidates allStepCandidates;
  488.         private final EmbedderMonitor embedderMonitor;
  489.         private final MetaFilter filter;
  490.         private final BatchFailures failures;
  491.         private final StepsContext stepsContext;
  492.         private final Map<Story, StoryDuration> cancelledStories = new HashMap<>();
  493.         private final Map<String, List<PendingStep>> pendingStories = new HashMap<>();
  494.         private final ThreadLocal<StoryRunContext> storyRunContext = ThreadLocal.withInitial(StoryRunContext::new);

  495.         public RunContext(Configuration configuration, AllStepCandidates allStepCandidates,
  496.                 EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) {
  497.             this(configuration, allStepCandidates, embedderMonitor, filter, failures, false);
  498.         }

  499.         private RunContext(Configuration configuration, AllStepCandidates allStepCandidates,
  500.                 EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures, boolean givenStory) {
  501.             this.configuration = configuration;
  502.             this.givenStory = givenStory;
  503.             this.allStepCandidates = allStepCandidates;
  504.             this.embedderMonitor = embedderMonitor;
  505.             this.filter = filter;
  506.             this.failures = failures;
  507.             this.stepsContext = configuration.stepsContext();
  508.             resetState();
  509.         }

  510.         public StepsContext stepsContext() {
  511.             return stepsContext;
  512.         }

  513.         public boolean restartScenario() {
  514.             Throwable cause = failure(state());
  515.             while (cause != null) {
  516.                 if (cause instanceof RestartingScenarioFailure) {
  517.                     return true;
  518.                 }
  519.                 cause = cause.getCause();
  520.             }
  521.             return false;
  522.         }

  523.         public boolean restartStory() {
  524.             Throwable cause = failure(state());
  525.             while (cause != null) {
  526.                 if (cause instanceof RestartingStoryFailure) {
  527.                     return true;
  528.                 }
  529.                 cause = cause.getCause();
  530.             }
  531.             return false;
  532.         }

  533.         public void currentPath(String path) {
  534.             currentRunContext().pathIs(path);
  535.             currentRunContext().reporterIs(configuration.storyReporter(path));
  536.         }

  537.         public void interruptIfCancelled() throws InterruptedException {
  538.             for (Story story : cancelledStories.keySet()) {
  539.                 if (path().equals(story.getPath())) {
  540.                     throw new InterruptedException(path());
  541.                 }
  542.             }
  543.         }

  544.         public boolean dryRun() {
  545.             return configuration.storyControls().dryRun();
  546.         }

  547.         public Configuration configuration() {
  548.             return configuration;
  549.         }

  550.         public boolean givenStory() {
  551.             return givenStory;
  552.         }

  553.         public String path() {
  554.             return currentRunContext().path();
  555.         }

  556.         public FilteredStory filter(Story story) {
  557.             return new FilteredStory(filter, story, configuration.storyControls(), givenStory());
  558.         }

  559.         public MetaFilter filter() {
  560.             return filter;
  561.         }

  562.         public PerformableSteps beforeStoriesSteps() {
  563.             return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(
  564.                     allStepCandidates.getBeforeStoriesSteps()));
  565.         }

  566.         public PerformableSteps afterStoriesSteps() {
  567.             return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(
  568.                     allStepCandidates.getAfterStoriesSteps()));
  569.         }

  570.         public PerformableSteps beforeStorySteps(Meta storyMeta) {
  571.             return new PerformableSteps(configuration.stepCollector()
  572.                     .collectBeforeOrAfterStorySteps(allStepCandidates.getBeforeStorySteps(givenStory), storyMeta));
  573.         }

  574.         public PerformableSteps afterStorySteps(Meta storyMeta) {
  575.             return new PerformableSteps(configuration.stepCollector()
  576.                     .collectBeforeOrAfterStorySteps(allStepCandidates.getAfterStorySteps(givenStory), storyMeta));
  577.         }

  578.         public PerformableSteps beforeScenarioSteps(Meta storyAndScenarioMeta, ScenarioType type) {
  579.             return new PerformableSteps(configuration.stepCollector()
  580.                     .collectBeforeScenarioSteps(allStepCandidates.getBeforeScenarioSteps(type), storyAndScenarioMeta));
  581.         }

  582.         public PerformableSteps afterScenarioSteps(Meta storyAndScenarioMeta, ScenarioType type) {
  583.             return new PerformableSteps(configuration.stepCollector()
  584.                     .collectAfterScenarioSteps(allStepCandidates.getAfterScenarioSteps(type), storyAndScenarioMeta));
  585.         }

  586.         private Map<Stage, PerformableSteps> lifecycleSteps(Lifecycle lifecycle, Meta meta, Scope scope,
  587.                 Map<String, String> parameters) {
  588.             MatchingStepMonitor monitor = new MatchingStepMonitor(configuration.stepMonitor());
  589.             Map<Stage, List<Step>> steps = configuration.stepCollector().collectLifecycleSteps(
  590.                     allStepCandidates.getRegularSteps(), lifecycle, meta, scope, parameters, monitor);
  591.             Map<Stage, PerformableSteps> performableSteps = new EnumMap<>(Stage.class);
  592.             for (Map.Entry<Stage, List<Step>> entry : steps.entrySet()) {
  593.                 performableSteps.put(entry.getKey(), new PerformableSteps(entry.getValue(), monitor.matched()));
  594.             }
  595.             return performableSteps;
  596.         }

  597.         private PerformableSteps scenarioSteps(Lifecycle lifecycle, Meta meta, Scenario scenario,
  598.                 Map<String, String> parameters) {
  599.             MatchingStepMonitor monitor = new MatchingStepMonitor(configuration.stepMonitor());
  600.             StepCollector stepCollector = configuration.stepCollector();
  601.             Map<Stage, List<Step>> beforeOrAfterStepSteps = stepCollector.collectLifecycleSteps(
  602.                     allStepCandidates.getRegularSteps(), lifecycle, meta, Scope.STEP, parameters, monitor);
  603.             List<Step> steps = new LinkedList<>();
  604.             for (Step step : stepCollector.collectScenarioSteps(allStepCandidates.getRegularSteps(), scenario,
  605.                     parameters, monitor)) {
  606.                 steps.addAll(beforeOrAfterStepSteps.get(Stage.BEFORE));
  607.                 steps.add(step);
  608.                 steps.addAll(beforeOrAfterStepSteps.get(Stage.AFTER));
  609.             }
  610.             return new PerformableSteps(steps, monitor.matched());
  611.         }

  612.         public RunContext childContextFor(GivenStory givenStory) {
  613.             RunContext child = new RunContext(configuration, allStepCandidates, embedderMonitor, filter,
  614.                     failures, true);
  615.             child.currentRunContext().pathIs(configuration.pathCalculator().calculate(path(), givenStory.getPath()));
  616.             return child;
  617.         }

  618.         public void cancelStory(Story story, StoryDuration storyDuration) {
  619.             cancelledStories.put(story, storyDuration);
  620.         }

  621.         public boolean isCancelled(Story story) {
  622.             return cancelledStories.containsKey(story);
  623.         }

  624.         public StoryDuration storyDuration(Story story) {
  625.             return cancelledStories.get(story);
  626.         }

  627.         public State state() {
  628.             return currentRunContext().state();
  629.         }

  630.         public void stateIs(State state) {
  631.             currentRunContext().stateIs(state);
  632.         }

  633.         public boolean failureOccurred() {
  634.             return failed(state());
  635.         }

  636.         public void resetState() {
  637.             currentRunContext().resetState();
  638.         }

  639.         /**
  640.          * Reset all the existing failures.
  641.          */
  642.         public void resetFailures() {
  643.             this.failures.clear();
  644.         }

  645.         /**
  646.          * Resets only the failures corresponding to the given story.
  647.          * @param story the story for which we want to remove the failures.
  648.          */
  649.         public void resetFailures(Story story) {
  650.             this.failures.entrySet()
  651.                     .removeIf(entry -> entry.getKey().equals(toBatchFailuresKey(story, entry.getValue())));
  652.         }

  653.         public StoryReporter reporter() {
  654.             return currentRunContext().reporter();
  655.         }

  656.         public boolean failed(State state) {
  657.             return !state.getClass().equals(FineSoFar.class);
  658.         }

  659.         public Throwable failure(State state) {
  660.             if (failed(state)) {
  661.                 return state.getFailure().getCause();
  662.             }
  663.             return null;
  664.         }

  665.         public void addFailure(Story story) {
  666.             addFailure(story, failure(state()));
  667.         }

  668.         public void addFailure(Story story, Throwable cause) {
  669.             if (cause != null) {
  670.                 failures.put(toBatchFailuresKey(story, cause), cause);
  671.             }
  672.         }

  673.         public void pendingSteps(List<PendingStep> pendingSteps) {
  674.             if (!pendingSteps.isEmpty()) {
  675.                 pendingStories.put(path(), pendingSteps);
  676.             }
  677.         }

  678.         public boolean hasPendingSteps() {
  679.             return pendingStories.containsKey(path());
  680.         }

  681.         public boolean isStoryPending() {
  682.             return pendingStories.containsKey(path());
  683.         }

  684.         public boolean hasFailed() {
  685.             return failed(state());
  686.         }

  687.         public Status status(State initial) {
  688.             if (isStoryPending()) {
  689.                 return Status.PENDING;
  690.             } else if (failed(initial)) {
  691.                 return Status.NOT_PERFORMED;
  692.             } else {
  693.                 return (hasFailed() ? Status.FAILED : Status.SUCCESSFUL);
  694.             }
  695.         }

  696.         public MetaFilter getFilter() {
  697.             return filter;
  698.         }

  699.         public BatchFailures getFailures() {
  700.             return failures;
  701.         }
  702.        
  703.         public EmbedderMonitor embedderMonitor() {
  704.             return embedderMonitor;
  705.         }

  706.         private StoryRunContext currentRunContext() {
  707.             return storyRunContext.get();
  708.         }

  709.         /**
  710.          * Converts the given story and failure to the key to use to store the failure.
  711.          * @param story the story where the failure occurred.
  712.          * @param cause the failure that occurred.
  713.          * @return the key to use to store the failure into the {@code BatchFailures}.
  714.          */
  715.         private String toBatchFailuresKey(Story story, Throwable cause) {
  716.             return String.format("%s@%s", story.getPath(), Integer.toHexString(cause.hashCode()));
  717.         }
  718.     }

  719.     private static class StoryRunContext {
  720.         private State state;
  721.         private String path;
  722.         private StoryReporter reporter;

  723.         private State state() {
  724.             return state;
  725.         }

  726.         private void stateIs(State state) {
  727.             this.state = state;
  728.         }

  729.         private void resetState() {
  730.             this.state = new FineSoFar();
  731.         }

  732.         private String path() {
  733.             return path;
  734.         }

  735.         private void pathIs(String path) {
  736.             this.path = path;
  737.         }

  738.         public StoryReporter reporter() {
  739.             return this.reporter;
  740.         }

  741.         private void reporterIs(StoryReporter reporter) {
  742.             this.reporter = reporter;
  743.         }
  744.     }

  745.     public static class FailureContext {

  746.         List<Throwable> failures = new ArrayList<>();

  747.         public void addFailure(Throwable failure) {
  748.             failures.add(failure);
  749.         }

  750.         public List<Throwable> getFailures() {
  751.             return failures;
  752.         }

  753.     }

  754.     public interface ReportingFailures {

  755.         void reportFailures(FailureContext context);

  756.     }

  757.     public interface Performable extends ReportingFailures {

  758.         void perform(RunContext context) throws InterruptedException;

  759.     }

  760.     public static class PerformableRoot {

  761.         private PerformableSteps beforeSteps = new PerformableSteps();
  762.         private Map<String, PerformableStory> stories = new LinkedHashMap<>();
  763.         private PerformableSteps afterSteps = new PerformableSteps();

  764.         public void addBeforeSteps(PerformableSteps beforeSteps) {
  765.             this.beforeSteps = beforeSteps;
  766.         }

  767.         public void add(PerformableStory performableStory) {
  768.             stories.put(performableStory.getStory().getPath(), performableStory);
  769.         }

  770.         public void addAfterSteps(PerformableSteps afterSteps) {
  771.             this.afterSteps = afterSteps;
  772.         }

  773.         public PerformableStory get(Story story) {
  774.             PerformableStory performableStory = stories.get(story.getPath());
  775.             if (performableStory != null) {
  776.                 return performableStory;
  777.             }
  778.             throw new RuntimeException("No performable story for path " + story.getPath());
  779.         }

  780.         public List<PerformableStory> getStories() {
  781.             return new ArrayList<>(stories.values());
  782.         }

  783.     }

  784.     public static enum Status {
  785.         SUCCESSFUL, FAILED, PENDING, NOT_PERFORMED, EXCLUDED;
  786.     }

  787.     public abstract static class PerformableEntity implements Performable {

  788.         private PerformableGivenStories givenStories;
  789.         private final Map<Stage, Map<ExecutionType, PerformableSteps>> stageSteps;
  790.         private final LifecycleStepsExecutionHook beforeHook;
  791.         private final LifecycleStepsExecutionHook afterHook;

  792.         public PerformableEntity(LifecycleStepsExecutionHook beforeHook, LifecycleStepsExecutionHook afterHook) {
  793.             this.givenStories = new PerformableGivenStories(Collections.emptyList(), null);
  794.             this.stageSteps = new EnumMap<>(Stage.class);
  795.             this.beforeHook = beforeHook;
  796.             this.afterHook = afterHook;
  797.         }

  798.         public void setGivenStories(PerformableGivenStories givenStories) {
  799.             this.givenStories = givenStories;
  800.         }

  801.         public void addBeforeSteps(Lifecycle.ExecutionType type, PerformableSteps beforeSteps) {
  802.             getBeforeSteps(type).add(beforeSteps);
  803.         }

  804.         public void addAfterSteps(Lifecycle.ExecutionType type, PerformableSteps afterSteps) {
  805.             getAfterSteps(type).add(afterSteps);
  806.         }

  807.         protected void performWithStopExecutionExceptionHandling(FailableRunnable<InterruptedException> action,
  808.                 FailableRunnable<InterruptedException> afterHook) throws InterruptedException {
  809.             try {
  810.                 action.run();
  811.             } finally {
  812.                 afterHook.run();
  813.             }
  814.         }

  815.         protected void performBeforeSteps(RunContext context) throws InterruptedException {
  816.             performHookSteps(context, Stage.BEFORE, ExecutionType.SYSTEM);
  817.             performHookSteps(context, Stage.BEFORE, ExecutionType.USER);
  818.         }

  819.         protected void performAfterSteps(RunContext context) throws InterruptedException {
  820.             performHookSteps(context, Stage.AFTER, ExecutionType.USER);
  821.             performHookSteps(context, Stage.AFTER, ExecutionType.SYSTEM);
  822.         }

  823.         protected PerformableSteps getBeforeSteps(Lifecycle.ExecutionType type) {
  824.             return getPerformableSteps(Stage.BEFORE, type);
  825.         }

  826.         protected PerformableSteps getAfterSteps(Lifecycle.ExecutionType type) {
  827.             return getPerformableSteps(Stage.AFTER, type);
  828.         }

  829.         private PerformableSteps getPerformableSteps(Stage stage, Lifecycle.ExecutionType type) {
  830.             Map<ExecutionType, PerformableSteps> steps = stageSteps.computeIfAbsent(stage,
  831.                     t -> new EnumMap<>(ExecutionType.class));
  832.             return steps.computeIfAbsent(type, s -> new PerformableSteps());
  833.         }

  834.         private void performHookSteps(RunContext context, Stage stage, Lifecycle.ExecutionType type)
  835.                 throws InterruptedException {
  836.             StoryReporter reporter = context.reporter();
  837.             this.beforeHook.perform(reporter, stage, type);
  838.             getPerformableSteps(stage, type).perform(context, false);
  839.             this.afterHook.perform(reporter, stage, type);
  840.         }

  841.         public PerformableGivenStories getGivenStories() {
  842.             return givenStories;
  843.         }

  844.     }

  845.     private interface LifecycleStepsExecutionHook {
  846.         void perform(StoryReporter reporter, Stage stage, Lifecycle.ExecutionType type);
  847.     }

  848.     public static class PerformableStory extends PerformableEntity {

  849.         private final Story story;
  850.         private final transient Keywords keywords;
  851.         private final boolean givenStory;
  852.         private boolean excluded;
  853.         private Status status;
  854.         private Timing timing = new Timing();
  855.         private List<PerformableScenario> scenarios = new ArrayList<>();

  856.         public PerformableStory(Story story, Keywords keywords, boolean givenStory) {
  857.             super(StoryReporter::beforeStorySteps, StoryReporter::afterStorySteps);
  858.             this.story = story;
  859.             this.keywords = keywords;
  860.             this.givenStory = givenStory;
  861.         }

  862.         public void excluded(boolean excluded) {
  863.             this.excluded = excluded;
  864.         }

  865.         public boolean isExcluded() {
  866.             return excluded;
  867.         }

  868.         public Story getStory() {
  869.             return story;
  870.         }

  871.         public Keywords getKeywords() {
  872.             return keywords;
  873.         }

  874.         public boolean givenStory() {
  875.             return givenStory;
  876.         }

  877.         public Status getStatus() {
  878.             return status;
  879.         }

  880.         public Timing getTiming() {
  881.             return timing;
  882.         }

  883.         public void add(PerformableScenario performableScenario) {
  884.             scenarios.add(performableScenario);
  885.         }

  886.         public void addAll(List<PerformableScenario> performableScenarios) {
  887.             scenarios.addAll(performableScenarios);
  888.         }

  889.         @Override
  890.         public void perform(RunContext context) throws InterruptedException {
  891.             if (isExcluded()) {
  892.                 context.reporter().storyExcluded(story, context.filter.asString());
  893.                 this.status = Status.EXCLUDED;
  894.             }
  895.             Timer timer = new Timer().start();
  896.             try {
  897.                 context.stepsContext().resetStory();
  898.                 context.reporter().beforeStory(story, givenStory);
  899.                 context.reporter().narrative(story.getNarrative());
  900.                 context.reporter().lifecycle(story.getLifecycle());
  901.                 State state = context.state();
  902.                 performBeforeSteps(context);
  903.                 getGivenStories().perform(context);

  904.                 performWithStopExecutionExceptionHandling(
  905.                     () -> {
  906.                         if (!context.failureOccurred() || !context.configuration().storyControls()
  907.                                 .skipStoryIfGivenStoryFailed()) {

  908.                             context.reporter().beforeScenarios();

  909.                             for (PerformableScenario scenario : scenarios) {
  910.                                 scenario.perform(context);
  911.                             }
  912.                         }
  913.                     },
  914.                     () -> {
  915.                         context.reporter().afterScenarios();
  916.                         performAfterSteps(context);
  917.                     }
  918.                 );

  919.                 context.configuration().storyControls().resetCurrentStoryControls();
  920.                 if (context.restartStory()) {
  921.                     context.reporter().afterStory(true);
  922.                 } else {
  923.                     context.reporter().afterStory(givenStory);
  924.                 }
  925.                 this.status = context.status(state);
  926.             } finally {
  927.                 timing = new Timing(timer.stop());
  928.             }
  929.         }

  930.         @Override
  931.         public void reportFailures(FailureContext context) {
  932.             for (PerformableScenario scenario : scenarios) {
  933.                 scenario.reportFailures(context);
  934.             }
  935.         }

  936.         public List<PerformableScenario> getScenarios() {
  937.             return scenarios;
  938.         }

  939.         public boolean hasIncludedScenarios() {
  940.             return getScenarios().stream().anyMatch(scenario -> !scenario.isExcluded());
  941.         }
  942.     }

  943.     public static class PerformableScenario implements Performable {

  944.         private final Scenario scenario;
  945.         private final String storyPath;
  946.         private boolean excluded;
  947.         @SuppressWarnings("unused")
  948.         private Status status;
  949.         private Timing timing = new Timing();
  950.         private NormalPerformableScenario normalScenario;
  951.         private List<ExamplePerformableScenario> exampleScenarios;

  952.         public PerformableScenario(Scenario scenario, String storyPath) {
  953.             this.scenario = scenario;
  954.             this.storyPath = storyPath;
  955.         }

  956.         public void useNormalScenario(NormalPerformableScenario normalScenario) {
  957.             this.normalScenario = normalScenario;
  958.         }

  959.         public void addExampleScenario(ExamplePerformableScenario exampleScenario) {
  960.             if (exampleScenarios == null) {
  961.                 exampleScenarios = new ArrayList<>();
  962.             }
  963.             exampleScenarios.add(exampleScenario);
  964.         }

  965.         public void excluded(boolean excluded) {
  966.             this.excluded = excluded;
  967.         }

  968.         public boolean isExcluded() {
  969.             return excluded;
  970.         }

  971.         public Status getStatus() {
  972.             return status;
  973.         }

  974.         public Timing getTiming() {
  975.             return timing;
  976.         }

  977.         public Scenario getScenario() {
  978.             return scenario;
  979.         }

  980.         public String getStoryPath() {
  981.             return storyPath;
  982.         }

  983.         public Throwable getFailure() {
  984.             FailureContext context = new FailureContext();
  985.             reportFailures(context);
  986.             List<Throwable> failures = context.getFailures();
  987.             if (failures.size() > 0) {
  988.                 return failures.get(0);
  989.             }
  990.             return null;
  991.         }

  992.         public boolean hasNormalScenario() {
  993.             return normalScenario != null;
  994.         }

  995.         public boolean hasExamples() {
  996.             return exampleScenarios != null && exampleScenarios.size() > 0;
  997.         }

  998.         public boolean isPerformable() {
  999.             return hasNormalScenario() || hasExamples() || isExcluded();
  1000.         }

  1001.         public List<ExamplePerformableScenario> getExamples() {
  1002.             return exampleScenarios;
  1003.         }

  1004.         @Override
  1005.         public void perform(RunContext context) throws InterruptedException {
  1006.             if (isExcluded()) {
  1007.                 context.embedderMonitor().scenarioExcluded(scenario, context.filter());
  1008.                 return;
  1009.             }
  1010.             Timer timer = new Timer().start();
  1011.             try {
  1012.                 context.stepsContext().resetScenario();
  1013.                 context.reporter().beforeScenario(scenario);
  1014.                 State state = context.state();
  1015.                 if (hasExamples()) {
  1016.                     context.reporter().beforeExamples(scenario.getSteps(),
  1017.                             scenario.getExamplesTable());
  1018.                     for (ExamplePerformableScenario exampleScenario : exampleScenarios) {
  1019.                         exampleScenario.perform(context);
  1020.                     }
  1021.                     context.reporter().afterExamples();
  1022.                 } else {
  1023.                     context.stepsContext().resetExample();
  1024.                     normalScenario.perform(context);
  1025.                 }
  1026.                 this.status = context.status(state);
  1027.             } finally {
  1028.                 timing = new Timing(timer.stop());
  1029.                 context.reporter().afterScenario(timing);
  1030.             }
  1031.         }

  1032.         @Override
  1033.         public void reportFailures(FailureContext context) {
  1034.             if (hasExamples()) {
  1035.                 for (ExamplePerformableScenario exampleScenario : exampleScenarios) {
  1036.                     exampleScenario.reportFailures(context);
  1037.                 }
  1038.             } else {
  1039.                 normalScenario.reportFailures(context);
  1040.             }
  1041.         }

  1042.     }

  1043.     public abstract static class AbstractPerformableScenario extends PerformableEntity {

  1044.         private transient Story story;
  1045.         protected final Map<String, String> parameters;
  1046.         protected final PerformableSteps steps = new PerformableSteps();
  1047.         private Meta storyAndScenarioMeta = new Meta();

  1048.         protected AbstractPerformableScenario(Story story) {
  1049.             this(story, new HashMap<>());
  1050.         }

  1051.         protected AbstractPerformableScenario(Story story, Map<String, String> parameters) {
  1052.             super(StoryReporter::beforeScenarioSteps, StoryReporter::afterScenarioSteps);
  1053.             this.story = story;
  1054.             this.parameters = parameters;
  1055.         }

  1056.         public void addSteps(PerformableSteps steps) {
  1057.             this.steps.add(steps);
  1058.         }

  1059.         public Map<String, String> getParameters() {
  1060.             return parameters;
  1061.         }

  1062.         protected void performScenario(RunContext context) throws InterruptedException {
  1063.             performBeforeSteps(context);
  1064.             getGivenStories().perform(context);

  1065.             performWithStopExecutionExceptionHandling(
  1066.                 () -> {
  1067.                     boolean restart = true;
  1068.                     while (restart) {
  1069.                         restart = false;
  1070.                         try {
  1071.                             context.reporter().beforeScenarioSteps(null, null);
  1072.                             steps.perform(context, true);
  1073.                         } catch (RestartingScenarioFailure e) {
  1074.                             restart = true;
  1075.                         }
  1076.                     }
  1077.                 },
  1078.                 () -> {
  1079.                     context.reporter().afterScenarioSteps(null, null);
  1080.                     performAfterSteps(context);
  1081.                 }
  1082.             );
  1083.         }

  1084.         @Override
  1085.         public void reportFailures(FailureContext context) {
  1086.             getBeforeSteps(ExecutionType.SYSTEM).reportFailures(context);
  1087.             getBeforeSteps(ExecutionType.USER).reportFailures(context);
  1088.             steps.reportFailures(context);
  1089.             getAfterSteps(ExecutionType.USER).reportFailures(context);
  1090.             getAfterSteps(ExecutionType.SYSTEM).reportFailures(context);
  1091.         }

  1092.         protected void resetStateIfConfigured(RunContext context) {
  1093.             if (context.configuration().storyControls().resetStateBeforeScenario()) {
  1094.                 if (context.failureOccurred()) {
  1095.                     context.addFailure(story);
  1096.                 }
  1097.                 context.resetState();
  1098.             }
  1099.         }

  1100.         public Meta getStoryAndScenarioMeta() {
  1101.             return storyAndScenarioMeta;
  1102.         }

  1103.         public void setStoryAndScenarioMeta(Meta storyAndScenarioMeta) {
  1104.             this.storyAndScenarioMeta = storyAndScenarioMeta;
  1105.         }
  1106.     }

  1107.     public static class NormalPerformableScenario extends AbstractPerformableScenario {

  1108.         public NormalPerformableScenario(Story story) {
  1109.             super(story);
  1110.         }

  1111.         @Override
  1112.         public void perform(RunContext context) throws InterruptedException {
  1113.             resetStateIfConfigured(context);
  1114.             performScenario(context);
  1115.         }
  1116.     }

  1117.     public static class ExamplePerformableScenario extends AbstractPerformableScenario {

  1118.         private final int exampleIndex;

  1119.         public ExamplePerformableScenario(Story story, Map<String, String> exampleParameters, int exampleIndex) {
  1120.             super(story, exampleParameters);
  1121.             this.exampleIndex = exampleIndex;
  1122.         }

  1123.         @Override
  1124.         public void perform(RunContext context) throws InterruptedException {
  1125.             Meta parameterMeta = parameterMeta(context.configuration().keywords(), parameters).inheritFrom(
  1126.                     getStoryAndScenarioMeta());
  1127.             if (parameterMeta.isEmpty() || !context.filter().excluded(parameterMeta)) {
  1128.                 resetStateIfConfigured(context);
  1129.                 context.stepsContext().resetExample();
  1130.                 context.reporter().example(parameters, exampleIndex);
  1131.                 performScenario(context);
  1132.             }
  1133.         }

  1134.         private Meta parameterMeta(Keywords keywords, Map<String, String> parameters) {
  1135.             String meta = keywords.meta();
  1136.             if (parameters.containsKey(meta)) {
  1137.                 return Meta.createMeta(parameters.get(meta), keywords);
  1138.             }
  1139.             return Meta.EMPTY;
  1140.         }

  1141.     }

  1142.     public static class PerformableGivenStories implements Performable {

  1143.         private final List<PerformableStory> performableGivenStories;
  1144.         private final GivenStories givenStories;

  1145.         public PerformableGivenStories(List<PerformableStory> performableGivenStories, GivenStories givenStories) {
  1146.             this.performableGivenStories = performableGivenStories;
  1147.             this.givenStories = givenStories;
  1148.         }

  1149.         @Override
  1150.         public void perform(RunContext context) throws InterruptedException {
  1151.             if (performableGivenStories.size() > 0) {
  1152.                 StoryReporter storyReporter = context.reporter();
  1153.                 storyReporter.beforeGivenStories();
  1154.                 storyReporter.givenStories(givenStories);
  1155.                 for (PerformableStory story : performableGivenStories) {
  1156.                     story.perform(context);
  1157.                 }
  1158.                 storyReporter.afterGivenStories();
  1159.             }
  1160.         }

  1161.         @Override
  1162.         public void reportFailures(FailureContext context) {
  1163.         }
  1164.     }

  1165.     public static class PerformableSteps implements ReportingFailures {

  1166.         private final transient List<Step> steps;
  1167.         private final transient List<PendingStep> pendingSteps;
  1168.         private List<StepMatch> matches;
  1169.         private List<StepResult> results;

  1170.         public PerformableSteps() {
  1171.             this(null);
  1172.         }

  1173.         public PerformableSteps(List<Step> steps) {
  1174.             this(steps, null);
  1175.         }

  1176.         public PerformableSteps(List<Step> steps, List<StepMatch> stepMatches) {
  1177.             this.steps = steps != null ? steps : new ArrayList<Step>();
  1178.             this.pendingSteps = pendingSteps();
  1179.             this.matches = stepMatches;
  1180.         }

  1181.         public void add(PerformableSteps performableSteps) {
  1182.             this.steps.addAll(performableSteps.steps);
  1183.             this.pendingSteps.addAll(performableSteps.pendingSteps);
  1184.             if (performableSteps.matches != null) {
  1185.                 if (this.matches == null) {
  1186.                     this.matches = new ArrayList<>();
  1187.                 }
  1188.                 this.matches.addAll(performableSteps.matches);
  1189.             }
  1190.         }
  1191.        
  1192.         public void perform(RunContext context, boolean interruptIfCancelled) throws InterruptedException {
  1193.             if (steps.size() == 0) {
  1194.                 return;
  1195.             }
  1196.             Keywords keywords = context.configuration().keywords();
  1197.             State state = context.state();
  1198.             State originalState = state;
  1199.             StoryReporter reporter = context.reporter();
  1200.             results = new ArrayList<>();
  1201.             for (Step step : steps) {
  1202.                 try {
  1203.                     if (interruptIfCancelled) {
  1204.                         context.interruptIfCancelled();
  1205.                     }
  1206.                     state = state.run(step, results, keywords, reporter);
  1207.                 } catch (RestartingScenarioFailure e) {
  1208.                     reporter.restarted(step.asString(keywords), e);
  1209.                     throw e;
  1210.                 }
  1211.             }
  1212.             context.stateIs(state instanceof Ignoring ? originalState : state);
  1213.             context.pendingSteps(pendingSteps);
  1214.             generatePendingStepMethods(context, pendingSteps);
  1215.         }

  1216.         @Override
  1217.         public void reportFailures(FailureContext context) {
  1218.             // Results can be null if the steps are not executed
  1219.             if (results == null) {
  1220.                 return;
  1221.             }
  1222.             for (StepResult result : results) {
  1223.                 if (result instanceof AbstractStepResult.Failed) {
  1224.                     context.addFailure(result.getFailure());
  1225.                 }
  1226.             }
  1227.         }

  1228.         private List<PendingStep> pendingSteps() {
  1229.             List<PendingStep> pending = new ArrayList<>();
  1230.             for (Step step : steps) {
  1231.                 if (step instanceof PendingStep) {
  1232.                     pending.add((PendingStep) step);
  1233.                 }
  1234.             }
  1235.             return pending;
  1236.         }

  1237.         private void generatePendingStepMethods(RunContext context, List<PendingStep> pendingSteps) {
  1238.             if (!pendingSteps.isEmpty()) {
  1239.                 PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration()
  1240.                         .keywords());
  1241.                 List<String> methods = new ArrayList<>();
  1242.                 for (PendingStep pendingStep : pendingSteps) {
  1243.                     if (!pendingStep.annotated()) {
  1244.                         String generatedMethod = generator.generateMethod(pendingStep);
  1245.                         pendingStep.setPendingMethod(generatedMethod);
  1246.                         methods.add(generatedMethod);
  1247.                     }
  1248.                 }
  1249.                 context.reporter().pendingMethods(methods);
  1250.                 if (context.configuration().pendingStepStrategy() instanceof FailingUponPendingStep) {
  1251.                     throw new PendingStepsFound(pendingSteps);
  1252.                 }
  1253.             }
  1254.         }

  1255.         @Override
  1256.         public String toString() {
  1257.             return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  1258.         }

  1259.     }

  1260.     public RunContext newRunContext(Configuration configuration, AllStepCandidates allStepCandidates,
  1261.             EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) {
  1262.         return new RunContext(configuration, allStepCandidates, embedderMonitor, filter, failures);
  1263.     }
  1264. }