Skip to content

Commit a94bd6a

Browse files
committed
maven support for cross module tests
1 parent 6ffc319 commit a94bd6a

File tree

9 files changed

+180
-41
lines changed

9 files changed

+180
-41
lines changed

pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.pitest.functional.Streams;
99

1010
import java.util.Collection;
11+
import java.util.HashSet;
1112
import java.util.Optional;
1213
import java.util.Set;
1314
import java.util.stream.Collectors;
@@ -36,7 +37,7 @@ public Stream<ClassTree> codeTrees() {
3637
}
3738

3839
public Set<ClassName> getCodeUnderTestNames() {
39-
return this.classPath.code().stream().collect(Collectors.toSet());
40+
return new HashSet<>(this.classPath.code());
4041
}
4142

4243
public Set<ClassName> getTestClassNames() {

pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@
3333
import org.pitest.util.Verbosity;
3434

3535
import java.io.File;
36-
import java.io.FileInputStream;
3736
import java.io.IOException;
3837
import java.io.InputStreamReader;
3938
import java.io.Reader;
4039
import java.nio.charset.Charset;
4140
import java.nio.charset.StandardCharsets;
41+
import java.nio.file.Files;
4242
import java.nio.file.Path;
4343
import java.util.ArrayList;
4444
import java.util.Arrays;
@@ -502,8 +502,7 @@ public Optional<Reader> createHistoryReader() {
502502
try {
503503
if (this.historyInputLocation.exists()
504504
&& (this.historyInputLocation.length() > 0)) {
505-
return Optional.ofNullable(new InputStreamReader(new FileInputStream(
506-
this.historyInputLocation), StandardCharsets.UTF_8));
505+
return Optional.of(new InputStreamReader(Files.newInputStream(this.historyInputLocation.toPath()), StandardCharsets.UTF_8));
507506
}
508507
return Optional.empty();
509508
} catch (final IOException ex) {

pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public AnalysisResult execute(File baseDir, ReportOptions data,
111111
final LaunchOptions launchOptions = new LaunchOptions(ja,
112112
settings.getJavaExecutable(), createJvmArgs(data), environmentVariables)
113113
.usingClassPathJar(data.useClasspathJar());
114+
114115
final ProjectClassPaths cps = data.getMutationClassPaths();
115116

116117
final CodeSource code = settings.createCodeSource(cps);

pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ public void handlesTestsInSeparateModulesWhenConfigured()
455455
throws Exception {
456456
File testDir = prepare("/pit-cross-module-tests");
457457

458-
verifier.executeGoal("test");
458+
verifier.executeGoal("install");
459459
verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage");
460460

461461
verifier.executeGoal("org.pitest:pitest-maven:report-aggregate-module");

pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
<packaging>pom</packaging>
77
<version>1.0-SNAPSHOT</version>
88
<name>pit-parent-module</name>
9-
<dependencies>
10-
<dependency>
11-
<groupId>junit</groupId>
12-
<artifactId>junit</artifactId>
13-
<version>${junit.version}</version>
14-
</dependency>
15-
</dependencies>
9+
10+
<properties>
11+
<junit.version>4.13.1</junit.version>
12+
<pit.version>dev-SNAPSHOT</pit.version>
13+
</properties>
14+
15+
1616
<build>
1717
<plugins>
1818
<plugin>
@@ -33,6 +33,7 @@
3333
<artifactId>pitest-maven</artifactId>
3434
<version>${pit.version}</version>
3535
<configuration>
36+
<crossModule>true</crossModule>
3637
<timestampedReports>false</timestampedReports>
3738
<outputFormats>
3839
<outputFormat>HTML</outputFormat>
@@ -46,12 +47,39 @@
4647
</plugin>
4748
</plugins>
4849
</pluginManagement>
50+
4951
</build>
5052

51-
52-
<properties>
53-
<junit.version>4.13.1</junit.version>
54-
</properties>
53+
<profiles>
54+
<profile>
55+
<id>pitest</id>
56+
<build>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.pitest</groupId>
60+
<artifactId>pitest-maven</artifactId>
61+
<executions>
62+
<execution>
63+
<id>pitest</id>
64+
<phase>test-compile</phase>
65+
<goals>
66+
<goal>mutationCoverage</goal>
67+
</goals>
68+
</execution>
69+
</executions>
70+
</plugin>
71+
</plugins>
72+
</build>
73+
</profile>
74+
</profiles>
75+
<dependencies>
76+
<dependency>
77+
<groupId>junit</groupId>
78+
<artifactId>junit</artifactId>
79+
<version>${junit.version}</version>
80+
</dependency>
81+
</dependencies>
82+
5583
<modules>
5684
<module>cross-tests-code</module>
5785
<module>cross-tests-tests</module>

pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,13 @@ public class AbstractPitMojo extends AbstractMojo {
332332
@Parameter(property = "skipTests", defaultValue = "false")
333333
private boolean skipTests;
334334

335+
/**
336+
* Mutate code outside current module
337+
*/
338+
@Parameter(property = "crossModule", defaultValue = "false")
339+
private boolean crossModule;
340+
341+
335342
/**
336343
* When set will ignore failing tests when computing coverage. Otherwise, the
337344
* run will fail. If parseSurefireConfig is true, will be overridden from
@@ -739,7 +746,7 @@ protected RunDecision shouldRun() {
739746
decision.addReason("Packaging is POM.");
740747
}
741748

742-
if (!notEmptyProject.test(project)) {
749+
if (!notEmptyProject.test(project) && !crossModule) {
743750
decision.addReason("Project has either no tests or no production code.");
744751
}
745752

@@ -816,6 +823,14 @@ public RepositorySystem repositorySystem() {
816823
return repositorySystem;
817824
}
818825

826+
public boolean isCrossModule() {
827+
return crossModule;
828+
}
829+
830+
public List<MavenProject> allProjects() {
831+
return session.getProjects();
832+
}
833+
819834
static class RunDecision {
820835
private List<String> reasons = new ArrayList<>(4);
821836

pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import org.apache.maven.artifact.Artifact;
1818
import org.apache.maven.artifact.DependencyResolutionRequiredException;
19+
import org.apache.maven.model.Build;
1920
import org.apache.maven.model.Plugin;
2021
import org.apache.maven.plugin.logging.Log;
2122
import org.apache.maven.project.MavenProject;
@@ -43,18 +44,20 @@
4344
import java.util.Enumeration;
4445
import java.util.HashSet;
4546
import java.util.List;
47+
import java.util.Objects;
4648
import java.util.Optional;
4749
import java.util.Properties;
4850
import java.util.Set;
4951
import java.util.function.Function;
5052
import java.util.function.Predicate;
5153
import java.util.stream.Collectors;
54+
import java.util.stream.Stream;
5255

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

5558
public class MojoToReportOptionsConverter {
5659

57-
private final AbstractPitMojo mojo;
60+
private final AbstractPitMojo mojo;
5861
private final Predicate<Artifact> dependencyFilter;
5962
private final Log log;
6063
private final SurefireConfigConverter surefireConverter;
@@ -85,6 +88,8 @@ public ReportOptions convert() {
8588
autoAddJUnitPlatform(classPath);
8689
removeExcludedDependencies(classPath);
8790

91+
addCrossModuleDirsToClasspath(classPath);
92+
8893
ReportOptions option = parseReportOptions(classPath);
8994
ReportOptions withSureFire = updateFromSurefire(option);
9095

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

101106
}
102107

108+
private void addCrossModuleDirsToClasspath(List<String> classPath) {
109+
// Add the output directories modules we depend on to the start of the classpath.
110+
// If we resolve cross project classes from a jar, the path match
111+
// will fail. This is only an issue when running the pitest goal directly.
112+
if (mojo.isCrossModule()) {
113+
classPath.addAll(0, crossModuleDependencies());
114+
}
115+
}
116+
103117
/**
104118
* The junit 5 plugin needs junit-platform-launcher to run, but this will not be on the classpath
105119
* of the project. We want to use the same version that surefire (and therefore the SUT) uses, not
@@ -176,10 +190,22 @@ private ReportOptions parseReportOptions(final List<String> classPath) {
176190
final ReportOptions data = new ReportOptions();
177191

178192
if (this.mojo.getProject().getBuild() != null) {
193+
194+
List<String> codePaths = new ArrayList<>();
195+
codePaths.add(this.mojo.getProject().getBuild()
196+
.getOutputDirectory());
197+
198+
if (mojo.isCrossModule()) {
199+
codePaths.addAll(crossModuleDependencies());
200+
}
201+
179202
this.log.info("Mutating from "
180-
+ this.mojo.getProject().getBuild().getOutputDirectory());
203+
+ String.join(",", codePaths));
204+
181205
data.setCodePaths(Collections.singleton(this.mojo.getProject().getBuild()
182206
.getOutputDirectory()));
207+
208+
data.setCodePaths(codePaths);
183209
}
184210

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

218-
final List<String> sourceRoots = new ArrayList<>();
219-
sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots());
220-
sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots());
244+
final List<String> sourceRoots = determineSourceRoots();
221245

222246
data.setSourceDirs(stringsToPaths(sourceRoots));
223247

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

280+
private List<String> determineSourceRoots() {
281+
final List<String> sourceRoots = new ArrayList<>();
282+
sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots());
283+
sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots());
284+
if (mojo.isCrossModule()) {
285+
List<String> otherRoots = dependedOnProjects().stream()
286+
.flatMap(p -> p.getCompileSourceRoots().stream())
287+
.collect(Collectors.toList());
288+
289+
sourceRoots.addAll(otherRoots);
290+
}
291+
return sourceRoots;
292+
}
293+
294+
private Collection<String> crossModuleDependencies() {
295+
return dependedOnProjects().stream()
296+
.map(MavenProject::getBuild)
297+
.map(Build::getOutputDirectory)
298+
.filter(Objects::nonNull)
299+
.collect(Collectors.toList());
300+
}
301+
302+
private List<MavenProject> dependedOnProjects() {
303+
// strip version from dependencies
304+
Set<String> inScope = this.mojo.getProject().getDependencies().stream()
305+
.map(p -> p.getGroupId() + ":" + p.getArtifactId())
306+
.collect(Collectors.toSet());
307+
308+
309+
return this.mojo.allProjects().stream()
310+
.filter(p -> inScope.contains(p.getGroupId() + ":" + p.getArtifactId()))
311+
.collect(Collectors.toList());
312+
313+
}
314+
256315
private void configureVerbosity(ReportOptions data) {
257316
if (this.mojo.isVerbose()) {
258317
data.setVerbosity(Verbosity.VERBOSE);
@@ -384,6 +443,8 @@ private Collection<String> useConfiguredTargetTestsOrFindOccupiedPackages(
384443
}
385444

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

432493
private Collection<String> findOccupiedPackages() {
433-
String outputDirName = this.mojo.getProject().getBuild()
434-
.getOutputDirectory();
435-
File outputDir = new File(outputDirName);
436-
return findOccupiedPackagesIn(outputDir);
494+
return Stream.concat(Stream.of(mojo.getProject()), dependedOnProjects().stream())
495+
.distinct()
496+
.map(p -> new File(p.getBuild().getOutputDirectory()))
497+
.flatMap(f -> findOccupiedPackagesIn(f).stream())
498+
.distinct()
499+
.collect(Collectors.toList());
437500
}
438501

439502
public static Collection<String> findOccupiedPackagesIn(File dir) {

pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.stream.Collectors;
2828

2929
import org.apache.maven.artifact.Artifact;
30+
import org.apache.maven.execution.MavenSession;
3031
import org.apache.maven.model.Build;
3132
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
3233
import org.apache.maven.project.MavenProject;
@@ -45,6 +46,9 @@ public abstract class BasePitMojoTest extends AbstractMojoTestCase {
4546
@Mock
4647
protected MavenProject project;
4748

49+
@Mock
50+
protected MavenSession session;
51+
4852
@Mock
4953
protected RunPitStrategy executionStrategy;
5054

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

120124
setVariableValueToObject(pitMojo, "project", this.project);
125+
setVariableValueToObject(pitMojo, "session", this.session);
121126

122127
if (pitMojo.getAdditionalClasspathElements() == null) {
123128
ArrayList<String> elements = new ArrayList<>();

0 commit comments

Comments
 (0)