ConcurrentStoryReporter.java

  1. package org.jbehave.core.reporters;

  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.function.Consumer;
  9. import java.util.stream.Collectors;

  10. import org.jbehave.core.model.ExamplesTable;
  11. import org.jbehave.core.model.GivenStories;
  12. import org.jbehave.core.model.Lifecycle;
  13. import org.jbehave.core.model.Lifecycle.ExecutionType;
  14. import org.jbehave.core.model.Narrative;
  15. import org.jbehave.core.model.OutcomesTable;
  16. import org.jbehave.core.model.Scenario;
  17. import org.jbehave.core.model.Step;
  18. import org.jbehave.core.model.Story;
  19. import org.jbehave.core.model.StoryDuration;
  20. import org.jbehave.core.steps.StepCollector.Stage;
  21. import org.jbehave.core.steps.StepCreator.PendingStep;
  22. import org.jbehave.core.steps.Timing;

  23. /**
  24.  * When running a multithreading mode, reports cannot be written concurrently but should
  25.  * be delayed and invoked only at the end of a story, ensuring synchronization on the delegate
  26.  * responsible for the reporting.
  27.  */
  28. public class ConcurrentStoryReporter implements StoryReporter {

  29.     private static Method beforeStoriesSteps;
  30.     private static Method afterStoriesSteps;
  31.     private static Method storyCancelled;
  32.     private static Method storyExcluded;
  33.     private static Method beforeStory;
  34.     private static Method afterStory;
  35.     private static Method narrative;
  36.     private static Method lifecycle;
  37.     private static Method beforeStorySteps;
  38.     private static Method afterStorySteps;
  39.     private static Method beforeScenarioSteps;
  40.     private static Method afterScenarioSteps;
  41.     private static Method beforeComposedSteps;
  42.     private static Method afterComposedSteps;
  43.     private static Method scenarioExcluded;
  44.     private static Method beforeScenarios;
  45.     private static Method beforeScenario;
  46.     private static Method afterScenario;
  47.     private static Method afterScenarios;
  48.     private static Method beforeGivenStories;
  49.     private static Method givenStories;
  50.     private static Method givenStoriesPaths;
  51.     private static Method afterGivenStories;
  52.     private static Method beforeExamples;
  53.     private static Method example;
  54.     private static Method afterExamples;
  55.     private static Method beforeStep;
  56.     private static Method successful;
  57.     private static Method ignorable;
  58.     private static Method comment;
  59.     private static Method pending;
  60.     private static Method pendingDeprecated;
  61.     private static Method notPerformed;
  62.     private static Method failed;
  63.     private static Method failedOutcomes;
  64.     private static Method dryRun;
  65.     private static Method pendingMethods;
  66.     private static Method restarted;
  67.     private static Method restartedStory;

  68.     static {
  69.         try {
  70.             beforeStoriesSteps = StoryReporter.class.getMethod("beforeStoriesSteps", Stage.class);
  71.             afterStoriesSteps = StoryReporter.class.getMethod("afterStoriesSteps", Stage.class);
  72.             storyCancelled = StoryReporter.class.getMethod("storyCancelled", Story.class, StoryDuration.class);
  73.             storyExcluded = StoryReporter.class.getMethod("storyExcluded", Story.class, String.class);
  74.             beforeStory = StoryReporter.class.getMethod("beforeStory", Story.class, Boolean.TYPE);
  75.             afterStory = StoryReporter.class.getMethod("afterStory", Boolean.TYPE);
  76.             narrative = StoryReporter.class.getMethod("narrative", Narrative.class);
  77.             lifecycle = StoryReporter.class.getMethod("lifecycle", Lifecycle.class);
  78.             beforeStorySteps = StoryReporter.class.getMethod("beforeStorySteps", Stage.class, ExecutionType.class);
  79.             afterStorySteps = StoryReporter.class.getMethod("afterStorySteps", Stage.class, ExecutionType.class);
  80.             beforeScenarioSteps = StoryReporter.class.getMethod("beforeScenarioSteps", Stage.class,
  81.                     ExecutionType.class);
  82.             afterScenarioSteps = StoryReporter.class.getMethod("afterScenarioSteps", Stage.class, ExecutionType.class);
  83.             beforeComposedSteps = StoryReporter.class.getMethod("beforeComposedSteps");
  84.             afterComposedSteps = StoryReporter.class.getMethod("afterComposedSteps");
  85.             scenarioExcluded = StoryReporter.class.getMethod("scenarioExcluded", Scenario.class, String.class);
  86.             beforeScenarios = StoryReporter.class.getMethod("beforeScenarios");
  87.             beforeScenario = StoryReporter.class.getMethod("beforeScenario", Scenario.class);
  88.             afterScenario = StoryReporter.class.getMethod("afterScenario", Timing.class);
  89.             afterScenarios = StoryReporter.class.getMethod("afterScenarios");
  90.             beforeGivenStories = StoryReporter.class.getMethod("beforeGivenStories");
  91.             givenStories = StoryReporter.class.getMethod("givenStories", GivenStories.class);
  92.             givenStoriesPaths = StoryReporter.class.getMethod("givenStories", List.class);
  93.             afterGivenStories = StoryReporter.class.getMethod("afterGivenStories");
  94.             beforeExamples = StoryReporter.class.getMethod("beforeExamples", List.class, ExamplesTable.class);
  95.             example = StoryReporter.class.getMethod("example", Map.class, int.class);
  96.             afterExamples = StoryReporter.class.getMethod("afterExamples");
  97.             beforeStep = StoryReporter.class.getMethod("beforeStep", Step.class);
  98.             successful = StoryReporter.class.getMethod("successful", String.class);
  99.             ignorable = StoryReporter.class.getMethod("ignorable", String.class);
  100.             comment = StoryReporter.class.getMethod("comment", String.class);
  101.             pending = StoryReporter.class.getMethod("pending", PendingStep.class);
  102.             pendingDeprecated = StoryReporter.class.getMethod("pending", String.class);
  103.             notPerformed = StoryReporter.class.getMethod("notPerformed", String.class);
  104.             failed = StoryReporter.class.getMethod("failed", String.class, Throwable.class);
  105.             failedOutcomes = StoryReporter.class.getMethod("failedOutcomes", String.class, OutcomesTable.class);
  106.             dryRun = StoryReporter.class.getMethod("dryRun");
  107.             pendingMethods = StoryReporter.class.getMethod("pendingMethods", List.class);
  108.             restarted = StoryReporter.class.getMethod("restarted", String.class, Throwable.class);
  109.             restartedStory = StoryReporter.class.getMethod("restartedStory", Story.class, Throwable.class);
  110.         } catch (NoSuchMethodException e) {
  111.             throw new RuntimeException(e);
  112.         }
  113.     }

  114.     private final StoryReporter crossReferencing;
  115.     private final StoryReporter delegate;
  116.     private final StoryReporter threadSafeDelegate;
  117.     private final boolean multiThreading;
  118.     private final List<DelayedMethod> delayedMethods;
  119.     private boolean invoked = false;

  120.     public ConcurrentStoryReporter(StoryReporter crossReferencing, List<StoryReporter> delegates,
  121.             boolean multiThreading) {
  122.         this.crossReferencing = crossReferencing;
  123.         List<StoryReporter> reporters = new ArrayList<>(delegates);
  124.         List<StoryReporter> threadSafeDelegates = reporters.stream()
  125.                                                            .filter(ThreadSafeReporter.class::isInstance)
  126.                                                            .collect(Collectors.toList());
  127.         this.threadSafeDelegate = new DelegatingStoryReporter(threadSafeDelegates);
  128.         reporters.removeAll(threadSafeDelegates);
  129.         this.delegate = new DelegatingStoryReporter(reporters);
  130.         this.multiThreading = multiThreading;
  131.         this.delayedMethods = multiThreading ? Collections.synchronizedList(new ArrayList<DelayedMethod>()) : null;
  132.     }

  133.     @Override
  134.     public void beforeStoriesSteps(Stage stage) {
  135.         perform(reporter ->  reporter.beforeStoriesSteps(stage), beforeStoriesSteps, stage);
  136.     }

  137.     @Override
  138.     public void afterStoriesSteps(Stage stage) {
  139.         perform(reporter ->  reporter.afterStoriesSteps(stage), afterStoriesSteps, stage);
  140.     }

  141.     @Override
  142.     public void storyExcluded(Story story, String filter) {
  143.         perform(reporter -> reporter.storyExcluded(story, filter), storyExcluded, story, filter);
  144.     }

  145.     @Override
  146.     public void beforeStory(Story story, boolean givenStory) {
  147.         perform(reporter ->  reporter.beforeStory(story, givenStory), beforeStory, story, givenStory);
  148.     }

  149.     @Override
  150.     public void afterStory(boolean givenStory) {
  151.         perform(reporter ->  reporter.afterStory(givenStory), afterStory, givenStory);
  152.     }

  153.     @Override
  154.     public void narrative(Narrative narrative) {
  155.         perform(reporter ->  reporter.narrative(narrative), ConcurrentStoryReporter.narrative, narrative);
  156.     }
  157.    
  158.     @Override
  159.     public void lifecycle(Lifecycle lifecycle) {
  160.         perform(reporter ->  reporter.lifecycle(lifecycle), ConcurrentStoryReporter.lifecycle, lifecycle);
  161.     }

  162.     @Override
  163.     public void beforeStorySteps(Stage stage, Lifecycle.ExecutionType type) {
  164.         perform(reporter ->  reporter.beforeStorySteps(stage, type), beforeStorySteps, stage, type);
  165.     }

  166.     @Override
  167.     public void afterStorySteps(Stage stage, Lifecycle.ExecutionType type) {
  168.         perform(reporter ->  reporter.afterStorySteps(stage, type), afterStorySteps, stage, type);
  169.     }

  170.     @Override
  171.     public void beforeComposedSteps() {
  172.         perform(StoryReporter::beforeComposedSteps, beforeComposedSteps);
  173.     }

  174.     @Override
  175.     public void afterComposedSteps() {
  176.         perform(StoryReporter::afterComposedSteps, afterComposedSteps);
  177.     }

  178.     @Override
  179.     public void beforeScenarioSteps(Stage stage, Lifecycle.ExecutionType type) {
  180.         perform(reporter ->  reporter.beforeScenarioSteps(stage, type), beforeScenarioSteps, stage, type);
  181.     }

  182.     @Override
  183.     public void afterScenarioSteps(Stage stage, Lifecycle.ExecutionType type) {
  184.         perform(reporter ->  reporter.afterScenarioSteps(stage, type), afterScenarioSteps, stage, type);
  185.     }

  186.     @Override
  187.     public void scenarioExcluded(Scenario scenario, String filter) {
  188.         perform(reporter ->  reporter.scenarioExcluded(scenario, filter), scenarioExcluded, scenario, filter);
  189.     }

  190.     @Override
  191.     public void beforeScenarios() {
  192.         perform(StoryReporter::beforeScenarios, beforeScenarios);
  193.     }

  194.     @Override
  195.     public void beforeScenario(Scenario scenario) {
  196.         perform(reporter ->  reporter.beforeScenario(scenario), beforeScenario, scenario);
  197.     }

  198.     @Override
  199.     public void afterScenario(Timing timing) {
  200.         perform(reporter -> reporter.afterScenario(timing), afterScenario, timing);
  201.     }

  202.     @Override
  203.     public void afterScenarios() {
  204.         perform(StoryReporter::afterScenarios, afterScenarios);
  205.     }

  206.     @Override
  207.     public void beforeGivenStories() {
  208.         perform(StoryReporter::beforeGivenStories, beforeGivenStories);
  209.     }

  210.     @Override
  211.     public void givenStories(GivenStories stories) {
  212.         perform(reporter ->  reporter.givenStories(stories), givenStories, stories);
  213.     }

  214.     @Override
  215.     public void givenStories(List<String> storyPaths) {
  216.         perform(reporter ->  reporter.givenStories(storyPaths), givenStoriesPaths, storyPaths);
  217.     }

  218.     @Override
  219.     public void afterGivenStories() {
  220.         perform(StoryReporter::afterGivenStories, afterGivenStories);
  221.     }

  222.     @Override
  223.     public void beforeExamples(List<String> steps, ExamplesTable table) {
  224.         perform(reporter ->  reporter.beforeExamples(steps, table), beforeExamples, steps, table);
  225.     }

  226.     @Override
  227.     public void example(Map<String, String> tableRow, int exampleIndex) {
  228.         perform(reporter ->  reporter.example(tableRow, exampleIndex), example, tableRow, exampleIndex);
  229.     }

  230.     @Override
  231.     public void afterExamples() {
  232.         perform(StoryReporter::afterExamples, afterExamples);
  233.     }

  234.     @Override
  235.     public void beforeStep(Step step) {
  236.         perform(reporter ->  reporter.beforeStep(step), beforeStep, step);
  237.     }

  238.     @Override
  239.     public void successful(String step) {
  240.         perform(reporter ->  reporter.successful(step), successful, step);
  241.     }

  242.     @Override
  243.     public void ignorable(String step) {
  244.         perform(reporter ->  reporter.ignorable(step), ignorable, step);
  245.     }

  246.     @Override
  247.     public void comment(String step) {
  248.         perform(reporter ->  reporter.comment(step), comment, step);
  249.     }

  250.     @Override
  251.     public void pending(PendingStep step) {
  252.         perform(reporter ->  reporter.pending(step), pending, step);
  253.     }

  254.     @Override
  255.     public void pending(String step) {
  256.         perform(reporter ->  reporter.pending(step), pendingDeprecated, step);
  257.     }

  258.     @Override
  259.     public void notPerformed(String step) {
  260.         perform(reporter ->  reporter.notPerformed(step), notPerformed, step);
  261.     }

  262.     @Override
  263.     public void failed(String step, Throwable cause) {
  264.         perform(reporter ->  reporter.failed(step, cause), failed, step, cause);
  265.     }

  266.     @Override
  267.     public void failedOutcomes(String step, OutcomesTable table) {
  268.         perform(reporter ->  reporter.failedOutcomes(step, table), failedOutcomes, step, table);
  269.     }

  270.     @Override
  271.     public void dryRun() {
  272.         perform(StoryReporter::dryRun, dryRun);
  273.     }

  274.     @Override
  275.     public void pendingMethods(List<String> methods) {
  276.         perform(reporter ->  reporter.pendingMethods(methods), pendingMethods, methods);
  277.     }
  278.    
  279.     @Override
  280.     public void restarted(String step, Throwable cause) {
  281.         perform(reporter ->  reporter.restarted(step, cause), restarted, step, cause);
  282.     }
  283.    
  284.     @Override
  285.     public void restartedStory(Story story, Throwable cause) {
  286.         perform(reporter ->  reporter.restartedStory(story, cause), restartedStory, story, cause);
  287.     }

  288.     @Override
  289.     public void storyCancelled(Story story, StoryDuration storyDuration) {
  290.         perform(reporter ->  reporter.storyCancelled(story, storyDuration), storyCancelled, story, storyDuration);
  291.     }

  292.     private void perform(Consumer<StoryReporter> crossReferencingInvoker, Method delayedMethod,
  293.             Object... delayedMethodArgs) {
  294.         crossReferencingInvoker.accept(crossReferencing);
  295.         crossReferencingInvoker.accept(threadSafeDelegate);
  296.         if (multiThreading) {
  297.             delayedMethods.add(new DelayedMethod(delayedMethod, delayedMethodArgs));
  298.         } else {
  299.             crossReferencingInvoker.accept(delegate);
  300.         }
  301.     }

  302.     public StoryReporter getDelegate() {
  303.         return delegate;
  304.     }

  305.     public boolean invoked() {
  306.         return invoked;
  307.     }
  308.    
  309.     public void invokeDelayed() {
  310.         if (!multiThreading) {
  311.             return;
  312.         }
  313.         synchronized (delegate) {
  314.             for (DelayedMethod delayed : delayedMethods) {
  315.                 delayed.invoke(delegate);
  316.             }
  317.             delayedMethods.clear();
  318.         }
  319.         invoked = true;
  320.     }

  321.     public static class DelayedMethod {
  322.         private Method method;
  323.         private Object[] args;

  324.         public DelayedMethod(Method method, Object... args) {
  325.             this.method = method;
  326.             this.args = args;
  327.         }

  328.         public void invoke(StoryReporter delegate) {
  329.             try {
  330.                 method.invoke(delegate, args);
  331.             } catch (IllegalAccessException e) {
  332.                 throw new RuntimeException(e);
  333.             } catch (InvocationTargetException e) {
  334.                 throw new RuntimeException(e);
  335.             } catch (IllegalArgumentException e) {
  336.                 throw new RuntimeException("" + method, e);
  337.             }
  338.         }
  339.     }
  340. }