All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pitest.maven.MojoToReportOptionsConverter Maven / Gradle / Ivy

There is a newer version: 1.17.1
Show newest version
/*
 * Copyright 2011 Henry Coles
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 */
package org.pitest.maven;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.pitest.classinfo.ClassName;
import org.pitest.classpath.DirectoryClassPathRoot;
import org.pitest.functional.FCollection;
import org.pitest.mutationtest.config.ReportOptions;
import org.pitest.testapi.TestGroupConfig;
import org.pitest.util.Glob;
import org.pitest.util.Verbosity;

import java.io.File;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.pitest.functional.Streams.asStream;

public class MojoToReportOptionsConverter {

  private final AbstractPitMojo                 mojo;
  private final Predicate     dependencyFilter;
  private final Log                     log;
  private final SurefireConfigConverter surefireConverter;

  public MojoToReportOptionsConverter(final AbstractPitMojo mojo,
      SurefireConfigConverter surefireConverter,
      Predicate dependencyFilter) {
    this.mojo = mojo;
    this.dependencyFilter = dependencyFilter;
    this.log = mojo.getLog();
    this.surefireConverter = surefireConverter;
  }

  public ReportOptions convert() {

    final List classPath = new ArrayList<>();

    try {
      classPath.addAll(this.mojo.getProject().getTestClasspathElements());
    } catch (final DependencyResolutionRequiredException e1) {
      this.log.info(e1);
    }

    addOwnDependenciesToClassPath(classPath);

    classPath.addAll(this.mojo.getAdditionalClasspathElements());

    autoAddJUnitPlatform(classPath);
    removeExcludedDependencies(classPath);

    ReportOptions option = parseReportOptions(classPath);
    ReportOptions withSureFire = updateFromSurefire(option);

    // Null check here is a bad unit testing artifact, should never be null in real life
    ReportOptions effective = withSureFire != null ? withSureFire : option;

    // argline may contain surefire style properties that require expanding
    if (effective.getArgLine() != null) {
      log.info("Replacing properties in argLine " + effective.getArgLine());
      effective.setArgLine(this.replacePropertyExpressions(effective.getArgLine()));
    }
    return effective;

  }

  /**
   * The junit 5 plugin needs junit-platform-launcher to run, but this will not be on the classpath
   * of the project. We want to use the same version that surefire (and therefore the SUT) uses, not
   * the one the plugin was built against.
   * 

* It is not declared as a normal dependency, instead surefire picks the version to use based on * other junit jars on the classpath. We're forced to do something similar here. * * @param classPath classpath to modify */ private void autoAddJUnitPlatform(List classPath) { List junitDependencies = this.mojo.getProject().getArtifacts().stream() .filter(a -> a.getGroupId().equals("org.junit.platform")) .collect(Collectors.toList()); // If the launcher has been manually added to the dependencies, there is nothing to do if (junitDependencies.stream().anyMatch(a -> a.getArtifactId().equals("junit-platform-launcher"))) { return; } Optional maybeJUnitPlatform = findJUnitArtifact(junitDependencies); if (!maybeJUnitPlatform.isPresent()) { this.log.debug("JUnit 5 not on classpath"); return; } // Look for platform engine or platform commons on classpath Artifact toMatch = maybeJUnitPlatform.get(); // Assume that platform launcher has been released with same version number as engine and commons DefaultArtifact platformLauncher = new DefaultArtifact(toMatch.getGroupId(), "junit-platform-launcher", "jar", toMatch.getVersion()); try { ArtifactRequest r = new ArtifactRequest(); r.setArtifact(platformLauncher); r.setRepositories(this.mojo.getProject().getRemotePluginRepositories()); ArtifactResult resolved = this.mojo.repositorySystem().resolveArtifact(mojo.session().getRepositorySession(), r); this.log.info("Auto adding " + resolved + " to classpath."); classPath.add(resolved.getArtifact().getFile().getAbsolutePath()); } catch (ArtifactResolutionException e) { this.log.error("Could not resolve " + platformLauncher); throw new RuntimeException(e); } } private static Optional findJUnitArtifact(List junitDependencies) { Optional maybeEngine = junitDependencies.stream() .filter(a -> a.getArtifactId().equals("junit-platform-engine")) .findAny(); if (maybeEngine.isPresent()) { return maybeEngine; } return junitDependencies.stream() .filter(a -> a.getArtifactId().equals("junit-platform-commons")) .findAny(); } private void removeExcludedDependencies(List classPath) { for (Object artifact : this.mojo.getProject().getArtifacts()) { final Artifact dependency = (Artifact) artifact; if (this.mojo.getClasspathDependencyExcludes().contains( dependency.getGroupId() + ":" + dependency.getArtifactId())) { classPath.remove(dependency.getFile().getPath()); } } } private ReportOptions parseReportOptions(final List classPath) { final ReportOptions data = new ReportOptions(); if (this.mojo.getProject().getBuild() != null) { this.log.info("Mutating from " + this.mojo.getProject().getBuild().getOutputDirectory()); data.setCodePaths(Collections.singleton(this.mojo.getProject().getBuild() .getOutputDirectory())); } data.setUseClasspathJar(this.mojo.isUseClasspathJar()); data.setClassPathElements(classPath); data.setFailWhenNoMutations(shouldFailWhenNoMutations()); data.setTargetClasses(determineTargetClasses()); data.setTargetTests(determineTargetTests()); data.setExcludedMethods(this.mojo .getExcludedMethods()); data.setExcludedClasses(this.mojo.getExcludedClasses()); data.setExcludedTestClasses(globStringsToPredicates(this.mojo .getExcludedTestClasses())); data.setNumberOfThreads(this.mojo.getThreads()); data.setExcludedRunners(this.mojo.getExcludedRunners()); data.setReportDir(this.mojo.getReportsDirectory().getAbsolutePath()); configureVerbosity(data); if (this.mojo.getJvmArgs() != null) { data.addChildJVMArgs(this.mojo.getJvmArgs()); } if (this.mojo.getArgLine() != null) { data.setArgLine(this.mojo.getArgLine()); } data.setMutators(determineMutators()); data.setFeatures(determineFeatures()); data.setTimeoutConstant(this.mojo.getTimeoutConstant()); data.setTimeoutFactor(this.mojo.getTimeoutFactor()); if (hasValue(this.mojo.getAvoidCallsTo())) { data.setLoggingClasses(this.mojo.getAvoidCallsTo()); } final List sourceRoots = new ArrayList<>(); sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots()); sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots()); data.setSourceDirs(stringsToPaths(sourceRoots)); data.addOutputFormats(determineOutputFormats()); setTestGroups(data); data.setFullMutationMatrix(this.mojo.isFullMutationMatrix()); data.setMutationUnitSize(this.mojo.getMutationUnitSize()); data.setShouldCreateTimestampedReports(this.mojo.isTimestampedReports()); data.setDetectInlinedCode(this.mojo.isDetectInlinedCode()); determineHistory(data); data.setExportLineCoverage(this.mojo.isExportLineCoverage()); data.setMutationEngine(this.mojo.getMutationEngine()); data.setJavaExecutable(this.mojo.getJavaExecutable()); data.setFreeFormProperties(createPluginProperties()); data.setIncludedTestMethods(this.mojo.getIncludedTestMethods()); data.setSkipFailingTests(this.mojo.skipFailingTests()); data.setInputEncoding(this.mojo.getSourceEncoding()); data.setOutputEncoding(this.mojo.getOutputEncoding()); if (this.mojo.getProjectBase() != null) { data.setProjectBase(FileSystems.getDefault().getPath(this.mojo.getProjectBase())); } checkForObsoleteOptions(this.mojo); return data; } private void configureVerbosity(ReportOptions data) { if (this.mojo.isVerbose()) { data.setVerbosity(Verbosity.VERBOSE); } else { Verbosity v = Verbosity.fromString(mojo.getVerbosity()); data.setVerbosity(v); } } private void checkForObsoleteOptions(AbstractPitMojo mojo) { if (mojo.getMaxMutationsPerClass() > 0) { throw new IllegalArgumentException("The max mutations per class argument is no longer supported, " + "use features=+CLASSLIMIT(limit[" + mojo.getMaxMutationsPerClass() + "]) instead"); } } private void determineHistory(final ReportOptions data) { // set explicit history files if configured data.setHistoryInputLocation(this.mojo.getHistoryInputFile()); data.setHistoryOutputLocation(this.mojo.getHistoryOutputFile()); // If withHistory option set, overwrite config with files in temp dir. // This allows a user to configure files for use on ci, but still easily use temp files // for local running if (this.mojo.useHistory()) { useHistoryFileInTempDir(data); } if (data.getHistoryInputLocation() != null) { log.info("Will read history at " + data.getHistoryInputLocation()); } if (data.getHistoryOutputLocation() != null) { log.info("Will write history at " + data.getHistoryOutputLocation()); } } private void useHistoryFileInTempDir(final ReportOptions data) { String tempDir = System.getProperty("java.io.tmpdir"); MavenProject project = this.mojo.getProject(); String name = project.getGroupId() + "." + project.getArtifactId() + "." + project.getVersion() + "_pitest_history.bin"; File historyFile = new File(tempDir, name); if (mojo.getHistoryInputFile() != null || mojo.getHistoryOutputFile() != null) { log.info("Using withHistory option. This overrides the explicitly set history file paths."); } data.setHistoryInputLocation(historyFile); data.setHistoryOutputLocation(historyFile); } private ReportOptions updateFromSurefire(ReportOptions option) { Collection plugins = lookupPlugin("org.apache.maven.plugins:maven-surefire-plugin"); if (!this.mojo.isParseSurefireConfig()) { return option; } else if (plugins.isEmpty()) { this.log.warn("Could not find surefire configuration in pom"); return option; } Plugin surefire = plugins.iterator().next(); if (surefire != null) { return this.surefireConverter.update(option, (Xpp3Dom) surefire.getConfiguration()); } else { return option; } } private Collection lookupPlugin(String key) { List plugins = this.mojo.getProject().getBuildPlugins(); return FCollection.filter(plugins, hasKey(key)); } private static Predicate hasKey(final String key) { return a -> a.getKey().equals(key); } private boolean shouldFailWhenNoMutations() { return this.mojo.isFailWhenNoMutations(); } private void setTestGroups(final ReportOptions data) { final TestGroupConfig conf = new TestGroupConfig( this.mojo.getExcludedGroups(), this.mojo.getIncludedGroups()); data.setGroupConfig(conf); } private void addOwnDependenciesToClassPath(final List classPath) { for (final Artifact dependency : filteredDependencies()) { this.log.info("Adding " + dependency.getGroupId() + ":" + dependency.getArtifactId() + " to SUT classpath"); classPath.add(dependency.getFile().getAbsolutePath()); } // If the user as explicitly added junit platform classes to the pitest classpath, trust that they // know what they're doing and add them in this.mojo.getPluginArtifactMap().values().stream().filter(a -> a.getGroupId().equals("org.junit.platform")) .forEach(dependency -> classPath.add(dependency.getFile().getAbsolutePath())); } private Collection> globStringsToPredicates( final List excludedMethods) { return asStream(excludedMethods) .map(Glob.toGlobPredicate()) .collect(Collectors.toList()); } private Collection> determineTargetTests() { return useConfiguredTargetTestsOrFindOccupiedPackages(this.mojo.getTargetTests()).stream() .map(Glob.toGlobPredicate()) .collect(Collectors.toList()); } private Collection useConfiguredTargetTestsOrFindOccupiedPackages( final Collection filters) { if (!hasValue(filters)) { this.mojo.getLog().info("Defaulting target tests to match packages in test build directory"); return findOccupiedTestPackages(); } else { return filters; } } private Collection findOccupiedTestPackages() { String outputDirName = this.mojo.getProject().getBuild() .getTestOutputDirectory(); if (outputDirName != null) { File outputDir = new File(outputDirName); return findOccupiedPackagesIn(outputDir); } else { return Collections.emptyList(); } } private Collection filteredDependencies() { return FCollection.filter(this.mojo.getPluginArtifactMap().values(), this.dependencyFilter); } private Collection determineMutators() { if (this.mojo.getMutators() != null) { return this.mojo.getMutators(); } else { return Collections.emptyList(); } } private Collection determineFeatures() { if (this.mojo.getFeatures() != null) { return this.mojo.getFeatures(); } else { return Collections.emptyList(); } } private Collection determineTargetClasses() { return useConfiguredTargetClassesOrFindOccupiedPackages(this.mojo.getTargetClasses()); } private Collection useConfiguredTargetClassesOrFindOccupiedPackages( final Collection filters) { if (!hasValue(filters)) { this.mojo.getLog().info("Defaulting target classes to match packages in build directory"); return findOccupiedPackages(); } else { return filters; } } private Collection findOccupiedPackages() { String outputDirName = this.mojo.getProject().getBuild() .getOutputDirectory(); File outputDir = new File(outputDirName); return findOccupiedPackagesIn(outputDir); } public static Collection findOccupiedPackagesIn(File dir) { if (dir.exists()) { DirectoryClassPathRoot root = new DirectoryClassPathRoot(dir); Set occupiedPackages = new HashSet<>(); FCollection.mapTo(root.classNames(), classToPackageGlob(), occupiedPackages); return occupiedPackages; } return Collections.emptyList(); } private static Function classToPackageGlob() { return a -> ClassName.fromString(a).getPackage().asJavaName() + ".*"; } private Collection stringsToPaths(final List sourceRoots) { return sourceRoots.stream() .map(Paths::get) .collect(Collectors.toList()); } private Collection determineOutputFormats() { if (hasValue(this.mojo.getOutputFormats())) { return this.mojo.getOutputFormats(); } else { return Arrays.asList("HTML"); } } private boolean hasValue(final Collection collection) { return (collection != null) && !collection.isEmpty(); } private Properties createPluginProperties() { Properties p = new Properties(); if (this.mojo.getPluginProperties() != null) { p.putAll(this.mojo.getPluginProperties()); } return p; } /** * This method taken from surefire under Apache 2 licence. * * Replaces expressions

@{property-name}
with the corresponding properties * from the model. This allows late evaluation of property values when the plugin is executed (as compared * to evaluation when the pom is parsed as is done with
${property-name}
expressions). * * This allows other plugins to modify or set properties with the changes getting picked up by surefire. */ private String replacePropertyExpressions(String argLine) { for (Enumeration e = mojo.getProject().getProperties().propertyNames(); e.hasMoreElements();) { String key = e.nextElement().toString(); // Replace surefire late evaluation syntax properties argLine = replaceFieldForSymbol('@', key, argLine); // Normally properties will already have been expanded by maven, but this is // bypassed for argLines pulled from surefire, se we must handle them here argLine = replaceFieldForSymbol('$', key, argLine); } return argLine; } private String replaceFieldForSymbol(char symbol, String key, String argLine) { String field = symbol + "{" + key + "}"; if (argLine.contains(field)) { return argLine.replace(field, mojo.getProject().getProperties().getProperty(key, "")); } return argLine; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy