Skip to content

Commit 0fa8573

Browse files
committed
Setup for Gradle Plugin tests
1 parent 7f3228e commit 0fa8573

File tree

13 files changed

+627
-8
lines changed

13 files changed

+627
-8
lines changed

gradle-plugin/build.gradle.kts

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,28 @@ kotlin {
2121
}
2222

2323
dependencies {
24-
compileOnly(libs.kotlin.gradle.plugin)
24+
implementation(libs.kotlin.gradle.plugin)
25+
26+
testImplementation(libs.kotlin.gradle.plugin)
27+
testImplementation(gradleTestKit())
28+
testImplementation(platform(libs.junit5.bom))
29+
testImplementation(libs.kotlin.test.junit5)
30+
testImplementation(libs.junit5.jupiter)
31+
testImplementation(libs.junit5.jupiter.api)
32+
testRuntimeOnly(libs.junit5.platform.launcher)
33+
34+
testImplementation(libs.logback.classic)
35+
}
36+
37+
tasks.test {
38+
val forwardOutput: Boolean = (properties.getOrDefault("gradle.test.forward.output", "false")
39+
as String).toBooleanStrictOrNull() ?: false
40+
41+
systemProperty("gradle.test.forward.output", forwardOutput)
42+
43+
useJUnitPlatform()
44+
45+
dependsOn(gradle.includedBuild("protoc-gen").task(":publishAllPublicationsToBuildRepoRepository"))
2546
}
2647

2748
// This block is needed to show plugin tasks on --dry-run
@@ -46,7 +67,7 @@ gradlePlugin {
4667
}
4768
}
4869

49-
abstract class GeneratePluginVersionTask @Inject constructor(
70+
abstract class GeneratePluginVersionsTask @Inject constructor(
5071
@get:Input val libraryVersion: String,
5172
@get:Input val protobufVersion: String,
5273
@get:Input val grpcVersion: String,
@@ -96,15 +117,15 @@ public const val BUF_TOOL_VERSION: String = "$bufToolVersion"
96117
)
97118
}
98119

99-
companion object {
100-
const val NAME = "generatePluginVersion"
120+
companion object Companion {
121+
const val NAME = "generatePluginVersions"
101122
}
102123
}
103124

104-
val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/pluginVersion")
125+
val sourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/pluginVersions")
105126

106-
val generatePluginVersionTask = tasks.register<GeneratePluginVersionTask>(
107-
GeneratePluginVersionTask.NAME,
127+
val generatePluginVersionsTask = tasks.register<GeneratePluginVersionsTask>(
128+
GeneratePluginVersionsTask.NAME,
108129
version.toString(),
109130
libs.versions.protobuf.asProvider().get(),
110131
libs.versions.grpc.asProvider().get(),
@@ -113,10 +134,63 @@ val generatePluginVersionTask = tasks.register<GeneratePluginVersionTask>(
113134
sourcesDir,
114135
)
115136

137+
abstract class GenerateTestVersionTask @Inject constructor(
138+
@get:Input val kotlinVersion: String,
139+
@get:Input val protobufVersion: String,
140+
@get:Input val grpcVersion: String,
141+
@get:Input val grpcKotlinVersion: String,
142+
@get:Input val buildRepo: String,
143+
@get:OutputDirectory val sourcesDir: File
144+
) : DefaultTask() {
145+
@TaskAction
146+
fun generate() {
147+
val sourceFile = File(sourcesDir, "Versions.kt")
148+
149+
sourceFile.writeText(
150+
"""
151+
// This file is generated by a $NAME gradle task. Do not modify manually.
152+
153+
package kotlinx.rpc
154+
155+
const val KOTLIN_VERSION: String = "$kotlinVersion"
156+
157+
const val BUILD_REPO: String = "$buildRepo"
158+
159+
// can't use from generatePluginVersionsTask bacause Gradle messes up caches
160+
const val TEST_PROTOBUF_VERSION: String = "$protobufVersion"
161+
const val TEST_GRPC_VERSION: String = "$grpcVersion"
162+
const val TEST_GRPC_KOTLIN_VERSION: String = "$grpcKotlinVersion"
163+
164+
""".trimIndent()
165+
)
166+
}
167+
168+
companion object {
169+
const val NAME = "generateTestVersions"
170+
}
171+
}
172+
173+
val testSourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/testVersions")
174+
175+
val globalRootDir: String by extra
176+
177+
val generateTestVersionsTask = tasks.register<GenerateTestVersionTask>(
178+
GenerateTestVersionTask.NAME,
179+
libs.versions.kotlin.lang.get(),
180+
libs.versions.protobuf.asProvider().get(),
181+
libs.versions.grpc.asProvider().get(),
182+
libs.versions.grpc.kotlin.get(),
183+
File(globalRootDir).resolve("build/repo").absolutePath,
184+
testSourcesDir,
185+
)
186+
116187
kotlin {
117188
sourceSets {
118189
main {
119-
kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir })
190+
kotlin.srcDir(generatePluginVersionsTask.map { it.sourcesDir })
191+
}
192+
test {
193+
kotlin.srcDir(generateTestVersionsTask.map { it.sourcesDir })
120194
}
121195
}
122196
}

gradle-plugin/settings.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ pluginManagement {
1111
includeBuild("../gradle-conventions-settings")
1212
}
1313

14+
dependencyResolutionManagement {
15+
// for tests
16+
includeBuild("../protoc-gen")
17+
includeBuild("../compiler-plugin")
18+
}
19+
1420
plugins {
1521
id("conventions-repositories")
1622
id("conventions-version-resolution")
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc
6+
7+
import kotlinx.rpc.base.GrpcBaseTest
8+
import org.gradle.testkit.runner.TaskOutcome
9+
import org.junit.jupiter.api.TestInstance
10+
import kotlin.io.path.Path
11+
import kotlin.test.Test
12+
import kotlin.test.assertEquals
13+
14+
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
15+
class GrpcJvmProjectTest : GrpcBaseTest() {
16+
override val isKmp: Boolean = false
17+
18+
@Test
19+
fun `Minimal gRPC Configuration`() {
20+
val result = runGradle(bufGenerateMain)
21+
22+
assertEquals(TaskOutcome.SUCCESS, result.protoTaskOutcome(bufGenerateMain))
23+
assertEquals(TaskOutcome.SUCCESS, result.protoTaskOutcome(processMainProtoFiles))
24+
assertEquals(TaskOutcome.SUCCESS, result.protoTaskOutcome(generateBufYamlMain))
25+
assertEquals(TaskOutcome.SUCCESS, result.protoTaskOutcome(generateBufGenYamlMain))
26+
27+
result.assertProtoTaskNotExecuted(bufGenerateTest)
28+
result.assertProtoTaskNotExecuted(processTestProtoFiles)
29+
result.assertProtoTaskNotExecuted(processTestImportProtoFiles)
30+
result.assertProtoTaskNotExecuted(generateBufYamlTest)
31+
result.assertProtoTaskNotExecuted(generateBufGenYamlTest)
32+
33+
assertSourceCodeGenerated(
34+
mainSourceSet,
35+
Path("Some.kt"),
36+
Path(RPC_INTERNAL, "Some.kt")
37+
)
38+
39+
assertWorkspaceProtoFilesCopied(mainSourceSet, Path("some.proto"))
40+
assertWorkspaceImportProtoFilesCopied(mainSourceSet)
41+
}
42+
43+
@Test
44+
fun `No gRPC`() {
45+
runNonExistentTask(bufGenerateMain)
46+
runNonExistentTask(bufGenerateTest)
47+
runNonExistentTask(processMainProtoFiles)
48+
runNonExistentTask(processTestProtoFiles)
49+
runNonExistentTask(processTestImportProtoFiles)
50+
runNonExistentTask(generateBufYamlMain)
51+
runNonExistentTask(generateBufYamlTest)
52+
runNonExistentTask(generateBufGenYamlMain)
53+
runNonExistentTask(generateBufGenYamlTest)
54+
}
55+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2023-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.rpc.base
6+
7+
import org.gradle.testkit.runner.BuildResult
8+
import org.gradle.testkit.runner.GradleRunner
9+
import org.junit.jupiter.api.BeforeEach
10+
import org.junit.jupiter.api.TestInfo
11+
import org.junit.jupiter.api.TestInstance
12+
import java.nio.file.Path
13+
import kotlin.io.path.ExperimentalPathApi
14+
import kotlin.io.path.absolutePathString
15+
import kotlin.io.path.copyTo
16+
import kotlin.io.path.copyToRecursively
17+
import kotlin.io.path.readText
18+
import kotlin.io.path.writeText
19+
import kotlinx.rpc.KOTLIN_VERSION
20+
import kotlinx.rpc.BUILD_REPO
21+
import kotlin.io.path.absolute
22+
import kotlin.io.path.createDirectories
23+
import kotlin.io.path.deleteRecursively
24+
25+
@OptIn(ExperimentalPathApi::class)
26+
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
27+
abstract class BaseTest {
28+
private lateinit var projectDir: Path
29+
30+
@BeforeEach
31+
protected fun setup(testInfo: TestInfo) {
32+
TEST_KIT_PATH.createDirectories()
33+
34+
val testClassName = testInfo.testClass.get().simpleName
35+
val testMethodName = testInfo.testMethod.get().name
36+
.replace(nameRegex, "_")
37+
.lowercase()
38+
39+
val baseDir = TEST_PROJECTS_PATH
40+
.resolve(testClassName)
41+
.resolve(testMethodName)
42+
43+
baseDir.deleteRecursively()
44+
baseDir.createDirectories()
45+
46+
projectDir = baseDir.resolve(PROJECT_DIR)
47+
val buildCacheDir = baseDir.resolve(BUILD_CACHE_DIR)
48+
49+
projectDir.createDirectories()
50+
buildCacheDir.createDirectories()
51+
52+
val settingsTemplate = RESOURCES_PATH.resolve(SETTINGS_TEMPLATE)
53+
?: error("template.settings.gradle.kts not found")
54+
val propertiesTemplate = RESOURCES_PATH.resolve(PROPERTIES_TEMPLATE)
55+
?: error("template.gradle.properties not found")
56+
57+
val settingsFile = projectDir.resolve("settings.gradle.kts")
58+
val propertiesFile = projectDir.resolve("gradle.properties")
59+
60+
settingsTemplate.copyTo(settingsFile)
61+
propertiesTemplate.copyTo(propertiesFile)
62+
63+
val projectName = "$testClassName-$testMethodName"
64+
settingsFile.replace("<test-name>", projectName)
65+
settingsFile.replace("<build-cache-dir>", buildCacheDir.absolutePathString())
66+
settingsFile.replace("<build-repo>", BUILD_REPO)
67+
68+
val testTemplateDirectory = RESOURCES_PATH.resolve(PROJECTS_DIR)
69+
.resolve(testClassName)
70+
.resolve(testMethodName)
71+
72+
testTemplateDirectory.copyToRecursively(projectDir, followLinks = false, overwrite = true)
73+
74+
val buildScriptFile = projectDir.resolve("build.gradle.kts")
75+
buildScriptFile.replace("<kotlin-version>", KOTLIN_VERSION)
76+
77+
println("""
78+
Setup project '$projectName'
79+
- in directory: ${projectDir.absolutePathString()}
80+
- from directory: ${testTemplateDirectory.absolutePathString()}
81+
""".trimIndent())
82+
}
83+
84+
private fun runGradleInternal(
85+
task: String,
86+
vararg args: String,
87+
body: GradleRunner.() -> BuildResult,
88+
): BuildResult {
89+
val gradleRunner = GradleRunner.create()
90+
.withProjectDir(projectDir.absolute().toFile())
91+
.withTestKitDir(TEST_KIT_PATH.absolute().toFile())
92+
.withPluginClasspath()
93+
.withArguments(
94+
listOfNotNull(
95+
task,
96+
"--stacktrace",
97+
"--info",
98+
"-Dorg.gradle.kotlin.dsl.scriptCompilationAvoidance=false",
99+
*args,
100+
)
101+
).apply {
102+
if (forwardOutput) {
103+
forwardOutput()
104+
}
105+
}
106+
107+
println("Running Gradle task '$task' with arguments: [${args.joinToString()}]")
108+
return gradleRunner.body()
109+
}
110+
111+
protected fun runBaseTest(body: TestEnv.() -> Unit) {
112+
runTest(TestEnv(), body)
113+
}
114+
115+
protected fun <T : TestEnv> runTest(testEnv: T, body: T.() -> Unit) {
116+
try {
117+
testEnv.body()
118+
} catch (e: Throwable) {
119+
val output = testEnv.latestBuild?.output
120+
if (output != null) {
121+
println("Latest gradle build output:")
122+
println(output)
123+
} else {
124+
println("No gradle build output available")
125+
}
126+
throw e
127+
}
128+
}
129+
130+
open inner class TestEnv {
131+
val projectDir: Path get() = this@BaseTest.projectDir
132+
var latestBuild: BuildResult? = null
133+
private set
134+
135+
fun runGradle(task: String, vararg args: String): BuildResult {
136+
return runGradleInternal(task, *args) {
137+
build().also { latestBuild = it }
138+
}
139+
}
140+
141+
fun runGradleToFail(task: String, vararg args: String): BuildResult {
142+
return runGradleInternal(task, *args) {
143+
buildAndFail().also { latestBuild = it }
144+
}
145+
}
146+
147+
fun runNonExistentTask(task: String): BuildResult {
148+
return runGradleToFail(task).apply {
149+
assertNoTask(task)
150+
}
151+
}
152+
153+
fun BuildResult.assertNoTask(name: String) {
154+
assert(output.contains("Task '$name' not found")) {
155+
"Task '$name' should not be present in the project"
156+
}
157+
}
158+
}
159+
160+
protected fun Path.replace(oldValue: String, newValue: String) {
161+
writeText(readText().replace(oldValue, newValue))
162+
}
163+
164+
companion object {
165+
private val forwardOutput = System.getProperty("gradle.test.forward.output")
166+
?.toBooleanStrictOrNull() ?: false
167+
168+
private val nameRegex = Regex("[ .,-]")
169+
170+
private val TEST_PROJECTS_PATH = Path.of("build", "gradle-test")
171+
private val TEST_KIT_PATH = Path.of("build", "test-kit")
172+
private const val BUILD_CACHE_DIR = "build-cache"
173+
private const val PROJECT_DIR = "project"
174+
175+
private val RESOURCES_PATH = Path.of("src", "test", "resources")
176+
private const val SETTINGS_TEMPLATE = "template.settings.gradle.kts"
177+
private const val PROPERTIES_TEMPLATE = "template.gradle.properties"
178+
private const val PROJECTS_DIR = "projects"
179+
}
180+
}

0 commit comments

Comments
 (0)