Verify call cleanup completion, core barriers, retry lifetime and stale callbacks

This commit is contained in:
evgeny committed 2026-10-29 13:34:23 +01:00
1 parent 3e7ac47d18
commit a5d8131d8d
1 file changed
+105
@@ -230,4 +230,109 @@ class CallServiceLifecycleTest {
assertTrue(error.get() is IllegalStateException); assertEquals(0, service.foregroundAttempts)
diagnostic("requires main thread")
}
@Test fun radioRestorationAndOtherMediaWaitForConfirmedAudioCleanup() {
val radio = RadioAudioEngine.instance(); radio.activeGroup.value = 7
val service = active(); service.audio.failures = setOf("stop")
CallController.end(); dispatcher.scheduler.runCurrent()
assertEquals(CallPhase.ENDED, state?.phase); assertTrue(CallController.blocksMedia())
val completion = requireNotNull(CallService.pendingRelease)
assertFalse(completion.isCompleted); assertTrue(radio.restored.isEmpty()); assertTrue(manager.active.containsKey(1002))
assertTrue(runCatching { CallService.requireReleased() }.isFailure)
service.audio.failures = emptySet(); main.advanceBy(1000); dispatcher.scheduler.runCurrent()
assertTrue(completion.isCompleted); assertFalse(CallService.cleanupPending())
await("radio restored after cleanup") { radio.restored == listOf(7L) }
assertFalse(CallController.blocksMedia())
}
@Test fun unexpectedDestroyStillRetainsFailedCleanupAndEndsTheControllerSession() {
val service = active(); service.audio.failures = setOf("stop")
service.onDestroy()
assertEquals(1, service.destroys); assertEquals(CallPhase.ENDED, state?.phase)
assertTrue(CallService.cleanupPending()); assertTrue(CallController.blocksMedia())
service.audio.failures = emptySet(); main.advanceBy(1000)
assertFalse(service.audio.active); assertFalse(CallService.cleanupPending())
}
@Test fun rejectedRetryQueueRetainsAnExplicitCleanupPathAndBlocksCoreShutdown() {
val service = active(); service.audio.failures = setOf("stop"); Handler.rejectDelayed = true
val completion = service.shutdown()
assertFalse(completion.isCompleted); diagnostic("cleanup retry rejected")
assertTrue(runCatching { CallService.requireReleased() }.isFailure)
service.audio.failures = emptySet(); CallService.requireReleased()
assertTrue(completion.isCompleted); assertFalse(CallService.cleanupPending())
}
@Test fun aCancelledCompletionCannotBeMistakenForSuccessfulResourceCleanup() {
val service = active(); service.audio.failures = setOf("stop")
val completion = service.shutdown(); completion.cancel()
assertTrue(completion.isCompleted)
assertTrue(runCatching { CallService.requireReleased() }.isFailure)
assertTrue(CallService.cleanupPending()); assertTrue(service.audio.active)
}
@Test fun foregroundAndStopSelfFailuresDoNotRepeatReleasedMedia() {
for (foreground in listOf(true, false)) {
val service = active()
service.failForegroundStop = foreground; service.failStopSelf = !foreground
val completion = service.shutdown(); assertFalse(completion.isCompleted)
assertFalse(service.audio.active); assertFalse(power.locks.last().held); assertTrue(service.receivers.isEmpty())
assertEquals(1, service.audio.stops)
service.failForegroundStop = false; service.failStopSelf = false; service.shutdown()
assertTrue(completion.isCompleted); assertEquals(1, service.audio.stops)
CallController.end(); service.onDestroy()
}
}
@Test fun actionCancellationFailuresRetainTheTokensUntilAcknowledged() {
val service = active(); val token = requireNotNull(manager.active.getValue(1002).hangup)
token.failCancel = true
val completion = service.shutdown()
assertFalse(completion.isCompleted); assertSame(token, field(service, "endIntent")); assertTrue(CallService.cleanupPending())
token.failCancel = false; main.advanceBy(1000)
assertTrue(completion.isCompleted); assertTrue(token.cancelled); assertNull(field(service, "endIntent"))
}
@Test fun failedReleaseAfterItsEffectConfirmsAbsenceOnTheNextAttempt() {
val service = active(); val lock = power.locks.single()
lock.failures = setOf("release_after"); service.receiverFailures = setOf("unregister after")
val completion = service.shutdown()
assertFalse(lock.held); assertTrue(service.receivers.isEmpty()); assertFalse(completion.isCompleted)
lock.failures = emptySet(); service.receiverFailures = emptySet(); main.advanceBy(1000)
assertTrue(completion.isCompleted); assertEquals(1, lock.releaseCalls)
assertNull(field(service, "wakeLock")); assertEquals(false, field(service, "powerEventsRegistered"))
}
@Test fun staleAudioFailureCannotEndANewCallOnAReusedServiceInstance() {
val service = active(); val oldId = requireNotNull(state).callId; val callback = requireNotNull(service.audio.onFailure)
CallController.end(); val nextId = request(); start(service, nextId)
await("new signaling") { NativeLib.callCommands.any { it == "start" to nextId } }
callback(oldId, "old worker failed")
assertEquals(nextId, state?.callId); assertTrue(state?.ongoing == true)
assertTrue(power.locks.last().held)
}
@Test fun notificationUpdatesReuseOwnedTokensAndKeepTheConversationClock() {
val service = active(); val original = manager.active.getValue(1002)
assertTrue(original.chronometer); assertTrue(original.showWhen); assertEquals("Alice", original.title)
assertEquals("node:99", original.person?.key); assertEquals(2, PendingIntent.created.size)
AppLanguage.language = "ru"; service.refreshLanguage()
val updated = manager.active.getValue(1002)
assertSame(original.hangup, updated.hangup); assertSame(original.contentIntent, updated.contentIntent)
assertTrue(kotlin.math.abs(original.whenMs - updated.whenMs) < 50)
assertEquals(2, PendingIntent.created.size)
}
@Test fun powerDiagnosticFailuresAreLoggedAndDoNotLoseTheOwnedLock() {
val service = active(); val receiver = service.receivers.keys.single(); val lock = power.locks.single()
lock.failures = setOf("query")
receiver.onReceive(service, Intent(Intent.ACTION_SCREEN_OFF))
assertSame(lock, field(service, "wakeLock")); assertTrue(lock.held); diagnostic("power diagnostics failed")
}
@Test fun aMissingPowerServiceRollsBackForegroundAndFinishesStartup() {
val service = create(); service.powerManager = null; start(service)
assertEquals(CallPhase.ENDED, state?.phase); assertTrue(service.stops > 0)
assertFalse(manager.active.containsKey(1002)); diagnostic("start failed operation=wake lock")
}
}