Coverage Report - org.jbehave.core.embedder.StoryRunner
 
Classes in this File Line Coverage Branch Coverage Complexity
StoryRunner
93%
115/123
84%
56/66
2.171
StoryRunner$1
N/A
N/A
2.171
StoryRunner$FineSoFar
100%
14/14
70%
7/10
2.171
StoryRunner$RunContext
100%
22/22
100%
4/4
2.171
StoryRunner$SomethingHappened
100%
4/4
N/A
2.171
StoryRunner$State
N/A
N/A
2.171
 
 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.configuration.Configuration;
 9  
 import org.jbehave.core.failures.FailureStrategy;
 10  
 import org.jbehave.core.failures.PendingStepFound;
 11  
 import org.jbehave.core.failures.PendingStepStrategy;
 12  
 import org.jbehave.core.failures.SilentlyAbsorbingFailure;
 13  
 import org.jbehave.core.failures.UUIDExceptionWrapper;
 14  
 import org.jbehave.core.model.ExamplesTable;
 15  
 import org.jbehave.core.model.GivenStories;
 16  
 import org.jbehave.core.model.GivenStory;
 17  
 import org.jbehave.core.model.Meta;
 18  
 import org.jbehave.core.model.Scenario;
 19  
 import org.jbehave.core.model.Story;
 20  
 import org.jbehave.core.reporters.ConcurrentStoryReporter;
 21  
 import org.jbehave.core.reporters.StoryReporter;
 22  
 import org.jbehave.core.steps.CandidateSteps;
 23  
 import org.jbehave.core.steps.PendingStepMethodGenerator;
 24  
 import org.jbehave.core.steps.Step;
 25  
 import org.jbehave.core.steps.StepCollector;
 26  
 import org.jbehave.core.steps.StepCollector.Stage;
 27  
 import org.jbehave.core.steps.StepCreator.PendingStep;
 28  
 import org.jbehave.core.steps.StepResult;
 29  
 
 30  
 import static org.codehaus.plexus.util.StringUtils.capitalizeFirstLetter;
 31  
 
 32  
 /**
 33  
  * Runs a {@link Story}, given a {@link Configuration} and a list of
 34  
  * {@link CandidateSteps}, describing the results to the {@link StoryReporter}.
 35  
  * 
 36  
  * @author Elizabeth Keogh
 37  
  * @author Mauro Talevi
 38  
  * @author Paul Hammant
 39  
  */
 40  210
 public class StoryRunner {
 41  
 
 42  67
     private ThreadLocal<FailureStrategy> currentStrategy = new ThreadLocal<FailureStrategy>();
 43  67
     private ThreadLocal<FailureStrategy> failureStrategy = new ThreadLocal<FailureStrategy>();
 44  67
     private ThreadLocal<PendingStepStrategy> pendingStepStrategy = new ThreadLocal<PendingStepStrategy>();
 45  67
     private ThreadLocal<UUIDExceptionWrapper> storyFailure = new ThreadLocal<UUIDExceptionWrapper>();
 46  67
     private ThreadLocal<StoryReporter> reporter = new ThreadLocal<StoryReporter>();
 47  67
     private ThreadLocal<String> reporterStoryPath = new ThreadLocal<String>();
 48  
 
 49  
     /**
 50  
      * Run steps before or after a collection of stories. Steps are execute only
 51  
      * <b>once</b> per collection of stories.
 52  
      * 
 53  
      * @param configuration the Configuration used to find the steps to run
 54  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 55  
      *            steps methods
 56  
      * @param stage the Stage
 57  
      */
 58  
     public void runBeforeOrAfterStories(Configuration configuration, List<CandidateSteps> candidateSteps, Stage stage) {
 59  4
         String storyPath = capitalizeFirstLetter(stage.name().toLowerCase()) + "Stories";
 60  4
         reporter.set(configuration.storyReporter(storyPath));
 61  4
         reporter.get().beforeStory(new Story(storyPath), false);
 62  4
         runStepsWhileKeepingState(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(candidateSteps, stage));
 63  4
     }
 64  
 
 65  
     /**
 66  
      * Runs a Story with the given configuration and steps.
 67  
      * 
 68  
      * @param configuration the Configuration used to run story
 69  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 70  
      *            steps methods
 71  
      * @param story the Story to run
 72  
      * @throws Throwable if failures occurred and FailureStrategy dictates it to
 73  
      *             be re-thrown.
 74  
      */
 75  
     public void run(Configuration configuration, List<CandidateSteps> candidateSteps, Story story) throws Throwable {
 76  12
         run(configuration, candidateSteps, story, MetaFilter.EMPTY);
 77  11
     }
 78  
 
 79  
     /**
 80  
      * Runs a Story with the given configuration and steps, applying the given
 81  
      * meta filter.
 82  
      * 
 83  
      * @param configuration the Configuration used to run story
 84  
      * @param candidateSteps the List of CandidateSteps containing the candidate
 85  
      *            steps methods
 86  
      * @param story the Story to run
 87  
      * @param filter the Filter to apply to the story Meta
 88  
      * @throws Throwable if failures occurred and FailureStrategy dictates it to
 89  
      *             be re-thrown.
 90  
      */
 91  
     public void run(Configuration configuration, List<CandidateSteps> candidateSteps, Story story, MetaFilter filter)
 92  
             throws Throwable {
 93  14
         RunContext context = new RunContext(configuration, filter, candidateSteps, story.getPath());
 94  14
         Map<String, String> storyParameters = new HashMap<String, String>();
 95  14
         run(context, story, storyParameters);
 96  13
     }
 97  
 
 98  
     /**
 99  
      * Returns the parsed story from the given path
 100  
      * 
 101  
      * @param configuration the Configuration used to run story
 102  
      * @param storyPath the story path
 103  
      * @return The parsed Story
 104  
      */
 105  
     public Story storyOfPath(Configuration configuration, String storyPath) {
 106  2
         String storyAsText = configuration.storyLoader().loadStoryAsText(storyPath);
 107  2
         return configuration.storyParser().parseStory(storyAsText, storyPath);
 108  
     }
 109  
 
 110  
     private void run(RunContext context, Story story, Map<String, String> storyParameters) throws Throwable {
 111  16
         if (!context.givenStory) {
 112  14
             reporter.set(reporterFor(context, story));
 113  
         }
 114  16
         pendingStepStrategy.set(context.configuration().pendingStepStrategy());
 115  16
         failureStrategy.set(context.configuration().failureStrategy());
 116  
 
 117  
         try {
 118  16
             resetFailureState(context.givenStory());
 119  
 
 120  16
             if (context.dryRun()) {
 121  2
                 reporter.get().dryRun();
 122  
             }
 123  
 
 124  
             // run before story steps, if any
 125  16
             reporter.get().beforeStory(story, context.givenStory());
 126  
 
 127  16
             boolean storyAllowed = true;
 128  
 
 129  16
             if (context.metaNotAllowed(story.getMeta())) {
 130  1
                 reporter.get().storyNotAllowed(story, context.metaFilterAsString());
 131  1
                 storyAllowed = false;
 132  
             }
 133  
 
 134  16
             if (storyAllowed) {
 135  
 
 136  15
                 reporter.get().narrative(story.getNarrative());
 137  
 
 138  15
                 runBeforeOrAfterStorySteps(context, story, Stage.BEFORE);
 139  
 
 140  
                 // determine if before and after scenario steps should be run
 141  15
                 boolean runBeforeAndAfterScenarioSteps = shouldRunBeforeOrAfterScenarioSteps(context);
 142  
 
 143  15
                 for (Scenario scenario : story.getScenarios()) {
 144  
                     // scenario also inherits meta from story
 145  20
                     boolean scenarioAllowed = true;
 146  20
                     if (failureOccurred() && context.configuration().storyControls().skipScenariosAfterFailure()) {
 147  1
                         continue;
 148  
                     }
 149  19
                     reporter.get().beforeScenario(scenario.getTitle());
 150  19
                     reporter.get().scenarioMeta(scenario.getMeta());
 151  
 
 152  19
                     if (context.metaNotAllowed(scenario.getMeta().inheritFrom(story.getMeta()))) {
 153  1
                         reporter.get().scenarioNotAllowed(scenario, context.metaFilterAsString());
 154  1
                         scenarioAllowed = false;
 155  
                     }
 156  
 
 157  19
                     if (scenarioAllowed) {
 158  
 
 159  
                         // run before scenario steps, if allowed
 160  18
                         if (runBeforeAndAfterScenarioSteps) {
 161  17
                             runBeforeOrAfterScenarioSteps(context, scenario, Stage.BEFORE);
 162  
                         }
 163  
 
 164  
                         // run given stories, if any
 165  18
                         runGivenStories(scenario, context);
 166  18
                         if (isParameterisedByExamples(scenario)) {
 167  
                             // run parametrised scenarios by examples
 168  1
                             runParametrisedScenariosByExamples(context, scenario);
 169  
                         } else { // run as plain old scenario
 170  17
                             runScenarioSteps(context, scenario, storyParameters);
 171  
                         }
 172  
 
 173  
                         // run after scenario steps, if allowed
 174  18
                         if (runBeforeAndAfterScenarioSteps) {
 175  17
                             runBeforeOrAfterScenarioSteps(context, scenario, Stage.AFTER);
 176  
                         }
 177  
 
 178  
                     }
 179  
 
 180  19
                     reporter.get().afterScenario();
 181  19
                 }
 182  
 
 183  
                 // run after story steps, if any
 184  15
                 runBeforeOrAfterStorySteps(context, story, Stage.AFTER);
 185  
 
 186  
             }
 187  
 
 188  16
             reporter.get().afterStory(context.givenStory());
 189  
 
 190  
             // handle any failure according to strategy
 191  16
             if (!context.givenStory()) {
 192  14
                 currentStrategy.get().handleFailure(storyFailure.get());
 193  
             }
 194  
         } finally {
 195  16
             if (!context.givenStory() && reporter.get() instanceof ConcurrentStoryReporter) {
 196  14
                 ((ConcurrentStoryReporter) reporter.get()).invokeDelayed();
 197  
             }
 198  
         }
 199  15
     }
 200  
 
 201  
     private boolean shouldRunBeforeOrAfterScenarioSteps(RunContext context) {
 202  15
         Configuration configuration = context.configuration();
 203  15
         if (!configuration.storyControls().skipBeforeAndAfterScenarioStepsIfGivenStory()) {
 204  13
             return true;
 205  
         }
 206  
 
 207  2
         return !context.givenStory();
 208  
     }
 209  
 
 210  
     private boolean failureOccurred() {
 211  20
         return storyFailure.get() != null;
 212  
     }
 213  
 
 214  
     private StoryReporter reporterFor(RunContext context, Story story) {
 215  14
         Configuration configuration = context.configuration();
 216  14
         if (context.givenStory()) {
 217  0
             return configuration.storyReporter(reporterStoryPath.get());
 218  
         } else {
 219  
             // store parent story path for reporting
 220  14
             reporterStoryPath.set(story.getPath());
 221  14
             return configuration.storyReporter(reporterStoryPath.get());
 222  
         }
 223  
     }
 224  
 
 225  
     private void resetFailureState(boolean givenStory) {
 226  16
         if (givenStory) {
 227  
             // do not reset failure state for given stories
 228  2
             return;
 229  
         }
 230  14
         currentStrategy.set(new SilentlyAbsorbingFailure());
 231  14
         storyFailure.set(null);
 232  14
     }
 233  
 
 234  
     private void runGivenStories(Scenario scenario, RunContext context) throws Throwable {
 235  18
         GivenStories givenStories = scenario.getGivenStories();
 236  18
         if (givenStories.getPaths().size() > 0) {
 237  2
             reporter.get().givenStories(givenStories);
 238  2
             for (GivenStory givenStory : givenStories.getStories()) {
 239  2
                 RunContext childContext = context.childContextFor(givenStory);
 240  
                 // run given story, using any parameters if provided
 241  2
                 Story story = storyOfPath(context.configuration(), childContext.path());
 242  2
                 run(childContext, story, givenStory.getParameters());
 243  2
             }
 244  
         }
 245  18
     }
 246  
 
 247  
     private boolean isParameterisedByExamples(Scenario scenario) {
 248  18
         return scenario.getExamplesTable().getRowCount() > 0 && !scenario.getGivenStories().requireParameters();
 249  
     }
 250  
 
 251  
     private void runParametrisedScenariosByExamples(RunContext context, Scenario scenario) {
 252  1
         ExamplesTable table = scenario.getExamplesTable();
 253  1
         reporter.get().beforeExamples(scenario.getSteps(), table);
 254  1
         for (Map<String, String> scenarioParameters : table.getRows()) {
 255  1
             reporter.get().example(scenarioParameters);
 256  1
             runScenarioSteps(context, scenario, scenarioParameters);
 257  
         }
 258  1
         reporter.get().afterExamples();
 259  1
     }
 260  
 
 261  
     private void runBeforeOrAfterStorySteps(RunContext context, Story story, Stage stage) {
 262  30
         runStepsWhileKeepingState(context.collectBeforeOrAfterStorySteps(story, stage));
 263  30
     }
 264  
 
 265  
     private void runBeforeOrAfterScenarioSteps(RunContext context, Scenario scenario, Stage stage) {
 266  34
         runStepsWhileKeepingState(context.collectBeforeOrAfterScenarioSteps(stage));
 267  34
     }
 268  
 
 269  
     private void runScenarioSteps(RunContext context, Scenario scenario, Map<String, String> scenarioParameters) {
 270  18
         List<Step> steps = context.collectScenarioSteps(scenario, scenarioParameters);
 271  18
         runStepsWhileKeepingState(steps);
 272  18
         generatePendingStepMethods(context, steps);
 273  18
     }
 274  
 
 275  
     private void generatePendingStepMethods(RunContext context, List<Step> steps) {
 276  18
         List<PendingStep> pendingSteps = new ArrayList<PendingStep>();
 277  18
         for (Step step : steps) {
 278  27
             if (step instanceof PendingStep) {
 279  0
                 pendingSteps.add((PendingStep) step);
 280  
             }
 281  
         }
 282  18
         if (!pendingSteps.isEmpty()) {
 283  0
             PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration().keywords());
 284  0
             List<String> methods = new ArrayList<String>();
 285  0
             for (PendingStep pendingStep : pendingSteps) {
 286  0
                 if (!pendingStep.annotated()) {
 287  0
                     methods.add(generator.generateMethod(pendingStep));
 288  
                 }
 289  
             }
 290  0
             reporter.get().pendingMethods(methods);
 291  
         }
 292  18
     }
 293  
 
 294  
     private void runStepsWhileKeepingState(List<Step> steps) {
 295  86
         if (steps == null || steps.size() == 0) {
 296  60
             return;
 297  
         }
 298  26
         State state = new FineSoFar();
 299  26
         for (Step step : steps) {
 300  35
             state = state.run(step);
 301  
         }
 302  26
     }
 303  
 
 304  
     private interface State {
 305  
         State run(Step step);
 306  
     }
 307  
 
 308  52
     private final class FineSoFar implements State {
 309  
 
 310  
         public State run(Step step) {
 311  30
             UUIDExceptionWrapper storyFailureIfItHappened = storyFailure.get();
 312  30
             StepResult result = step.perform(storyFailureIfItHappened);
 313  30
             result.describeTo(reporter.get());
 314  30
             UUIDExceptionWrapper stepFailure = result.getFailure();
 315  
             // JBEHAVE-472: storyFailure is not sufficient for state management,
 316  
             // we need scenarioFailure too.
 317  
             // if (storyFailureIfItHappened == null && stepFailure == null) {
 318  30
             if (stepFailure == null) {
 319  19
                 return this;
 320  
             }
 321  
 
 322  11
             storyFailure.set(mostImportantOf(storyFailureIfItHappened, stepFailure));
 323  11
             currentStrategy.set(strategyFor(storyFailure.get()));
 324  11
             return new SomethingHappened();
 325  
         }
 326  
 
 327  
         private UUIDExceptionWrapper mostImportantOf(UUIDExceptionWrapper failure1, UUIDExceptionWrapper failure2) {
 328  11
             return failure1 == null ? failure2
 329  
                     : failure1.getCause() instanceof PendingStepFound ? (failure2 == null ? failure1 : failure2)
 330  
                             : failure1;
 331  
         }
 332  
 
 333  
         private FailureStrategy strategyFor(Throwable failure) {
 334  11
             if (failure instanceof PendingStepFound) {
 335  4
                 return pendingStepStrategy.get();
 336  
             } else {
 337  7
                 return failureStrategy.get();
 338  
             }
 339  
         }
 340  
     }
 341  
 
 342  22
     private class SomethingHappened implements State {
 343  
         public State run(Step step) {
 344  5
             StepResult result = step.doNotPerform();
 345  5
             result.describeTo(reporter.get());
 346  5
             return this;
 347  
         }
 348  
     }
 349  
 
 350  
     @Override
 351  
     public String toString() {
 352  1
         return this.getClass().getSimpleName();
 353  
     }
 354  
 
 355  
     /**
 356  
      * The context for running a story.
 357  
      */
 358  67
     private class RunContext {
 359  
         private final List<CandidateSteps> candidateSteps;
 360  
         private final MetaFilter filter;
 361  
         private final Configuration configuration;
 362  
         private final String path;
 363  
         private final boolean givenStory;
 364  
 
 365  
         private final StepCollector stepCollector;
 366  
 
 367  
         public RunContext(Configuration configuration, MetaFilter filter, List<CandidateSteps> steps, String path) {
 368  14
             this(configuration, filter, steps, path, false);
 369  14
         }
 370  
 
 371  
         private RunContext(Configuration configuration, MetaFilter filter, List<CandidateSteps> steps, String path,
 372  16
                 boolean givenStory) {
 373  16
             this.configuration = configuration;
 374  16
             this.filter = filter;
 375  16
             this.candidateSteps = steps;
 376  16
             this.path = path;
 377  16
             this.givenStory = givenStory;
 378  
 
 379  16
             this.stepCollector = configuration.stepCollector();
 380  16
         }
 381  
 
 382  
         public boolean dryRun() {
 383  16
             return configuration.storyControls().dryRun();
 384  
         }
 385  
 
 386  
         public Configuration configuration() {
 387  69
             return configuration;
 388  
         }
 389  
 
 390  
         public boolean givenStory() {
 391  96
             return givenStory;
 392  
         }
 393  
 
 394  
         public String path() {
 395  2
             return path;
 396  
         }
 397  
 
 398  
         public boolean metaNotAllowed(Meta meta) {
 399  35
             return !filter.allow(meta);
 400  
         }
 401  
 
 402  
         public String metaFilterAsString() {
 403  2
             return filter.asString();
 404  
         }
 405  
 
 406  
         public List<Step> collectBeforeOrAfterStorySteps(Story story, Stage stage) {
 407  30
             return stepCollector.collectBeforeOrAfterStorySteps(candidateSteps, story, stage, givenStory);
 408  
         }
 409  
 
 410  
         public List<Step> collectBeforeOrAfterScenarioSteps(Stage stage) {
 411  34
             return stepCollector.collectBeforeOrAfterScenarioSteps(candidateSteps, stage, storyFailure.get() != null);
 412  
         }
 413  
 
 414  
         public List<Step> collectScenarioSteps(Scenario scenario, Map<String, String> parameters) {
 415  18
             return stepCollector.collectScenarioSteps(candidateSteps, scenario, parameters);
 416  
         }
 417  
 
 418  
         public RunContext childContextFor(GivenStory givenStory) {
 419  2
             String actualPath = configuration.pathCalculator().calculate(path, givenStory.getPath());
 420  2
             return new RunContext(configuration, filter, candidateSteps, actualPath, true);
 421  
         }
 422  
     }
 423  
 }