From e9fa7ebb453d440f534244d51718129f93f7ebd1 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 29 Oct 2026 01:29:47 +0100 Subject: [PATCH] Exercise camera teardown despite RX or captured-image cleanup errors --- .../chat/data/CallVideoEngineLifecycleTest.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tools/chatgui-android/video-tests/src/test/kotlin/com/utun/chat/data/CallVideoEngineLifecycleTest.kt b/tools/chatgui-android/video-tests/src/test/kotlin/com/utun/chat/data/CallVideoEngineLifecycleTest.kt index 901c39b25..ddb74872d 100644 --- a/tools/chatgui-android/video-tests/src/test/kotlin/com/utun/chat/data/CallVideoEngineLifecycleTest.kt +++ b/tools/chatgui-android/video-tests/src/test/kotlin/com/utun/chat/data/CallVideoEngineLifecycleTest.kt @@ -21,6 +21,7 @@ class CallVideoEngineLifecycleTest { private val engines = mutableListOf() private val uncaught = CopyOnWriteArrayList() private val gates = mutableListOf() + private val images = mutableListOf() private val manager get() = CameraManager.shared private val main get() = Looper.getMainLooper() @Before fun prepare() { @@ -56,6 +57,7 @@ class CallVideoEngineLifecycleTest { Handler.rejectDelayed = false; Handler.rejectImmediate = false OrientationEventListener.failures = emptySet(); ChatApplication.instance.configProvider.failures = emptySet() CallVideoNative.failures = emptySet(); CallVideoNative.onOpen = {}; CallVideoNative.onClose = {} + images.forEach { it.failures = emptySet() } CallVideoCodec.onProbe = {}; CallVideoCodec.created.forEach { it.failures = emptySet() } manager.failures = emptySet(); manager.autoClosed = true manager.opened.forEachIndexed { index, open -> @@ -110,6 +112,26 @@ class CallVideoEngineLifecycleTest { encoder.failures = emptySet(); await("encoder release retries") { engine.stop() } } + @Test fun rxReleaseFailureDoesNotKeepTheCameraOrEncoderRunning() { + val engine = capture(); CallVideoNative.failures = setOf("destroy") + assertThrows(IllegalStateException::class.java) { engine.stop() } + await("TX camera and encoder close despite RX error") { + manager.opened.single().device.closed && CallVideoCodec.created.all { it.closed } + } + assertTrue(7L in CallVideoNative.ports) + CallVideoNative.failures = emptySet(); await("RX retry allows final native close") { engine.stop() } + } + + @Test fun cameraImageReleaseFailureIsContainedAndRetainedUntilRetry() { + val engine = capture(); val image = android.media.Image().also { images.add(it); it.failures = setOf("close") } + ImageReader.created.single().push(image) + await("image failure disables capture") { !engine.state.value.settings.enabled } + await("image failure closes camera independently") { manager.opened.single().device.closed } + assertTrue(thread(engine).isAlive); assertFalse(engine.stop()); assertFalse(image.closed) + image.failures = emptySet(); await("image retry allows acknowledged stop") { engine.stop() } + assertTrue(image.closed) + } + @Test fun settingsSaveFailureIsContainedAndDoesNotApplyAnUncommittedSelection() { val engine = engine(); val previous = engine.state.value.settings ChatApplication.instance.configProvider.failures = setOf("settings save")