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