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