From 17caeb0a9c8687350f92e401b2f7e28bbe137c23 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 28 Oct 2026 17:46:55 +0100 Subject: [PATCH] Cover capture startup failures that overlap a replacement group --- .../utun/chat/data/RouterHostEnvironment.kt | 2 ++ .../chat/data/RadioWorkerLifecycleTest.kt | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt b/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt index aa3a4a9b..f137208f 100644 --- a/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt +++ b/tools/chatgui-android/router-tests/src/main/kotlin/com/utun/chat/data/RouterHostEnvironment.kt @@ -46,6 +46,7 @@ object NativeLib { @Volatile var radioPullResult = 0 val radioPullResults = ConcurrentLinkedQueue() @Volatile var onRadioFeed: (Long) -> Unit = {} + @Volatile var onRadioCaptureStart: (Long) -> Unit = {} val radioFeeds: MutableList = Collections.synchronizedList(mutableListOf()) fun radioSetActive(group: Long, active: Boolean) { radioCommands.add(RadioCommand("active=$active", group)) } fun radioConfigure(request: Long, settings: RadioAutoSettings): Boolean { radioRequest = request; return settings.valid() } @@ -75,6 +76,7 @@ object NativeLib { if ("capture reject" in radioFailures) return false radioMode = if ("detector off" in radioFailures) 0 else mode radioCaptureAlive = true + onRadioCaptureStart(group) check("capture start" !in radioFailures) { "native capture start failed after effect" } return true } diff --git a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioWorkerLifecycleTest.kt b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioWorkerLifecycleTest.kt index a2fd35a9..df39b0b5 100644 --- a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioWorkerLifecycleTest.kt +++ b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/RadioWorkerLifecycleTest.kt @@ -29,7 +29,7 @@ class RadioWorkerLifecycleTest { AudioManager.shared = AudioManager(); CallController.blocked = false AndroidMicrophoneCapture.reset(); AudioTrack.created.clear(); AudioTrack.nextFailures = emptySet(); AudioTrack.minimumBuffer = 1920 NativeLib.radioFailures = emptySet(); NativeLib.radioPullResult = 0; NativeLib.radioPullResults.clear() - NativeLib.onRadioFeed = {}; NativeLib.radioCommands.clear(); NativeLib.radioFeeds.clear() + NativeLib.onRadioFeed = {}; NativeLib.onRadioCaptureStart = {}; NativeLib.radioCommands.clear(); NativeLib.radioFeeds.clear() Handler.rejectDelayed = false; Handler.rejectImmediate = false LogManager.entries.clear(); LogManager.console = true engine = RadioAudioEngine() @@ -39,7 +39,8 @@ class RadioWorkerLifecycleTest { @After fun release() { gates.forEach { it.countDown() } - NativeLib.onRadioFeed = {}; NativeLib.radioFailures = emptySet(); NativeLib.radioPullResult = 0; NativeLib.radioPullResults.clear() + NativeLib.onRadioFeed = {}; NativeLib.onRadioCaptureStart = {}; NativeLib.radioFailures = emptySet() + NativeLib.radioPullResult = 0; NativeLib.radioPullResults.clear() Handler.rejectImmediate = false; Handler.rejectDelayed = false AudioTrack.created.forEach { it.failures = emptySet(); it.forcedWriteResult = null; it.onWrite = {} } engine.stop().get(5, TimeUnit.SECONDS); main.advanceBy(10_000); drain() @@ -257,4 +258,18 @@ class RadioWorkerLifecycleTest { await("new manual request") { NativeLib.radioTransmitting() } assertEquals(8L, engine.activeGroup.value); assertTrue(engine.isTalking()) } + + @Test fun aLateCaptureStartupFailureCannotClearANewGroupsManualRequest() { + val entered = CountDownLatch(1) + val gate = CountDownLatch(1).also { gates.add(it) } + NativeLib.onRadioCaptureStart = { group -> + if (group == 7L) { entered.countDown(); gate.await(5, TimeUnit.SECONDS); throw IllegalStateException("old capture startup failed") } + } + assertTrue(engine.talkBegin()); assertTrue(entered.await(5, TimeUnit.SECONDS)) + engine.start(8); assertTrue(engine.talkBegin(RadioPttSource.VOLUME)) + gate.countDown(); drain(); main.dispatchAll() + NativeLib.onRadioCaptureStart = {}; engine.onSettingsApplied(NativeLib.radioRequest, true); drain() + await("new manual request after old startup failure") { NativeLib.radioTransmitting() } + assertEquals(8L, engine.activeGroup.value); assertTrue(engine.isTalking()) + } }