Coverage Report - org.jbehave.core.embedder.StoryRunner
 
Classes in this File Line Coverage Branch Coverage Complexity
StoryRunner
92%
180/195
84%
79/94
2.327
StoryRunner$1
N/A
N/A
2.327
StoryRunner$FineSoFar
100%
16/16
75%
9/12
2.327
StoryRunner$RunContext
100%
35/35
75%
3/4
2.327
StoryRunner$SomethingHappened
100%
6/6
N/A
2.327
StoryRunner$State
N/A
N/A
2.327
 
 1  
 package org.jbehave.core.embedder;
 2  
 
 3  
 import java.util.ArrayList;
 4  
 import java.util.HashMap;
 5  
 import java.util.List;
 6  
 import java.util.Map;
 7  
 
 8  
 import org.jbehave.core.annotations.ScenarioType;
 9  
 import org.jbehave.core.configuration.Configuration;
 10  
 import org.jbehave.core.failures.FailureStrategy;
 11  
 import org.jbehave.core.failures.PendingStepFound;
 12  
 import org.jbehave.core.failures.PendingStepStrategy;
 13  
 import org.jbehave.core.failures.RestartingScenarioFailure;
 14  
 import org.jbehave.core.failures.UUIDExceptionWrapper;
 15  
 import org.jbehave.core.model.ExamplesTable;
 16  
 import org.jbehave.core.model.GivenStories;
 17  
 import org.jbehave.core.model.GivenStory;
 18  
 import org.jbehave.core.model.Meta;
 19  
 import org.jbehave.core.model.Scenario;
 20  
 import org.jbehave.core.model.Story;
 21  
 import org.jbehave.core.model.StoryDuration;
 22  
 import org.jbehave.core.reporters.ConcurrentStoryReporter;
 23  
 import org.jbehave.core.reporters.StoryReporter;
 24  
 import org.jbehave.core.steps.CandidateSteps;
 25  
 import org.jbehave.core.steps.InjectableStepsFactory;
 26  
 import org.jbehave.core.steps.PendingStepMethodGenerator;
 27  
 import org.jbehave.core.steps.ProvidedStepsFactory;
 28  
 import org.jbehave.core.steps.Step;
 29  
 import org.jbehave.core.steps.StepCollector.Stage;
 30  
 import org.jbehave.core.steps.StepCreator.ParameterisedStep;
 31  
 import org.jbehave.core.steps.StepCreator.PendingStep;
 32  
 import org.jbehave.core.steps.StepResult;
 33  
 
 34  
 import static org.codehaus.plexus.util.StringUtils.capitalizeFirstLetter;
 35  
 
 36  
 /**
 37  
  * Runs a {@link Story}, given a {@link Configuration} and a list of
 38  
  * {@link CandidateSteps}, describing the results to the {@link StoryReporter}.
 39  
  * 
 40  
  * @author Elizabeth Keogh
 41  
  * @author Mauro Talevi
 42  
  * @author Paul Hammant
 43  
  */
 44  254
 public class StoryRunner {
 45  
 
 46  65
     private ThreadLocal<FailureStrategy> currentStrategy = new ThreadLocal<FailureStrategy>();
 47  65
     private ThreadLocal<FailureStrategy> failureStrategy = new ThreadLocal<FailureStrategy>();
 48  65
     private ThreadLocal<PendingStepStrategy> pendingStepStrategy = new ThreadLocal<PendingStepStrategy>();
 49  65
     private ThreadLocal<UUIDExceptionWrapper> storyFailure = new ThreadLocal<UUIDExceptionWrapper>();
 50  65
     private ThreadLocal<StoryReporter> reporter = new ThreadLocal<StoryReporter>();
 51  65
     private ThreadLocal<String> reporterStoryPath = new ThreadLocal<String>();
 52  65
     private ThreadLocal<State> storiesState = new ThreadLocal<State>();
 53  
     // should this be volatile?
 54  65
     private Map<Story, StoryDuration> cancelledStories = new HashMap<Story, StoryDuration>();
 55  
 
 56  
     /**
 57  
      * Run steps before or after a collection of stories. Steps are execute only
 58  
      * <b>once</b> per collection of stories.
 59  
      * 
 60  
      * @param configuration the Configuration used to find the steps to run
 61  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 62  
      *            steps methods
 63  
      * @param stage the Stage
 64  
      * @return The State after running the steps
 65  
      */
 66  
     public State runBeforeOrAfterStories(Configuration configuration, List<CandidateSteps> candidateSteps, Stage stage) {
 67  7
         String storyPath = capitalizeFirstLetter(stage.name().toLowerCase()) + "Stories";
 68  7
         reporter.set(configuration.storyReporter(storyPath));
 69  7
         reporter.get().beforeStory(new Story(storyPath), false);
 70  7
         RunContext context = new RunContext(configuration, candidateSteps, storyPath, MetaFilter.EMPTY);
 71  7
         if (stage == Stage.BEFORE) {
 72  4
             resetStoryFailure(context);
 73  
         }
 74  7
         if (stage == Stage.AFTER && storiesState.get() != null) {
 75  3
             context.stateIs(storiesState.get());
 76  
         }
 77  
         try {
 78  7
             runStepsWhileKeepingState(context,
 79  
                     configuration.stepCollector().collectBeforeOrAfterStoriesSteps(context.candidateSteps(), stage));
 80  0
         } catch (InterruptedException e) {
 81  0
             throw new UUIDExceptionWrapper(e);
 82  7
         }
 83  7
         reporter.get().afterStory(false);
 84  7
         storiesState.set(context.state());
 85  
         // handle any after stories failure according to strategy
 86  7
         if (stage == Stage.AFTER) {
 87  
             try {
 88  3
                 handleStoryFailureByStrategy();
 89  1
             } catch (Throwable e) {
 90  1
                 return new SomethingHappened(storyFailure.get());
 91  
             } finally {
 92  3
                 if (reporter.get() instanceof ConcurrentStoryReporter) {
 93  1
                     ((ConcurrentStoryReporter) reporter.get()).invokeDelayed();
 94  
                 }
 95  
             }
 96  
         }
 97  6
         return context.state();
 98  
     }
 99  
 
 100  
     /**
 101  
      * Runs a Story with the given configuration and steps.
 102  
      * 
 103  
      * @param configuration the Configuration used to run story
 104  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 105  
      *            steps methods
 106  
      * @param story the Story to run
 107  
      * @throws Throwable if failures occurred and FailureStrategy dictates it to
 108  
      *             be re-thrown.
 109  
      */
 110  
     public void run(Configuration configuration, List<CandidateSteps> candidateSteps, Story story) throws Throwable {
 111  15
         run(configuration, candidateSteps, story, MetaFilter.EMPTY);
 112  14
     }
 113  
 
 114  
     /**
 115  
      * Runs a Story with the given configuration and steps, applying the given
 116  
      * meta filter.
 117  
      * 
 118  
      * @param configuration the Configuration used to run story
 119  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 120  
      *            steps methods
 121  
      * @param story the Story to run
 122  
      * @param filter the Filter to apply to the story Meta
 123  
      * @throws Throwable if failures occurred and FailureStrategy dictates it to
 124  
      *             be re-thrown.
 125  
      */
 126  
     public void run(Configuration configuration, List<CandidateSteps> candidateSteps, Story story, MetaFilter filter)
 127  
             throws Throwable {
 128  19
         run(configuration, candidateSteps, story, filter, null);
 129  18
     }
 130  
 
 131  
     /**
 132  
      * Runs a Story with the given configuration and steps, applying the given
 133  
      * meta filter, and staring from given state.
 134  
      * 
 135  
      * @param configuration the Configuration used to run story
 136  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 137  
      *            steps methods
 138  
      * @param story the Story to run
 139  
      * @param filter the Filter to apply to the story Meta
 140  
      * @param beforeStories the State before running any of the stories, if not
 141  
      *            <code>null</code>
 142  
      * @throws Throwable if failures occurred and FailureStrategy dictates it to
 143  
      *             be re-thrown.
 144  
      */
 145  
     public void run(Configuration configuration, List<CandidateSteps> candidateSteps, Story story, MetaFilter filter,
 146  
             State beforeStories) throws Throwable {
 147  19
         run(configuration, new ProvidedStepsFactory(candidateSteps), story, filter, beforeStories);
 148  18
     }
 149  
 
 150  
     /**
 151  
      * Runs a Story with the given steps factory, applying the given meta
 152  
      * filter, and staring from given state.
 153  
      * 
 154  
      * @param configuration the Configuration used to run story
 155  
      * @param stepsFactory the InjectableStepsFactory used to created the
 156  
      *            candidate steps methods
 157  
      * @param story the Story to run
 158  
      * @param filter the Filter to apply to the story Meta
 159  
      * @param beforeStories the State before running any of the stories, if not
 160  
      *            <code>null</code>
 161  
      * 
 162  
      * @throws Throwable if failures occurred and FailureStrategy dictates it to
 163  
      *             be re-thrown.
 164  
      */
 165  
     public void run(Configuration configuration, InjectableStepsFactory stepsFactory, Story story, MetaFilter filter,
 166  
             State beforeStories) throws Throwable {
 167  22
         RunContext context = new RunContext(configuration, stepsFactory, story.getPath(), filter);
 168  22
         if (beforeStories != null) {
 169  3
             context.stateIs(beforeStories);
 170  
         }
 171  22
         Map<String, String> storyParameters = new HashMap<String, String>();
 172  22
         run(context, story, storyParameters);
 173  19
     }
 174  
 
 175  
     /**
 176  
      * Returns the parsed story from the given path
 177  
      * 
 178  
      * @param configuration the Configuration used to run story
 179  
      * @param storyPath the story path
 180  
      * @return The parsed Story
 181  
      */
 182  
     public Story storyOfPath(Configuration configuration, String storyPath) {
 183  4
         String storyAsText = configuration.storyLoader().loadStoryAsText(storyPath);
 184  4
         return configuration.storyParser().parseStory(storyAsText, storyPath);
 185  
     }
 186  
 
 187  
     /**
 188  
      * Returns the parsed story from the given text
 189  
      * 
 190  
      * @param configuration the Configuration used to run story
 191  
      * @param storyAsText the story text
 192  
      * @param storyId the story Id, which will be returned as story path
 193  
      * @return The parsed Story
 194  
      */
 195  
     public Story storyOfText(Configuration configuration, String storyAsText, String storyId) {
 196  0
         return configuration.storyParser().parseStory(storyAsText, storyId);
 197  
     }
 198  
 
 199  
     /**
 200  
      * Cancels story execution following a timeout
 201  
      * 
 202  
      * @param story the Story that was timed out
 203  
      * @param storyDuration the StoryDuration
 204  
      */
 205  
     public void cancelStory(Story story, StoryDuration storyDuration) {
 206  2
         cancelledStories.put(story, storyDuration);
 207  2
     }
 208  
 
 209  
     private void run(RunContext context, Story story, Map<String, String> storyParameters) throws Throwable {
 210  
         try {
 211  24
             runCancellable(context, story, storyParameters);
 212  3
         } catch (Throwable e) {
 213  3
             if (cancelledStories.containsKey(story)) {
 214  2
                 reporter.get().storyCancelled(story, cancelledStories.get(story));
 215  2
                 reporter.get().afterStory(context.givenStory);
 216  
             }
 217  3
             throw e;
 218  
         } finally {
 219  24
             if (!context.givenStory() && reporter.get() instanceof ConcurrentStoryReporter) {
 220  22
                 ((ConcurrentStoryReporter) reporter.get()).invokeDelayed();
 221  
             }
 222  
         }
 223  21
     }
 224  
 
 225  
     private void runCancellable(RunContext context, Story story, Map<String, String> storyParameters) throws Throwable {
 226  24
         if (!context.givenStory) {
 227  22
             reporter.set(reporterFor(context, story));
 228  
         }
 229  24
         pendingStepStrategy.set(context.configuration().pendingStepStrategy());
 230  24
         failureStrategy.set(context.configuration().failureStrategy());
 231  
 
 232  24
         resetStoryFailure(context);
 233  
 
 234  24
         if (context.dryRun()) {
 235  2
             reporter.get().dryRun();
 236  
         }
 237  
 
 238  24
         if (context.configuration().storyControls().resetStateBeforeStory()) {
 239  22
             context.resetState();
 240  
         }
 241  
 
 242  
         // run before story steps, if any
 243  24
         reporter.get().beforeStory(story, context.givenStory());
 244  
 
 245  24
         boolean storyAllowed = true;
 246  
 
 247  24
         FilteredStory filterContext = context.filter(story);
 248  23
         Meta storyMeta = story.getMeta();
 249  23
         if (!filterContext.allowed()) {
 250  2
             reporter.get().storyNotAllowed(story, context.metaFilterAsString());
 251  2
             storyAllowed = false;
 252  
         }
 253  
 
 254  23
         if (storyAllowed) {
 255  
 
 256  21
             reporter.get().narrative(story.getNarrative());
 257  
 
 258  21
             runBeforeOrAfterStorySteps(context, story, Stage.BEFORE);
 259  
 
 260  
             // determine if before and after scenario steps should be run
 261  21
             boolean runBeforeAndAfterScenarioSteps = shouldRunBeforeOrAfterScenarioSteps(context);
 262  
 
 263  21
             for (Scenario scenario : story.getScenarios()) {
 264  
                 // scenario also inherits meta from story
 265  27
                 boolean scenarioAllowed = true;
 266  27
                 if (failureOccurred(context) && context.configuration().storyControls().skipScenariosAfterFailure()) {
 267  1
                     continue;
 268  
                 }
 269  26
                 reporter.get().beforeScenario(scenario.getTitle());
 270  26
                 reporter.get().scenarioMeta(scenario.getMeta());
 271  
 
 272  26
                 if (!filterContext.allowed(scenario)) {
 273  2
                     reporter.get().scenarioNotAllowed(scenario, context.metaFilterAsString());
 274  2
                     scenarioAllowed = false;
 275  
                 }
 276  
 
 277  26
                 if (scenarioAllowed) {
 278  24
                     if (context.configuration().storyControls().resetStateBeforeScenario()) {
 279  21
                         context.resetState();
 280  
                     }
 281  24
                     Meta storyAndScenarioMeta = scenario.getMeta().inheritFrom(storyMeta);
 282  
                     // run before scenario steps, if allowed
 283  24
                     if (runBeforeAndAfterScenarioSteps) {
 284  23
                         runBeforeOrAfterScenarioSteps(context, scenario, storyAndScenarioMeta, Stage.BEFORE,
 285  
                                 ScenarioType.NORMAL);
 286  
                     }
 287  
 
 288  
                     // run given stories, if any
 289  24
                     runGivenStories(scenario, context);
 290  24
                     if (isParameterisedByExamples(scenario)) {
 291  
                         // run parametrised scenarios by examples
 292  1
                         runParametrisedScenariosByExamples(context, scenario, storyAndScenarioMeta);
 293  
                     } else { // run as plain old scenario
 294  23
                         addMetaParameters(storyParameters, storyAndScenarioMeta);
 295  23
                         runScenarioSteps(context, scenario, storyParameters);
 296  
                     }
 297  
 
 298  
                     // run after scenario steps, if allowed
 299  23
                     if (runBeforeAndAfterScenarioSteps) {
 300  22
                         runBeforeOrAfterScenarioSteps(context, scenario, storyAndScenarioMeta, Stage.AFTER,
 301  
                                 ScenarioType.NORMAL);
 302  
                     }
 303  
 
 304  
                 }
 305  
 
 306  25
                 reporter.get().afterScenario();
 307  25
             }
 308  
 
 309  
             // run after story steps, if any
 310  20
             runBeforeOrAfterStorySteps(context, story, Stage.AFTER);
 311  
 
 312  
         }
 313  
 
 314  22
         reporter.get().afterStory(context.givenStory());
 315  
 
 316  
         // handle any failure according to strategy
 317  22
         if (!context.givenStory()) {
 318  20
             handleStoryFailureByStrategy();
 319  
         }
 320  21
     }
 321  
 
 322  
     private void addMetaParameters(Map<String, String> storyParameters, Meta meta) {
 323  24
         for (String name : meta.getPropertyNames()) {
 324  0
             storyParameters.put(name, meta.getProperty(name));
 325  
         }
 326  24
     }
 327  
 
 328  
     private boolean shouldRunBeforeOrAfterScenarioSteps(RunContext context) {
 329  21
         Configuration configuration = context.configuration();
 330  21
         if (!configuration.storyControls().skipBeforeAndAfterScenarioStepsIfGivenStory()) {
 331  19
             return true;
 332  
         }
 333  
 
 334  2
         return !context.givenStory();
 335  
     }
 336  
 
 337  
     private boolean failureOccurred(RunContext context) {
 338  27
         return context.failureOccurred();
 339  
     }
 340  
 
 341  
     private StoryReporter reporterFor(RunContext context, Story story) {
 342  22
         Configuration configuration = context.configuration();
 343  22
         if (context.givenStory()) {
 344  0
             return configuration.storyReporter(reporterStoryPath.get());
 345  
         } else {
 346  
             // store parent story path for reporting
 347  22
             reporterStoryPath.set(story.getPath());
 348  22
             return configuration.storyReporter(reporterStoryPath.get());
 349  
         }
 350  
     }
 351  
 
 352  
     private void handleStoryFailureByStrategy() throws Throwable {
 353  23
         Throwable throwable = storyFailure.get();
 354  23
         if (throwable != null) {
 355  11
             currentStrategy.get().handleFailure(throwable);
 356  
         }
 357  21
     }
 358  
 
 359  
     private void resetStoryFailure(RunContext context) {
 360  28
         if (context.givenStory()) {
 361  
             // do not reset failure for given stories
 362  2
             return;
 363  
         }
 364  26
         currentStrategy.set(context.configuration().failureStrategy());
 365  26
         storyFailure.set(null);
 366  26
     }
 367  
 
 368  
     private void runGivenStories(Scenario scenario, RunContext context) throws Throwable {
 369  24
         GivenStories givenStories = scenario.getGivenStories();
 370  24
         if (givenStories.getPaths().size() > 0) {
 371  2
             reporter.get().givenStories(givenStories);
 372  2
             for (GivenStory givenStory : givenStories.getStories()) {
 373  2
                 RunContext childContext = context.childContextFor(givenStory);
 374  
                 // run given story, using any parameters if provided
 375  2
                 Story story = storyOfPath(context.configuration(), childContext.path());
 376  2
                 run(childContext, story, givenStory.getParameters());
 377  2
             }
 378  
         }
 379  24
     }
 380  
 
 381  
     private boolean isParameterisedByExamples(Scenario scenario) {
 382  24
         return scenario.getExamplesTable().getRowCount() > 0 && !scenario.getGivenStories().requireParameters();
 383  
     }
 384  
 
 385  
     private void runParametrisedScenariosByExamples(RunContext context, Scenario scenario, Meta storyAndScenarioMeta)
 386  
             throws InterruptedException {
 387  1
         ExamplesTable table = scenario.getExamplesTable();
 388  1
         reporter.get().beforeExamples(scenario.getSteps(), table);
 389  1
         for (Map<String, String> scenarioParameters : table.getRows()) {
 390  1
             reporter.get().example(scenarioParameters);
 391  1
             if (context.configuration().storyControls().resetStateBeforeScenario()) {
 392  1
                 context.resetState();
 393  
             }
 394  1
             runBeforeOrAfterScenarioSteps(context, scenario, storyAndScenarioMeta, Stage.BEFORE, ScenarioType.EXAMPLE);
 395  1
             addMetaParameters(scenarioParameters, storyAndScenarioMeta);
 396  1
             runScenarioSteps(context, scenario, scenarioParameters);
 397  1
             runBeforeOrAfterScenarioSteps(context, scenario, storyAndScenarioMeta, Stage.AFTER, ScenarioType.EXAMPLE);
 398  
         }
 399  1
         reporter.get().afterExamples();
 400  1
     }
 401  
 
 402  
     private void runBeforeOrAfterStorySteps(RunContext context, Story story, Stage stage) throws InterruptedException {
 403  41
         runStepsWhileKeepingState(context, context.collectBeforeOrAfterStorySteps(story, stage));
 404  41
     }
 405  
 
 406  
     private void runBeforeOrAfterScenarioSteps(RunContext context, Scenario scenario, Meta storyAndScenarioMeta,
 407  
             Stage stage, ScenarioType type) throws InterruptedException {
 408  47
         runStepsWhileKeepingState(context, context.collectBeforeOrAfterScenarioSteps(storyAndScenarioMeta, stage, type));
 409  47
     }
 410  
 
 411  
     private void runScenarioSteps(RunContext context, Scenario scenario, Map<String, String> scenarioParameters)
 412  
             throws InterruptedException {
 413  24
         boolean restart = true;
 414  48
         while (restart) {
 415  25
             restart = false;
 416  25
             List<Step> steps = context.collectScenarioSteps(scenario, scenarioParameters);
 417  
             try {
 418  25
                 runStepsWhileKeepingState(context, steps);
 419  1
             } catch (RestartingScenarioFailure e) {
 420  1
                 restart = true;
 421  1
                 continue;
 422  23
             }
 423  23
             generatePendingStepMethods(context, steps);
 424  23
         }
 425  23
     }
 426  
 
 427  
     private void generatePendingStepMethods(RunContext context, List<Step> steps) {
 428  23
         List<PendingStep> pendingSteps = new ArrayList<PendingStep>();
 429  23
         for (Step step : steps) {
 430  33
             if (step instanceof PendingStep) {
 431  0
                 pendingSteps.add((PendingStep) step);
 432  
             }
 433  
         }
 434  23
         if (!pendingSteps.isEmpty()) {
 435  0
             PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration().keywords());
 436  0
             List<String> methods = new ArrayList<String>();
 437  0
             for (PendingStep pendingStep : pendingSteps) {
 438  0
                 if (!pendingStep.annotated()) {
 439  0
                     methods.add(generator.generateMethod(pendingStep));
 440  
                 }
 441  
             }
 442  0
             reporter.get().pendingMethods(methods);
 443  
         }
 444  23
     }
 445  
 
 446  
     private void runStepsWhileKeepingState(RunContext context, List<Step> steps) throws InterruptedException {
 447  120
         if (steps == null || steps.size() == 0) {
 448  87
             return;
 449  
         }
 450  33
         State state = context.state();
 451  33
         for (Step step : steps) {
 452  
             try {
 453  46
                 context.interruptIfCancelled();
 454  45
                 state = state.run(step);
 455  1
             } catch (RestartingScenarioFailure e) {
 456  1
                 reporter.get().restarted(step.toString(), e);
 457  1
                 throw e;
 458  44
             }
 459  
         }
 460  31
         context.stateIs(state);
 461  31
     }
 462  
 
 463  
     public interface State {
 464  
         State run(Step step);
 465  
     }
 466  
 
 467  150
     private final class FineSoFar implements State {
 468  
 
 469  
         public State run(Step step) {
 470  38
             if ( step instanceof ParameterisedStep ){
 471  1
                 ((ParameterisedStep)step).describeTo(reporter.get());
 472  
             }
 473  38
             UUIDExceptionWrapper storyFailureIfItHappened = storyFailure.get(); 
 474  38
             StepResult result = step.perform(storyFailureIfItHappened);
 475  37
             result.describeTo(reporter.get());
 476  37
             UUIDExceptionWrapper stepFailure = result.getFailure();
 477  37
             if (stepFailure == null) {
 478  22
                 return this;
 479  
             }
 480  
 
 481  15
             storyFailure.set(mostImportantOf(storyFailureIfItHappened, stepFailure));
 482  15
             currentStrategy.set(strategyFor(storyFailure.get()));
 483  15
             return new SomethingHappened(stepFailure);
 484  
         }
 485  
 
 486  
         private UUIDExceptionWrapper mostImportantOf(UUIDExceptionWrapper failure1, UUIDExceptionWrapper failure2) {
 487  15
             return failure1 == null ? failure2
 488  
                     : failure1.getCause() instanceof PendingStepFound ? (failure2 == null ? failure1 : failure2)
 489  
                             : failure1;
 490  
         }
 491  
 
 492  
         private FailureStrategy strategyFor(Throwable failure) {
 493  15
             if (failure instanceof PendingStepFound) {
 494  6
                 return pendingStepStrategy.get();
 495  
             } else {
 496  9
                 return failureStrategy.get();
 497  
             }
 498  
         }
 499  
     }
 500  
 
 501  
     private final class SomethingHappened implements State {
 502  
         UUIDExceptionWrapper scenarioFailure;
 503  
 
 504  16
         public SomethingHappened(UUIDExceptionWrapper scenarioFailure) {
 505  16
             this.scenarioFailure = scenarioFailure;
 506  16
         }
 507  
 
 508  
         public State run(Step step) {
 509  7
             StepResult result = step.doNotPerform(scenarioFailure);
 510  7
             result.describeTo(reporter.get());
 511  7
             return this;
 512  
         }
 513  
     }
 514  
 
 515  
     @Override
 516  
     public String toString() {
 517  1
         return this.getClass().getSimpleName();
 518  
     }
 519  
 
 520  
     /**
 521  
      * The context for running a story.
 522  
      */
 523  65
     private class RunContext {
 524  
         private final Configuration configuration;
 525  
         private final List<CandidateSteps> candidateSteps;
 526  
         private final String path;
 527  
         private final MetaFilter filter;
 528  
         private final boolean givenStory;
 529  
         private State state;
 530  
 
 531  
         public RunContext(Configuration configuration, InjectableStepsFactory stepsFactory, String path,
 532  
                 MetaFilter filter) {
 533  22
             this(configuration, stepsFactory.createCandidateSteps(), path, filter);
 534  22
         }
 535  
 
 536  
         public RunContext(Configuration configuration, List<CandidateSteps> steps, String path, MetaFilter filter) {
 537  29
             this(configuration, steps, path, filter, false);
 538  29
         }
 539  
 
 540  
         private RunContext(Configuration configuration, List<CandidateSteps> steps, String path, MetaFilter filter,
 541  31
                 boolean givenStory) {
 542  31
             this.configuration = configuration;
 543  31
             this.candidateSteps = steps;
 544  31
             this.path = path;
 545  31
             this.filter = filter;
 546  31
             this.givenStory = givenStory;
 547  31
             resetState();
 548  31
         }
 549  
 
 550  
         public void interruptIfCancelled() throws InterruptedException {
 551  46
             for (Story story : cancelledStories.keySet()) {
 552  1
                 if (path.equals(story.getPath())) {
 553  1
                     throw new InterruptedException(path);
 554  
                 }
 555  
             }
 556  45
         }
 557  
 
 558  
         public boolean dryRun() {
 559  24
             return configuration.storyControls().dryRun();
 560  
         }
 561  
 
 562  
         public Configuration configuration() {
 563  173
             return configuration;
 564  
         }
 565  
 
 566  
         public List<CandidateSteps> candidateSteps() {
 567  7
             return candidateSteps;
 568  
         }
 569  
 
 570  
         public boolean givenStory() {
 571  144
             return givenStory;
 572  
         }
 573  
 
 574  
         public String path() {
 575  2
             return path;
 576  
         }
 577  
 
 578  
         public FilteredStory filter(Story story) {
 579  24
             return new FilteredStory(filter, story, configuration.storyControls());
 580  
         }
 581  
 
 582  
         public String metaFilterAsString() {
 583  4
             return filter.asString();
 584  
         }
 585  
 
 586  
         public List<Step> collectBeforeOrAfterStorySteps(Story story, Stage stage) {
 587  41
             return configuration.stepCollector().collectBeforeOrAfterStorySteps(candidateSteps, story, stage,
 588  
                     givenStory);
 589  
         }
 590  
 
 591  
         public List<Step> collectBeforeOrAfterScenarioSteps(Meta storyAndScenarioMeta, Stage stage, ScenarioType type) {
 592  47
             return configuration.stepCollector().collectBeforeOrAfterScenarioSteps(candidateSteps,
 593  
                     storyAndScenarioMeta, stage, type);
 594  
         }
 595  
 
 596  
         public List<Step> collectScenarioSteps(Scenario scenario, Map<String, String> parameters) {
 597  25
             return configuration.stepCollector().collectScenarioSteps(candidateSteps, scenario, parameters);
 598  
         }
 599  
 
 600  
         public RunContext childContextFor(GivenStory givenStory) {
 601  2
             String actualPath = configuration.pathCalculator().calculate(path, givenStory.getPath());
 602  2
             return new RunContext(configuration, candidateSteps, actualPath, filter, true);
 603  
         }
 604  
 
 605  
         public State state() {
 606  46
             return state;
 607  
         }
 608  
 
 609  
         public void stateIs(State state) {
 610  37
             this.state = state;
 611  37
         }
 612  
 
 613  
         public boolean failureOccurred() {
 614  27
             return failed(state);
 615  
         }
 616  
 
 617  
         public void resetState() {
 618  75
             this.state = new FineSoFar();
 619  75
         }
 620  
 
 621  
     }
 622  
 
 623  
     public boolean failed(State state) {
 624  29
         return !state.getClass().equals(FineSoFar.class);
 625  
     }
 626  
 
 627  
     public Throwable failure(State state) {
 628  0
         if (failed(state)) {
 629  0
             return ((SomethingHappened) state).scenarioFailure.getCause();
 630  
         }
 631  0
         return null;
 632  
     }
 633  
 }