From 5c3900a95a581f053d118d8bdd88f6a517acc0bc Mon Sep 17 00:00:00 2001 From: Marcel Schnelle Date: Sun, 17 Sep 2023 00:09:07 +0900 Subject: [PATCH] Add apiguardian-api runtime dependency to satisfy ART's internal runtime annotation loader Essentially, `@API` is added to most of the JUnit 5 annotations. This is an annotation from a library called "API Guardian", which declares a `RUNTIME` visibility. For Android specifically, this visibility makes it eligible for native annotation scanning when calling `Class.isAnnotationPresent()` on any of the JUnit 5 annotations with that meta-annotation. The problem is that JUnit Jupier declares its transitive dependency as "compile-only", causing `@API` to be absent at runtime. This creates a log statement every time an annotation is queried, causing very noisy logs. The fix is to submit API Guardian as a runtime-only dependency to the instrumentation core. Refs: https://github.com/junit-team/junit5/blob/70e33483530259edef1ab3c1ba12971ac3fc7db7/junit-platform-commons/src/main/java/org/junit/platform/commons/util/AnnotationUtils.java#L136C38-L136C57 https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/dex/dex_file_annotations.cc;l=780-781 Resolves #291. --- build-logic/src/main/kotlin/Dependencies.kt | 1 + instrumentation/core/build.gradle.kts | 5 +++++ .../runners/AndroidJUnitPlatformRunnerListener.kt | 14 ++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/build-logic/src/main/kotlin/Dependencies.kt b/build-logic/src/main/kotlin/Dependencies.kt index b56eda35..fc109780 100644 --- a/build-logic/src/main/kotlin/Dependencies.kt +++ b/build-logic/src/main/kotlin/Dependencies.kt @@ -38,6 +38,7 @@ object libs { const val junitVintageEngine = "org.junit.vintage:junit-vintage-engine:${versions.junitVintage}" const val junitPlatformCommons = "org.junit.platform:junit-platform-commons:${versions.junitPlatform}" const val junitPlatformRunner = "org.junit.platform:junit-platform-runner:${versions.junitPlatform}" + const val apiguardianApi = "org.apiguardian:apiguardian-api:1.1.2" const val composeBom = "androidx.compose:compose-bom:${versions.composeBom}" const val composeUi = "androidx.compose.ui:ui" diff --git a/instrumentation/core/build.gradle.kts b/instrumentation/core/build.gradle.kts index 9bdea166..66bac19b 100644 --- a/instrumentation/core/build.gradle.kts +++ b/instrumentation/core/build.gradle.kts @@ -76,6 +76,11 @@ dependencies { runtimeOnly(libs.junitPlatformRunner) runtimeOnly(libs.junitJupiterEngine) + // This transitive dependency of JUnit 5 is required to be on the runtime classpath, + // since otherwise ART will print noisy logs to console when trying to resolve any + // of the annotations of JUnit 5 (see #291 for more info) + runtimeOnly(libs.apiguardianApi) + androidTestImplementation(libs.junitJupiterApi) androidTestImplementation(libs.junitJupiterParams) androidTestImplementation(libs.espressoCore) diff --git a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformRunnerListener.kt b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformRunnerListener.kt index 65578b3e..00ed722c 100644 --- a/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformRunnerListener.kt +++ b/instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitPlatformRunnerListener.kt @@ -4,8 +4,10 @@ import android.annotation.SuppressLint import android.util.Log import de.mannodermaus.junit5.internal.LOG_TAG import org.junit.platform.engine.TestExecutionResult +import org.junit.platform.engine.reporting.ReportEntry import org.junit.platform.launcher.TestExecutionListener import org.junit.platform.launcher.TestIdentifier +import org.junit.platform.launcher.TestPlan import org.junit.runner.Description import org.junit.runner.notification.Failure import org.junit.runner.notification.RunNotifier @@ -19,6 +21,18 @@ internal class AndroidJUnitPlatformRunnerListener( private val notifier: RunNotifier ) : TestExecutionListener { + override fun testPlanExecutionStarted(testPlan: TestPlan) { + // No-op, but must be declared to avoid AbstractMethodError + } + + override fun testPlanExecutionFinished(testPlan: TestPlan) { + // No-op, but must be declared to avoid AbstractMethodError + } + + override fun reportingEntryPublished(testIdentifier: TestIdentifier?, entry: ReportEntry?) { + // No-op, but must be declared to avoid AbstractMethodError + } + override fun executionStarted(testIdentifier: TestIdentifier) { val description = testTree.getDescription(testIdentifier) if (testIdentifier.isTest) {