From 9a34708b1f4a70be15dae889e57ba315d5e05eeb Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sat, 25 Jul 2026 23:40:01 +0100 Subject: [PATCH] Share Robolectric JVM setup in a convention plugin Both okhttp and android-test configure their test JVMs for Robolectric, in different ways: android-test opened nine JDK packages via the Android Gradle Plugin's unitTests DSL, while okhttp stripped the --add-opens flags the plugin adds when running on Java 8. Move both into okhttp.robolectric-conventions so there is one place describing what Robolectric needs from the JVM. Also skip tests that can only run on an Android SDK the test JVM can't sandbox. Robolectric needs Java 21 to build an SDK 37 sandbox, and fails the test class outright on Java 17. Setting robolectric.enabledSdks below Java 21 leaves those tests unselected instead, which scales as more of them arrive without naming each one in the build. --- android-test/build.gradle.kts | 17 +------ .../okhttp.robolectric-conventions.gradle.kts | 45 +++++++++++++++++++ okhttp/build.gradle.kts | 15 +------ 3 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 build-logic/src/main/kotlin/okhttp.robolectric-conventions.gradle.kts diff --git a/android-test/build.gradle.kts b/android-test/build.gradle.kts index bc0e5212c78b..d2af2bee97d3 100644 --- a/android-test/build.gradle.kts +++ b/android-test/build.gradle.kts @@ -3,6 +3,7 @@ import okhttp3.buildsupport.androidBuild plugins { id("okhttp.base-conventions") + id("okhttp.robolectric-conventions") id("com.android.library") id("de.mannodermaus.android-junit5") } @@ -43,22 +44,6 @@ android { testOptions { targetSdk = 37 unitTests.isIncludeAndroidResources = true - - // Robolectric 4.17 reflects into JDK internals, which JDK 17+ blocks by default. - // https://robolectric.org/getting-started/ - unitTests.all { - it.jvmArgs( - "--add-opens=java.base/java.lang=ALL-UNNAMED", - "--add-opens=java.base/java.util=ALL-UNNAMED", - "--add-opens=java.base/java.io=ALL-UNNAMED", - "--add-opens=java.base/java.net=ALL-UNNAMED", - "--add-opens=java.base/java.security=ALL-UNNAMED", - "--add-opens=java.base/java.text=ALL-UNNAMED", - "--add-opens=java.base/jdk.internal.access=ALL-UNNAMED", - "--add-opens=java.desktop/java.awt.font=ALL-UNNAMED", - "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - ) - } } diff --git a/build-logic/src/main/kotlin/okhttp.robolectric-conventions.gradle.kts b/build-logic/src/main/kotlin/okhttp.robolectric-conventions.gradle.kts new file mode 100644 index 000000000000..3663adc984de --- /dev/null +++ b/build-logic/src/main/kotlin/okhttp.robolectric-conventions.gradle.kts @@ -0,0 +1,45 @@ +import okhttp3.buildsupport.testJavaVersion + +/** + * Shared configuration for the modules that run Robolectric tests. + * + * Robolectric reflects into JDK internals, which JDK 17+ blocks by default, and its newer Android + * images need a newer JVM. https://robolectric.org/getting-started/ + */ + +/** Opened so Robolectric can reflect into the JDK internals its shadows and interceptors use. */ +val robolectricJvmArgs = + listOf( + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.base/java.util=ALL-UNNAMED", + "--add-opens=java.base/java.io=ALL-UNNAMED", + "--add-opens=java.base/java.net=ALL-UNNAMED", + "--add-opens=java.base/java.security=ALL-UNNAMED", + "--add-opens=java.base/java.text=ALL-UNNAMED", + "--add-opens=java.base/jdk.internal.access=ALL-UNNAMED", + "--add-opens=java.desktop/java.awt.font=ALL-UNNAMED", + "--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + ) + +tasks.withType().configureEach { + if (testJavaVersion >= 9) { + jvmArgs(robolectricJvmArgs) + } + + if (testJavaVersion < 21) { + // Robolectric needs Java 21 to sandbox Android SDK 37. Tests configured only for SDKs outside + // this set are skipped rather than failing to create a sandbox. + systemProperty("robolectric.enabledSdks", (21..36).joinToString(",")) + } +} + +if (testJavaVersion < 9) { + afterEvaluate { + tasks.withType { + // Work around robolectric requirements and limitations. The Android Gradle Plugin adds + // --add-opens itself, which Java 8 doesn't understand. + // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/factory/AndroidUnitTest.java;l=339 + allJvmArgs = allJvmArgs.filter { !it.startsWith("--add-opens") } + } + } +} diff --git a/okhttp/build.gradle.kts b/okhttp/build.gradle.kts index 15d24f092949..44928588ff72 100644 --- a/okhttp/build.gradle.kts +++ b/okhttp/build.gradle.kts @@ -16,6 +16,7 @@ plugins { id("okhttp.jvm-conventions") id("okhttp.quality-conventions") id("okhttp.testing-conventions") + id("okhttp.robolectric-conventions") id("app.cash.burst") alias(libs.plugins.maven.sympathy) } @@ -324,20 +325,6 @@ project.tasks.withType { } } -afterEvaluate { - tasks.withType { - if (javaLauncher - .get() - .metadata.languageVersion - .asInt() < 9 - ) { - // Work around robolectric requirements and limitations - // https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/factory/AndroidUnitTest.java;l=339 - allJvmArgs = allJvmArgs.filter { !it.startsWith("--add-opens") } - } - } -} - // Work around issue 8826, where the Sentry SDK assumes that OkHttp's internal-visibility symbols // will be suffixed '$okhttp' in deployable artifacts. This isn't intended to be a published API, // but it's easy enough for us to keep it working. https://github.com/lysine-dev/okhttp/issues/8826