Skip to content

Commit

Permalink
[#337] Automatically add junit-platform-launcher to testRuntimeOnly
Browse files Browse the repository at this point in the history
... 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:
#337
  • Loading branch information
szpak committed Sep 20, 2023
1 parent cbc3201 commit 1b29062
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 13 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", "")
Expand All @@ -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"
Expand Down
6 changes: 3 additions & 3 deletions src/funcTest/resources/testProjects/junit5kotlin/build.gradle
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
11 changes: 4 additions & 7 deletions src/funcTest/resources/testProjects/junit5simple/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@ 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 {
useJUnitPlatform()
}

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
}
50 changes: 50 additions & 0 deletions src/main/groovy/info/solidsoft/gradle/pitest/PitestPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -112,6 +115,7 @@ class PitestPlugin implements Plugin<Project> {
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() {
Expand Down Expand Up @@ -239,6 +243,52 @@ class PitestPlugin implements Plugin<Project> {
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<ResolvedComponentResult> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,23 @@ class PitestPluginExtension {
@Incubating
final ListProperty<String> 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<Boolean> addJUnitPlatformLauncher

final ReportAggregatorProperties reportAggregatorProperties

PitestPluginExtension(Project project) {
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit 1b29062

Please sign in to comment.