Coverage Report - org.jbehave.core.embedder.StoryManager
 
Classes in this File Line Coverage Branch Coverage Complexity
StoryManager
81%
81/99
66%
24/36
1.848
StoryManager$1
N/A
N/A
1.848
StoryManager$EnqueuedStory
100%
21/21
100%
2/2
1.848
StoryManager$RunningStory
46%
6/13
0%
0/4
1.848
StoryManager$StoryExecutionFailed
100%
2/2
N/A
1.848
StoryManager$StoryOutcome
0%
0/8
N/A
1.848
StoryManager$StoryTimeout
100%
2/2
N/A
1.848
StoryManager$ThrowableStory
83%
5/6
N/A
1.848
 
 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  14
     private final Map<String, RunningStory> runningStories = new HashMap<String, RunningStory>();
 35  14
     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  14
             StoryRunner storyRunner) {
 40  14
         this.configuration = configuration;
 41  14
         this.embedderControls = embedderControls;
 42  14
         this.embedderMonitor = embedderMonitor;
 43  14
         this.executorService = executorService;
 44  14
         this.stepsFactory = stepsFactory;
 45  14
         this.storyRunner = storyRunner;
 46  14
     }
 47  
 
 48  
     public Story storyOfPath(String storyPath) {
 49  24
         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  0
         }
 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  13
         if ( configuration.storyReporterBuilder().hasCrossReference() ){
 71  0
             configuration.storyReporterBuilder().crossReference().withMetaFilter(filter.asString());
 72  
         }
 73  
         
 74  
         // run before stories
 75  13
         State beforeStories = runBeforeOrAfterStories(failures, Stage.BEFORE);
 76  
 
 77  
         // run stories as paths
 78  13
         runningStoriesAsPaths(storyPaths, filter, beforeStories);
 79  13
         waitUntilAllDoneOrFailed(failures);
 80  12
         List<Story> notAllowed = notAllowedBy(filter);
 81  12
         if (!notAllowed.isEmpty()) {
 82  0
             embedderMonitor.storiesNotAllowed(notAllowed, filter, embedderControls.verboseFiltering());
 83  
         }
 84  
 
 85  
         // run after stories
 86  12
        runBeforeOrAfterStories(failures, Stage.AFTER);
 87  12
     }
 88  
 
 89  
     public State runBeforeOrAfterStories(BatchFailures failures, Stage stage) {
 90  25
         List<CandidateSteps> candidateSteps = stepsFactory.createCandidateSteps();
 91  25
         State state = storyRunner.runBeforeOrAfterStories(configuration, candidateSteps, stage);
 92  25
         if (storyRunner.failed(state)) {
 93  0
             failures.put(state.toString(), storyRunner.failure(state));
 94  
         }
 95  25
         return state;
 96  
     }
 97  
 
 98  
     public Map<String, RunningStory> runningStoriesAsPaths(List<String> storyPaths, MetaFilter filter,
 99  
             State beforeStories) {
 100  13
         for (String storyPath : storyPaths) {
 101  22
             filterRunning(filter, beforeStories, storyPath, storyOfPath(storyPath));
 102  22
         }
 103  13
         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  0
         }
 110  0
         return runningStories;
 111  
     }
 112  
 
 113  
     private void filterRunning(MetaFilter filter, State beforeStories, String storyPath, Story story) {
 114  22
         FilteredStory filteredStory = new FilteredStory(filter, story, configuration.storyControls());
 115  22
         if (filteredStory.allowed()) {
 116  22
             runningStories.put(storyPath, runningStory(storyPath, story, filter, beforeStories));
 117  
         } else {
 118  0
             notAllowedBy(filter).add(story);
 119  
         }
 120  22
     }
 121  
 
 122  
     public List<Story> notAllowedBy(MetaFilter filter) {
 123  12
         List<Story> stories = excludedStories.get(filter);
 124  12
         if (stories == null) {
 125  12
             stories = new ArrayList<Story>();
 126  12
             excludedStories.put(filter, stories);
 127  
         }
 128  12
         return stories;
 129  
     }
 130  
 
 131  
     public RunningStory runningStory(String storyPath, Story story, MetaFilter filter, State beforeStories) {
 132  22
         return submit(new EnqueuedStory(storyRunner, configuration, stepsFactory, embedderControls, embedderMonitor,
 133  
                 storyPath, story, filter, beforeStories));
 134  
     }
 135  
 
 136  
     public void waitUntilAllDoneOrFailed(BatchFailures failures) {
 137  13
         long start = System.currentTimeMillis();
 138  13
         boolean allDone = false;
 139  137
         while (!allDone) {
 140  125
             allDone = true;
 141  125
             for (RunningStory runningStory : runningStories.values()) {
 142  131
                 Future<ThrowableStory> future = runningStory.getFuture();
 143  131
                 if (!future.isDone()) {
 144  113
                     allDone = false;
 145  113
                     long durationInSecs = storyDurationInSecs(start);
 146  113
                     long timeoutInSecs = embedderControls.storyTimeoutInSecs();
 147  113
                     if (durationInSecs > timeoutInSecs) {
 148  5
                         Story story = runningStory.getStory();
 149  5
                         StoryDuration storyDuration = new StoryDuration(durationInSecs, timeoutInSecs);
 150  5
                         embedderMonitor.storyTimeout(story, storyDuration);
 151  5
                         storyRunner.cancelStory(story, storyDuration);
 152  5
                         future.cancel(true);
 153  5
                                                 if (embedderControls.failOnStoryTimeout()) {
 154  1
                                                         StoryTimeout timeout = new StoryTimeout(
 155  
                                                                         durationInSecs + "s > " + timeoutInSecs + "s");
 156  1
                                                         throw new StoryExecutionFailed(story.getPath(),
 157  
                                                                         timeout);
 158  
                                                 }
 159  4
                     }
 160  
                     break;
 161  
                 } else {
 162  18
                     Story story = runningStory.getStory();
 163  
                     try {
 164  18
                         ThrowableStory throwableStory = future.get();
 165  14
                         Throwable throwable = throwableStory.getThrowable();
 166  14
                         if (throwable != null) {
 167  2
                             failures.put(story.getPath(), throwable);
 168  2
                             if (!embedderControls.ignoreFailureInStories()) {
 169  2
                                 break;
 170  
                             }
 171  
                         }
 172  4
                     } catch (Throwable e) {
 173  4
                         failures.put(story.getPath(), e);
 174  4
                         if (!embedderControls.ignoreFailureInStories()) {
 175  4
                             break;
 176  
                         }
 177  12
                     }
 178  
                 }
 179  12
             }
 180  124
             tickTock();
 181  
         }
 182  
         // cancel any outstanding execution which is not done before returning
 183  12
         for (RunningStory runningStory : runningStories.values()) {
 184  21
             Future<ThrowableStory> future = runningStory.getFuture();
 185  21
             if (!future.isDone()) {
 186  0
                 future.cancel(true);
 187  
             }
 188  21
         }
 189  
 
 190  12
     }
 191  
 
 192  
     private void tickTock() {
 193  
         try {
 194  124
             Thread.sleep(100);
 195  0
         } catch (InterruptedException e) {
 196  124
         }
 197  124
     }
 198  
 
 199  
     private long storyDurationInSecs(long start) {
 200  113
         return (System.currentTimeMillis() - start) / 1000;
 201  
     }
 202  
 
 203  
     private synchronized RunningStory submit(EnqueuedStory enqueuedStory) {
 204  22
         return new RunningStory(enqueuedStory.getStory(), executorService.submit(enqueuedStory));
 205  
     }
 206  
 
 207  44
     private static class EnqueuedStory implements Callable<ThrowableStory> {
 208  
         private final StoryRunner storyRunner;
 209  
         private final Configuration configuration;
 210  
         private final InjectableStepsFactory stepsFactory;
 211  
         private final EmbedderControls embedderControls;
 212  
         private final EmbedderMonitor embedderMonitor;
 213  
         private final String storyPath;
 214  
         private final Story story;
 215  
         private final MetaFilter filter;
 216  
         private final State beforeStories;
 217  
 
 218  
         private EnqueuedStory(StoryRunner storyRunner, Configuration configuration,
 219  
                 InjectableStepsFactory stepsFactory, EmbedderControls embedderControls,
 220  22
                 EmbedderMonitor embedderMonitor, String storyPath, Story story, MetaFilter filter, State beforeStories) {
 221  22
             this.storyRunner = storyRunner;
 222  22
             this.configuration = configuration;
 223  22
             this.stepsFactory = stepsFactory;
 224  22
             this.embedderControls = embedderControls;
 225  22
             this.embedderMonitor = embedderMonitor;
 226  22
             this.storyPath = storyPath;
 227  22
             this.story = story;
 228  22
             this.filter = filter;
 229  22
             this.beforeStories = beforeStories;
 230  22
         }
 231  
 
 232  
         public ThrowableStory call() throws Exception {
 233  
             try {
 234  22
                 embedderMonitor.runningStory(storyPath);
 235  22
                 storyRunner.run(configuration, stepsFactory, story, filter, beforeStories);
 236  12
             } catch (Throwable e) {
 237  12
                 if (embedderControls.ignoreFailureInStories()) {
 238  4
                     embedderMonitor.storyFailed(storyPath, e);
 239  
                 } else {
 240  8
                     return new ThrowableStory(story, new StoryExecutionFailed(storyPath, e));
 241  
                 }
 242  9
             }
 243  13
             return new ThrowableStory(story, null);
 244  
         }
 245  
 
 246  
         public Story getStory() {
 247  22
             return story;
 248  
         }
 249  
 
 250  
     }
 251  
 
 252  
     @SuppressWarnings("serial")
 253  
     public static class StoryExecutionFailed extends RuntimeException {
 254  
 
 255  
         public StoryExecutionFailed(String storyPath, Throwable failure) {
 256  9
             super(storyPath, failure);
 257  9
         }
 258  
 
 259  
     }
 260  
 
 261  
     @SuppressWarnings("serial")
 262  
     public static class StoryTimeout extends RuntimeException {
 263  
 
 264  
             public StoryTimeout(String message) {
 265  1
                         super(message);
 266  1
                 }
 267  
 
 268  
     }
 269  
 
 270  
     public static class ThrowableStory {
 271  
         private Story story;
 272  
         private Throwable throwable;
 273  
 
 274  21
         public ThrowableStory(Story story, Throwable throwable) {
 275  21
             this.story = story;
 276  21
             this.throwable = throwable;
 277  21
         }
 278  
 
 279  
         public Story getStory() {
 280  0
             return story;
 281  
         }
 282  
 
 283  
         public Throwable getThrowable() {
 284  14
             return throwable;
 285  
         }
 286  
     }
 287  
 
 288  
     public static class RunningStory {
 289  
         private Story story;
 290  
         private Future<ThrowableStory> future;
 291  
 
 292  22
         public RunningStory(Story story, Future<ThrowableStory> future) {
 293  22
             this.story = story;
 294  22
             this.future = future;
 295  22
         }
 296  
 
 297  
         public Future<ThrowableStory> getFuture() {
 298  152
             return future;
 299  
         }
 300  
 
 301  
         public Story getStory() {
 302  23
             return story;
 303  
         }
 304  
 
 305  
         public boolean isDone() {
 306  0
             return future.isDone();
 307  
         }
 308  
 
 309  
         public boolean isFailed() {
 310  0
             if (isDone()) {
 311  
                 try {
 312  0
                     return future.get().getThrowable() != null;
 313  0
                 } catch (InterruptedException e) {
 314  0
                 } catch (ExecutionException e) {
 315  0
                 }
 316  
             }
 317  0
             return false;
 318  
         }
 319  
     }
 320  
 
 321  
     public static class StoryOutcome {
 322  
         private String path;
 323  
         private Boolean done;
 324  
         private Boolean failed;
 325  
 
 326  0
         public StoryOutcome(RunningStory story) {
 327  0
             this.path = story.getStory().getPath();
 328  0
             this.done = story.isDone();
 329  0
             this.failed = story.isFailed();
 330  0
         }
 331  
 
 332  
         public String getPath() {
 333  0
             return path;
 334  
         }
 335  
 
 336  
         public Boolean isDone() {
 337  0
             return done;
 338  
         }
 339  
 
 340  
         public Boolean isFailed() {
 341  0
             return failed;
 342  
         }
 343  
 
 344  
     }
 345  
 
 346  
 }