Test retained voice resources, decoder failures and nonblocking disposal
This commit is contained in:
1 parent
bb08b20e58
commit
b25766ae5a
3 files changed
+78
-4
No files matched your search
+6
-2
@@ -9,10 +9,11 @@ object AudioManager { const val STREAM_MUSIC = 3 }
|
||||
class AudioTrack(stream: Int, rate: Int, mask: Int, encoding: Int, bytes: Int, mode: Int) {
|
||||
class Plan {
|
||||
var initialized = true
|
||||
var failures = emptySet<String>()
|
||||
@Volatile var failures = emptySet<String>()
|
||||
val results = LinkedBlockingQueue<Int>()
|
||||
val accepted = CopyOnWriteArrayList<Short>()
|
||||
var releaseCalls = 0
|
||||
@Volatile var released = false
|
||||
}
|
||||
companion object {
|
||||
const val MODE_STREAM = 1; const val STATE_INITIALIZED = 1
|
||||
@@ -32,5 +33,8 @@ class AudioTrack(stream: Int, rate: Int, mask: Int, encoding: Int, bytes: Int, m
|
||||
return result
|
||||
}
|
||||
fun stop() { check("stop" !in plan.failures) { "track stop failed" } }
|
||||
fun release() { plan.releaseCalls++; check("release" !in plan.failures) { "track release failed" } }
|
||||
fun release() {
|
||||
plan.releaseCalls++; check("release" !in plan.failures) { "track release failed" }; plan.released = true
|
||||
check("release after" !in plan.failures) { "track release failed after effect" }
|
||||
}
|
||||
}
|
||||
+5
-2
@@ -17,11 +17,14 @@ object NativeLib {
|
||||
val cancels = AtomicInteger()
|
||||
val decoderReads = LinkedBlockingQueue<Int>()
|
||||
val decoderCloses = AtomicInteger()
|
||||
@Volatile var failDecoderClose = false
|
||||
@Volatile var failCancel = false
|
||||
var decoderInfo = intArrayOf(48000, 1, 960)
|
||||
fun reset() {
|
||||
active = false; acceptStart = true; acceptFeed = true; stopResult = 800; onFeed = {}
|
||||
starts.clear(); feeds.clear(); stops.set(0); cancels.set(0)
|
||||
decoderReads.clear(); decoderCloses.set(0); decoderInfo = intArrayOf(48000, 1, 960)
|
||||
failDecoderClose = false; failCancel = false
|
||||
}
|
||||
@Synchronized fun voiceStart(channel: String, reply: String = "", request: Long = 0, generation: Long = 0): Boolean {
|
||||
starts.add(listOf(channel, reply, request, generation))
|
||||
@@ -30,14 +33,14 @@ object NativeLib {
|
||||
}
|
||||
fun voiceFeed(pcm: ShortArray): Boolean { feeds.add(pcm.copyOf()); onFeed(); return active && acceptFeed }
|
||||
@Synchronized fun voiceStop(): Int { stops.incrementAndGet(); active = false; return stopResult }
|
||||
@Synchronized fun voiceCancel() { cancels.incrementAndGet(); active = false }
|
||||
@Synchronized fun voiceCancel() { check(!failCancel) { "native cancel rejected" }; cancels.incrementAndGet(); active = false }
|
||||
fun voiceDecodeOpen(path: String, info: IntArray): Long { decoderInfo.copyInto(info); return 1 }
|
||||
fun voiceDecodeRead(handle: Long, pcm: ShortArray): Int {
|
||||
val count = decoderReads.poll() ?: 0
|
||||
if (count > 0) pcm.fill(12, 0, count)
|
||||
return count
|
||||
}
|
||||
fun voiceDecodeClose(handle: Long) { decoderCloses.incrementAndGet() }
|
||||
fun voiceDecodeClose(handle: Long) { check(!failDecoderClose) { "decoder close rejected" }; decoderCloses.incrementAndGet() }
|
||||
}
|
||||
|
||||
object StreamController { var blocked = false; fun blocksCapture() = blocked }
|
||||
+67
@@ -21,9 +21,11 @@ class AudioRecorderManagerTest {
|
||||
ChatApplication.instance = RecorderHostContext(); LogManager.entries.clear()
|
||||
}
|
||||
@After fun release() {
|
||||
NativeLib.failCancel = false; NativeLib.failDecoderClose = false
|
||||
AudioRecord.created.forEach { it.plan.failures = emptySet(); it.plan.chunks.offer(AudioRecord.Chunk(count = -3)) }
|
||||
AudioTrack.created.forEach { it.plan.failures = emptySet() }
|
||||
manager.cancelRecording(); manager.stopMicTest(); manager.stopPlayback()
|
||||
manager.requestClose().get(3, TimeUnit.SECONDS)
|
||||
AudioRecord.created.forEach {
|
||||
it.plan.reader?.join(2_000)
|
||||
assertFalse("reader survived cleanup", it.plan.reader?.isAlive == true)
|
||||
@@ -176,4 +178,69 @@ class AudioRecorderManagerTest {
|
||||
manager.playVoiceFile("voice.opus", onComplete = { done.countDown() })
|
||||
assertTrue(done.await(2, TimeUnit.SECONDS)); assertEquals(1, NativeLib.decoderCloses.get()); diagnostic("decode")
|
||||
}
|
||||
|
||||
@Test fun disposedViewModelLeavesAProcessOwnerUntilCaptureReleaseIsAcknowledged() {
|
||||
val device = plan(); assertTrue(manager.startRecording("7")); device.failures = setOf("release")
|
||||
val closed = manager.requestClose()
|
||||
waitUntil { NativeLib.cancels.get() == 1 }
|
||||
assertFalse(closed.isDone); assertFalse(NativeLib.active)
|
||||
assertThrows(IllegalStateException::class.java) { AudioRecorderManager.requireReleasedForCore() }
|
||||
device.failures = emptySet(); closed.get(2, TimeUnit.SECONDS)
|
||||
assertTrue(device.released); AudioRecorderManager.requireReleasedForCore()
|
||||
}
|
||||
|
||||
@Test fun nativeCancelFailureRetainsTheCaptureUntilARealAcknowledgement() {
|
||||
plan(); assertTrue(manager.startRecording("7")); NativeLib.failCancel = true
|
||||
assertEquals(-1, manager.stopRecording()); assertTrue(NativeLib.active)
|
||||
val closed = manager.requestClose(); assertFalse(closed.isDone)
|
||||
assertThrows(IllegalStateException::class.java) { AudioRecorderManager.requireReleasedForCore() }
|
||||
NativeLib.failCancel = false; closed.get(2, TimeUnit.SECONDS)
|
||||
assertFalse(NativeLib.active); AudioRecorderManager.requireReleasedForCore()
|
||||
}
|
||||
|
||||
@Test fun playbackReleaseFailureDoesNotLoseItsDeviceAfterCompletionCallback() {
|
||||
val output = AudioTrack.Plan().also { it.failures = setOf("release"); AudioTrack.plans.add(it) }
|
||||
val completed = CountDownLatch(1)
|
||||
manager.playVoiceFile("voice.opus", onComplete = { completed.countDown() })
|
||||
assertTrue(completed.await(2, TimeUnit.SECONDS)); assertEquals(1, NativeLib.decoderCloses.get())
|
||||
val closed = manager.requestClose(); assertFalse(closed.isDone); assertFalse(output.released)
|
||||
output.failures = emptySet(); closed.get(2, TimeUnit.SECONDS)
|
||||
assertTrue(output.released); assertTrue(output.releaseCalls >= 2)
|
||||
}
|
||||
|
||||
@Test fun releaseAfterEffectStillNeedsAcknowledgementAndDoesNotCloseDecoderTwice() {
|
||||
val output = AudioTrack.Plan().also { it.failures = setOf("release after"); AudioTrack.plans.add(it) }
|
||||
val completed = CountDownLatch(1)
|
||||
manager.playVoiceFile("voice.opus", onComplete = { completed.countDown() })
|
||||
assertTrue(completed.await(2, TimeUnit.SECONDS)); assertTrue(output.released)
|
||||
val closed = manager.requestClose(); assertFalse(closed.isDone)
|
||||
output.failures = emptySet(); closed.get(2, TimeUnit.SECONDS)
|
||||
assertEquals(1, NativeLib.decoderCloses.get())
|
||||
}
|
||||
|
||||
@Test fun decoderCloseFailureDoesNotSkipTheIndependentTrackRelease() {
|
||||
NativeLib.failDecoderClose = true
|
||||
val output = AudioTrack.Plan().also { AudioTrack.plans.add(it) }; val completed = CountDownLatch(1)
|
||||
manager.playVoiceFile("voice.opus", onComplete = { completed.countDown() })
|
||||
assertTrue(completed.await(2, TimeUnit.SECONDS)); assertTrue(output.released)
|
||||
val closed = manager.requestClose(); assertFalse(closed.isDone)
|
||||
NativeLib.failDecoderClose = false; closed.get(2, TimeUnit.SECONDS)
|
||||
assertEquals(1, NativeLib.decoderCloses.get()); assertEquals(1, output.releaseCalls)
|
||||
}
|
||||
|
||||
@Test fun blockedNativeFeedCannotHoldMainWhileAnAsynchronousCloseIsPending() {
|
||||
val entered = CountDownLatch(1); val leave = CountDownLatch(1)
|
||||
NativeLib.onFeed = {
|
||||
entered.countDown()
|
||||
while (leave.count != 0L) try { leave.await() } catch (_: InterruptedException) {}
|
||||
}
|
||||
val device = plan(); assertTrue(manager.startRecording("7")); device.chunks.add(AudioRecord.Chunk())
|
||||
assertTrue(entered.await(1, TimeUnit.SECONDS))
|
||||
val closed = manager.requestClose()
|
||||
try {
|
||||
assertFalse(closed.isDone); assertFalse(manager.startRecording("8"))
|
||||
assertThrows(IllegalStateException::class.java) { AudioRecorderManager.requireReleasedForCore() }
|
||||
} finally { leave.countDown() }
|
||||
closed.get(2, TimeUnit.SECONDS); assertTrue(device.released); assertFalse(NativeLib.active)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user