diff --git a/build-logic/src/main/kotlin/Dependencies.kt b/build-logic/src/main/kotlin/Dependencies.kt index 6e3ec94b..79ed8c7f 100644 --- a/build-logic/src/main/kotlin/Dependencies.kt +++ b/build-logic/src/main/kotlin/Dependencies.kt @@ -3,9 +3,9 @@ object libs { object versions { const val kotlin = "1.9.25" - const val junitJupiter = "5.11.4" - const val junitVintage = "5.11.4" - const val junitPlatform = "1.11.4" + const val junitJupiter = "5.12.0" + const val junitVintage = "5.12.0" + const val junitPlatform = "1.12.0" const val composeBom = "2024.09.00" const val androidXTestAnnotation = "1.0.1" @@ -55,6 +55,7 @@ object libs { const val junitJupiterEngine = "org.junit.jupiter:junit-jupiter-engine:${versions.junitJupiter}" const val junitVintageEngine = "org.junit.vintage:junit-vintage-engine:${versions.junitVintage}" const val junitPlatformCommons = "org.junit.platform:junit-platform-commons:${versions.junitPlatform}" + const val junitPlatformLauncher = "org.junit.platform:junit-platform-launcher:${versions.junitPlatform}" const val junitPlatformRunner = "org.junit.platform:junit-platform-runner:${versions.junitPlatform}" const val apiguardianApi = "org.apiguardian:apiguardian-api:${versions.apiGuardian}" diff --git a/build-logic/src/main/kotlin/Deployment.kt b/build-logic/src/main/kotlin/Deployment.kt index b11cfabe..df98769b 100644 --- a/build-logic/src/main/kotlin/Deployment.kt +++ b/build-logic/src/main/kotlin/Deployment.kt @@ -15,6 +15,7 @@ import org.gradle.configurationcache.extensions.capitalized import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.support.uppercaseFirstChar import org.gradle.kotlin.dsl.withGroovyBuilder +import org.gradle.plugins.signing.Sign import org.gradle.plugins.signing.SigningExtension import java.io.File @@ -112,6 +113,12 @@ fun Project.configureDeployment(deployConfig: Deployed) { signing { sign(publishing.publications) } + + // Connect signing task to artifact-producing task + // (build an AAR for Android modules, assemble a JAR for other modules) + tasks.withType(Sign::class.java).configureEach { + dependsOn(if (isAndroid) "bundleReleaseAar" else "assemble") + } } /* Private */ diff --git a/build-logic/src/main/kotlin/Environment.kt b/build-logic/src/main/kotlin/Environment.kt index 1e29c89a..f553f095 100644 --- a/build-logic/src/main/kotlin/Environment.kt +++ b/build-logic/src/main/kotlin/Environment.kt @@ -91,7 +91,7 @@ object Artifacts { platform = Java, groupId = "de.mannodermaus.gradle.plugins", artifactId = "android-junit5", - currentVersion = "1.11.4.0-SNAPSHOT", + currentVersion = "1.12.0.0-SNAPSHOT", latestStableVersion = "1.11.3.0", description = "Unit Testing with JUnit 5 for Android." ) @@ -101,7 +101,7 @@ object Artifacts { */ object Instrumentation { const val groupId = "de.mannodermaus.junit5" - private const val currentVersion = "1.6.1-SNAPSHOT" + private const val currentVersion = "1.7.0-SNAPSHOT" private const val latestStableVersion = "1.6.0" val Core = Deployed( @@ -156,6 +156,7 @@ class DeployedCredentials(private val project: Project) { // // * Local development: // Stored in local.properties file on the machine + // (in the root folder of the project – the one containing "plugin/" and "instrumentation/") // * CI Server: // Stored in environment variables before launch val properties = Properties().apply { diff --git a/build-logic/src/main/kotlin/Tasks.kt b/build-logic/src/main/kotlin/Tasks.kt index f44a2465..43a26ad4 100644 --- a/build-logic/src/main/kotlin/Tasks.kt +++ b/build-logic/src/main/kotlin/Tasks.kt @@ -166,6 +166,10 @@ fun Copy.configureCreateVersionClassTask( // Find an appropriate version of the instrumentation library, // depending on the version of how the plugin is configured "INSTRUMENTATION_VERSION" to instrumentationVersion, + + // JUnit 5.12+ requires the platform launcher on the runtime classpath; + // to prevent issues with version mismatching, the plugin applies this for users + "JUNIT_PLATFORM_LAUNCHER" to libs.junitPlatformLauncher ) ), ReplaceTokens::class.java ) diff --git a/instrumentation/CHANGELOG.md b/instrumentation/CHANGELOG.md index d0e43b53..7b244ea1 100644 --- a/instrumentation/CHANGELOG.md +++ b/instrumentation/CHANGELOG.md @@ -3,6 +3,9 @@ Change Log ## Unreleased +- **This version requires (at least) android-junit5 1.12.0.0 and JUnit 5.12.0.** +- Migrate to new TestPlan API in JUnit 5.12, which changed in a binary-incompatible fashion + ## 1.6.0 (2024-10-05) - Use square brackets for parameterized tests to ensure that their logs show correctly in the IDE (#350) diff --git a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/discovery/EmptyTestPlan.kt b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/discovery/EmptyTestPlan.kt new file mode 100644 index 00000000..585cadda --- /dev/null +++ b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/discovery/EmptyTestPlan.kt @@ -0,0 +1,38 @@ +package de.mannodermaus.junit5.internal.discovery + +import androidx.annotation.RequiresApi +import org.junit.platform.engine.ConfigurationParameters +import org.junit.platform.engine.TestDescriptor +import org.junit.platform.engine.reporting.OutputDirectoryProvider +import org.junit.platform.launcher.TestPlan +import java.io.File +import java.util.Optional + +/** + * A JUnit TestPlan that does absolutely nothing. + * Used by [de.mannodermaus.junit5.internal.runners.AndroidJUnit5] whenever a class + * is not loadable through the JUnit Platform and should be discarded. + */ +@RequiresApi(26) +internal object EmptyTestPlan : TestPlan( + false, + emptyConfigurationParameters, + emptyOutputDirectoryProvider +) + +@RequiresApi(26) +private val emptyConfigurationParameters = object : ConfigurationParameters { + override fun get(key: String?) = Optional.empty() + override fun getBoolean(key: String?) = Optional.empty() + override fun keySet() = emptySet() + + @Deprecated("Deprecated in Java", ReplaceWith("keySet().size")) + override fun size() = 0 +} + +@RequiresApi(26) +private val emptyOutputDirectoryProvider = object : OutputDirectoryProvider { + private val path = File.createTempFile("empty-output", ".nop").toPath() + override fun getRootDirectory() = path + override fun createOutputDirectory(testDescriptor: TestDescriptor?) = path +} diff --git a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnit5.kt b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnit5.kt index 6b27240a..b5d2b5ae 100644 --- a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnit5.kt +++ b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnit5.kt @@ -2,6 +2,7 @@ package de.mannodermaus.junit5.internal.runners import androidx.annotation.RequiresApi import androidx.annotation.VisibleForTesting +import de.mannodermaus.junit5.internal.discovery.EmptyTestPlan import de.mannodermaus.junit5.internal.runners.notification.ParallelRunNotifier import org.junit.platform.commons.JUnitException import org.junit.platform.engine.ConfigurationParameters @@ -25,19 +26,6 @@ internal class AndroidJUnit5( private val testClass: Class<*>, paramsSupplier: () -> AndroidJUnit5RunnerParams = AndroidJUnit5RunnerParams.Companion::create, ) : Runner() { - private companion object { - private val emptyConfigurationParameters = object : ConfigurationParameters { - override fun get(key: String?) = Optional.empty() - override fun getBoolean(key: String?) = Optional.empty() - override fun keySet() = emptySet() - - @Deprecated("Deprecated in Java", ReplaceWith("keySet().size")) - override fun size() = 0 - } - - private val emptyTestPlan = TestPlan.from(emptyList(), emptyConfigurationParameters) - } - private val launcher = LauncherFactory.create() private val testTree by lazy { generateTestTree(paramsSupplier()) } @@ -80,7 +68,7 @@ internal class AndroidJUnit5( // or anything else not present in the Android runtime). // Log those to console, but discard them from being considered at all e.printStackTrace() - emptyTestPlan + EmptyTestPlan } return AndroidJUnitPlatformTestTree( diff --git a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformTestTree.kt b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformTestTree.kt index f7352ba7..35d72980 100644 --- a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformTestTree.kt +++ b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformTestTree.kt @@ -137,7 +137,8 @@ internal class AndroidJUnitPlatformTestTree( return if (identifier.isTest || identifier.isDynamicTest) { Description.createTestDescription( - /* className = */ testPlan.getParent(identifier) + /* className = */ + testPlan.getParent(identifier) .map(nameExtractor) .orElse(""), /* name = */ name, @@ -180,7 +181,11 @@ internal class AndroidJUnitPlatformTestTree( * Custom drop-in TestPlan for Android purposes. */ private class ModifiedTestPlan(val delegate: TestPlan) : - TestPlan(delegate.containsTests(), delegate.configurationParameters) { + TestPlan( + /* containsTests = */ delegate.containsTests(), + /* configurationParameters = */ delegate.configurationParameters, + /* outputDirectoryProvider = */ delegate.outputDirectoryProvider + ) { fun getRealParent(child: TestIdentifier?): Optional { // Because the overridden "getParent()" from the superclass is modified, diff --git a/plugin/CHANGELOG.md b/plugin/CHANGELOG.md index cf0617e4..babbc266 100644 --- a/plugin/CHANGELOG.md +++ b/plugin/CHANGELOG.md @@ -2,6 +2,8 @@ Change Log ========== ## Unreleased +- JUnit 5.12.0 +- Add dependency on JUnit Platform Launcher to runtime classpath, accommodating an upstream change ## 1.11.3.0 (2024-12-23) - JUnit 5.11.3 diff --git a/plugin/android-junit5/build.gradle.kts b/plugin/android-junit5/build.gradle.kts index 0ce841ba..d2cdbbbe 100644 --- a/plugin/android-junit5/build.gradle.kts +++ b/plugin/android-junit5/build.gradle.kts @@ -114,6 +114,7 @@ dependencies { testImplementation(libs.junitJupiterApi) testImplementation(libs.junitJupiterParams) testRuntimeOnly(libs.junitJupiterEngine) + testRuntimeOnly(libs.junitPlatformLauncher) } project.configureDeployment(Artifacts.Plugin) diff --git a/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt b/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt index e7eff939..87260031 100644 --- a/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt +++ b/plugin/android-junit5/src/main/kotlin/de/mannodermaus/gradle/plugins/junit5/internal/configureJUnit5.kt @@ -148,13 +148,15 @@ private fun AndroidJUnitPlatformExtension.attachDependencies( includeRunner: Boolean, ) { if (project.usesJUnitJupiterIn(configurationName)) { + val runtimeOnlyConfigurationName = configurationName.replace("Implementation", "RuntimeOnly") val version = instrumentationTests.version.get() + project.dependencies.add(runtimeOnlyConfigurationName, Libraries.junitPlatformLauncher) project.dependencies.add(configurationName, "${Libraries.instrumentationCore}:$version") if (includeRunner) { project.dependencies.add( - configurationName.replace("Implementation", "RuntimeOnly"), + runtimeOnlyConfigurationName, "${Libraries.instrumentationRunner}:$version", ) } diff --git a/plugin/android-junit5/src/main/templates/Libraries.kt b/plugin/android-junit5/src/main/templates/Libraries.kt index b992a9b2..5846555f 100644 --- a/plugin/android-junit5/src/main/templates/Libraries.kt +++ b/plugin/android-junit5/src/main/templates/Libraries.kt @@ -6,4 +6,6 @@ internal object Libraries { const val instrumentationCore = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_CORE@" const val instrumentationExtensions = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_EXTENSIONS@" const val instrumentationRunner = "@INSTRUMENTATION_GROUP@:@INSTRUMENTATION_RUNNER@" + + const val junitPlatformLauncher = "@JUNIT_PLATFORM_LAUNCHER@" }