diff options
Diffstat (limited to 'integration-tests/src/test/kotlin/com/google/devtools/ksp/test')
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() + } +} |