diff --git a/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt new file mode 100644 index 00000000..7f6ee1d9 --- /dev/null +++ b/tools/chatgui-android/router-tests/src/test/kotlin/com/utun/chat/data/SettingsOperationsTest.kt @@ -0,0 +1,40 @@ +package com.utun.chat.data + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Test + +class SettingsOperationsTest { + @Test fun acceptedSaveSurvivesUiCancellationAndRestartWaitsForItsCommit() = runTest { + val owner = SettingsOperations(this) { _, _ -> } + val committed = CompletableDeferred(); val order = mutableListOf() + val save = owner.submit("name") { committed.await(); order.add("commit") } + val second = owner.submit("runtime") { order.add("apply") } + val ui = launch { save.await() }; ui.cancel() + val restart = async { owner.awaitIdle(); order.add("restart") } + assertTrue(order.isEmpty()); assertFalse(save.isCancelled) + committed.complete(Unit); save.await(); second.await(); restart.await() + assertEquals(listOf("commit", "apply", "restart"), order) + } + @Test fun failedPersistenceCannotRunTheFollowingRuntimeUpdateOrKillTheQueue() = runTest { + val logs = mutableListOf(); val owner = SettingsOperations(this) { _, message -> logs.add(message) } + var runtime = false + val failed = owner.submit("volume") { throw java.io.IOException("disk rejected"); @Suppress("UNREACHABLE_CODE") runtime = true } + assertTrue(runCatching { failed.await() }.exceptionOrNull() is java.io.IOException) + assertFalse(runtime); assertTrue(owner.failed.value); assertTrue(logs.any { "disk rejected" in it }) + owner.submit("retry") { runtime = true }.await() + assertTrue(runtime); owner.dismissFailure(); assertFalse(owner.failed.value) + } + @Test fun cancelledBarrierWaiterCannotCancelAnAcceptedChange() = runTest { + val owner = SettingsOperations(this) { _, _ -> } + val gate = CompletableDeferred(); var saved = false + val change = owner.submit("stereo") { gate.await(); saved = true } + val waiter = launch { owner.awaitIdle() }; waiter.cancel() + gate.complete(Unit); change.await(); assertTrue(saved); assertFalse(change.isCancelled) + } +}