refactor: coroutine dispatchers and modernize testing infrastructure (#4901)
Some checks are pending
Dependency Submission / dependency-submission (push) Waiting to run
Main CI (Verify & Build) / validate-and-build (push) Waiting to run
Main Push Changelog / Generate main push changelog (push) Waiting to run

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-23 20:31:48 -05:00 committed by GitHub
parent 664ebf218e
commit 96060a0a4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 621 additions and 182 deletions

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 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 org.meshtastic.core.testing
import kotlinx.coroutines.flow.MutableStateFlow
import org.meshtastic.core.repository.MeshLogPrefs
class FakeMeshLogPrefs : MeshLogPrefs {
private val _retentionDays = MutableStateFlow(MeshLogPrefs.DEFAULT_RETENTION_DAYS)
override val retentionDays = _retentionDays
override fun setRetentionDays(days: Int) {
_retentionDays.value = days
}
private val _loggingEnabled = MutableStateFlow(true)
override val loggingEnabled = _loggingEnabled
override fun setLoggingEnabled(enabled: Boolean) {
_loggingEnabled.value = enabled
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 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 org.meshtastic.core.testing
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import org.meshtastic.core.model.MeshLog
import org.meshtastic.core.repository.MeshLogRepository
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.MyNodeInfo
import org.meshtastic.proto.PortNum
import org.meshtastic.proto.Telemetry
@Suppress("TooManyFunctions")
class FakeMeshLogRepository : MeshLogRepository {
private val logsFlow = MutableStateFlow<List<MeshLog>>(emptyList())
val currentLogs: List<MeshLog>
get() = logsFlow.value
var deleteLogsOlderThanCalledDays: Int? = null
override fun getAllLogs(maxItem: Int): Flow<List<MeshLog>> = logsFlow.map { it.take(maxItem) }
override fun getAllLogsInReceiveOrder(maxItem: Int): Flow<List<MeshLog>> = logsFlow.map { it.take(maxItem) }
override fun getAllLogsUnbounded(): Flow<List<MeshLog>> = logsFlow
override fun getLogsFrom(nodeNum: Int, portNum: Int): Flow<List<MeshLog>> = logsFlow.map {
it.filter { log -> log.fromNum == nodeNum && log.portNum == portNum }
}
override fun getMeshPacketsFrom(nodeNum: Int, portNum: Int): Flow<List<MeshPacket>> = MutableStateFlow(emptyList())
override fun getTelemetryFrom(nodeNum: Int): Flow<List<Telemetry>> = MutableStateFlow(emptyList())
override fun getRequestLogs(targetNodeNum: Int, portNum: PortNum): Flow<List<MeshLog>> =
MutableStateFlow(emptyList())
override fun getMyNodeInfo(): Flow<MyNodeInfo?> = MutableStateFlow(null)
override suspend fun insert(log: MeshLog) {
logsFlow.value = logsFlow.value + log
}
override suspend fun deleteAll() {
logsFlow.value = emptyList()
}
override suspend fun deleteLog(uuid: String) {
logsFlow.value = logsFlow.value.filter { it.uuid != uuid }
}
override suspend fun deleteLogs(nodeNum: Int, portNum: Int) {
logsFlow.value = logsFlow.value.filterNot { it.fromNum == nodeNum && it.portNum == portNum }
}
override suspend fun deleteLogsOlderThan(retentionDays: Int) {
deleteLogsOlderThanCalledDays = retentionDays
}
fun setLogs(logs: List<MeshLog>) {
logsFlow.value = logs
}
}

View file

@ -52,6 +52,7 @@ class FakeRadioController : RadioController {
val favoritedNodes = mutableListOf<Int>()
val sentSharedContacts = mutableListOf<Int>()
var throwOnSend: Boolean = false
var lastSetDeviceAddress: String? = null
override suspend fun sendMessage(packet: DataPacket) {
if (throwOnSend) error("Fake send failure")
@ -136,7 +137,9 @@ class FakeRadioController : RadioController {
override fun stopProvideLocation() {}
override fun setDeviceAddress(address: String) {}
override fun setDeviceAddress(address: String) {
lastSetDeviceAddress = address
}
// --- Helper methods for testing ---

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 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 org.meshtastic.core.testing
import co.touchlab.kermit.Severity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.service.ServiceAction
import org.meshtastic.core.model.service.TracerouteResponse
import org.meshtastic.core.repository.ServiceRepository
import org.meshtastic.proto.ClientNotification
import org.meshtastic.proto.MeshPacket
@Suppress("TooManyFunctions")
class FakeServiceRepository : ServiceRepository {
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
override val connectionState: StateFlow<ConnectionState> = _connectionState
override fun setConnectionState(connectionState: ConnectionState) {
_connectionState.value = connectionState
}
private val _clientNotification = MutableStateFlow<ClientNotification?>(null)
override val clientNotification: StateFlow<ClientNotification?> = _clientNotification
override fun setClientNotification(notification: ClientNotification?) {
_clientNotification.value = notification
}
override fun clearClientNotification() {
_clientNotification.value = null
}
private val _errorMessage = MutableStateFlow<String?>(null)
override val errorMessage: StateFlow<String?> = _errorMessage
override fun setErrorMessage(text: String, severity: Severity) {
_errorMessage.value = text
}
override fun clearErrorMessage() {
_errorMessage.value = null
}
private val _connectionProgress = MutableStateFlow<String?>(null)
override val connectionProgress: StateFlow<String?> = _connectionProgress
override fun setConnectionProgress(text: String) {
_connectionProgress.value = text
}
private val _meshPacketFlow = MutableSharedFlow<MeshPacket>()
override val meshPacketFlow: SharedFlow<MeshPacket> = _meshPacketFlow
override suspend fun emitMeshPacket(packet: MeshPacket) {
_meshPacketFlow.emit(packet)
}
private val _tracerouteResponse = MutableStateFlow<TracerouteResponse?>(null)
override val tracerouteResponse: StateFlow<TracerouteResponse?> = _tracerouteResponse
override fun setTracerouteResponse(value: TracerouteResponse?) {
_tracerouteResponse.value = value
}
override fun clearTracerouteResponse() {
_tracerouteResponse.value = null
}
private val _neighborInfoResponse = MutableStateFlow<String?>(null)
override val neighborInfoResponse: StateFlow<String?> = _neighborInfoResponse
override fun setNeighborInfoResponse(value: String?) {
_neighborInfoResponse.value = value
}
override fun clearNeighborInfoResponse() {
_neighborInfoResponse.value = null
}
private val _serviceAction = MutableSharedFlow<ServiceAction>(replay = 1)
override val serviceAction: Flow<ServiceAction> = _serviceAction
override suspend fun onServiceAction(action: ServiceAction) {
_serviceAction.emit(action)
}
}