Merge pull request #4602

* refactor(test): Introduce MeshTestApplication for robust testing
This commit is contained in:
James Rich 2026-02-20 11:25:11 -06:00 committed by GitHub
parent f75622002f
commit ac5a1714e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 77 additions and 2 deletions

View file

@ -31,6 +31,7 @@ import dagger.hilt.android.HiltAndroidApp
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import org.meshtastic.core.database.DatabaseManager
import org.meshtastic.core.prefs.mesh.MeshPrefs
@ -47,10 +48,12 @@ import kotlin.time.toJavaDuration
* user preferences.
*/
@HiltAndroidApp
class MeshUtilApplication :
open class MeshUtilApplication :
Application(),
Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
private val applicationScope = CoroutineScope(Dispatchers.Default)
override fun onCreate() {
super.onCreate()
@ -61,11 +64,19 @@ class MeshUtilApplication :
// Initialize DatabaseManager asynchronously with current device address so DAO consumers have an active DB
val entryPoint = EntryPointAccessors.fromApplication(this, AppEntryPoint::class.java)
CoroutineScope(Dispatchers.Default).launch {
applicationScope.launch {
entryPoint.databaseManager().init(entryPoint.meshPrefs().deviceAddress)
}
}
override fun onTerminate() {
// Shutdown managers (useful for Robolectric tests)
val entryPoint = EntryPointAccessors.fromApplication(this, AppEntryPoint::class.java)
entryPoint.databaseManager().close()
applicationScope.cancel()
super.onTerminate()
}
private fun scheduleMeshLogCleanup() {
val cleanupRequest =
PeriodicWorkRequestBuilder<MeshLogCleanupWorker>(repeatInterval = 1.hours.toJavaDuration()).build()

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh
import androidx.work.Configuration
import dagger.hilt.android.EntryPointAccessors
/**
* A lightweight application class for Robolectric tests.
*
* It prevents heavy background initialization (WorkManager, DatabaseManager) by default
* to avoid resource leaks and flaky native SQLite issues on the JVM.
*/
class MeshTestApplication : MeshUtilApplication() {
override fun onCreate() {
// Only run real onCreate logic if a test explicitly asks for it
if (shouldInitialize) {
super.onCreate()
}
}
override fun onTerminate() {
if (shouldInitialize) {
val entryPoint = EntryPointAccessors.fromApplication(this, AppEntryPoint::class.java)
entryPoint.databaseManager().close()
}
super.onTerminate()
}
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.build()
companion object {
/** Set to true in a test @Before block if you need real DB/WorkManager init. */
var shouldInitialize = false
}
}

View file

@ -0,0 +1 @@
application = com.geeksville.mesh.MeshTestApplication

View file

@ -24,6 +24,7 @@ import androidx.room.RoomDatabase
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -209,6 +210,14 @@ class DatabaseManager @Inject constructor(private val app: Application, private
}
prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply()
}
/** Closes all open databases and cancels background work. */
fun close() {
managerScope.cancel()
dbCache.values.forEach { it.close() }
dbCache.clear()
_currentDb.value = null
}
}
object DatabaseConstants {