##### Coverage Report - org.jbehave.core.embedder.StoryRunner

Classes in this File | Line Coverage | Branch Coverage | Complexity
StoryRunner |
91% | 193/210
81% | 85/104
| 2.4339622641509435;2.434
StoryRunner$1 |
N/A | N/A
N/A | N/A
| 2.4339622641509435;2.434
StoryRunner$FineSoFar |
100% | 16/16
75% | 9/12
| 2.4339622641509435;2.434
StoryRunner$RunContext |
100% | 38/38
83% | 5/6
| 2.4339622641509435;2.434
StoryRunner$SomethingHappened |
100% | 6/6
N/A | N/A
| 2.4339622641509435;2.434
StoryRunner$State |
N/A | N/A
N/A | N/A
| 2.4339622641509435;2.434

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 ( !parameterMeta.isEmpty() && !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 |  |
     }