Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add support for JUnit 5.12 #367

Merged
merged 4 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions build-logic/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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}"

Expand Down
7 changes: 7 additions & 0 deletions build-logic/src/main/kotlin/Deployment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 */
Expand Down
5 changes: 3 additions & 2 deletions build-logic/src/main/kotlin/Environment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
Expand All @@ -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(
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions build-logic/src/main/kotlin/Tasks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
3 changes: 3 additions & 0 deletions instrumentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String>()
override fun getBoolean(key: String?) = Optional.empty<Boolean>()
override fun keySet() = emptySet<String>()

@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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String>()
override fun getBoolean(key: String?) = Optional.empty<Boolean>()
override fun keySet() = emptySet<String>()

@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()) }

Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("<unrooted>"),
/* name = */ name,
Expand Down Expand Up @@ -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<TestIdentifier> {
// Because the overridden "getParent()" from the superclass is modified,
Expand Down
2 changes: 2 additions & 0 deletions plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions plugin/android-junit5/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ dependencies {
testImplementation(libs.junitJupiterApi)
testImplementation(libs.junitJupiterParams)
testRuntimeOnly(libs.junitJupiterEngine)
testRuntimeOnly(libs.junitPlatformLauncher)
}

project.configureDeployment(Artifacts.Plugin)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
}
Expand Down
2 changes: 2 additions & 0 deletions plugin/android-junit5/src/main/templates/Libraries.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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@"
}