AbstractEmbedderMojo.java
package org.jbehave.mojo;
import static org.apache.commons.lang3.ArrayUtils.isNotEmpty;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.jbehave.core.ConfigurableEmbedder;
import org.jbehave.core.InjectableEmbedder;
import org.jbehave.core.embedder.Embedder;
import org.jbehave.core.embedder.EmbedderClassLoader;
import org.jbehave.core.embedder.EmbedderControls;
import org.jbehave.core.embedder.EmbedderMonitor;
import org.jbehave.core.embedder.MetaFilter;
import org.jbehave.core.embedder.UnmodifiableEmbedderControls;
import org.jbehave.core.embedder.executors.ExecutorServiceFactory;
import org.jbehave.core.failures.BatchFailures;
import org.jbehave.core.io.StoryFinder;
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.model.StoryMaps;
import org.jbehave.core.reporters.ReportsCount;
/**
* Abstract mojo that holds all the configuration parameters to specify and load
* stories.
*/
public abstract class AbstractEmbedderMojo extends AbstractMojo {
static final String TEST_SCOPE = "test";
@Parameter(defaultValue = "${project.build.sourceDirectory}", required = true)
String sourceDirectory;
@Parameter(defaultValue = "${project.build.testSourceDirectory}", required = true)
String testSourceDirectory;
@Parameter(defaultValue = "${project.build.outputDirectory}", required = true)
String outputDirectory;
@Parameter(defaultValue = "${project.build.testOutputDirectory}", required = true)
String testOutputDirectory;
/**
* The scope of the mojo classpath, either "compile" or "test"
*/
@Parameter(defaultValue = "compile")
String scope;
/**
* Include filters, relative to the root source directory determined by the
* scope
*/
@Parameter
List<String> includes;
/**
* Exclude filters, relative to the root source directory determined by the
* scope
*/
@Parameter
List<String> excludes;
/**
* Compile classpath.
*/
@Parameter(defaultValue = "${project.compileClasspathElements}", required = true, readonly = true)
List<String> compileClasspathElements;
/**
* Test classpath.
*/
@Parameter(defaultValue = "${project.testClasspathElements}", required = true, readonly = true)
List<String> testClasspathElements;
/**
* The boolean flag to skip stories
*/
@Parameter(defaultValue = "false")
boolean skip = false;
/**
* The boolean flag to run in batch mode
*/
@Parameter(defaultValue = "false")
boolean batch = false;
/**
* The boolean flag to ignore failure in stories
*/
@Parameter(defaultValue = "false")
boolean ignoreFailureInStories = false;
/**
* The boolean flag to ignore failure in view
*/
@Parameter(defaultValue = "false")
boolean ignoreFailureInView = false;
/**
* The boolean flag to generate view after stories are run
*/
@Parameter(defaultValue = "true")
boolean generateViewAfterStories = true;
/**
* The boolean flag to output failures in verbose mode
*/
@Parameter(defaultValue = "false")
boolean verboseFailures = false;
/**
* The boolean flag to output filtering in verbose mode
*/
@Parameter(defaultValue = "false")
boolean verboseFiltering = false;
/**
* The story timeouts
*/
@Parameter
String storyTimeouts;
/**
* The boolean flag to fail on story timeout
*/
@Parameter(defaultValue = "false")
boolean failOnStoryTimeout = false;
/**
* The number of threads
*/
@Parameter(defaultValue = "1")
int threads = 1;
/**
* The embedder class
*/
@Parameter(defaultValue = "org.jbehave.core.embedder.Embedder")
String embedderClass = Embedder.class.getName();
/**
* The implementation class of the {@link ExecutorServiceFactory}
*/
@Parameter
String executorsClass;
/**
* The class that is injected with the embedder
*/
@Parameter
String injectableEmbedderClass;
/**
* The story finder used to retrieve story paths and class names
*/
@Parameter
String storyFinderClass = StoryFinder.class.getName();
/**
* The meta filters
*/
@Parameter
String[] metaFilters;
/**
* The system properties
*/
@Parameter
Properties systemProperties = new Properties();
/**
* The class loader
*/
private EmbedderClassLoader classLoader;
/**
* Determines if the scope of the mojo classpath is "test"
*
* @return A boolean <code>true</code> if test scoped
*/
boolean isTestScope() {
return TEST_SCOPE.equals(scope);
}
String searchDirectory() {
if (isTestScope()) {
return testSourceDirectory;
}
return sourceDirectory;
}
String outputDirectory() {
if (isTestScope()) {
return testOutputDirectory;
}
return outputDirectory;
}
URL codeLocation() {
String outputDirectory = outputDirectory();
try {
return outputDirectory != null ? new File(outputDirectory).toURI().toURL() : null;
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Failed to create code location from " + outputDirectory, e);
}
}
/**
* Returns the EmbedderClassLoader with the classpath element of the
* selected scope.
*
* @return An EmbedderClassLoader
*/
protected EmbedderClassLoader classLoader() {
if (classLoader == null) {
classLoader = new EmbedderClassLoader(classpathElements());
}
return classLoader;
}
List<String> classpathElements() {
List<String> classpathElements = compileClasspathElements;
if (isTestScope()) {
classpathElements = testClasspathElements;
}
return classpathElements;
}
/**
* Finds story paths, using the {@link #newStoryFinder()}, in the
* {@link #searchDirectory()} given specified {@link #includes} and
* {@link #excludes}.
*
* @return A List of story paths found
*/
protected List<String> storyPaths() {
getLog().debug("Searching for story paths including " + includes + " and excluding " + excludes);
List<String> storyPaths = newStoryFinder().findPaths(searchDirectory(), includes, excludes);
getLog().info("Found story paths: " + storyPaths);
return storyPaths;
}
/**
* Finds class names, using the {@link #newStoryFinder()}, in the
* {@link #searchDirectory()} given specified {@link #includes} and
* {@link #excludes}.
*
* @return A List of class names found
*/
protected List<String> classNames() {
getLog().debug("Searching for class names including " + includes + " and excluding " + excludes);
List<String> classNames = newStoryFinder().findClassNames(searchDirectory(), includes, excludes);
getLog().info("Found class names: " + classNames);
return classNames;
}
/**
* Creates an instance of StoryFinder, using the {@link #storyFinderClass}
*
* @return A StoryFinder
*/
protected StoryFinder newStoryFinder() {
return classLoader().newInstance(StoryFinder.class, storyFinderClass);
}
/**
* Creates an instance of Embedder, either using
* {@link #injectableEmbedderClass} (if set) or defaulting to
* {@link #embedderClass}.
*
* @return An Embedder
*/
protected Embedder newEmbedder() {
Embedder embedder = null;
EmbedderClassLoader classLoader = classLoader();
if (injectableEmbedderClass != null) {
embedder = classLoader.newInstance(InjectableEmbedder.class, injectableEmbedderClass).injectedEmbedder();
} else {
embedder = classLoader.newInstance(Embedder.class, embedderClass);
}
URL codeLocation = codeLocation();
if (codeLocation != null) {
embedder.configuration().storyReporterBuilder().withCodeLocation(codeLocation);
}
embedder.useClassLoader(classLoader);
embedder.useEmbedderControls(embedderControls());
if (executorsClass != null) {
ExecutorServiceFactory executorServiceFactory = classLoader.newInstance(ExecutorServiceFactory.class,
executorsClass);
embedder.useExecutorService(executorServiceFactory.create(embedder.embedderControls()));
}
embedder.useEmbedderMonitor(embedderMonitor());
if (isNotEmpty(metaFilters)) {
List<String> filters = new ArrayList<>();
for (String filter : metaFilters) {
if (filter != null) {
filters.add(filter);
}
}
embedder.useMetaFilters(filters);
}
if (!systemProperties.isEmpty()) {
embedder.useSystemProperties(systemProperties);
}
return embedder;
}
protected EmbedderMonitor embedderMonitor() {
return new MavenEmbedderMonitor();
}
protected EmbedderControls embedderControls() {
EmbedderControls embedderControls = new EmbedderControls().doBatch(batch).doSkip(skip)
.doGenerateViewAfterStories(generateViewAfterStories).doIgnoreFailureInStories(ignoreFailureInStories)
.doIgnoreFailureInView(ignoreFailureInView).doVerboseFailures(verboseFailures)
.doVerboseFiltering(verboseFiltering)
.doFailOnStoryTimeout(failOnStoryTimeout).useThreads(threads);
if (storyTimeouts != null) {
embedderControls.useStoryTimeouts(storyTimeouts);
}
return new UnmodifiableEmbedderControls(embedderControls);
}
protected class MavenEmbedderMonitor implements EmbedderMonitor {
@Override
public void batchFailed(BatchFailures failures) {
getLog().warn("Failed to run batch " + failures);
}
@Override
public void beforeOrAfterStoriesFailed() {
getLog().warn("Failed to run before or after stories steps");
}
@Override
public void embeddableFailed(String name, Throwable cause) {
getLog().warn("Failed to run embeddable " + name, cause);
}
@Override
public void embeddableNotConfigurable(String name) {
getLog().warn("Embeddable " + name + " must be an instance of " + ConfigurableEmbedder.class);
}
@Override
public void embeddablesSkipped(List<String> classNames) {
getLog().info("Skipped embeddables " + classNames);
}
@Override
public void metaExcluded(Meta meta, MetaFilter filter) {
getLog().debug(meta + " excluded by filter '" + filter.asString() + "'");
}
@Override
public void runningEmbeddable(String name) {
getLog().info("Running embeddable " + name);
}
@Override
public void runningStory(String path) {
getLog().info("Running story " + path);
}
@Override
public void storyFailed(String path, Throwable cause) {
getLog().warn("Failed to run story " + path, cause);
}
@Override
public void storiesSkipped(List<String> storyPaths) {
getLog().info("Skipped stories " + storyPaths);
}
@Override
public void storiesExcluded(List<Story> excluded, MetaFilter filter, boolean verbose) {
StringBuilder sb = new StringBuilder();
sb.append(excluded.size() + " stories excluded by filter: " + filter.asString() + "\n");
if (verbose) {
for (Story story : excluded) {
sb.append(story.getPath()).append("\n");
}
}
getLog().info(sb.toString());
}
@Override
public void scenarioExcluded(Scenario scenario, MetaFilter filter) {
getLog().info("Scenario '" + scenario.getTitle() + "' excluded by filter: " + filter.asString() + "\n");
}
@Override
public void runningWithAnnotatedEmbedderRunner(String className) {
getLog().info("Running with AnnotatedEmbedderRunner '" + className + "'");
}
@Override
public void annotatedInstanceNotOfType(Object annotatedInstance, Class<?> type) {
getLog().warn("Annotated instance " + annotatedInstance + " not of type " + type);
}
@Override
public void generatingReportsView(File outputDirectory, List<String> formats, Properties viewProperties) {
getLog().info(
"Generating reports view to '" + outputDirectory + "' using formats '" + formats + "'"
+ " and view properties '" + viewProperties + "'");
}
@Override
public void reportsViewGenerationFailed(File outputDirectory, List<String> formats, Properties viewProperties,
Throwable cause) {
String message = "Failed to generate reports view to '" + outputDirectory + "' using formats '" + formats
+ "'" + " and view properties '" + viewProperties + "'";
getLog().warn(message, cause);
}
@Override
public void reportsViewGenerated(ReportsCount count) {
getLog().info(
"Reports view generated with " + count.getStories() + " stories (of which "
+ count.getStoriesPending() + " pending) containing " + count.getScenarios()
+ " scenarios (of which " + count.getScenariosPending() + " pending)");
if (count.getStoriesExcluded() > 0 || count.getScenariosExcluded() > 0) {
getLog().info(
"Meta filters excluded " + count.getStoriesExcluded() + " stories and "
+ count.getScenariosExcluded() + " scenarios");
}
}
@Override
public void reportsViewFailures(ReportsCount count) {
getLog().warn("Failures in reports view: " + count.getScenariosFailed() + " scenarios failed");
}
@Override
public void reportsViewNotGenerated() {
getLog().info("Reports view not generated");
}
@Override
public void mappingStory(String storyPath, List<String> metaFilters) {
getLog().info("Mapping story " + storyPath + " with meta filters " + metaFilters);
}
@Override
public void generatingMapsView(File outputDirectory, StoryMaps storyMaps, Properties viewProperties) {
getLog().info(
"Generating maps view to '" + outputDirectory + "' using story maps '" + storyMaps + "'"
+ " and view properties '" + viewProperties + "'");
}
@Override
public void mapsViewGenerationFailed(File outputDirectory, StoryMaps storyMaps, Properties viewProperties,
Throwable cause) {
getLog().warn(
"Failed to generate maps view to '" + outputDirectory + "' using story maps '" + storyMaps + "'"
+ " and view properties '" + viewProperties + "'", cause);
}
@Override
public void processingSystemProperties(Properties properties) {
getLog().info("Processing system properties " + properties);
}
@Override
public void systemPropertySet(String name, String value) {
getLog().info("System property '" + name + "' set to '" + value + "'");
}
@Override
public void storyTimeout(Story story, StoryDuration storyDuration) {
getLog().warn(
"Story " + story.getPath() + " duration of " + storyDuration.getDurationInSecs()
+ " seconds has exceeded timeout of " + storyDuration.getTimeoutInSecs() + " seconds");
}
@Override
public void usingThreads(int threads) {
getLog().info("Using " + threads + " threads");
}
@Override
public void usingExecutorService(ExecutorService executorService) {
getLog().info("Using executor service " + executorService);
}
@Override
public void usingControls(EmbedderControls embedderControls) {
getLog().info("Using controls " + embedderControls);
}
@Override
public void invalidTimeoutFormat(String path) {
getLog().warn("Failed to set specific story timeout for story " + path
+ " because 'storyTimeoutInSecsByPath' has incorrect format");
getLog().warn("'storyTimeoutInSecsByPath' must be a CSV of regex expressions matching story paths. "
+ "E.g. \"*/long/*.story:5000,*/short/*.story:200\"");
}
@Override
public void usingTimeout(String path, long timeout) {
getLog().info("Using timeout for story " + path + " of " + timeout + " secs.");
}
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
}