From 4ca67df4ac78285c31784730edd0eb32a429ce43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 14:34:24 +0000 Subject: [PATCH 1/3] build(deps): bump org.robolectric:robolectric from 4.16.1 to 4.17 Bumps [org.robolectric:robolectric](https://github.com/robolectric/robolectric) from 4.16.1 to 4.17. - [Release notes](https://github.com/robolectric/robolectric/releases) - [Commits](https://github.com/robolectric/robolectric/compare/robolectric-4.16.1...robolectric-4.17) --- updated-dependencies: - dependency-name: org.robolectric:robolectric dependency-version: '4.17' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 385fcc4aaa..a72da76ab7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -89,7 +89,7 @@ espresso = "3.7.0" mockito-kotlin = "6.3.0" mockk = "1.14.11" turbine = "1.2.1" -robolectric = "4.16.1" +robolectric = "4.17" slf4j-simple = "2.0.19" androidxUiJUnit = "1.11.4" mixpanel = "8.10.0" From 08dd5b3e0f1286d4f6cf76fa0e61b9edd87ea352 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 18 Sep 2026 11:47:34 -0400 Subject: [PATCH 2/3] chore(deps): correct the Robolectric SDK pin comment for 4.17 The comment justified sdk=36 as "the latest version it supports (4.16.1)". 4.17 supports SDK 37, which is this repo's compileSdk and targetSdk, so the stated reason no longer holds. Left the pin at 36. Raising it changes the Android level every test without an explicit @Config(sdk = ...) runs against, which is worth its own test run rather than a ride-along on a version bump. --- libs/test-utils/src/main/resources/robolectric.properties | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/test-utils/src/main/resources/robolectric.properties b/libs/test-utils/src/main/resources/robolectric.properties index 564b8fa356..aff638fb93 100644 --- a/libs/test-utils/src/main/resources/robolectric.properties +++ b/libs/test-utils/src/main/resources/robolectric.properties @@ -1,3 +1,4 @@ -# Pin Robolectric to SDK 36 — the latest version it supports (4.16.1). -# This avoids needing @Config(sdk = [36]) on every test class. +# Pin Robolectric to SDK 36 so tests don't need @Config(sdk = ...) on every class. +# Robolectric 4.17 supports SDK 37 (= compileSdk/targetSdk); moving the pin is a +# deliberate change to what every unpinned test runs against, so it lands separately. sdk=36 From 9b22139bd53c57b2a5d5bf5b78d7b029b33be365 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 18 Sep 2026 11:57:56 -0400 Subject: [PATCH 3/3] fix(build): open JDK internals for Robolectric 4.17 on Java 21 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Robolectric 4.17 sets up FileDescriptor through jdk.internal.access.SharedSecrets, a package the module system does not export. On the Java 21 toolchain every test that starts the Android environment throws IllegalAccessException out of AndroidTestEnvironment.setUpApplicationState before reaching an assertion — 31 failures in :libs:datetime and :libs:logging alone, where 4.16.1 was green. Apply the --add-opens set from Robolectric's own test configuration to every Test task. This lands in settings.gradle.kts rather than a convention plugin because :libs:encryption:utils runs Robolectric from androidHostTest and applies the AGP and KMP plugins directly, so no flipcash convention plugin reaches it; under Isolated Projects the beforeProject hook is the one hook that sees every project. All 25 modules that declare Robolectric now pass: 1439 tests, 0 failures. --- .../kotlin/AndroidLibraryConventionPlugin.kt | 2 +- settings.gradle.kts | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt index afeed6c4f7..255be5fed7 100644 --- a/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -78,7 +78,7 @@ class AndroidLibraryConventionPlugin : Plugin { // AGP's built-in Kotlin no longer auto-selects the kotlin-test // JUnit variant, so provide it explicitly for all library modules. "testImplementation"(libs.findLibrary("kotlin-test-junit").get()) - // Provides robolectric.properties pinning SDK to 36 (latest Robolectric supports). + // Provides robolectric.properties, which pins the Robolectric SDK level. if (path != ":libs:test-utils") { "testImplementation"(project(":libs:test-utils")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 4c8d4d7ba1..71ee3ac63e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -374,6 +374,26 @@ run { "org.jetbrains.kotlinx:kotlinx-serialization-json:$serializationVersion", "org.jetbrains.kotlin:kotlin-metadata-jvm:$kotlinVersion", ) + // Robolectric reaches into JDK internals that the module system closes off by + // default on Java 9+. From 4.17 it uses jdk.internal.access.SharedSecrets to set + // up FileDescriptor, which throws IllegalAccessException on our Java 21 toolchain + // unless that package is opened; the rest mirror Robolectric's own test config so + // shadows that reflect into java.nio, java.text and the AWT font stack keep working. + // This has to live here rather than in a convention plugin: :libs:encryption:utils + // runs Robolectric from androidHostTest and applies the AGP/KMP plugins directly, + // so no flipcash convention plugin sees it. + val robolectricJvmArgs = listOf( + "--add-opens=java.base/java.lang=ALL-UNNAMED", + "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens=java.base/java.io=ALL-UNNAMED", + "--add-opens=java.base/java.net=ALL-UNNAMED", + "--add-opens=java.base/java.nio=ALL-UNNAMED", + "--add-opens=java.base/java.security=ALL-UNNAMED", + "--add-opens=java.base/java.text=ALL-UNNAMED", + "--add-opens=java.base/java.util=ALL-UNNAMED", + "--add-opens=java.base/jdk.internal.access=ALL-UNNAMED", + "--add-opens=java.desktop/java.awt.font=ALL-UNNAMED", + ) gradle.lifecycle.beforeProject { if (path == ":") { extra["flipcash.koverModules"] = koverModulesForRoot @@ -389,5 +409,8 @@ run { tasks.matching { task -> task.name.contains("kapt") }.configureEach { enabled = false } + tasks.withType(org.gradle.api.tasks.testing.Test::class.java).configureEach { + jvmArgs(robolectricJvmArgs) + } } }