From b5152d14a0ddb93ff2daff6ddb57cbd47dacc2cd Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:15:27 +0000 Subject: [PATCH] fix(test): isolate androidApp unit tests from the production Application (#6644) Co-authored-by: Claude Opus 5 --- .../main/kotlin/org/meshtastic/app/MeshUtilApplication.kt | 5 ++--- .../kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt | 4 +++- androidApp/src/test/resources/robolectric.properties | 5 +++++ .../kotlin/org/meshtastic/app/map/MapNodeClusterItemsTest.kt | 4 +++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt b/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt index bb1375a165..8ecc1ecb24 100644 --- a/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt +++ b/androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt @@ -144,9 +144,8 @@ open class MeshUtilApplication : } override fun onTerminate() { - // Shutdown managers (useful for Robolectric tests). - // Non-blocking: cancelAndJoin inside runBlocking on the main thread can deadlock - // if any active coroutine is dispatching to Dispatchers.Main. + // Robolectric never calls this, so unit tests booting this Application cannot rely on it. + // cancel() not cancelAndJoin(): joining under runBlocking on the main thread can deadlock. applicationScope.cancel() try { runBlocking { get().close() } diff --git a/androidApp/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt b/androidApp/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt index a8d82a6b28..9917c8629d 100644 --- a/androidApp/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt +++ b/androidApp/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt @@ -36,9 +36,11 @@ import org.meshtastic.feature.settings.radio.channel.channelsGraph import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +// Graph assembly only builds entry providers, so a bare Application is enough. Booting +// MeshUtilApplication here leaks its applicationScope launches into the rest of the fork. @OptIn(ExperimentalTestApi::class) @RunWith(RobolectricTestRunner::class) -@Config(sdk = [34]) +@Config(sdk = [34], application = android.app.Application::class) class NavigationAssemblyTest { @Test diff --git a/androidApp/src/test/resources/robolectric.properties b/androidApp/src/test/resources/robolectric.properties index 979b5eebcf..7598f1481f 100644 --- a/androidApp/src/test/resources/robolectric.properties +++ b/androidApp/src/test/resources/robolectric.properties @@ -1 +1,6 @@ +# Legacy SQLite keeps Robolectric's native SQLite off androidApp's unit-test path. The native +# implementation segfaults the whole test fork if its nativeruntime temp dir is torn down while a +# background database open is still in flight, which fails the task outright (retry cannot help). +# No androidApp unit test asserts database behaviour -- :core:database owns that and stays native. sdk=34 +sqliteMode=LEGACY diff --git a/androidApp/src/testGoogle/kotlin/org/meshtastic/app/map/MapNodeClusterItemsTest.kt b/androidApp/src/testGoogle/kotlin/org/meshtastic/app/map/MapNodeClusterItemsTest.kt index fac8e6093e..ed82534217 100644 --- a/androidApp/src/testGoogle/kotlin/org/meshtastic/app/map/MapNodeClusterItemsTest.kt +++ b/androidApp/src/testGoogle/kotlin/org/meshtastic/app/map/MapNodeClusterItemsTest.kt @@ -44,9 +44,11 @@ import kotlin.test.assertEquals import kotlin.test.assertNotSame import kotlin.test.assertTrue +// A bare Application keeps MeshUtilApplication.onCreate out of this test: its fire-and-forget +// applicationScope launches outlive the class and surface here as UncaughtExceptionsBeforeTest. @OptIn(ExperimentalTestApi::class, ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) -@Config(sdk = [34]) +@Config(sdk = [34], application = android.app.Application::class) class MapNodeClusterItemsTest { @Test