Skip to content

Commit ed6fa8d

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

File tree

13 files changed

+466
-8
lines changed

13 files changed

+466
-8
lines changed

gradle-plugin/build.gradle.kts

Lines changed: 71 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,52 @@ 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 buildRepo: String,
140+
@get:OutputDirectory val sourcesDir: File
141+
) : DefaultTask() {
142+
@TaskAction
143+
fun generate() {
144+
val sourceFile = File(sourcesDir, "Versions.kt")
145+
146+
sourceFile.writeText(
147+
"""
148+
// This file is generated by a $NAME gradle task. Do not modify manually.
149+
150+
package kotlinx.rpc
151+
152+
const val KOTLIN_VERSION: String = "$kotlinVersion"
153+
154+
const val BUILD_REPO: String = "$buildRepo"
155+
156+
""".trimIndent()
157+
)
158+
}
159+
160+
companion object {
161+
const val NAME = "generateTestVersions"
162+
}
163+
}
164+
165+
val testSourcesDir = File(project.layout.buildDirectory.asFile.get(), "generated-sources/testVersions")
166+
167+
val globalRootDir: String by extra
168+
169+
val generateTestVersionsTask = tasks.register<GenerateTestVersionTask>(
170+
GenerateTestVersionTask.NAME,
171+
libs.versions.kotlin.lang.get(),
172+
File(globalRootDir).resolve("build/repo").absolutePath,
173+
testSourcesDir,
174+
)
175+
116176
kotlin {
117177
sourceSets {
118178
main {
119-
kotlin.srcDir(generatePluginVersionTask.map { it.sourcesDir })
179+
kotlin.srcDir(generatePluginVersionsTask.map { it.sourcesDir })
180+
}
181+
test {
182+
kotlin.srcDir(generateTestVersionsTask.map { it.sourcesDir })
120183
}
121184
}
122185
}

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: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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+
protected 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 runGradle(task: String, vararg args: String): BuildResult {
112+
return runGradleInternal(task, *args) {
113+
build()
114+
}
115+
}
116+
117+
protected fun runGradleToFail(task: String, vararg args: String): BuildResult {
118+
return runGradleInternal(task, *args) {
119+
buildAndFail()
120+
}
121+
}
122+
123+
protected fun runNonExistentTask(task: String): BuildResult {
124+
return runGradleToFail(task).apply {
125+
assertNoTask(task)
126+
}
127+
}
128+
129+
private fun Path.replace(oldValue: String, newValue: String) {
130+
writeText(readText().replace(oldValue, newValue))
131+
}
132+
133+
protected fun BuildResult.assertNoTask(name: String) {
134+
assert(output.contains("Task '$name' not found")) {
135+
"Task '$name' should not be present in the project"
136+
}
137+
}
138+
139+
companion object {
140+
private val forwardOutput = System.getProperty("gradle.test.forward.output")
141+
?.toBooleanStrictOrNull() ?: false
142+
143+
private val nameRegex = Regex("[ .,-]")
144+
145+
private val TEST_PROJECTS_PATH = Path.of("build", "gradle-test")
146+
private val TEST_KIT_PATH = Path.of("build", "test-kit")
147+
private const val BUILD_CACHE_DIR = "build-cache"
148+
private const val PROJECT_DIR = "project"
149+
150+
private val RESOURCES_PATH = Path.of("src", "test", "resources")
151+
private const val SETTINGS_TEMPLATE = "template.settings.gradle.kts"
152+
private const val PROPERTIES_TEMPLATE = "template.gradle.properties"
153+
private const val PROJECTS_DIR = "projects"
154+
}
155+
}

0 commit comments

Comments
 (0)