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

Classes in this File | Line Coverage | Branch Coverage | Complexity
StoryManager |
82% | 77/93
64% | 22/34
| 1.8125;1.812
StoryManager$1 |
N/A | N/A
N/A | N/A
| 1.8125;1.812
StoryManager$EnqueuedStory |
100% | 21/21
100% | 2/2
| 1.8125;1.812
StoryManager$RunningStory |
46% | 6/13
0% | 0/4
| 1.8125;1.812
StoryManager$StoryExecutionFailed |
100% | 2/2
N/A | N/A
| 1.8125;1.812
StoryManager$StoryOutcome |
0% | 0/8
N/A | N/A
| 1.8125;1.812
StoryManager$ThrowableStory |
83% | 5/6
N/A | N/A
| 1.8125;1.812

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 |  |
     import java.util.concurrent.Callable;
8 |  |
     import java.util.concurrent.ExecutionException;
9 |  |
     import java.util.concurrent.ExecutorService;
10 |  |
     import java.util.concurrent.Future;
11 |  |
12 |  |
     import org.jbehave.core.configuration.Configuration;
13 |  |
     import org.jbehave.core.embedder.StoryRunner.State;
14 |  |
     import org.jbehave.core.failures.BatchFailures;
15 |  |
     import org.jbehave.core.model.Story;
16 |  |
     import org.jbehave.core.model.StoryDuration;
17 |  |
     import org.jbehave.core.steps.CandidateSteps;
18 |  |
     import org.jbehave.core.steps.InjectableStepsFactory;
19 |  |
     import org.jbehave.core.steps.StepCollector.Stage;
20 |  |
21 |  |
     /**
22 |  |
      * Manages the execution and outcomes of running stories. While each story is
23 |  |
      * run by the {@link StoryRunner}, the manager is responsible for the concurrent
24 |  |
      * submission and monitoring of their execution via the {@link ExecutorService}.
25 |  |
      */
26 |  |
     public class StoryManager {
27 |  |
28 |  |
         private final Configuration configuration;
29 |  |
         private final EmbedderControls embedderControls;
30 |  |
         private final EmbedderMonitor embedderMonitor;
31 |  |
         private final ExecutorService executorService;
32 |  |
         private final InjectableStepsFactory stepsFactory;
33 |  |
         private final StoryRunner storyRunner;
34 | 12 |
         private final Map<String, RunningStory> runningStories = new HashMap<String, RunningStory>();
35 | 12 |
         private final Map<MetaFilter, List<Story>> excludedStories = new HashMap<MetaFilter, List<Story>>();
36 |  |
37 |  |
         public StoryManager(Configuration configuration, EmbedderControls embedderControls,
38 |  |
                 EmbedderMonitor embedderMonitor, ExecutorService executorService, InjectableStepsFactory stepsFactory,
39 | 12 |
                 StoryRunner storyRunner) {
40 | 12 |
             this.configuration = configuration;
41 | 12 |
             this.embedderControls = embedderControls;
42 | 12 |
             this.embedderMonitor = embedderMonitor;
43 | 12 |
             this.executorService = executorService;
44 | 12 |
             this.stepsFactory = stepsFactory;
45 | 12 |
             this.storyRunner = storyRunner;
46 | 12 |
         }
47 |  |
48 |  |
         public Story storyOfPath(String storyPath) {
49 | 22 |
             return storyRunner.storyOfPath(configuration, storyPath);
50 |  |
         }
51 |  |
52 |  |
         public Story storyOfText(String storyAsText, String storyId) {
53 | 0 |
             return storyRunner.storyOfText(configuration, storyAsText, storyId);
54 |  |
         }
55 |  |
56 |  |
         public void clear() {
57 | 0 |
             runningStories.clear();
58 | 0 |
         }
59 |  |
60 |  |
         public List<StoryOutcome> outcomes() {
61 | 0 |
             List<StoryOutcome> outcomes = new ArrayList<StoryOutcome>();
62 | 0 |
             for (RunningStory story : runningStories.values()) {
63 | 0 |
                 outcomes.add(new StoryOutcome(story));
64 |  |
             }
65 | 0 |
             return outcomes;
66 |  |
         }
67 |  |
68 |  |
         public void runStories(List<String> storyPaths, MetaFilter filter, BatchFailures failures) {
69 |  |
             // configure cross reference with meta filter
70 | 11 |
             if ( configuration.storyReporterBuilder().hasCrossReference() ){
71 | 0 |
                 configuration.storyReporterBuilder().crossReference().withMetaFilter(filter.asString());
72 |  |
             }
73 |  |
74 |  |
             // run before stories
75 | 11 |
             State beforeStories = runBeforeOrAfterStories(failures, Stage.BEFORE);
76 |  |
77 |  |
             // run stories as paths
78 | 11 |
             runningStoriesAsPaths(storyPaths, filter, beforeStories);
79 | 11 |
             waitUntilAllDoneOrFailed(failures);
80 | 11 |
             List<Story> notAllowed = notAllowedBy(filter);
81 | 11 |
             if (!notAllowed.isEmpty()) {
82 | 0 |
                 embedderMonitor.storiesNotAllowed(notAllowed, filter, embedderControls.verboseFiltering());
83 |  |
             }
84 |  |
85 |  |
             // run after stories
86 | 11 |
            runBeforeOrAfterStories(failures, Stage.AFTER);
87 | 11 |
         }
88 |  |
89 |  |
         public State runBeforeOrAfterStories(BatchFailures failures, Stage stage) {
90 | 22 |
             List<CandidateSteps> candidateSteps = stepsFactory.createCandidateSteps();
91 | 22 |
             State state = storyRunner.runBeforeOrAfterStories(configuration, candidateSteps, stage);
92 | 22 |
             if (storyRunner.failed(state)) {
93 | 0 |
                 failures.put(state.toString(), storyRunner.failure(state));
94 |  |
             }
95 | 22 |
             return state;
96 |  |
         }
97 |  |
98 |  |
         public Map<String, RunningStory> runningStoriesAsPaths(List<String> storyPaths, MetaFilter filter,
99 |  |
                 State beforeStories) {
100 | 11 |
             for (String storyPath : storyPaths) {
101 | 20 |
                 filterRunning(filter, beforeStories, storyPath, storyOfPath(storyPath));
102 |  |
             }
103 | 11 |
             return runningStories;
104 |  |
         }
105 |  |
106 |  |
         public Map<String, RunningStory> runningStories(List<Story> stories, MetaFilter filter, State beforeStories) {
107 | 0 |
             for (Story story : stories) {
108 | 0 |
                 filterRunning(filter, beforeStories, story.getPath(), story);
109 |  |
             }
110 | 0 |
             return runningStories;
111 |  |
         }
112 |  |
113 |  |
         private void filterRunning(MetaFilter filter, State beforeStories, String storyPath, Story story) {
114 | 20 |
             FilteredStory filteredStory = new FilteredStory(filter, story, configuration.storyControls());
115 | 20 |
             if (filteredStory.allowed()) {
116 | 20 |
                 runningStories.put(storyPath, runningStory(storyPath, story, filter, beforeStories));
117 |  |
             } else {
118 | 0 |
                 notAllowedBy(filter).add(story);
119 |  |
             }
120 | 20 |
         }
121 |  |
122 |  |
         public List<Story> notAllowedBy(MetaFilter filter) {
123 | 11 |
             List<Story> stories = excludedStories.get(filter);
124 | 11 |
             if (stories == null) {
125 | 11 |
                 stories = new ArrayList<Story>();
126 | 11 |
                 excludedStories.put(filter, stories);
127 |  |
             }
128 | 11 |
             return stories;
129 |  |
         }
130 |  |
131 |  |
         public RunningStory runningStory(String storyPath, Story story, MetaFilter filter, State beforeStories) {
132 | 20 |
             return submit(new EnqueuedStory(storyRunner, configuration, stepsFactory, embedderControls, embedderMonitor,
133 |  |
                     storyPath, story, filter, beforeStories));
134 |  |
         }
135 |  |
136 |  |
         public void waitUntilAllDoneOrFailed(BatchFailures failures) {
137 | 11 |
             long start = System.currentTimeMillis();
138 | 11 |
             boolean allDone = false;
139 | 93 |
             while (!allDone) {
140 | 82 |
                 allDone = true;
141 | 82 |
                 for (RunningStory runningStory : runningStories.values()) {
142 | 88 |
                     Future<ThrowableStory> future = runningStory.getFuture();
143 | 88 |
                     if (!future.isDone()) {
144 | 71 |
                         allDone = false;
145 | 71 |
                         long durationInSecs = storyDurationInSecs(start);
146 | 71 |
                         long timeoutInSecs = embedderControls.storyTimeoutInSecs();
147 | 71 |
                         if (durationInSecs > timeoutInSecs) {
148 | 3 |
                             Story story = runningStory.getStory();
149 | 3 |
                             StoryDuration storyDuration = new StoryDuration(durationInSecs, timeoutInSecs);
150 | 3 |
                             embedderMonitor.storyTimeout(story, storyDuration);
151 | 3 |
                             storyRunner.cancelStory(story, storyDuration);
152 | 3 |
                             future.cancel(true);
153 | 3 |
                         }
154 |  |
                         break;
155 |  |
                     } else {
156 | 17 |
                         Story story = runningStory.getStory();
157 |  |
                         try {
158 | 17 |
                             ThrowableStory throwableStory = future.get();
159 | 14 |
                             Throwable throwable = throwableStory.getThrowable();
160 | 14 |
                             if (throwable != null) {
161 | 2 |
                                 failures.put(story.getPath(), throwable);
162 | 2 |
                                 if (!embedderControls.ignoreFailureInStories()) {
163 | 2 |
                                     break;
164 |  |
                                 }
165 |  |
                             }
166 | 3 |
                         } catch (Throwable e) {
167 | 3 |
                             failures.put(story.getPath(), e);
168 | 3 |
                             if (!embedderControls.ignoreFailureInStories()) {
169 | 3 |
                                 break;
170 |  |
                             }
171 | 12 |
                         }
172 |  |
                     }
173 | 12 |
                 }
174 | 82 |
                 tickTock();
175 |  |
             }
176 |  |
             // cancel any outstanding execution which is not done before returning
177 | 11 |
             for (RunningStory runningStory : runningStories.values()) {
178 | 20 |
                 Future<ThrowableStory> future = runningStory.getFuture();
179 | 20 |
                 if (!future.isDone()) {
180 | 0 |
                     future.cancel(true);
181 |  |
                 }
182 | 20 |
             }
183 |  |
184 | 11 |
         }
185 |  |
186 |  |
         private void tickTock() {
187 |  |
             try {
188 | 82 |
                 Thread.sleep(100);
189 | 0 |
             } catch (InterruptedException e) {
190 | 82 |
             }
191 | 82 |
         }
192 |  |
193 |  |
         private long storyDurationInSecs(long start) {
194 | 71 |
             return (System.currentTimeMillis() - start) / 1000;
195 |  |
         }
196 |  |
197 |  |
         private synchronized RunningStory submit(EnqueuedStory enqueuedStory) {
198 | 20 |
             return new RunningStory(enqueuedStory.getStory(), executorService.submit(enqueuedStory));
199 |  |
         }
200 |  |
201 | 40 |
         private static class EnqueuedStory implements Callable<ThrowableStory> {
202 |  |
             private final StoryRunner storyRunner;
203 |  |
             private final Configuration configuration;
204 |  |
             private final InjectableStepsFactory stepsFactory;
205 |  |
             private final EmbedderControls embedderControls;
206 |  |
             private final EmbedderMonitor embedderMonitor;
207 |  |
             private final String storyPath;
208 |  |
             private final Story story;
209 |  |
             private final MetaFilter filter;
210 |  |
             private final State beforeStories;
211 |  |
212 |  |
             private EnqueuedStory(StoryRunner storyRunner, Configuration configuration,
213 |  |
                     InjectableStepsFactory stepsFactory, EmbedderControls embedderControls,
214 | 20 |
                     EmbedderMonitor embedderMonitor, String storyPath, Story story, MetaFilter filter, State beforeStories) {
215 | 20 |
                 this.storyRunner = storyRunner;
216 | 20 |
                 this.configuration = configuration;
217 | 20 |
                 this.stepsFactory = stepsFactory;
218 | 20 |
                 this.embedderControls = embedderControls;
219 | 20 |
                 this.embedderMonitor = embedderMonitor;
220 | 20 |
                 this.storyPath = storyPath;
221 | 20 |
                 this.story = story;
222 | 20 |
                 this.filter = filter;
223 | 20 |
                 this.beforeStories = beforeStories;
224 | 20 |
             }
225 |  |
226 |  |
             public ThrowableStory call() throws Exception {
227 |  |
                 try {
228 | 20 |
                     embedderMonitor.runningStory(storyPath);
229 | 20 |
                     storyRunner.run(configuration, stepsFactory, story, filter, beforeStories);
230 | 10 |
                 } catch (Throwable e) {
231 | 10 |
                     if (embedderControls.ignoreFailureInStories()) {
232 | 4 |
                         embedderMonitor.storyFailed(storyPath, e);
233 |  |
                     } else {
234 | 6 |
                         return new ThrowableStory(story, new StoryExecutionFailed(storyPath, e));
235 |  |
                     }
236 | 9 |
                 }
237 | 13 |
                 return new ThrowableStory(story, null);
238 |  |
             }
239 |  |
240 |  |
             public Story getStory() {
241 | 20 |
                 return story;
242 |  |
             }
243 |  |
244 |  |
         }
245 |  |
246 |  |
         @SuppressWarnings("serial")
247 |  |
         public static class StoryExecutionFailed extends RuntimeException {
248 |  |
249 |  |
             public StoryExecutionFailed(String storyPath, Throwable failure) {
250 | 6 |
                 super(storyPath, failure);
251 | 6 |
             }
252 |  |
253 |  |
         }
254 |  |
255 |  |
         public static class ThrowableStory {
256 |  |
             private Story story;
257 |  |
             private Throwable throwable;
258 |  |
259 | 19 |
             public ThrowableStory(Story story, Throwable throwable) {
260 | 19 |
                 this.story = story;
261 | 19 |
                 this.throwable = throwable;
262 | 19 |
             }
263 |  |
264 |  |
             public Story getStory() {
265 | 0 |
                 return story;
266 |  |
             }
267 |  |
268 |  |
             public Throwable getThrowable() {
269 | 14 |
                 return throwable;
270 |  |
             }
271 |  |
         }
272 |  |
273 |  |
         public static class RunningStory {
274 |  |
             private Story story;
275 |  |
             private Future<ThrowableStory> future;
276 |  |
277 | 20 |
             public RunningStory(Story story, Future<ThrowableStory> future) {
278 | 20 |
                 this.story = story;
279 | 20 |
                 this.future = future;
280 | 20 |
             }
281 |  |
282 |  |
             public Future<ThrowableStory> getFuture() {
283 | 108 |
                 return future;
284 |  |
             }
285 |  |
286 |  |
             public Story getStory() {
287 | 20 |
                 return story;
288 |  |
             }
289 |  |
290 |  |
             public boolean isDone() {
291 | 0 |
                 return future.isDone();
292 |  |
             }
293 |  |
294 |  |
             public boolean isFailed() {
295 | 0 |
                 if (isDone()) {
296 |  |
                     try {
297 | 0 |
                         return future.get().getThrowable() != null;
298 | 0 |
                     } catch (InterruptedException e) {
299 | 0 |
                     } catch (ExecutionException e) {
300 | 0 |
                     }
301 |  |
                 }
302 | 0 |
                 return false;
303 |  |
             }
304 |  |
         }
305 |  |
306 |  |
         public static class StoryOutcome {
307 |  |
             private String path;
308 |  |
             private Boolean done;
309 |  |
             private Boolean failed;
310 |  |
311 | 0 |
             public StoryOutcome(RunningStory story) {
312 | 0 |
                 this.path = story.getStory().getPath();
313 | 0 |
                 this.done = story.isDone();
314 | 0 |
                 this.failed = story.isFailed();
315 | 0 |
             }
316 |  |
317 |  |
             public String getPath() {
318 | 0 |
                 return path;
319 |  |
             }
320 |  |
321 |  |
             public Boolean isDone() {
322 | 0 |
                 return done;
323 |  |
             }
324 |  |
325 |  |
             public Boolean isFailed() {
326 | 0 |
                 return failed;
327 |  |
             }
328 |  |
329 |  |
         }
330 |  |
331 |  |
     }