From 1b2906247afd77b46f370ee5cca58545a58b73fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Zaj=C4=85czkowski?= <148013+szpak@users.noreply.github.com> Date: Wed, 20 Sep 2023 23:39:20 +0200 Subject: [PATCH] [#337] Automatically add junit-platform-launcher to testRuntimeOnly ... for JUnit Platform projects to avoid "UNKNOWN_ERROR" or: "NoClassDefFoundError: org.junit.platform.launcher.core.LauncherFactory" with PIT 1.14.0+ (with pitest-junit-plugin 1.2.0+). That dependency is no longer shaded. More details: https://github.com/szpak/gradle-pitest-plugin/issues/337 --- CHANGELOG.md | 11 +++- .../functional/Junit5FunctionalSpec.groovy | 6 ++- .../testProjects/junit5kotlin/build.gradle | 6 +-- .../build-pit-plugin-1.0.0-junit-5.8.gradle | 33 ++++++++++++ .../testProjects/junit5simple/build.gradle | 11 ++-- .../gradle/pitest/PitestPlugin.groovy | 50 +++++++++++++++++++ .../pitest/PitestPluginExtension.groovy | 18 +++++++ 7 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 src/funcTest/resources/testProjects/junit5simple/build-pit-plugin-1.0.0-junit-5.8.gradle diff --git a/CHANGELOG.md b/CHANGELOG.md index 33fe7905..15c8aa9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,18 @@ ## 1.14.0 - Unreleased - - Remove deprecated Project.getConvention() usage (in Gradle 8.2+) - [#343](https://github.com/szpak/gradle-pitest-plugin/issues/343) + - Automatically add `junit-platform-launcher` dependency to `testRuntimeOnly` for JUnit Platform projects - [#337](https://github.com/szpak/gradle-pitest-plugin/issues/337) - help from [Björn Kautler](https://github.com/Vampire) + - Remove deprecated `Project.getConvention()` usage (in Gradle 8.2+) - [#343](https://github.com/szpak/gradle-pitest-plugin/issues/343) - Basic regression testing with Gradle up to 8.2 +**Compatibility notes** +Starting with PIT 1.14.0 (with pitest-junit-plugin 1.2.0+) `junit-platform-launcher` is no longer shaded and has to be explicitly added to avoid: +"Minion exited abnormally due to UNKNOWN_ERROR" or "NoClassDefFoundError: org.junit.platform.launcher.core.LauncherFactory". + +As an experimental (incubating) feature, `junit-platform-launcher` is automatically added to the `testRuntimeOnly` configuration for the JUnit Platform projects. + +**PLEASE NOTE**. This feature is experimental and might not work as expected in some corner cases. In that situation, just disable it with `addJUnitPlatformLauncher = false` and add the required dependency 'junit-platform-launcher' in a proper version to 'testRuntimeOnly' manually. More information: https://github.com/szpak/gradle-pitest-plugin/issues/337 + ## 1.9.11 - 2022-11-27 diff --git a/src/funcTest/groovy/info/solidsoft/gradle/pitest/functional/Junit5FunctionalSpec.groovy b/src/funcTest/groovy/info/solidsoft/gradle/pitest/functional/Junit5FunctionalSpec.groovy index 637ed1a5..e5cae986 100644 --- a/src/funcTest/groovy/info/solidsoft/gradle/pitest/functional/Junit5FunctionalSpec.groovy +++ b/src/funcTest/groovy/info/solidsoft/gradle/pitest/functional/Junit5FunctionalSpec.groovy @@ -33,7 +33,8 @@ class Junit5FunctionalSpec extends AbstractPitestFunctionalSpec { result.standardOutput.contains('Generated 2 mutations Killed 2 (100%)') } - @Issue(["https://github.com/szpak/gradle-pitest-plugin/issues/177", "https://github.com/szpak/gradle-pitest-plugin/issues/300"]) + @Issue(["https://github.com/szpak/gradle-pitest-plugin/issues/177", "https://github.com/szpak/gradle-pitest-plugin/issues/300", + "https://github.com/szpak/gradle-pitest-plugin/issues/337"]) void "should work with junit5 without explicitly adding dependency (#description)"() { given: copyResources("testProjects/junit5simple", "") @@ -51,7 +52,8 @@ class Junit5FunctionalSpec extends AbstractPitestFunctionalSpec { result.standardOutput.contains("junit-platform-commons-${expectedJUnitPlatformVersion}.jar") where: buildFileName || expectedJunitPluginVersion | expectedJUnitJupiterVersion | expectedJUnitPlatformVersion - 'build.gradle' || "1.0.0" | "5.8.0" | "1.8.0" + 'build.gradle' || "1.2.0" | "5.10.0" | "1.10.0" + 'build-pit-plugin-1.0.0-junit-5.8.gradle' || "1.0.0" | "5.8.0" | "1.8.0" 'build-pit-1.8-junit-platform-5.7.gradle' || "0.14" | "5.7.0" | "1.7.0" description = "plugin $expectedJunitPluginVersion, junit $expectedJUnitJupiterVersion, platform $expectedJUnitPlatformVersion" diff --git a/src/funcTest/resources/testProjects/junit5kotlin/build.gradle b/src/funcTest/resources/testProjects/junit5kotlin/build.gradle index f01834d5..377e9aff 100644 --- a/src/funcTest/resources/testProjects/junit5kotlin/build.gradle +++ b/src/funcTest/resources/testProjects/junit5kotlin/build.gradle @@ -1,7 +1,7 @@ buildscript { ext.kotlin_version = '1.3.61' - ext.junit5Version = '5.7.0' - ext.junitPlatformVersion = '1.7.0' + ext.junit5Version = '5.10.0' + ext.junitPlatformVersion = '1.10.0' repositories { mavenCentral() @@ -31,7 +31,7 @@ dependencies { testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junit5Version" testImplementation "org.junit.platform:junit-platform-runner:$junitPlatformVersion" - pitest 'org.pitest:pitest-junit5-plugin:1.0.0' + pitest 'org.pitest:pitest-junit5-plugin:1.2.0' } compileKotlin { diff --git a/src/funcTest/resources/testProjects/junit5simple/build-pit-plugin-1.0.0-junit-5.8.gradle b/src/funcTest/resources/testProjects/junit5simple/build-pit-plugin-1.0.0-junit-5.8.gradle new file mode 100644 index 00000000..58a82f0b --- /dev/null +++ b/src/funcTest/resources/testProjects/junit5simple/build-pit-plugin-1.0.0-junit-5.8.gradle @@ -0,0 +1,33 @@ +apply plugin: 'java' +apply plugin: 'info.solidsoft.pitest' + +/* +//Local/current version of the plugin should be put on a classpath earlier to override that plugin version +buildscript { + repositories { + mavenCentral() + mavenLocal() + } + dependencies { + classpath 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:X.Y.Z-SNAPSHOT' + } +} +*/ + +repositories { + mavenCentral() +} + +group = "pitest.test" + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.0' +} + +test { + useJUnitPlatform() +} + +pitest { + junit5PluginVersion = "1.0.0" +} diff --git a/src/funcTest/resources/testProjects/junit5simple/build.gradle b/src/funcTest/resources/testProjects/junit5simple/build.gradle index afdf0cb5..c9747f0e 100644 --- a/src/funcTest/resources/testProjects/junit5simple/build.gradle +++ b/src/funcTest/resources/testProjects/junit5simple/build.gradle @@ -18,15 +18,10 @@ repositories { mavenCentral() } -dependencies { -// //Not needed, 'junit5PluginVersion' should implicitly add it in requested version -// pitest 'org.pitest:pitest-junit5-plugin:1.0.0' -} - group = "pitest.test" dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.0' + testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0' } test { @@ -34,5 +29,7 @@ test { } pitest { - junit5PluginVersion = "1.0.0" + pitestVersion = "1.14.4" + junit5PluginVersion = "1.2.0" //with no longer shaded junit-platform-launcher + verbose = true //for "ClassNotFoundException: org.junit.platform.launcher.core.LauncherFactory" which should not happen } diff --git a/src/main/groovy/info/solidsoft/gradle/pitest/PitestPlugin.groovy b/src/main/groovy/info/solidsoft/gradle/pitest/PitestPlugin.groovy index 7a3eee5c..c264b6db 100644 --- a/src/main/groovy/info/solidsoft/gradle/pitest/PitestPlugin.groovy +++ b/src/main/groovy/info/solidsoft/gradle/pitest/PitestPlugin.groovy @@ -23,6 +23,9 @@ import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.artifacts.result.ResolutionResult +import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.file.FileCollection import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging @@ -112,6 +115,7 @@ class PitestPlugin implements Plugin { extension.fileExtensionsToFilter.set(DEFAULT_FILE_EXTENSIONS_TO_FILTER_FROM_CLASSPATH) extension.useClasspathFile.set(false) extension.verbosity.set("NO_SPINNER") + extension.addJUnitPlatformLauncher.set(true) } private void failWithMeaningfulErrorMessageOnUnsupportedConfigurationInRootProjectBuildScript() { @@ -239,6 +243,52 @@ class PitestPlugin implements Plugin { dependencies.add(project.dependencies.create(junit5PluginDependencyAsString)) } } + + addJUnitPlatformLauncherDependencyIfNeeded() + } + + private void addJUnitPlatformLauncherDependencyIfNeeded() { + Configuration testImplementation = project.configurations.findByName("testImplementation") + testImplementation.withDependencies { directDependencies -> + if (!extension.addJUnitPlatformLauncher.isPresent() || !extension.addJUnitPlatformLauncher.get()) { + log.info("'addJUnitPlatformLauncher' feature explicitly disabled in configuration. " + + "Add junit-platform-launcher manually or expect 'Minion exited abnormally due to UNKNOWN_ERROR' or 'NoClassDefFoundError'") + return + } + + //Note: For simplicity, adding also for older pitest-junit5-plugin versions (<1.2.0), which is not needed + + final String orgJUnitPlatformGroup = "org.junit.platform" + + log.debug("Direct ${testImplementation.name} dependencies (${directDependencies.size()}): ${directDependencies}") + + //copy() seems to copy also something that refers to original configuration and generates StackOverflow on getting components + Configuration tmpTestImplementation = project.configurations.maybeCreate("tmpTestImplementation") + directDependencies.each { directDependency -> + tmpTestImplementation.dependencies.add(directDependency) + } + + ResolutionResult resolutionResult = tmpTestImplementation.incoming.resolutionResult + Set allResolvedComponents = resolutionResult.allComponents + log.debug("All resolved components ${testImplementation.name} (${allResolvedComponents.size()}): ${allResolvedComponents}") + + ResolvedComponentResult foundJunitPlatformComponent = allResolvedComponents.find { ResolvedComponentResult componentResult -> + ModuleVersionIdentifier moduleVersion = componentResult.moduleVersion + return moduleVersion.group == orgJUnitPlatformGroup && + (moduleVersion.name == "junit-platform-engine" || moduleVersion.name == "junit-platform-commons") + } + + if (!foundJunitPlatformComponent) { + log.info("No ${orgJUnitPlatformGroup} components founds in ${testImplementation.name}, junit-platform-launcher will not be added") + return + } + + String junitPlatformLauncherDependencyAsString = "${orgJUnitPlatformGroup}:junit-platform-launcher:${foundJunitPlatformComponent.moduleVersion.version}" + log.info("${orgJUnitPlatformGroup} component (${foundJunitPlatformComponent}) found in ${testImplementation.name}, " + + "adding junit-platform-launcher (${junitPlatformLauncherDependencyAsString}) to testRuntimeOnly") + project.configurations.findByName("testRuntimeOnly").dependencies.add( + project.dependencies.create(junitPlatformLauncherDependencyAsString)) + } } private void suppressPassingDeprecatedTestPluginForNewerPitVersions(PitestTask pitestTask) { diff --git a/src/main/groovy/info/solidsoft/gradle/pitest/PitestPluginExtension.groovy b/src/main/groovy/info/solidsoft/gradle/pitest/PitestPluginExtension.groovy index 48075706..744e03e6 100644 --- a/src/main/groovy/info/solidsoft/gradle/pitest/PitestPluginExtension.groovy +++ b/src/main/groovy/info/solidsoft/gradle/pitest/PitestPluginExtension.groovy @@ -250,6 +250,23 @@ class PitestPluginExtension { @Incubating final ListProperty fileExtensionsToFilter + /** + * Adds 'junit-platform-launcher' automatically to the 'testRuntimeOnly' configuration. + * + * Starting with PIT 1.14.0 (with pitest-junit-plugin 1.2.0+) that dependency is no longer shaded and has to be explicitly added to avoid: + * "Minion exited abnormally due to UNKNOWN_ERROR" or "NoClassDefFoundError: org.junit.platform.launcher.core.LauncherFactory". + * This feature is enabled by default if junit-platform is found on the testImplementation classes. + * + * PLEASE NOTE. This feature is experimental and might not work as expected in some corner cases. In that situation, just disable it and add + * required dependency 'junit-platform-launcher' in a proper version to 'testRuntimeOnly' manually. + * + * More information: https://github.com/szpak/gradle-pitest-plugin/issues/337 + * + * @since 1.14.0 + */ + @Incubating + final Property addJUnitPlatformLauncher + final ReportAggregatorProperties reportAggregatorProperties PitestPluginExtension(Project project) { @@ -303,6 +320,7 @@ class PitestPluginExtension { outputCharset = of.property(Charset) features = nullListPropertyOf(p, String) fileExtensionsToFilter = nullListPropertyOf(p, String) + addJUnitPlatformLauncher = of.property(Boolean) reportAggregatorProperties = new ReportAggregatorProperties(of) }