From 59ce582bd8f7103bfeff43509bcc306b88f9a80f Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Wed, 17 Sep 2025 16:49:06 +0200 Subject: [PATCH 1/2] Fix flaky tests --- karma/chrome_bin.js | 2 +- krpc/krpc-test/build.gradle.kts | 8 -------- .../kotlinx/rpc/krpc/test/KrpcTestService.kt | 6 ++++++ .../rpc/krpc/test/KrpcTestServiceBackend.kt | 1 + .../rpc/krpc/test/KrpcTransportTestBase.kt | 16 +++++++++++++--- .../kotlinx/rpc/krpc/test/LocalTransportTest.kt | 3 +++ .../kotlinx/rpc/krpc/test/TransportTest.kt | 7 ++++--- .../rpc/krpc/test/KrpcTransportTestBase.js.kt | 1 + .../kotlinx/rpc/krpc/test/TransportTest.js.kt | 2 +- .../rpc/krpc/test/KrpcTransportTestBase.jvm.kt | 1 + .../krpc/test/KrpcTransportTestBase.native.kt | 1 + .../krpc/test/KrpcTransportTestBase.wasmJs.kt | 1 + .../rpc/krpc/test/TransportTest.wasmJs.kt | 2 +- .../krpc/test/KrpcTransportTestBase.wasmWasi.kt | 1 + 14 files changed, 35 insertions(+), 17 deletions(-) diff --git a/karma/chrome_bin.js b/karma/chrome_bin.js index 7887ca840..59cede3ce 100644 --- a/karma/chrome_bin.js +++ b/karma/chrome_bin.js @@ -21,7 +21,7 @@ config.set({ "client": { captureConsole: true, "mocha": { - timeout: 10000 + timeout: 300000 } } }); diff --git a/krpc/krpc-test/build.gradle.kts b/krpc/krpc-test/build.gradle.kts index 758e6b45f..092aefdaa 100644 --- a/krpc/krpc-test/build.gradle.kts +++ b/krpc/krpc-test/build.gradle.kts @@ -100,14 +100,6 @@ tasks.named("clean") { delete(resourcesPath.walk().filter { it.isFile && it.extension == tmpExt }.toList()) } -tasks.withType { - onlyIf { - // for some reason browser tests don't wait for the test to complete and end immediately - // KRPC-166 - !targetName.orEmpty().endsWith("browser") - } -} - tasks.register("moveToGold") { doLast { resourcesPath.walk().forEach { diff --git a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt index 1c46e57c9..70a2fc878 100644 --- a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt @@ -94,6 +94,7 @@ interface KrpcTestService { suspend fun nullableInt(v: Int?): Int? suspend fun nullableList(v: List?): List? + suspend fun nullableEnum(enum: TestEnum?): TestEnum? fun delayForever(): Flow suspend fun answerToAnything(arg: String): Int @@ -101,4 +102,9 @@ interface KrpcTestService { suspend fun krpc173() fun unitFlow(): Flow + + @Serializable + enum class TestEnum { + ENUM_VALUE_1, ENUM_VALUE_2 + } } diff --git a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt index bd1b386bc..8fb3a0482 100644 --- a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt @@ -243,6 +243,7 @@ class KrpcTestServiceBackend : KrpcTestService { override suspend fun nullableInt(v: Int?): Int? = v override suspend fun nullableList(v: List?): List? = v + override suspend fun nullableEnum(enum: KrpcTestService.TestEnum?): KrpcTestService.TestEnum? = enum override fun delayForever(): Flow = flow { emit(true) diff --git a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt index 43424cc93..bc9fb16c3 100644 --- a/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt @@ -324,7 +324,7 @@ abstract class KrpcTransportTestBase { @Test fun RPC_should_be_able_to_receive_100_000_ints_in_reasonable_time() = runTest(timeout = EXTENDED_TIMEOUT) { - val n = 100_000 + val n = iterations_100_000 var counter = 0 val last = client.getNInts(n).onEach { counter++ @@ -336,8 +336,8 @@ abstract class KrpcTransportTestBase { } @Test - fun RPC_should_be_able_to_receive_100_000_ints_with_batching_in_reasonable_time() = runTest { - val n = 100_000 + fun RPC_should_be_able_to_receive_100_000_ints_with_batching_in_reasonable_time() = runTest(timeout = EXTENDED_TIMEOUT) { + val n = iterations_100_000 assertEquals(client.getNIntsBatched(n).last().last(), n) } @@ -400,6 +400,15 @@ abstract class KrpcTransportTestBase { assertEquals(listOf(1), client.nullableList(listOf(1))) } + @Test + open fun testNullableEnums() = runTest { + assertNull(client.nullableEnum(null)) + assertEquals( + KrpcTestService.TestEnum.ENUM_VALUE_1, + client.nullableEnum(KrpcTestService.TestEnum.ENUM_VALUE_1), + ) + } + @Test fun testServerCallCancellation() = runTest { val flag: Channel = Channel() @@ -500,3 +509,4 @@ abstract class KrpcTransportTestBase { private val EXTENDED_TIMEOUT = if (isJs) 500.seconds else 200.seconds internal expect val isJs: Boolean +internal expect val iterations_100_000 : Int diff --git a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt index 1e8e30f84..5616e5a96 100644 --- a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt @@ -65,4 +65,7 @@ class ProtoBufLocalTransportTest : LocalTransportTest() { @Test override fun testNullableLists(): TestResult = runTest { } + + @Test + override fun testNullableEnums(): TestResult = runTest { } } diff --git a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt index 8897ef491..14ce7a600 100644 --- a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt @@ -258,12 +258,12 @@ class TransportTest { val server = serverOf(transports) - delay(1000) + withContext(Dispatchers.Default) { delay(1000) } val echoServices = server.registerServiceAndReturn { EchoImpl() } assertEquals("foo", firstResult.await()) assertEquals(1, echoServices.single().received.value) - delay(1000) + withContext(Dispatchers.Default) { delay(1000) } val secondServices = server.registerServiceAndReturn { SecondServer() } assertEquals("bar", secondResult.await()) assertEquals(1, secondServices.single().received.value) @@ -272,6 +272,7 @@ class TransportTest { } private val handshakeClassSerialName = KrpcProtocolMessage.Handshake.serializer().descriptor.serialName + @Suppress("RegExpRedundantEscape") // fails on js otherwise private val clientHandshake = ".*\\[Client\\] \\[Send\\] \\{\"type\":\"$handshakeClassSerialName\".*".toRegex() @@ -299,7 +300,7 @@ class TransportTest { server.registerServiceAndReturn { SecondServer() } client.withService().apply { echo("foo"); echo("bar") } - client.withService().apply{ second("bar"); second("baz") } + client.withService().apply { second("bar"); second("baz") } assertEquals(1, transportInitialized.value) assertEquals(1, configInitialized.value) diff --git a/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.js.kt b/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.js.kt index 118af4f57..a28ddd4d4 100644 --- a/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.js.kt +++ b/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.js.kt @@ -5,3 +5,4 @@ package kotlinx.rpc.krpc.test actual val isJs: Boolean = true +internal actual val iterations_100_000: Int = 10_000 diff --git a/krpc/krpc-test/src/jsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.js.kt b/krpc/krpc-test/src/jsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.js.kt index 483c79d1e..709a052f9 100644 --- a/krpc/krpc-test/src/jsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.js.kt +++ b/krpc/krpc-test/src/jsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.js.kt @@ -4,4 +4,4 @@ package kotlinx.rpc.krpc.test -internal actual val testIterations: Int = 100 +internal actual val testIterations: Int = 10 diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.jvm.kt b/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.jvm.kt index ee108e9c0..caf04c984 100644 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.jvm.kt +++ b/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.jvm.kt @@ -5,3 +5,4 @@ package kotlinx.rpc.krpc.test actual val isJs: Boolean = false +internal actual val iterations_100_000: Int = 100_000 diff --git a/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.native.kt b/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.native.kt index ee108e9c0..caf04c984 100644 --- a/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.native.kt +++ b/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.native.kt @@ -5,3 +5,4 @@ package kotlinx.rpc.krpc.test actual val isJs: Boolean = false +internal actual val iterations_100_000: Int = 100_000 diff --git a/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmJs.kt b/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmJs.kt index 118af4f57..a28ddd4d4 100644 --- a/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmJs.kt +++ b/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmJs.kt @@ -5,3 +5,4 @@ package kotlinx.rpc.krpc.test actual val isJs: Boolean = true +internal actual val iterations_100_000: Int = 10_000 diff --git a/krpc/krpc-test/src/wasmJsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.wasmJs.kt b/krpc/krpc-test/src/wasmJsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.wasmJs.kt index 483c79d1e..709a052f9 100644 --- a/krpc/krpc-test/src/wasmJsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.wasmJs.kt +++ b/krpc/krpc-test/src/wasmJsTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.wasmJs.kt @@ -4,4 +4,4 @@ package kotlinx.rpc.krpc.test -internal actual val testIterations: Int = 100 +internal actual val testIterations: Int = 10 diff --git a/krpc/krpc-test/src/wasmWasiMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmWasi.kt b/krpc/krpc-test/src/wasmWasiMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmWasi.kt index debf06d0e..14eaab9e7 100644 --- a/krpc/krpc-test/src/wasmWasiMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmWasi.kt +++ b/krpc/krpc-test/src/wasmWasiMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmWasi.kt @@ -5,3 +5,4 @@ package kotlinx.rpc.krpc.test internal actual val isJs: Boolean = true +internal actual val iterations_100_000: Int = 100_000 From c33dfb3ee5d8a9c2ef4a6dc122a4e753770580ff Mon Sep 17 00:00:00 2001 From: Alexander Sysoev Date: Thu, 18 Sep 2025 14:27:45 +0200 Subject: [PATCH 2/2] Added extensive logging for flaky tests, fixed Transport test --- .../kotlinx/rpc/krpc/test/TransportTest.kt | 10 ++++-- .../test/cancellation/CancellationTest.kt | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt index 14ce7a600..a79e63196 100644 --- a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt @@ -258,12 +258,18 @@ class TransportTest { val server = serverOf(transports) - withContext(Dispatchers.Default) { delay(1000) } + repeat(10) { + // give way to requests + yield() + } val echoServices = server.registerServiceAndReturn { EchoImpl() } assertEquals("foo", firstResult.await()) assertEquals(1, echoServices.single().received.value) - withContext(Dispatchers.Default) { delay(1000) } + repeat(10) { + // give way to requests + yield() + } val secondServices = server.registerServiceAndReturn { SecondServer() } assertEquals("bar", secondResult.await()) assertEquals(1, secondServices.single().received.value) diff --git a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt index 19575c465..d314c7729 100644 --- a/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt @@ -127,25 +127,33 @@ class CancellationTest { service.cancellationInOutgoingStream( stream = flow { emit(42) + println("[testCancellationInClientStream] emit 42") emit(43) + println("[testCancellationInClientStream] emit 43") }, cancelled = flow { emit(1) + println("[testCancellationInClientStream] emit 1") serverInstance().firstIncomingConsumed.await() + println("[testCancellationInClientStream] firstIncomingConsumed") throw CancellationException("cancellationInClientStream") }, ) } requestJob.join() + println("[testCancellationInClientStream] Request job finished") serverInstance().consumedAll.await() + println("[testCancellationInClientStream] Server consumed all") assertFalse(requestJob.isCancelled, "Expected requestJob not to be cancelled") assertContentEquals(listOf(42, 43), serverInstance().consumedIncomingValues) } + println("[testCancellationInClientStream] Scope finished") checkAlive() stopAllAndJoin() + println("[testCancellationInClientStream] All done") assertEquals(1, serverInstance().cancellationsCounter.value, "Expected 1 request to be cancelled") } @@ -154,19 +162,23 @@ class CancellationTest { @Test fun testCancelClient() = runCancellationTest { val firstRequestJob = launch { + println("[testCancelClient] firstRequestJob started") service.longRequest() } val secondService = client.withService() val secondRequestJob = launch { + println("[testCancelClient] secondRequestJob started") secondService.longRequest() } val clientFlowJob = launch { service.outgoingStream(flow { emit(0) + println("[testCancelClient] emit 0") serverInstance().fence.await() + println("[testCancelClient] fence awaited") emit(1) }) } @@ -181,18 +193,27 @@ class CancellationTest { } } + println("[testCancelClient] Requests sent") serverInstance().waitCounter.await(4) + println("[testCancelClient] Requests reached") client.close() client.awaitCompletion() + println("[testCancelClient] Client stopped") server.awaitCompletion() + println("[testCancelClient] Server stopped") firstRequestJob.join() + println("[testCancelClient] First request finished") secondRequestJob.join() + println("[testCancelClient] Second request finished") clientFlowJob.join() + println("[testCancelClient] Client flow finished") serverInstance().fence.complete(Unit) serverFlowJob.join() + println("[testCancelClient] Server flow finished") serverInstance().cancellationsCounter.await(4) + println("[testCancelClient] Server cancellations counted") assertTrue(firstRequestJob.isCancelled, "Expected firstRequestJob to be cancelled") assertTrue(secondRequestJob.isCancelled, "Expected secondRequestJob to be cancelled") @@ -203,6 +224,7 @@ class CancellationTest { checkAlive(clientAlive = false, serverAlive = false) stopAllAndJoin() + println("[testCancelClient] All done") assertEquals(4, serverInstance().cancellationsCounter.value, "Expected 4 requests to be cancelled") } @@ -210,19 +232,23 @@ class CancellationTest { @Test fun testCancelServer() = runCancellationTest { val firstRequestJob = launch { + println("[testCancelServer] firstRequestJob started") service.longRequest() } val secondService = client.withService() val secondRequestJob = launch { + println("[testCancelServer] secondRequestJob started") secondService.longRequest() } val clientFlowJob = launch { service.outgoingStream(flow { emit(0) + println("[testCancelServer] emit 0") serverInstance().fence.await() + println("[testCancelServer] fence awaited") emit(1) }) } @@ -237,18 +263,27 @@ class CancellationTest { } } + println("[testCancelServer] Requests sent") serverInstance().waitCounter.await(4) // wait for requests to reach server + println("[testCancelServer] Requests reached") server.close() server.awaitCompletion() + println("[testCancelServer] Server stopped") client.awaitCompletion() + println("[testCancelServer] Client stopped") firstRequestJob.join() + println("[testCancelServer] First request finished") secondRequestJob.join() + println("[testCancelServer] Second request finished") clientFlowJob.join() + println("[testCancelServer] Client flow finished") serverInstance().fence.complete(Unit) serverFlowJob.join() + println("[testCancelServer] Server flow finished") serverInstance().cancellationsCounter.await(4) + println("[testCancelServer] Server cancellations counted") assertTrue(firstRequestJob.isCancelled, "Expected firstRequestJob to be cancelled") assertTrue(secondRequestJob.isCancelled, "Expected secondRequestJob to be cancelled") @@ -259,6 +294,7 @@ class CancellationTest { checkAlive(clientAlive = false, serverAlive = false) stopAllAndJoin() + println("[testCancelServer] All done") assertEquals(4, serverInstance().cancellationsCounter.value, "Expected 4 requests to be cancelled") }