Coverage Report - org.jbehave.core.embedder.PerformableTree
 
Classes in this File Line Coverage Branch Coverage Complexity
PerformableTree
62%
96/153
37%
29/78
1.87
PerformableTree$1
N/A
N/A
1.87
PerformableTree$AbstractPerformableScenario
92%
23/25
75%
3/4
1.87
PerformableTree$ExamplePerformableScenario
0%
0/20
0%
0/10
1.87
PerformableTree$FineSoFar
92%
13/14
40%
4/10
1.87
PerformableTree$NormalPerformableScenario
66%
10/15
33%
2/6
1.87
PerformableTree$Performable
N/A
N/A
1.87
PerformableTree$PerformableRoot
93%
14/15
50%
1/2
1.87
PerformableTree$PerformableScenario
56%
18/32
25%
2/8
1.87
PerformableTree$PerformableSteps
72%
37/51
59%
13/22
1.87
PerformableTree$PerformableStory
75%
43/57
50%
4/8
1.87
PerformableTree$RunContext
85%
76/89
75%
21/28
1.87
PerformableTree$SomethingHappened
100%
8/8
N/A
1.87
PerformableTree$State
N/A
N/A
1.87
PerformableTree$Status
100%
2/2
N/A
1.87
PerformableTree$Timing
75%
3/4
N/A
1.87
 
 1  
 package org.jbehave.core.embedder;
 2  
 
 3  
 import java.util.ArrayList;
 4  
 import java.util.HashMap;
 5  
 import java.util.LinkedHashMap;
 6  
 import java.util.List;
 7  
 import java.util.Map;
 8  
 
 9  
 import org.apache.commons.lang.StringUtils;
 10  
 import org.apache.commons.lang.builder.ToStringBuilder;
 11  
 import org.apache.commons.lang.builder.ToStringStyle;
 12  
 import org.jbehave.core.annotations.ScenarioType;
 13  
 import org.jbehave.core.configuration.Configuration;
 14  
 import org.jbehave.core.configuration.Keywords;
 15  
 import org.jbehave.core.embedder.MatchingStepMonitor.StepMatch;
 16  
 import org.jbehave.core.failures.BatchFailures;
 17  
 import org.jbehave.core.failures.FailingUponPendingStep;
 18  
 import org.jbehave.core.failures.PendingStepFound;
 19  
 import org.jbehave.core.failures.PendingStepsFound;
 20  
 import org.jbehave.core.failures.RestartingScenarioFailure;
 21  
 import org.jbehave.core.failures.RestartingStoryFailure;
 22  
 import org.jbehave.core.failures.UUIDExceptionWrapper;
 23  
 import org.jbehave.core.model.ExamplesTable;
 24  
 import org.jbehave.core.model.GivenStories;
 25  
 import org.jbehave.core.model.GivenStory;
 26  
 import org.jbehave.core.model.Lifecycle;
 27  
 import org.jbehave.core.model.Meta;
 28  
 import org.jbehave.core.model.Scenario;
 29  
 import org.jbehave.core.model.Story;
 30  
 import org.jbehave.core.model.StoryDuration;
 31  
 import org.jbehave.core.reporters.ConcurrentStoryReporter;
 32  
 import org.jbehave.core.reporters.StoryReporter;
 33  
 import org.jbehave.core.steps.CandidateSteps;
 34  
 import org.jbehave.core.steps.InjectableStepsFactory;
 35  
 import org.jbehave.core.steps.PendingStepMethodGenerator;
 36  
 import org.jbehave.core.steps.Step;
 37  
 import org.jbehave.core.steps.StepCollector.Stage;
 38  
 import org.jbehave.core.steps.StepCreator.ParametrisedStep;
 39  
 import org.jbehave.core.steps.StepCreator.PendingStep;
 40  
 import org.jbehave.core.steps.StepResult;
 41  
 import org.jbehave.core.steps.Timer;
 42  
 
 43  
 /**
 44  
  * Creates a tree of {@link Performable} objects for a set of stories, grouping
 45  
  * sets of performable steps for each story and scenario, and adding before and
 46  
  * after stories steps. The process has two phases:
 47  
  * <ol>
 48  
  * <li>The tree is populated with groups of performable steps when the stories
 49  
  * are added via the {@link #addStories(RunContext, List)} method.</li>
 50  
  * <li>The performable steps are then populated with the results when the
 51  
  * {@link #performBeforeOrAfterStories(RunContext, Stage)} and
 52  
  * {@link #perform(RunContext, Story)} methods are executed.</li>
 53  
  * </ol>
 54  
  * The tree is created per {@link RunContext} for the set of stories being run
 55  
  * but the individual stories can be performed concurrently.
 56  
  */
 57  74
 public class PerformableTree {
 58  
 
 59  1
     private static final Map<String, String> NO_PARAMETERS = new HashMap<String, String>();
 60  
 
 61  74
     private PerformableRoot root = new PerformableRoot();
 62  
 
 63  
     public PerformableRoot getRoot() {
 64  17
         return root;
 65  
     }
 66  
 
 67  
     public void addStories(RunContext context, List<Story> stories) {
 68  16
         root.addBeforeSteps(context.beforeOrAfterStoriesSteps(Stage.BEFORE));
 69  16
         for (Story story : stories) {
 70  46
             root.add(performableStory(context, story, NO_PARAMETERS));
 71  46
         }
 72  16
         root.addAfterSteps(context.beforeOrAfterStoriesSteps(Stage.AFTER));
 73  16
     }
 74  
 
 75  
     private PerformableStory performableStory(RunContext context, Story story, Map<String, String> storyParameters) {
 76  46
         PerformableStory performableStory = new PerformableStory(story, context.configuration().keywords(),
 77  46
                 context.givenStory());
 78  
 
 79  
         // determine if story is allowed
 80  46
         boolean storyAllowed = true;
 81  46
         FilteredStory filteredStory = context.filter(story);
 82  46
         Meta storyMeta = story.getMeta();
 83  46
         if (!filteredStory.allowed()) {
 84  0
             storyAllowed = false;
 85  
         }
 86  
 
 87  46
         performableStory.allowed(storyAllowed);
 88  
 
 89  46
         if (storyAllowed) {
 90  
 
 91  46
             performableStory.addBeforeSteps(context.beforeOrAfterStorySteps(story, Stage.BEFORE));
 92  
 
 93  46
             performableStory
 94  46
                     .addGivenStories(performableGivenStories(context, story.getGivenStories(), storyParameters));
 95  
 
 96  
             // determine if before and after scenario steps should be run
 97  46
             boolean runBeforeAndAfterScenarioSteps = shouldRunBeforeOrAfterScenarioSteps(context);
 98  
 
 99  46
             for (Scenario scenario : story.getScenarios()) {
 100  46
                 performableStory.add(performableScenario(context, story, storyParameters, filteredStory, storyMeta,
 101  
                         runBeforeAndAfterScenarioSteps, scenario));
 102  46
             }
 103  
 
 104  46
             performableStory.addAfterSteps(context.beforeOrAfterStorySteps(story, Stage.AFTER));
 105  
 
 106  
         }
 107  
 
 108  46
         return performableStory;
 109  
     }
 110  
 
 111  
     private PerformableScenario performableScenario(RunContext context, Story story,
 112  
             Map<String, String> storyParameters, FilteredStory filterContext, Meta storyMeta,
 113  
             boolean runBeforeAndAfterScenarioSteps, Scenario scenario) {
 114  46
         PerformableScenario performableScenario = new PerformableScenario(scenario, story.getPath());
 115  
         // scenario also inherits meta from story
 116  46
         boolean scenarioAllowed = true;
 117  46
         if (failureOccurred(context) && context.configuration().storyControls().skipScenariosAfterFailure()) {
 118  0
             return performableScenario;
 119  
         }
 120  
 
 121  46
         if (!filterContext.allowed(scenario)) {
 122  0
             scenarioAllowed = false;
 123  
         }
 124  
 
 125  46
         performableScenario.allowed(scenarioAllowed);
 126  
 
 127  46
         if (scenarioAllowed) {
 128  46
             Lifecycle lifecycle = story.getLifecycle();
 129  
 
 130  46
             Meta storyAndScenarioMeta = scenario.getMeta().inheritFrom(storyMeta);
 131  46
                         NormalPerformableScenario normalScenario = normalScenario(
 132  
                                         context, lifecycle, scenario, storyAndScenarioMeta,
 133  
                                         storyParameters);
 134  
 
 135  
             // run before scenario steps, if allowed
 136  46
             if (runBeforeAndAfterScenarioSteps) {
 137  46
                     normalScenario.addBeforeSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta,
 138  
                         Stage.BEFORE, ScenarioType.NORMAL));
 139  
             }
 140  
             
 141  46
                         if (isParameterisedByExamples(scenario)) {
 142  0
                 ExamplesTable table = scenario.getExamplesTable();
 143  0
                 for (Map<String, String> scenarioParameters : table.getRows()) {
 144  0
                                         ExamplePerformableScenario exampleScenario = exampleScenario(
 145  
                                                         context, lifecycle, scenario, storyAndScenarioMeta,
 146  
                                                         scenarioParameters);
 147  0
                                         performableScenario.addExampleScenario(exampleScenario);
 148  0
                 }
 149  0
             } else { // plain old scenario
 150  46
                                 performableScenario.useNormalScenario(normalScenario);
 151  
             }
 152  
 
 153  
             // after scenario steps, if allowed
 154  46
             if (runBeforeAndAfterScenarioSteps) {
 155  46
                     normalScenario.addAfterSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.AFTER,
 156  
                         ScenarioType.NORMAL));
 157  
             }
 158  
 
 159  
         }
 160  46
         return performableScenario;
 161  
     }
 162  
 
 163  
         private NormalPerformableScenario normalScenario(RunContext context,
 164  
                         Lifecycle lifecycle, Scenario scenario, Meta storyAndScenarioMeta,
 165  
                         Map<String, String> storyParameters) {
 166  46
                 NormalPerformableScenario normalScenario = new NormalPerformableScenario(scenario);
 167  46
                 addStepsWithLifecycle(normalScenario, context, lifecycle, storyParameters,
 168  
                                 scenario, storyAndScenarioMeta);
 169  46
                 return normalScenario;
 170  
         }
 171  
 
 172  
         private ExamplePerformableScenario exampleScenario(RunContext context,
 173  
                         Lifecycle lifecycle, Scenario scenario, Meta storyAndScenarioMeta,
 174  
                         Map<String, String> parameters) {
 175  0
         ExamplePerformableScenario exampleScenario = new ExamplePerformableScenario(parameters);
 176  0
         exampleScenario.addBeforeSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.BEFORE,
 177  
                 ScenarioType.EXAMPLE));
 178  0
         addStepsWithLifecycle(exampleScenario, context, lifecycle, parameters, scenario, storyAndScenarioMeta);
 179  0
         exampleScenario.addAfterSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.AFTER,
 180  
                 ScenarioType.EXAMPLE));
 181  0
         return exampleScenario;
 182  
     }
 183  
 
 184  
         private void addStepsWithLifecycle(AbstractPerformableScenario performableScenario, RunContext context,
 185  
                         Lifecycle lifecycle, Map<String, String> parameters, Scenario scenario, Meta storyAndScenarioMeta) {
 186  46
                 performableScenario.addBeforeSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.BEFORE,
 187  
                 ScenarioType.ANY));
 188  46
                 performableScenario.addBeforeSteps(context.lifecycleSteps(lifecycle, storyAndScenarioMeta, Stage.BEFORE));
 189  46
                 addMetaParameters(parameters, storyAndScenarioMeta);
 190  46
                 performableScenario.addGivenStories(performableGivenStories(context, scenario.getGivenStories(),
 191  
                         parameters));
 192  46
                 performableScenario.addSteps(context.scenarioSteps(scenario, parameters));
 193  46
                 performableScenario.addAfterSteps(context.lifecycleSteps(lifecycle, storyAndScenarioMeta, Stage.AFTER));
 194  46
                 performableScenario.addAfterSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.AFTER,
 195  
                 ScenarioType.ANY));
 196  46
         }
 197  
 
 198  
     private List<PerformableStory> performableGivenStories(RunContext context, GivenStories givenStories,
 199  
             Map<String, String> parameters) {
 200  92
         List<PerformableStory> stories = new ArrayList<PerformableStory>();
 201  92
         if (givenStories.getPaths().size() > 0) {
 202  0
             for (GivenStory givenStory : givenStories.getStories()) {
 203  0
                 RunContext childContext = context.childContextFor(givenStory);
 204  
                 // run given story, using any parameters provided
 205  0
                 Story story = storyOfPath(context.configuration(), childContext.path());                
 206  0
                 if ( givenStory.hasAnchorParameters() ){
 207  0
                     story = storyWithMatchingScenarios(story, givenStory.getAnchorParameters());
 208  
                 }
 209  0
                 parameters.putAll(givenStory.getParameters());
 210  0
                 stories.add(performableStory(childContext, story, parameters));
 211  0
             }
 212  
         }
 213  92
         return stories;
 214  
     }
 215  
 
 216  
     private Story storyWithMatchingScenarios(Story story, Map<String,String> parameters) {
 217  0
         if ( parameters.isEmpty() ) return story;
 218  0
         List<Scenario> scenarios = new ArrayList<Scenario>();
 219  0
         for ( Scenario scenario : story.getScenarios() ){
 220  0
             if ( matchesParameters(scenario, parameters) ){
 221  0
                 scenarios.add(scenario);
 222  
             }
 223  0
         }
 224  0
         return new Story(story.getPath(), story.getDescription(), story.getMeta(), story.getNarrative(), scenarios); 
 225  
     }
 226  
 
 227  
     private boolean matchesParameters(Scenario scenario, Map<String, String> parameters) {
 228  0
         Meta meta = scenario.getMeta();
 229  0
         for ( String name : parameters.keySet() ){
 230  0
             if ( meta.hasProperty(name) ){
 231  0
                 return meta.getProperty(name).equals(parameters.get(name));
 232  
             }
 233  0
         }
 234  0
         return false;
 235  
     }
 236  
 
 237  
     /**
 238  
      * Returns the parsed story from the given path
 239  
      * 
 240  
      * @param configuration the Configuration used to run story
 241  
      * @param storyPath the story path
 242  
      * @return The parsed Story
 243  
      */
 244  
     public Story storyOfPath(Configuration configuration, String storyPath) {
 245  46
         String storyAsText = configuration.storyLoader().loadStoryAsText(storyPath);
 246  46
         return configuration.storyParser().parseStory(storyAsText, storyPath);
 247  
     }
 248  
 
 249  
     /**
 250  
      * Returns the parsed story from the given text
 251  
      * 
 252  
      * @param configuration the Configuration used to run story
 253  
      * @param storyAsText the story text
 254  
      * @param storyId the story Id, which will be returned as story path
 255  
      * @return The parsed Story
 256  
      */
 257  
     public Story storyOfText(Configuration configuration, String storyAsText, String storyId) {
 258  0
         return configuration.storyParser().parseStory(storyAsText, storyId);
 259  
     }
 260  
 
 261  
     private void addMetaParameters(Map<String, String> storyParameters, Meta meta) {
 262  46
         for (String name : meta.getPropertyNames()) {
 263  0
             storyParameters.put(name, meta.getProperty(name));
 264  0
         }
 265  46
     }
 266  
 
 267  
     private boolean shouldRunBeforeOrAfterScenarioSteps(RunContext context) {
 268  46
         Configuration configuration = context.configuration();
 269  46
         if (!configuration.storyControls().skipBeforeAndAfterScenarioStepsIfGivenStory()) {
 270  46
             return true;
 271  
         }
 272  
 
 273  0
         return !context.givenStory();
 274  
     }
 275  
 
 276  
     private boolean failureOccurred(RunContext context) {
 277  46
         return context.failureOccurred();
 278  
     }
 279  
 
 280  
     private boolean isParameterisedByExamples(Scenario scenario) {
 281  46
         return scenario.getExamplesTable().getRowCount() > 0 && !scenario.getGivenStories().requireParameters();
 282  
     }
 283  
 
 284  
     static void generatePendingStepMethods(RunContext context, List<Step> steps) {
 285  0
         List<PendingStep> pendingSteps = new ArrayList<PendingStep>();
 286  0
         for (Step step : steps) {
 287  0
             if (step instanceof PendingStep) {
 288  0
                 pendingSteps.add((PendingStep) step);
 289  
             }
 290  0
         }
 291  0
         if (!pendingSteps.isEmpty()) {
 292  0
             PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration().keywords());
 293  0
             List<String> methods = new ArrayList<String>();
 294  0
             for (PendingStep pendingStep : pendingSteps) {
 295  0
                 if (!pendingStep.annotated()) {
 296  0
                     methods.add(generator.generateMethod(pendingStep));
 297  
                 }
 298  0
             }
 299  
         }
 300  0
     }
 301  
 
 302  
     public interface State {
 303  
         
 304  
         State run(Step step, List<StepResult> results, StoryReporter reporter,
 305  
                 UUIDExceptionWrapper storyFailureIfItHappened);
 306  
 
 307  
         UUIDExceptionWrapper getFailure();
 308  
     }
 309  
 
 310  190
     private final static class FineSoFar implements State {
 311  
 
 312  
         public State run(Step step, List<StepResult> results, StoryReporter reporter,
 313  
                 UUIDExceptionWrapper storyFailureIfItHappened) {
 314  37
             if (step instanceof ParametrisedStep) {
 315  37
                 ((ParametrisedStep) step).describeTo(reporter);
 316  
             }
 317  37
             StepResult result = step.perform(storyFailureIfItHappened);
 318  37
             results.add(result);
 319  37
             result.describeTo(reporter);
 320  36
             UUIDExceptionWrapper stepFailure = result.getFailure();
 321  36
             if (stepFailure == null) {
 322  23
                 return this;
 323  
             }
 324  
 
 325  13
             mostImportantOf(storyFailureIfItHappened, stepFailure);
 326  13
             return new SomethingHappened(stepFailure);
 327  
         }
 328  
 
 329  
         private UUIDExceptionWrapper mostImportantOf(UUIDExceptionWrapper failure1, UUIDExceptionWrapper failure2) {
 330  13
             return failure1 == null ? failure2
 331  0
                     : failure1.getCause() instanceof PendingStepFound ? (failure2 == null ? failure1 : failure2)
 332  
                             : failure1;
 333  
         }
 334  
 
 335  
         public UUIDExceptionWrapper getFailure() {
 336  37
             return null;
 337  
         }
 338  
 
 339  
     }
 340  
 
 341  
     private final static class SomethingHappened implements State {
 342  
         private UUIDExceptionWrapper failure;
 343  
 
 344  13
         public SomethingHappened(UUIDExceptionWrapper failure) {
 345  13
             this.failure = failure;
 346  13
         }
 347  
 
 348  
         public State run(Step step, List<StepResult> results, StoryReporter reporter, UUIDExceptionWrapper storyFailure) {
 349  2
             StepResult result = step.doNotPerform(storyFailure);
 350  2
             results.add(result);
 351  2
             result.describeTo(reporter);
 352  2
             return this;
 353  
         }
 354  
 
 355  
         public UUIDExceptionWrapper getFailure() {
 356  17
             return failure;
 357  
         }
 358  
     }
 359  
 
 360  
     public void perform(RunContext context, Story story) {
 361  36
             boolean restartingStory = false;
 362  
 
 363  
         try {
 364  36
             performCancellable(context, story);
 365  28
             if (context.restartStory()){
 366  0
                                 context.reporter().restartedStory(story, context.failure(context.state()));
 367  0
                                 restartingStory = true;
 368  0
                     perform(context, story);
 369  
             }
 370  8
         } catch (InterruptedException e) {
 371  8
             if (context.isCancelled(story)) {
 372  8
                 context.reporter().storyCancelled(story, context.storyDuration(story));
 373  8
                 context.reporter().afterStory(context.givenStory);
 374  
             }
 375  8
                 throw new UUIDExceptionWrapper(e);
 376  
         } finally {
 377  36
             if (!context.givenStory() && context.reporter() instanceof ConcurrentStoryReporter && !restartingStory ) {
 378  35
                 ((ConcurrentStoryReporter) context.reporter()).invokeDelayed();
 379  
             }
 380  
         }
 381  28
     }
 382  
 
 383  
     private void performCancellable(RunContext context, Story story) throws InterruptedException {
 384  36
         if (context.configuration().storyControls().resetStateBeforeStory()) {
 385  36
             context.resetState();
 386  36
             context.resetFailures();
 387  
         }
 388  36
         context.currentPath(story.getPath());
 389  
 
 390  36
         root.get(story).perform(context);
 391  28
         if (context.failureOccurred()) {
 392  5
             context.addFailure();
 393  
         }
 394  28
     }
 395  
 
 396  
     public void performBeforeOrAfterStories(RunContext context, Stage stage) {
 397  26
         String storyPath = StringUtils.capitalize(stage.name().toLowerCase()) + "Stories";
 398  26
         context.currentPath(storyPath);
 399  26
         context.reporter().beforeStory(new Story(storyPath), false);
 400  
         try {
 401  26
             (stage == Stage.BEFORE ? root.beforeSteps : root.afterSteps).perform(context);
 402  0
         } catch (InterruptedException e) {
 403  0
             throw new UUIDExceptionWrapper(e);
 404  
         } finally {
 405  26
             if (context.reporter() instanceof ConcurrentStoryReporter) {
 406  24
                 ((ConcurrentStoryReporter) context.reporter()).invokeDelayed();
 407  
             }
 408  
         }
 409  26
         context.reporter().afterStory(false);
 410  26
     }
 411  
 
 412  
     @Override
 413  
     public String toString() {
 414  1
         return this.getClass().getSimpleName();
 415  
     }
 416  
 
 417  
     /**
 418  
      * The context for running a story.
 419  
      */
 420  72
     public static class RunContext {
 421  
         private final Configuration configuration;
 422  
         private final InjectableStepsFactory stepsFactory;
 423  
         private final List<CandidateSteps> candidateSteps;
 424  
                 private final EmbedderMonitor embedderMonitor;
 425  
         private final MetaFilter filter;
 426  
         private final BatchFailures failures;
 427  23
         private Map<Story, StoryDuration> cancelledStories = new HashMap<Story, StoryDuration>();
 428  
         private String path;
 429  
         private boolean givenStory;
 430  
         private State state;
 431  
         private StoryReporter reporter;
 432  23
         private Map<String, List<PendingStep>> pendingStories = new HashMap<String, List<PendingStep>>();
 433  
 
 434  
         public RunContext(Configuration configuration, InjectableStepsFactory stepsFactory, EmbedderMonitor embedderMonitor,
 435  23
                 MetaFilter filter, BatchFailures failures) {
 436  23
             this.configuration = configuration;
 437  23
             this.stepsFactory = stepsFactory;
 438  23
                         this.embedderMonitor = embedderMonitor;
 439  23
             this.candidateSteps = stepsFactory.createCandidateSteps();
 440  23
             this.filter = filter;
 441  23
             this.failures = failures;
 442  23
             resetState();
 443  23
         }
 444  
 
 445  
             public boolean restartScenario() {
 446  28
                     Throwable cause = failure(state);
 447  33
                     while (cause != null) {
 448  5
                             if (cause instanceof RestartingScenarioFailure) {
 449  0
                                     return true;
 450  
                             }
 451  5
                             cause = cause.getCause();
 452  
                     }
 453  28
                     return false;
 454  
             }
 455  
 
 456  
             public boolean restartStory() {
 457  28
                     Throwable cause = failure(state);
 458  33
                     while (cause != null) {
 459  5
                             if (cause instanceof RestartingStoryFailure) {
 460  0
                                     return true;
 461  
                             }
 462  5
                             cause = cause.getCause();
 463  
                     }
 464  28
                     return false;
 465  
             }
 466  
 
 467  
                 public void currentPath(String path) {
 468  62
             this.path = path;
 469  62
             this.reporter = configuration.storyReporter(path);
 470  62
         }
 471  
 
 472  
         public void interruptIfCancelled() throws InterruptedException {
 473  47
             for (Story story : cancelledStories.keySet()) {
 474  15
                 if (path.equals(story.getPath())) {
 475  8
                     throw new InterruptedException(path);
 476  
                 }
 477  7
             }
 478  39
         }
 479  
 
 480  
         public boolean dryRun() {
 481  0
             return configuration.storyControls().dryRun();
 482  
         }
 483  
 
 484  
         public Configuration configuration() {
 485  164
             return configuration;
 486  
         }
 487  
 
 488  
         public boolean givenStory() {
 489  82
             return givenStory;
 490  
         }
 491  
 
 492  
         public String path() {
 493  0
             return path;
 494  
         }
 495  
 
 496  
         public FilteredStory filter(Story story) {
 497  104
             return new FilteredStory(filter, story, configuration.storyControls(), givenStory);
 498  
         }
 499  
 
 500  
         public MetaFilter filter() {
 501  16
             return filter;
 502  
         }
 503  
 
 504  
         public PerformableSteps beforeOrAfterStoriesSteps(Stage stage) {
 505  32
             return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(candidateSteps,
 506  
                     stage));
 507  
         }
 508  
 
 509  
         public PerformableSteps beforeOrAfterStorySteps(Story story, Stage stage) {
 510  92
             return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStorySteps(candidateSteps,
 511  
                     story, stage, givenStory));
 512  
         }
 513  
 
 514  
         public PerformableSteps beforeOrAfterScenarioSteps(Meta storyAndScenarioMeta, Stage stage, ScenarioType type) {
 515  184
             return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterScenarioSteps(candidateSteps,
 516  
                     storyAndScenarioMeta, stage, type));
 517  
         }
 518  
 
 519  
         public PerformableSteps lifecycleSteps(Lifecycle lifecycle, Meta meta, Stage stage) {
 520  92
             MatchingStepMonitor monitor = new MatchingStepMonitor();
 521  92
             List<Step> steps = configuration.stepCollector().collectLifecycleSteps(candidateSteps, lifecycle, meta, stage);
 522  92
             return new PerformableSteps(steps, monitor.matched());
 523  
         }
 524  
 
 525  
         public PerformableSteps scenarioSteps(Scenario scenario, Map<String, String> parameters) {
 526  46
             MatchingStepMonitor monitor = new MatchingStepMonitor();
 527  46
             List<Step> steps = configuration.stepCollector().collectScenarioSteps(candidateSteps, scenario, parameters,
 528  
                     monitor);
 529  46
             return new PerformableSteps(steps, monitor.matched());
 530  
         }
 531  
 
 532  
         public RunContext childContextFor(GivenStory givenStory) {
 533  0
             RunContext child = new RunContext(configuration, stepsFactory, embedderMonitor, filter, failures);
 534  0
             child.path = configuration.pathCalculator().calculate(path, givenStory.getPath());
 535  0
             child.givenStory = true;
 536  0
             return child;
 537  
         }
 538  
 
 539  
         public void cancelStory(Story story, StoryDuration storyDuration) {
 540  9
             cancelledStories.put(story, storyDuration);
 541  9
         }
 542  
 
 543  
         public boolean isCancelled(Story story) {
 544  8
             return cancelledStories.containsKey(story);
 545  
         }
 546  
 
 547  
         public StoryDuration storyDuration(Story story) {
 548  8
             return cancelledStories.get(story);
 549  
         }
 550  
 
 551  
         public State state() {
 552  102
             return state;
 553  
         }
 554  
 
 555  
         public void stateIs(State state) {
 556  22
             this.state = state;
 557  22
         }
 558  
 
 559  
         public boolean failureOccurred() {
 560  74
             return failed(state);
 561  
         }
 562  
 
 563  
         public void resetState() {
 564  95
             this.state = new FineSoFar();
 565  95
         }
 566  
 
 567  
         public void resetFailures() {
 568  36
             this.failures.clear();
 569  36
         }
 570  
 
 571  
         public StoryReporter reporter() {
 572  419
             return reporter;
 573  
         }
 574  
 
 575  
         public boolean failed(State state) {
 576  247
             return !state.getClass().equals(FineSoFar.class);
 577  
         }
 578  
 
 579  
         public Throwable failure(State state) {
 580  61
             if (failed(state)) {
 581  15
                 return state.getFailure().getCause();
 582  
             }
 583  46
             return null;
 584  
         }
 585  
 
 586  
         public void addFailure() {
 587  5
             Throwable failure = failure(state);
 588  5
             if (failure != null) {
 589  5
                 failures.put(state.toString(), failure);
 590  
             }
 591  5
         }
 592  
 
 593  
         public void addFailure(String path, Throwable cause) {
 594  65
             if (cause != null) {
 595  65
                 failures.put(path, cause);
 596  
             }
 597  65
         }
 598  
 
 599  
         public void pendingSteps(List<PendingStep> pendingSteps) {
 600  22
             if (!pendingSteps.isEmpty()) {
 601  0
                 pendingStories.put(path, pendingSteps);
 602  
             }
 603  22
         }
 604  
 
 605  
         public boolean hasPendingSteps() {
 606  0
             return pendingStories.containsKey(path);
 607  
         }
 608  
 
 609  
         public boolean isStoryPending() {
 610  56
             return pendingStories.containsKey(path);
 611  
         }
 612  
 
 613  
         public boolean hasFailed() {
 614  56
             return failed(state);
 615  
         }
 616  
 
 617  
         public Status status(State initial) {
 618  56
             if (isStoryPending()) {
 619  0
                 return Status.PENDING;
 620  56
             } else if (failed(initial)) {
 621  0
                 return Status.NOT_PERFORMED;
 622  
             } else {
 623  56
                 return (hasFailed() ? Status.FAILED : Status.SUCCESSFUL);
 624  
             }
 625  
         }
 626  
 
 627  
         public MetaFilter getFilter() {
 628  2
             return filter;
 629  
         }
 630  
 
 631  
         public BatchFailures getFailures() {
 632  16
             return failures;
 633  
         }
 634  
         
 635  
         public EmbedderMonitor embedderMonitor(){
 636  0
                 return embedderMonitor;
 637  
         }
 638  
     }
 639  
 
 640  
     public static interface Performable {
 641  
 
 642  
         void perform(RunContext context) throws InterruptedException;
 643  
 
 644  
     }
 645  
 
 646  101
     public static class PerformableRoot {
 647  
 
 648  75
         private PerformableSteps beforeSteps = new PerformableSteps();
 649  75
         private Map<String, PerformableStory> stories = new LinkedHashMap<String, PerformableStory>();
 650  75
         private PerformableSteps afterSteps = new PerformableSteps();
 651  
 
 652  
         public void addBeforeSteps(PerformableSteps beforeSteps) {
 653  16
             this.beforeSteps = beforeSteps;
 654  16
         }
 655  
 
 656  
         public void add(PerformableStory performableStory) {
 657  47
             stories.put(performableStory.getStory().getPath(), performableStory);
 658  47
         }
 659  
 
 660  
         public void addAfterSteps(PerformableSteps afterSteps) {
 661  16
             this.afterSteps = afterSteps;
 662  16
         }
 663  
 
 664  
         public PerformableStory get(Story story) {
 665  36
             PerformableStory performableStory = stories.get(story.getPath());
 666  36
             if (performableStory != null) {
 667  36
                 return performableStory;
 668  
             }
 669  0
             throw new RuntimeException("No performable story for path " + story.getPath());
 670  
         }
 671  
 
 672  
         public List<PerformableStory> getStories() {
 673  18
             return new ArrayList<PerformableStory>(stories.values());
 674  
         }
 675  
 
 676  
     }
 677  
 
 678  6
     public static enum Status {
 679  1
         SUCCESSFUL, FAILED, PENDING, NOT_PERFORMED, NOT_ALLOWED;
 680  
     }
 681  
 
 682  
     public static class PerformableStory implements Performable {
 683  
 
 684  
         private final Story story;
 685  
         private String localizedNarrative;
 686  
         private boolean allowed;
 687  
         private Status status;
 688  47
         private List<PerformableStory> givenStories = new ArrayList<PerformableStory>();
 689  47
         private List<PerformableScenario> scenarios = new ArrayList<PerformableScenario>();
 690  47
         private PerformableSteps beforeSteps = new PerformableSteps();
 691  47
         private PerformableSteps afterSteps = new PerformableSteps();
 692  47
         private Timing timing = new Timing();
 693  
         private boolean givenStory;
 694  
 
 695  47
         public PerformableStory(Story story, Keywords keywords, boolean givenStory) {
 696  47
             this.story = story;
 697  47
             this.givenStory = givenStory;
 698  47
             this.localizedNarrative = story.getNarrative().asString(keywords);
 699  47
         }
 700  
 
 701  
         public void allowed(boolean allowed) {
 702  46
             this.allowed = allowed;
 703  46
         }
 704  
 
 705  
         public boolean isAllowed() {
 706  0
             return allowed;
 707  
         }
 708  
 
 709  
         public boolean givenStory() {
 710  0
             return givenStory;
 711  
         }
 712  
 
 713  
         public Status getStatus() {
 714  0
             return status;
 715  
         }
 716  
 
 717  
         public void addGivenStories(List<PerformableStory> performableGivenStories) {
 718  46
             this.givenStories.addAll(performableGivenStories);
 719  46
         }
 720  
 
 721  
         public void addBeforeSteps(PerformableSteps beforeSteps) {
 722  46
             this.beforeSteps = beforeSteps;
 723  46
         }
 724  
 
 725  
         public void addAfterSteps(PerformableSteps afterSteps) {
 726  46
             this.afterSteps = afterSteps;
 727  46
         }
 728  
 
 729  
         public void add(PerformableScenario performableScenario) {
 730  47
             scenarios.add(performableScenario);
 731  47
         }
 732  
 
 733  
         public Story getStory() {
 734  47
             return story;
 735  
         }
 736  
 
 737  
         public String getLocalisedNarrative() {
 738  0
             return localizedNarrative;
 739  
         }
 740  
 
 741  
         public Timing getTiming() {
 742  0
             return timing;
 743  
         }
 744  
 
 745  
         public void perform(RunContext context) throws InterruptedException {
 746  36
             if (!allowed) {
 747  0
                 context.reporter().storyNotAllowed(story, context.filter.asString());
 748  0
                 this.status = Status.NOT_ALLOWED;
 749  
             }
 750  36
             context.reporter().beforeStory(story, context.givenStory);
 751  36
             context.reporter().narrative(story.getNarrative());
 752  36
             context.reporter().lifecyle(story.getLifecycle());
 753  36
             State state = context.state();
 754  36
             Timer timer = new Timer().start();
 755  
             try {
 756  36
                 beforeSteps.perform(context);
 757  36
                     performGivenStories(context);
 758  36
                 performScenarios(context);
 759  28
                 afterSteps.perform(context);
 760  
             } finally {
 761  36
                 timing.setDurationInMillis(timer.stop());
 762  28
             }
 763  28
             context.reporter().afterStory(context.givenStory);
 764  28
             this.status = context.status(state);
 765  28
         }
 766  
 
 767  
         private void performGivenStories(RunContext context) throws InterruptedException {
 768  36
             if (givenStories.size() > 0) {
 769  0
                 context.reporter().givenStories(story.getGivenStories());
 770  0
                 final boolean parentGivenStory = context.givenStory;
 771  0
                 for (PerformableStory story : givenStories) {
 772  0
                     context.givenStory = story.givenStory();
 773  0
                     story.perform(context);
 774  0
                 }
 775  0
                 context.givenStory = parentGivenStory;
 776  
            }
 777  36
         }
 778  
 
 779  
         private void performScenarios(RunContext context) throws InterruptedException {
 780  36
             for (PerformableScenario scenario : scenarios) {
 781  36
                 scenario.perform(context);
 782  28
             }
 783  28
         }
 784  
 
 785  
         public List<PerformableScenario> getScenarios() {
 786  50
             return scenarios;
 787  
         }
 788  
 
 789  
     }
 790  
 
 791  
     public static class PerformableScenario implements Performable {
 792  
 
 793  
         private final Scenario scenario;
 794  
         private final String storyPath;
 795  
         private boolean allowed;
 796  
                 private NormalPerformableScenario normalPerformableScenario;
 797  47
         private List<ExamplePerformableScenario> examplePerformableScenarios = new ArrayList<ExamplePerformableScenario>();
 798  
         @SuppressWarnings("unused")
 799  
                 private Status status;
 800  
 
 801  47
         public PerformableScenario(Scenario scenario, String storyPath) {
 802  47
             this.scenario = scenario;
 803  47
             this.storyPath = storyPath;
 804  47
         }
 805  
 
 806  
         public void useNormalScenario(NormalPerformableScenario normalScenario) {
 807  47
             this.normalPerformableScenario = normalScenario;
 808  47
         }
 809  
 
 810  
         public void addExampleScenario(ExamplePerformableScenario exampleScenario) {
 811  0
             this.examplePerformableScenarios.add(exampleScenario);
 812  0
         }
 813  
 
 814  
         public void allowed(boolean allowed) {
 815  46
             this.allowed = allowed;
 816  46
         }
 817  
 
 818  
         public boolean isAllowed() {
 819  36
             return allowed;
 820  
         }
 821  
 
 822  
         public Scenario getScenario() {
 823  0
             return scenario;
 824  
         }
 825  
 
 826  
         public String getStoryPath() {
 827  0
             return storyPath;
 828  
         }
 829  
 
 830  
         public boolean hasExamples() {
 831  0
             return examplePerformableScenarios.size() > 0;
 832  
         }
 833  
 
 834  
         public List<ExamplePerformableScenario> getExamples() {
 835  0
             return examplePerformableScenarios;
 836  
         }
 837  
 
 838  
         public void perform(RunContext context) throws InterruptedException {
 839  36
                 if ( !isAllowed() ) {
 840  0
                         context.embedderMonitor().scenarioNotAllowed(scenario, context.filter());
 841  0
                         return;
 842  
                 }
 843  36
             context.reporter().beforeScenario(scenario.getTitle());
 844  36
             State state = context.state();
 845  36
                         if (!examplePerformableScenarios.isEmpty()) {
 846  0
                                 context.reporter().beforeExamples(scenario.getSteps(),
 847  0
                                                 scenario.getExamplesTable());
 848  0
                                 for (ExamplePerformableScenario exampleScenario : examplePerformableScenarios) {
 849  0
                                         exampleScenario.perform(context);
 850  0
                                 }
 851  0
                                 context.reporter().afterExamples();
 852  
                         } else {
 853  36
                                 normalPerformableScenario.perform(context);
 854  
                         }
 855  28
                         this.status = context.status(state);
 856  28
                         context.reporter().afterScenario();
 857  28
         }
 858  
 
 859  
     }
 860  
 
 861  
     public static abstract class AbstractPerformableScenario implements Performable {
 862  
 
 863  
         protected final Map<String, String> parameters;
 864  47
         protected final List<PerformableStory> givenStories = new ArrayList<PerformableStory>();
 865  47
         protected final PerformableSteps beforeSteps = new PerformableSteps();
 866  47
         protected final PerformableSteps steps = new PerformableSteps();
 867  47
         protected final PerformableSteps afterSteps = new PerformableSteps();
 868  
 
 869  
         public AbstractPerformableScenario() {
 870  47
             this(new HashMap<String, String>());
 871  47
         }
 872  
 
 873  47
         public AbstractPerformableScenario(Map<String, String> parameters) {
 874  47
             this.parameters = parameters;
 875  47
         }
 876  
 
 877  
         public void addGivenStories(List<PerformableStory> givenStories) {
 878  46
             this.givenStories.addAll(givenStories);
 879  46
         }
 880  
 
 881  
         public void addBeforeSteps(PerformableSteps beforeSteps) {
 882  138
             this.beforeSteps.add(beforeSteps);
 883  138
         }
 884  
 
 885  
         public void addSteps(PerformableSteps steps) {
 886  47
             this.steps.add(steps);
 887  47
         }
 888  
 
 889  
         public void addAfterSteps(PerformableSteps afterSteps) {
 890  138
             this.afterSteps.add(afterSteps);
 891  138
         }
 892  
 
 893  
         public Map<String, String> getParameters() {
 894  0
             return parameters;
 895  
         }
 896  
 
 897  
                 protected void performRestartableSteps(RunContext context)
 898  
                                 throws InterruptedException {
 899  36
                         boolean restart = true;
 900  64
                         while (restart) {
 901  36
                                 restart = false;
 902  36
                                 steps.perform(context);
 903  28
                                 if (context.restartScenario()){
 904  0
                                         restart = true;
 905  
                                 }
 906  
                         }
 907  28
                 }
 908  
     }
 909  
 
 910  
     public static class NormalPerformableScenario extends AbstractPerformableScenario {
 911  
 
 912  
         private Scenario scenario;
 913  
 
 914  47
                 public NormalPerformableScenario(Scenario scenario) {
 915  47
                         this.scenario = scenario;
 916  47
         }
 917  
 
 918  
         public void perform(RunContext context) throws InterruptedException {
 919  36
             if (context.configuration().storyControls().resetStateBeforeScenario()) {
 920  36
                 context.resetState();
 921  
             }
 922  36
             beforeSteps.perform(context);
 923  36
                         if (givenStories.size() > 0) {
 924  0
                                 context.reporter().givenStories(scenario.getGivenStories());
 925  0
                                 for (PerformableStory story : givenStories) {
 926  0
                                         context.givenStory = story.givenStory();
 927  0
                                         story.perform(context);
 928  0
                                 }
 929  
                         }
 930  36
                         performRestartableSteps(context);                
 931  28
             afterSteps.perform(context);
 932  28
         }
 933  
 
 934  
     }
 935  
 
 936  
     public static class ExamplePerformableScenario extends AbstractPerformableScenario {
 937  
 
 938  
         public ExamplePerformableScenario(Map<String, String> exampleParameters) {
 939  0
                 super(exampleParameters);
 940  0
         }
 941  
 
 942  
         public void perform(RunContext context) throws InterruptedException {
 943  0
                         Meta parameterMeta = parameterMeta(context.configuration().keywords(), parameters);
 944  0
                         if (!parameterMeta.isEmpty() && !context.filter().allow(parameterMeta)) {
 945  0
                                 return;
 946  
                         }
 947  0
             if (context.configuration().storyControls().resetStateBeforeScenario()) {
 948  0
                 context.resetState();
 949  
             }
 950  0
             context.reporter().example(parameters);
 951  0
             beforeSteps.perform(context);
 952  0
             for (PerformableStory story : givenStories) {
 953  0
                                 context.givenStory = story.givenStory();
 954  0
                 story.perform(context);
 955  0
             }
 956  0
                         performRestartableSteps(context);                
 957  0
             afterSteps.perform(context);
 958  0
         }
 959  
 
 960  
         private Meta parameterMeta(Keywords keywords, Map<String, String> parameters) {
 961  0
             String meta = keywords.meta();
 962  0
             if (parameters.containsKey(meta)) {
 963  0
                 return Meta.createMeta(parameters.get(meta), keywords);
 964  
             }
 965  0
             return Meta.EMPTY;
 966  
         }
 967  
 
 968  
     }
 969  
 
 970  
     public static class PerformableSteps implements Performable {
 971  
 
 972  
         private transient final List<Step> steps;
 973  
         private transient final List<PendingStep> pendingSteps;
 974  
         private List<StepMatch> matches;
 975  
         private List<StepResult> results;
 976  
 
 977  
         public PerformableSteps() {
 978  385
             this(null);
 979  385
         }
 980  
 
 981  
         public PerformableSteps(List<Step> steps) {
 982  693
             this(steps, null);
 983  693
         }
 984  
 
 985  832
         public PerformableSteps(List<Step> steps, List<StepMatch> stepMatches) {
 986  832
             this.steps =  ( steps == null ? new ArrayList<Step>() : steps );
 987  832
             this.pendingSteps = pendingSteps();
 988  832
             this.matches = stepMatches;
 989  832
         }
 990  
 
 991  
         public void add(PerformableSteps performableSteps){
 992  323
             this.steps.addAll(performableSteps.steps);
 993  323
             this.pendingSteps.addAll(performableSteps.pendingSteps);
 994  323
             if ( performableSteps.matches != null ){
 995  139
                 if ( this.matches == null ){
 996  139
                     this.matches = new ArrayList<StepMatch>();
 997  
                 }
 998  139
                 this.matches.addAll(performableSteps.matches);
 999  
             }
 1000  323
         }
 1001  
         
 1002  
         public void perform(RunContext context) throws InterruptedException {
 1003  190
             if (steps.size() == 0) {
 1004  160
                 return;
 1005  
             }
 1006  30
             State state = context.state();
 1007  30
             StoryReporter reporter = context.reporter();
 1008  30
             results = new ArrayList<StepResult>();
 1009  30
             for (Step step : steps) {
 1010  
                 try {
 1011  47
                                         context.interruptIfCancelled();
 1012  39
                                         state = state.run(step, results, reporter, state.getFailure());
 1013  0
                                 } catch (RestartingScenarioFailure e) {
 1014  0
                         reporter.restarted(step.toString(), e);
 1015  39
                                 }
 1016  38
             }
 1017  22
             context.stateIs(state);
 1018  22
             context.pendingSteps(pendingSteps);
 1019  22
             generatePendingStepMethods(context, pendingSteps);
 1020  22
         }
 1021  
 
 1022  
         private List<PendingStep> pendingSteps() {
 1023  832
             List<PendingStep> pending = new ArrayList<PendingStep>();
 1024  832
             for (Step step : steps) {
 1025  57
                 if (step instanceof PendingStep) {
 1026  0
                     pending.add((PendingStep) step);
 1027  
                 }
 1028  57
             }
 1029  832
             return pending;
 1030  
         }
 1031  
 
 1032  
         private void generatePendingStepMethods(RunContext context, List<PendingStep> pendingSteps) {
 1033  22
             if (!pendingSteps.isEmpty()) {
 1034  0
                 PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration()
 1035  0
                         .keywords());
 1036  0
                 List<String> methods = new ArrayList<String>();
 1037  0
                 for (PendingStep pendingStep : pendingSteps) {
 1038  0
                     if (!pendingStep.annotated()) {
 1039  0
                         methods.add(generator.generateMethod(pendingStep));
 1040  
                     }
 1041  0
                 }
 1042  0
                 context.reporter().pendingMethods(methods);
 1043  0
                 if ( context.configuration().pendingStepStrategy() instanceof FailingUponPendingStep ){
 1044  0
                      throw new PendingStepsFound(pendingSteps);
 1045  
                 }
 1046  
             }
 1047  22
         }
 1048  
 
 1049  
         @Override
 1050  
         public String toString() {
 1051  0
             return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
 1052  
         }
 1053  
 
 1054  
     }
 1055  
 
 1056  74
     public static class Timing {
 1057  
         private long durationInMillis;
 1058  
 
 1059  
         public long getDurationInMillis() {
 1060  0
             return durationInMillis;
 1061  
         }
 1062  
 
 1063  
         public void setDurationInMillis(long durationInMillis) {
 1064  36
             this.durationInMillis = durationInMillis;
 1065  36
         }
 1066  
     }
 1067  
 
 1068  
     public RunContext newRunContext(Configuration configuration, InjectableStepsFactory stepsFactory,
 1069  
             EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) {
 1070  16
         return new RunContext(configuration, stepsFactory, embedderMonitor, filter, failures);
 1071  
     }
 1072  
 
 1073  
 }