Refactor time access behind TimeSource

This commit is contained in:
darken
2026-04-14 11:28:41 +02:00
committed by Matthias Urhahn
parent c1601c105c
commit 1cd8ae5ace
71 changed files with 362 additions and 212 deletions
@@ -1,41 +1,35 @@
package eu.darken.capod.common
import android.media.AudioManager
import android.os.SystemClock
import io.mockk.Runs
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import org.junit.jupiter.api.AfterEach
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.TestTimeSource
class MediaControlTest : BaseTest() {
private lateinit var audioManager: AudioManager
private lateinit var mediaControl: MediaControl
private lateinit var timeSource: TestTimeSource
@BeforeEach
fun setup() {
mockkStatic(SystemClock::class)
every { SystemClock.elapsedRealtime() } returns 1_000L
every { SystemClock.uptimeMillis() } returns 1_000L
timeSource = TestTimeSource(
elapsedRealtimeMs = 1_000L,
uptimeMillisValue = 1_000L,
)
audioManager = mockk(relaxed = true)
every { audioManager.dispatchMediaKeyEvent(any()) } just Runs
mediaControl = MediaControl(audioManager)
}
@AfterEach
fun teardown() {
unmockkStatic(SystemClock::class)
mediaControl = MediaControl(audioManager, timeSource)
}
@Test
@@ -1,5 +1,6 @@
package eu.darken.capod.main.ui.devicesettings
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.common.bluetooth.BluetoothDevice2
import eu.darken.capod.common.bluetooth.BluetoothManager2
@@ -36,6 +37,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import testhelpers.BaseTest
import testhelpers.TestTimeSource
import testhelpers.coroutine.TestDispatcherProvider
import testhelpers.livedata.InstantExecutorExtension
@@ -53,6 +55,7 @@ class DeviceSettingsViewModelTest : BaseTest() {
private lateinit var bluetoothManager: BluetoothManager2
private lateinit var profilesRepo: DeviceProfilesRepo
private lateinit var generalSettings: GeneralSettings
private val timeSource: TimeSource = TestTimeSource()
private lateinit var devicesFlow: MutableStateFlow<List<PodDevice>>
private lateinit var upgradeInfoFlow: MutableStateFlow<UpgradeRepo.Info>
@@ -107,6 +110,7 @@ class DeviceSettingsViewModelTest : BaseTest() {
bluetoothManager = bluetoothManager,
profilesRepo = profilesRepo,
generalSettings = generalSettings,
timeSource = timeSource,
)
@Test
@@ -1,5 +1,6 @@
package eu.darken.capod.main.ui.overview
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.BluetoothDevice2
import eu.darken.capod.common.bluetooth.BluetoothManager2
import eu.darken.capod.common.debug.DebugSettings
@@ -34,6 +35,7 @@ import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import testhelpers.BaseTest
import testhelpers.TestTimeSource
import testhelpers.coroutine.TestDispatcherProvider
import testhelpers.datastore.FakeDataStoreValue
import testhelpers.livedata.InstantExecutorExtension
@@ -52,6 +54,7 @@ class OverviewViewModelTest : BaseTest() {
private lateinit var upgradeRepo: UpgradeRepo
private lateinit var bluetoothManager: BluetoothManager2
private lateinit var profilesRepo: DeviceProfilesRepo
private val timeSource: TimeSource = TestTimeSource()
private lateinit var missingPermissionsFlow: MutableStateFlow<Set<Permission>>
private lateinit var devicesFlow: MutableStateFlow<List<PodDevice>>
@@ -126,6 +129,7 @@ class OverviewViewModelTest : BaseTest() {
bluetoothManager = bluetoothManager,
profilesRepo = profilesRepo,
aapManager = mockk(relaxed = true),
timeSource = timeSource,
)
@Nested
@@ -1,5 +1,6 @@
package eu.darken.capod.monitor.core
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.BluetoothAddress
import eu.darken.capod.monitor.core.aap.AapLifecycleManager
import eu.darken.capod.monitor.core.ble.BlePodMonitor
@@ -26,12 +27,14 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.TestTimeSource
import java.time.Instant
@OptIn(ExperimentalCoroutinesApi::class)
class DeviceMonitorTest : BaseTest() {
private val testDispatcher = UnconfinedTestDispatcher()
private val timeSource: TimeSource = TestTimeSource(wallNow = Instant.parse("2026-04-05T18:00:00Z"))
private val testAddress: BluetoothAddress = "AA:BB:CC:DD:EE:FF"
private val testProfile = AppleDeviceProfile(
@@ -135,6 +138,7 @@ class DeviceMonitorTest : BaseTest() {
deviceStateCache = deviceStateCache,
profilesRepo = profilesRepo,
aapLifecycleManager = aapLifecycleManager,
timeSource = timeSource,
)
}
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.aap
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothSocket
import eu.darken.capod.common.TimeSource
import eu.darken.capod.common.bluetooth.l2cap.L2capSocketFactory
import eu.darken.capod.pods.core.apple.PodModel
import eu.darken.capod.pods.core.apple.aap.protocol.AapCommand
@@ -20,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.TestTimeSource
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -32,6 +34,7 @@ class AapConnectionManagerTest : BaseTest() {
private lateinit var socketFactory: L2capSocketFactory
private lateinit var manager: AapConnectionManager
private val timeSource: TimeSource = TestTimeSource()
private val testAddress = "AA:BB:CC:DD:EE:FF"
private val testDevice: BluetoothDevice = mockk(relaxed = true) {
@@ -44,6 +47,7 @@ class AapConnectionManagerTest : BaseTest() {
manager = AapConnectionManager(
socketFactory = socketFactory,
scope = testScope,
timeSource = timeSource,
)
}
@@ -2,7 +2,6 @@ package eu.darken.capod.pods.core.apple.ble.devices
import dagger.BindsInstance
import dagger.Component
import eu.darken.capod.common.SystemClockWrap
import eu.darken.capod.common.bluetooth.BleScanResult
import eu.darken.capod.common.fromHex
import eu.darken.capod.common.serialization.SerializationModule
@@ -15,9 +14,6 @@ import eu.darken.capod.profiles.core.DeviceProfilesRepo
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
import org.junit.jupiter.api.AfterEach
import kotlinx.coroutines.flow.flowOf
import org.junit.jupiter.api.BeforeEach
import testhelpers.BaseTest
@@ -59,14 +55,6 @@ abstract class BaseBlePodsTest : BaseTest() {
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
mockkObject(SystemClockWrap)
every { SystemClockWrap.elapsedRealtimeNanos } returns 1000L
}
@AfterEach
fun teardown() {
unmockkObject(SystemClockWrap)
}
internal suspend inline fun <reified T : BlePodSnapshot?> create(
@@ -75,7 +63,7 @@ abstract class BaseBlePodsTest : BaseTest() {
block: T.() -> Unit
) {
val result = BleScanResult(
receivedAt = Instant.now(),
receivedAt = Instant.parse("2026-01-01T00:00:00Z"),
address = address,
rssi = -66,
generatedAtNanos = 136136027721826,
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import testhelpers.BaseTest
import testhelpers.TestTimeSource
import java.time.Duration
import java.time.Instant
@@ -19,6 +20,7 @@ class PopUpReactionLogicTest : BaseTest() {
popUpReaction = PopUpReaction(
deviceMonitor = mockk(relaxed = true),
bluetoothManager = mockk(relaxed = true),
timeSource = TestTimeSource(),
)
}
@@ -0,0 +1,30 @@
package testhelpers
import eu.darken.capod.common.TimeSource
import java.time.Duration
import java.time.Instant
class TestTimeSource(
var wallNow: Instant = Instant.parse("2026-01-01T00:00:00Z"),
var elapsedRealtimeMs: Long = 0L,
var elapsedRealtimeNanosValue: Long = 0L,
var uptimeMillisValue: Long = 0L,
) : TimeSource {
override fun now(): Instant = wallNow
override fun currentTimeMillis(): Long = wallNow.toEpochMilli()
override fun elapsedRealtime(): Long = elapsedRealtimeMs
override fun elapsedRealtimeNanos(): Long = elapsedRealtimeNanosValue
override fun uptimeMillis(): Long = uptimeMillisValue
fun advanceBy(duration: Duration) {
wallNow = wallNow.plus(duration)
elapsedRealtimeMs += duration.toMillis()
elapsedRealtimeNanosValue += duration.toNanos()
uptimeMillisValue += duration.toMillis()
}
}