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