package org.jbehave.core.embedder; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.jbehave.core.annotations.ScenarioType; import org.jbehave.core.configuration.Configuration; import org.jbehave.core.configuration.Keywords; import org.jbehave.core.embedder.MatchingStepMonitor.StepMatch; import org.jbehave.core.failures.BatchFailures; import org.jbehave.core.failures.FailingUponPendingStep; import org.jbehave.core.failures.IgnoringStepsFailure; import org.jbehave.core.failures.PendingStepFound; import org.jbehave.core.failures.PendingStepsFound; import org.jbehave.core.failures.RestartingScenarioFailure; import org.jbehave.core.failures.RestartingStoryFailure; import org.jbehave.core.failures.UUIDExceptionWrapper; import org.jbehave.core.model.ExamplesTable; import org.jbehave.core.model.GivenStories; import org.jbehave.core.model.GivenStory; import org.jbehave.core.model.Lifecycle; import org.jbehave.core.model.Meta; import org.jbehave.core.model.Scenario; import org.jbehave.core.model.Story; import org.jbehave.core.model.StoryDuration; import org.jbehave.core.reporters.ConcurrentStoryReporter; import org.jbehave.core.reporters.StoryReporter; import org.jbehave.core.steps.CandidateSteps; import org.jbehave.core.steps.context.StepsContext; import org.jbehave.core.steps.InjectableStepsFactory; import org.jbehave.core.steps.PendingStepMethodGenerator; import org.jbehave.core.steps.Step; import org.jbehave.core.steps.StepCollector.Stage; import org.jbehave.core.steps.StepCreator.ParametrisedStep; import org.jbehave.core.steps.StepCreator.PendingStep; import org.jbehave.core.steps.StepResult; import org.jbehave.core.steps.Timer; /** * Creates a tree of {@link Performable} objects for a set of stories, grouping * sets of performable steps for each story and scenario, and adding before and * after stories steps. The process has two phases: * <ol> * <li>The tree is populated with groups of performable steps when the stories * are added via the {@link #addStories(RunContext, List)} method.</li> * <li>The performable steps are then populated with the results when the * {@link #performBeforeOrAfterStories(RunContext, Stage)} and * {@link #perform(RunContext, Story)} methods are executed.</li> * </ol> * The tree is created per {@link RunContext} for the set of stories being run * but the individual stories can be performed concurrently. */ public class PerformableTree { private static final Map<String, String> NO_PARAMETERS = new HashMap<String, String>(); private PerformableRoot root = new PerformableRoot(); public PerformableRoot getRoot() { return root; } public void addStories(RunContext context, List<Story> stories) { root.addBeforeSteps(context.beforeOrAfterStoriesSteps(Stage.BEFORE)); for (Story story : stories) { root.add(performableStory(context, story, NO_PARAMETERS)); } root.addAfterSteps(context.beforeOrAfterStoriesSteps(Stage.AFTER)); } private PerformableStory performableStory(RunContext context, Story story, Map<String, String> storyParameters) { PerformableStory performableStory = new PerformableStory(story, context.configuration().keywords(), context.givenStory()); // determine if story is allowed boolean storyAllowed = true; FilteredStory filteredStory = context.filter(story); Meta storyMeta = story.getMeta(); if (!filteredStory.allowed()) { storyAllowed = false; } performableStory.allowed(storyAllowed); if (storyAllowed) { performableStory.addBeforeSteps(context.beforeOrAfterStorySteps(story, Stage.BEFORE)); // determine if before and after scenario steps should be run boolean runBeforeAndAfterScenarioSteps = shouldRunBeforeOrAfterScenarioSteps(context); for (Scenario scenario : story.getScenarios()) { Map<String, String> scenarioParameters = new HashMap<String, String>(storyParameters); PerformableScenario performableScenario = performableScenario(context, story, scenarioParameters, filteredStory, storyMeta, runBeforeAndAfterScenarioSteps, scenario); if (performableScenario.isNormalPerformableScenario() || performableScenario.hasExamples()) { performableStory.add(performableScenario); } } // Add Given stories only if story contains scenarios if (!performableStory.getScenarios().isEmpty()) { performableStory.addGivenStories(performableGivenStories(context, story.getGivenStories(), storyParameters)); } performableStory.addAfterSteps(context.beforeOrAfterStorySteps(story, Stage.AFTER)); } return performableStory; } private PerformableScenario performableScenario(RunContext context, Story story, Map<String, String> storyParameters, FilteredStory filterContext, Meta storyMeta, boolean runBeforeAndAfterScenarioSteps, Scenario scenario) { PerformableScenario performableScenario = new PerformableScenario(scenario, story.getPath()); // scenario also inherits meta from story boolean scenarioAllowed = true; if (failureOccurred(context) && context.configuration().storyControls().skipScenariosAfterFailure()) { return performableScenario; } if (!filterContext.allowed(scenario)) { scenarioAllowed = false; } performableScenario.allowed(scenarioAllowed); if (scenarioAllowed) { Lifecycle lifecycle = story.getLifecycle(); Meta storyAndScenarioMeta = scenario.getMeta().inheritFrom(storyMeta); NormalPerformableScenario normalScenario = normalScenario( context, lifecycle, scenario, storyAndScenarioMeta, storyParameters); // run before scenario steps, if allowed if (runBeforeAndAfterScenarioSteps) { normalScenario.addBeforeSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.BEFORE, ScenarioType.NORMAL)); } if (isParameterisedByExamples(scenario)) { ExamplesTable table = scenario.getExamplesTable(); for (Map<String, String> scenarioParameters : table.getRows()) { Meta exampleScenarioMeta = parameterMeta(context, scenarioParameters).inheritFrom(storyAndScenarioMeta); boolean exampleScenarioAllowed = context.filter().allow(exampleScenarioMeta); if (exampleScenarioAllowed) { ExamplePerformableScenario exampleScenario = exampleScenario( context, lifecycle, scenario, storyAndScenarioMeta, scenarioParameters); performableScenario.addExampleScenario(exampleScenario); } } } else { // plain old scenario performableScenario.useNormalScenario(normalScenario); } // after scenario steps, if allowed if (runBeforeAndAfterScenarioSteps) { normalScenario.addAfterSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.AFTER, ScenarioType.NORMAL)); } } return performableScenario; } private NormalPerformableScenario normalScenario(RunContext context, Lifecycle lifecycle, Scenario scenario, Meta storyAndScenarioMeta, Map<String, String> storyParameters) { NormalPerformableScenario normalScenario = new NormalPerformableScenario(scenario); normalScenario.setStoryAndScenarioMeta(storyAndScenarioMeta); addStepsWithLifecycle(normalScenario, context, lifecycle, storyParameters, scenario, storyAndScenarioMeta); return normalScenario; } private ExamplePerformableScenario exampleScenario(RunContext context, Lifecycle lifecycle, Scenario scenario, Meta storyAndScenarioMeta, Map<String, String> parameters) { ExamplePerformableScenario exampleScenario = new ExamplePerformableScenario(scenario, parameters); exampleScenario.setStoryAndScenarioMeta(storyAndScenarioMeta); exampleScenario.addBeforeSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.BEFORE, ScenarioType.EXAMPLE)); addStepsWithLifecycle(exampleScenario, context, lifecycle, parameters, scenario, storyAndScenarioMeta); exampleScenario.addAfterSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.AFTER, ScenarioType.EXAMPLE)); return exampleScenario; } private Meta parameterMeta(RunContext context, Map<String, String> parameters) { Meta meta = Meta.EMPTY; Keywords keywords = context.configuration().keywords(); String metaText = keywords.meta(); if (parameters.containsKey(metaText)) { meta = Meta.createMeta(parameters.get(metaText), keywords); } return meta; } private void addStepsWithLifecycle(AbstractPerformableScenario performableScenario, RunContext context, Lifecycle lifecycle, Map<String, String> parameters, Scenario scenario, Meta storyAndScenarioMeta) { performableScenario.addBeforeSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.BEFORE, ScenarioType.ANY)); performableScenario.addBeforeSteps(context.lifecycleSteps(lifecycle, storyAndScenarioMeta, Stage.BEFORE)); addMetaParameters(parameters, storyAndScenarioMeta); performableScenario.addGivenStories(performableGivenStories(context, scenario.getGivenStories(), parameters)); performableScenario.addSteps(context.scenarioSteps(scenario, parameters)); performableScenario.addAfterSteps(context.lifecycleSteps(lifecycle, storyAndScenarioMeta, Stage.AFTER)); performableScenario.addAfterSteps(context.beforeOrAfterScenarioSteps(storyAndScenarioMeta, Stage.AFTER, ScenarioType.ANY)); } private List<PerformableStory> performableGivenStories(RunContext context, GivenStories givenStories, Map<String, String> parameters) { List<PerformableStory> stories = new ArrayList<PerformableStory>(); if (givenStories.getPaths().size() > 0) { for (GivenStory givenStory : givenStories.getStories()) { RunContext childContext = context.childContextFor(givenStory); // run given story, using any parameters provided Story story = storyOfPath(context.configuration(), childContext.path()); if ( givenStory.hasAnchorParameters() ){ story = storyWithMatchingScenarios(story, givenStory.getAnchorParameters()); } parameters.putAll(givenStory.getParameters()); stories.add(performableStory(childContext, story, parameters)); } } return stories; } private Story storyWithMatchingScenarios(Story story, Map<String,String> parameters) { if ( parameters.isEmpty() ) return story; List<Scenario> scenarios = new ArrayList<Scenario>(); for ( Scenario scenario : story.getScenarios() ){ if ( matchesParameters(scenario, parameters) ){ scenarios.add(scenario); } } return new Story(story.getPath(), story.getDescription(), story.getMeta(), story.getNarrative(), scenarios); } private boolean matchesParameters(Scenario scenario, Map<String, String> parameters) { Meta meta = scenario.getMeta(); for ( String name : parameters.keySet() ){ if ( meta.hasProperty(name) ){ return meta.getProperty(name).equals(parameters.get(name)); } } return false; } /** * Returns the parsed story from the given path * * @param configuration the Configuration used to run story * @param storyPath the story path * @return The parsed Story */ public Story storyOfPath(Configuration configuration, String storyPath) { String storyAsText = configuration.storyLoader().loadStoryAsText(storyPath); return configuration.storyParser().parseStory(storyAsText, storyPath); } /** * Returns the parsed story from the given text * * @param configuration the Configuration used to run story * @param storyAsText the story text * @param storyId the story Id, which will be returned as story path * @return The parsed Story */ public Story storyOfText(Configuration configuration, String storyAsText, String storyId) { return configuration.storyParser().parseStory(storyAsText, storyId); } private void addMetaParameters(Map<String, String> storyParameters, Meta meta) { for (String name : meta.getPropertyNames()) { if (!storyParameters.containsKey(name)) { storyParameters.put(name, meta.getProperty(name)); } } } private boolean shouldRunBeforeOrAfterScenarioSteps(RunContext context) { Configuration configuration = context.configuration(); if (!configuration.storyControls().skipBeforeAndAfterScenarioStepsIfGivenStory()) { return true; } return !context.givenStory(); } private boolean failureOccurred(RunContext context) { return context.failureOccurred(); } private boolean isParameterisedByExamples(Scenario scenario) { return scenario.getExamplesTable().getHeaders().size() > 0 && !scenario.getGivenStories().requireParameters(); } static void generatePendingStepMethods(RunContext context, List<Step> steps) { List<PendingStep> pendingSteps = new ArrayList<PendingStep>(); for (Step step : steps) { if (step instanceof PendingStep) { pendingSteps.add((PendingStep) step); } } if (!pendingSteps.isEmpty()) { PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration().keywords()); List<String> methods = new ArrayList<String>(); for (PendingStep pendingStep : pendingSteps) { if (!pendingStep.annotated()) { methods.add(generator.generateMethod(pendingStep)); } } } } public interface State { State run(Step step, List<StepResult> results, StoryReporter reporter, UUIDExceptionWrapper storyFailureIfItHappened); UUIDExceptionWrapper getFailure(); } private final static class FineSoFar implements State { public State run(Step step, List<StepResult> results, StoryReporter reporter, UUIDExceptionWrapper storyFailureIfItHappened) { if (step instanceof ParametrisedStep) { ((ParametrisedStep) step).describeTo(reporter); } StepResult result = step.perform(storyFailureIfItHappened); results.add(result); result.describeTo(reporter); UUIDExceptionWrapper stepFailure = result.getFailure(); if (stepFailure == null) { return this; } mostImportantOf(storyFailureIfItHappened, stepFailure); return new SomethingHappened(stepFailure); } private UUIDExceptionWrapper mostImportantOf(UUIDExceptionWrapper failure1, UUIDExceptionWrapper failure2) { return failure1 == null ? failure2 : failure1.getCause() instanceof PendingStepFound ? (failure2 == null ? failure1 : failure2) : failure1; } public UUIDExceptionWrapper getFailure() { return null; } } private final static class SomethingHappened implements State { private UUIDExceptionWrapper failure; public SomethingHappened(UUIDExceptionWrapper failure) { this.failure = failure; } public State run(Step step, List<StepResult> results, StoryReporter reporter, UUIDExceptionWrapper storyFailure) { StepResult result = step.doNotPerform(storyFailure); results.add(result); result.describeTo(reporter); return this; } public UUIDExceptionWrapper getFailure() { return failure; } } public void perform(RunContext context, Story story) { boolean restartingStory = false; try { performCancellable(context, story); if (context.restartStory()){ context.reporter().restartedStory(story, context.failure(context.state())); restartingStory = true; perform(context, story); } } catch (InterruptedException e) { if (context.isCancelled(story)) { context.reporter().storyCancelled(story, context.storyDuration(story)); context.reporter().afterStory(context.givenStory); } throw new UUIDExceptionWrapper(e); } finally { if (!context.givenStory() && context.reporter() instanceof ConcurrentStoryReporter && !restartingStory ) { ((ConcurrentStoryReporter) context.reporter()).invokeDelayed(); } } } private void performCancellable(RunContext context, Story story) throws InterruptedException { if (context.configuration().storyControls().resetStateBeforeStory()) { context.resetState(); context.resetFailures(); } if (!story.getPath().equals(context.path())) { context.currentPath(story.getPath()); } root.get(story).perform(context); if (context.failureOccurred()) { context.addFailure(); } } public void performBeforeOrAfterStories(RunContext context, Stage stage) { String storyPath = StringUtils.capitalize(stage.name().toLowerCase()) + "Stories"; context.currentPath(storyPath); context.reporter().beforeStory(new Story(storyPath), false); try { (stage == Stage.BEFORE ? root.beforeSteps : root.afterSteps).perform(context); } catch (InterruptedException e) { throw new UUIDExceptionWrapper(e); } finally { if (context.reporter() instanceof ConcurrentStoryReporter) { ((ConcurrentStoryReporter) context.reporter()).invokeDelayed(); } } context.reporter().afterStory(false); } @Override public String toString() { return this.getClass().getSimpleName(); } /** * The context for running a story. */ public static class RunContext { private final Configuration configuration; private final InjectableStepsFactory stepsFactory; private final List<CandidateSteps> candidateSteps; private final EmbedderMonitor embedderMonitor; private final MetaFilter filter; private final BatchFailures failures; private final StepsContext stepsContext; private Map<Story, StoryDuration> cancelledStories = new HashMap<Story, StoryDuration>(); private Map<String, List<PendingStep>> pendingStories = new HashMap<String, List<PendingStep>>(); private final ThreadLocal<StoryReporter> reporter = new ThreadLocal<StoryReporter>(); private String path; private boolean givenStory; private State state; public RunContext(Configuration configuration, InjectableStepsFactory stepsFactory, EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) { this.configuration = configuration; this.stepsFactory = stepsFactory; this.embedderMonitor = embedderMonitor; this.candidateSteps = stepsFactory.createCandidateSteps(); this.filter = filter; this.failures = failures; this.stepsContext = configuration.stepsContext(); resetState(); } public StepsContext stepsContext() { return stepsContext; } public boolean restartScenario() { Throwable cause = failure(state); while (cause != null) { if (cause instanceof RestartingScenarioFailure) { return true; } cause = cause.getCause(); } return false; } public boolean restartStory() { Throwable cause = failure(state); while (cause != null) { if (cause instanceof RestartingStoryFailure) { return true; } cause = cause.getCause(); } return false; } public void currentPath(String path) { this.path = path; this.reporter.set(configuration.storyReporter(path)); } public void interruptIfCancelled() throws InterruptedException { for (Story story : cancelledStories.keySet()) { if (path.equals(story.getPath())) { throw new InterruptedException(path); } } } public boolean dryRun() { return configuration.storyControls().dryRun(); } public Configuration configuration() { return configuration; } public boolean givenStory() { return givenStory; } public String path() { return path; } public FilteredStory filter(Story story) { return new FilteredStory(filter, story, configuration.storyControls(), givenStory); } public MetaFilter filter() { return filter; } public PerformableSteps beforeOrAfterStoriesSteps(Stage stage) { return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStoriesSteps(candidateSteps, stage)); } public PerformableSteps beforeOrAfterStorySteps(Story story, Stage stage) { return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterStorySteps(candidateSteps, story, stage, givenStory)); } public PerformableSteps beforeOrAfterScenarioSteps(Meta storyAndScenarioMeta, Stage stage, ScenarioType type) { return new PerformableSteps(configuration.stepCollector().collectBeforeOrAfterScenarioSteps(candidateSteps, storyAndScenarioMeta, stage, type)); } public PerformableSteps lifecycleSteps(Lifecycle lifecycle, Meta meta, Stage stage) { MatchingStepMonitor monitor = new MatchingStepMonitor(configuration.stepMonitor()); List<Step> steps = configuration.stepCollector().collectLifecycleSteps(candidateSteps, lifecycle, meta, stage); return new PerformableSteps(steps, monitor.matched()); } public PerformableSteps scenarioSteps(Scenario scenario, Map<String, String> parameters) { MatchingStepMonitor monitor = new MatchingStepMonitor(configuration.stepMonitor()); List<Step> steps = configuration.stepCollector().collectScenarioSteps(candidateSteps, scenario, parameters, monitor); return new PerformableSteps(steps, monitor.matched()); } public RunContext childContextFor(GivenStory givenStory) { RunContext child = new RunContext(configuration, stepsFactory, embedderMonitor, filter, failures); child.path = configuration.pathCalculator().calculate(path, givenStory.getPath()); child.givenStory = true; return child; } public void cancelStory(Story story, StoryDuration storyDuration) { cancelledStories.put(story, storyDuration); } public boolean isCancelled(Story story) { return cancelledStories.containsKey(story); } public StoryDuration storyDuration(Story story) { return cancelledStories.get(story); } public State state() { return state; } public void stateIs(State state) { this.state = state; } public boolean failureOccurred() { return failed(state); } public void resetState() { this.state = new FineSoFar(); } public void resetFailures() { this.failures.clear(); } public StoryReporter reporter() { return reporter.get(); } public boolean failed(State state) { return !state.getClass().equals(FineSoFar.class); } public Throwable failure(State state) { if (failed(state)) { return state.getFailure().getCause(); } return null; } public void addFailure() { Throwable failure = failure(state); if (failure != null) { failures.put(state.toString(), failure); } } public void addFailure(String path, Throwable cause) { if (cause != null) { failures.put(path, cause); } } public void pendingSteps(List<PendingStep> pendingSteps) { if (!pendingSteps.isEmpty()) { pendingStories.put(path, pendingSteps); } } public boolean hasPendingSteps() { return pendingStories.containsKey(path); } public boolean isStoryPending() { return pendingStories.containsKey(path); } public boolean hasFailed() { return failed(state); } public Status status(State initial) { if (isStoryPending()) { return Status.PENDING; } else if (failed(initial)) { return Status.NOT_PERFORMED; } else { return (hasFailed() ? Status.FAILED : Status.SUCCESSFUL); } } public MetaFilter getFilter() { return filter; } public BatchFailures getFailures() { return failures; } public EmbedderMonitor embedderMonitor(){ return embedderMonitor; } } public static interface Performable { void perform(RunContext context) throws InterruptedException; } public static class PerformableRoot { private PerformableSteps beforeSteps = new PerformableSteps(); private Map<String, PerformableStory> stories = new LinkedHashMap<String, PerformableStory>(); private PerformableSteps afterSteps = new PerformableSteps(); public void addBeforeSteps(PerformableSteps beforeSteps) { this.beforeSteps = beforeSteps; } public void add(PerformableStory performableStory) { stories.put(performableStory.getStory().getPath(), performableStory); } public void addAfterSteps(PerformableSteps afterSteps) { this.afterSteps = afterSteps; } public PerformableStory get(Story story) { PerformableStory performableStory = stories.get(story.getPath()); if (performableStory != null) { return performableStory; } throw new RuntimeException("No performable story for path " + story.getPath()); } public List<PerformableStory> getStories() { return new ArrayList<PerformableStory>(stories.values()); } } public static enum Status { SUCCESSFUL, FAILED, PENDING, NOT_PERFORMED, NOT_ALLOWED; } public static class PerformableStory implements Performable { private final Story story; private String localizedNarrative; private boolean allowed; private Status status; private List<PerformableStory> givenStories = new ArrayList<PerformableStory>(); private List<PerformableScenario> scenarios = new ArrayList<PerformableScenario>(); private PerformableSteps beforeSteps = new PerformableSteps(); private PerformableSteps afterSteps = new PerformableSteps(); private Timing timing = new Timing(); private boolean givenStory; public PerformableStory(Story story, Keywords keywords, boolean givenStory) { this.story = story; this.givenStory = givenStory; this.localizedNarrative = story.getNarrative().asString(keywords); } public void allowed(boolean allowed) { this.allowed = allowed; } public boolean isAllowed() { return allowed; } public boolean givenStory() { return givenStory; } public Status getStatus() { return status; } public void addGivenStories(List<PerformableStory> performableGivenStories) { this.givenStories.addAll(performableGivenStories); } public void addBeforeSteps(PerformableSteps beforeSteps) { this.beforeSteps = beforeSteps; } public void addAfterSteps(PerformableSteps afterSteps) { this.afterSteps = afterSteps; } public void add(PerformableScenario performableScenario) { scenarios.add(performableScenario); } public Story getStory() { return story; } public String getLocalisedNarrative() { return localizedNarrative; } public Timing getTiming() { return timing; } public void perform(RunContext context) throws InterruptedException { if (!allowed) { context.reporter().storyNotAllowed(story, context.filter.asString()); this.status = Status.NOT_ALLOWED; } context.stepsContext().resetStory(); context.reporter().beforeStory(story, context.givenStory); context.reporter().narrative(story.getNarrative()); context.reporter().lifecyle(story.getLifecycle()); State state = context.state(); Timer timer = new Timer().start(); try { beforeSteps.perform(context); performGivenStories(context); performScenarios(context); afterSteps.perform(context); } finally { timing.setTimings(timer.stop()); } if (context.restartStory()) { context.reporter().afterStory(true); } else { context.reporter().afterStory(context.givenStory); } this.status = context.status(state); } private void performGivenStories(RunContext context) throws InterruptedException { if (givenStories.size() > 0) { context.reporter().givenStories(story.getGivenStories()); final boolean parentGivenStory = context.givenStory; for (PerformableStory story : givenStories) { context.givenStory = story.givenStory(); story.perform(context); } context.givenStory = parentGivenStory; } } private void performScenarios(RunContext context) throws InterruptedException { for (PerformableScenario scenario : scenarios) { scenario.perform(context); } } public List<PerformableScenario> getScenarios() { return scenarios; } } public static class PerformableScenario implements Performable { private final Scenario scenario; private final String storyPath; private boolean allowed; private NormalPerformableScenario normalPerformableScenario; private List<ExamplePerformableScenario> examplePerformableScenarios = new ArrayList<ExamplePerformableScenario>(); @SuppressWarnings("unused") private Status status; public PerformableScenario(Scenario scenario, String storyPath) { this.scenario = scenario; this.storyPath = storyPath; } public void useNormalScenario(NormalPerformableScenario normalScenario) { this.normalPerformableScenario = normalScenario; } public void addExampleScenario(ExamplePerformableScenario exampleScenario) { this.examplePerformableScenarios.add(exampleScenario); } public void allowed(boolean allowed) { this.allowed = allowed; } public boolean isAllowed() { return allowed; } public Scenario getScenario() { return scenario; } public String getStoryPath() { return storyPath; } public boolean hasExamples() { return examplePerformableScenarios.size() > 0; } public List<ExamplePerformableScenario> getExamples() { return examplePerformableScenarios; } public boolean isNormalPerformableScenario() { return normalPerformableScenario != null; } public void perform(RunContext context) throws InterruptedException { if ( !isAllowed() ) { context.embedderMonitor().scenarioNotAllowed(scenario, context.filter()); return; } context.stepsContext().resetScenario(); context.reporter().beforeScenario(scenario.getTitle()); context.reporter().scenarioMeta(scenario.getMeta()); State state = context.state(); if (!examplePerformableScenarios.isEmpty()) { context.reporter().beforeExamples(scenario.getSteps(), scenario.getExamplesTable()); for (ExamplePerformableScenario exampleScenario : examplePerformableScenarios) { exampleScenario.perform(context); } context.reporter().afterExamples(); } else { context.stepsContext().resetExample(); normalPerformableScenario.perform(context); } this.status = context.status(state); context.reporter().afterScenario(); } } public static abstract class AbstractPerformableScenario implements Performable { protected final Map<String, String> parameters; protected final List<PerformableStory> givenStories = new ArrayList<PerformableStory>(); protected final PerformableSteps beforeSteps = new PerformableSteps(); protected final PerformableSteps steps = new PerformableSteps(); protected final PerformableSteps afterSteps = new PerformableSteps(); private Meta storyAndScenarioMeta = new Meta(); public AbstractPerformableScenario() { this(new HashMap<String, String>()); } public AbstractPerformableScenario(Map<String, String> parameters) { this.parameters = parameters; } public void addGivenStories(List<PerformableStory> givenStories) { this.givenStories.addAll(givenStories); } public void addBeforeSteps(PerformableSteps beforeSteps) { this.beforeSteps.add(beforeSteps); } public void addSteps(PerformableSteps steps) { this.steps.add(steps); } public void addAfterSteps(PerformableSteps afterSteps) { this.afterSteps.add(afterSteps); } public Map<String, String> getParameters() { return parameters; } protected void performRestartableSteps(RunContext context) throws InterruptedException { boolean restart = true; while (restart) { restart = false; try { steps.perform(context); } catch (RestartingScenarioFailure e) { restart = true; continue; } } } public Meta getStoryAndScenarioMeta() { return storyAndScenarioMeta; } public void setStoryAndScenarioMeta(Meta storyAndScenarioMeta) { this.storyAndScenarioMeta = storyAndScenarioMeta; } } public static class NormalPerformableScenario extends AbstractPerformableScenario { private Scenario scenario; public NormalPerformableScenario(Scenario scenario) { this.scenario = scenario; } public void perform(RunContext context) throws InterruptedException { if (context.configuration().storyControls().resetStateBeforeScenario()) { context.resetState(); } beforeSteps.perform(context); if (givenStories.size() > 0) { context.reporter().givenStories(scenario.getGivenStories()); final boolean parentGivenStory = context.givenStory; for (PerformableStory story : givenStories) { context.givenStory = story.givenStory(); story.perform(context); } context.givenStory = parentGivenStory; } performRestartableSteps(context); afterSteps.perform(context); } } public static class ExamplePerformableScenario extends AbstractPerformableScenario { private Scenario scenario; public ExamplePerformableScenario(Scenario scenario, Map<String, String> exampleParameters) { super(exampleParameters); this.scenario = scenario; } public void perform(RunContext context) throws InterruptedException { Meta parameterMeta = parameterMeta(context.configuration().keywords(), parameters).inheritFrom(getStoryAndScenarioMeta()); if (!parameterMeta.isEmpty() && !context.filter().allow(parameterMeta)) { return; } if (context.configuration().storyControls().resetStateBeforeScenario()) { context.resetState(); } context.stepsContext().resetExample(); context.reporter().example(parameters); beforeSteps.perform(context); if (givenStories.size() > 0) { context.reporter().givenStories(scenario.getGivenStories()); final boolean parentGivenStory = context.givenStory; for (PerformableStory story : givenStories) { context.givenStory = story.givenStory(); story.perform(context); } context.givenStory = parentGivenStory; } performRestartableSteps(context); afterSteps.perform(context); } private Meta parameterMeta(Keywords keywords, Map<String, String> parameters) { String meta = keywords.meta(); if (parameters.containsKey(meta)) { return Meta.createMeta(parameters.get(meta), keywords); } return Meta.EMPTY; } } public static class PerformableSteps implements Performable { private transient final List<Step> steps; private transient final List<PendingStep> pendingSteps; private List<StepMatch> matches; private List<StepResult> results; public PerformableSteps() { this(null); } public PerformableSteps(List<Step> steps) { this(steps, null); } public PerformableSteps(List<Step> steps, List<StepMatch> stepMatches) { this.steps = ( steps == null ? new ArrayList<Step>() : steps ); this.pendingSteps = pendingSteps(); this.matches = stepMatches; } public void add(PerformableSteps performableSteps){ this.steps.addAll(performableSteps.steps); this.pendingSteps.addAll(performableSteps.pendingSteps); if ( performableSteps.matches != null ){ if ( this.matches == null ){ this.matches = new ArrayList<StepMatch>(); } this.matches.addAll(performableSteps.matches); } } public void perform(RunContext context) throws InterruptedException { if (steps.size() == 0) { return; } Keywords keywords = context.configuration().keywords(); State state = context.state(); StoryReporter reporter = context.reporter(); results = new ArrayList<StepResult>(); boolean ignoring = false; for (Step step : steps) { try { context.interruptIfCancelled(); if (ignoring) { reporter.ignorable(step.asString(keywords)); } else { state = state.run(step, results, reporter, state.getFailure()); } } catch (IgnoringStepsFailure e) { reporter.ignorable(step.asString(keywords)); ignoring = true; } catch (RestartingScenarioFailure e) { reporter.restarted(step.asString(keywords), e); throw e; } } context.stateIs(state); context.pendingSteps(pendingSteps); generatePendingStepMethods(context, pendingSteps); } private List<PendingStep> pendingSteps() { List<PendingStep> pending = new ArrayList<PendingStep>(); for (Step step : steps) { if (step instanceof PendingStep) { pending.add((PendingStep) step); } } return pending; } private void generatePendingStepMethods(RunContext context, List<PendingStep> pendingSteps) { if (!pendingSteps.isEmpty()) { PendingStepMethodGenerator generator = new PendingStepMethodGenerator(context.configuration() .keywords()); List<String> methods = new ArrayList<String>(); for (PendingStep pendingStep : pendingSteps) { if (!pendingStep.annotated()) { methods.add(generator.generateMethod(pendingStep)); } } context.reporter().pendingMethods(methods); if ( context.configuration().pendingStepStrategy() instanceof FailingUponPendingStep ){ throw new PendingStepsFound(pendingSteps); } } } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } } public static class Timing { private long durationInMillis; private long start; private long end; public long getDurationInMillis() { return durationInMillis; } public void setTimings(Timer timer) { this.start = timer.getStart(); this.end = timer.getEnd(); this.durationInMillis = timer.getDuration(); } } public RunContext newRunContext(Configuration configuration, InjectableStepsFactory stepsFactory, EmbedderMonitor embedderMonitor, MetaFilter filter, BatchFailures failures) { return new RunContext(configuration, stepsFactory, embedderMonitor, filter, failures); } }