Browse Source

Test route-local retry policy and output position continuity

master
evgeny 4 days ago
parent
commit
3226865ad7
  1. 36
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallOutputRecovery.kt
  2. 31
      tools/chatgui-android/app/src/test/java/com/utun/chat/data/CallOutputRecoveryTest.kt

36
tools/chatgui-android/app/src/main/java/com/utun/chat/data/CallOutputRecovery.kt

@ -0,0 +1,36 @@
package com.utun.chat.data
/** Только playback worker. Ошибка ограничивает повтор в текущем маршруте, не в следующем. */
internal class CallOutputRecovery {
var deviceId = Int.MIN_VALUE
private set
var failures = 0
private set
fun route(id: Int): Boolean {
if (id == deviceId) return false
deviceId = id; failures = 0
return true
}
fun failed(): Boolean { failures++; return failures <= 2 }
val tryFast: Boolean get() = failures == 0
}
/** Локальный счётчик нового устройства начинается с нуля; общий PCM-счётчик не сбрасывается. */
internal class CallOutputPosition {
var base = 0L
private set
private var head = CallPlaybackHead()
fun reset() { base = 0; head = CallPlaybackHead() }
fun reopen(written: Long) {
require(written >= base)
base = written
head = CallPlaybackHead()
}
fun played(raw: Int, written: Long): Long? = head.update(raw, written - base)?.let { base + it }
fun timestamp(local: Long): Long = if (local < 0) -1 else base + local
}

31
tools/chatgui-android/app/src/test/java/com/utun/chat/data/CallOutputRecoveryTest.kt

@ -0,0 +1,31 @@
package com.utun.chat.data
import org.junit.Assert.*
import org.junit.Test
class CallOutputRecoveryTest {
@Test fun routeChangeRestoresFastAttemptButDuplicateConfirmationDoesNot() {
val p = CallOutputRecovery()
assertTrue(p.route(3)); assertTrue(p.tryFast)
assertTrue(p.failed()); assertFalse(p.tryFast)
assertFalse(p.route(3)); assertFalse(p.tryFast)
assertTrue(p.failed()); assertFalse(p.failed())
assertTrue(p.route(2)); assertTrue(p.tryFast); assertEquals(0, p.failures)
assertTrue(p.route(3)); assertTrue(p.tryFast)
}
@Test fun replacementKeepsAcceptedPcmPositionAndPartialBlockRemainder() {
val p = CallOutputPosition()
assertEquals(480L, p.played(480, 960))
// 288 сэмплов следующего блока успели записаться; остаток 672 ещё принадлежит worker.
val accepted = 960L + 288
p.reopen(accepted)
assertEquals(accepted, p.played(0, accepted))
assertEquals(accepted + 96, p.played(96, accepted + 672))
assertEquals(accepted + 48, p.timestamp(48))
assertEquals(-1L, p.timestamp(-1))
assertNull(p.played(1000, accepted + 672))
p.reopen(1920)
assertEquals(1920L, p.played(0, 1920))
}
}
Loading…
Cancel
Save