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