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