Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion krpc/krpc-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* 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
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ includePublic(":krpc:krpc-ktor:krpc-ktor-server")
includePublic(":krpc:krpc-ktor:krpc-ktor-client")

include(":tests")
include(":tests:krpc-compatibility-tests")

val kotlinMasterBuild = providers.gradleProperty("kotlinx.rpc.kotlinMasterBuild").orNull == "true"

Expand Down
80 changes: 80 additions & 0 deletions tests/krpc-compatibility-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode
import util.applyAtomicfuPlugin

plugins {
alias(libs.plugins.conventions.jvm)
alias(libs.plugins.serialization)
alias(libs.plugins.kotlinx.rpc)
}

applyAtomicfuPlugin()

val main: SourceSet by sourceSets.getting
val test: SourceSet by sourceSets.getting

val oldApi: SourceSet by sourceSets.creating {
compileClasspath += main.output
compileClasspath += test.compileClasspath
runtimeClasspath += main.output
runtimeClasspath += test.runtimeClasspath
}

val newApi: SourceSet by sourceSets.creating {
compileClasspath += main.output
compileClasspath += test.compileClasspath
runtimeClasspath += main.output
runtimeClasspath += test.runtimeClasspath
}

val compatibilityTestSourcesDir: File = project.layout.buildDirectory.dir("compatibilityTestSources").get().asFile

val copyOldToTestResources by tasks.register<Copy>("copyOldToTestResources") {
dependsOn(oldApi.output)
from(oldApi.output)
into(compatibilityTestSourcesDir.resolve("old"))
}

val copyNewToTestResources by tasks.register<Copy>("copyNewToTestResources") {
dependsOn(newApi.output)
from(newApi.output)
into(compatibilityTestSourcesDir.resolve("new"))
}

test.resources {
srcDir(compatibilityTestSourcesDir)
}

tasks.processTestResources.configure {
dependsOn(copyOldToTestResources, copyNewToTestResources)
}

dependencies {
api(libs.atomicfu)

api(projects.krpc.krpcCore)
api(projects.krpc.krpcServer)
api(projects.krpc.krpcClient)

implementation(projects.krpc.krpcSerialization.krpcSerializationJson)

implementation(libs.serialization.core)
implementation(libs.coroutines.test)
implementation(libs.kotlin.test.junit5)
implementation(libs.kotlin.reflect)

testImplementation(libs.slf4j.api)
testImplementation(libs.logback.classic)
testImplementation(libs.coroutines.debug)
}

kotlin {
explicitApi = ExplicitApiMode.Disabled
}

tasks.test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.compatibility

import kotlinx.rpc.RpcClient

interface CompatibilityTest {
fun getAllTests(): Map<String, suspend (RpcClient) -> Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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.compatibility

import kotlinx.rpc.RpcServer

interface TestApiServer {
fun serveAllInterfaces(rpcServer: RpcServer)
}
21 changes: 21 additions & 0 deletions tests/krpc-compatibility-tests/src/newApi/kotlin/interfaces/Bar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package interfaces

import kotlinx.rpc.RemoteService
import kotlinx.rpc.annotations.Rpc
import kotlin.coroutines.CoroutineContext

@Rpc
interface BarInterface : RemoteService {
suspend fun get(): Unit
suspend fun get2(): Unit
}

class BarInterfaceImpl(override val coroutineContext: CoroutineContext) : BarInterface {
override suspend fun get() {}

override suspend fun get2() {}
}
22 changes: 22 additions & 0 deletions tests/krpc-compatibility-tests/src/newApi/kotlin/interfaces/Baz.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package interfaces

import kotlinx.rpc.RemoteService
import kotlinx.rpc.annotations.Rpc
import kotlinx.serialization.Serializable
import kotlin.coroutines.CoroutineContext

@Serializable
data class Baz(val field: String, val field2: String = "")

@Rpc
interface BazInterface : RemoteService {
suspend fun get(): Baz
}

class BazInterfaceImpl(override val coroutineContext: CoroutineContext) : BazInterface {
override suspend fun get(): Baz = Baz("asd", "def")
}
24 changes: 24 additions & 0 deletions tests/krpc-compatibility-tests/src/newApi/kotlin/interfaces/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package interfaces

import kotlinx.rpc.RemoteService
import kotlinx.rpc.annotations.Rpc
import kotlinx.serialization.Serializable
import kotlin.coroutines.CoroutineContext

@Serializable
data class Foo(val field: String, val field2: String? = null)

@Rpc
interface FooInterface : RemoteService {
suspend fun get(): Foo
}

class FooInterfaceImpl(override val coroutineContext: CoroutineContext) : FooInterface {
override suspend fun get(): Foo {
return Foo("", "")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package tests

import kotlinx.rpc.krpc.compatibility.TestApiServer
import interfaces.BarInterface
import interfaces.BarInterfaceImpl
import interfaces.BazInterface
import interfaces.BazInterfaceImpl
import interfaces.FooInterface
import interfaces.FooInterfaceImpl
import kotlinx.rpc.RpcServer
import kotlinx.rpc.registerService

@Suppress("unused")
class ApiServer : TestApiServer {
override fun serveAllInterfaces(rpcServer: RpcServer) {
rpcServer.apply {
registerService<FooInterface> { FooInterfaceImpl(it) }
registerService<BarInterface> { BarInterfaceImpl(it) }
registerService<BazInterface> { BazInterfaceImpl(it) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 tests

import kotlinx.rpc.krpc.compatibility.CompatibilityTest
import interfaces.BarInterface
import interfaces.BazInterface
import interfaces.FooInterface
import kotlinx.rpc.RpcClient
import kotlinx.rpc.withService
import kotlin.reflect.KCallable
import kotlin.reflect.full.callSuspend
import kotlin.test.assertEquals

@Suppress("unused")
class CompatibilityTests : CompatibilityTest {
override fun getAllTests(): Map<String, suspend (RpcClient) -> Unit> {
return mapOf(
this::`should work with older data class without nullable field`.toEntry(),
this::`should work with older interface without method`.toEntry(),
this::`should work with older data class without a field with default value`.toEntry(),
)
}

suspend fun `should work with older data class without nullable field`(rpcClient: RpcClient) {
val service = rpcClient.withService<FooInterface>()
val res = service.get()
assertEquals("", res.field)
assertEquals(null, res.field2)
}

suspend fun `should work with older interface without method`(rpcClient: RpcClient) {
val service = rpcClient.withService<BarInterface>()
service.get()
// Of course, we can't call the second method
}

suspend fun `should work with older data class without a field with default value`(rpcClient: RpcClient) {
val service = rpcClient.withService<BazInterface>()
val res = service.get()
assertEquals("asd", res.field)
assertEquals("", res.field2)
// Of course, we can't call the second method
}

private fun KCallable<Unit>.toEntry(): Pair<String, suspend (RpcClient) -> Unit> {
return name to { callSuspend(it) }
}
}
18 changes: 18 additions & 0 deletions tests/krpc-compatibility-tests/src/oldApi/kotlin/interfaces/Bar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package interfaces

import kotlinx.rpc.RemoteService
import kotlinx.rpc.annotations.Rpc
import kotlin.coroutines.CoroutineContext

@Rpc
interface BarInterface : RemoteService {
suspend fun get()
}

class BarInterfaceImpl(override val coroutineContext: CoroutineContext) : BarInterface {
override suspend fun get() {}
}
22 changes: 22 additions & 0 deletions tests/krpc-compatibility-tests/src/oldApi/kotlin/interfaces/Baz.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package interfaces

import kotlinx.rpc.RemoteService
import kotlinx.rpc.annotations.Rpc
import kotlinx.serialization.Serializable
import kotlin.coroutines.CoroutineContext

@Serializable
data class Baz(val field: String)

@Rpc
interface BazInterface : RemoteService {
suspend fun get(): Baz
}

class BazInterfaceImpl(override val coroutineContext: CoroutineContext) : BazInterface {
override suspend fun get(): Baz = Baz("asd")
}
24 changes: 24 additions & 0 deletions tests/krpc-compatibility-tests/src/oldApi/kotlin/interfaces/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package interfaces

import kotlinx.rpc.RemoteService
import kotlinx.rpc.annotations.Rpc
import kotlinx.serialization.Serializable
import kotlin.coroutines.CoroutineContext

@Serializable
data class Foo(val field: String)

@Rpc
interface FooInterface : RemoteService {
suspend fun get(): Foo
}

class FooInterfaceImpl(override val coroutineContext: CoroutineContext) : FooInterface {
override suspend fun get(): Foo {
return Foo("")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

package tests

import kotlinx.rpc.krpc.compatibility.TestApiServer
import interfaces.BarInterface
import interfaces.BarInterfaceImpl
import interfaces.BazInterface
import interfaces.BazInterfaceImpl
import interfaces.FooInterface
import interfaces.FooInterfaceImpl
import kotlinx.rpc.RpcServer
import kotlinx.rpc.registerService

@Suppress("unused")
class ApiServer : TestApiServer {
override fun serveAllInterfaces(rpcServer: RpcServer) {
rpcServer.apply {
registerService<FooInterface> { FooInterfaceImpl(it) }
registerService<BarInterface> { BarInterfaceImpl(it) }
registerService<BazInterface> { BazInterfaceImpl(it) }
}
}
}
Loading