From 55865428bc6ffef9b1de919d9e4bca1f9d18d226 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 18 Sep 2024 13:33:23 +0100 Subject: [PATCH 1/4] make visible for 3rd party tests --- .../pitest/mutationtest/config/ClientPluginServices.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java b/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java index 4524f4a79..4d1bca470 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java +++ b/pitest/src/main/java/org/pitest/mutationtest/config/ClientPluginServices.java @@ -20,15 +20,15 @@ public static ClientPluginServices makeForContextLoader() { return new ClientPluginServices(IsolationUtils.getContextClassLoader()); } - Collection findTestFrameworkPlugins() { + public Collection findTestFrameworkPlugins() { return ServiceLoader.load(TestPluginFactory.class, this.loader); } - Collection findMutationEngines() { + public Collection findMutationEngines() { return ServiceLoader.load(MutationEngineFactory.class, this.loader); } - Collection findResets() { + public Collection findResets() { return ServiceLoader.load(EnvironmentResetPlugin.class, this.loader); } From be303c6c17c52eba04f100e2c0085aeb8f3c7742 Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 2 Oct 2024 08:30:01 +0100 Subject: [PATCH 2/4] cross module example project --- .../cross-tests-code/pom.xml | 37 +++++++++++ .../java/org/example1/SystemUnderTest.java | 24 ++++++++ .../java/org/example2/SystemUnderTest.java | 30 +++++++++ .../cross-tests-tests/pom.xml | 42 +++++++++++++ .../java/org/example1/FileOpeningTest.java | 21 +++++++ .../java/org/example2/FileOpeningTest.java | 21 +++++++ .../org/example2/SystemUnderTestTest.java | 19 ++++++ .../src/test/resources/fixture.file | 1 + .../resources/pit-cross-module-tests/pom.xml | 61 +++++++++++++++++++ 9 files changed, 256 insertions(+) create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/pom.xml create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example1/SystemUnderTest.java create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example2/SystemUnderTest.java create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/pom.xml create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example1/FileOpeningTest.java create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/FileOpeningTest.java create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/SystemUnderTestTest.java create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/resources/fixture.file create mode 100644 pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/pom.xml b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/pom.xml new file mode 100644 index 000000000..0276c4681 --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + org.example + pit-parent-module + 1.0-SNAPSHOT + + + cross-tests-code + jar + 1.0-SNAPSHOT + cross-tests-code + + + junit + junit + ${junit.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + 4.13.1 + + diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example1/SystemUnderTest.java b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example1/SystemUnderTest.java new file mode 100644 index 000000000..c5fb0fb9e --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example1/SystemUnderTest.java @@ -0,0 +1,24 @@ +package org.example1; + +public class SystemUnderTest { + + private int aNumber = 0; + + public SystemUnderTest() { + super(); + + int a = 25; + int b = 10; + if (a < b) { + aNumber = 10; + } else { + aNumber = -25; + } + } + + public String toString() { + return "SystemUnderTest"; + } + + +} \ No newline at end of file diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example2/SystemUnderTest.java b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example2/SystemUnderTest.java new file mode 100644 index 000000000..afc937966 --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-code/src/main/java/org/example2/SystemUnderTest.java @@ -0,0 +1,30 @@ +package org.example2; + +public class SystemUnderTest { + + private int aNumber = 0; + + public SystemUnderTest() { + super(); + + int a = 25; + int b = 10; + if (a < b) { + aNumber = 10; + } else { + aNumber = -25; + } + } + + public int getNumber() { + return aNumber; + } + + public String toString() { + return "SystemUnderTest"; + } + + public boolean isActive() { + return false; + } +} \ No newline at end of file diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/pom.xml b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/pom.xml new file mode 100644 index 000000000..502bf70cc --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/pom.xml @@ -0,0 +1,42 @@ + + 4.0.0 + + org.example + pit-parent-module + 1.0-SNAPSHOT + + + cross-tests-tests + jar + 1.0-SNAPSHOT + cross-tests-tests + + + junit + junit + ${junit.version} + + + org.example + cross-tests-code + ${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + 4.13.1 + + diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example1/FileOpeningTest.java b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example1/FileOpeningTest.java new file mode 100644 index 000000000..97796f0ab --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example1/FileOpeningTest.java @@ -0,0 +1,21 @@ +package org.example1; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertNotNull; + +import java.io.File; +import org.junit.Test; + +public class FileOpeningTest { + + @Test + public void testOpenFileRelativeToWorkingDirectory() { + SystemUnderTest sut = new SystemUnderTest(); + + File file = new File("src/test/resources/fixture.file"); + assertTrue(file.exists()); + assertNotNull(sut.toString()); + } + + +} \ No newline at end of file diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/FileOpeningTest.java b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/FileOpeningTest.java new file mode 100644 index 000000000..97ade29c8 --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/FileOpeningTest.java @@ -0,0 +1,21 @@ +package org.example2; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertNotNull; + +import java.io.File; +import org.junit.Test; + +public class FileOpeningTest { + + @Test + public void testOpenFileRelativeToWorkingDirectory() { + SystemUnderTest sut = new SystemUnderTest(); + + File file = new File("src/test/resources/fixture.file"); + assertTrue(file.exists()); + assertNotNull(sut.toString()); + } + + +} \ No newline at end of file diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/SystemUnderTestTest.java b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/SystemUnderTestTest.java new file mode 100644 index 000000000..d3b22fa66 --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/java/org/example2/SystemUnderTestTest.java @@ -0,0 +1,19 @@ +package org.example2; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertNotNull; + +import org.junit.Test; + +public class SystemUnderTestTest { + + @Test + public void testGetNumber() { + SystemUnderTest sut = new SystemUnderTest(); + + int result = sut.getNumber(); + assertTrue(result == -25); + } + + +} \ No newline at end of file diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/resources/fixture.file b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/resources/fixture.file new file mode 100644 index 000000000..0c759a444 --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/cross-tests-tests/src/test/resources/fixture.file @@ -0,0 +1 @@ +test123 \ No newline at end of file diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml new file mode 100644 index 000000000..955f71e24 --- /dev/null +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml @@ -0,0 +1,61 @@ + + 4.0.0 + org.example + pit-parent-module + pom + 1.0-SNAPSHOT + pit-parent-module + + + junit + junit + ${junit.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.4 + + 1.8 + 1.8 + + + + + + + + org.pitest + pitest-maven + ${pit.version} + + false + + HTML + + XML + + + true + 26 + 20 + 6 + + + + + + + + + 4.13.1 + + + cross-tests-code + cross-tests-tests + + From 6ffc3199a7b13626317b05b6a3172b16013e2bcb Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Wed, 2 Oct 2024 12:55:41 +0100 Subject: [PATCH 3/4] failing test --- .../src/test/java/org/pitest/PitMojoIT.java | 40 ++++++++++++------- .../resources/pit-cross-module-tests/pom.xml | 4 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java index 242e595c9..442a5b25b 100755 --- a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java +++ b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java @@ -360,21 +360,6 @@ public void shouldWorkWithGWTMockito() throws Exception { assertThat(actual).doesNotContain("status='RUN_ERROR'"); } - @Test - @Ignore("yatspec is not available on maven central. Repo currently down") - public void shouldWorkWithYatspec() throws Exception { - File testDir = prepare("/pit-263-yatspec"); - verifier.executeGoal("test"); - verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage"); - - String actual = readResults(testDir); - assertThat(actual) - .contains( - "SomeClass.java"); - assertThat(actual).doesNotContain("status='NO_COVERAGE'"); - assertThat(actual).doesNotContain("status='RUN_ERROR'"); - } - @Test // note this test depends on the junit5 plugin public void shouldWorkWithQuarkus() throws Exception { @@ -465,6 +450,31 @@ public void resolvesCorrectFilesForKotlinMultiModules() throws Exception { } + @Test + public void handlesTestsInSeparateModulesWhenConfigured() + throws Exception { + File testDir = prepare("/pit-cross-module-tests"); + + verifier.executeGoal("test"); + verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage"); + + verifier.executeGoal("org.pitest:pitest-maven:report-aggregate-module"); + + File siteParentDir = buildFilePath(testDir, "target", "pit-reports"); + assertThat(buildFilePath(siteParentDir, "index.html")).exists(); + String projectReportsHtmlContents = FileUtils + .readFileToString(buildFilePath(testDir, "target", "pit-reports", + "index.html")); + + assertTrue("miss data of subModule 1", + projectReportsHtmlContents + .contains("org.example1")); + + assertTrue("coverage included", + projectReportsHtmlContents + .contains("85%")); + } + private void runForJava8Only() { String javaVersion = System.getProperty("java.version"); assumeTrue(javaVersion.startsWith("1.8")); diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml index 955f71e24..d98c8fbd6 100644 --- a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml @@ -41,9 +41,7 @@ true - 26 - 20 - 6 + From a94bd6a0402b17ae956875ae3e4b3f498ba3927b Mon Sep 17 00:00:00 2001 From: Henry Coles Date: Thu, 3 Oct 2024 12:39:01 +0100 Subject: [PATCH 4/4] maven support for cross module tests --- .../pitest/classpath/DefaultCodeSource.java | 3 +- .../mutationtest/config/ReportOptions.java | 5 +- .../mutationtest/tooling/EntryPoint.java | 1 + .../src/test/java/org/pitest/PitMojoIT.java | 2 +- .../resources/pit-cross-module-tests/pom.xml | 50 +++++++++--- .../org/pitest/maven/AbstractPitMojo.java | 17 +++- .../maven/MojoToReportOptionsConverter.java | 81 ++++++++++++++++--- .../org/pitest/maven/BasePitMojoTest.java | 5 ++ .../MojoToReportOptionsConverterTest.java | 57 +++++++++---- 9 files changed, 180 insertions(+), 41 deletions(-) diff --git a/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java b/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java index c9fdc61b6..469681ba1 100644 --- a/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java +++ b/pitest-entry/src/main/java/org/pitest/classpath/DefaultCodeSource.java @@ -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; @@ -36,7 +37,7 @@ public Stream codeTrees() { } public Set getCodeUnderTestNames() { - return this.classPath.code().stream().collect(Collectors.toSet()); + return new HashSet<>(this.classPath.code()); } public Set getTestClassNames() { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java b/pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java index 723eaf20d..843fb8b9d 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/config/ReportOptions.java @@ -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; @@ -502,8 +502,7 @@ public Optional 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) { diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java index 973aad398..75be0707f 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/tooling/EntryPoint.java @@ -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); diff --git a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java index 442a5b25b..9fb9cc92c 100755 --- a/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java +++ b/pitest-maven-verification/src/test/java/org/pitest/PitMojoIT.java @@ -455,7 +455,7 @@ public void handlesTestsInSeparateModulesWhenConfigured() throws Exception { File testDir = prepare("/pit-cross-module-tests"); - verifier.executeGoal("test"); + verifier.executeGoal("install"); verifier.executeGoal("org.pitest:pitest-maven:mutationCoverage"); verifier.executeGoal("org.pitest:pitest-maven:report-aggregate-module"); diff --git a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml index d98c8fbd6..dba85b996 100644 --- a/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml +++ b/pitest-maven-verification/src/test/resources/pit-cross-module-tests/pom.xml @@ -6,13 +6,13 @@ pom 1.0-SNAPSHOT pit-parent-module - - - junit - junit - ${junit.version} - - + + + 4.13.1 + dev-SNAPSHOT + + + @@ -33,6 +33,7 @@ pitest-maven ${pit.version} + true false HTML @@ -46,12 +47,39 @@ + - - - 4.13.1 - + + + pitest + + + + org.pitest + pitest-maven + + + pitest + test-compile + + mutationCoverage + + + + + + + + + + + junit + junit + ${junit.version} + + + cross-tests-code cross-tests-tests diff --git a/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java b/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java index 514c3198b..f2560cc0f 100644 --- a/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java +++ b/pitest-maven/src/main/java/org/pitest/maven/AbstractPitMojo.java @@ -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 @@ -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."); } @@ -816,6 +823,14 @@ public RepositorySystem repositorySystem() { return repositorySystem; } + public boolean isCrossModule() { + return crossModule; + } + + public List allProjects() { + return session.getProjects(); + } + static class RunDecision { private List reasons = new ArrayList<>(4); diff --git a/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java b/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java index 7ea345b49..b90da4f20 100644 --- a/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java +++ b/pitest-maven/src/main/java/org/pitest/maven/MojoToReportOptionsConverter.java @@ -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; @@ -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 dependencyFilter; private final Log log; private final SurefireConfigConverter surefireConverter; @@ -85,6 +88,8 @@ public ReportOptions convert() { autoAddJUnitPlatform(classPath); removeExcludedDependencies(classPath); + addCrossModuleDirsToClasspath(classPath); + ReportOptions option = parseReportOptions(classPath); ReportOptions withSureFire = updateFromSurefire(option); @@ -100,6 +105,15 @@ public ReportOptions convert() { } + private void addCrossModuleDirsToClasspath(List 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 @@ -176,10 +190,22 @@ private ReportOptions parseReportOptions(final List classPath) { final ReportOptions data = new ReportOptions(); if (this.mojo.getProject().getBuild() != null) { + + List 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()); @@ -215,9 +241,7 @@ private ReportOptions parseReportOptions(final List classPath) { data.setLoggingClasses(this.mojo.getAvoidCallsTo()); } - final List sourceRoots = new ArrayList<>(); - sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots()); - sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots()); + final List sourceRoots = determineSourceRoots(); data.setSourceDirs(stringsToPaths(sourceRoots)); @@ -253,6 +277,41 @@ private ReportOptions parseReportOptions(final List classPath) { return data; } + private List determineSourceRoots() { + final List sourceRoots = new ArrayList<>(); + sourceRoots.addAll(this.mojo.getProject().getCompileSourceRoots()); + sourceRoots.addAll(this.mojo.getProject().getTestCompileSourceRoots()); + if (mojo.isCrossModule()) { + List otherRoots = dependedOnProjects().stream() + .flatMap(p -> p.getCompileSourceRoots().stream()) + .collect(Collectors.toList()); + + sourceRoots.addAll(otherRoots); + } + return sourceRoots; + } + + private Collection crossModuleDependencies() { + return dependedOnProjects().stream() + .map(MavenProject::getBuild) + .map(Build::getOutputDirectory) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private List dependedOnProjects() { + // strip version from dependencies + Set 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); @@ -384,6 +443,8 @@ private Collection useConfiguredTargetTestsOrFindOccupiedPackages( } private Collection findOccupiedTestPackages() { + // use only the tests within current project, even if in + // cross module mode String outputDirName = this.mojo.getProject().getBuild() .getTestOutputDirectory(); if (outputDirName != null) { @@ -430,10 +491,12 @@ private Collection useConfiguredTargetClassesOrFindOccupiedPackages( } private Collection 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 findOccupiedPackagesIn(File dir) { diff --git a/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java b/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java index 21419f6de..204c74e91 100644 --- a/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java +++ b/pitest-maven/src/test/java/org/pitest/maven/BasePitMojoTest.java @@ -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; @@ -45,6 +46,9 @@ public abstract class BasePitMojoTest extends AbstractMojoTestCase { @Mock protected MavenProject project; + @Mock + protected MavenSession session; + @Mock protected RunPitStrategy executionStrategy; @@ -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 elements = new ArrayList<>(); diff --git a/pitest-maven/src/test/java/org/pitest/maven/MojoToReportOptionsConverterTest.java b/pitest-maven/src/test/java/org/pitest/maven/MojoToReportOptionsConverterTest.java index 7edcf0798..3b741cc1c 100644 --- a/pitest-maven/src/test/java/org/pitest/maven/MojoToReportOptionsConverterTest.java +++ b/pitest-maven/src/test/java/org/pitest/maven/MojoToReportOptionsConverterTest.java @@ -17,23 +17,20 @@ import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.model.Build; +import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; +import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.xml.Xpp3Dom; -import org.junit.rules.TemporaryFolder; import org.mockito.Mockito; import org.pitest.mutationtest.config.ConfigOption; import org.pitest.mutationtest.config.ReportOptions; import org.pitest.util.Unchecked; import java.io.File; -import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -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.HashSet; @@ -41,6 +38,7 @@ import java.util.Set; import java.util.function.Predicate; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.AdditionalAnswers.returnsFirstArg; @@ -92,9 +90,9 @@ public void testCreatesPredicateFromListOfTargetClassGlobs() { } public void testUsesSourceDirectoriesFromProject() { - when(this.project.getCompileSourceRoots()).thenReturn(Arrays.asList("src")); + when(this.project.getCompileSourceRoots()).thenReturn(asList("src")); when(this.project.getTestCompileSourceRoots()).thenReturn( - Arrays.asList("tst")); + asList("tst")); final ReportOptions actual = parseConfig(""); assertThat(actual.getSourcePaths()).containsExactly(Paths.get("src"), Paths.get("tst")); } @@ -125,7 +123,7 @@ public void testParsesListOfMutationOperators() { " bar" + // " "; final ReportOptions actual = parseConfig(xml); - assertEquals(Arrays.asList("foo", "bar"), actual.getMutators()); + assertEquals(asList("foo", "bar"), actual.getMutators()); } public void testParsesListOfFeatures() { @@ -205,7 +203,7 @@ public void testParsesListOfClassesToAvoidCallTo() { " foo.bar" + // " "; final ReportOptions actual = parseConfig(xml); - assertEquals(Arrays.asList("foo", "bar", "foo.bar"), + assertEquals(asList("foo", "bar", "foo.bar"), actual.getLoggingClasses()); } @@ -244,7 +242,7 @@ public void testParsesDetectInlineCodeFlag() { public void testDefaultsToHtmlReportWhenNoOutputFormatsSpecified() { final ReportOptions actual = parseConfig(""); - assertEquals(new HashSet<>(Arrays.asList("HTML")), + assertEquals(new HashSet<>(asList("HTML")), actual.getOutputFormats()); } @@ -254,7 +252,7 @@ public void testParsesListOfOutputFormatsWhenSupplied() { " CSV" + // " "; final ReportOptions actual = parseConfig(xml); - assertEquals(new HashSet<>(Arrays.asList("HTML", "CSV")), + assertEquals(new HashSet<>(asList("HTML", "CSV")), actual.getOutputFormats()); } @@ -276,19 +274,19 @@ public void testObeysSkipFailingTestsFlagWhenPackagingTypeIsNotPOM() { public void testParsesTestGroupsToExclude() { final ReportOptions actual = parseConfig("foobar"); - assertEquals(Arrays.asList("foo", "bar"), actual.getGroupConfig() + assertEquals(asList("foo", "bar"), actual.getGroupConfig() .getExcludedGroups()); } public void testParsesTestGroupsToInclude() { final ReportOptions actual = parseConfig("foobar"); - assertEquals(Arrays.asList("foo", "bar"), actual.getGroupConfig() + assertEquals(asList("foo", "bar"), actual.getGroupConfig() .getIncludedGroups()); } public void testParsesTestMethodsToInclude() { final ReportOptions actual = parseConfig("foobar"); - assertEquals(Arrays.asList("foo", "bar"), actual + assertEquals(asList("foo", "bar"), actual .getIncludedTestMethods()); } @@ -388,7 +386,7 @@ public void testParsesExcludedClasspathElements() artifacts.add(dependency); when(this.project.getArtifacts()).thenReturn(artifacts); when(this.project.getTestClasspathElements()).thenReturn( - Arrays.asList("group" + sep + "artifact" + sep + "1.0.0" + sep + asList("group" + sep + "artifact" + sep + "1.0.0" + sep + "group-artifact-1.0.0.jar")); final ReportOptions actual = parseConfig("" @@ -470,6 +468,35 @@ public void testEvaluatesNormalPropertiesInArgLines() { assertThat(actual.getArgLine()).isEqualTo("fooValue barValue"); } + public void testAddsModulesToMutationPathWhenCrossModule() { + MavenProject dependedOn = project("com.example", "foo"); + MavenProject notDependedOn = project("com.example", "bar"); + + when(session.getProjects()).thenReturn(asList(dependedOn, notDependedOn)); + + Dependency dependency = new Dependency(); + dependency.setGroupId("com.example"); + dependency.setArtifactId("foo"); + when(project.getDependencies()).thenReturn(asList(dependency)); + + final ReportOptions actual = parseConfig("true"); + + assertThat(actual.getCodePaths()).contains("foobuild"); + assertThat(actual.getCodePaths()).doesNotContain("barbuild"); + } + + private static MavenProject project(String group, String artefact) { + MavenProject dependedOn = new MavenProject(); + dependedOn.setGroupId(group); + dependedOn.setArtifactId(artefact); + + Build build = new Build(); + build.setOutputDirectory(artefact + "build"); + dependedOn.setBuild(build); + + return dependedOn; + } + private ReportOptions parseConfig(final String xml) { try { final String pom = createPomWithConfiguration(xml);