
com.github.andreptb.fitnessemavenrunner.FitnesseRunnerMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fitnesse-maven-runner-plugin Show documentation
Show all versions of fitnesse-maven-runner-plugin Show documentation
Maven plugin to run FitNesse testing suite.
The newest version!
package com.github.andreptb.fitnessemavenrunner;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Range;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import fitnesse.ConfigurationParameter;
import fitnesse.ContextConfigurator;
import fitnesseMain.FitNesseMain;
/**
* Plugin allow configuration to run FitNesse, resembling
* regular FitNesse command line
* but providing extra features such as classpath configuration and so on.
*/
@Mojo(name = "run", requiresDependencyResolution = ResolutionScope.TEST)
public class FitnesseRunnerMojo extends AbstractMojo {
/**
* If no port configuration is supplied, the first obtained port will be used, starting from the constant value below
*/
private static final Range PORT_RANGE = Range.between(8000, 9000);
/**
* Reference to maven project
*/
@Parameter(defaultValue = "${project}", readonly = true)
private MavenProject project;
/**
* Reference to the current maven session
*/
@Parameter(defaultValue = "${plugin}", readonly = true)
private PluginDescriptor plugin;
@Parameter(defaultValue = "${plugin.artifacts}", readonly = true)
private Collection pluginClasspath;
/**
* Reference to the current maven session
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession mavenSession;
/**
* FitNesse port to start in WEB mode. Defaults to 8000
*/
@Parameter(property = "fitnesse.port")
private Integer port;
/**
* The directory in which FitNesse expects to find its page root.
*/
@Parameter(property = "fitnesse.rootPath")
private String rootPath;
/**
* The directory in which FitNesse looks for top level pages.
*/
@Parameter(property = "fitnesse.fitNesseRoot", defaultValue = "FitNesseRoot")
private String fitNesseRoot;
/**
* Allows setting a custom contextRoot to the application: http://[host][contextRoot]
*/
@Parameter(property = "fitnesse.contextRoot")
private String contextRoot;
/**
* Creates a variable to be used by fitnesse containing the classpath available along the run command.
*/
@Parameter(property = "fitnesse.classpath.variable", defaultValue = "FITNESSE_CLASSPATH")
private String fitnesseClasspathVariable;
/**
* If informed, will run the command and then exit, useful to run tests and then exit. For example: mvn fitnesserunner:run -Dfitnesse.command=SuiteToRun?suite&format=text
*
* Check here for all available options.
* Note that only resource?responder&inputs part of the URL needs be informed
*/
@Parameter(property = "fitnesse.command")
private String command;
/**
* Redirect command output. This is most useful in conjunction with the {@link #command} option
*/
@Parameter(property = "fitnesse.redirectOutput")
private String redirectOutput;
/**
* Indicates if the project dependencies should be used when executing the main class.
*/
@Parameter(property = "fitnesse.includeProjectDependencies", defaultValue = "false")
private boolean includeProjectDependencies;
/**
* Allows setting a custom theme
*/
@Parameter(property = "fitnesse.theme")
private String theme;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
Collection classpath = determineClasspath();
execute(configuration(classpath), classpath);
}
private ContextConfigurator configuration(Collection classpath) {
ContextConfigurator config = ContextConfigurator.systemDefaults();
withParameter(config, ConfigurationParameter.ROOT_PATH, determineRootPath());
withParameter(config, ConfigurationParameter.ROOT_DIRECTORY, this.fitNesseRoot);
withParameter(config, ConfigurationParameter.CONTEXT_ROOT, this.contextRoot);
withParameter(config, ConfigurationParameter.COMMAND, this.command);
withParameter(config, ConfigurationParameter.OUTPUT, this.redirectOutput);
withParameter(config, ConfigurationParameter.PORT, determinePort());
withParameter(config, this.fitnesseClasspathVariable, StringUtils.join(classpath, SystemUtils.PATH_SEPARATOR));
withParameter(config, "Theme", this.theme);
exposeMavenProperties(config);
// TODO: still not compatible with fitnesse updates nor install only
withParameter(config, ConfigurationParameter.OMITTING_UPDATES, true);
return config;
}
private void exposeMavenProperties(ContextConfigurator config) {
BiConsumer super Object, ? super Object> propertyCollector = (key, value) -> withParameter(config, key, value);
this.mavenSession.getCurrentProject().getProperties().forEach(propertyCollector);
this.mavenSession.getUserProperties().forEach((key, value) -> withParameter(config, key, value));
}
private void withParameter(ContextConfigurator config, Object key, Object value) {
String valueString = Objects.toString(value, null);
if (StringUtils.isBlank(valueString)) {
return;
}
if (key instanceof ConfigurationParameter) {
config.withParameter((ConfigurationParameter) key, valueString);
} else {
config.withParameter(Objects.toString(key), valueString);
}
getLog().debug(String.format("%s: %s", key, valueString));
}
private String determineRootPath() {
if (StringUtils.isNotBlank(this.rootPath)) {
return Paths.get(this.rootPath).normalize().toString();
}
Path basedir = this.project.getBasedir().toPath();
try {
Stream fitNesseRootSearcher = Files.walk(basedir).filter(t -> t.toFile().isDirectory() && t.endsWith(FitnesseRunnerMojo.this.fitNesseRoot));
return fitNesseRootSearcher.findFirst().get().getParent().toString();
} catch (NoSuchElementException | IOException e) {
getLog().debug("Failed to find a valid FitNesseRoot directory under: " + basedir, e);
}
return null;
}
private int determinePort() {
return ObjectUtils.defaultIfNull(this.port, determinePort(FitnesseRunnerMojo.PORT_RANGE.getMinimum()));
}
private int determinePort(Integer port) {
if (!FitnesseRunnerMojo.PORT_RANGE.contains(port)) {
throw new IllegalStateException("Unable to find an available port to run FitNesse within the range: " + FitnesseRunnerMojo.PORT_RANGE);
}
try (ServerSocket socket = new ServerSocket(port)) {
return port;
} catch (IOException e) {
return determinePort(port + NumberUtils.INTEGER_ONE);
}
}
private Collection determineClasspath() {
Map> classpathMap = new HashMap<>();
collectFileFromArtifacts(classpathMap, this.pluginClasspath);
Collection classpath = new ArrayList<>();
if (this.includeProjectDependencies) {
collectFileFromArtifacts(classpathMap, this.project.getArtifacts());
classpath.add(this.project.getBuild().getOutputDirectory() + File.separator);
classpath.add(this.project.getBuild().getTestOutputDirectory() + File.separator);
}
classpath.addAll(classpathMap.values().stream().map(versionAndFile -> versionAndFile.getValue()).collect(Collectors.toList()));
return classpath;
}
private void collectFileFromArtifacts(Map> artifactMap, Collection artifacts) {
artifacts.stream().forEach(artifact -> {
String artifactKey = String.format("%s%s", artifact.getGroupId(), artifact.getArtifactId());
Pair entry = artifactMap.get(artifactKey);
try {
if (entry == null || entry.getKey().compareTo(artifact.getSelectedVersion()) < NumberUtils.INTEGER_ZERO) {
artifactMap.put(artifactKey, Pair.of(artifact.getSelectedVersion(), artifact.getFile().getAbsolutePath()));
}
} catch (OverConstrainedVersionException e) {
getLog().warn("Failed to compare artifact versions: " + artifact, e);
}
});
}
private void execute(ContextConfigurator config, Collection classpath) throws MojoExecutionException {
Integer result = null;
try {
Collection previousThreadsSnapshot = new HashSet<>(Thread.getAllStackTraces().keySet());
URL[] classpathUrl = new URL[classpath.size()];
int i = 0;
for (String entry : classpath) {
classpathUrl[i++] = new File(entry).toURI().toURL();
}
Thread.currentThread().setContextClassLoader(new URLClassLoader(classpathUrl, getClass().getClassLoader()));
result = new FitNesseMain().launchFitNesse(config);
// no result means web execution mode
if (result == null) {
Thread.getAllStackTraces().keySet().stream().filter(thread -> !previousThreadsSnapshot.contains(thread)).findFirst().get().join();
return;
}
} catch (Exception e) {
throw new MojoExecutionException(e.getMessage(), ExceptionUtils.getRootCause(e));
}
if (result > NumberUtils.INTEGER_ZERO) {
throw new MojoExecutionException(result + " FitNesse tests failed to execute");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy