Migrate KaptIncrementalIT to new test DSL

^KT-45745 In Progress
This commit is contained in:
Yahor Berdnikau
2022-01-27 11:28:51 +01:00
committed by TeamCityServer
parent a0cb7c8173
commit 9ca2ce6647
@@ -1,58 +1,66 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode import org.gradle.api.logging.configuration.WarningMode
import org.jetbrains.kotlin.gradle.util.* import org.gradle.testkit.runner.BuildResult
import org.junit.Test import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import java.nio.file.Path
import java.util.*
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText
import kotlin.test.assertEquals import kotlin.test.assertEquals
open class KaptIncrementalIT : BaseGradleIT() { @DisplayName("Kapt incremental compilation")
@OtherGradlePluginTests
open class KaptIncrementalIT : KGPBaseTest() {
companion object { companion object {
private val EXAMPLE_ANNOTATION_REGEX = "@(field:)?example.ExampleAnnotation".toRegex() private val EXAMPLE_ANNOTATION_REGEX = "@(field:)?example.ExampleAnnotation".toRegex()
const val PROJECT_NAME = "kaptIncrementalCompilationProject"
const val KAPT3_STUBS_PATH = "build/tmp/kapt3/stubs/main"
} }
open fun getProject() =
Project(
"kaptIncrementalCompilationProject",
GradleVersionRequired.None
).apply { setupWorkingDir() }
private val annotatedElements = private val annotatedElements =
arrayOf("A", "funA", "valA", "funUtil", "valUtil", "B", "funB", "valB", "useB") arrayOf("A", "funA", "valA", "funUtil", "valUtil", "B", "funB", "valB", "useB")
override fun defaultBuildOptions(): BuildOptions { override val defaultBuildOptions = super.defaultBuildOptions.copy(
return super.defaultBuildOptions().copy(incremental = true, warningMode = WarningMode.Fail) incremental = true,
} warningMode = WarningMode.Fail,
kaptOptions = BuildOptions.KaptOptions(incrementalKapt = true)
)
@Test @DisplayName("After adding new line compilation is incremental")
fun testAddNewLine() { @GradleTest
val project = getProject() fun testAddNewLine(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) {
build("clean", "build")
project.build("clean", "build") { javaSourcesDir().resolve("bar/useB.kt").modify { "\n$it" }
assertSuccessful() build("build") {
}
project.projectFile("useB.kt").modify { "\n$it" }
project.build("build") {
assertSuccessful()
assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin") assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin")
assertTasksUpToDate(":kaptKotlin") assertTasksUpToDate(":kaptKotlin")
assertTasksUpToDate(":compileJava") assertTasksUpToDate(":compileJava")
} }
} }
@Test
fun testBasic() {
val project = getProject()
project.build("build") {
assertSuccessful()
checkGenerated(*annotatedElements)
checkNotGenerated("notAnnotatedFun")
assertContains("foo.ATest PASSED")
} }
project.build("build") { @DisplayName("On rebuild without changes tasks should be UP-TO-DATE")
assertSuccessful() @GradleTest
fun testBasic(gradleVersion: GradleVersion) {
project(
PROJECT_NAME,
gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
) {
build("build") {
checkGenerated(kaptGeneratedToPath, *annotatedElements)
checkNotGenerated(kaptGeneratedToPath, "notAnnotatedFun")
assertOutputContains("foo.ATest PASSED")
}
build("build") {
assertTasksUpToDate( assertTasksUpToDate(
":compileKotlin", ":compileKotlin",
":compileJava" ":compileJava"
@@ -64,159 +72,156 @@ open class KaptIncrementalIT : BaseGradleIT() {
) )
} }
} }
@Test
fun testCompileError() {
val project = getProject()
project.build("build") {
assertSuccessful()
} }
val bKt = project.projectDir.getFileByName("B.kt") @DisplayName("Successfully rebuild after error")
@GradleTest
fun testCompileError(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) {
build("assemble")
val bKt = javaSourcesDir().resolve("bar/B.kt")
val errorKt = bKt.resolveSibling("error.kt") val errorKt = bKt.resolveSibling("error.kt")
errorKt.writeText("<COMPILE_ERROR_MARKER>") errorKt.writeText("<COMPILE_ERROR_MARKER>")
project.build("build") { buildAndFail("assemble") {
assertFailed()
assertTasksFailed(":kaptGenerateStubsKotlin") assertTasksFailed(":kaptGenerateStubsKotlin")
} }
errorKt.delete() errorKt.deleteIfExists()
bKt.modify { "$it\n" } bKt.modify { "$it\n" }
project.build("build") { build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertSuccessful() assertCompiledKotlinSources(listOf(projectPath.relativize(bKt)), output)
assertCompiledKotlinSources(project.relativize(bKt))
assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin") assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin")
} }
} }
@Test
fun testChangeFunctionBodyWithoutChangingSignature() {
val project = getProject()
project.build("build") {
assertSuccessful()
checkGenerated(*annotatedElements)
checkNotGenerated("notAnnotatedFun")
assertContains("foo.ATest PASSED")
} }
val utilKt = project.projectDir.getFileByName("util.kt") @DisplayName("Change in the function body without changing the signature")
@GradleTest
fun testChangeFunctionBodyWithoutChangingSignature(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) {
build("build", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
checkGenerated(kaptGeneratedToPath, *annotatedElements)
checkNotGenerated(kaptGeneratedToPath, "notAnnotatedFun")
assertOutputContains("foo.ATest PASSED")
}
val utilKt = javaSourcesDir().resolve("baz/util.kt")
utilKt.modify { oldContent -> utilKt.modify { oldContent ->
assert(oldContent.contains("2 * 2 == 4")) assert(oldContent.contains("2 * 2 == 4"))
oldContent.replace("2 * 2 == 4", "2 * 2 == 5") oldContent.replace("2 * 2 == 4", "2 * 2 == 5")
} }
project.build("build") { build("assemble") {
assertSuccessful()
assertTasksExecuted(":kaptGenerateStubsKotlin") assertTasksExecuted(":kaptGenerateStubsKotlin")
assertTasksUpToDate(":kaptKotlin") assertTasksUpToDate(":kaptKotlin")
} }
} }
@Test
fun testAddAnnotatedElement() {
val project = getProject()
project.build("build") {
assertSuccessful()
} }
val utilKt = project.projectDir.getFileByName("util.kt") @DisplayName("Adding new annotated element")
@GradleTest
fun testAddAnnotatedElement(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) {
build("assemble")
val utilKt = javaSourcesDir().resolve("baz/util.kt")
utilKt.modify { oldContent -> utilKt.modify { oldContent ->
"""$oldContent """
$oldContent
@example.ExampleAnnotation @example.ExampleAnnotation
fun newUtilFun() {}""" fun newUtilFun() {}
""".trimIndent()
} }
project.build("build") { build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertSuccessful()
assertKapt3FullyExecuted() assertKapt3FullyExecuted()
// todo: for kapt with stubs check compileKotlin and compileKotlinAfterJava separately assertCompiledKotlinSourcesHandleKapt3(this, listOf(projectPath.relativize(utilKt)))
assertCompiledKotlinSourcesHandleKapt3(project.relativize(utilKt)) checkGenerated(kaptGeneratedToPath, *(annotatedElements + arrayOf("newUtilFun")))
checkGenerated(*(annotatedElements + arrayOf("newUtilFun"))) }
} }
} }
@Test @DisplayName("Adding new annotation triggers kapt run")
fun testAddAnnotation() { @GradleTest
val project = getProject() fun testAddAnnotation(gradleVersion: GradleVersion) {
project.build("build") { project(PROJECT_NAME, gradleVersion) {
assertSuccessful() build("assemble")
val utilKt = javaSourcesDir().resolve("baz/util.kt")
utilKt.modify {
it.replace("fun notAnnotatedFun", "@example.ExampleAnnotation fun notAnnotatedFun")
} }
val utilKt = project.projectDir.getFileByName("util.kt") build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
utilKt.modify { it.replace("fun notAnnotatedFun", "@example.ExampleAnnotation fun notAnnotatedFun") }
project.build("build") {
assertSuccessful()
assertKapt3FullyExecuted() assertKapt3FullyExecuted()
assertCompiledKotlinSources(project.relativize(utilKt)) assertCompiledKotlinSources(listOf(projectPath.relativize(utilKt)), output)
checkGenerated(*(annotatedElements + arrayOf("notAnnotatedFun"))) checkGenerated(kaptGeneratedToPath, *(annotatedElements + arrayOf("notAnnotatedFun")))
}
} }
} }
@Test @DisplayName("Kapt run is incremental after source file was removed")
fun testRemoveSourceFile() { @GradleTest
val project = getProject() fun testRemoveSourceFile(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) {
val kapt3IncDataPath = "build/tmp/kapt3/incrementalData/main" val kapt3IncDataPath = "build/tmp/kapt3/incrementalData/main"
val kapt3StubsPath = "build/tmp/kapt3/stubs/main" val kapt3StubsPath = "build/tmp/kapt3/stubs/main"
project.build("build") { build("assemble") {
assertSuccessful()
assertKapt3FullyExecuted() assertKapt3FullyExecuted()
assertFileExists("$kapt3IncDataPath/bar/B.class") assertFileInProjectExists("$kapt3IncDataPath/bar/B.class")
assertFileExists("$kapt3IncDataPath/bar/UseBKt.class") assertFileInProjectExists("$kapt3IncDataPath/bar/UseBKt.class")
assertFileExists("$kapt3StubsPath/bar/B.java") assertFileInProjectExists("$kapt3StubsPath/bar/B.java")
assertFileExists("$kapt3StubsPath/bar/B.kapt_metadata") assertFileInProjectExists("$kapt3StubsPath/bar/B.kapt_metadata")
assertFileExists("$kapt3StubsPath/bar/UseBKt.java") assertFileInProjectExists("$kapt3StubsPath/bar/UseBKt.java")
assertFileExists("$kapt3StubsPath/bar/UseBKt.kapt_metadata") assertFileInProjectExists("$kapt3StubsPath/bar/UseBKt.kapt_metadata")
} }
with(project.projectDir) { with(javaSourcesDir()) {
getFileByName("B.kt").delete() resolve("bar/B.kt").deleteIfExists()
getFileByName("useB.kt").delete() resolve("bar/useB.kt").deleteIfExists()
} }
project.build("build") { buildAndFail("assemble") {
assertFailed() assertFileInProjectNotExists("$kapt3IncDataPath/bar/B.class")
assertFileInProjectNotExists("$kapt3IncDataPath/bar/UseBKt.class")
assertNoSuchFile("$kapt3IncDataPath/bar/B.class") assertFileInProjectNotExists("$kapt3StubsPath/bar/B.java")
assertNoSuchFile("$kapt3IncDataPath/bar/UseBKt.class") assertFileInProjectNotExists("$kapt3StubsPath/bar/B.kaptMetadata")
assertNoSuchFile("$kapt3StubsPath/bar/B.java") assertFileInProjectNotExists("$kapt3StubsPath/bar/UseBKt.java")
assertNoSuchFile("$kapt3StubsPath/bar/B.kaptMetadata") assertFileInProjectNotExists("$kapt3StubsPath/bar/UseBKt.kaptMetadata")
assertNoSuchFile("$kapt3StubsPath/bar/UseBKt.java")
assertNoSuchFile("$kapt3StubsPath/bar/UseBKt.kaptMetadata")
} }
project.projectDir.getFileByName("JavaClass.java").delete() javaSourcesDir().resolve("foo/JavaClass.java").deleteIfExists()
project.build("build") { build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertSuccessful()
assertKapt3FullyExecuted() assertKapt3FullyExecuted()
assertCompiledKotlinSourcesHandleKapt3(project.relativize(project.projectDir.allKotlinFiles())) assertCompiledKotlinSourcesHandleKapt3(
this,
projectPath.allKotlinFiles.map { projectPath.relativize(it) }
)
val affectedElements = arrayOf("B", "funB", "valB", "useB") val affectedElements = arrayOf("B", "funB", "valB", "useB")
checkGenerated(*(annotatedElements.toSet() - affectedElements).toTypedArray()) checkGenerated(kaptGeneratedToPath, *(annotatedElements.toSet() - affectedElements).toTypedArray())
checkNotGenerated(*affectedElements) checkNotGenerated(kaptGeneratedToPath, *affectedElements)
}
} }
} }
@Test @DisplayName("Incremental kapt run is correct after removing all Kotlin sources")
fun testRemoveAllKotlinSources() { @GradleTest
val project = getProject() fun testRemoveAllKotlinSources(gradleVersion: GradleVersion) {
val kapt3StubsPath = "build/tmp/kapt3/stubs/main" project(PROJECT_NAME, gradleVersion) {
build("assemble") {
project.build("build") { assertFileInProjectExists("$KAPT3_STUBS_PATH/bar/UseBKt.java")
assertSuccessful()
assertFileExists("$kapt3StubsPath/bar/UseBKt.java")
} }
with(project.projectDir) { with(projectPath) {
resolve("src/").deleteRecursively() resolve("src/").deleteRecursively()
resolve("src/main/java/bar").mkdirs() resolve("src/main/java/bar").createDirectories()
resolve("src/main/java/bar/MyClass.java").writeText( resolve("src/main/java/bar/MyClass.java").writeText(
""" """
package bar; package bar;
@@ -225,114 +230,150 @@ open class KaptIncrementalIT : BaseGradleIT() {
) )
} }
project.build("build") { build("assemble") {
assertSuccessful()
// Make sure all generated stubs are removed (except for NonExistentClass). // Make sure all generated stubs are removed (except for NonExistentClass).
assertEquals( assertEquals(
listOf(fileInWorkingDir("$kapt3StubsPath/error/NonExistentClass.java").canonicalPath), listOf(projectPath.resolve("$KAPT3_STUBS_PATH/error/NonExistentClass.java").toRealPath().toString()),
fileInWorkingDir(kapt3StubsPath).walk().filter { it.extension == "java" }.map { it.canonicalPath }.toList() projectPath
.resolve(KAPT3_STUBS_PATH)
.toFile()
.walk()
.filter { it.extension == "java" }
.map { it.canonicalPath }
.toList()
) )
// Make sure all compiled kt files are cleaned up. // Make sure all compiled kt files are cleaned up.
assertEquals(emptyList(), fileInWorkingDir("build/classes/kotlin").walk().filter { it.extension == "class" }.toList()) assertEquals(
emptyList(),
projectPath
.resolve("build/classes/kotlin")
.toFile()
.walk()
.filter { it.extension == "class" }
.toList()
)
}
} }
} }
@Test @DisplayName("On all annotations remove kapt and compile runs incremenatally")
fun testRemoveAnnotations() { @GradleTest
val project = getProject() fun testRemoveAnnotations(gradleVersion: GradleVersion) {
project.build("build") { project(PROJECT_NAME, gradleVersion) {
assertSuccessful() build("assemble")
}
val bKt = project.projectDir.getFileByName("B.kt") val bKt = javaSourcesDir().resolve("bar/B.kt")
bKt.modify { it.replace(EXAMPLE_ANNOTATION_REGEX, "") } bKt.modify { it.replace(EXAMPLE_ANNOTATION_REGEX, "") }
val affectedElements = arrayOf("B", "funB", "valB") val affectedElements = arrayOf("B", "funB", "valB")
project.build("build") { build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertSuccessful()
assertKapt3FullyExecuted() assertKapt3FullyExecuted()
val useBKt = project.projectDir.getFileByName("useB.kt") val useBKt = javaSourcesDir().resolve("bar/useB.kt")
assertCompiledKotlinSources(project.relativize(bKt, useBKt), tasks = listOf("kaptGenerateStubsKotlin")) assertCompiledKotlinSources(
listOf(projectPath.relativize(bKt), projectPath.relativize(useBKt)),
getOutputForTask("kaptGenerateStubs"),
errorMessageSuffix = " in task 'kaptGenerateStubs'"
)
// java removal is detected // java removal is detected
assertCompiledKotlinSources( assertCompiledKotlinSources(
project.relativize(project.projectDir.allKotlinFiles()), projectPath.allKotlinFiles.map { projectPath.relativize(it) },
tasks = listOf("compileKotlin") output
) )
checkGenerated(*(annotatedElements.toSet() - affectedElements).toTypedArray()) checkGenerated(
checkNotGenerated(*affectedElements) kaptGeneratedToPath,
*(annotatedElements.toSet() - affectedElements).toTypedArray()
)
checkNotGenerated(kaptGeneratedToPath, *affectedElements)
}
} }
} }
@Test @DisplayName("Changing annotated property type is handled correctly")
fun testChangeAnnotatedPropertyType() { @GradleTest
val project = getProject() fun testChangeAnnotatedPropertyType(gradleVersion: GradleVersion) {
project.build("build") { project(PROJECT_NAME, gradleVersion) {
assertSuccessful() build("assemble")
}
val bKt = project.projectDir.getFileByName("B.kt") val bKt = javaSourcesDir().resolve("bar/B.kt")
val useBKt = project.projectDir.getFileByName("useB.kt") val useBKt = javaSourcesDir().resolve("bar/useB.kt")
bKt.modify { it.replace("val valB = \"text\"", "val valB = 4") } bKt.modify { it.replace("val valB = \"text\"", "val valB = 4") }
project.build("build") { build("assemble", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertSuccessful()
assertKapt3FullyExecuted() assertKapt3FullyExecuted()
assertCompiledKotlinSourcesHandleKapt3(project.relativize(bKt, useBKt)) assertCompiledKotlinSourcesHandleKapt3(
checkGenerated(*annotatedElements) this,
listOf(bKt, useBKt).map { projectPath.relativize(it) }
)
checkGenerated(kaptGeneratedToPath, *annotatedElements)
}
} }
} }
@Test @DisplayName("Change in inline delegate is handled correctly")
fun testChangeInlineDelegate() { @GradleTest
val project = getProject() fun testChangeInlineDelegate(gradleVersion: GradleVersion) {
project.build("build") { project(PROJECT_NAME, gradleVersion) {
assertSuccessful() build("assemble")
}
val file = project.projectDir.getFileByName("Usage.kt") val file = javaSourcesDir().resolve("delegate/Usage.kt")
file.modify { "$it//" } file.modify { "$it//" }
project.build("build") { build("assemble") {
assertSuccessful()
assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin") assertTasksExecuted(":kaptGenerateStubsKotlin", ":compileKotlin")
} }
} }
}
private fun CompiledProject.assertCompiledKotlinSourcesHandleKapt3( private fun TestProject.assertCompiledKotlinSourcesHandleKapt3(
sources: Iterable<String>, buildResult: BuildResult,
weakTesting: Boolean = false sources: List<Path>
) { ) {
assertCompiledKotlinSources( assertCompiledKotlinSources(
sources, weakTesting, sources,
tasks = listOf("compileKotlin", "kaptGenerateStubsKotlin") buildResult.getOutputForTask("kaptGenerateStubsKotlin"),
errorMessageSuffix = " in task 'kaptGenerateStubsKotlin"
)
assertCompiledKotlinSources(
sources,
buildResult.getOutputForTask("compileKotlin"),
errorMessageSuffix = " in task 'compileKotlin'"
) )
} }
private fun CompiledProject.assertKapt3FullyExecuted() { private fun BuildResult.assertKapt3FullyExecuted() {
assertTasksExecuted(":kaptKotlin", ":kaptGenerateStubsKotlin") assertTasksExecuted(":kaptKotlin", ":kaptGenerateStubsKotlin")
} }
private fun CompiledProject.checkGenerated(vararg annotatedElementNames: String) { private fun TestProject.checkGenerated(
generateToPath: Path,
vararg annotatedElementNames: String
) {
getGeneratedFileNames(*annotatedElementNames).forEach { getGeneratedFileNames(*annotatedElementNames).forEach {
val file = project.projectDir.getFileByName(it) assertFileExistsInTree(generateToPath, it)
assert(file.isFile) { "$file must exist" }
} }
} }
private fun CompiledProject.checkNotGenerated(vararg annotatedElementNames: String) { private fun TestProject.checkNotGenerated(
generateToPath: Path,
vararg annotatedElementNames: String
) {
getGeneratedFileNames(*annotatedElementNames).forEach { getGeneratedFileNames(*annotatedElementNames).forEach {
val file = project.projectDir.findFileByName(it) assertFileNotExistsInTree(generateToPath, it)
assert(file == null) { "$file must not exist" }
} }
} }
private fun getGeneratedFileNames(vararg annotatedElementNames: String): Iterable<String> { @OptIn(ExperimentalStdlibApi::class)
val names = annotatedElementNames.map { it.capitalize() + "Generated" } private fun getGeneratedFileNames(vararg annotatedElementNames: String) =
return names.map { it + ".java" } annotatedElementNames
.map { name ->
name.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
} + "Generated.java"
} }
val TestProject.kaptGeneratedToPath get() = projectPath.resolve("build/generated/source/kapt")
} }