StoryManager.java
- package org.jbehave.core.embedder;
- import java.io.File;
- import java.io.FileWriter;
- import java.io.IOException;
- import java.io.Writer;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Properties;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Future;
- import org.jbehave.core.configuration.Configuration;
- import org.jbehave.core.embedder.PerformableTree.PerformableRoot;
- import org.jbehave.core.embedder.PerformableTree.RunContext;
- import org.jbehave.core.embedder.StoryTimeouts.TimeoutParser;
- import org.jbehave.core.failures.BatchFailures;
- import org.jbehave.core.model.Story;
- import org.jbehave.core.model.StoryDuration;
- import org.jbehave.core.steps.InjectableStepsFactory;
- import org.jbehave.core.steps.StepCollector.Stage;
- /**
- * Manages the execution and outcomes of running stories. While each story is
- * run by the {@link PerformableTree}, the manager is responsible for the concurrent
- * submission and monitoring of their execution via the {@link ExecutorService}.
- */
- public class StoryManager {
- private final Configuration configuration;
- private final EmbedderControls embedderControls;
- private final EmbedderMonitor embedderMonitor;
- private final ExecutorService executorService;
- private final InjectableStepsFactory stepsFactory;
- private final PerformableTree performableTree;
- private final Map<String, RunningStory> runningStories = new HashMap<>();
- private final Map<MetaFilter, List<Story>> excludedStories = new HashMap<>();
- private RunContext context;
- private StoryTimeouts timeouts;
-
- public StoryManager(Configuration configuration,
- InjectableStepsFactory stepsFactory,
- EmbedderControls embedderControls, EmbedderMonitor embedderMonitor,
- ExecutorService executorService, PerformableTree performableTree, TimeoutParser... parsers) {
- this.configuration = configuration;
- this.embedderControls = embedderControls;
- this.embedderMonitor = embedderMonitor;
- this.executorService = executorService;
- this.stepsFactory = stepsFactory;
- this.performableTree = performableTree;
- this.timeouts = new StoryTimeouts(embedderControls, embedderMonitor);
- this.timeouts.withParsers(parsers);
- }
- public Story storyOfPath(String storyPath) {
- return performableTree.storyOfPath(configuration, storyPath);
- }
- public List<Story> storiesOfPaths(List<String> storyPaths) {
- List<Story> stories = new ArrayList<>(storyPaths.size());
- for (String storyPath : storyPaths) {
- stories.add(storyOfPath(storyPath));
- }
- if (configuration.isParallelStoryExamplesEnabled()) {
- StorySplitter storySplitter = new StorySplitter(configuration.storyControls().storyIndexFormat());
- return storySplitter.splitStories(stories);
- }
- return stories;
- }
- public Story storyOfText(String storyAsText, String storyId) {
- return performableTree.storyOfText(configuration, storyAsText, storyId);
- }
- public void clear() {
- runningStories.clear();
- }
- public PerformableRoot performableRoot() {
- return performableTree.getRoot();
- }
- public List<StoryOutcome> outcomes() {
- List<StoryOutcome> outcomes = new ArrayList<>();
- for (RunningStory story : runningStories.values()) {
- outcomes.add(new StoryOutcome(story));
- }
- return outcomes;
- }
- public void runStoriesAsPaths(List<String> storyPaths, MetaFilter filter,
- BatchFailures failures) {
- runStories(storiesOfPaths(storyPaths), filter, failures);
- }
- public void runStories(List<Story> stories, MetaFilter filter,
- BatchFailures failures) {
- // create new run context
- AllStepCandidates allStepCandidates = new AllStepCandidates(configuration.stepConditionMatcher(),
- stepsFactory.createCandidateSteps());
- context = performableTree.newRunContext(configuration, allStepCandidates, embedderMonitor, filter, failures);
- // add stories
- performableTree.addStories(context, stories);
- // perform stories
- performStories(context, performableTree, stories);
- // collect failures
- failures.putAll(context.getFailures());
- }
- private void runStories(RunContext context, List<Story> stories) {
- stories.stream()
- .sorted(configuration.storyExecutionComparator())
- .forEach(story -> filterRunning(context, story));
- }
- private void performStories(RunContext context,
- PerformableTree performableTree, List<Story> stories) {
- // before stories
- performableTree.performBeforeOrAfterStories(context, Stage.BEFORE);
- // run stories
- runStories(context, stories);
- waitUntilAllDoneOrFailed(context);
- MetaFilter filter = context.filter();
- List<Story> excluded = excludedBy(filter);
- if (!excluded.isEmpty()) {
- embedderMonitor.storiesExcluded(excluded, filter,
- embedderControls.verboseFiltering());
- }
- // after stories
- performableTree.performBeforeOrAfterStories(context, Stage.AFTER);
- }
- private void filterRunning(RunContext context, Story story) {
- if (context.filter(story).excluded()) {
- excludedBy(context.getFilter()).add(story);
- } else {
- runningStories.put(story.getPath(), runningStory(story));
- }
- }
- public List<Story> excludedBy(MetaFilter filter) {
- List<Story> stories = excludedStories.get(filter);
- if (stories == null) {
- stories = new ArrayList<>();
- excludedStories.put(filter, stories);
- }
- return stories;
- }
- public RunningStory runningStory(Story story) {
- return submit(new EnqueuedStory(performableTree, context,
- embedderControls, embedderMonitor, story, timeouts));
- }
- public void waitUntilAllDoneOrFailed(RunContext context) {
- if (runningStories.values().isEmpty()) {
- return;
- }
- boolean allDone = false;
- boolean started = false;
- while (!allDone || !started) {
- allDone = true;
- for (RunningStory runningStory : runningStories.values()) {
- if (runningStory.isStarted()) {
- started = true;
- Story story = runningStory.getStory();
- Future<ThrowableStory> future = runningStory.getFuture();
- if (!future.isDone()) {
- allDone = false;
- StoryDuration duration = runningStory.getDuration();
- runningStory.updateDuration();
- if (context.isCancelled(story)) {
- if (duration.cancelTimedOut()) {
- future.cancel(true);
- }
- continue;
- }
- if (duration.timedOut()) {
- embedderMonitor.storyTimeout(story, duration);
- context.cancelStory(story, duration);
- if (embedderControls.failOnStoryTimeout()) {
- throw new StoryExecutionFailed(story.getPath(),
- new StoryTimedOut(duration));
- }
- continue;
- }
- } else {
- try {
- ThrowableStory throwableStory = future.get();
- Throwable throwable = throwableStory.getThrowable();
- if (throwable != null) {
- context.addFailure(story, throwable);
- if (!embedderControls.ignoreFailureInStories()) {
- continue;
- }
- }
- } catch (Throwable e) {
- context.addFailure(story, e);
- if (!embedderControls.ignoreFailureInStories()) {
- continue;
- }
- }
- }
- } else {
- started = false;
- allDone = false;
- }
- }
- tickTock();
- }
- writeStoryDurations(runningStories.values());
- }
- protected void writeStoryDurations(Collection<RunningStory> runningStories) {
- // collect story durations and cancel any outstanding execution which is
- // not done before returning
- Properties storyDurations = new Properties();
- long total = 0;
- for (RunningStory runningStory : runningStories) {
- long durationInMillis = runningStory.getDurationInMillis();
- total += durationInMillis;
- storyDurations.setProperty(runningStory.getStory().getPath(),
- Long.toString(durationInMillis));
- Future<ThrowableStory> future = runningStory.getFuture();
- if (!future.isDone()) {
- future.cancel(true);
- }
- }
- int threads = embedderControls.threads();
- long threadAverage = total / threads;
- storyDurations.setProperty("total", Long.toString(total));
- storyDurations.setProperty("threads", Long.toString(threads));
- storyDurations.setProperty("threadAverage",
- Long.toString(threadAverage));
- write(storyDurations, "storyDurations.props");
- }
- private void write(Properties p, String name) {
- File outputDirectory = configuration.storyReporterBuilder()
- .outputDirectory();
- try {
- outputDirectory.mkdirs();
- Writer output = new FileWriter(new File(outputDirectory, name));
- p.store(output, this.getClass().getName());
- output.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- private void tickTock() {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // swallow exception quietly
- }
- }
- private synchronized RunningStory submit(EnqueuedStory enqueuedStory) {
- return new RunningStory(enqueuedStory, executorService.submit(enqueuedStory));
- }
- static class EnqueuedStory implements Callable<ThrowableStory> {
- private final PerformableTree performableTree;
- private final RunContext context;
- private final EmbedderControls embedderControls;
- private final EmbedderMonitor embedderMonitor;
- private final Story story;
- private final StoryTimeouts timeouts;
- private long startedAtMillis;
- public EnqueuedStory(PerformableTree performableTree,
- RunContext context, EmbedderControls embedderControls,
- EmbedderMonitor embedderMonitor, Story story, StoryTimeouts timeouts) {
- this.performableTree = performableTree;
- this.context = context;
- this.embedderControls = embedderControls;
- this.embedderMonitor = embedderMonitor;
- this.story = story;
- this.timeouts = timeouts;
- }
- @Override
- public ThrowableStory call() {
- startedAtMillis = System.currentTimeMillis();
- String storyPath = story.getPath();
- try {
- embedderMonitor.runningStory(storyPath);
- performableTree.perform(context, story);
- } catch (Throwable e) {
- if (embedderControls.ignoreFailureInStories()) {
- embedderMonitor.storyFailed(storyPath, e);
- } else {
- return new ThrowableStory(story, new StoryExecutionFailed(
- storyPath, e));
- }
- }
- return new ThrowableStory(story, null);
- }
- public Story getStory() {
- return story;
- }
- public long getStartedAtMillis() {
- return startedAtMillis;
- }
- public long getTimeoutInSecs() {
- return timeouts.getTimeoutInSecs(story);
- }
- }
- @SuppressWarnings("serial")
- public static class StoryExecutionFailed extends RuntimeException {
- public StoryExecutionFailed(String storyPath, Throwable failure) {
- super(storyPath, failure);
- }
- }
- @SuppressWarnings("serial")
- public static class StoryTimedOut extends RuntimeException {
- public StoryTimedOut(StoryDuration storyDuration) {
- super(storyDuration.getDurationInSecs() + "s > "
- + storyDuration.getTimeoutInSecs() + "s");
- }
- }
- public static class ThrowableStory {
- private Story story;
- private Throwable throwable;
- public ThrowableStory(Story story, Throwable throwable) {
- this.story = story;
- this.throwable = throwable;
- }
- public Story getStory() {
- return story;
- }
- public Throwable getThrowable() {
- return throwable;
- }
- }
- public static class RunningStory {
- private EnqueuedStory enqueuedStory;
- private Future<ThrowableStory> future;
- private StoryDuration duration;
- public RunningStory(EnqueuedStory enqueuedStory,
- Future<ThrowableStory> future) {
- this.enqueuedStory = enqueuedStory;
- this.future = future;
- }
- public Future<ThrowableStory> getFuture() {
- return future;
- }
- public Story getStory() {
- return enqueuedStory.getStory();
- }
- public long getDurationInMillis() {
- if (duration == null) {
- return 0;
- }
- return duration.getDurationInSecs() * 1000;
- }
- public StoryDuration getDuration() {
- if (duration == null) {
- duration = new StoryDuration(enqueuedStory.getStartedAtMillis(), enqueuedStory.getTimeoutInSecs());
- }
- return duration;
- }
- public void updateDuration() {
- duration.update();
- }
- public boolean isDone() {
- return future.isDone();
- }
- public boolean isFailed() {
- if (isDone()) {
- try {
- return future.get().getThrowable() != null;
- } catch (InterruptedException | ExecutionException e) {
- // swallow exception quietly
- }
- }
- return false;
- }
-
- public boolean isStarted() {
- return enqueuedStory.getStartedAtMillis() != 0;
- }
- }
- public static class StoryOutcome {
- private String path;
- private Boolean done;
- private Boolean failed;
- public StoryOutcome(RunningStory story) {
- this.path = story.getStory().getPath();
- this.done = story.isDone();
- this.failed = story.isFailed();
- }
- public String getPath() {
- return path;
- }
- public Boolean isDone() {
- return done;
- }
- public Boolean isFailed() {
- return failed;
- }
- }
- }