AbstractEmbedderMojo.java

  1. package org.jbehave.mojo;

  2. import static org.apache.commons.lang3.ArrayUtils.isNotEmpty;

  3. import java.io.File;
  4. import java.net.MalformedURLException;
  5. import java.net.URL;
  6. import java.util.ArrayList;
  7. import java.util.List;
  8. import java.util.Properties;
  9. import java.util.concurrent.ExecutorService;

  10. import org.apache.maven.plugin.AbstractMojo;
  11. import org.apache.maven.plugins.annotations.Parameter;
  12. import org.jbehave.core.ConfigurableEmbedder;
  13. import org.jbehave.core.InjectableEmbedder;
  14. import org.jbehave.core.embedder.Embedder;
  15. import org.jbehave.core.embedder.EmbedderClassLoader;
  16. import org.jbehave.core.embedder.EmbedderControls;
  17. import org.jbehave.core.embedder.EmbedderMonitor;
  18. import org.jbehave.core.embedder.MetaFilter;
  19. import org.jbehave.core.embedder.UnmodifiableEmbedderControls;
  20. import org.jbehave.core.embedder.executors.ExecutorServiceFactory;
  21. import org.jbehave.core.failures.BatchFailures;
  22. import org.jbehave.core.io.StoryFinder;
  23. import org.jbehave.core.model.Meta;
  24. import org.jbehave.core.model.Scenario;
  25. import org.jbehave.core.model.Story;
  26. import org.jbehave.core.model.StoryDuration;
  27. import org.jbehave.core.model.StoryMaps;
  28. import org.jbehave.core.reporters.ReportsCount;

  29. /**
  30.  * Abstract mojo that holds all the configuration parameters to specify and load
  31.  * stories.
  32.  */
  33. public abstract class AbstractEmbedderMojo extends AbstractMojo {

  34.     static final String TEST_SCOPE = "test";

  35.     @Parameter(defaultValue = "${project.build.sourceDirectory}", required = true)
  36.     String sourceDirectory;

  37.     @Parameter(defaultValue = "${project.build.testSourceDirectory}", required = true)
  38.     String testSourceDirectory;

  39.     @Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
  40.     String outputDirectory;

  41.     @Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
  42.     String testOutputDirectory;

  43.     /**
  44.      * The scope of the mojo classpath, either "compile" or "test"
  45.      */
  46.     @Parameter(defaultValue = "compile")
  47.     String scope;

  48.     /**
  49.      * Include filters, relative to the root source directory determined by the
  50.      * scope
  51.      */
  52.     @Parameter
  53.     List<String> includes;

  54.     /**
  55.      * Exclude filters, relative to the root source directory determined by the
  56.      * scope
  57.      */
  58.     @Parameter
  59.     List<String> excludes;

  60.     /**
  61.      * Compile classpath.
  62.      */
  63.     @Parameter(defaultValue = "${project.compileClasspathElements}", required = true, readonly = true)
  64.     List<String> compileClasspathElements;

  65.     /**
  66.      * Test classpath.
  67.      */
  68.     @Parameter(defaultValue = "${project.testClasspathElements}", required = true, readonly = true)
  69.     List<String> testClasspathElements;

  70.     /**
  71.      * The boolean flag to skip stories
  72.      */
  73.     @Parameter(defaultValue = "false")
  74.     boolean skip = false;

  75.     /**
  76.      * The boolean flag to run in batch mode
  77.      */
  78.     @Parameter(defaultValue = "false")
  79.     boolean batch = false;

  80.     /**
  81.      * The boolean flag to ignore failure in stories
  82.      */
  83.     @Parameter(defaultValue = "false")
  84.     boolean ignoreFailureInStories = false;

  85.     /**
  86.      * The boolean flag to ignore failure in view
  87.      */
  88.     @Parameter(defaultValue = "false")
  89.     boolean ignoreFailureInView = false;

  90.     /**
  91.      * The boolean flag to generate view after stories are run
  92.      */
  93.     @Parameter(defaultValue = "true")
  94.     boolean generateViewAfterStories = true;

  95.     /**
  96.      * The boolean flag to output failures in verbose mode
  97.      */
  98.     @Parameter(defaultValue = "false")
  99.     boolean verboseFailures = false;

  100.     /**
  101.      * The boolean flag to output filtering in verbose mode
  102.      */
  103.     @Parameter(defaultValue = "false")
  104.     boolean verboseFiltering = false;

  105.     /**
  106.      * The story timeouts
  107.      */
  108.     @Parameter
  109.     String storyTimeouts;

  110.     /**
  111.      * The boolean flag to fail on story timeout
  112.      */
  113.     @Parameter(defaultValue = "false")
  114.     boolean failOnStoryTimeout = false;

  115.     /**
  116.      * The number of threads
  117.      */
  118.     @Parameter(defaultValue = "1")
  119.     int threads = 1;

  120.     /**
  121.      * The embedder class
  122.      */
  123.     @Parameter(defaultValue = "org.jbehave.core.embedder.Embedder")
  124.     String embedderClass = Embedder.class.getName();

  125.     /**
  126.      * The implementation class of the {@link ExecutorServiceFactory}
  127.      */
  128.     @Parameter
  129.     String executorsClass;

  130.     /**
  131.      * The class that is injected with the embedder
  132.      */
  133.     @Parameter
  134.     String injectableEmbedderClass;

  135.     /**
  136.      * The story finder used to retrieve story paths and class names
  137.      */
  138.     @Parameter
  139.     String storyFinderClass = StoryFinder.class.getName();

  140.     /**
  141.      * The meta filters
  142.      */
  143.     @Parameter
  144.     String[] metaFilters;

  145.     /**
  146.      * The system properties
  147.      */
  148.     @Parameter
  149.     Properties systemProperties = new Properties();

  150.     /**
  151.      * The class loader
  152.      */
  153.     private EmbedderClassLoader classLoader;

  154.     /**
  155.      * Determines if the scope of the mojo classpath is "test"
  156.      *
  157.      * @return A boolean <code>true</code> if test scoped
  158.      */
  159.     boolean isTestScope() {
  160.         return TEST_SCOPE.equals(scope);
  161.     }

  162.     String searchDirectory() {
  163.         if (isTestScope()) {
  164.             return testSourceDirectory;
  165.         }
  166.         return sourceDirectory;
  167.     }

  168.     String outputDirectory() {
  169.         if (isTestScope()) {
  170.             return testOutputDirectory;
  171.         }
  172.         return outputDirectory;
  173.     }

  174.     URL codeLocation() {
  175.         String outputDirectory = outputDirectory();
  176.         try {
  177.             return outputDirectory != null ? new File(outputDirectory).toURI().toURL() : null;
  178.         } catch (MalformedURLException e) {
  179.             throw new IllegalArgumentException("Failed to create code location from " + outputDirectory, e);
  180.         }
  181.     }

  182.     /**
  183.      * Returns the EmbedderClassLoader with the classpath element of the
  184.      * selected scope.
  185.      *
  186.      * @return An EmbedderClassLoader
  187.      */
  188.     protected EmbedderClassLoader classLoader() {
  189.         if (classLoader == null) {
  190.             classLoader = new EmbedderClassLoader(classpathElements());
  191.         }
  192.         return classLoader;
  193.     }

  194.     List<String> classpathElements() {
  195.         List<String> classpathElements = compileClasspathElements;
  196.         if (isTestScope()) {
  197.             classpathElements = testClasspathElements;
  198.         }
  199.         return classpathElements;
  200.     }

  201.     /**
  202.      * Finds story paths, using the {@link #newStoryFinder()}, in the
  203.      * {@link #searchDirectory()} given specified {@link #includes} and
  204.      * {@link #excludes}.
  205.      *
  206.      * @return A List of story paths found
  207.      */
  208.     protected List<String> storyPaths() {
  209.         getLog().debug("Searching for story paths including " + includes + " and excluding " + excludes);
  210.         List<String> storyPaths = newStoryFinder().findPaths(searchDirectory(), includes, excludes);
  211.         getLog().info("Found story paths: " + storyPaths);
  212.         return storyPaths;
  213.     }

  214.     /**
  215.      * Finds class names, using the {@link #newStoryFinder()}, in the
  216.      * {@link #searchDirectory()} given specified {@link #includes} and
  217.      * {@link #excludes}.
  218.      *
  219.      * @return A List of class names found
  220.      */
  221.     protected List<String> classNames() {
  222.         getLog().debug("Searching for class names including " + includes + " and excluding " + excludes);
  223.         List<String> classNames = newStoryFinder().findClassNames(searchDirectory(), includes, excludes);
  224.         getLog().info("Found class names: " + classNames);
  225.         return classNames;
  226.     }

  227.     /**
  228.      * Creates an instance of StoryFinder, using the {@link #storyFinderClass}
  229.      *
  230.      * @return A StoryFinder
  231.      */
  232.     protected StoryFinder newStoryFinder() {
  233.         return classLoader().newInstance(StoryFinder.class, storyFinderClass);
  234.     }

  235.     /**
  236.      * Creates an instance of Embedder, either using
  237.      * {@link #injectableEmbedderClass} (if set) or defaulting to
  238.      * {@link #embedderClass}.
  239.      *
  240.      * @return An Embedder
  241.      */
  242.     protected Embedder newEmbedder() {
  243.         Embedder embedder = null;
  244.         EmbedderClassLoader classLoader = classLoader();
  245.         if (injectableEmbedderClass != null) {
  246.             embedder = classLoader.newInstance(InjectableEmbedder.class, injectableEmbedderClass).injectedEmbedder();
  247.         } else {
  248.             embedder = classLoader.newInstance(Embedder.class, embedderClass);
  249.         }
  250.        
  251.         URL codeLocation = codeLocation();
  252.         if (codeLocation != null) {
  253.             embedder.configuration().storyReporterBuilder().withCodeLocation(codeLocation);
  254.         }

  255.         embedder.useClassLoader(classLoader);
  256.         embedder.useEmbedderControls(embedderControls());
  257.         if (executorsClass != null) {
  258.             ExecutorServiceFactory executorServiceFactory = classLoader.newInstance(ExecutorServiceFactory.class,
  259.                     executorsClass);
  260.             embedder.useExecutorService(executorServiceFactory.create(embedder.embedderControls()));
  261.         }
  262.         embedder.useEmbedderMonitor(embedderMonitor());
  263.         if (isNotEmpty(metaFilters)) {
  264.             List<String> filters = new ArrayList<>();
  265.             for (String filter : metaFilters) {
  266.                 if (filter != null) {
  267.                     filters.add(filter);
  268.                 }
  269.             }
  270.             embedder.useMetaFilters(filters);
  271.         }
  272.         if (!systemProperties.isEmpty()) {
  273.             embedder.useSystemProperties(systemProperties);
  274.         }
  275.         return embedder;
  276.     }

  277.     protected EmbedderMonitor embedderMonitor() {
  278.         return new MavenEmbedderMonitor();
  279.     }

  280.     protected EmbedderControls embedderControls() {
  281.         EmbedderControls embedderControls = new EmbedderControls().doBatch(batch).doSkip(skip)
  282.                 .doGenerateViewAfterStories(generateViewAfterStories).doIgnoreFailureInStories(ignoreFailureInStories)
  283.                 .doIgnoreFailureInView(ignoreFailureInView).doVerboseFailures(verboseFailures)
  284.                 .doVerboseFiltering(verboseFiltering)
  285.                 .doFailOnStoryTimeout(failOnStoryTimeout).useThreads(threads);
  286.         if (storyTimeouts != null) {
  287.             embedderControls.useStoryTimeouts(storyTimeouts);
  288.         }        
  289.         return new UnmodifiableEmbedderControls(embedderControls);
  290.     }

  291.     protected class MavenEmbedderMonitor implements EmbedderMonitor {

  292.         @Override
  293.         public void batchFailed(BatchFailures failures) {
  294.             getLog().warn("Failed to run batch " + failures);
  295.         }

  296.         @Override
  297.         public void beforeOrAfterStoriesFailed() {
  298.             getLog().warn("Failed to run before or after stories steps");
  299.         }

  300.         @Override
  301.         public void embeddableFailed(String name, Throwable cause) {
  302.             getLog().warn("Failed to run embeddable " + name, cause);
  303.         }

  304.         @Override
  305.         public void embeddableNotConfigurable(String name) {
  306.             getLog().warn("Embeddable " + name + " must be an instance of " + ConfigurableEmbedder.class);
  307.         }

  308.         @Override
  309.         public void embeddablesSkipped(List<String> classNames) {
  310.             getLog().info("Skipped embeddables " + classNames);
  311.         }

  312.         @Override
  313.         public void metaExcluded(Meta meta, MetaFilter filter) {
  314.             getLog().debug(meta + " excluded by filter '" + filter.asString() + "'");
  315.         }

  316.         @Override
  317.         public void runningEmbeddable(String name) {
  318.             getLog().info("Running embeddable " + name);
  319.         }

  320.         @Override
  321.         public void runningStory(String path) {
  322.             getLog().info("Running story " + path);
  323.         }

  324.         @Override
  325.         public void storyFailed(String path, Throwable cause) {
  326.             getLog().warn("Failed to run story " + path, cause);
  327.         }

  328.         @Override
  329.         public void storiesSkipped(List<String> storyPaths) {
  330.             getLog().info("Skipped stories " + storyPaths);
  331.         }

  332.         @Override
  333.         public void storiesExcluded(List<Story> excluded, MetaFilter filter, boolean verbose) {
  334.             StringBuilder sb = new StringBuilder();
  335.             sb.append(excluded.size() + " stories excluded by filter: " + filter.asString() + "\n");
  336.             if (verbose) {
  337.                 for (Story story : excluded) {
  338.                     sb.append(story.getPath()).append("\n");
  339.                 }
  340.             }
  341.             getLog().info(sb.toString());
  342.         }

  343.         @Override
  344.         public void scenarioExcluded(Scenario scenario, MetaFilter filter) {
  345.             getLog().info("Scenario '" + scenario.getTitle() + "' excluded by filter: " + filter.asString() + "\n");
  346.         }

  347.         @Override
  348.         public void runningWithAnnotatedEmbedderRunner(String className) {
  349.             getLog().info("Running with AnnotatedEmbedderRunner '" + className + "'");
  350.         }

  351.         @Override
  352.         public void annotatedInstanceNotOfType(Object annotatedInstance, Class<?> type) {
  353.             getLog().warn("Annotated instance " + annotatedInstance + " not of type " + type);
  354.         }

  355.         @Override
  356.         public void generatingReportsView(File outputDirectory, List<String> formats, Properties viewProperties) {
  357.             getLog().info(
  358.                     "Generating reports view to '" + outputDirectory + "' using formats '" + formats + "'"
  359.                             + " and view properties '" + viewProperties + "'");
  360.         }

  361.         @Override
  362.         public void reportsViewGenerationFailed(File outputDirectory, List<String> formats, Properties viewProperties,
  363.                 Throwable cause) {
  364.             String message = "Failed to generate reports view to '" + outputDirectory + "' using formats '" + formats
  365.                     + "'" + " and view properties '" + viewProperties + "'";
  366.             getLog().warn(message, cause);
  367.         }

  368.         @Override
  369.         public void reportsViewGenerated(ReportsCount count) {
  370.             getLog().info(
  371.                     "Reports view generated with " + count.getStories() + " stories (of which "
  372.                             + count.getStoriesPending() + " pending) containing " + count.getScenarios()
  373.                             + " scenarios (of which " + count.getScenariosPending() + " pending)");
  374.             if (count.getStoriesExcluded() > 0 || count.getScenariosExcluded() > 0) {
  375.                 getLog().info(
  376.                         "Meta filters excluded " + count.getStoriesExcluded() + " stories and  "
  377.                                 + count.getScenariosExcluded() + " scenarios");
  378.             }
  379.         }

  380.         @Override
  381.         public void reportsViewFailures(ReportsCount count) {
  382.             getLog().warn("Failures in reports view: " + count.getScenariosFailed() + " scenarios failed");
  383.         }

  384.         @Override
  385.         public void reportsViewNotGenerated() {
  386.             getLog().info("Reports view not generated");
  387.         }

  388.         @Override
  389.         public void mappingStory(String storyPath, List<String> metaFilters) {
  390.             getLog().info("Mapping story " + storyPath + " with meta filters " + metaFilters);
  391.         }

  392.         @Override
  393.         public void generatingMapsView(File outputDirectory, StoryMaps storyMaps, Properties viewProperties) {
  394.             getLog().info(
  395.                     "Generating maps view to '" + outputDirectory + "' using story maps '" + storyMaps + "'"
  396.                             + " and view properties '" + viewProperties + "'");
  397.         }

  398.         @Override
  399.         public void mapsViewGenerationFailed(File outputDirectory, StoryMaps storyMaps, Properties viewProperties,
  400.                 Throwable cause) {
  401.             getLog().warn(
  402.                     "Failed to generate maps view to '" + outputDirectory + "' using story maps '" + storyMaps + "'"
  403.                             + " and view properties '" + viewProperties + "'", cause);
  404.         }

  405.         @Override
  406.         public void processingSystemProperties(Properties properties) {
  407.             getLog().info("Processing system properties " + properties);
  408.         }

  409.         @Override
  410.         public void systemPropertySet(String name, String value) {
  411.             getLog().info("System property '" + name + "' set to '" + value + "'");
  412.         }

  413.         @Override
  414.         public void storyTimeout(Story story, StoryDuration storyDuration) {
  415.             getLog().warn(
  416.                     "Story " + story.getPath() + " duration of " + storyDuration.getDurationInSecs()
  417.                             + " seconds has exceeded timeout of " + storyDuration.getTimeoutInSecs() + " seconds");
  418.         }

  419.         @Override
  420.         public void usingThreads(int threads) {
  421.             getLog().info("Using " + threads + " threads");
  422.         }

  423.         @Override
  424.         public void usingExecutorService(ExecutorService executorService) {
  425.             getLog().info("Using executor service " + executorService);
  426.         }

  427.         @Override
  428.         public void usingControls(EmbedderControls embedderControls) {
  429.             getLog().info("Using controls " + embedderControls);
  430.         }
  431.        
  432.         @Override
  433.         public void invalidTimeoutFormat(String path) {
  434.             getLog().warn("Failed to set specific story timeout for story " + path
  435.                     + " because 'storyTimeoutInSecsByPath' has incorrect format");
  436.             getLog().warn("'storyTimeoutInSecsByPath' must be a CSV of regex expressions matching story paths. "
  437.                     + "E.g. \"*/long/*.story:5000,*/short/*.story:200\"");
  438.         }

  439.         @Override
  440.         public void usingTimeout(String path, long timeout) {
  441.             getLog().info("Using timeout for story " + path + " of " + timeout + " secs.");
  442.         }

  443.         @Override
  444.         public String toString() {
  445.             return this.getClass().getSimpleName();
  446.         }

  447.     }
  448. }