diff --git a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt index 4a2b138e4..aaa9b4fc2 100644 --- a/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt +++ b/core/src/commonMain/kotlin/kotlinx/rpc/descriptor/RpcServiceDescriptor.kt @@ -88,6 +88,6 @@ public class RpcParameter(public val name: String, public val type: RpcType) @ExperimentalRpcApi public class RpcType(public val kType: KType) { override fun toString(): String { - return return kType.toString() + return kType.toString() } } diff --git a/gradle-conventions/common/src/main/kotlin/util/JsTarget.kt b/gradle-conventions/common/src/main/kotlin/util/JsTarget.kt index 12187c011..2d480c488 100644 --- a/gradle-conventions/common/src/main/kotlin/util/JsTarget.kt +++ b/gradle-conventions/common/src/main/kotlin/util/JsTarget.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package util @@ -40,7 +40,7 @@ fun KotlinJsTargetDsl.configureJsAndWasmJsTasks() { nodejs { testTask { useMocha { - timeout = "10000" + timeout = "100s" } } } diff --git a/gradle-conventions/common/src/main/kotlin/util/ProjectKotlinConfig.kt b/gradle-conventions/common/src/main/kotlin/util/ProjectKotlinConfig.kt index c2361a28f..aea92ee86 100644 --- a/gradle-conventions/common/src/main/kotlin/util/ProjectKotlinConfig.kt +++ b/gradle-conventions/common/src/main/kotlin/util/ProjectKotlinConfig.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package util @@ -38,6 +38,7 @@ class ProjectKotlinConfig( jvm: Boolean = true, js: Boolean = true, wasmJs: Boolean = true, + wasmJsD8: Boolean = true, wasmWasi: Boolean = true, val native: Boolean = true, ) : Project by project { @@ -48,18 +49,18 @@ class ProjectKotlinConfig( private fun isIncluded( targetName: String, - kotlinVersion: KotlinVersion, - lookupTable: Map, + lookupTable: Map = targetsLookup, ): Boolean { return lookupTable[targetName]?.let { sinceKotlin -> sinceKotlin == FULLY_SUPPORTED_TARGET || sinceKotlin.kotlinVersionParsed() <= kotlinVersion } ?: false } - val jvm: Boolean by lazy { jvm && isIncluded("jvm", kotlinVersion, targetsLookup) } - val js: Boolean by lazy { js && isIncluded("js", kotlinVersion, targetsLookup) } - val wasmJs: Boolean by lazy { wasmJs && isIncluded("wasmJs", kotlinVersion, targetsLookup) } - val wasmWasi: Boolean by lazy { wasmWasi && isIncluded("wasmWasi", kotlinVersion, targetsLookup) } + val jvm: Boolean by lazy { jvm && isIncluded("jvm") } + val js: Boolean by lazy { js && isIncluded("js") } + val wasmJs: Boolean by lazy { wasmJs && isIncluded("wasmJs") } + val wasmJsD8: Boolean by lazy { wasmJsD8 && wasmJs } + val wasmWasi: Boolean by lazy { wasmWasi && isIncluded("wasmWasi") } private val nativeLookup by lazy { targetsLookup.filterKeys { key -> @@ -71,7 +72,6 @@ class ProjectKotlinConfig( .filter { targetFunction -> targetFunction.parameters.size == 1 && isIncluded( targetName = targetFunction.name, - kotlinVersion = kotlinVersion, lookupTable = nativeLookup, ) }.map { function -> @@ -84,6 +84,7 @@ fun Project.withKotlinConfig(configure: ProjectKotlinConfig.() -> Unit) { val excludeJvm: Boolean by optionalProperty() val excludeJs: Boolean by optionalProperty() val excludeWasmJs: Boolean by optionalProperty() + val excludeWasmJsD8: Boolean by optionalProperty() val excludeWasmWasi: Boolean by optionalProperty() val excludeNative: Boolean by optionalProperty() @@ -93,6 +94,7 @@ fun Project.withKotlinConfig(configure: ProjectKotlinConfig.() -> Unit) { jvm = !excludeJvm, js = !excludeJs, wasmJs = !excludeWasmJs, + wasmJsD8 = !excludeWasmJsD8, wasmWasi = !excludeWasmWasi, native = !excludeNative, ).configure() diff --git a/gradle-conventions/src/main/kotlin/util/wasm.kt b/gradle-conventions/src/main/kotlin/util/wasm.kt index b8633b529..0184877be 100644 --- a/gradle-conventions/src/main/kotlin/util/wasm.kt +++ b/gradle-conventions/src/main/kotlin/util/wasm.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package util @@ -23,7 +23,9 @@ fun ProjectKotlinConfig.configureWasm() { browser() nodejs() - d8() + if (wasmJsD8) { + d8() + } binaries.library() }.configurePublication() diff --git a/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt b/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt index 665b35d98..3a31eeacc 100644 --- a/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt +++ b/krpc/krpc-client/src/commonMain/kotlin/kotlinx/rpc/krpc/client/KrpcClient.kt @@ -7,7 +7,6 @@ package kotlinx.rpc.krpc.client import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ClosedReceiveChannelException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.rpc.RpcCall @@ -258,9 +257,7 @@ public abstract class KrpcClient( val id = callCounter.incrementAndGet() - val dataTypeString = callable.dataType.toString() - - val callId = "$connectionId:$dataTypeString:$id" + val callId = "$connectionId:${callable.name}:$id" logger.trace { "start a call[$callId] ${callable.name}" } @@ -325,9 +322,7 @@ public abstract class KrpcClient( val callable = call.descriptor.getCallable(call.callableName) ?: error("Unexpected callable '${call.callableName}' for ${call.descriptor.fqName} service") - val dataTypeString = callable.dataType.toString() - - val callId = "$connectionId:$dataTypeString:$id" + val callId = "$connectionId:${callable.name}:$id" val channel = Channel() diff --git a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/KrpcConnector.kt b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/KrpcConnector.kt index 08bdcf327..a5fe988f0 100644 --- a/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/KrpcConnector.kt +++ b/krpc/krpc-core/src/commonMain/kotlin/kotlinx/rpc/krpc/internal/KrpcConnector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.internal @@ -154,9 +154,25 @@ public class KrpcConnector( if (waitForSubscribers) { waiting.getOrPut(message.getKey()) { mutableListOf() }.add(message) - logger.warn { + val reason = when (result) { + is HandlerResult.Failure -> { + "Unhandled exception while processing ${result.cause?.message}" + } + + is HandlerResult.NoSubscription -> { + "No service with key '${message.getKey()}' and '${message.serviceType}' type was registered." + + "Available: keys: [${subscriptions.keys.joinToString()}]" + } + + else -> { + "Unknown" + } + } + + logger.warn((result as? HandlerResult.Failure)?.cause) { "No registered service of ${message.serviceType} service type " + - "was able to process message at the moment. Waiting for new services." + "was able to process message at the moment. Waiting for new services." + + "Reason: $reason" } return diff --git a/krpc/krpc-ktor/krpc-ktor-core/src/jvmTest/kotlin/kotlinx/rpc/krpc/ktor/KtorTransportTest.kt b/krpc/krpc-ktor/krpc-ktor-core/src/jvmTest/kotlin/kotlinx/rpc/krpc/ktor/KtorTransportTest.kt index 21ae3d0c0..b7584d98a 100644 --- a/krpc/krpc-ktor/krpc-ktor-core/src/jvmTest/kotlin/kotlinx/rpc/krpc/ktor/KtorTransportTest.kt +++ b/krpc/krpc-ktor/krpc-ktor-core/src/jvmTest/kotlin/kotlinx/rpc/krpc/ktor/KtorTransportTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ @file:Suppress("ExtractKtorModule") @@ -31,6 +31,7 @@ class NewServiceImpl( override val coroutineContext: CoroutineContext, private val call: ApplicationCall, ) : NewService { + @Suppress("UastIncorrectHttpHeaderInspection") override suspend fun echo(value: String): String { assertEquals("test-header", call.request.headers["TestHeader"]) return value diff --git a/krpc/krpc-ktor/krpc-ktor-server/build.gradle.kts b/krpc/krpc-ktor/krpc-ktor-server/build.gradle.kts index 042078e46..eb1011754 100644 --- a/krpc/krpc-ktor/krpc-ktor-server/build.gradle.kts +++ b/krpc/krpc-ktor/krpc-ktor-server/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ plugins { @@ -9,7 +9,7 @@ plugins { kotlin { sourceSets { - jvmMain { + commonMain { dependencies { api(projects.krpc.krpcServer) api(projects.krpc.krpcKtor.krpcKtorCore) diff --git a/krpc/krpc-ktor/krpc-ktor-server/gradle.properties b/krpc/krpc-ktor/krpc-ktor-server/gradle.properties deleted file mode 100644 index 58d0f9459..000000000 --- a/krpc/krpc-ktor/krpc-ktor-server/gradle.properties +++ /dev/null @@ -1,6 +0,0 @@ -# -# Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. -# - -kotlinx.rpc.excludeJs=true -kotlinx.rpc.excludeNative=true diff --git a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/Krpc.kt b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/Krpc.kt similarity index 93% rename from krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/Krpc.kt rename to krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/Krpc.kt index 4f5922d1b..41ac1076c 100644 --- a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/Krpc.kt +++ b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/Krpc.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.ktor.server diff --git a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt similarity index 97% rename from krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt rename to krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt index 0647529b1..efff16c5e 100644 --- a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt +++ b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KrpcRoute.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.ktor.server diff --git a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorKrpcServer.kt b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorKrpcServer.kt similarity index 86% rename from krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorKrpcServer.kt rename to krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorKrpcServer.kt index 11431d831..8bba63699 100644 --- a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorKrpcServer.kt +++ b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorKrpcServer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.ktor.server diff --git a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorServerDsl.kt b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorServerDsl.kt similarity index 96% rename from krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorServerDsl.kt rename to krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorServerDsl.kt index db259b872..927e61a67 100644 --- a/krpc/krpc-ktor/krpc-ktor-server/src/jvmMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorServerDsl.kt +++ b/krpc/krpc-ktor/krpc-ktor-server/src/commonMain/kotlin/kotlinx/rpc/krpc/ktor/server/KtorServerDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.ktor.server diff --git a/krpc/krpc-test/build.gradle.kts b/krpc/krpc-test/build.gradle.kts index 8c6fa361f..80e328f36 100644 --- a/krpc/krpc-test/build.gradle.kts +++ b/krpc/krpc-test/build.gradle.kts @@ -1,8 +1,10 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ +import com.osacky.doctor.internal.sysProperty import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest import org.jetbrains.kotlin.gradle.targets.jvm.tasks.KotlinJvmTest import util.applyAtomicfuPlugin import java.nio.file.Files @@ -22,11 +24,7 @@ kotlin { // Workaround for // KLIB resolver: Could not find "org.jetbrains.kotlinx:atomicfu" api(libs.atomicfu) - } - } - jvmMain { - dependencies { api(projects.krpc.krpcCore) api(projects.krpc.krpcServer) api(projects.krpc.krpcClient) @@ -35,11 +33,17 @@ kotlin { implementation(libs.serialization.core) implementation(libs.kotlin.test) + implementation(libs.coroutines.test) + } + } + + jvmMain { + dependencies { implementation(libs.kotlin.test.junit) } } - jvmTest { + commonTest { dependencies { implementation(projects.krpc.krpcTest) implementation(projects.krpc.krpcSerialization.krpcSerializationJson) @@ -47,12 +51,16 @@ kotlin { implementation(projects.krpc.krpcSerialization.krpcSerializationProtobuf) implementation(projects.krpc.krpcLogging) + implementation(libs.coroutines.test) + implementation(libs.kotlin.reflect) + } + } + + jvmTest { + dependencies { implementation(libs.slf4j.api) implementation(libs.logback.classic) - - implementation(libs.coroutines.test) implementation(libs.coroutines.debug) - implementation(libs.kotlin.reflect) } } } @@ -72,7 +80,15 @@ tasks.named("clean") { delete(resourcesPath.walk().filter { it.isFile && it.extension == tmpExt }.toList()) } -tasks.create("moveToGold") { +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 { if (it.isFile && it.extension == tmpExt) { diff --git a/krpc/krpc-test/gradle.properties b/krpc/krpc-test/gradle.properties new file mode 100644 index 000000000..825c4ae92 --- /dev/null +++ b/krpc/krpc-test/gradle.properties @@ -0,0 +1,6 @@ +# +# Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. +# + +# tests fail with some obscure reason +kotlinx.rpc.excludeWasmJsD8=true diff --git a/krpc/krpc-test/src/commonMain/kotlin/Stub.kt b/krpc/krpc-test/src/commonMain/kotlin/Stub.kt deleted file mode 100644 index 2ebbf3513..000000000 --- a/krpc/krpc-test/src/commonMain/kotlin/Stub.kt +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("detekt.MissingPackageDeclaration") - -@Suppress("unused") -internal class Stub diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestClient.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestClient.kt similarity index 90% rename from krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestClient.kt rename to krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestClient.kt index f2ccc1f42..5799f4328 100644 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestClient.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestClient.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServer.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServer.kt similarity index 90% rename from krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServer.kt rename to krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServer.kt index 5176f0913..24c03069e 100644 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServer.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServer.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt similarity index 86% rename from krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt rename to krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt index b4d31f729..6a031a73d 100644 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestService.kt @@ -11,8 +11,37 @@ import kotlinx.rpc.RemoteService import kotlinx.rpc.annotations.Rpc import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable -import java.time.LocalDate -import java.time.LocalDateTime + +data class LocalDate( + val year: Int, + val month: Int, + val day: Int, +) { + override fun toString(): String { + return "$year-$month-$day" + } + + companion object { + fun parse(str: String): LocalDate = str.split('-').let { + LocalDate(it[0].toInt(), it[1].toInt(), it[2].toInt()) + } + } +} + +data class LocalDateTime( + val date: LocalDate, + val time: String, +) { + override fun toString(): String { + return "$date $time" + } + + companion object { + fun parse(str: String): LocalDateTime = str.split(' ').let { + LocalDateTime(LocalDate.parse(it[0]), it[1]) + } + } +} @Suppress("detekt.TooManyFunctions") @Rpc diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt similarity index 94% rename from krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt rename to krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt index 8f72e4056..f47ac6bba 100644 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.kt @@ -7,9 +7,6 @@ package kotlinx.rpc.krpc.test import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.serialization.Serializable -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resumeWithException import kotlin.test.assertContentEquals @@ -110,22 +107,32 @@ class KrpcTestServiceBackend(override val coroutineContext: CoroutineContext) : } override suspend fun nonSerializableClass(localDate: LocalDate): LocalDate { - return localDate.plusDays(1) + return LocalDate(localDate.year, localDate.month, localDate.day + 1) } override suspend fun nonSerializableClassWithSerializer(localDateTime: LocalDateTime): String { - return localDateTime.plusDays(1).format(DateTimeFormatter.ISO_DATE_TIME) + return LocalDateTime( + date = LocalDate( + year = localDateTime.date.year, + month = localDateTime.date.month, + day = localDateTime.date.day + 1, + ), + time = localDateTime.time, + ).toString() } override suspend fun incomingStreamSyncCollect(arg1: Flow): Int { return arg1.count() } + val incomingStreamAsyncCollectLatch = CompletableDeferred() + @OptIn(DelicateCoroutinesApi::class) override suspend fun incomingStreamAsyncCollect(arg1: Flow): Int { @Suppress("detekt.GlobalCoroutineUsage") GlobalScope.launch { assertContentEquals(listOf("test1", "test2", "test3"), arg1.toList()) + incomingStreamAsyncCollectLatch.complete(Unit) } return 5 } @@ -240,9 +247,9 @@ class KrpcTestServiceBackend(override val coroutineContext: CoroutineContext) : override suspend fun throwsUNSTOPPABLEThrowable(message: String) { suspendCancellableCoroutine { continuation -> - Thread { + runThreadIfPossible { continuation.resumeWithException(Throwable(message)) - }.start() + } } } @@ -320,3 +327,5 @@ class KrpcTestServiceBackend(override val coroutineContext: CoroutineContext) : return state } } + +internal expect fun runThreadIfPossible(runner: () -> Unit) 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 new file mode 100644 index 000000000..b1456e09f --- /dev/null +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt @@ -0,0 +1,689 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("FunctionName") + +package kotlinx.rpc.krpc.test + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.test.runTest +import kotlinx.rpc.awaitFieldInitialization +import kotlinx.rpc.krpc.KrpcTransport +import kotlinx.rpc.krpc.rpcClientConfig +import kotlinx.rpc.krpc.rpcServerConfig +import kotlinx.rpc.krpc.serialization.KrpcSerialFormatConfiguration +import kotlinx.rpc.krpc.server.KrpcServer +import kotlinx.rpc.krpc.streamScoped +import kotlinx.rpc.registerService +import kotlinx.rpc.withService +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.modules.SerializersModule +import kotlin.coroutines.cancellation.CancellationException +import kotlin.test.* + +internal object LocalDateSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) + + override fun serialize( + encoder: Encoder, + value: LocalDate, + ) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): LocalDate { + return LocalDate.parse(decoder.decodeString()) + } +} + +internal object LocalDateTimeSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) + + override fun serialize( + encoder: Encoder, + value: LocalDateTime, + ) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): LocalDateTime { + return LocalDateTime.parse(decoder.decodeString()) + } +} + +abstract class KrpcTransportTestBase { + protected val serializersModule = SerializersModule { + contextual(LocalDate::class) { LocalDateSerializer } + } + + protected abstract val serializationConfig: KrpcSerialFormatConfiguration.() -> Unit + + private val serverConfig by lazy { + rpcServerConfig { + sharedFlowParameters { + replay = KrpcTestServiceBackend.SHARED_FLOW_REPLAY + } + + serialization { + serializationConfig() + } + } + } + + private val clientConfig by lazy { + rpcClientConfig { + sharedFlowParameters { + replay = KrpcTestServiceBackend.SHARED_FLOW_REPLAY + } + + serialization { + serializationConfig() + } + } + } + + abstract val clientTransport: KrpcTransport + abstract val serverTransport: KrpcTransport + + private lateinit var backend: KrpcServer + private lateinit var client: KrpcTestService + private lateinit var server: KrpcTestServiceBackend + + @BeforeTest + fun start() { + backend = KrpcTestServer(serverConfig, serverTransport) + backend.registerService { ctx -> + KrpcTestServiceBackend(ctx).also { + server = it + } + } + + client = KrpcTestClient(clientConfig, clientTransport).withService() + } + + @AfterTest + fun end() { + backend.cancel() + } + + @Test + fun nonSuspend() = runTest { + assertEquals(List(10) { it }, client.nonSuspendFlow().toList()) + } + + @Test + fun nonSuspendErrorOnEmit() = runTest { + val flow = client.nonSuspendFlowErrorOnReturn() + assertFails { + flow.toList() + } + } + + @Test + fun nonSuspendErrorOnReturn() = runTest { + assertFails { + client.nonSuspendFlowErrorOnReturn().toList() + } + } + + + @Test + fun empty() { + backend.cancel() + client.cancel() + } + + @Test + fun returnType() = runTest { + assertEquals("test", client.returnType()) + } + + @Test + fun simpleWithParams() = runTest { + assertEquals("name".reversed(), client.simpleWithParams("name")) + } + + @Test + @Ignore // works on my machine issue – timeouts on TC + fun simpleWithParams100000() = runTest { + repeat(100000) { + assertEquals("name".reversed(), client.simpleWithParams("name")) + } + } + + @Test + fun genericReturnType() = runTest { + assertEquals(listOf("hello", "world"), client.genericReturnType()) + } + + @Test + fun nonSerializableParameter() = runTest { + val localDate = LocalDate(2001, 8, 23) + val resultDate = client.nonSerializableClass(localDate) + assertEquals(LocalDate(2001, 8, 24), resultDate) + + val localDateTime = LocalDateTime(LocalDate(2001, 8, 23), "17:03") + val resultDateTime = client.nonSerializableClassWithSerializer(localDateTime) + + assertEquals( + LocalDateTime(LocalDate(2001, 8, 24), "17:03").toString(), + resultDateTime, + ) + } + + @Test + fun doubleGenericReturnType() = runTest { + val result = client.doubleGenericReturnType() + assertEquals(listOf(listOf("1", "2"), listOf("a", "b")), result) + } + + @Test + fun paramsSingle() = runTest { + val result = client.paramsSingle("test") + assertEquals(Unit, result) + } + + @Test + fun paramsDouble() = runTest { + val result = client.paramsDouble("test", "test2") + assertEquals(Unit, result) + } + + @Test + @Ignore + fun varargParams() = runTest { + val result = client.varargParams("test", "test2", "test3") + assertEquals(Unit, result) + } + + @Test + fun genericParams() = runTest { + val result = client.genericParams(listOf("test", "test2", "test3")) + assertEquals(Unit, result) + } + + @Test + fun doubleGenericParams() = runTest { + val result = client.doubleGenericParams(listOf(listOf("test", "test2", "test3"))) + assertEquals(Unit, result) + } + + @Test + fun mapParams() = runTest { + val result = client.mapParams(mapOf("key" to mapOf(1 to listOf("test", "test2", "test3")))) + assertEquals(Unit, result) + } + + @Test + fun customType() = runTest { + val result = client.customType(TestClass()) + assertEquals(TestClass(), result) + } + + @Test + open fun nullable() = runTest { + val result = client.nullable("test") + assertEquals(TestClass(), result) + + val result2 = client.nullable(null) + assertEquals(null, result2) + } + + @Test + fun variance() = runTest { + val result = client.variance(TestList(), TestList2()) + assertEquals(TestList(3), result) + } + + @Test + fun incomingStreamSyncCollect() = runTest { + val result = streamScoped { + client.incomingStreamSyncCollect(flowOf("test1", "test2", "test3")) + } + + assertEquals(3, result) + } + + @Test + fun incomingStreamAsyncCollect() = runTest { + val result = streamScoped { + client.incomingStreamAsyncCollect(flowOf("test1", "test2", "test3")).also { + server.incomingStreamAsyncCollectLatch.await() + } + } + + assertEquals(5, result) + } + + @Test + fun outgoingStream() = runTest { + streamScoped { + val result = client.outgoingStream() + assertEquals(listOf("a", "b", "c"), result.toList(mutableListOf())) + } + } + + @Test + fun bidirectionalStream() = runTest { + streamScoped { + val result = client.bidirectionalStream(flowOf("test1", "test2", "test3")) + assertEquals( + listOf("test1".reversed(), "test2".reversed(), "test3".reversed()), + result.toList(mutableListOf()), + ) + } + } + + @Test + fun streamInDataClass() = runTest { + streamScoped { + val result = client.streamInDataClass(payload()) + assertEquals(8, result) + } + } + + @Test + fun streamInStream() = runTest { + streamScoped { + val result = client.streamInStream(payloadStream()) + assertEquals(30, result) + } + } + + @Test + fun streamOutDataClass() = runTest { + streamScoped { + val result = client.streamOutDataClass() + assertEquals("test0", result.payload) + assertEquals(listOf("a0", "b0", "c0"), result.stream.toList(mutableListOf())) + } + } + + @Test + fun streamOfStreamsInReturn() = runTest { + streamScoped { + val result = client.streamOfStreamsInReturn().map { + it.toList(mutableListOf()) + }.toList(mutableListOf()) + assertEquals(listOf(listOf("a", "b", "c"), listOf("1", "2", "3")), result) + } + } + + @Test + fun streamOfPayloadsInReturn() = runTest { + streamScoped { + val result = client.streamOfPayloadsInReturn().map { + it.stream.toList(mutableListOf()).joinToString() + }.toList(mutableListOf()).joinToString() + assertEquals( + "a0, b0, c0, a1, b1, c1, a2, b2, c2, a3, b3, c3, a4, " + + "b4, c4, a5, b5, c5, a6, b6, c6, a7, b7, c7, a8, b8, c8, a9, b9, c9", + result, + ) + } + } + + @Test + fun streamInDataClassWithStream() = runTest { + streamScoped { + val result = client.streamInDataClassWithStream(payloadWithPayload()) + assertEquals(5, result) + } + } + + @Test + fun streamInStreamWithStream() = runTest { + val result = streamScoped { + client.streamInStreamWithStream(payloadWithPayloadStream()) + } + assertEquals(5, result) + } + + @Test + fun returnPayloadWithPayload() = runTest { + streamScoped { + assertContentEquals(expectedPayloadWithPayload(10), client.returnPayloadWithPayload().collect()) + } + } + + @Test + fun returnFlowPayloadWithPayload() = runTest { + streamScoped { + client.returnFlowPayloadWithPayload().collectIndexed { index, payloadWithPayload -> + assertContentEquals(expectedPayloadWithPayload(index), payloadWithPayload.collect()) + } + } + } + + @Test + fun bidirectionalFlowOfPayloadWithPayload() = runTest { + streamScoped { + val result = client.bidirectionalFlowOfPayloadWithPayload( + flow { + repeat(5) { + emit(payloadWithPayload(10)) + } + }, + ) + + val all = result.toList().onEach { + assertContentEquals(expectedPayloadWithPayload(10), it.collect()) + }.size + + assertEquals(5, all) + } + } + + @Test + fun bidirectionalAsyncStream() = runTest { + streamScoped { + val flow = MutableSharedFlow(1) + val result = client.echoStream(flow.take(10)) + launch { + var id = 0 + result.collect { + assertEquals(id, it) + id++ + flow.emit(id) + } + } + + flow.emit(0) + } + } + + @Test + fun `RPC should be able to receive 100_000 ints in reasonable time`() = runTest { + streamScoped { + val n = 100_000 + assertEquals(client.getNInts(n).last(), n) + } + } + + @Test + fun `RPC should be able to receive 100_000 ints with batching in reasonable time`() = runTest { + streamScoped { + val n = 100_000 + assertEquals(client.getNIntsBatched(n).last().last(), n) + } + } + + @Test + open fun testByteArraySerialization() = runTest { + client.bytes("hello".encodeToByteArray()) + client.nullableBytes(null) + client.nullableBytes("hello".encodeToByteArray()) + } + + @Test + @Suppress("detekt.TooGenericExceptionCaught", "detekt.ThrowsCount") + fun testExceptionSerializationAndPropagating() = runTest { + try { + client.throwsIllegalArgument("me") + fail("Exception expected: throwsIllegalArgument") + } catch (e : AssertionError) { + throw e + } catch (e: Throwable) { + assertEquals("me", e.message) + } + try { + client.throwsSerializableWithMessageAndCause("me") + fail("Exception expected: throwsSerializableWithMessageAndCause") + } catch (e : AssertionError) { + throw e + } catch (e: Throwable) { + assertEquals("me", e.message) + assertEquals("cause: me", e.cause?.message) + } + try { + client.throwsThrowable("me") + fail("Exception expected: throwsThrowable") + } catch (e : AssertionError) { + throw e + } catch (e: Throwable) { + assertEquals("me", e.message) + } + try { + client.throwsUNSTOPPABLEThrowable("me") + fail("Exception expected: throwsUNSTOPPABLEThrowable") + } catch (e : AssertionError) { + throw e + } catch (e: Throwable) { + assertEquals("me", e.message) + } + } + + @Test + open fun testNullables() = runTest { + assertEquals(1, client.nullableInt(1)) + assertNull(client.nullable(null)) + } + + @Test + open fun testNullableLists() = runTest { + assertNull(client.nullableList(null)) + + assertEquals(emptyList(), client.nullableList(emptyList())) + assertEquals(listOf(1), client.nullableList(listOf(1))) + } + + @Test + fun testServerCallCancellation() = runTest { + val flag: Channel = Channel() + val remote = launch { + try { + streamScoped { + client.delayForever().collect { + flag.send(it) + } + } + } catch (e: CancellationException) { + throw e + } + + fail("Shall not pass here") + }.apply { + invokeOnCompletion { cause: Throwable? -> + if (cause != null && cause !is CancellationException) { + throw cause + } + } + } + + flag.receive() + remote.cancelAndJoin() + } + + class AtomicTest { + val atomic = atomic(true) + } + + @Test + fun `rpc continuation is called in the correct scope and doesn't block other rpcs`() = runTest { + if (isJs) { + println("Test is skipped on JS, because it doesn't support multiple threads.") + return@runTest + } + + val inContinuation = Semaphore(1) + val running = AtomicTest() + + // start a coroutine that block the thread after continuation + val c1 = async(Dispatchers.Default) { // make a rpc call + client.answerToAnything("hello") + + // now we are in the continuation + // important for test: don't use suspending primitives to signal it, + // as we want to make sure we have a blocked thread, + // when the second coroutine is launched + inContinuation.release() + + // let's block the thread + while (running.atomic.value) { + // do nothing + } + } + + // wait, till the Rpc continuation thread is blocked + delay(100) + assertTrue(inContinuation.tryAcquire()) + + val c2 = async { // make a call + // and make sure the continuation is executed, + // even though another call's continuation has blocked its thread. + client.answerToAnything("hello") + running.atomic.compareAndSet(expect = true, update = false) + } + + awaitAll(c1, c2) + } + + @Test + fun testPlainFlowOfInts() = runTest { + val flow = client.plainFlowOfInts.toList() + assertEquals(List(5) { it }, flow) + } + + @Test + fun testPlainFlowOfFlowsOfInts() = runTest { + val lists = client.plainFlowOfFlowsOfInts.map { + it.toList() + }.toList() + + assertEquals(List(5) { List(5) { it } }, lists) + } + + @Test + fun testPlainFlowOfFlowsOfFlowsOfInts() = runTest { + val lists = client.plainFlowOfFlowsOfFlowsOfInts.map { + it.map { i -> i.toList() }.toList() + }.toList() + + assertEquals(List(5) { List(5) { List(5) { it } } }, lists) + } + + @Test + fun testSharedFlowOfInts() = runTest { + val list1 = client.sharedFlowOfInts.take(5).toList() + val list2 = client.sharedFlowOfInts.take(3).toList() + + assertEquals(List(5) { it }, list1) + assertEquals(List(3) { it }, list2) + } + + @Test + fun testSharedFlowOfFlowsOfInts() = runTest { + val list1 = client.sharedFlowOfFlowsOfInts.take(5).map { + it.take(5).toList() + }.toList() + + val list2 = client.sharedFlowOfFlowsOfInts.take(3).map { + it.take(3).toList() + }.toList() + + assertEquals(List(5) { List(5) { it } }, list1) + assertEquals(List(3) { List(3) { it } }, list2) + } + + @Test + fun testSharedFlowOfFlowsOfFlowsOfInts() = runTest { + val list1 = client.sharedFlowOfFlowsOfFlowsOfInts.take(5).map { + it.take(5).map { i -> i.take(5).toList() }.toList() + }.toList() + + val list2 = client.sharedFlowOfFlowsOfFlowsOfInts.take(3).map { + it.take(3).map { i -> i.take(3).toList() }.toList() + }.toList() + + assertEquals(List(5) { List(5) { List(5) { it } } }, list1) + assertEquals(List(3) { List(3) { List(3) { it } } }, list2) + } + + @Test + fun testStateFlowOfInts() = runTest { + val flow = client.awaitFieldInitialization { stateFlowOfInts } + + assertEquals(-1, flow.value) + + client.emitNextForStateFlowOfInts(42) + + assertEquals(42, flow.first { it == 42 }) + } + + @Test + fun testStateFlowOfFlowsOfInts() = runTest { + val flow1 = client.awaitFieldInitialization { stateFlowOfFlowsOfInts } + val flow2 = flow1.value + + assertEquals(-1, flow2.value) + + client.emitNextForStateFlowOfFlowsOfInts(42) + + assertEquals(42, flow2.first { it == 42 }) + assertEquals(42, flow1.first { it.value == 42 }.value) + } + + @Test + fun testStateFlowOfFlowsOfFlowsOfInts() = runTest { + val flow1 = client.awaitFieldInitialization { stateFlowOfFlowsOfFlowsOfInts } + val flow2 = flow1.value + val flow3 = flow2.value + + assertEquals(-1, flow3.value) + + client.emitNextForStateFlowOfFlowsOfFlowsOfInts(42) + + assertEquals(42, flow3.first { it == 42 }) + assertEquals(42, flow2.first { it.value == 42 }.value) + assertEquals(42, flow1.first { it.value.value == 42 }.value.value) + } + + @Test + fun testSharedFlowInFunction() = runTest { + streamScoped { + val flow = sharedFlowOfT { it } + + val state = client.sharedFlowInFunction(flow) + + assertEquals(1, state.first { it == 1 }) + } + } + + @Test + fun testStateFlowInFunction() = runTest { + streamScoped { + val flow = stateFlowOfT { it } + + val state = client.stateFlowInFunction(flow) + + flow.emit(42) + + assertEquals(1, state.first { it == 1 }) + } + } + + @Test + fun testAwaitAllFields() = runTest { + with(client.awaitFieldInitialization()) { + assertEquals(-1, stateFlowOfInts.value) + assertEquals(-1, stateFlowOfFlowsOfInts.value.value) + assertEquals(-1, stateFlowOfFlowsOfFlowsOfInts.value.value.value) + } + } + + companion object { + fun expectedPayloadWithPayload(size: Int) = List(size) { listOf("a$it", "b$it", "c$it") } + } +} + +internal expect val isJs: Boolean diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/Payloads.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/Payloads.kt similarity index 100% rename from krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/Payloads.kt rename to krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/Payloads.kt diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/TestClass.kt b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/TestClass.kt similarity index 90% rename from krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/TestClass.kt rename to krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/TestClass.kt index 44e171533..40bc98568 100644 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/TestClass.kt +++ b/krpc/krpc-test/src/commonMain/kotlin/kotlinx/rpc/krpc/test/TestClass.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/LocalTransport.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransport.kt similarity index 96% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/LocalTransport.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransport.kt index 4250b105b..d4cda5500 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/LocalTransport.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransport.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt similarity index 80% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt index 02e8c1cd3..b8d033106 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/LocalTransportTest.kt @@ -1,9 +1,11 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.runTest import kotlinx.rpc.krpc.KrpcTransport import kotlinx.rpc.krpc.serialization.KrpcSerialFormatConfiguration import kotlinx.rpc.krpc.serialization.cbor.cbor @@ -48,11 +50,11 @@ class ProtoBufLocalTransportTest : LocalTransportTest() { } // 'null' is not supported in ProtoBuf - override fun nullable() { } + override fun nullable(): TestResult = runTest { } - override fun testByteArraySerialization() { } + override fun testByteArraySerialization(): TestResult = runTest { } - override fun testNullables() { } + override fun testNullables(): TestResult = runTest { } - override fun testNullableLists() { } + override fun testNullableLists(): TestResult = runTest { } } diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTest.kt similarity index 94% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTest.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTest.kt index e78423121..974277e81 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTest.kt @@ -1,10 +1,9 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test -import junit.framework.TestCase.assertEquals import kotlinx.coroutines.launch import kotlinx.rpc.krpc.KrpcTransportMessage import kotlinx.rpc.krpc.internal.KrpcPlugin @@ -12,8 +11,9 @@ import kotlinx.rpc.krpc.rpcClientConfig import kotlinx.rpc.krpc.rpcServerConfig import kotlinx.rpc.krpc.serialization.protobuf.protobuf import kotlinx.serialization.ExperimentalSerializationApi -import org.junit.Test +import kotlin.test.Test import kotlin.test.assertContentEquals +import kotlin.test.assertEquals @Suppress("detekt.MaxLineLength") @OptIn(ExperimentalSerializationApi::class) @@ -72,7 +72,7 @@ class ProtocolTest : ProtocolTestBase() { // callId changes here are always compatible val serverResponseMessage = KrpcTransportMessage.StringMessage( - "{\"type\":\"org.jetbrains.krpc.RPCMessage.CallSuccess\",\"callId\":\"$connectionId:kotlinx.rpc.krpc.test.ProtocolTestService.`\$rpcServiceStub`.`sendRequest\$rpcMethod`:1\",\"serviceType\":\"kotlinx.rpc.krpc.test.ProtocolTestService\",\"data\":\"{}\"}" + "{\"type\":\"org.jetbrains.krpc.RPCMessage.CallSuccess\",\"callId\":\"$connectionId:sendRequest:1\",\"serviceType\":\"kotlinx.rpc.krpc.test.ProtocolTestService\",\"data\":\"{}\"}" ) transport.server.send(serverResponseMessage) diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTestBase.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTestBase.kt similarity index 97% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTestBase.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTestBase.kt index e84a37f44..f1388db8d 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTestBase.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/ProtocolTestBase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test @@ -27,7 +27,6 @@ import kotlinx.serialization.BinaryFormat import kotlin.coroutines.CoroutineContext abstract class ProtocolTestBase { - @Suppress("RedundantUnitReturnType") protected fun runTest( clientConfig: KrpcConfig.Client = rpcClientConfig { serialization { diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/SamplingService.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/SamplingService.kt similarity index 97% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/SamplingService.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/SamplingService.kt index 998e87975..54267498e 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/SamplingService.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/SamplingService.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ // NOTE: preserve package for compatibility tests diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt similarity index 76% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt index 3607bce66..63b7ebc58 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/TransportTest.kt @@ -1,22 +1,28 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test -import junit.framework.TestCase.assertEquals +import kotlinx.atomicfu.atomic import kotlinx.coroutines.* +import kotlinx.coroutines.test.TestResult +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import kotlinx.rpc.* import kotlinx.rpc.annotations.Rpc import kotlinx.rpc.krpc.KrpcConfigBuilder +import kotlinx.rpc.krpc.internal.logging.CommonLogger +import kotlinx.rpc.krpc.internal.logging.DumpLogger +import kotlinx.rpc.krpc.internal.logging.DumpLoggerContainer import kotlinx.rpc.krpc.rpcClientConfig import kotlinx.rpc.krpc.rpcServerConfig import kotlinx.rpc.krpc.serialization.json.json -import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.CoroutineContext import kotlin.test.Ignore import kotlin.test.Test -import kotlin.test.assertFailsWith +import kotlin.test.assertEquals +import kotlin.test.assertFails import kotlin.test.assertTrue @Rpc @@ -30,7 +36,7 @@ interface Second : RemoteService { } class EchoImpl(override val coroutineContext: CoroutineContext) : Echo { - val received = AtomicInteger() + val received = atomic(0) override suspend fun echo(message: String): String { received.incrementAndGet() @@ -39,7 +45,7 @@ class EchoImpl(override val coroutineContext: CoroutineContext) : Echo { } class SecondServer(override val coroutineContext: CoroutineContext) : Second { - val received = AtomicInteger() + val received = atomic(0) override suspend fun second(message: String): String { received.incrementAndGet() @@ -72,13 +78,29 @@ class TransportTest { return KrpcTestServer(serverConfig, localTransport.server) } + private fun runTest(block: suspend TestScope.() -> Unit): TestResult = kotlinx.coroutines.test.runTest { + val logger = CommonLogger.logger("TransportTest") + + DumpLoggerContainer.set(object : DumpLogger { + override val isEnabled: Boolean = true + + override fun dump(vararg tags: String, message: () -> String) { + logger.info { "${tags.joinToString(" ") { "[$it]" }} ${message()}" } + } + }) + + block() + + DumpLoggerContainer.set(null) + } + @Test - fun testUsingWrongService(): Unit = runBlocking { + fun testUsingWrongService() = runTest { val transports = LocalTransport() val client = clientOf(transports).withService() val result = async { - assertFailsWith { + assertFails { client.simpleWithParams("foo") } } @@ -97,7 +119,7 @@ class TransportTest { } @Test - fun testLateConnect() = runBlocking { + fun testLateConnect() = runTest { val transports = LocalTransport() val client = clientOf(transports).withService() @@ -110,13 +132,13 @@ class TransportTest { val echoServices = server.registerServiceAndReturn { EchoImpl(it) } assertEquals("foo", result.await()) - assertEquals(1, echoServices.single().received.get()) + assertEquals(1, echoServices.single().received.value) server.cancel() } @Test - fun testLateConnectWithManyCalls() = runBlocking { + fun testLateConnectWithManyCalls() = runTest { val transports = LocalTransport() val client = clientOf(transports).withService() @@ -131,13 +153,13 @@ class TransportTest { val response = result.awaitAll() assertTrue { response.all { it == "foo" } } - assertEquals(10, echoServices.single().received.get()) + assertEquals(10, echoServices.single().received.value) server.cancel() } @Test - fun testLateConnectWithManyServices() = runBlocking { + fun testLateConnectWithManyServices() = runTest { val transports = LocalTransport() val client = clientOf(transports) @@ -154,14 +176,14 @@ class TransportTest { val response = result.awaitAll() assertTrue { response.all { it == "foo" } } - assertEquals(10, echoServices.sumOf { it.received.get() }) + assertEquals(10, echoServices.sumOf { it.received.value }) server.cancel() } @Test - fun testLateConnectWithManyCallsAndClients() = runBlocking { + fun testLateConnectWithManyCallsAndClients() = runTest { val transports = LocalTransport() val client = clientOf(transports) @@ -182,13 +204,13 @@ class TransportTest { val response = result.awaitAll().flatten() assertTrue { response.all { it == "foo" } } - assertEquals(100, echoServices.sumOf { it.received.get() }) + assertEquals(100, echoServices.sumOf { it.received.value }) server.cancel() } @Test - fun testConnectMultipleServicesOnSingleClient(): Unit = runBlocking { + fun testConnectMultipleServicesOnSingleClient() = runTest { val transports = LocalTransport() val client = clientOf(transports) @@ -208,13 +230,13 @@ class TransportTest { delay(1000) val echoServices = server.registerServiceAndReturn { EchoImpl(it) } assertEquals("foo", firstResult.await()) - assertEquals(1, echoServices.single().received.get()) + assertEquals(1, echoServices.single().received.value) echoServices.single().cancel() delay(1000) val secondServices = server.registerServiceAndReturn { SecondServer(it) } assertEquals("bar", secondResult.await()) - assertEquals(1, secondServices.single().received.get()) + assertEquals(1, secondServices.single().received.value) secondServices.single().cancel() server.cancel() @@ -222,7 +244,7 @@ class TransportTest { @Test @Ignore - fun testCancelFromClientToServer() = runBlocking { + fun testCancelFromClientToServer() = runTest { val transports = LocalTransport() val client = clientOf(transports).withService() diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationService.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationService.kt similarity index 100% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationService.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationService.kt diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt similarity index 98% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt index ab393d5d9..3f690abeb 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest import kotlinx.rpc.krpc.StreamScope import kotlinx.rpc.krpc.internal.STREAM_SCOPES_ENABLED import kotlinx.rpc.krpc.invokeOnStreamScopeCompletion @@ -285,11 +286,9 @@ class CancellationTest { } @Test - fun testNestedStreamScopesForbidden() { - runBlocking { - assertFailsWith { - streamScoped { streamScoped { } } - } + fun testNestedStreamScopesForbidden() = runTest { + assertFailsWith { + streamScoped { streamScoped { } } } } @@ -682,8 +681,8 @@ class CancellationTest { } } } - firstDone.await() + requestJob.cancel("Cancelled by test") requestJob.join() serverInstance().nonSuspendableFinished.await() @@ -701,7 +700,9 @@ class CancellationTest { val requestJob = processFlowAndLeaveUnusedForGC(firstDone, latch) firstDone.await() - System.gc() // hint GC to collect the flow + // here, GC should collect the flow. + // so far, it works well locally and on TC, + // so, until it is flaky, we're good serverInstance().nonSuspendableFinished.await() assertEquals(false, serverInstance().nonSuspendableSecond) diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationToolkit.kt b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationToolkit.kt similarity index 93% rename from krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationToolkit.kt rename to krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationToolkit.kt index b16359a6c..430abff7e 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationToolkit.kt +++ b/krpc/krpc-test/src/commonTest/kotlin/kotlinx/rpc/krpc/test/cancellation/CancellationToolkit.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test.cancellation @@ -21,15 +21,14 @@ import kotlinx.rpc.krpc.test.LocalTransport import kotlinx.rpc.registerService import kotlinx.rpc.withService -@Suppress("RedundantUnitReturnType") fun runCancellationTest(body: suspend CancellationToolkit.() -> Unit): TestResult { - runTest { + return runTest { CancellationToolkit(this).apply { body() } } } class CancellationToolkit(scope: CoroutineScope) : CoroutineScope by scope { - private val logger = CommonLogger.logger("[CancellationTest]") + private val logger = CommonLogger.logger("CancellationTest") init { DumpLoggerContainer.set(object : DumpLogger { diff --git a/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.js.kt b/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.js.kt new file mode 100644 index 000000000..5a01eb69d --- /dev/null +++ b/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.js.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual inline fun runThreadIfPossible(runner: () -> Unit) { + runner() +} 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 new file mode 100644 index 000000000..118af4f57 --- /dev/null +++ b/krpc/krpc-test/src/jsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.js.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual val isJs: Boolean = true diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.jvm.kt b/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.jvm.kt new file mode 100644 index 000000000..9142c37fa --- /dev/null +++ b/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.jvm.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual fun runThreadIfPossible(runner: () -> Unit) { + Thread(runner).start() +} 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 new file mode 100644 index 000000000..ee108e9c0 --- /dev/null +++ b/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.jvm.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual val isJs: Boolean = false diff --git a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt b/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt deleted file mode 100644 index c1c51759b..000000000 --- a/krpc/krpc-test/src/jvmMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.kt +++ /dev/null @@ -1,745 +0,0 @@ -/* - * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("FunctionName") - -package kotlinx.rpc.krpc.test - -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.* -import kotlinx.rpc.awaitFieldInitialization -import kotlinx.rpc.krpc.KrpcTransport -import kotlinx.rpc.krpc.rpcClientConfig -import kotlinx.rpc.krpc.rpcServerConfig -import kotlinx.rpc.krpc.serialization.KrpcSerialFormatConfiguration -import kotlinx.rpc.krpc.server.KrpcServer -import kotlinx.rpc.krpc.streamScoped -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.modules.SerializersModule -import org.junit.Assert.assertEquals -import org.junit.Rule -import org.junit.rules.Timeout -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter -import java.util.concurrent.Semaphore -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.coroutines.cancellation.CancellationException -import kotlin.test.* - -internal object LocalDateSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING) - - override fun serialize( - encoder: Encoder, - value: LocalDate, - ) { - encoder.encodeString(value.format(DateTimeFormatter.ISO_DATE)) - } - - override fun deserialize(decoder: Decoder): LocalDate { - return LocalDate.parse(decoder.decodeString(), DateTimeFormatter.ISO_DATE) - } -} - -internal object LocalDateTimeSerializer : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING) - - override fun serialize( - encoder: Encoder, - value: LocalDateTime, - ) { - encoder.encodeString(value.format(DateTimeFormatter.ISO_DATE_TIME)) - } - - override fun deserialize(decoder: Decoder): LocalDateTime { - return LocalDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_DATE_TIME) - } -} - -abstract class KrpcTransportTestBase { - protected val serializersModule = SerializersModule { - contextual(LocalDate::class) { LocalDateSerializer } - } - - protected abstract val serializationConfig: KrpcSerialFormatConfiguration.() -> Unit - - private val serverConfig by lazy { - rpcServerConfig { - sharedFlowParameters { - replay = KrpcTestServiceBackend.SHARED_FLOW_REPLAY - } - - serialization { - serializationConfig() - } - } - } - - private val clientConfig by lazy { - rpcClientConfig { - sharedFlowParameters { - replay = KrpcTestServiceBackend.SHARED_FLOW_REPLAY - } - - serialization { - serializationConfig() - } - } - } - - abstract val clientTransport: KrpcTransport - abstract val serverTransport: KrpcTransport - - private lateinit var backend: KrpcServer - private lateinit var client: KrpcTestService - - @BeforeTest - fun start() { - backend = KrpcTestServer(serverConfig, serverTransport) - backend.registerService { KrpcTestServiceBackend(it) } - - client = KrpcTestClient(clientConfig, clientTransport).withService() - } - - @AfterTest - fun end() { - backend.cancel() - } - - @Rule - @JvmField - val globalTimeout: Timeout = Timeout.seconds(30) - - @Test - fun nonSuspend() { - runBlocking { - assertEquals(List(10) { it }, client.nonSuspendFlow().toList()) - } - } - - @Test - fun nonSuspendErrorOnEmit() { - runBlocking { - val flow = client.nonSuspendFlowErrorOnReturn() - assertFailsWith { - flow.toList() - } - } - } - - @Test - fun nonSuspendErrorOnReturn() { - runBlocking { - assertFailsWith { - client.nonSuspendFlowErrorOnReturn().toList() - } - } - } - - @Test - fun empty() { - backend.cancel() - client.cancel() - } - - @Test - fun returnType() { - runBlocking { - assertEquals("test", client.returnType()) - } - } - - @Test - fun simpleWithParams() { - runBlocking { - assertEquals("name".reversed(), client.simpleWithParams("name")) - } - } - - @Test - @Ignore // works on my machine issue – timeouts on TC - fun simpleWithParams100000() = runBlocking { - repeat(100000) { - assertEquals("name".reversed(), client.simpleWithParams("name")) - } - } - - @Test - fun genericReturnType() { - runBlocking { - assertEquals(listOf("hello", "world"), client.genericReturnType()) - } - } - - @Test - fun nonSerializableParameter() { - runBlocking { - val localDate = LocalDate.of(2001, 8, 23) - val resultDate = client.nonSerializableClass(localDate) - assertEquals(localDate.plusDays(1), resultDate) - - val localDateTime = LocalDateTime.of(2001, 8, 23, 0, 0) - val resultDateTime = client.nonSerializableClassWithSerializer(localDateTime) - assertEquals(localDateTime.plusDays(1).format(DateTimeFormatter.ISO_DATE_TIME), resultDateTime) - } - } - - @Test - fun doubleGenericReturnType() { - val result = runBlocking { client.doubleGenericReturnType() } - assertEquals(listOf(listOf("1", "2"), listOf("a", "b")), result) - } - - @Test - fun paramsSingle() { - val result = runBlocking { client.paramsSingle("test") } - assertEquals(Unit, result) - } - - @Test - fun paramsDouble() { - val result = runBlocking { client.paramsDouble("test", "test2") } - assertEquals(Unit, result) - } - - @Test - @Ignore - fun varargParams() { - val result = runBlocking { client.varargParams("test", "test2", "test3") } - assertEquals(Unit, result) - } - - @Test - fun genericParams() { - val result = runBlocking { client.genericParams(listOf("test", "test2", "test3")) } - assertEquals(Unit, result) - } - - @Test - fun doubleGenericParams() { - val result = runBlocking { client.doubleGenericParams(listOf(listOf("test", "test2", "test3"))) } - assertEquals(Unit, result) - } - - @Test - fun mapParams() { - val result = runBlocking { client.mapParams(mapOf("key" to mapOf(1 to listOf("test", "test2", "test3")))) } - assertEquals(Unit, result) - } - - @Test - fun customType() { - val result = runBlocking { client.customType(TestClass()) } - assertEquals(TestClass(), result) - } - - @Test - open fun nullable() { - val result = runBlocking { client.nullable("test") } - assertEquals(TestClass(), result) - - val result2 = runBlocking { client.nullable(null) } - assertEquals(null, result2) - } - - @Test - fun variance() { - val result = runBlocking { client.variance(TestList(), TestList2()) } - assertEquals(TestList(3), result) - } - - @Test - fun incomingStreamSyncCollect() { - val result = runBlocking { - streamScoped { - client.incomingStreamSyncCollect(flowOf("test1", "test2", "test3")) - } - } - assertEquals(3, result) - } - - @Test - fun incomingStreamAsyncCollect() { - val result = runBlocking { - streamScoped { - client.incomingStreamAsyncCollect(flowOf("test1", "test2", "test3")) - } - } - assertEquals(5, result) - } - - @Test - fun outgoingStream() { - runBlocking { - streamScoped { - val result = client.outgoingStream() - assertEquals(listOf("a", "b", "c"), result.toList(mutableListOf())) - } - } - } - - @Test - fun bidirectionalStream() { - runBlocking { - streamScoped { - val result = client.bidirectionalStream(flowOf("test1", "test2", "test3")) - assertEquals( - listOf("test1".reversed(), "test2".reversed(), "test3".reversed()), - result.toList(mutableListOf()), - ) - } - } - } - - @Test - fun streamInDataClass() { - runBlocking { - streamScoped { - val result = client.streamInDataClass(payload()) - assertEquals(8, result) - } - } - } - - @Test - fun streamInStream() { - runBlocking { - streamScoped { - val result = client.streamInStream(payloadStream()) - assertEquals(30, result) - } - } - } - - @Test - fun streamOutDataClass() { - runBlocking { - streamScoped { - val result = client.streamOutDataClass() - assertEquals("test0", result.payload) - assertEquals(listOf("a0", "b0", "c0"), result.stream.toList(mutableListOf())) - } - } - } - - @Test - fun streamOfStreamsInReturn() { - runBlocking { - streamScoped { - val result = client.streamOfStreamsInReturn().map { - it.toList(mutableListOf()) - }.toList(mutableListOf()) - assertEquals(listOf(listOf("a", "b", "c"), listOf("1", "2", "3")), result) - } - } - } - - @Test - fun streamOfPayloadsInReturn() { - runBlocking { - streamScoped { - val result = client.streamOfPayloadsInReturn().map { - it.stream.toList(mutableListOf()).joinToString() - }.toList(mutableListOf()).joinToString() - assertEquals( - "a0, b0, c0, a1, b1, c1, a2, b2, c2, a3, b3, c3, a4, " + - "b4, c4, a5, b5, c5, a6, b6, c6, a7, b7, c7, a8, b8, c8, a9, b9, c9", - result, - ) - } - } - } - - @Test - fun streamInDataClassWithStream() { - runBlocking { - streamScoped { - val result = client.streamInDataClassWithStream(payloadWithPayload()) - assertEquals(5, result) - } - } - } - - @Test - fun streamInStreamWithStream() { - runBlocking { - val result = streamScoped { - client.streamInStreamWithStream(payloadWithPayloadStream()) - } - assertEquals(5, result) - } - } - - @Test - fun returnPayloadWithPayload() { - runBlocking { - streamScoped { - assertContentEquals(expectedPayloadWithPayload(10), client.returnPayloadWithPayload().collect()) - } - } - } - - @Test - fun returnFlowPayloadWithPayload() { - runBlocking { - streamScoped { - client.returnFlowPayloadWithPayload().collectIndexed { index, payloadWithPayload -> - assertContentEquals(expectedPayloadWithPayload(index), payloadWithPayload.collect()) - } - } - } - } - - @Test - fun bidirectionalFlowOfPayloadWithPayload() { - runBlocking { - streamScoped { - val result = client.bidirectionalFlowOfPayloadWithPayload( - flow { - repeat(5) { - emit(payloadWithPayload(10)) - } - }, - ) - - val all = result.toList().onEach { - assertContentEquals(expectedPayloadWithPayload(10), it.collect()) - }.size - - assertEquals(5, all) - } - } - } - - @Test - fun bidirectionalAsyncStream() { - runBlocking { - streamScoped { - val flow = MutableSharedFlow(1) - val result = client.echoStream(flow.take(10)) - launch { - var id = 0 - result.collect { - assertEquals(id, it) - id++ - flow.emit(id) - } - } - - flow.emit(0) - } - } - } - - @Test - fun `RPC should be able to receive 100_000 ints in reasonable time`() { - runBlocking { - streamScoped { - val n = 100_000 - assertEquals(client.getNInts(n).last(), n) - } - } - } - - @Test - fun `RPC should be able to receive 100_000 ints with batching in reasonable time`() { - runBlocking { - streamScoped { - val n = 100_000 - assertEquals(client.getNIntsBatched(n).last().last(), n) - } - } - } - - @Test - open fun testByteArraySerialization() { - runBlocking { - client.bytes("hello".toByteArray()) - client.nullableBytes(null) - client.nullableBytes("hello".toByteArray()) - } - } - - @Test - @Suppress("detekt.TooGenericExceptionCaught") - fun testExceptionSerializationAndPropagating() { - runBlocking { - try { - client.throwsIllegalArgument("me") - } catch (e: IllegalArgumentException) { - assertEquals("me", e.message) - } - try { - client.throwsSerializableWithMessageAndCause("me") - } catch (e: KrpcTestServiceBackend.SerializableTestException) { - assertEquals("me", e.message) - assertEquals("cause: me", e.cause?.message) - } - try { - client.throwsThrowable("me") - } catch (e: Throwable) { - assertEquals("me", e.message) - } - try { - client.throwsUNSTOPPABLEThrowable("me") - } catch (e: Throwable) { - assertEquals("me", e.message) - } - } - } - - @Test - open fun testNullables() { - runBlocking { - assertEquals(1, client.nullableInt(1)) - assertNull(client.nullable(null)) - } - } - - @Test - open fun testNullableLists() { - runBlocking { - assertNull(client.nullableList(null)) - - assertEquals(emptyList(), client.nullableList(emptyList())) - assertEquals(listOf(1), client.nullableList(listOf(1))) - } - } - - @Test - fun testServerCallCancellation() { - runBlocking { - val flag: Channel = Channel() - val remote = launch { - try { - streamScoped { - client.delayForever().collect { - flag.send(it) - } - } - } catch (e: CancellationException) { - throw e - } - - fail("Shall not pass here") - }.apply { - invokeOnCompletion { cause: Throwable? -> - if (cause != null && cause !is CancellationException) { - throw cause - } - } - } - - flag.receive() - remote.cancelAndJoin() - } - } - - @Test - fun `rpc continuation is called in the correct scope and doesn't block other rpcs`() { - runBlocking { - val inContinuation = Semaphore(1) - val running = AtomicBoolean(true) - - // start a coroutine that block the thread after continuation - val c1 = async(Dispatchers.IO) { // make a rpc call - client.answerToAnything("hello") - - // now we are in the continuation - // important for test: don't use suspending primitives to signal it, - // as we want to make sure we have a blocked thread, - // when the second coroutine is launched - inContinuation.release() - - // let's block the thread - while (running.get()) { - // do nothing - } - } - - // wait, till the Rpc continuation thread is blocked - assertTrue(inContinuation.tryAcquire(100, TimeUnit.MILLISECONDS)) - - val c2 = async { // make a call - // and make sure the continuation is executed, - // even though another call's continuation has blocked its thread. - client.answerToAnything("hello") - running.set(false) - } - - awaitAll(c1, c2) - } - } - - @Test - fun testPlainFlowOfInts() { - runBlocking { - val flow = client.plainFlowOfInts.toList() - assertEquals(List(5) { it }, flow) - } - } - - @Test - fun testPlainFlowOfFlowsOfInts() { - runBlocking { - val lists = client.plainFlowOfFlowsOfInts.map { - it.toList() - }.toList() - - assertEquals(List(5) { List(5) { it } }, lists) - } - } - - @Test - fun testPlainFlowOfFlowsOfFlowsOfInts() { - runBlocking { - val lists = client.plainFlowOfFlowsOfFlowsOfInts.map { - it.map { i -> i.toList() }.toList() - }.toList() - - assertEquals(List(5) { List(5) { List(5) { it } } }, lists) - } - } - - @Test - fun testSharedFlowOfInts() { - runBlocking { - val list1 = client.sharedFlowOfInts.take(5).toList() - val list2 = client.sharedFlowOfInts.take(3).toList() - - assertEquals(List(5) { it }, list1) - assertEquals(List(3) { it }, list2) - } - } - - @Test - fun testSharedFlowOfFlowsOfInts() { - runBlocking { - val list1 = client.sharedFlowOfFlowsOfInts.take(5).map { - it.take(5).toList() - }.toList() - - val list2 = client.sharedFlowOfFlowsOfInts.take(3).map { - it.take(3).toList() - }.toList() - - assertEquals(List(5) { List(5) { it } }, list1) - assertEquals(List(3) { List(3) { it } }, list2) - } - } - - @Test - fun testSharedFlowOfFlowsOfFlowsOfInts() { - runBlocking { - val list1 = client.sharedFlowOfFlowsOfFlowsOfInts.take(5).map { - it.take(5).map { i -> i.take(5).toList() }.toList() - }.toList() - - val list2 = client.sharedFlowOfFlowsOfFlowsOfInts.take(3).map { - it.take(3).map { i -> i.take(3).toList() }.toList() - }.toList() - - assertEquals(List(5) { List(5) { List(5) { it } } }, list1) - assertEquals(List(3) { List(3) { List(3) { it } } }, list2) - } - } - - @Test - fun testStateFlowOfInts() { - runBlocking { - val flow = client.awaitFieldInitialization { stateFlowOfInts } - - assertEquals(-1, flow.value) - - client.emitNextForStateFlowOfInts(42) - - assertEquals(42, flow.first { it == 42 }) - } - } - - @Test - fun testStateFlowOfFlowsOfInts() { - runBlocking { - val flow1 = client.awaitFieldInitialization { stateFlowOfFlowsOfInts } - val flow2 = flow1.value - - assertEquals(-1, flow2.value) - - client.emitNextForStateFlowOfFlowsOfInts(42) - - assertEquals(42, flow2.first { it == 42 }) - assertEquals(42, flow1.first { it.value == 42 }.value) - } - } - - @Test - fun testStateFlowOfFlowsOfFlowsOfInts() { - runBlocking { - val flow1 = client.awaitFieldInitialization { stateFlowOfFlowsOfFlowsOfInts } - val flow2 = flow1.value - val flow3 = flow2.value - - assertEquals(-1, flow3.value) - - client.emitNextForStateFlowOfFlowsOfFlowsOfInts(42) - - assertEquals(42, flow3.first { it == 42 }) - assertEquals(42, flow2.first { it.value == 42 }.value) - assertEquals(42, flow1.first { it.value.value == 42 }.value.value) - } - } - - @Test - fun testSharedFlowInFunction() { - runBlocking { - streamScoped { - val flow = sharedFlowOfT { it } - - val state = client.sharedFlowInFunction(flow) - - assertEquals(1, state.first { it == 1 }) - } - } - } - - @Test - fun testStateFlowInFunction() { - runBlocking { - streamScoped { - val flow = stateFlowOfT { it } - - val state = client.stateFlowInFunction(flow) - - flow.emit(42) - - assertEquals(1, state.first { it == 1 }) - } - } - } - - @Test - fun testAwaitAllFields() { - runBlocking { - with(client.awaitFieldInitialization()) { - assertEquals(-1, stateFlowOfInts.value) - assertEquals(-1, stateFlowOfFlowsOfInts.value.value) - assertEquals(-1, stateFlowOfFlowsOfFlowsOfInts.value.value.value) - } - } - } - - companion object { - fun expectedPayloadWithPayload(size: Int) = List(size) { listOf("a$it", "b$it", "c$it") } - } -} diff --git a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/api/WireSamplingTestScope.kt b/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/api/WireSamplingTestScope.kt index 6196dc68c..46f9b0578 100644 --- a/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/api/WireSamplingTestScope.kt +++ b/krpc/krpc-test/src/jvmTest/kotlin/kotlinx/rpc/krpc/test/api/WireSamplingTestScope.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.rpc.krpc.test.api @@ -282,7 +282,7 @@ data class DumpLog( fun fromText(lines: List): List { return lines .map { it.trim() } - .filter { !it.startsWith("//") } + .filter { !it.startsWith("//") && it.isNotBlank() } .map { line -> val (prefix, log) = line.split("\$", limit = 2).map { it.trim() } val (role, phase) = prefix.split(" ") diff --git a/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/callException_json.gold b/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/callException_json.gold index 138b31556..8a3ceff5d 100644 --- a/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/callException_json.gold +++ b/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/callException_json.gold @@ -2,9 +2,9 @@ [Server] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake","supportedPlugins":[-32767,-32766,-32765]} [Server] [Send] $ {"type":"org.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake","supportedPlugins":[-32767,-32766,-32765],"connectionId":1} [Client] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake","supportedPlugins":[-32767,-32766,-32765],"connectionId":1} -[Client] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`callException$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"callException","callType":"Method","data":"{}","connectionId":1,"serviceId":1} -[Server] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`callException$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"callException","callType":"Method","data":"{}","connectionId":1,"serviceId":1} -[Server] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallException","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`callException$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":null,"className":"java.lang.IllegalStateException"},"className":"java.lang.IllegalStateException"},"connectionId":1,"serviceId":1} -[Server] [Send] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`callException$rpcMethod`:1"}} -[Client] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallException","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`callException$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":null,"className":"java.lang.IllegalStateException"},"className":"java.lang.IllegalStateException"},"connectionId":1,"serviceId":1} -[Client] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`callException$rpcMethod`:1"}} \ No newline at end of file +[Client] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:callException:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"callException","callType":"Method","data":"{}","connectionId":1,"serviceId":1} +[Server] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:callException:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"callException","callType":"Method","data":"{}","connectionId":1,"serviceId":1} +[Server] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallException","callId":"1:callException:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":null,"className":"java.lang.IllegalStateException"},"className":"java.lang.IllegalStateException"},"connectionId":1,"serviceId":1} +[Server] [Send] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:callException:1"}} +[Client] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallException","callId":"1:callException:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":{"toStringMessage":"java.lang.IllegalStateException: Server exception","message":"Server exception","stacktrace":[],"cause":null,"className":"java.lang.IllegalStateException"},"className":"java.lang.IllegalStateException"},"connectionId":1,"serviceId":1} +[Client] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:callException:1"}} diff --git a/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_json.gold b/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_json.gold index 0746c8b30..37c083ac4 100644 --- a/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_json.gold +++ b/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_json.gold @@ -2,9 +2,9 @@ [Server] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake","supportedPlugins":[-32767,-32766,-32765]} [Server] [Send] $ {"type":"org.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake","supportedPlugins":[-32767,-32766,-32765],"connectionId":1} [Client] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake","supportedPlugins":[-32767,-32766,-32765],"connectionId":1} -[Client] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"echo","callType":"Method","data":"{\"arg1\":\"Hello\",\"data\":{\"data\":\"data\"}}","connectionId":1,"serviceId":1} -[Server] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"echo","callType":"Method","data":"{\"arg1\":\"Hello\",\"data\":{\"data\":\"data\"}}","connectionId":1,"serviceId":1} -[Server] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallSuccess","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","data":"{\"data\":\"data\"}","connectionId":1,"serviceId":1} -[Server] [Send] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1"}} -[Client] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallSuccess","callId":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","data":"{\"data\":\"data\"}","connectionId":1,"serviceId":1} -[Client] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1"}} \ No newline at end of file +[Client] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:echo:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"echo","callType":"Method","data":"{\"arg1\":\"Hello\",\"data\":{\"data\":\"data\"}}","connectionId":1,"serviceId":1} +[Server] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallData","callId":"1:echo:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","method":"echo","callType":"Method","data":"{\"arg1\":\"Hello\",\"data\":{\"data\":\"data\"}}","connectionId":1,"serviceId":1} +[Server] [Send] $ {"type":"org.jetbrains.krpc.RPCMessage.CallSuccess","callId":"1:echo:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","data":"{\"data\":\"data\"}","connectionId":1,"serviceId":1} +[Server] [Send] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:echo:1"}} +[Client] [Receive] $ {"type":"org.jetbrains.krpc.RPCMessage.CallSuccess","callId":"1:echo:1","serviceType":"org.jetbrains.krpc.test.api.util.SamplingService","data":"{\"data\":\"data\"}","connectionId":1,"serviceId":1} +[Client] [Receive] $ {"type":"org.jetbrains.krpc.internal.transport.RPCGenericMessage","connectionId":null,"pluginParams":{"-32767":"cancellation","-32766":"CANCELLATION_ACK","-32765":"1:echo:1"}} diff --git a/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_protobuf.gold b/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_protobuf.gold index b043a1ae5..d52bc3ab5 100644 --- a/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_protobuf.gold +++ b/krpc/krpc-test/src/jvmTest/resources/wire_dumps/0_6_0/echo_protobuf.gold @@ -6,13 +6,15 @@ [Server] [Send] $ 0a426f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e52504350726f746f636f6c4d6573736167652e48616e647368616b651223088180feffffffffffff01088280feffffffffffff01088380feffffffffffff011001 // decoded: ?Borg.jetbrains.krpc.internal.transport.RPCProtocolMessage.Handshake?#??????????????????????????????????? [Client] [Receive] $ 0a426f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e52504350726f746f636f6c4d6573736167652e48616e647368616b651223088180feffffffffffff01088280feffffffffffff01088380feffffffffffff011001 -// decoded: ??org.jetbrains.krpc.internal.transport.RPCMessage.CallDataBinary????W1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1?0org.jetbrains.krpc.test.api.util.SamplingService??echo ?*???Hello????data0?8? -[Client] [Send] $ 0a3f6f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c4461746142696e61727912a8010a57313a6f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963652e60247270635365727669636553747562602e606563686f247270634d6574686f64603a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a046563686f20002a0f0a0548656c6c6f12060a046461746130013801 -// decoded: ??org.jetbrains.krpc.internal.transport.RPCMessage.CallDataBinary????W1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1?0org.jetbrains.krpc.test.api.util.SamplingService??echo ?*???Hello????data0?8? -[Server] [Receive] $ 0a3f6f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c4461746142696e61727912a8010a57313a6f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963652e60247270635365727669636553747562602e606563686f247270634d6574686f64603a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a046563686f20002a0f0a0548656c6c6f12060a046461746130013801 -// decoded: ?Borg.jetbrains.krpc.internal.transport.RPCMessage.CallSuccessBinary????W1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1?0org.jetbrains.krpc.test.api.util.SamplingService????data ?(? -[Server] [Send] $ 0a426f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c5375636365737342696e6172791297010a57313a6f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963652e60247270635365727669636553747562602e606563686f247270634d6574686f64603a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a060a046461746120012801 -// decoded: ?Borg.jetbrains.krpc.internal.transport.RPCMessage.CallSuccessBinary????W1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1?0org.jetbrains.krpc.test.api.util.SamplingService????data ?(? -[Client] [Receive] $ 0a426f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c5375636365737342696e6172791297010a57313a6f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963652e60247270635365727669636553747562602e606563686f247270634d6574686f64603a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a060a046461746120012801 -// decoded: ?7org.jetbrains.krpc.internal.transport.RPCGenericMessage??????????????????cancellation???????????????CANCELLATION_ACK?d????????????W1:org.jetbrains.krpc.test.api.util.SamplingService.`$rpcServiceStub`.`echo$rpcMethod`:1 -[Server] [Send] $ 0a376f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e52504347656e657269634d65737361676512a0011219088180feffffffffffff01120c63616e63656c6c6174696f6e121d088280feffffffffffff01121043414e43454c4c4154494f4e5f41434b1264088380feffffffffffff011257313a6f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963652e60247270635365727669636553747562602e606563686f247270634d6574686f64603a31 \ No newline at end of file +// decoded: ??org.jetbrains.krpc.internal.transport.RPCMessage.CallDataBinary?Y??1:echo:1?0org.jetbrains.krpc.test.api.util.SamplingService??echo ?*???Hello????data0?8? +[Client] [Send] $ 0a3f6f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c4461746142696e61727912590a08313a6563686f3a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a046563686f20002a0f0a0548656c6c6f12060a046461746130013801 +// decoded: ??org.jetbrains.krpc.internal.transport.RPCMessage.CallDataBinary?Y??1:echo:1?0org.jetbrains.krpc.test.api.util.SamplingService??echo ?*???Hello????data0?8? +[Server] [Receive] $ 0a3f6f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c4461746142696e61727912590a08313a6563686f3a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a046563686f20002a0f0a0548656c6c6f12060a046461746130013801 +// decoded: ?Borg.jetbrains.krpc.internal.transport.RPCMessage.CallSuccessBinary?H??1:echo:1?0org.jetbrains.krpc.test.api.util.SamplingService????data ?(? +[Server] [Send] $ 0a426f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c5375636365737342696e61727912480a08313a6563686f3a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a060a046461746120012801 +// decoded: ?7org.jetbrains.krpc.internal.transport.RPCGenericMessage?Q???????????????cancellation???????????????CANCELLATION_ACK???????????????1:echo:1 +[Server] [Send] $ 0a376f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e52504347656e657269634d65737361676512511219088180feffffffffffff01120c63616e63656c6c6174696f6e121d088280feffffffffffff01121043414e43454c4c4154494f4e5f41434b1215088380feffffffffffff011208313a6563686f3a31 +// decoded: ?Borg.jetbrains.krpc.internal.transport.RPCMessage.CallSuccessBinary?H??1:echo:1?0org.jetbrains.krpc.test.api.util.SamplingService????data ?(? +[Client] [Receive] $ 0a426f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e5250434d6573736167652e43616c6c5375636365737342696e61727912480a08313a6563686f3a3112306f72672e6a6574627261696e732e6b7270632e746573742e6170692e7574696c2e53616d706c696e67536572766963651a060a046461746120012801 +// decoded: ?7org.jetbrains.krpc.internal.transport.RPCGenericMessage?Q???????????????cancellation???????????????CANCELLATION_ACK???????????????1:echo:1 +[Client] [Receive] $ 0a376f72672e6a6574627261696e732e6b7270632e696e7465726e616c2e7472616e73706f72742e52504347656e657269634d65737361676512511219088180feffffffffffff01120c63616e63656c6c6174696f6e121d088280feffffffffffff01121043414e43454c4c4154494f4e5f41434b1215088380feffffffffffff011208313a6563686f3a31 diff --git a/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.native.kt b/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.native.kt new file mode 100644 index 000000000..72c450a8e --- /dev/null +++ b/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.native.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +import kotlin.native.concurrent.ObsoleteWorkersApi +import kotlin.native.concurrent.Worker + +@OptIn(ObsoleteWorkersApi::class) +actual fun runThreadIfPossible(runner: () -> Unit) { + Worker.start(errorReporting = true).executeAfter(0L, runner) +} 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 new file mode 100644 index 000000000..ee108e9c0 --- /dev/null +++ b/krpc/krpc-test/src/nativeMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.native.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual val isJs: Boolean = false diff --git a/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.wasmJs.kt b/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.wasmJs.kt new file mode 100644 index 000000000..5a01eb69d --- /dev/null +++ b/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTestServiceBackend.wasmJs.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual inline fun runThreadIfPossible(runner: () -> Unit) { + runner() +} 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 new file mode 100644 index 000000000..118af4f57 --- /dev/null +++ b/krpc/krpc-test/src/wasmJsMain/kotlin/kotlinx/rpc/krpc/test/KrpcTransportTestBase.wasmJs.kt @@ -0,0 +1,7 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.rpc.krpc.test + +actual val isJs: Boolean = true