Java tutorial
/* * Stefano Maestri, javalinuxlabs.org Copyright 2008, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package it.javalinux.sibilla.plugins; import it.javalinux.sibilla.plugins.MavenLogStreamConsumer.Type; import it.javalinux.sibilla.plugins.scanner.ChangedOrNewSourceScanner; import it.javalinux.sibilla.plugins.scanner.RunsRepository; import it.javalinux.sibilla.plugins.scanner.SibillaSourceScanner; import it.javalinux.sibilla.runner.impl.JunitTestRunner; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.model.Dependency; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.cli.CommandLineUtils; import org.codehaus.plexus.util.cli.Commandline; /** * The Sibilla Maven plugin Mojo * * @author alessio.soldano@javalinux.it * * @goal sibilla * * @phase process-test-classes * @requiresDependencyResolution test */ public class SibillaMojo extends AbstractMojo { // ------------ Maven parameters and components for accessing dependencies and repositories ------------ /** * @parameter default-value="${project}" * @required * @readonly */ private MavenProject mavenProject; /** * @parameter default-value="${project.dependencies} * @required * @readonly */ private List<Dependency> dependencies; /** * @component */ private ArtifactResolver resolver; /** * @component */ private ArtifactFactory artifactFactory; /** * @parameter default-value="${localRepository}" * @required * @readonly */ private ArtifactRepository localRepository; /** * @parameter default-value="${project.remoteArtifactRepositories}" * @required * @readonly */ private List<ArtifactRepository> remoteRepositories; /** * @parameter default-value="${plugin.artifacts}" * @required * @readonly */ private List<Artifact> pluginArtifacts; // ------------ Parameters leveraging special maven properties for injecting execution status ------------ /** * The target directory * * @parameter expression="${project.build.directory}" * @required */ private File targetDirectory; /** * The source directories containing the sources * * @parameter expression="${project.compileSourceRoots}" * @required * @readonly */ private List<String> sourceRoots; /** * Project classpath. * * @parameter expression="${project.compileClasspathElements}" * @required * @readonly */ private List<String> classpathElements; /** * The compiled classes directory * * @parameter expression="${project.build.outputDirectory}" * @required */ private File outputDirectory; /** * The source directories containing the test-source * * @parameter expression="${project.testCompileSourceRoots}" * @required * @readonly */ private List<String> testSourceRoots; /** * Project test classpath. * * @parameter expression="${project.testClasspathElements}" * @required * @readonly */ private List<String> testClasspathElements; /** * The compiled test classes directory * * @parameter expression="${project.build.testOutputDirectory}" * @required * @readonly */ private File testOutputDirectory; // ------------ Inclusion / exclusion filters ------------ /** * A list of inclusion filters for classesUnderTest. * * @parameter */ private Set<String> includes = new HashSet<String>(); /** * A list of exclusion filters for classesUnderTest. * * @parameter */ private Set<String> excludes = new HashSet<String>(); /** * A list of inclusion filters for test classes. * * @parameter */ private Set<String> testIncludes = new HashSet<String>(); /** * A list of exclusion filters for test classes. * * @parameter */ private Set<String> testExcludes = new HashSet<String>(); // ------------ Sibilla additional parameters ------------ /** * Set to true to for verbose logging * * @parameter expression="${maven.compiler.verbose}" default-value="false" */ private boolean verbose; /** * Sets the granularity in milliseconds of the last modification * date for testing whether a source needs has changed since last run. * * @parameter expression="${sibilla.lastModGranularityMs}" default-value="0" */ private int staleMillis; /** * Sets the granularity in milliseconds of the last modification * date for testing whether a source needs has changed since last run. * * @parameter expression="${sibilla.runnerClass}" default-value="it.javalinux.sibilla.runner.impl.JunitTestRunner" */ private String runnerClass = JunitTestRunner.class.getName(); // ------------------------------------------------------------------------------------------- /** * * {@inheritDoc} * * @see org.apache.maven.plugin.AbstractMojo#execute() */ public void execute() throws MojoExecutionException { if ("pom".equalsIgnoreCase(mavenProject.getPackaging())) { return; } Long time = System.currentTimeMillis(); RunsRepository repository = new RunsRepository(getTargetDirectory()); repository.load(); try { // creating configuration... Configuration config = new Configuration(); config.setRunner(getRunnerClass()); config.setTargetDir(getTargetDirectory().getPath()); // use scanners to first get changed sources and then retrieve the corresponding targets (internally uses the mappings) SibillaSourceScanner classesUnderTestScanner = getSourceInclusionScanner(repository, getStaleMillis()); Set<File> filesClassesUnderTest = computeChangedSources(getSourceRoots(), classesUnderTestScanner, getOutputDirectory()); List<File> classesUnderTest = config.getChangedClassesUnderTest(); classesUnderTest .addAll(computeChangedTargets(getSourceRoots(), classesUnderTestScanner, getOutputDirectory())); for (File file : filesClassesUnderTest) { //update repository after finishing using it repository.setLastRunTimeMillis(file.getCanonicalPath(), time); } SibillaSourceScanner testClassesScanner = getTestSourceInclusionScanner(repository, staleMillis); Set<File> filesTestClasses = computeChangedSources(getTestSourceRoots(), testClassesScanner, getTestOutputDirectory()); List<File> testClasses = config.getChangedTestClasses(); testClasses.addAll( computeChangedTargets(getTestSourceRoots(), testClassesScanner, getTestOutputDirectory())); for (File file : filesTestClasses) { //update repository after finishing using it repository.setLastRunTimeMillis(file.getCanonicalPath(), time); } // serializing configuration to temp file File confFile = File.createTempFile("sibilla-maven-plugin-", ".config"); config.save(confFile); printDebugLogs(getLog(), config); // preparing arguments and invoking runner in new process String sibillaPluginJar = getBuildPluginArtifactPath("it.javalinux.sibilla.plugins", "maven-sibilla-plugin"); String sibillaJar = getArtifactPath("it.javalinux.sibilla", "Sibilla", null, "jar"); String junitJar = getArtifactPath("junit", "junit", null, "jar"); String javassistJar = getArtifactPath("org.javassist", "javassist", null, "jar"); String xStreamJar = getArtifactPath("com.thoughtworks.xstream", "xstream", null, "jar"); int res = invokeExecutor("java", getExecutorArguments(sibillaJar, confFile.getCanonicalPath(), sibillaPluginJar, junitJar, javassistJar, xStreamJar)); if (res != 0) { throw new MojoExecutionException( "Error during Sibilla invocation, child process returned value: " + res); } } catch (MojoExecutionException mee) { throw mee; } catch (Exception e) { throw new MojoExecutionException("Error while running SibillaMojo: ", e); } //saving run try { repository.save(); } catch (IOException e) { getLog().warn("Unable to write run's data to disk: ", e); } } private String[] getExecutorArguments(String sibillaJar, String configPath, String... additionalBootCPJars) { List<String> args = new LinkedList<String>(); StringBuilder bootCpArg = new StringBuilder("-Xbootclasspath/a:"); bootCpArg.append(sibillaJar); for (String s : additionalBootCPJars) { bootCpArg.append(File.pathSeparator); bootCpArg.append(s); } List<String> cpElements = getTestClasspathElements(); if (cpElements != null && !cpElements.isEmpty()) { for (Iterator<String> it = cpElements.iterator(); it.hasNext();) { bootCpArg.append(File.pathSeparator); bootCpArg.append(it.next()); } } args.add(bootCpArg.toString()); args.add("-javaagent:" + sibillaJar); args.add(Executor.class.getCanonicalName()); args.add(configPath); return args.toArray(new String[args.size()]); } private int invokeExecutor(String command, String[] arguments) throws Exception { Commandline cl = new Commandline(command); cl.addArguments(arguments); MavenLogStreamConsumer output = new MavenLogStreamConsumer(getLog(), Type.OUTPUT); MavenLogStreamConsumer error = new MavenLogStreamConsumer(getLog(), Type.ERROR); if (getLog().isDebugEnabled()) { getLog().debug("Command line: " + cl); } int returnValue = CommandLineUtils.executeCommandLine(cl, output, error); return returnValue; } private String getArtifactPath(String groupId, String artifactId, String classifier, String type) throws ArtifactResolutionException, ArtifactNotFoundException, IOException { if (pluginArtifacts != null) { for (Artifact artifact : pluginArtifacts) { if (StringUtils.equals(groupId, artifact.getGroupId()) && StringUtils.equals(artifactId, artifact.getArtifactId()) && StringUtils.equals(classifier, artifact.getClassifier()) && StringUtils.equals(type, artifact.getType())) { resolver.resolve(artifact, remoteRepositories, localRepository); return artifact.getFile().getCanonicalPath(); } } } if (dependencies != null) { for (Dependency dep : dependencies) { if (StringUtils.equals(groupId, dep.getGroupId()) && StringUtils.equals(artifactId, dep.getArtifactId()) && StringUtils.equals(classifier, dep.getClassifier()) && StringUtils.equals(type, dep.getType())) { Artifact artifact = artifactFactory.createArtifactWithClassifier(groupId, artifactId, dep.getVersion(), type, classifier); resolver.resolve(artifact, remoteRepositories, localRepository); return artifact.getFile().getCanonicalPath(); } } } return null; } @SuppressWarnings("unchecked") private String getBuildPluginArtifactPath(String groupId, String artifactId) throws ArtifactResolutionException, ArtifactNotFoundException, IOException { List<Plugin> plugins = mavenProject.getBuildPlugins(); for (Plugin plugin : plugins) { if (StringUtils.equals(groupId, plugin.getGroupId()) && StringUtils.equals(artifactId, plugin.getArtifactId())) { Artifact artifact = artifactFactory.createArtifactWithClassifier(plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion(), "jar", null); resolver.resolve(artifact, remoteRepositories, localRepository); return artifact.getFile().getCanonicalPath(); } } throw new RuntimeException("it.javalinux.sibilla.plugins:maven-sibilla-plugin not found"); } private static Set<File> computeChangedSources(List<String> sourceRoots, SibillaSourceScanner scanner, File outputDirectory) throws MojoExecutionException { scanner.addSourceMapping(new SuffixMapping(".java", ".class")); Set<File> changedFiles = new HashSet<File>(); for (String sourceRoot : sourceRoots) { File rootFile = new File(sourceRoot); if (!rootFile.isDirectory()) { continue; } try { changedFiles.addAll(scanner.getIncludedSources(rootFile, outputDirectory)); } catch (InclusionScanException e) { throw new MojoExecutionException("Error scanning source root: \'" + sourceRoot + "\' " + " for changed files since last run.", e); } } return changedFiles; } private static Set<File> computeChangedTargets(List<String> sourceRoots, SibillaSourceScanner scanner, File outputDirectory) throws MojoExecutionException { Set<File> changedFiles = new HashSet<File>(); for (String sourceRoot : sourceRoots) { File rootFile = new File(sourceRoot); if (!rootFile.isDirectory()) { continue; } try { changedFiles.addAll(scanner.getIncludedTargets(rootFile, outputDirectory)); } catch (InclusionScanException e) { throw new MojoExecutionException("Error retrieving targets for source root: \'" + sourceRoot + "\'", e); } } return changedFiles; } private void printDebugLogs(Log log, Configuration config) { if (log.isDebugEnabled()) { log.debug("Classpath:"); for (String s : getClasspathElements()) { log.debug(" " + s); } log.debug("Source roots:"); for (String root : getSourceRoots()) { log.debug(" " + root); } log.debug("Output directory:"); log.debug(" " + getOutputDirectory()); log.debug("Test classpath:"); for (String s : getTestClasspathElements()) { log.debug(" " + s); } log.debug("Test source roots:"); for (String root : getTestSourceRoots()) { log.debug(" " + root); } log.debug("Test output directory:"); log.debug(" " + getTestOutputDirectory()); log.debug("Classes under test changed since last run: "); for (File f : config.getChangedClassesUnderTest()) { log.debug(" " + f); } log.debug("Test classes changed since last run: "); for (File f : config.getChangedTestClasses()) { log.debug(" " + f); } log.debug("Provided Sibilla runner:"); log.debug(" " + config.getRunner()); log.debug("Provided Sibilla metadata serializer:"); log.debug(" " + config.getSerializer()); } } protected SibillaSourceScanner getSourceInclusionScanner(RunsRepository repository, int staleMillis) { if (includes.isEmpty()) { includes.add("**/*.java"); } return new ChangedOrNewSourceScanner(staleMillis, includes, excludes, repository); } protected SibillaSourceScanner getTestSourceInclusionScanner(RunsRepository repository, int staleMillis) { if (testIncludes.isEmpty()) { testIncludes.add("**/*.java"); } return new ChangedOrNewSourceScanner(staleMillis, testIncludes, testExcludes, repository); } // ------------ package visible getters (for testability) ------------ /** * @return sourceRoots */ List<String> getSourceRoots() { return sourceRoots; } /** * @return classpathElements */ List<String> getClasspathElements() { return classpathElements; } /** * @return outputDirectory */ File getOutputDirectory() { return outputDirectory; } /** * @return testSourceRoots */ List<String> getTestSourceRoots() { return testSourceRoots; } /** * @return testClasspathElements */ List<String> getTestClasspathElements() { return testClasspathElements; } /** * @return testOutputDirectory */ File getTestOutputDirectory() { return testOutputDirectory; } /** * @return includes */ Set<String> getIncludes() { return includes; } /** * @return excludes */ Set<String> getExcludes() { return excludes; } /** * @return includes */ Set<String> getTestIncludes() { return testIncludes; } /** * @return excludes */ Set<String> getTestExcludes() { return testExcludes; } /** * @return verbose */ boolean isVerbose() { return verbose; } /** * @return staleMillis */ int getStaleMillis() { return staleMillis; } /** * @return runnerClass */ String getRunnerClass() { return runnerClass; } /** * @return targetDirectory */ File getTargetDirectory() { return targetDirectory; } }