- package org.jbehave.core.reporters;
- import java.lang.reflect.InvocationTargetException;
- import java.lang.reflect.Method;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
- import java.util.Map;
- import java.util.function.Consumer;
- import java.util.stream.Collectors;
- import org.jbehave.core.model.ExamplesTable;
- import org.jbehave.core.model.GivenStories;
- import org.jbehave.core.model.Lifecycle;
- import org.jbehave.core.model.Lifecycle.ExecutionType;
- import org.jbehave.core.model.Narrative;
- import org.jbehave.core.model.OutcomesTable;
- import org.jbehave.core.model.Scenario;
- import org.jbehave.core.model.Step;
- import org.jbehave.core.model.Story;
- import org.jbehave.core.model.StoryDuration;
- import org.jbehave.core.steps.StepCollector.Stage;
- import org.jbehave.core.steps.StepCreator.PendingStep;
- import org.jbehave.core.steps.Timing;
- /**
- * When running a multithreading mode, reports cannot be written concurrently but should
- * be delayed and invoked only at the end of a story, ensuring synchronization on the delegate
- * responsible for the reporting.
- */
- public class ConcurrentStoryReporter implements StoryReporter {
- private static Method beforeStoriesSteps;
- private static Method afterStoriesSteps;
- private static Method storyCancelled;
- private static Method storyExcluded;
- private static Method beforeStory;
- private static Method afterStory;
- private static Method narrative;
- private static Method lifecycle;
- private static Method beforeStorySteps;
- private static Method afterStorySteps;
- private static Method beforeScenarioSteps;
- private static Method afterScenarioSteps;
- private static Method beforeComposedSteps;
- private static Method afterComposedSteps;
- private static Method scenarioExcluded;
- private static Method beforeScenarios;
- private static Method beforeScenario;
- private static Method afterScenario;
- private static Method afterScenarios;
- private static Method beforeGivenStories;
- private static Method givenStories;
- private static Method givenStoriesPaths;
- private static Method afterGivenStories;
- private static Method beforeExamples;
- private static Method example;
- private static Method afterExamples;
- private static Method beforeStep;
- private static Method successful;
- private static Method ignorable;
- private static Method comment;
- private static Method pending;
- private static Method pendingDeprecated;
- private static Method notPerformed;
- private static Method failed;
- private static Method failedOutcomes;
- private static Method dryRun;
- private static Method pendingMethods;
- private static Method restarted;
- private static Method restartedStory;
- static {
- try {
- beforeStoriesSteps = StoryReporter.class.getMethod("beforeStoriesSteps", Stage.class);
- afterStoriesSteps = StoryReporter.class.getMethod("afterStoriesSteps", Stage.class);
- storyCancelled = StoryReporter.class.getMethod("storyCancelled", Story.class, StoryDuration.class);
- storyExcluded = StoryReporter.class.getMethod("storyExcluded", Story.class, String.class);
- beforeStory = StoryReporter.class.getMethod("beforeStory", Story.class, Boolean.TYPE);
- afterStory = StoryReporter.class.getMethod("afterStory", Boolean.TYPE);
- narrative = StoryReporter.class.getMethod("narrative", Narrative.class);
- lifecycle = StoryReporter.class.getMethod("lifecycle", Lifecycle.class);
- beforeStorySteps = StoryReporter.class.getMethod("beforeStorySteps", Stage.class, ExecutionType.class);
- afterStorySteps = StoryReporter.class.getMethod("afterStorySteps", Stage.class, ExecutionType.class);
- beforeScenarioSteps = StoryReporter.class.getMethod("beforeScenarioSteps", Stage.class,
- ExecutionType.class);
- afterScenarioSteps = StoryReporter.class.getMethod("afterScenarioSteps", Stage.class, ExecutionType.class);
- beforeComposedSteps = StoryReporter.class.getMethod("beforeComposedSteps");
- afterComposedSteps = StoryReporter.class.getMethod("afterComposedSteps");
- scenarioExcluded = StoryReporter.class.getMethod("scenarioExcluded", Scenario.class, String.class);
- beforeScenarios = StoryReporter.class.getMethod("beforeScenarios");
- beforeScenario = StoryReporter.class.getMethod("beforeScenario", Scenario.class);
- afterScenario = StoryReporter.class.getMethod("afterScenario", Timing.class);
- afterScenarios = StoryReporter.class.getMethod("afterScenarios");
- beforeGivenStories = StoryReporter.class.getMethod("beforeGivenStories");
- givenStories = StoryReporter.class.getMethod("givenStories", GivenStories.class);
- givenStoriesPaths = StoryReporter.class.getMethod("givenStories", List.class);
- afterGivenStories = StoryReporter.class.getMethod("afterGivenStories");
- beforeExamples = StoryReporter.class.getMethod("beforeExamples", List.class, ExamplesTable.class);
- example = StoryReporter.class.getMethod("example", Map.class, int.class);
- afterExamples = StoryReporter.class.getMethod("afterExamples");
- beforeStep = StoryReporter.class.getMethod("beforeStep", Step.class);
- successful = StoryReporter.class.getMethod("successful", String.class);
- ignorable = StoryReporter.class.getMethod("ignorable", String.class);
- comment = StoryReporter.class.getMethod("comment", String.class);
- pending = StoryReporter.class.getMethod("pending", PendingStep.class);
- pendingDeprecated = StoryReporter.class.getMethod("pending", String.class);
- notPerformed = StoryReporter.class.getMethod("notPerformed", String.class);
- failed = StoryReporter.class.getMethod("failed", String.class, Throwable.class);
- failedOutcomes = StoryReporter.class.getMethod("failedOutcomes", String.class, OutcomesTable.class);
- dryRun = StoryReporter.class.getMethod("dryRun");
- pendingMethods = StoryReporter.class.getMethod("pendingMethods", List.class);
- restarted = StoryReporter.class.getMethod("restarted", String.class, Throwable.class);
- restartedStory = StoryReporter.class.getMethod("restartedStory", Story.class, Throwable.class);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- }
- }
- private final StoryReporter crossReferencing;
- private final StoryReporter delegate;
- private final StoryReporter threadSafeDelegate;
- private final boolean multiThreading;
- private final List<DelayedMethod> delayedMethods;
- private boolean invoked = false;
- public ConcurrentStoryReporter(StoryReporter crossReferencing, List<StoryReporter> delegates,
- boolean multiThreading) {
- this.crossReferencing = crossReferencing;
- List<StoryReporter> reporters = new ArrayList<>(delegates);
- List<StoryReporter> threadSafeDelegates = reporters.stream()
- .filter(ThreadSafeReporter.class::isInstance)
- .collect(Collectors.toList());
- this.threadSafeDelegate = new DelegatingStoryReporter(threadSafeDelegates);
- reporters.removeAll(threadSafeDelegates);
- this.delegate = new DelegatingStoryReporter(reporters);
- this.multiThreading = multiThreading;
- this.delayedMethods = multiThreading ? Collections.synchronizedList(new ArrayList<DelayedMethod>()) : null;
- }
- @Override
- public void beforeStoriesSteps(Stage stage) {
- perform(reporter -> reporter.beforeStoriesSteps(stage), beforeStoriesSteps, stage);
- }
- @Override
- public void afterStoriesSteps(Stage stage) {
- perform(reporter -> reporter.afterStoriesSteps(stage), afterStoriesSteps, stage);
- }
- @Override
- public void storyExcluded(Story story, String filter) {
- perform(reporter -> reporter.storyExcluded(story, filter), storyExcluded, story, filter);
- }
- @Override
- public void beforeStory(Story story, boolean givenStory) {
- perform(reporter -> reporter.beforeStory(story, givenStory), beforeStory, story, givenStory);
- }
- @Override
- public void afterStory(boolean givenStory) {
- perform(reporter -> reporter.afterStory(givenStory), afterStory, givenStory);
- }
- @Override
- public void narrative(Narrative narrative) {
- perform(reporter -> reporter.narrative(narrative), ConcurrentStoryReporter.narrative, narrative);
- }
- @Override
- public void lifecycle(Lifecycle lifecycle) {
- perform(reporter -> reporter.lifecycle(lifecycle), ConcurrentStoryReporter.lifecycle, lifecycle);
- }
- @Override
- public void beforeStorySteps(Stage stage, Lifecycle.ExecutionType type) {
- perform(reporter -> reporter.beforeStorySteps(stage, type), beforeStorySteps, stage, type);
- }
- @Override
- public void afterStorySteps(Stage stage, Lifecycle.ExecutionType type) {
- perform(reporter -> reporter.afterStorySteps(stage, type), afterStorySteps, stage, type);
- }
- @Override
- public void beforeComposedSteps() {
- perform(StoryReporter::beforeComposedSteps, beforeComposedSteps);
- }
- @Override
- public void afterComposedSteps() {
- perform(StoryReporter::afterComposedSteps, afterComposedSteps);
- }
- @Override
- public void beforeScenarioSteps(Stage stage, Lifecycle.ExecutionType type) {
- perform(reporter -> reporter.beforeScenarioSteps(stage, type), beforeScenarioSteps, stage, type);
- }
- @Override
- public void afterScenarioSteps(Stage stage, Lifecycle.ExecutionType type) {
- perform(reporter -> reporter.afterScenarioSteps(stage, type), afterScenarioSteps, stage, type);
- }
- @Override
- public void scenarioExcluded(Scenario scenario, String filter) {
- perform(reporter -> reporter.scenarioExcluded(scenario, filter), scenarioExcluded, scenario, filter);
- }
- @Override
- public void beforeScenarios() {
- perform(StoryReporter::beforeScenarios, beforeScenarios);
- }
- @Override
- public void beforeScenario(Scenario scenario) {
- perform(reporter -> reporter.beforeScenario(scenario), beforeScenario, scenario);
- }
- @Override
- public void afterScenario(Timing timing) {
- perform(reporter -> reporter.afterScenario(timing), afterScenario, timing);
- }
- @Override
- public void afterScenarios() {
- perform(StoryReporter::afterScenarios, afterScenarios);
- }
- @Override
- public void beforeGivenStories() {
- perform(StoryReporter::beforeGivenStories, beforeGivenStories);
- }
- @Override
- public void givenStories(GivenStories stories) {
- perform(reporter -> reporter.givenStories(stories), givenStories, stories);
- }
- @Override
- public void givenStories(List<String> storyPaths) {
- perform(reporter -> reporter.givenStories(storyPaths), givenStoriesPaths, storyPaths);
- }
- @Override
- public void afterGivenStories() {
- perform(StoryReporter::afterGivenStories, afterGivenStories);
- }
- @Override
- public void beforeExamples(List<String> steps, ExamplesTable table) {
- perform(reporter -> reporter.beforeExamples(steps, table), beforeExamples, steps, table);
- }
- @Override
- public void example(Map<String, String> tableRow, int exampleIndex) {
- perform(reporter -> reporter.example(tableRow, exampleIndex), example, tableRow, exampleIndex);
- }
- @Override
- public void afterExamples() {
- perform(StoryReporter::afterExamples, afterExamples);
- }
- @Override
- public void beforeStep(Step step) {
- perform(reporter -> reporter.beforeStep(step), beforeStep, step);
- }
- @Override
- public void successful(String step) {
- perform(reporter -> reporter.successful(step), successful, step);
- }
- @Override
- public void ignorable(String step) {
- perform(reporter -> reporter.ignorable(step), ignorable, step);
- }
- @Override
- public void comment(String step) {
- perform(reporter -> reporter.comment(step), comment, step);
- }
- @Override
- public void pending(PendingStep step) {
- perform(reporter -> reporter.pending(step), pending, step);
- }
- @Override
- public void pending(String step) {
- perform(reporter -> reporter.pending(step), pendingDeprecated, step);
- }
- @Override
- public void notPerformed(String step) {
- perform(reporter -> reporter.notPerformed(step), notPerformed, step);
- }
- @Override
- public void failed(String step, Throwable cause) {
- perform(reporter -> reporter.failed(step, cause), failed, step, cause);
- }
- @Override
- public void failedOutcomes(String step, OutcomesTable table) {
- perform(reporter -> reporter.failedOutcomes(step, table), failedOutcomes, step, table);
- }
- @Override
- public void dryRun() {
- perform(StoryReporter::dryRun, dryRun);
- }
- @Override
- public void pendingMethods(List<String> methods) {
- perform(reporter -> reporter.pendingMethods(methods), pendingMethods, methods);
- }
- @Override
- public void restarted(String step, Throwable cause) {
- perform(reporter -> reporter.restarted(step, cause), restarted, step, cause);
- }
- @Override
- public void restartedStory(Story story, Throwable cause) {
- perform(reporter -> reporter.restartedStory(story, cause), restartedStory, story, cause);
- }
- @Override
- public void storyCancelled(Story story, StoryDuration storyDuration) {
- perform(reporter -> reporter.storyCancelled(story, storyDuration), storyCancelled, story, storyDuration);
- }
- private void perform(Consumer<StoryReporter> crossReferencingInvoker, Method delayedMethod,
- Object... delayedMethodArgs) {
- crossReferencingInvoker.accept(crossReferencing);
- crossReferencingInvoker.accept(threadSafeDelegate);
- if (multiThreading) {
- delayedMethods.add(new DelayedMethod(delayedMethod, delayedMethodArgs));
- } else {
- crossReferencingInvoker.accept(delegate);
- }
- }
- public StoryReporter getDelegate() {
- return delegate;
- }
- public boolean invoked() {
- return invoked;
- }
- public void invokeDelayed() {
- if (!multiThreading) {
- return;
- }
- synchronized (delegate) {
- for (DelayedMethod delayed : delayedMethods) {
- delayed.invoke(delegate);
- }
- delayedMethods.clear();
- }
- invoked = true;
- }
- public static class DelayedMethod {
- private Method method;
- private Object[] args;
- public DelayedMethod(Method method, Object... args) {
- this.method = method;
- this.args = args;
- }
- public void invoke(StoryReporter delegate) {
- try {
- method.invoke(delegate, args);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException("" + method, e);
- }
- }
- }
- }