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