Skip to content

Commit

Permalink
maven support for cross module tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hcoles committed Nov 1, 2024
1 parent 6ffc319 commit 82bdbd2
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.pitest.functional.Streams;

import java.util.Collection;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -36,7 +37,7 @@ public Stream<ClassTree> codeTrees() {
}

public Set<ClassName> getCodeUnderTestNames() {
return this.classPath.code().stream().collect(Collectors.toSet());
return new HashSet<>(this.classPath.code());
}

public Set<ClassName> getTestClassNames() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
import org.pitest.util.Verbosity;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -502,8 +502,7 @@ public Optional<Reader> createHistoryReader() {
try {
if (this.historyInputLocation.exists()
&& (this.historyInputLocation.length() > 0)) {
return Optional.ofNullable(new InputStreamReader(new FileInputStream(
this.historyInputLocation), StandardCharsets.UTF_8));
return Optional.of(new InputStreamReader(Files.newInputStream(this.historyInputLocation.toPath()), StandardCharsets.UTF_8));
}
return Optional.empty();
} catch (final IOException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public AnalysisResult execute(File baseDir, ReportOptions data,
final LaunchOptions launchOptions = new LaunchOptions(ja,
settings.getJavaExecutable(), createJvmArgs(data), environmentVariables)
.usingClassPathJar(data.useClasspathJar());

final ProjectClassPaths cps = data.getMutationClassPaths();

final CodeSource code = settings.createCodeSource(cps);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>pit-parent-module</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>

<properties>
<junit.version>4.13.1</junit.version>
<pit.version>dev-SNAPSHOT</pit.version>
</properties>


<build>
<plugins>
<plugin>
Expand All @@ -33,6 +33,7 @@
<artifactId>pitest-maven</artifactId>
<version>${pit.version}</version>
<configuration>
<crossModule>true</crossModule>
<timestampedReports>false</timestampedReports>
<outputFormats>
<outputFormat>HTML</outputFormat>
Expand All @@ -46,12 +47,39 @@
</plugin>
</plugins>
</pluginManagement>

</build>


<properties>
<junit.version>4.13.1</junit.version>
</properties>
<profiles>
<profile>
<id>pitest</id>
<build>
<plugins>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<executions>
<execution>
<id>pitest</id>
<phase>test-compile</phase>
<goals>
<goal>mutationCoverage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>

<modules>
<module>cross-tests-code</module>
<module>cross-tests-tests</module>
Expand Down
17 changes: 16 additions & 1 deletion pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,13 @@ public class AbstractPitMojo extends AbstractMojo {
@Parameter(property = "skipTests", defaultValue = "false")
private boolean skipTests;

/**
* Mutate code outside current module
*/
@Parameter(property = "crossModule", defaultValue = "false")
private boolean crossModule;


/**
* When set will ignore failing tests when computing coverage. Otherwise, the
* run will fail. If parseSurefireConfig is true, will be overridden from
Expand Down Expand Up @@ -739,7 +746,7 @@ protected RunDecision shouldRun() {
decision.addReason("Packaging is POM.");
}

if (!notEmptyProject.test(project)) {
if (!notEmptyProject.test(project) && !crossModule) {
decision.addReason("Project has either no tests or no production code.");
}

Expand Down Expand Up @@ -816,6 +823,14 @@ public RepositorySystem repositorySystem() {
return repositorySystem;
}

public boolean isCrossModule() {
return crossModule;
}

public List<MavenProject> allProjects() {
return session.getProjects();
}

static class RunDecision {
private List<String> reasons = new ArrayList<>(4);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.model.Build;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
Expand Down Expand Up @@ -43,18 +44,20 @@
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
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 java.util.stream.Stream;

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

public class MojoToReportOptionsConverter {

private final AbstractPitMojo mojo;
private final AbstractPitMojo mojo;
private final Predicate<Artifact> dependencyFilter;
private final Log log;
private final SurefireConfigConverter surefireConverter;
Expand Down Expand Up @@ -85,6 +88,8 @@ public ReportOptions convert() {
autoAddJUnitPlatform(classPath);
removeExcludedDependencies(classPath);

addCrossModuleDirsToClasspath(classPath);

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

Expand All @@ -100,6 +105,15 @@ public ReportOptions convert() {

}

private void addCrossModuleDirsToClasspath(List<String> classPath) {
// Add the output directories modules we depend on to the start of the classpath.
// If we resolve cross project classes from a jar, the path match
// will fail. This is only an issue when running the pitest goal directly.
if (mojo.isCrossModule()) {
classPath.addAll(0, crossModuleDependencies());
}
}

/**
* 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
Expand Down Expand Up @@ -176,10 +190,22 @@ private ReportOptions parseReportOptions(final List<String> classPath) {
final ReportOptions data = new ReportOptions();

if (this.mojo.getProject().getBuild() != null) {

List<String> codePaths = new ArrayList<>();
codePaths.add(this.mojo.getProject().getBuild()
.getOutputDirectory());

if (mojo.isCrossModule()) {
codePaths.addAll(crossModuleDependencies());
}

this.log.info("Mutating from "
+ this.mojo.getProject().getBuild().getOutputDirectory());
+ String.join(",", codePaths));

data.setCodePaths(Collections.singleton(this.mojo.getProject().getBuild()
.getOutputDirectory()));

data.setCodePaths(codePaths);
}

data.setUseClasspathJar(this.mojo.isUseClasspathJar());
Expand Down Expand Up @@ -215,9 +241,7 @@ private ReportOptions parseReportOptions(final List<String> classPath) {
data.setLoggingClasses(this.mojo.getAvoidCallsTo());
}

final List<String> sourceRoots = new ArrayList<>();
sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots());
sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots());
final List<String> sourceRoots = determineSourceRoots();

data.setSourceDirs(stringsToPaths(sourceRoots));

Expand Down Expand Up @@ -253,6 +277,41 @@ private ReportOptions parseReportOptions(final List<String> classPath) {
return data;
}

private List<String> determineSourceRoots() {
final List<String> sourceRoots = new ArrayList<>();
sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots());
sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots());
if (mojo.isCrossModule()) {
List<String> otherRoots = dependedOnProjects().stream()
.flatMap(p -> p.getCompileSourceRoots().stream())
.collect(Collectors.toList());

sourceRoots.addAll(otherRoots);
}
return sourceRoots;
}

private Collection<String> crossModuleDependencies() {
return dependedOnProjects().stream()
.map(MavenProject::getBuild)
.map(Build::getOutputDirectory)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

private List<MavenProject> dependedOnProjects() {
// strip version from dependencies
Set<String> inScope = this.mojo.getProject().getDependencies().stream()
.map(p -> p.getGroupId() + ":" + p.getArtifactId())
.collect(Collectors.toSet());


return this.mojo.allProjects().stream()
.filter(p -> inScope.contains(p.getGroupId() + ":" + p.getArtifactId()))
.collect(Collectors.toList());

}

private void configureVerbosity(ReportOptions data) {
if (this.mojo.isVerbose()) {
data.setVerbosity(Verbosity.VERBOSE);
Expand Down Expand Up @@ -384,6 +443,8 @@ private Collection<String> useConfiguredTargetTestsOrFindOccupiedPackages(
}

private Collection<String> findOccupiedTestPackages() {
// use only the tests within current project, even if in
// cross module mode
String outputDirName = this.mojo.getProject().getBuild()
.getTestOutputDirectory();
if (outputDirName != null) {
Expand Down Expand Up @@ -430,10 +491,12 @@ private Collection<String> useConfiguredTargetClassesOrFindOccupiedPackages(
}

private Collection<String> findOccupiedPackages() {
String outputDirName = this.mojo.getProject().getBuild()
.getOutputDirectory();
File outputDir = new File(outputDirName);
return findOccupiedPackagesIn(outputDir);
return Stream.concat(Stream.of(mojo.getProject()), dependedOnProjects().stream())
.distinct()
.map(p -> new File(p.getBuild().getOutputDirectory()))
.flatMap(f -> findOccupiedPackagesIn(f).stream())
.distinct()
.collect(Collectors.toList());
}

public static Collection<String> findOccupiedPackagesIn(File dir) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.stream.Collectors;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Build;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.apache.maven.project.MavenProject;
Expand All @@ -45,6 +46,9 @@ public abstract class BasePitMojoTest extends AbstractMojoTestCase {
@Mock
protected MavenProject project;

@Mock
protected MavenSession session;

@Mock
protected RunPitStrategy executionStrategy;

Expand Down Expand Up @@ -118,6 +122,7 @@ protected void configurePitMojo(final AbstractPitMojo pitMojo, final String conf
setVariableValueToObject(pitMojo, "pluginArtifactMap", pluginArtifacts);

setVariableValueToObject(pitMojo, "project", this.project);
setVariableValueToObject(pitMojo, "session", this.session);

if (pitMojo.getAdditionalClasspathElements() == null) {
ArrayList<String> elements = new ArrayList<>();
Expand Down
Loading

0 comments on commit 82bdbd2

Please sign in to comment.