Verify call cleanup completion, core barriers, retry lifetime and stale callbacks
This commit is contained in:
1 parent
3e7ac47d18
commit
a5d8131d8d
1 file changed
+105
+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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user