aboutsummaryrefslogtreecommitdiff
path: root/integration-tests/src/test/kotlin/com/google/devtools/ksp/test
diff options
context:
space:
mode:
Diffstat (limited to 'integration-tests/src/test/kotlin/com/google/devtools/ksp/test')
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIT.kt84
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIncrementalIT.kt62
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/Artifact.kt24
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIT.kt41
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedRefsIncIT.kt69
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedSrcsIncIT.kt34
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GetSealedSubclassesIncIT.kt56
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/HmppIT.kt83
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalCPIT.kt130
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalIT.kt262
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalMultiChainIT.kt49
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalRemovalIT.kt35
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/InitPlusProviderIT.kt33
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaNestedClassIT.kt22
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaOnlyIT.kt34
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KMPImplementedIT.kt369
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KSPCmdLineOptionsIT.kt85
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt42
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt40
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MultiplatformIT.kt34
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnErrorIT.kt111
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnlyResourcesFileIT.kt21
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OutputDepsIt.kt157
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt210
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PsiCacheIT.kt18
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt41
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VerboseIT.kt67
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VersionCheckIT.kt42
-rw-r--r--integration-tests/src/test/kotlin/com/google/devtools/ksp/test/fixtures/BuildResultFixture.kt34
29 files changed, 2289 insertions, 0 deletions
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIT.kt
new file mode 100644
index 00000000..7c485212
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIT.kt
@@ -0,0 +1,84 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class AndroidIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("playground-android", "playground")
+
+ @Test
+ fun testPlaygroundAndroid() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ // Disabling configuration cache. See https://github.com/google/ksp/issues/299 for details
+ gradleRunner.withArguments(
+ "clean", "build", "minifyReleaseWithR8", "--configuration-cache-problems=warn", "--info", "--stacktrace"
+ ).build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:build")?.outcome)
+ val mergedConfiguration = File(project.root, "workload/build/outputs/mapping/release/configuration.txt")
+ assert(mergedConfiguration.exists()) {
+ "Merged configuration file not found!\n${printDirectoryTree(project.root)}"
+ }
+ val configurationText = mergedConfiguration.readText()
+ assert("-keep class com.example.AClassBuilder { *; }" in configurationText) {
+ "Merged configuration did not contain generated proguard rules!\n$configurationText"
+ }
+ }
+ }
+}
+
+/**
+ * Pretty print the directory tree and its file names.
+ *
+ * @param folder
+ * must be a folder.
+ * @return
+ */
+fun printDirectoryTree(folder: File): String? {
+ require(folder.isDirectory) { "folder is not a Directory" }
+ val indent = 0
+ val sb = StringBuilder()
+ printDirectoryTree(folder, indent, sb)
+ return sb.toString()
+}
+
+private fun printDirectoryTree(
+ folder: File,
+ indent: Int,
+ sb: StringBuilder
+) {
+ require(folder.isDirectory) { "folder is not a Directory" }
+ sb.append(getIndentString(indent))
+ sb.append("+--")
+ sb.append(folder.name)
+ sb.append("/")
+ sb.append("\n")
+ for (file in folder.listFiles()) {
+ if (file.isDirectory) {
+ printDirectoryTree(file, indent + 1, sb)
+ } else {
+ printFile(file, indent + 1, sb)
+ }
+ }
+}
+
+private fun printFile(file: File, indent: Int, sb: StringBuilder) {
+ sb.append(getIndentString(indent))
+ sb.append("+--")
+ sb.append(file.name)
+ sb.append("\n")
+}
+
+private fun getIndentString(indent: Int): String? {
+ val sb = StringBuilder()
+ for (i in 0 until indent) {
+ sb.append("| ")
+ }
+ return sb.toString()
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIncrementalIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIncrementalIT.kt
new file mode 100644
index 00000000..3cd01c14
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/AndroidIncrementalIT.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2020 Google LLC
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.ksp.test
+
+import com.google.devtools.ksp.test.fixtures.BuildResultFixture
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class AndroidIncrementalIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("playground-android-multi", "playground")
+
+ @Test
+ fun testPlaygroundAndroid() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "clean", ":application:compileDebugKotlin", "--configuration-cache-problems=warn", "--debug", "--stacktrace"
+ ).build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:compileDebugKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":application:compileDebugKotlin")?.outcome)
+ }
+
+ project.root.resolve("workload/src/main/java/com/example/A.kt").also {
+ it.appendText(
+ """
+
+ class Unused
+ """.trimIndent()
+ )
+ }
+
+ gradleRunner.withArguments(
+ ":application:compileDebugKotlin", "--configuration-cache-problems=warn", "--debug", "--stacktrace"
+ ).build().let { result ->
+ Assert.assertEquals(
+ setOf("workload/src/main/java/com/example/A.kt".replace('/', File.separatorChar)),
+ BuildResultFixture(result).compiledKotlinSources,
+ )
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/Artifact.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/Artifact.kt
new file mode 100644
index 00000000..0b09a07a
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/Artifact.kt
@@ -0,0 +1,24 @@
+import org.junit.*
+import java.io.File
+import java.util.zip.ZipFile
+
+// A snapshot of the digest of output jar.
+class Artifact(file: File) {
+ private fun getCRCs(file: File): Map<String, Long> {
+ Assert.assertTrue(file.exists())
+ return ZipFile(file).use {
+ it.entries().asSequence().map {
+ it.name to it.crc
+ }.toMap()
+ }
+ }
+
+ val crcs: Map<String, Long> = getCRCs(file)
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is Artifact)
+ return false
+
+ return crcs == other.crcs
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIT.kt
new file mode 100644
index 00000000..c1c0c498
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIT.kt
@@ -0,0 +1,41 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class BuildCacheIT {
+ @Rule
+ @JvmField
+ val project1: TemporaryTestProject = TemporaryTestProject("buildcache", "playground")
+
+ @Rule
+ @JvmField
+ val project2: TemporaryTestProject = TemporaryTestProject("buildcache", "playground")
+
+ @Test
+ fun testBuildCache() {
+ val buildCacheDir = File(project1.root, "build-cache").absolutePath.replace(File.separatorChar, '/')
+ File(project1.root, "gradle.properties").appendText("\nbuildCacheDir=$buildCacheDir")
+ File(project2.root, "gradle.properties").appendText("\nbuildCacheDir=$buildCacheDir")
+
+ GradleRunner.create().withProjectDir(project1.root).withArguments(
+ "--build-cache",
+ ":workload:clean",
+ "build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload:kspKotlin")?.outcome)
+ }
+
+ GradleRunner.create().withProjectDir(project2.root).withArguments(
+ "--build-cache",
+ ":workload:clean",
+ "build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.FROM_CACHE, it.task(":workload:kspKotlin")?.outcome)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedRefsIncIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedRefsIncIT.kt
new file mode 100644
index 00000000..30a8f01c
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedRefsIncIT.kt
@@ -0,0 +1,69 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class GeneratedRefsIncIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("refs-gen", "test-processor")
+
+ @Test
+ fun testGeneratedRefsInc() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val expected = listOf(
+ "w: [ksp] 1: [File: Bar.kt, File: Baz.kt]",
+ "w: [ksp] 2: [File: Foo.kt]",
+ "w: [ksp] 3: [File: Goo.kt]"
+ )
+
+ val expectedBar = listOf(
+ "w: [ksp] 1: [File: Bar.kt]",
+ "w: [ksp] 2: [File: Foo.kt]",
+ "w: [ksp] 3: [File: Goo.kt]"
+ )
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected, outputs)
+ }
+
+ File(project.root, "workload/src/main/kotlin/com/example/Baz.kt").appendText("\n\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected, outputs)
+ }
+
+ // Baz doesn't depend on Bar, so touching Bar won't invalidate Baz.
+ File(project.root, "workload/src/main/kotlin/com/example/Bar.kt").appendText("\n\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expectedBar, outputs)
+ }
+
+ // Make Baz depends on Bar; Bar will be invalidated.
+ File(project.root, "workload/src/main/kotlin/com/example/Bar.kt").appendText("\nclass List<T>\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected, outputs)
+ }
+
+ // Baz depended on Bar, so Baz should be invalidated.
+ project.restore("workload/src/main/kotlin/com/example/Bar.kt")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected, outputs)
+ }
+
+ // Baz doesn't depend on Bar, so touching Bar won't invalidate Baz.
+ File(project.root, "workload/src/main/kotlin/com/example/Bar.kt").appendText("\n\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expectedBar, outputs)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedSrcsIncIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedSrcsIncIT.kt
new file mode 100644
index 00000000..df9fc89f
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GeneratedSrcsIncIT.kt
@@ -0,0 +1,34 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class GeneratedSrcsIncIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("srcs-gen", "test-processor")
+
+ @Test
+ fun testGeneratedSrcsInc() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val expected = listOf(
+ "w: [ksp] 1: [File: Bar.kt, File: Baz.kt]",
+ "w: [ksp] 2: [File: Foo.kt]",
+ "w: [ksp] 3: [File: FooBar.kt, File: FooBaz.kt]"
+ )
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected, outputs)
+ }
+ File(project.root, "workload/src/main/kotlin/com/example/Baz.kt").appendText(System.lineSeparator())
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected, outputs)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GetSealedSubclassesIncIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GetSealedSubclassesIncIT.kt
new file mode 100644
index 00000000..29f56108
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/GetSealedSubclassesIncIT.kt
@@ -0,0 +1,56 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class GetSealedSubclassesIncIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("sealed-subclasses", "test-processor")
+
+ @Test
+ fun testGetSealedSubclassesInc() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val expected2 = listOf(
+ "w: [ksp] Processing Impl1.kt",
+ "w: [ksp] Impl1 : []",
+ "w: [ksp] Processing Impl2.kt",
+ "w: [ksp] Impl2 : []",
+ "w: [ksp] Processing Sealed.kt",
+ "w: [ksp] Sealed : [Impl1, Impl2]",
+ )
+
+ val expected3 = listOf(
+ "w: [ksp] Processing Impl1.kt",
+ "w: [ksp] Impl1 : []",
+ "w: [ksp] Processing Impl2.kt",
+ "w: [ksp] Impl2 : []",
+ "w: [ksp] Processing Impl3.kt",
+ "w: [ksp] Impl3 : []",
+ "w: [ksp] Processing Sealed.kt",
+ "w: [ksp] Sealed : [Impl1, Impl2, Impl3]",
+ )
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected2, outputs)
+ }
+
+ File(project.root, "workload/src/main/kotlin/com/example/Impl3.kt").appendText("package com.example\n\n")
+ File(project.root, "workload/src/main/kotlin/com/example/Impl3.kt").appendText("class Impl3 : Sealed()\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected3, outputs)
+ }
+
+ File(project.root, "workload/src/main/kotlin/com/example/Impl3.kt").delete()
+ gradleRunner.withArguments("assemble").build().let { result ->
+ val outputs = result.output.lines().filter { it.startsWith("w: [ksp]") }
+ Assert.assertEquals(expected2, outputs)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/HmppIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/HmppIT.kt
new file mode 100644
index 00000000..9da354ed
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/HmppIT.kt
@@ -0,0 +1,83 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+class HmppIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("hmpp")
+
+ val taskToFilesTraditional = mapOf(
+ ":workload:kspCommonMainKotlinMetadata" to "w: [ksp] EchoProcessor: CommonMain",
+ ":workload:kspJvmJsKotlinMetadata" to "w: [ksp] EchoProcessor: CommonMain_JvmJs",
+ ":workload:kspJvmLinuxX64KotlinMetadata" to "w: [ksp] EchoProcessor: CommonMain_JvmLinuxX64",
+ ":workload:kspKotlinJvm" to "w: [ksp] EchoProcessor: CommonMain_JvmJs_JvmLinuxX64_JvmMain_JvmOnly",
+ ":workload:kspKotlinJs" to "w: [ksp] EchoProcessor: CommonMain_JsMain_JvmJs",
+ ":workload:kspKotlinLinuxX64" to "w: [ksp] EchoProcessor: CommonMain_JvmLinuxX64_LinuxX64Main",
+ )
+
+ @Test
+ fun testTraditional() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ taskToFilesTraditional.forEach { (task, expected) ->
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ task,
+ ).build().let { result ->
+ val logs = result.output.lines().filter { it.startsWith("w: [ksp] EchoProcessor: ") }.toSet()
+ Assert.assertTrue(expected in logs)
+ }
+ }
+ }
+
+ val taskToFilesHmpp = mapOf(
+ ":workload:kspCommonMainKotlinMetadata" to setOf(
+ "w: [ksp] EchoProcessor: CommonMain",
+ ),
+ ":workload:kspJvmJsKotlinMetadata" to setOf(
+ "w: [ksp] EchoProcessor: CommonMain",
+ "w: [ksp] EchoProcessor: (CommonMain)_JvmJs",
+ ),
+ ":workload:kspJvmLinuxX64KotlinMetadata" to setOf(
+ "w: [ksp] EchoProcessor: CommonMain",
+ "w: [ksp] EchoProcessor: (CommonMain)_JvmLinuxX64",
+ ),
+ ":workload:kspKotlinJvm" to setOf(
+ "w: [ksp] EchoProcessor: CommonMain",
+ "w: [ksp] EchoProcessor: (CommonMain)_JvmJs",
+ "w: [ksp] EchoProcessor: (CommonMain)_JvmLinuxX64",
+ "w: [ksp] EchoProcessor: ((CommonMain)_JvmJs)_((CommonMain)_JvmLinuxX64)_(CommonMain)_JvmMain_JvmOnly",
+ ),
+ ":workload:kspKotlinJs" to setOf(
+ "w: [ksp] EchoProcessor: CommonMain",
+ "w: [ksp] EchoProcessor: (CommonMain)_JvmJs",
+ "w: [ksp] EchoProcessor: ((CommonMain)_JvmJs)_(CommonMain)_JsMain",
+ ),
+ ":workload:kspKotlinLinuxX64" to setOf(
+ "w: [ksp] EchoProcessor: CommonMain",
+ "w: [ksp] EchoProcessor: (CommonMain)_JvmLinuxX64",
+ "w: [ksp] EchoProcessor: ((CommonMain)_JvmLinuxX64)_(CommonMain)_LinuxX64Main",
+ ),
+ )
+
+ @Test
+ fun testHmpp() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ taskToFilesHmpp.forEach { (task, expected) ->
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "--rerun-tasks",
+ "-Pksp.experimental.processing.model=hierarchical",
+ task,
+ ).build().let { result ->
+ val logs = result.output.lines().filter { it.startsWith("w: [ksp] EchoProcessor: ") }.toSet()
+ Assert.assertTrue(logs == expected)
+ }
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalCPIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalCPIT.kt
new file mode 100644
index 00000000..cf07dd66
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalCPIT.kt
@@ -0,0 +1,130 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class IncrementalCPIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("incremental-classpath")
+
+ val src2Dirty = listOf(
+ "l1/src/main/kotlin/p1/L1.kt" to setOf(
+ "w: [ksp] p1/K1.kt",
+ "w: [ksp] processing done",
+ ),
+ "l2/src/main/kotlin/p1/L2.kt" to setOf(
+ "w: [ksp] p1/K1.kt",
+ "w: [ksp] p1/K2.kt",
+ "w: [ksp] processing done",
+ ),
+ "l3/src/main/kotlin/p1/L3.kt" to setOf(
+ "w: [ksp] p1/K3.kt",
+ "w: [ksp] processing done",
+ ),
+ "l4/src/main/kotlin/p1/L4.kt" to setOf(
+ "w: [ksp] p1/K3.kt",
+ "w: [ksp] processing done",
+ ),
+ "l5/src/main/kotlin/p1/L5.kt" to setOf(
+ "w: [ksp] processing done",
+ ),
+ )
+
+ val emptyMessage = setOf("w: [ksp] processing done")
+
+ @Test
+ fun testCPChanges() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ src2Dirty.forEach { (src, expectedDirties) ->
+ File(project.root, src).appendText("\n\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ // Trivial changes should not result in re-processing.
+ Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
+ }
+ }
+
+ var i = 100
+ src2Dirty.forEach { (src, expectedDirties) ->
+ File(project.root, src).appendText("\n{ val v$i = ${i++} }\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ Assert.assertEquals(expectedDirties, dirties)
+ }
+ }
+
+ src2Dirty.forEach { (src, expectedDirties) ->
+ File(project.root, src).appendText("\n\nclass C${i++}\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ // Non-signature changes should not affect anything.
+ Assert.assertEquals(emptyMessage, dirties)
+ }
+ }
+ }
+
+ private fun toggleFlags(vararg extras: String) {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)
+
+ gradleRunner.withArguments(
+ *extras,
+ "--rerun-tasks",
+ "-Pksp.incremental=true",
+ "-Pksp.incremental.intermodule=true",
+ "assemble"
+ ).build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ }
+
+ gradleRunner.withArguments(
+ *extras,
+ "--rerun-tasks",
+ "-Pksp.incremental=false",
+ "-Pksp.incremental.intermodule=true",
+ "assemble"
+ ).build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ }
+
+ gradleRunner.withArguments(
+ *extras,
+ "--rerun-tasks",
+ "-Pksp.incremental=true",
+ "-Pksp.incremental.intermodule=false",
+ "assemble"
+ ).build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ }
+
+ gradleRunner.withArguments(
+ *extras,
+ "--rerun-tasks",
+ "-Pksp.incremental=false",
+ "-Pksp.incremental.intermodule=false",
+ "assemble"
+ ).build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ }
+ }
+
+ @Test
+ fun toggleIncrementalFlagsWithoutConfigurationCache() {
+ toggleFlags("--no-configuration-cache")
+ }
+
+ @Test
+ fun toggleIncrementalFlagsWithConfigurationCache() {
+ toggleFlags("--configuration-cache")
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalIT.kt
new file mode 100644
index 00000000..880598bf
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalIT.kt
@@ -0,0 +1,262 @@
+package com.google.devtools.ksp.test
+
+import Artifact
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class IncrementalIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("incremental")
+
+ val src2Dirty = listOf(
+ "workload/src/main/java/p1/J1.java" to setOf(
+ "w: [ksp] p1/TestK2J.kt",
+ "w: [ksp] p1/TestJ2J.java",
+ "w: [ksp] p1/J1.java",
+ ),
+ "workload/src/main/java/p1/J2.java" to setOf(
+ "w: [ksp] p1/J2.java",
+ ),
+ "workload/src/main/java/p1/TestJ2J.java" to setOf(
+ "w: [ksp] p1/TestJ2J.java",
+ ),
+ "workload/src/main/java/p1/TestJ2K.java" to setOf(
+ "w: [ksp] p1/TestJ2K.java",
+ ),
+ "workload/src/main/java/p2/J2.java" to setOf(
+ "w: [ksp] p1/TestK2J.kt",
+ "w: [ksp] p2/J2.java",
+ "w: [ksp] p1/TestJ2J.java",
+ ),
+ "workload/src/main/java/p3/J1.java" to setOf(
+ "w: [ksp] p3/J1.java",
+ ),
+ "workload/src/main/java/p3/J2.java" to setOf(
+ "w: [ksp] p3/J2.java",
+ ),
+ "workload/src/main/java/p3/J3.java" to setOf(
+ "w: [ksp] p1/TestK2J.kt",
+ "w: [ksp] p1/TestJ2J.java",
+ "w: [ksp] p3/J3.java",
+ ),
+ "workload/src/main/kotlin/p1/K1.kt" to setOf(
+ "w: [ksp] p1/TestK2K.kt",
+ "w: [ksp] p1/K1.kt",
+ "w: [ksp] p1/TestJ2K.java",
+ ),
+ "workload/src/main/kotlin/p1/K2.kt" to setOf(
+ "w: [ksp] p1/K2.kt",
+ ),
+ "workload/src/main/kotlin/p1/TestK2J.kt" to setOf(
+ "w: [ksp] p1/TestK2J.kt",
+ ),
+ "workload/src/main/kotlin/p1/TestK2K.kt" to setOf(
+ "w: [ksp] p1/TestK2K.kt",
+ ),
+ "workload/src/main/kotlin/p2/K2.kt" to setOf(
+ "w: [ksp] p1/TestK2K.kt",
+ "w: [ksp] p2/K2.kt",
+ "w: [ksp] p1/TestJ2K.java",
+ ),
+ "workload/src/main/kotlin/p3/K1.kt" to setOf(
+ "w: [ksp] p3/K1.kt",
+ ),
+ "workload/src/main/kotlin/p3/K2.kt" to setOf(
+ "w: [ksp] p3/K2.kt",
+ ),
+ "workload/src/main/kotlin/p3/K3.kt" to setOf(
+ "w: [ksp] p1/TestK2K.kt",
+ "w: [ksp] p3/K3.kt",
+ "w: [ksp] p1/TestJ2K.java",
+ )
+ )
+
+ @Test
+ fun testUpToDate() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
+ }
+ }
+
+ @Test
+ fun testIsolating() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+ val cleanArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+
+ src2Dirty.forEach { (src, expectedDirties) ->
+ File(project.root, src).appendText("\n\n")
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ Assert.assertEquals(expectedDirties, dirties)
+ }
+ }
+ val incrementalArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+ Assert.assertEquals(cleanArtifact, incrementalArtifact)
+ }
+
+ val changeSets = listOf(
+ listOf(7, 5),
+ listOf(0, 12),
+ listOf(13, 14),
+ listOf(8, 10),
+ listOf(11, 4),
+ listOf(3, 15),
+ listOf(6, 9),
+ listOf(2, 1),
+ listOf(3, 1, 12),
+ listOf(13, 0, 11),
+ listOf(6, 8, 4),
+ listOf(10, 9, 15),
+ listOf(2, 14, 5, 7),
+ listOf(5, 0, 13, 15),
+ listOf(3, 2, 6, 7),
+ listOf(4, 14, 10, 1),
+ listOf(12, 9, 8, 11),
+ listOf(12, 13, 5, 14, 7),
+ listOf(11, 2, 8, 8, 9),
+ listOf(11, 2, 8, 8, 9),
+ listOf(4, 0, 15, 1, 10),
+ )
+
+ @Test
+ fun testMultipleChanges() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ changeSets.forEach { changeSet ->
+ changeSet.forEach {
+ File(project.root, src2Dirty[it].first).appendText("\n\n")
+ }
+ val expectedDirties = changeSet.flatMapTo(mutableSetOf()) {
+ src2Dirty[it].second
+ }
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ Assert.assertEquals(expectedDirties, dirties)
+ }
+ }
+ }
+
+ @Test
+ fun testMultipleDeletes() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ val srcs = src2Dirty.map { it.first }
+
+ changeSets.forEach { changeSet ->
+ val notChanged = IntRange(0, srcs.size - 1).filter { it !in changeSet }
+
+ // Touch a file so that Gradle won't UP_TO_DATE for us.
+ notChanged.first().let {
+ File(project.root, srcs[it]).appendText("\n\n")
+ }
+
+ // Delete files
+ changeSet.forEach {
+ File(project.root, srcs[it]).delete()
+ }
+
+ // in: "workload/src/main/kotlin/p1/K2.kt"
+ // out: "p1/K2.kt"
+ val expectedOutputs = notChanged.map() {
+ srcs[it].split("/").subList(4, 6).joinToString(File.separator) + ".log"
+ }.sorted()
+
+ gradleRunner.withArguments(":workload:kspKotlin").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val outputRoot = File(project.root, "workload/build/generated/ksp/main/resources")
+ val outputs = outputRoot.walk().filter { it.isFile() }.map {
+ it.toRelativeString(outputRoot)
+ }.sorted().toList()
+
+ Assert.assertEquals(expectedOutputs, outputs)
+ }
+
+ changeSet.forEach {
+ project.restore(srcs[it])
+ }
+ }
+ }
+
+ @Test
+ fun testArgChange() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+ val cleanArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+
+ val expectedDirties = src2Dirty.map { it.second }.flatten().toSet()
+ fun buildAndCheck() {
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ Assert.assertEquals(dirties, expectedDirties)
+ }
+ val incrementalArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+ Assert.assertEquals(cleanArtifact, incrementalArtifact)
+ }
+
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"option1\", \"value1\") }\n")
+ buildAndCheck()
+
+ project.restore("workload/build.gradle.kts")
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"option1\", \"value2\") }\n")
+ buildAndCheck()
+
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"option2\", \"value2\") }\n")
+ buildAndCheck()
+
+ project.restore("workload/build.gradle.kts")
+ buildAndCheck()
+ }
+
+ @Test
+ fun testProcessorChange() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.withArguments("build").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.NO_SOURCE, result.task(":workload:kspTestKotlin")?.outcome)
+ }
+ val cleanArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+
+ val expectedDirties = src2Dirty.map { it.second }.flatten().toSet()
+ fun buildAndCheck() {
+ gradleRunner.withArguments("build").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.NO_SOURCE, result.task(":workload:kspTestKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ Assert.assertEquals(dirties, expectedDirties)
+ }
+ val incrementalArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+ Assert.assertEquals(cleanArtifact, incrementalArtifact)
+ }
+
+ File(project.root, "validator/src/main/kotlin/Validator.kt").appendText("\n")
+ buildAndCheck()
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalMultiChainIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalMultiChainIT.kt
new file mode 100644
index 00000000..33680be3
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalMultiChainIT.kt
@@ -0,0 +1,49 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class IncrementalMultiChainIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("incremental-multi-chain")
+
+ @Test
+ fun testMultiChain() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ val k2 = File(project.root, "workload/src/main/kotlin/K2.kt")
+
+ gradleRunner.withArguments("run").build().let { result ->
+ Assert.assertTrue(result.output.contains("validating K1.kt"))
+ Assert.assertTrue(result.output.contains("validating Main.kt"))
+ Assert.assertTrue(result.output.contains("validating K1Impl.kt"))
+ Assert.assertTrue(result.output.contains("validating AllImpls.kt"))
+ Assert.assertTrue(result.output.contains("[K1Impl]"))
+ }
+
+ k2.writeText(
+ "@NeedsImpl\ninterface K2\n"
+ )
+ gradleRunner.withArguments("run").build().let { result ->
+ Assert.assertTrue(result.output.contains("validating K1.kt"))
+ Assert.assertTrue(result.output.contains("validating K2.kt"))
+ Assert.assertTrue(result.output.contains("validating Main.kt"))
+ Assert.assertTrue(result.output.contains("validating K1Impl.kt"))
+ Assert.assertTrue(result.output.contains("validating K2Impl.kt"))
+ Assert.assertTrue(result.output.contains("validating AllImpls.kt"))
+ Assert.assertTrue(result.output.contains("[K1Impl, K2Impl]"))
+ }
+
+ k2.delete()
+ gradleRunner.withArguments("run").build().let { result ->
+ Assert.assertTrue(result.output.contains("validating K1.kt"))
+ Assert.assertTrue(result.output.contains("validating Main.kt"))
+ Assert.assertTrue(result.output.contains("validating K1Impl.kt"))
+ Assert.assertTrue(result.output.contains("validating AllImpls.kt"))
+ Assert.assertTrue(result.output.contains("[K1Impl]"))
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalRemovalIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalRemovalIT.kt
new file mode 100644
index 00000000..78de3c04
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/IncrementalRemovalIT.kt
@@ -0,0 +1,35 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class IncrementalRemovalIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("incremental-removal")
+
+ @Test
+ fun testRemoveOutputs() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ val k1 = "workload/src/main/kotlin/p1/K1.kt"
+
+ gradleRunner.withArguments("run").build().let { result ->
+ Assert.assertTrue(result.output.contains("result: generated"))
+ }
+
+ File(project.root, k1).writeText(
+ "package p1\n\nclass K1\n\nclass Foo : Bar { override fun s() = \"crafted\" }\n"
+ )
+ gradleRunner.withArguments("run").build().let { result ->
+ Assert.assertTrue(result.output.contains("result: crafted"))
+ }
+
+ project.restore(k1)
+ gradleRunner.withArguments("run").build().let { result ->
+ Assert.assertTrue(result.output.contains("result: generated"))
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/InitPlusProviderIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/InitPlusProviderIT.kt
new file mode 100644
index 00000000..31b7cb58
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/InitPlusProviderIT.kt
@@ -0,0 +1,33 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+import java.util.jar.JarFile
+
+class InitPlusProviderIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("init-plus-provider")
+
+ @Test
+ fun testInitPlusProvider() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val resultCleanBuild = gradleRunner.withArguments("clean", "build").build()
+
+ Assert.assertEquals(TaskOutcome.SUCCESS, resultCleanBuild.task(":workload:build")?.outcome)
+
+ val artifact = File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar")
+ Assert.assertTrue(artifact.exists())
+
+ JarFile(artifact).use { jarFile ->
+ Assert.assertTrue(jarFile.getEntry("TestProcessor.log").size > 0)
+ Assert.assertTrue(jarFile.getEntry("HelloFromProvider.class").size > 0)
+ Assert.assertTrue(jarFile.getEntry("GeneratedFromProvider.class").size > 0)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaNestedClassIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaNestedClassIT.kt
new file mode 100644
index 00000000..14cd0903
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaNestedClassIT.kt
@@ -0,0 +1,22 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+class JavaNestedClassIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("javaNestedClass")
+
+ @Test
+ fun testJavaNestedClass() {
+
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val resultCleanBuild = gradleRunner.withArguments("clean", "build").build()
+ Assert.assertEquals(TaskOutcome.SUCCESS, resultCleanBuild.task(":workload:build")?.outcome)
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaOnlyIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaOnlyIT.kt
new file mode 100644
index 00000000..bf94e522
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/JavaOnlyIT.kt
@@ -0,0 +1,34 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class JavaOnlyIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("java-only", "test-processor")
+
+ @Test
+ fun testJavaOnly() {
+ // FIXME: `clean` fails to delete files on windows.
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ File(project.root, "workload/src/main/java/com/example/Foo.java").delete()
+
+ gradleRunner.withArguments("clean", "assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.NO_SOURCE, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KMPImplementedIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KMPImplementedIT.kt
new file mode 100644
index 00000000..7c9c2dcd
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KMPImplementedIT.kt
@@ -0,0 +1,369 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+import java.util.jar.*
+
+class KMPImplementedIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("kmp")
+
+ private fun verify(jarName: String, contents: List<String>) {
+ val artifact = File(project.root, jarName)
+ Assert.assertTrue(artifact.exists())
+
+ JarFile(artifact).use { jarFile ->
+ contents.forEach {
+ Assert.assertTrue(jarFile.getEntry(it).size > 0)
+ }
+ }
+ }
+
+ private fun verifyKexe(path: String) {
+ val artifact = File(project.root, path)
+ Assert.assertTrue(artifact.exists())
+ Assert.assertTrue(artifact.readBytes().size > 0)
+ }
+
+ private fun checkExecutionOptimizations(log: String) {
+ Assert.assertFalse(
+ "Execution optimizations have been disabled",
+ log.contains("Execution optimizations have been disabled")
+ )
+ }
+
+ @Test
+ fun testJvm() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-jvm:build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-jvm:build")?.outcome)
+ verify(
+ "workload-jvm/build/libs/workload-jvm-jvm-1.0-SNAPSHOT.jar",
+ listOf(
+ "com/example/Foo.class"
+ )
+ )
+ Assert.assertFalse(it.output.contains("kotlin scripting plugin:"))
+ Assert.assertTrue(it.output.contains("w: [ksp] platforms: [JVM"))
+ Assert.assertTrue(it.output.contains("w: [ksp] List has superTypes: true"))
+ checkExecutionOptimizations(it.output)
+ }
+ }
+
+ @Test
+ fun testJvmErrorLog() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ File(project.root, "workload-jvm/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"process\") }\n")
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-jvm:build"
+ ).buildAndFail().let {
+ val errors = it.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in process", errors.first())
+ }
+ project.restore("workload-jvm/build.gradle.kts")
+ }
+
+ @Test
+ fun testJs() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-js:build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-js:build")?.outcome)
+ verify(
+ "workload-js/build/libs/workload-js-jslegacy-1.0-SNAPSHOT.jar",
+ listOf(
+ "playground-workload-js-js-legacy.js"
+ )
+ )
+ verify(
+ "workload-js/build/libs/workload-js-jsir-1.0-SNAPSHOT.klib",
+ listOf(
+ "default/ir/types.knt"
+ )
+ )
+ Assert.assertFalse(it.output.contains("kotlin scripting plugin:"))
+ Assert.assertTrue(it.output.contains("w: [ksp] platforms: [JS"))
+ Assert.assertTrue(it.output.contains("w: [ksp] List has superTypes: true"))
+ checkExecutionOptimizations(it.output)
+ }
+ }
+
+ @Test
+ fun testJsErrorLog() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ File(project.root, "workload-js/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"process\") }\n")
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-js:build"
+ ).buildAndFail().let {
+ val errors = it.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in process", errors.first())
+ }
+ project.restore("workload-js/build.gradle.kts")
+ }
+
+ @Test
+ fun testJsFailWarning() {
+ File(project.root, "workload-js/build.gradle.kts")
+ .appendText("\nksp {\n allWarningsAsErrors = true\n}\n")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-js:build"
+ ).buildAndFail()
+ }
+
+ @Test
+ fun testAndroidNative() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-androidNative:build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-androidNative:build")?.outcome)
+ verifyKexe(
+ "workload-androidNative/build/bin/androidNativeX64/debugExecutable/workload-androidNative.kexe"
+ )
+ verifyKexe(
+ "workload-androidNative/build/bin/androidNativeX64/releaseExecutable/workload-androidNative.kexe"
+ )
+ verifyKexe(
+ "workload-androidNative/build/bin/androidNativeArm64/debugExecutable/workload-androidNative.kexe"
+ )
+ verifyKexe(
+ "workload-androidNative/build/bin/androidNativeArm64/releaseExecutable/workload-androidNative.kexe"
+ )
+ Assert.assertFalse(it.output.contains("kotlin scripting plugin:"))
+ Assert.assertTrue(it.output.contains("w: [ksp] platforms: [Native"))
+ checkExecutionOptimizations(it.output)
+ }
+ }
+
+ @Test
+ fun testLinuxX64() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ val genDir = File(project.root, "workload-linuxX64/build/generated/ksp/linuxX64/linuxX64Main/kotlin")
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-linuxX64:build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-linuxX64:build")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-linuxX64:kspTestKotlinLinuxX64")?.outcome)
+ verifyKexe("workload-linuxX64/build/bin/linuxX64/debugExecutable/workload-linuxX64.kexe")
+ verifyKexe("workload-linuxX64/build/bin/linuxX64/releaseExecutable/workload-linuxX64.kexe")
+
+ // TODO: Enable after CI's Xcode version catches up.
+ // Assert.assertTrue(
+ // result.task(":workload-linuxX64:kspKotlinIosArm64")?.outcome == TaskOutcome.SUCCESS ||
+ // result.task(":workload-linuxX64:kspKotlinIosArm64")?.outcome == TaskOutcome.SKIPPED
+ // )
+ // Assert.assertTrue(
+ // result.task(":workload-linuxX64:kspKotlinMacosX64")?.outcome == TaskOutcome.SUCCESS ||
+ // result.task(":workload-linuxX64:kspKotlinMacosX64")?.outcome == TaskOutcome.SKIPPED
+ // )
+ Assert.assertTrue(
+ it.task(":workload-linuxX64:kspKotlinMingwX64")?.outcome == TaskOutcome.SUCCESS ||
+ it.task(":workload-linuxX64:kspKotlinMingwX64")?.outcome == TaskOutcome.SKIPPED
+ )
+ Assert.assertFalse(it.output.contains("kotlin scripting plugin:"))
+ Assert.assertTrue(it.output.contains("w: [ksp] platforms: [Native"))
+ Assert.assertTrue(it.output.contains("w: [ksp] List has superTypes: true"))
+ Assert.assertTrue(File(genDir, "Main_dot_kt.kt").exists())
+ Assert.assertTrue(File(genDir, "ToBeRemoved_dot_kt.kt").exists())
+ checkExecutionOptimizations(it.output)
+ }
+
+ File(project.root, "workload-linuxX64/src/linuxX64Main/kotlin/ToBeRemoved.kt").delete()
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ ":workload-linuxX64:build"
+ ).build().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-linuxX64:build")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload-linuxX64:kspTestKotlinLinuxX64")?.outcome)
+ verifyKexe("workload-linuxX64/build/bin/linuxX64/debugExecutable/workload-linuxX64.kexe")
+ verifyKexe("workload-linuxX64/build/bin/linuxX64/releaseExecutable/workload-linuxX64.kexe")
+ Assert.assertTrue(File(genDir, "Main_dot_kt.kt").exists())
+ Assert.assertFalse(File(genDir, "ToBeRemoved_dot_kt.kt").exists())
+ checkExecutionOptimizations(it.output)
+ }
+ }
+
+ @Test
+ fun testNonEmbeddableArtifact() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "-Pkotlin.native.useEmbeddableCompilerJar=false",
+ ":workload-linuxX64:kspTestKotlinLinuxX64"
+ ).build()
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "-Pkotlin.native.useEmbeddableCompilerJar=true",
+ ":workload-linuxX64:kspTestKotlinLinuxX64"
+ ).build()
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ ":workload-linuxX64:kspTestKotlinLinuxX64"
+ ).build()
+ }
+
+ @Test
+ fun testLinuxX64ErrorLog() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ File(project.root, "workload-linuxX64/build.gradle.kts")
+ .appendText("\nksp { arg(\"exception\", \"process\") }\n")
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ ":workload-linuxX64:build"
+ ).buildAndFail().let {
+ val errors = it.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in process", errors.first())
+ }
+ project.restore("workload-js/build.gradle.kts")
+ }
+
+ private fun verifyAll(result: BuildResult) {
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:build")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspTestKotlinLinuxX64")?.outcome)
+
+ verify(
+ "workload/build/libs/workload-jvm-1.0-SNAPSHOT.jar",
+ listOf(
+ "com/example/Foo.class"
+ )
+ )
+
+ verify(
+ "workload/build/libs/workload-jslegacy-1.0-SNAPSHOT.jar",
+ listOf(
+ "playground-workload-js-legacy.js"
+ )
+ )
+
+ verify(
+ "workload/build/libs/workload-jsir-1.0-SNAPSHOT.klib",
+ listOf(
+ "default/ir/types.knt"
+ )
+ )
+
+ verifyKexe("workload/build/bin/linuxX64/debugExecutable/workload.kexe")
+ verifyKexe("workload/build/bin/linuxX64/releaseExecutable/workload.kexe")
+ verifyKexe("workload/build/bin/androidNativeX64/debugExecutable/workload.kexe")
+ verifyKexe("workload/build/bin/androidNativeX64/releaseExecutable/workload.kexe")
+ verifyKexe("workload/build/bin/androidNativeArm64/debugExecutable/workload.kexe")
+ verifyKexe("workload/build/bin/androidNativeArm64/releaseExecutable/workload.kexe")
+
+ // TODO: Enable after CI's Xcode version catches up.
+ // Assert.assertTrue(
+ // result.task(":workload:kspKotlinIosArm64")?.outcome == TaskOutcome.SUCCESS ||
+ // result.task(":workload:kspKotlinIosArm64")?.outcome == TaskOutcome.SKIPPED
+ // )
+ // Assert.assertTrue(
+ // result.task(":workload:kspKotlinMacosX64")?.outcome == TaskOutcome.SUCCESS ||
+ // result.task(":workload:kspKotlinMacosX64")?.outcome == TaskOutcome.SKIPPED
+ // )
+ Assert.assertTrue(
+ result.task(":workload:kspKotlinMingwX64")?.outcome == TaskOutcome.SUCCESS ||
+ result.task(":workload:kspKotlinMingwX64")?.outcome == TaskOutcome.SKIPPED
+ )
+
+ Assert.assertFalse(result.output.contains("kotlin scripting plugin:"))
+ Assert.assertTrue(result.output.contains("w: [ksp] platforms: [JVM"))
+ Assert.assertTrue(result.output.contains("w: [ksp] platforms: [JS"))
+ Assert.assertTrue(result.output.contains("w: [ksp] platforms: [Native"))
+ }
+
+ @Test
+ fun testMainConfiguration() {
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val buildScript = File(project.root, "workload/build.gradle.kts")
+ val lines = buildScript.readLines().takeWhile {
+ it.trimEnd() != "dependencies {"
+ }
+ buildScript.writeText(lines.joinToString(System.lineSeparator()))
+ buildScript.appendText(System.lineSeparator())
+ buildScript.appendText("dependencies {")
+ buildScript.appendText(System.lineSeparator())
+ buildScript.appendText(" add(\"ksp\", project(\":test-processor\"))")
+ buildScript.appendText(System.lineSeparator())
+ buildScript.appendText("}")
+
+ val messages = listOf(
+ "The 'ksp' configuration is deprecated in Kotlin Multiplatform projects. ",
+ "Please use target-specific configurations like 'kspJvm' instead."
+ )
+
+ // KotlinNative doesn't support configuration cache yet.
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ "build",
+ "-Pksp.allow.all.target.configuration=false"
+ ).buildAndFail().apply {
+ Assert.assertTrue(
+ messages.all {
+ output.contains(it)
+ }
+ )
+ checkExecutionOptimizations(output)
+ }
+
+ // KotlinNative doesn't support configuration cache yet.
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "clean",
+ "build"
+ ).build().apply {
+ Assert.assertTrue(
+ messages.all {
+ output.contains(it)
+ }
+ )
+ verifyAll(this)
+ checkExecutionOptimizations(output)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KSPCmdLineOptionsIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KSPCmdLineOptionsIT.kt
new file mode 100644
index 00000000..80f8cce6
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KSPCmdLineOptionsIT.kt
@@ -0,0 +1,85 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.jetbrains.kotlin.cli.common.ExitCode
+import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.PrintStream
+import java.net.URLClassLoader
+
+data class CompileResult(val exitCode: ExitCode, val output: String)
+
+class KSPCmdLineOptionsIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("cmd-options")
+
+ private fun runCmdCompiler(pluginOptions: List<String>): CompileResult {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.withArguments("clean", ":processors:build").build()
+ val processorJar = File(project.root, "processors/build/libs/processors-1.0-SNAPSHOT.jar")
+ val classLoader = URLClassLoader(arrayOf(processorJar.toURI().toURL()), javaClass.classLoader)
+ val compiler = classLoader.loadClass(K2JVMCompiler::class.java.name).newInstance() as K2JVMCompiler
+ val repoPath = "../build/repos/test/com/google/devtools/ksp/"
+ val kspPluginId = "com.google.devtools.ksp.symbol-processing"
+ val kspPluginJar = File("$repoPath/symbol-processing-cmdline/2.0.255-SNAPSHOT").listFiles()!!.filter {
+ it.name.matches(Regex(".*-\\d.jar"))
+ }.maxByOrNull { it.lastModified() }!!
+ val kspApiJar = File("$repoPath/symbol-processing-api/2.0.255-SNAPSHOT").listFiles()!!.filter {
+ it.name.matches(Regex(".*-\\d.jar"))
+ }.maxByOrNull { it.lastModified() }!!
+ val compilerArgs = mutableListOf(
+ "-no-stdlib",
+ "-Xplugin=${kspPluginJar.absolutePath}",
+ "-Xplugin=${kspApiJar.absolutePath}",
+ "-P", "plugin:$kspPluginId:apclasspath=${processorJar.absolutePath}",
+ "-P", "plugin:$kspPluginId:projectBaseDir=${project.root}/build",
+ "-P", "plugin:$kspPluginId:classOutputDir=${project.root}/build",
+ "-P", "plugin:$kspPluginId:javaOutputDir=${project.root}/build/out",
+ "-P", "plugin:$kspPluginId:kotlinOutputDir=${project.root}/build/out",
+ "-P", "plugin:$kspPluginId:resourceOutputDir=${project.root}/build/out",
+ "-P", "plugin:$kspPluginId:kspOutputDir=${project.root}/build/out",
+ "-P", "plugin:$kspPluginId:cachesDir=${project.root}/build/out",
+ "-P", "plugin:$kspPluginId:incremental=false",
+ "-d", "${project.root}/build/out"
+ )
+ pluginOptions.forEach {
+ compilerArgs.add("-P")
+ compilerArgs.add("plugin:$kspPluginId:$it")
+ }
+ compilerArgs.add(File(project.root, "workload/src/main/kotlin/com/example/A.kt").absolutePath)
+ val outStream = ByteArrayOutputStream()
+ val exitCode = compiler.exec(PrintStream(outStream), *compilerArgs.toTypedArray())
+ return CompileResult(exitCode, outStream.toString())
+ }
+
+ @Test
+ fun testWithCompilationOnError() {
+ val result = runCmdCompiler(listOf("apoption=error=true", "withCompilation=true"))
+ val errors = result.output.lines().filter { it.startsWith("error: [ksp]") }
+ val exitCode = result.exitCode
+ Assert.assertTrue(exitCode == ExitCode.COMPILATION_ERROR)
+ Assert.assertTrue(
+ errors.any {
+ it.startsWith("error: [ksp] java.lang.IllegalStateException: Error on request")
+ }
+ )
+ }
+
+ @Test
+ fun testWithCompilationOnErrorOk() {
+ val result = runCmdCompiler(listOf("apoption=error=true", "returnOkOnError=true", "withCompilation=true"))
+ val errors = result.output.lines().filter { it.startsWith("error: [ksp]") }
+ val exitCode = result.exitCode
+ Assert.assertTrue(exitCode == ExitCode.OK)
+ Assert.assertTrue(
+ errors.any {
+ it.startsWith("error: [ksp] java.lang.IllegalStateException: Error on request")
+ }
+ )
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt
new file mode 100644
index 00000000..d8714bf7
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/KotlinConstsInJavaIT.kt
@@ -0,0 +1,42 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class KotlinConstsInJavaIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("kotlin-consts-in-java")
+
+ private fun GradleRunner.buildAndCheck(vararg args: String, extraCheck: (BuildResult) -> Unit = {}) =
+ buildAndCheckOutcome(*args, outcome = TaskOutcome.SUCCESS, extraCheck = extraCheck)
+
+ private fun GradleRunner.buildAndCheckOutcome(
+ vararg args: String,
+ outcome: TaskOutcome,
+ extraCheck: (BuildResult) -> Unit = {}
+ ) {
+ val result = this.withArguments(*args).build()
+
+ Assert.assertEquals(outcome, result.task(":workload:kspKotlin")?.outcome)
+
+ extraCheck(result)
+ }
+
+ @Test
+ fun testKotlinConstsInJava() {
+ // FIXME: `clean` fails to delete files on windows.
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)
+ gradleRunner.buildAndCheck(":workload:kspKotlin")
+
+ File(project.root, "workload/src/main/java/com/example/JavaClass.java").appendText("\n")
+ gradleRunner.buildAndCheck(":workload:kspKotlin")
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt
new file mode 100644
index 00000000..870b4e60
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MapAnnotationArgumentsIT.kt
@@ -0,0 +1,40 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+class MapAnnotationArgumentsIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("map-annotation-arguments", "test-processor")
+
+ val expectedErrors = listOf(
+ "e: [ksp] unboxedChar: Char != Character\n",
+ "e: [ksp] boxedChar: (Char..Char?) != Character\n",
+ "e: Error occurred in KSP, check log for detail\n",
+ )
+
+ @Test
+ fun testMapAnnotationArguments() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("assemble", "-Pksp.map.annotation.arguments.in.java=true").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ gradleRunner.withArguments("clean", "assemble", "--rerun-tasks").buildAndFail().let { result ->
+ Assert.assertEquals(TaskOutcome.FAILED, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertTrue(expectedErrors.all { it in result.output })
+ }
+
+ gradleRunner.withArguments("clean", "assemble", "-Pksp.map.annotation.arguments.in.java=false", "--rerun-tasks")
+ .buildAndFail().let { result ->
+ Assert.assertEquals(TaskOutcome.FAILED, result.task(":workload:kspKotlin")?.outcome)
+ Assert.assertTrue(expectedErrors.all { it in result.output })
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MultiplatformIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MultiplatformIT.kt
new file mode 100644
index 00000000..9d7cb56e
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/MultiplatformIT.kt
@@ -0,0 +1,34 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+import java.util.jar.*
+
+class MultiplatformIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("playground-mpp", "playground")
+
+ @Test
+ fun testJVM() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ val resultCleanBuild =
+ gradleRunner.withArguments("--configuration-cache-problems=warn", "clean", "build").build()
+
+ Assert.assertEquals(TaskOutcome.SUCCESS, resultCleanBuild.task(":workload:build")?.outcome)
+
+ val artifact = File(project.root, "workload/build/libs/workload-jvm-1.0-SNAPSHOT.jar")
+ Assert.assertTrue(artifact.exists())
+
+ JarFile(artifact).use { jarFile ->
+ Assert.assertTrue(jarFile.getEntry("TestProcessor.log").size > 0)
+ Assert.assertTrue(jarFile.getEntry("hello/HELLO.class").size > 0)
+ Assert.assertTrue(jarFile.getEntry("com/example/AClassBuilder.class").size > 0)
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnErrorIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnErrorIT.kt
new file mode 100644
index 00000000..d44740af
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnErrorIT.kt
@@ -0,0 +1,111 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class OnErrorIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("on-error")
+
+ @Test
+ fun testOnError() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] Error processor: errored at 2", errors.first())
+ Assert.assertEquals("e: [ksp] NormalProcessor called error on 2", errors.last())
+ }
+ }
+
+ @Test
+ fun testOnExceptionInInit() {
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"init\") }\n")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in init", errors.first())
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+
+ @Test
+ fun testOnExceptionInProcess() {
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"process\") }\n")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in process", errors.first())
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+
+ @Test
+ fun testOnExceptionInFinish() {
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"finish\") }\n")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in finish", errors.first())
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+
+ @Test
+ fun testOnExceptionInOnError() {
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"error\") }\n")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+
+ Assert.assertEquals("e: [ksp] Error processor: errored at 2", errors.first())
+ Assert.assertEquals("e: [ksp] java.lang.Exception: Test Exception in error", errors[1])
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+
+ @Test
+ fun testCreateTwice() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)
+
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"createTwice\") }\n")
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+
+ Assert.assertTrue(
+ errors.any {
+ it.startsWith("e: [ksp] kotlin.io.FileAlreadyExistsException:")
+ }
+ )
+
+ Assert.assertFalse(result.output.contains("e: java.lang.IllegalStateException: Should not be called!"))
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+
+ @Test
+ fun testCreateTwiceNotOkOnError() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)
+
+ File(project.root, "workload/build.gradle.kts").appendText("\nksp { arg(\"exception\", \"createTwice\") }\n")
+ File(project.root, "gradle.properties").appendText("\nksp.return.ok.on.error=false")
+ gradleRunner.withArguments("clean", "assemble").buildAndFail().let { result ->
+ val errors = result.output.lines().filter { it.startsWith("e: [ksp]") }
+
+ Assert.assertTrue(
+ errors.any {
+ it.startsWith("e: [ksp] kotlin.io.FileAlreadyExistsException:")
+ }
+ )
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnlyResourcesFileIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnlyResourcesFileIT.kt
new file mode 100644
index 00000000..21b182a5
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OnlyResourcesFileIT.kt
@@ -0,0 +1,21 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Rule
+import org.junit.Test
+
+class OnlyResourcesFileIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("only-resources-file")
+
+ @Test
+ fun test() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments(
+ "--configuration-cache-problems=warn",
+ "jvmJar",
+ ).build()
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OutputDepsIt.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OutputDepsIt.kt
new file mode 100644
index 00000000..9c39e743
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/OutputDepsIt.kt
@@ -0,0 +1,157 @@
+package com.google.devtools.ksp.test
+
+import Artifact
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+
+class OutputDepsIt {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("output-deps")
+
+ val src2Dirty = listOf(
+ "workload/src/main/java/p1/J1.java" to setOf(
+ "w: [ksp] p1/J1.java",
+ "w: [ksp] p1/K1.kt",
+ "w: [ksp] p1/K2.kt",
+ ),
+ "workload/src/main/java/p1/J2.java" to setOf(
+ "w: [ksp] p1/J2.java",
+ ),
+ "workload/src/main/kotlin/p1/K1.kt" to setOf(
+ "w: [ksp] p1/J1.java",
+ "w: [ksp] p1/K1.kt",
+ "w: [ksp] p1/K2.kt",
+ ),
+ "workload/src/main/kotlin/p1/K2.kt" to setOf(
+ "w: [ksp] p1/J1.java",
+ "w: [ksp] p1/K1.kt",
+ "w: [ksp] p1/K2.kt",
+ ),
+ )
+
+ val src2Output = mapOf(
+ "workload/src/main/java/p1/J1.java" to setOf(
+ "kotlin/p1/J1Generated.kt",
+ "kotlin/p1/K1Generated.kt",
+ "kotlin/p1/K2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ "workload/src/main/java/p1/J2.java" to setOf(
+ "kotlin/p1/J2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ "workload/src/main/kotlin/p1/K1.kt" to setOf(
+ "kotlin/p1/J1Generated.kt",
+ "kotlin/p1/K1Generated.kt",
+ "kotlin/p1/K2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ "workload/src/main/kotlin/p1/K2.kt" to setOf(
+ "kotlin/p1/J1Generated.kt",
+ "kotlin/p1/K1Generated.kt",
+ "kotlin/p1/K2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ )
+
+ val deletedSrc2Output = listOf(
+ "workload/src/main/java/p1/J1.java" to listOf(
+ "kotlin/p1/Anno1Generated.kt",
+ "kotlin/p1/Anno2Generated.kt",
+ "kotlin/p1/J2Generated.kt",
+ "kotlin/p1/K1Generated.kt",
+ "kotlin/p1/K2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ "workload/src/main/java/p1/J2.java" to listOf(
+ "kotlin/p1/Anno1Generated.kt",
+ "kotlin/p1/Anno2Generated.kt",
+ "kotlin/p1/K1Generated.kt",
+ "kotlin/p1/K2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ "workload/src/main/kotlin/p1/K1.kt" to listOf(
+ "kotlin/p1/Anno1Generated.kt",
+ "kotlin/p1/Anno2Generated.kt",
+ "kotlin/p1/K2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ "workload/src/main/kotlin/p1/K2.kt" to listOf(
+ "kotlin/p1/Anno1Generated.kt",
+ "kotlin/p1/Anno2Generated.kt",
+ "resources/p1.Anno1.log",
+ "resources/p1.Anno2.log",
+ ),
+ )
+
+ @Test
+ fun testOutputDeps() {
+ // FIXME
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+ val cleanArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+
+ src2Dirty.forEach { (src, expectedDirties) ->
+ val srcFile = File(project.root, src)
+ // In case that the test goes faster than the precision of timestamps.
+ // It's 1s on Github's CI.
+ Thread.sleep(1000)
+ srcFile.appendText("\n\n")
+ Thread.sleep(1000)
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
+ Assert.assertEquals(expectedDirties, dirties)
+
+ val outputRoot = File(project.root, "workload/build/generated/ksp/main/")
+ outputRoot.walk().filter { it.isFile() }.forEach {
+ if (it.toRelativeString(outputRoot) in src2Output[src]!!) {
+ Assert.assertTrue(it.lastModified() > srcFile.lastModified())
+ } else {
+ Assert.assertTrue(it.lastModified() < srcFile.lastModified())
+ }
+ }
+ }
+ }
+ val incrementalArtifact = Artifact(File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar"))
+ Assert.assertEquals(cleanArtifact, incrementalArtifact)
+ }
+
+ @Test
+ fun testDeletion() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
+ }
+
+ deletedSrc2Output.forEach { (src, expectedDirties) ->
+ File(project.root, src).delete()
+ gradleRunner.withArguments("assemble").build().let { result ->
+ Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
+ val outputRoot = File(project.root, "workload/build/generated/ksp/main/")
+ val outputs = outputRoot.walk().filter { it.isFile() }.map {
+ it.toRelativeString(outputRoot).replace(File.separatorChar, '/')
+ }.toList().sorted()
+ Assert.assertEquals(expectedDirties, outputs)
+ }
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt
new file mode 100644
index 00000000..eeaaf502
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt
@@ -0,0 +1,210 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+import java.util.jar.*
+
+class PlaygroundIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("playground")
+
+ private fun GradleRunner.buildAndCheck(vararg args: String, extraCheck: (BuildResult) -> Unit = {}) =
+ buildAndCheckOutcome(*args, outcome = TaskOutcome.SUCCESS, extraCheck = extraCheck)
+
+ private fun GradleRunner.buildAndCheckOutcome(
+ vararg args: String,
+ outcome: TaskOutcome,
+ extraCheck: (BuildResult) -> Unit = {}
+ ) {
+ val result = this.withArguments(*args).build()
+
+ Assert.assertEquals(outcome, result.task(":workload:build")?.outcome)
+
+ val artifact = File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar")
+ Assert.assertTrue(artifact.exists())
+
+ JarFile(artifact).use { jarFile ->
+ Assert.assertTrue(jarFile.getEntry("TestProcessor.log").size > 0)
+ Assert.assertTrue(jarFile.getEntry("hello/HELLO.class").size > 0)
+ Assert.assertTrue(jarFile.getEntry("g/G.class").size > 0)
+ Assert.assertTrue(jarFile.getEntry("com/example/AClassBuilder.class").size > 0)
+ }
+
+ extraCheck(result)
+ }
+
+ @Test
+ fun testPlayground() {
+ // FIXME: `clean` fails to delete files on windows.
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.buildAndCheck("clean", "build")
+ gradleRunner.buildAndCheck("clean", "build")
+ }
+
+ // TODO: add another plugin and see if it is blocked.
+ // Or use a project that depends on a builtin plugin like all-open and see if the build fails
+ @Test
+ fun testBlockOtherCompilerPlugins() {
+ // FIXME: `clean` fails to delete files on windows.
+ Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows", ignoreCase = true))
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ File(project.root, "workload/build.gradle.kts")
+ .appendText("\nksp {\n blockOtherCompilerPlugins = false\n}\n")
+ gradleRunner.buildAndCheck("clean", "build")
+ gradleRunner.buildAndCheck("clean", "build")
+ project.restore("workload/build.gradle.kts")
+ }
+
+ @Test
+ fun testAllowSourcesFromOtherPlugins() {
+ fun checkGBuilder() {
+ val artifact = File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar")
+
+ JarFile(artifact).use { jarFile ->
+ Assert.assertTrue(jarFile.getEntry("g/GBuilder.class").size > 0)
+ }
+ }
+
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ File(project.root, "workload/build.gradle.kts")
+ .appendText("\nksp {\n allowSourcesFromOtherPlugins = true\n}\n")
+ gradleRunner.buildAndCheck("clean", "build") { checkGBuilder() }
+ gradleRunner.buildAndCheckOutcome("build", "--info", outcome = TaskOutcome.UP_TO_DATE) {
+ Assert.assertEquals(TaskOutcome.UP_TO_DATE, it.task(":workload:kspKotlin")?.outcome)
+ checkGBuilder()
+ }
+ project.restore("workload/build.gradle.kts")
+ }
+
+ /** Regression test for https://github.com/google/ksp/issues/518. */
+ @Test
+ fun testBuildWithConfigureOnDemand() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.buildAndCheck("--configure-on-demand", ":workload:build")
+ }
+
+ @Test
+ fun testBuildCache() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ // The first build can be FROM_CACHE or SUCCESS, and we only care about the second build.
+ gradleRunner.buildAndCheck("--build-cache", ":workload:clean", "build")
+ gradleRunner.buildAndCheck("--build-cache", ":workload:clean", "build") {
+ Assert.assertEquals(TaskOutcome.FROM_CACHE, it.task(":workload:kspKotlin")?.outcome)
+ }
+ }
+
+ @Test
+ fun testAllWarningsAsErrors() {
+ File(project.root, "workload/build.gradle.kts")
+ .appendText("\nksp {\n allWarningsAsErrors = true\n}\n")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.withArguments("build").buildAndFail().let { result ->
+ Assert.assertTrue(result.output.contains("This is a harmless warning."))
+ }
+ }
+
+ // Compiler's test infra report this kind of error before KSP, so it is not testable there.
+ @Test
+ fun testNoFunctionName() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ fun buildAndFileAndCheck() {
+ gradleRunner.withArguments("build").buildAndFail().let { result ->
+ Assert.assertTrue(result.output.contains("Function declaration must have a name"))
+ }
+ }
+
+ File(project.root, "workload/src/main/java/com/example/A.kt").appendText("\n{}\n")
+ buildAndFileAndCheck()
+ project.restore("workload/src/main/java/com/example/A.kt")
+
+ File(project.root, "workload/src/main/java/com/example/A.kt").appendText("\nfun() = {0}\n")
+ buildAndFileAndCheck()
+ project.restore("workload/src/main/java/com/example/A.kt")
+ }
+
+ @Test
+ fun testRewriteFile() {
+ File(
+ project.root,
+ "test-processor/src/main/resources/META-INF/services/" +
+ "com.google.devtools.ksp.processing.SymbolProcessorProvider"
+ ).writeText("RewriteProcessorProvider")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.withArguments("build").buildAndFail().let { result ->
+ Assert.assertTrue(result.output.contains("kotlin.io.FileAlreadyExistsException"))
+ }
+ }
+
+ // Disabled for now: ERROR: K2 does not support plugins yet, so please remove -Xuse-k2 flag
+ // Test -Xuse-fir for compilation; KSP still uses FE1.0
+ @Ignore
+ @Test
+ fun testFirPreview() {
+ val gradleProperties = File(project.root, "gradle.properties")
+ gradleProperties.appendText("\nkotlin.useK2=true")
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.buildAndCheck("clean", "build") { result ->
+ Assert.assertTrue(result.output.contains("This build uses experimental K2 compiler"))
+ Assert.assertTrue(result.output.contains("-Xuse-k2"))
+ }
+ project.restore(gradleProperties.path)
+ }
+
+ @Test
+ fun testVersions() {
+ val kotlinCompile = "org.jetbrains.kotlin.gradle.tasks.KotlinCompile"
+ val buildFile = File(project.root, "workload/build.gradle.kts")
+ buildFile.appendText("\ntasks.withType<$kotlinCompile> {")
+ buildFile.appendText("\n kotlinOptions.apiVersion = \"1.5\"")
+ buildFile.appendText("\n kotlinOptions.languageVersion = \"1.5\"")
+ buildFile.appendText("\n}")
+
+ val kotlinVersion = System.getProperty("kotlinVersion").split('-').first()
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.buildAndCheck("clean", "build") { result ->
+ Assert.assertTrue(result.output.contains("language version: 1.5"))
+ Assert.assertTrue(result.output.contains("api version: 1.5"))
+ Assert.assertTrue(result.output.contains("compiler version: $kotlinVersion"))
+ }
+ project.restore(buildFile.path)
+ }
+
+ @Test
+ fun testExcludeProcessor() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ File(project.root, "workload/build.gradle.kts")
+ .appendText("\nksp {\n excludeProcessor(\"TestProcessorProvider\")\n")
+ File(project.root, "workload/build.gradle.kts")
+ .appendText("\n excludeProcessor(\"NotMatchingAnything\")\n}\n")
+ gradleRunner.withArguments("build").buildAndFail().let {
+ Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.FAILED, it.task(":workload:compileKotlin")?.outcome)
+ Assert.assertTrue("Unresolved reference: AClassBuilder" in it.output)
+ }
+ gradleRunner.withArguments("build").buildAndFail().let {
+ Assert.assertEquals(TaskOutcome.UP_TO_DATE, it.task(":workload:kspKotlin")?.outcome)
+ Assert.assertEquals(TaskOutcome.FAILED, it.task(":workload:compileKotlin")?.outcome)
+ Assert.assertTrue("Unresolved reference: AClassBuilder" in it.output)
+ }
+
+ project.restore("workload/build.gradle.kts")
+ File(project.root, "workload/build.gradle.kts")
+ .appendText("\nksp {\n excludeProcessor(\"DoNotMatch\")\n}\n")
+ gradleRunner.buildAndCheck("build")
+
+ project.restore("workload/build.gradle.kts")
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PsiCacheIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PsiCacheIT.kt
new file mode 100644
index 00000000..9f3a46dd
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PsiCacheIT.kt
@@ -0,0 +1,18 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Rule
+import org.junit.Test
+
+class PsiCacheIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("psi-cache", "test-processor")
+
+ @Test
+ fun testPsiCache() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+
+ gradleRunner.withArguments("assemble").build()
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt
new file mode 100644
index 00000000..fea748f0
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt
@@ -0,0 +1,41 @@
+package com.google.devtools.ksp.test
+
+import org.junit.rules.TemporaryFolder
+import java.io.File
+
+class TemporaryTestProject(projectName: String, baseProject: String? = null) : TemporaryFolder() {
+ private val testProjectSrc = File("src/test/resources", projectName)
+ private val baseProjectSrc = baseProject?.let { File("src/test/resources", baseProject) }
+
+ override fun before() {
+ super.before()
+
+ baseProjectSrc?.copyRecursively(root)
+ testProjectSrc.copyRecursively(root, true)
+
+ val kotlinVersion = System.getProperty("kotlinVersion")
+ val kspVersion = System.getProperty("kspVersion")
+ val agpVersion = System.getProperty("agpVersion")
+ val testRepo = System.getProperty("testRepo").replace(File.separator, "/")
+ val gradleProperties = File(root, "gradle.properties")
+ gradleProperties.appendText("\nkotlinVersion=$kotlinVersion")
+ gradleProperties.appendText("\nkspVersion=$kspVersion")
+ gradleProperties.appendText("\nagpVersion=$agpVersion")
+ gradleProperties.appendText("\ntestRepo=$testRepo")
+ gradleProperties.appendText("\norg.gradle.unsafe.configuration-cache=true")
+ gradleProperties.appendText("\nkotlin.jvm.target.validation.mode=warning")
+ // Uncomment this to debug compiler and compiler plugin.
+ // gradleProperties.appendText("\nsystemProp.kotlin.compiler.execution.strategy=in-process")
+ }
+
+ fun restore(file: String) {
+ fun copySafe(src: File, dst: File) {
+ if (src.exists())
+ src.copyTo(dst, true)
+ }
+ baseProjectSrc?.let {
+ copySafe(File(baseProjectSrc, file), File(root, file))
+ }
+ copySafe(File(testProjectSrc, file), File(root, file))
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VerboseIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VerboseIT.kt
new file mode 100644
index 00000000..935cd39b
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VerboseIT.kt
@@ -0,0 +1,67 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.GradleRunner
+import org.gradle.testkit.runner.TaskOutcome
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import java.io.File
+import java.util.jar.JarFile
+
+class VerboseIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("playground")
+
+ private fun GradleRunner.buildAndCheck(vararg args: String, extraCheck: (BuildResult) -> Unit = {}) =
+ buildAndCheckOutcome(*args, outcome = TaskOutcome.SUCCESS, extraCheck = extraCheck)
+
+ private fun GradleRunner.buildAndCheckOutcome(
+ vararg args: String,
+ outcome: TaskOutcome,
+ extraCheck: (BuildResult) -> Unit = {}
+ ) {
+ val result = this.withArguments(*args).build()
+
+ Assert.assertEquals(outcome, result.task(":workload:build")?.outcome)
+
+ val artifact = File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar")
+ Assert.assertTrue(artifact.exists())
+
+ JarFile(artifact).use { jarFile ->
+ Assert.assertTrue(jarFile.getEntry("TestProcessor.log").size > 0)
+ Assert.assertTrue(jarFile.getEntry("hello/HELLO.class").size > 0)
+ Assert.assertTrue(jarFile.getEntry("g/G.class").size > 0)
+ Assert.assertTrue(jarFile.getEntry("com/example/AClassBuilder.class").size > 0)
+ }
+
+ extraCheck(result)
+ }
+
+ @Test
+ fun testNoProvider() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ File(
+ project.root,
+ "test-processor/src/main/resources/META-INF/services/" +
+ "com.google.devtools.ksp.processing.SymbolProcessorProvider"
+ ).delete()
+ gradleRunner.withArguments("build").buildAndFail().let { result ->
+ Assert.assertTrue(result.output.contains("No providers found in processor classpath."))
+ }
+ }
+
+ @Test
+ fun testProviderAndRoundLogging() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ gradleRunner.buildAndCheck("--debug", "clean", "build") { result ->
+ Assert.assertTrue(
+ result.output.contains(
+ "i: [ksp] loaded provider(s): [TestProcessorProvider, TestProcessorProvider2]"
+ )
+ )
+ Assert.assertTrue(result.output.contains("v: [ksp] round 3 of processing"))
+ }
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VersionCheckIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VersionCheckIT.kt
new file mode 100644
index 00000000..52a62a4d
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/VersionCheckIT.kt
@@ -0,0 +1,42 @@
+package com.google.devtools.ksp.test
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Assert
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore
+class VersionCheckIT {
+ @Rule
+ @JvmField
+ val project: TemporaryTestProject = TemporaryTestProject("playground")
+
+ @Test
+ fun testVersion() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ val result = gradleRunner.withArguments(
+ "-PkotlinVersion=1.4.20", "clean", "build"
+ ).buildAndFail()
+ Assert.assertTrue(result.output.contains("is too new for kotlin"))
+ }
+
+ @Test
+ fun testVersionOK() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ val result = gradleRunner.withArguments(
+ "clean", "build"
+ ).build()
+ Assert.assertFalse(result.output.contains("is too new for kotlin"))
+ Assert.assertFalse(result.output.contains("is too old for kotlin"))
+ }
+
+ @Test
+ fun testMuteVersionCheck() {
+ val gradleRunner = GradleRunner.create().withProjectDir(project.root)
+ val result = gradleRunner.withArguments(
+ "-PkotlinVersion=1.4.20", "-Pksp.version.check=false", "clean", "build"
+ ).buildAndFail()
+ Assert.assertFalse(result.output.contains("is too new for kotlin"))
+ }
+}
diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/fixtures/BuildResultFixture.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/fixtures/BuildResultFixture.kt
new file mode 100644
index 00000000..cb66ea1b
--- /dev/null
+++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/fixtures/BuildResultFixture.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2020 Google LLC
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.ksp.test.fixtures
+
+import org.gradle.testkit.runner.BuildResult
+
+private val compileKotlinSrcs = Regex("\\[KOTLIN\\] compile iteration: ([^\\r\\n]*)")
+
+class BuildResultFixture(private val result: BuildResult) {
+
+ /** Get all compiled Kotlin sources in the current build. */
+ val compiledKotlinSources by lazy {
+ compileKotlinSrcs.findAll(result.output)
+ .asIterable()
+ .flatMap {
+ it.groups[1]!!.value.split(", ")
+ }.toSet()
+ }
+}