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 | |
|
45 | |
|
46 | |
|
47 | |
|
48 | |
|
49 | |
|
50 | |
|
51 | |
|
52 | |
|
53 | |
|
54 | |
|
55 | |
|
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 | |
|
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 | |
|
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 | |
|
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 | |
|
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 { |
150 | 46 | performableScenario.useNormalScenario(normalScenario); |
151 | |
} |
152 | |
|
153 | |
|
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 | |
|
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 | |
|
239 | |
|
240 | |
|
241 | |
|
242 | |
|
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 | |
|
251 | |
|
252 | |
|
253 | |
|
254 | |
|
255 | |
|
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 | |
|
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 | |
} |