diff --git a/.gitignore b/.gitignore index 0003f1bfb..2943b9dc3 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ detekt/reports/** local.properties scan-journal.log +# krpc compat tests +tests/krpc-protocol-compatibility-tests/v* +gradle-conventions/src/main/kotlin/util/krpc_compat/versions.kt diff --git a/gradle-conventions-settings/settings.gradle.kts b/gradle-conventions-settings/settings.gradle.kts index 02117557b..c9dab5899 100644 --- a/gradle-conventions-settings/settings.gradle.kts +++ b/gradle-conventions-settings/settings.gradle.kts @@ -12,6 +12,7 @@ rootProject.name = "gradle-conventions-settings" // Code below is a hack because a chicken-egg problem, I can't use myself as a settings-plugin apply(from = "src/main/kotlin/conventions-repositories.settings.gradle.kts") apply(from = "src/main/kotlin/conventions-version-resolution.settings.gradle.kts") +apply(from = "src/main/kotlin/krpc-compat-tests.settings.gradle.kts") include(":develocity") diff --git a/gradle-conventions-settings/src/main/kotlin/conventions-version-resolution.settings.gradle.kts b/gradle-conventions-settings/src/main/kotlin/conventions-version-resolution.settings.gradle.kts index 596b91d9a..568c3d9f5 100644 --- a/gradle-conventions-settings/src/main/kotlin/conventions-version-resolution.settings.gradle.kts +++ b/gradle-conventions-settings/src/main/kotlin/conventions-version-resolution.settings.gradle.kts @@ -139,7 +139,7 @@ fun VersionCatalogBuilder.resolveKotlinVersion(versionCatalog: Map) { +fun resolveLibraryVersion(versionCatalog: Map): String { val libraryCoreVersion: String = System.getenv(SettingsConventions.LIBRARY_VERSION_ENV_VAR_NAME) ?.takeIf { it.isNotBlank() } ?: versionCatalog[SettingsConventions.LIBRARY_CORE_VERSION_ALIAS] @@ -158,7 +158,7 @@ fun VersionCatalogBuilder.resolveLibraryVersion(versionCatalog: Map libraryCoreVersion.substringBefore('-') + eapVersion } - version(SettingsConventions.LIBRARY_CORE_VERSION_ALIAS, resultingVersion) + return resultingVersion } fun String.kotlinVersionParsed(): KotlinVersion { @@ -166,19 +166,23 @@ fun String.kotlinVersionParsed(): KotlinVersion { return KotlinVersion(major, minor, patch) } +val currentPath: Path = file(".").toPath().toAbsolutePath() +val rootDir = findGlobalRootDirPath(currentPath) +val versionCatalog = resolveVersionCatalog(rootDir) +val libVersion = resolveLibraryVersion(versionCatalog) + +extra["libVersion"] = libVersion + dependencyResolutionManagement { versionCatalogs { create("libs") { - val currentPath = file(".").toPath().toAbsolutePath() - val rootDir = findGlobalRootDirPath(currentPath) - from(files("$rootDir/${SettingsConventions.LIBS_VERSION_CATALOG_PATH}")) val versionCatalog = resolveVersionCatalog(rootDir) val (kotlinVersion, compilerVersion) = resolveKotlinVersion(versionCatalog) - resolveLibraryVersion(versionCatalog) + version(SettingsConventions.LIBRARY_CORE_VERSION_ALIAS, libVersion) val kotlinVersionParsed = kotlinVersion.kotlinVersionParsed() diff --git a/gradle-conventions-settings/src/main/kotlin/krpc-compat-tests.settings.gradle.kts b/gradle-conventions-settings/src/main/kotlin/krpc-compat-tests.settings.gradle.kts new file mode 100644 index 000000000..35acb6dd9 --- /dev/null +++ b/gradle-conventions-settings/src/main/kotlin/krpc-compat-tests.settings.gradle.kts @@ -0,0 +1,85 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +import kotlin.io.path.createDirectories +import kotlin.io.path.writeText + +// ADD NEW VERSIONS HERE +val versionDirs = mapOf( + "v0_8" to CompatVersion("0.8.1", "2.2.0"), + "v0_9" to CompatVersion("0.9.1", "2.2.0"), + "v0_10" to CompatVersion("0.10.0", "2.2.20"), +) + +// DON'T MODIFY BELOW THIS LINE + +val globalRootDirValue: String = extra["globalRootDir"] as? String + ?: error("globalRootDir property is not set") + +val globalRootDir: java.nio.file.Path = java.nio.file.Path.of(globalRootDirValue) + +val compatDir: java.nio.file.Path = globalRootDir + .resolve("tests") + .resolve("krpc-protocol-compatibility-tests") + +val libVersion = settings.extra["libVersion"] as? String + ?: error("libVersion property is not set") + +class CompatVersion( + val rpc: String, + val kotlin: String, +) + +versionDirs.forEach { (dir, version) -> + val moduleDir = compatDir.resolve(dir) + moduleDir.createDirectories() + val buildFile = moduleDir.resolve("build.gradle.kts") + buildFile.writeText( + """ + /* THIS FILE IS AUTO-GENERATED, DO NOT EDIT! */ + + import util.krpc_compat.setupCompat + + setupCompat("${version.rpc}", "${version.kotlin}") + + """.trimIndent() + ) + val propertiesFile = moduleDir.resolve("gradle.properties") + propertiesFile.writeText( + """ + /* THIS FILE IS AUTO-GENERATED, DO NOT EDIT! */ + + kotlin.compiler.runViaBuildToolsApi=true + + """.trimIndent() + ) + + logger.lifecycle("Generating :tests:krpc-protocol-compatibility-tests:$dir") + include(":tests:krpc-protocol-compatibility-tests:$dir") +} + +val versionsConventionsFile: java.nio.file.Path = globalRootDir + .resolve("gradle-conventions") + .resolve("src") + .resolve("main") + .resolve("kotlin") + .resolve("util") + .resolve("krpc_compat") + .resolve("versions.kt") + +logger.lifecycle("Generating krpc_compat/versions.kt") +versionsConventionsFile.writeText( + """ + |/* THIS FILE IS AUTO-GENERATED, DO NOT EDIT! */ + | + |package util.krpc_compat + | + |val krpcCompatVersions = mapOf( + | ${versionDirs.entries.joinToString("\n| ") { "\"${it.key}\" to \"${it.value.rpc}\"," }} + | + | "Latest" to "$libVersion", // current version + |) + | + """.trimMargin() +) diff --git a/gradle-conventions/src/main/kotlin/compiler-specific-module.gradle.kts b/gradle-conventions/src/main/kotlin/compiler-specific-module.gradle.kts index 89a58d716..373eac3e4 100644 --- a/gradle-conventions/src/main/kotlin/compiler-specific-module.gradle.kts +++ b/gradle-conventions/src/main/kotlin/compiler-specific-module.gradle.kts @@ -24,6 +24,7 @@ val processCsmTemplates = tasks.register( "processCsmTemplates", libs.versions.kotlin.compiler.get(), + emptyMap(), templatesDir, sourcesDir, ) diff --git a/gradle-conventions/src/main/kotlin/util/csm/task.kt b/gradle-conventions/src/main/kotlin/util/csm/task.kt index 881d50c81..b91a303b3 100644 --- a/gradle-conventions/src/main/kotlin/util/csm/task.kt +++ b/gradle-conventions/src/main/kotlin/util/csm/task.kt @@ -21,6 +21,7 @@ import kotlin.io.path.writeLines abstract class ProcessCsmTemplate @Inject constructor( @get:Input val kotlinComplierVersion: String, + @get:Input val replacementMap: Map, @get:InputDirectory val templatesDir: Provider, @get:OutputDirectory val sourcesDir: Provider, ) : DefaultTask() { @@ -43,6 +44,7 @@ abstract class ProcessCsmTemplate @Inject constructor( val lines = CsmTemplateProcessor.process( lines = file.readLines(Charsets.UTF_8), kotlinCompilerVersion = kotlinComplierVersion, + replacementMap = replacementMap, logger = logger, ) out.parent.toFile().mkdirs() diff --git a/gradle-conventions/src/main/kotlin/util/csm/template.kt b/gradle-conventions/src/main/kotlin/util/csm/template.kt index cbab27b58..55626cf79 100644 --- a/gradle-conventions/src/main/kotlin/util/csm/template.kt +++ b/gradle-conventions/src/main/kotlin/util/csm/template.kt @@ -8,7 +8,7 @@ import org.gradle.api.GradleException import org.slf4j.Logger object CsmTemplateProcessor { - fun process(lines: List, kotlinCompilerVersion: String, logger: Logger): List { + fun process(lines: List, kotlinCompilerVersion: String, logger: Logger, replacementMap: Map): List { val result = mutableListOf() val tags: ArrayDeque = ArrayDeque() @@ -21,7 +21,7 @@ object CsmTemplateProcessor { val defaultBuffer = mutableListOf() val specificBuffer = mutableListOf() - lines.forEachIndexed { i, line -> + lines.map { it.applyReplacements(replacementMap) }.forEachIndexed { i, line -> val trimmed = line.trim() when { trimmed.startsWith("//##csm") -> { @@ -179,3 +179,11 @@ object CsmTemplateProcessor { Skip, Apply; } } + +private fun String.applyReplacements(replacementMap: Map): String { + var result = this + replacementMap.forEach { (from, to) -> + result = result.replace(from, to) + } + return result +} diff --git a/gradle-conventions/src/main/kotlin/util/krpc_compat/setup.kt b/gradle-conventions/src/main/kotlin/util/krpc_compat/setup.kt new file mode 100644 index 000000000..ed25d5663 --- /dev/null +++ b/gradle-conventions/src/main/kotlin/util/krpc_compat/setup.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package util.krpc_compat + +import org.gradle.api.Project +import org.gradle.jvm.tasks.Jar +import org.gradle.kotlin.dsl.named +import org.jetbrains.kotlin.buildtools.api.ExperimentalBuildToolsApi +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME +import util.withKotlinJvmExtension + +private fun kcp(suffix: String, version: String): String { + return "org.jetbrains.kotlinx:kotlinx-rpc-compiler-plugin-$suffix:$version" +} + +fun Project.setupCompat(rpcVersion: String, kotlinVersion: String) { + plugins.apply("org.jetbrains.kotlin.jvm") + + dependencies.apply { + add("compileOnly", project(":tests:krpc-protocol-compatibility-tests:test-api")) + + add("implementation", "org.jetbrains.kotlinx:kotlinx-rpc-krpc-client:$rpcVersion") + add("implementation", "org.jetbrains.kotlinx:kotlinx-rpc-krpc-server:$rpcVersion") + add("implementation", "org.jetbrains.kotlinx:kotlinx-rpc-krpc-serialization-json:$rpcVersion") + + val kcpVersion = "$kotlinVersion-$rpcVersion" + add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, kcp("common", kcpVersion)) + add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, kcp("cli", kcpVersion)) + add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, kcp("backend", kcpVersion)) + add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, kcp("k2", kcpVersion)) + } + + withKotlinJvmExtension { + @OptIn(ExperimentalBuildToolsApi::class, ExperimentalKotlinGradlePluginApi::class) + compilerVersion.set(kotlinVersion) + + sourceSets.named("main") { + kotlin.srcDirs(layout.buildDirectory.dir("generated-sources").map { it.asFile.resolve("csm") }) + } + } + + tasks.named("jar") { + archiveVersion.set(rpcVersion) + } + + tasks.named("compileKotlin").configure { + dependsOn(":tests:krpc-protocol-compatibility-tests:process_template_${project.name}") + } +} diff --git a/gradle-conventions/src/main/kotlin/util/other/generateSource.kt b/gradle-conventions/src/main/kotlin/util/other/generateSource.kt new file mode 100644 index 000000000..0a4df6b91 --- /dev/null +++ b/gradle-conventions/src/main/kotlin/util/other/generateSource.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +package util.other + +import org.gradle.api.DefaultTask +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.NamedDomainObjectProvider +import org.gradle.api.Project +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.register +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import util.withKotlinJvmExtension +import util.withKotlinKmpExtension +import java.io.File +import javax.inject.Inject + +abstract class GenerateSourceTask @Inject constructor( + @get:Input val filename: String, + @get:Input val text: String, + @get:OutputDirectory val sourcesDir: File, +) : DefaultTask() { + @TaskAction + fun generate() { + val sourceFile = File(sourcesDir, "${filename}.kt") + sourceFile.writeText(text) + } +} + +fun Project.generateSource( + name: String, + text: String, + chooseSourceSet: NamedDomainObjectContainer.() -> NamedDomainObjectProvider, +) { + val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/kotlin") + + val generatePluginVersionTask = + tasks.register("generateSources_$name", name, text, sourcesDir) + + withKotlinJvmExtension { + chooseSourceSet(sourceSets).configure { + kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir }) + } + } + + withKotlinKmpExtension { + chooseSourceSet(sourceSets).configure { + kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir }) + } + } +} diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index bf9345d31..3bd86a4f5 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -3,6 +3,7 @@ */ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import util.other.generateSource /* * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. @@ -55,36 +56,15 @@ gradlePlugin { } } -abstract class GeneratePluginVersionTask @Inject constructor( - @get:Input val pluginVersion: String, - @get:OutputDirectory val sourcesDir: File -) : DefaultTask() { - @TaskAction - fun generate() { - val sourceFile = File(sourcesDir, "Versions.kt") - - sourceFile.writeText( - """ - package kotlinx.rpc - - public const val PLUGIN_VERSION: String = "$pluginVersion" - - """.trimIndent() - ) - } -} - -val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/pluginVersion") - -val generatePluginVersionTask = - tasks.register("generatePluginVersion", version.toString(), sourcesDir) - -kotlin { - sourceSets { - main { - kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir }) - } - } -} +generateSource( + name = "Versions", + text = """ + package kotlinx.rpc + + public const val PLUGIN_VERSION: String = "$version" + + """.trimIndent(), + chooseSourceSet = { main } +) logger.lifecycle("[Gradle Plugin] kotlinx.rpc project version: $version") diff --git a/settings.gradle.kts b/settings.gradle.kts index 57b9ef8b3..0fe194dae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -55,6 +55,7 @@ include(":tests") include(":tests:test-utils") include(":tests:krpc-compatibility-tests") include(":tests:krpc-protocol-compatibility-tests") +include(":tests:krpc-protocol-compatibility-tests:test-api") val kotlinMasterBuild = providers.gradleProperty("kotlinx.rpc.kotlinMasterBuild").orNull == "true" diff --git a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java index fb2806788..1f6623139 100644 --- a/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java +++ b/tests/compiler-plugin-tests/src/test-gen/kotlinx/rpc/codegen/test/runners/BoxTestGenerated.java @@ -39,11 +39,11 @@ public void testMultiModule() { runTest("src/testData/box/multiModule.kt"); } - @Test - @TestMetadata("serviceDescriptor.kt") - public void testServiceDescriptor() { - runTest("src/testData/box/serviceDescriptor.kt"); - } + @Test + @TestMetadata("serviceDescriptor.kt") + public void testServiceDescriptor() { + runTest("src/testData/box/serviceDescriptor.kt"); + } @Test @TestMetadata("simple.kt") diff --git a/tests/krpc-protocol-compatibility-tests/build.gradle.kts b/tests/krpc-protocol-compatibility-tests/build.gradle.kts index 5b72247b3..45c230d0b 100644 --- a/tests/krpc-protocol-compatibility-tests/build.gradle.kts +++ b/tests/krpc-protocol-compatibility-tests/build.gradle.kts @@ -5,6 +5,10 @@ @file:Suppress("PropertyName") import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import util.csm.ProcessCsmTemplate +import util.krpc_compat.krpcCompatVersions +import util.other.generateSource +import util.other.libs plugins { alias(libs.plugins.conventions.jvm) @@ -13,65 +17,9 @@ plugins { alias(libs.plugins.atomicfu) } -val main: SourceSet by sourceSets.getting -val test: SourceSet by sourceSets.getting - -val compatibilityTestSourcesDir: File = project.layout.buildDirectory.dir("compatibilityTestSources").get().asFile - -fun versioned(name: String): Configuration { - val configuration = configurations.create(name) { - isCanBeConsumed = true - isCanBeResolved = true - isTransitive = true - } - - val sourceSet = sourceSets.create(name) { - compileClasspath += main.output - runtimeClasspath += main.output - - compileClasspath += configuration - runtimeClasspath += configuration - } - - val copySourceSetTestResources by tasks.register("copy_${name}_ToTestResources") { - dependsOn(sourceSet.output) - from(sourceSet.output) - into(compatibilityTestSourcesDir.resolve(name)) - } - - tasks.processTestResources.configure { - dependsOn(copySourceSetTestResources) - } - - return configuration -} - -val v0_9 = versioned("v0_9") -val v0_8 = versioned("v0_8") - -test.resources { - srcDir(compatibilityTestSourcesDir) -} - -fun DependencyHandlerScope.versioned(configuration: Configuration, version: String) { - add(configuration.name, "org.jetbrains.kotlinx:kotlinx-rpc-krpc-client:$version") - add(configuration.name, "org.jetbrains.kotlinx:kotlinx-rpc-krpc-server:$version") - add(configuration.name, "org.jetbrains.kotlinx:kotlinx-rpc-krpc-serialization-json:$version") - add(configuration.name, libs.atomicfu) - add(configuration.name, projects.tests.testUtils) -} - dependencies { - api(libs.atomicfu) - api(projects.tests.testUtils) - implementation(libs.serialization.core) - implementation(libs.coroutines.core) - implementation(libs.kotlin.reflect) + testImplementation(projects.tests.krpcProtocolCompatibilityTests.testApi) - versioned(v0_9, "0.9.1") - versioned(v0_8, "0.8.1") - - // current version is in test source set testImplementation(projects.krpc.krpcCore) testImplementation(projects.krpc.krpcServer) testImplementation(projects.krpc.krpcClient) @@ -93,3 +41,58 @@ kotlin { tasks.test { useJUnitPlatform() } + +val templates: java.nio.file.Path = project.layout.projectDirectory.dir("templates").asFile.toPath() + +krpcCompatVersions.forEach { (dir, version) -> + val templateTask = tasks.register( + "process_template_$dir", + version, + mapOf("" to dir), + project.provider { templates }, + project.provider { + val root = project.layout.projectDirectory.let { + if (dir != "Latest") it.dir(dir) else it + } + + root.dir("build") + .dir("generated-sources") + .dir("csm") + .asFile.toPath() + }, + ) + + tasks.named("processResources").configure { + dependsOn(templateTask) + } +} + +kotlin { + sourceSets.test { + kotlin.srcDirs(layout.buildDirectory.dir("generated-sources").map { it.asFile.resolve("csm") }) + } +} + +generateSource( + name = "versions", + text = """ + |package kotlinx.rpc.krpc.test.compat + | + |@Suppress("EnumEntryName", "detekt.EnumNaming") + |enum class Versions { + | ${krpcCompatVersions.keys.joinToString("\n| ") { "$it," }} + | ; + |} + | + """.trimMargin(), + chooseSourceSet = { test }, +) + +tasks.test { + krpcCompatVersions.keys.filter { it != "Latest" }.forEach { dir -> + val jarTask = project(":tests:krpc-protocol-compatibility-tests:$dir").tasks.named("jar") + dependsOn(jarTask) + + environment["JAR_PATH_$dir"] = jarTask.get().outputs.files.singleFile.absolutePath + } +} diff --git a/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/KrpcProtocolCompatibilityTestsBase.kt b/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/KrpcProtocolCompatibilityTestsBase.kt index a705a5259..1e0b5972f 100644 --- a/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/KrpcProtocolCompatibilityTestsBase.kt +++ b/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/KrpcProtocolCompatibilityTestsBase.kt @@ -10,24 +10,18 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.withContext -import kotlinx.rpc.krpc.test.compat.service.TestStarter +import kotlinx.rpc.krpc.test.compat.service.TestStarter_Latest +import kotlinx.rpc.test.WaitCounter import kotlinx.rpc.test.runTestWithCoroutinesProbes import org.junit.jupiter.api.DynamicTest import org.slf4j.LoggerFactory import java.net.URLClassLoader import java.util.stream.Stream +import kotlin.io.path.Path import kotlin.test.assertTrue import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -@Suppress("EnumEntryName", "detekt.EnumNaming") -enum class Versions { - v0_9, - v0_8, - Latest - ; -} - enum class Role { Server, Client; } @@ -52,7 +46,7 @@ abstract class KrpcProtocolCompatibilityTestsBase { class LoadedStarterImpl(override val version: Versions, val classLoader: URLClassLoader) : LoadedStarter { override val starter = classLoader - .loadClass("kotlinx.rpc.krpc.test.compat.service.TestStarter") + .loadClass("kotlinx.rpc.krpc.test.compat.service.TestStarter_${version.name}") .getDeclaredConstructor() .newInstance() as Starter @@ -67,8 +61,12 @@ abstract class KrpcProtocolCompatibilityTestsBase { private fun prepareStarters(exclude: List): List { return Versions.entries.filter { it !in exclude && it != Versions.Latest }.map { version -> - val versionResourcePath = javaClass.classLoader.getResource(version.name)!! - val versionClassLoader = URLClassLoader(arrayOf(versionResourcePath), javaClass.classLoader) + val jarPathValue = System.getenv("JAR_PATH_${version.name}") + ?: error("JAR_PATH_${version.name} environment variable is not set") + + val jarPath = Path(jarPathValue).toUri().toURL() + + val versionClassLoader = URLClassLoader(arrayOf(jarPath), javaClass.classLoader) LoadedStarterImpl(version, versionClassLoader) } + latestStarter() @@ -76,7 +74,7 @@ abstract class KrpcProtocolCompatibilityTestsBase { private fun latestStarter() = object : LoadedStarter { override val version: Versions = Versions.Latest - override val starter: Starter = TestStarter() + override val starter: Starter = TestStarter_Latest() override suspend fun close() { starter.stopClient() @@ -131,7 +129,7 @@ abstract class KrpcProtocolCompatibilityTestsBase { body: suspend TestEnv.(CompatService, CompatServiceImpl) -> Unit, ) = runTest(Role.Client, exclude, timeout) { val transport = testTransport() - val config = TestConfig(perCallBufferSize) + val config = TestConfig(perCallBufferSize, ::CompatWaitCounterImpl) val service = old.startClient(transport.client, config) val impl = new.startServer(transport.server, config) body(service, impl) @@ -144,7 +142,7 @@ abstract class KrpcProtocolCompatibilityTestsBase { body: suspend TestEnv.(CompatService, CompatServiceImpl) -> Unit, ) = runTest(Role.Server, exclude, timeout) { val transport = testTransport() - val config = TestConfig(perCallBufferSize) + val config = TestConfig(perCallBufferSize, ::CompatWaitCounterImpl) val service = new.startClient(transport.client, config) val impl = old.startServer(transport.server, config) body(service, impl) @@ -164,3 +162,18 @@ abstract class KrpcProtocolCompatibilityTestsBase { ) } } + +class CompatWaitCounterImpl : CompatWaitCounter { + private val counter = WaitCounter() + + override val value: Int + get() = counter.value + + override fun increment() { + counter.increment() + } + + override suspend fun await(value: Int) { + counter.await(value) + } +} diff --git a/tests/krpc-protocol-compatibility-tests/src/v0_8/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt b/tests/krpc-protocol-compatibility-tests/src/v0_8/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt deleted file mode 100644 index 4bd533560..000000000 --- a/tests/krpc-protocol-compatibility-tests/src/v0_8/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.compat.service - -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.toList -import kotlinx.rpc.annotations.Rpc -import kotlinx.rpc.krpc.test.compat.CompatServiceImpl -import kotlinx.rpc.test.WaitCounter -import kotlin.coroutines.cancellation.CancellationException - -@Rpc -interface TestService { - suspend fun unary(n: Int): Int - fun serverStreaming(num: Int): Flow - suspend fun clientStreaming(n: Flow): Int - fun bidiStreaming(flow: Flow): Flow - - suspend fun requestCancellation() - fun serverStreamCancellation(): Flow - suspend fun clientStreamCancellation(n: Flow) - - fun fastServerProduce(n: Int): Flow -} - -class TestServiceImpl : TestService, CompatServiceImpl { - override suspend fun unary(n: Int): Int { - return n - } - - override fun serverStreaming(num: Int): Flow { - return (1..num).asFlow() - } - - override suspend fun clientStreaming(n: Flow): Int { - return n.toList().sum() - } - - override fun bidiStreaming(flow: Flow): Flow { - return flow - } - - override val exitMethod: WaitCounter = WaitCounter() - override val cancelled: WaitCounter = WaitCounter() - - override val entered: CompletableDeferred = CompletableDeferred() - override val fence: CompletableDeferred = CompletableDeferred() - - override suspend fun requestCancellation() { - try { - entered.complete(Unit) - fence.await() - exitMethod.increment() - } catch (e: CancellationException) { - cancelled.increment() - throw e - } - } - - override fun serverStreamCancellation(): Flow { - return flow { - try { - emit(1) - entered.complete(Unit) - fence.await() - emit(2) - } catch (e: CancellationException) { - cancelled.increment() - throw e - } - } - } - - override suspend fun clientStreamCancellation(n: Flow) { - try { - n.collect { - if (it != 0) { - entered.complete(Unit) - } - } - } catch (e: CancellationException) { - cancelled.increment() - throw e - } - } - - override fun fastServerProduce(n: Int): Flow { - return flow { - repeat(n) { - emit(it) - } - } - } -} diff --git a/tests/krpc-protocol-compatibility-tests/src/v0_8/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt b/tests/krpc-protocol-compatibility-tests/src/v0_8/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt deleted file mode 100644 index c18878a74..000000000 --- a/tests/krpc-protocol-compatibility-tests/src/v0_8/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.compat.service - -import kotlinx.coroutines.flow.Flow -import kotlinx.rpc.krpc.KrpcTransport -import kotlinx.rpc.krpc.KrpcTransportMessage -import kotlinx.rpc.krpc.client.InitializedKrpcClient -import kotlinx.rpc.krpc.client.KrpcClient -import kotlinx.rpc.krpc.rpcClientConfig -import kotlinx.rpc.krpc.rpcServerConfig -import kotlinx.rpc.krpc.serialization.json.json -import kotlinx.rpc.krpc.server.KrpcServer -import kotlinx.rpc.krpc.test.compat.CompatService -import kotlinx.rpc.krpc.test.compat.CompatServiceImpl -import kotlinx.rpc.krpc.test.compat.CompatTransport -import kotlinx.rpc.krpc.test.compat.Starter -import kotlinx.rpc.krpc.test.compat.TestConfig -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.coroutines.CoroutineContext - -fun CompatTransport.toKrpc(): KrpcTransport { - return object : KrpcTransport { - override suspend fun send(message: KrpcTransportMessage) { - this@toKrpc.send((message as KrpcTransportMessage.StringMessage).value) - } - - override suspend fun receive(): KrpcTransportMessage { - return KrpcTransportMessage.StringMessage(this@toKrpc.receive()) - } - - override val coroutineContext: CoroutineContext = this@toKrpc.coroutineContext - } -} - -@Suppress("unused") -class TestStarter : Starter { - private var client: KrpcClient? = null - private var server: KrpcServer? = null - - override suspend fun startClient(transport: CompatTransport, config: TestConfig): CompatService { - val transport = transport.toKrpc() - val clientConfig = rpcClientConfig { - serialization { - json() - } - } - - client = object : InitializedKrpcClient(clientConfig, transport) {} - val service = client!!.withService() - return object : CompatService { - override suspend fun unary(n: Int): Int { - return service.unary(n) - } - - override fun serverStreaming(num: Int): Flow { - return service.serverStreaming(num) - } - - override suspend fun clientStreaming(n: Flow): Int { - return service.clientStreaming(n) - } - - override fun bidiStreaming(flow: Flow): Flow { - return service.bidiStreaming(flow) - } - - override suspend fun requestCancellation() { - return service.requestCancellation() - } - - override fun serverStreamCancellation(): Flow { - return service.serverStreamCancellation() - } - - override suspend fun clientStreamCancellation(n: Flow) { - return service.clientStreamCancellation(n) - } - - override fun fastServerProduce(n: Int): Flow { - return service.fastServerProduce(n) - } - } - } - - override suspend fun stopClient() { - client?.close() - client?.awaitCompletion() - } - - override suspend fun startServer(transport: CompatTransport, config: TestConfig): CompatServiceImpl { - val transport = transport.toKrpc() - val serverConfig = rpcServerConfig { - serialization { - json() - } - } - - server = object : KrpcServer(serverConfig, transport) {} - val impl = TestServiceImpl() - server?.registerService { impl } - return impl - } - - override suspend fun stopServer() { - server?.close() - server?.awaitCompletion() - } -} diff --git a/tests/krpc-protocol-compatibility-tests/src/v0_9/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt b/tests/krpc-protocol-compatibility-tests/src/v0_9/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt deleted file mode 100644 index 4bd533560..000000000 --- a/tests/krpc-protocol-compatibility-tests/src/v0_9/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.compat.service - -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.toList -import kotlinx.rpc.annotations.Rpc -import kotlinx.rpc.krpc.test.compat.CompatServiceImpl -import kotlinx.rpc.test.WaitCounter -import kotlin.coroutines.cancellation.CancellationException - -@Rpc -interface TestService { - suspend fun unary(n: Int): Int - fun serverStreaming(num: Int): Flow - suspend fun clientStreaming(n: Flow): Int - fun bidiStreaming(flow: Flow): Flow - - suspend fun requestCancellation() - fun serverStreamCancellation(): Flow - suspend fun clientStreamCancellation(n: Flow) - - fun fastServerProduce(n: Int): Flow -} - -class TestServiceImpl : TestService, CompatServiceImpl { - override suspend fun unary(n: Int): Int { - return n - } - - override fun serverStreaming(num: Int): Flow { - return (1..num).asFlow() - } - - override suspend fun clientStreaming(n: Flow): Int { - return n.toList().sum() - } - - override fun bidiStreaming(flow: Flow): Flow { - return flow - } - - override val exitMethod: WaitCounter = WaitCounter() - override val cancelled: WaitCounter = WaitCounter() - - override val entered: CompletableDeferred = CompletableDeferred() - override val fence: CompletableDeferred = CompletableDeferred() - - override suspend fun requestCancellation() { - try { - entered.complete(Unit) - fence.await() - exitMethod.increment() - } catch (e: CancellationException) { - cancelled.increment() - throw e - } - } - - override fun serverStreamCancellation(): Flow { - return flow { - try { - emit(1) - entered.complete(Unit) - fence.await() - emit(2) - } catch (e: CancellationException) { - cancelled.increment() - throw e - } - } - } - - override suspend fun clientStreamCancellation(n: Flow) { - try { - n.collect { - if (it != 0) { - entered.complete(Unit) - } - } - } catch (e: CancellationException) { - cancelled.increment() - throw e - } - } - - override fun fastServerProduce(n: Int): Flow { - return flow { - repeat(n) { - emit(it) - } - } - } -} diff --git a/tests/krpc-protocol-compatibility-tests/src/v0_9/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt b/tests/krpc-protocol-compatibility-tests/src/v0_9/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt deleted file mode 100644 index c18878a74..000000000 --- a/tests/krpc-protocol-compatibility-tests/src/v0_9/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * 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.compat.service - -import kotlinx.coroutines.flow.Flow -import kotlinx.rpc.krpc.KrpcTransport -import kotlinx.rpc.krpc.KrpcTransportMessage -import kotlinx.rpc.krpc.client.InitializedKrpcClient -import kotlinx.rpc.krpc.client.KrpcClient -import kotlinx.rpc.krpc.rpcClientConfig -import kotlinx.rpc.krpc.rpcServerConfig -import kotlinx.rpc.krpc.serialization.json.json -import kotlinx.rpc.krpc.server.KrpcServer -import kotlinx.rpc.krpc.test.compat.CompatService -import kotlinx.rpc.krpc.test.compat.CompatServiceImpl -import kotlinx.rpc.krpc.test.compat.CompatTransport -import kotlinx.rpc.krpc.test.compat.Starter -import kotlinx.rpc.krpc.test.compat.TestConfig -import kotlinx.rpc.registerService -import kotlinx.rpc.withService -import kotlin.coroutines.CoroutineContext - -fun CompatTransport.toKrpc(): KrpcTransport { - return object : KrpcTransport { - override suspend fun send(message: KrpcTransportMessage) { - this@toKrpc.send((message as KrpcTransportMessage.StringMessage).value) - } - - override suspend fun receive(): KrpcTransportMessage { - return KrpcTransportMessage.StringMessage(this@toKrpc.receive()) - } - - override val coroutineContext: CoroutineContext = this@toKrpc.coroutineContext - } -} - -@Suppress("unused") -class TestStarter : Starter { - private var client: KrpcClient? = null - private var server: KrpcServer? = null - - override suspend fun startClient(transport: CompatTransport, config: TestConfig): CompatService { - val transport = transport.toKrpc() - val clientConfig = rpcClientConfig { - serialization { - json() - } - } - - client = object : InitializedKrpcClient(clientConfig, transport) {} - val service = client!!.withService() - return object : CompatService { - override suspend fun unary(n: Int): Int { - return service.unary(n) - } - - override fun serverStreaming(num: Int): Flow { - return service.serverStreaming(num) - } - - override suspend fun clientStreaming(n: Flow): Int { - return service.clientStreaming(n) - } - - override fun bidiStreaming(flow: Flow): Flow { - return service.bidiStreaming(flow) - } - - override suspend fun requestCancellation() { - return service.requestCancellation() - } - - override fun serverStreamCancellation(): Flow { - return service.serverStreamCancellation() - } - - override suspend fun clientStreamCancellation(n: Flow) { - return service.clientStreamCancellation(n) - } - - override fun fastServerProduce(n: Int): Flow { - return service.fastServerProduce(n) - } - } - } - - override suspend fun stopClient() { - client?.close() - client?.awaitCompletion() - } - - override suspend fun startServer(transport: CompatTransport, config: TestConfig): CompatServiceImpl { - val transport = transport.toKrpc() - val serverConfig = rpcServerConfig { - serialization { - json() - } - } - - server = object : KrpcServer(serverConfig, transport) {} - val impl = TestServiceImpl() - server?.registerService { impl } - return impl - } - - override suspend fun stopServer() { - server?.close() - server?.awaitCompletion() - } -} diff --git a/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt b/tests/krpc-protocol-compatibility-tests/templates/kotlinx/rpc/krpc/test/compat/service/TestService.kt similarity index 90% rename from tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt rename to tests/krpc-protocol-compatibility-tests/templates/kotlinx/rpc/krpc/test/compat/service/TestService.kt index b522f51c8..256a0c6aa 100644 --- a/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/service/TestService.kt +++ b/tests/krpc-protocol-compatibility-tests/templates/kotlinx/rpc/krpc/test/compat/service/TestService.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.toList import kotlinx.rpc.annotations.Rpc import kotlinx.rpc.krpc.test.compat.CompatServiceImpl -import kotlinx.rpc.test.WaitCounter +import kotlinx.rpc.krpc.test.compat.CompatWaitCounter import kotlin.coroutines.cancellation.CancellationException @Rpc @@ -20,6 +20,7 @@ interface TestService { fun serverStreaming(num: Int): Flow suspend fun clientStreaming(n: Flow): Int fun bidiStreaming(flow: Flow): Flow + suspend fun requestCancellation() fun serverStreamCancellation(): Flow suspend fun clientStreamCancellation(n: Flow) @@ -27,7 +28,7 @@ interface TestService { fun fastServerProduce(n: Int): Flow } -class TestServiceImpl : TestService, CompatServiceImpl { +class TestServiceImpl(counterFactory: () -> CompatWaitCounter) : TestService, CompatServiceImpl { override suspend fun unary(n: Int): Int { return n } @@ -44,8 +45,8 @@ class TestServiceImpl : TestService, CompatServiceImpl { return flow } - override val exitMethod: WaitCounter = WaitCounter() - override val cancelled: WaitCounter = WaitCounter() + override val exitMethod: CompatWaitCounter = counterFactory() + override val cancelled: CompatWaitCounter = counterFactory() override val entered: CompletableDeferred = CompletableDeferred() override val fence: CompletableDeferred = CompletableDeferred() diff --git a/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt b/tests/krpc-protocol-compatibility-tests/templates/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt similarity index 87% rename from tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt rename to tests/krpc-protocol-compatibility-tests/templates/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt index f411b07cf..2e5e0396f 100644 --- a/tests/krpc-protocol-compatibility-tests/src/test/kotlin/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt +++ b/tests/krpc-protocol-compatibility-tests/templates/kotlinx/rpc/krpc/test/compat/service/TestStarter.kt @@ -36,8 +36,8 @@ fun CompatTransport.toKrpc(): KrpcTransport { } } -@Suppress("unused") -class TestStarter : Starter { +@Suppress("unused", "ClassName") +class TestStarter_ : Starter { private var client: KrpcClient? = null private var server: KrpcServer? = null @@ -48,9 +48,15 @@ class TestStarter : Starter { json() } + //##csm connector-API-client + //##csm default connector { perCallBufferSize = config.perCallBufferSize } + //##csm /default + //##csm specific=[0.8.1, 0.9.1] + //##csm /specific + //##csm /connector-API-client } client = object : InitializedKrpcClient(clientConfig, transport) {} @@ -102,13 +108,19 @@ class TestStarter : Starter { json() } + //##csm connector-API-server + //##csm default connector { perCallBufferSize = config.perCallBufferSize } + //##csm /default + //##csm specific=[0.8.1, 0.9.1] + //##csm /specific + //##csm /connector-API-server } server = object : KrpcServer(serverConfig, transport) {} - val impl = TestServiceImpl() + val impl = TestServiceImpl(config.counterFactory) server?.registerService { impl } return impl } diff --git a/tests/krpc-protocol-compatibility-tests/test-api/build.gradle.kts b/tests/krpc-protocol-compatibility-tests/test-api/build.gradle.kts new file mode 100644 index 000000000..6b0c99886 --- /dev/null +++ b/tests/krpc-protocol-compatibility-tests/test-api/build.gradle.kts @@ -0,0 +1,31 @@ +/* + * Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("PropertyName") +@file:OptIn(ExperimentalAbiValidation::class) + +import org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode +import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation + +plugins { + alias(libs.plugins.conventions.jvm) + alias(libs.plugins.atomicfu) +} + +dependencies { + compileOnly(libs.atomicfu) + compileOnly(libs.coroutines.core) +} + +kotlin { + explicitApi = ExplicitApiMode.Disabled + + abiValidation { + enabled.set(false) + } +} + +tasks.test { + useJUnitPlatform() +} diff --git a/tests/krpc-protocol-compatibility-tests/test-api/src/main/kotlin/kotlinx/rpc/krpc/test/compat/CompatWaitCounter.kt b/tests/krpc-protocol-compatibility-tests/test-api/src/main/kotlin/kotlinx/rpc/krpc/test/compat/CompatWaitCounter.kt new file mode 100644 index 000000000..fe3383b77 --- /dev/null +++ b/tests/krpc-protocol-compatibility-tests/test-api/src/main/kotlin/kotlinx/rpc/krpc/test/compat/CompatWaitCounter.kt @@ -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.test.compat + +interface CompatWaitCounter { + val value: Int + fun increment() + suspend fun await(value: Int) +} diff --git a/tests/krpc-protocol-compatibility-tests/src/main/kotlin/kotlinx/rpc/krpc/test/compat/TestApi.kt b/tests/krpc-protocol-compatibility-tests/test-api/src/main/kotlin/kotlinx/rpc/krpc/test/compat/TestApi.kt similarity index 90% rename from tests/krpc-protocol-compatibility-tests/src/main/kotlin/kotlinx/rpc/krpc/test/compat/TestApi.kt rename to tests/krpc-protocol-compatibility-tests/test-api/src/main/kotlin/kotlinx/rpc/krpc/test/compat/TestApi.kt index 3a1b30c2c..9d0324bea 100644 --- a/tests/krpc-protocol-compatibility-tests/src/main/kotlin/kotlinx/rpc/krpc/test/compat/TestApi.kt +++ b/tests/krpc-protocol-compatibility-tests/test-api/src/main/kotlin/kotlinx/rpc/krpc/test/compat/TestApi.kt @@ -7,7 +7,6 @@ package kotlinx.rpc.krpc.test.compat import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.rpc.test.WaitCounter interface CompatTransport : CoroutineScope { suspend fun send(message: String) @@ -16,6 +15,7 @@ interface CompatTransport : CoroutineScope { class TestConfig( val perCallBufferSize: Int, + val counterFactory: () -> CompatWaitCounter, ) interface CompatService { @@ -32,8 +32,8 @@ interface CompatService { } interface CompatServiceImpl { - val exitMethod: WaitCounter - val cancelled: WaitCounter + val exitMethod: CompatWaitCounter + val cancelled: CompatWaitCounter val entered: CompletableDeferred val fence: CompletableDeferred }