Coverage Report - org.jbehave.core.embedder.StoryManager
 
Classes in this File Line Coverage Branch Coverage Complexity
StoryManager
82%
77/93
64%
22/34
1.812
StoryManager$1
N/A
N/A
1.812
StoryManager$EnqueuedStory
100%
21/21
100%
2/2
1.812
StoryManager$RunningStory
46%
6/13
0%
0/4
1.812
StoryManager$StoryExecutionFailed
100%
2/2
N/A
1.812
StoryManager$StoryOutcome
0%
0/8
N/A
1.812
StoryManager$ThrowableStory
83%
5/6
N/A
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  
 }