Revise incremental compilation failure handling tests

Revise BaseIncrementalCompilationMultiProjectIT's failure handling
tests to make it clear we have covered 2 important scenarios:
 - Failures caused by user errors: testFailureHandling_UserError
 - Failures caused by tool errors: testFailureHandling_ToolError

^KT-53231 In progress
This commit is contained in:
Hung Nguyen
2022-07-20 10:48:51 +01:00
committed by teamcity
parent e006a7af57
commit 0c5eda1382
3 changed files with 66 additions and 88 deletions
@@ -335,7 +335,7 @@ class BuildCacheRelocationIT : KGPBaseTest() {
@GradleTest @GradleTest
fun testKotlinIncrementalCompilation_withGradleClasspathSnapshot(gradleVersion: GradleVersion) { fun testKotlinIncrementalCompilation_withGradleClasspathSnapshot(gradleVersion: GradleVersion) {
checkKotlinIncrementalCompilationAfterCacheHit(gradleVersion, defaultBuildOptions.copy(useGradleClasspathSnapshot = true)) { checkKotlinIncrementalCompilationAfterCacheHit(gradleVersion, defaultBuildOptions.copy(useGradleClasspathSnapshot = true)) {
assertIncrementalCompilation(listOf("src/main/kotlin/foo.kt", "src/main/kotlin/fooUsage.kt")) assertIncrementalCompilation(listOf("src/main/kotlin/foo.kt", "src/main/kotlin/fooUsage.kt").toPaths())
} }
} }
@@ -343,7 +343,7 @@ class BuildCacheRelocationIT : KGPBaseTest() {
@GradleTest @GradleTest
fun testKotlinIncrementalCompilation_withICClasspathSnapshot(gradleVersion: GradleVersion) { fun testKotlinIncrementalCompilation_withICClasspathSnapshot(gradleVersion: GradleVersion) {
checkKotlinIncrementalCompilationAfterCacheHit(gradleVersion, defaultBuildOptions.copy(useICClasspathSnapshot = true)) { checkKotlinIncrementalCompilationAfterCacheHit(gradleVersion, defaultBuildOptions.copy(useICClasspathSnapshot = true)) {
assertIncrementalCompilation(listOf("src/main/kotlin/foo.kt", "src/main/kotlin/fooUsage.kt")) assertIncrementalCompilation(listOf("src/main/kotlin/foo.kt", "src/main/kotlin/fooUsage.kt").toPaths())
assertOutputContains("Incremental compilation with ABI snapshot enabled") assertOutputContains("Incremental compilation with ABI snapshot enabled")
} }
} }
@@ -2,9 +2,9 @@ package org.jetbrains.kotlin.gradle
import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.build.report.metrics.BuildAttribute
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.checkedReplace import org.jetbrains.kotlin.gradle.util.checkedReplace
import org.jetbrains.kotlin.test.KtAssert.assertTrue
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.* import kotlin.io.path.*
@@ -38,8 +38,7 @@ class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiPr
get() = "caches-js" get() = "caches-js"
//compileKotlin2Js's modification doe not work //compileKotlin2Js's modification doe not work
override fun testIncrementalBuildWithCompilationError(gradleVersion: GradleVersion) {} override fun testFailureHandling_ToolError(gradleVersion: GradleVersion) {}
override fun testValidOutputsWithCacheCorrupted(gradleVersion: GradleVersion) {}
} }
@JvmGradlePluginTests @JvmGradlePluginTests
@@ -502,44 +501,6 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
} }
} }
@DisplayName("compile error in lib project")
@GradleTest
fun testCompileErrorInLib(gradleVersion: GradleVersion) {
defaultProject(gradleVersion) {
build("assemble")
val bKt = subProject("lib").kotlinSourcesDir().resolve("bar/B.kt")
val bKtContent = bKt.readText()
bKt.deleteExisting()
fun runFailingBuild() {
buildAndFail("assemble") {
assertOutputContains("B.kt has been removed")
assertTasksFailed(":lib:$compileKotlinTaskName")
assertCompiledKotlinSources(
getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/barUseAB.kt", "bar/barUseB.kt")
),
output
)
}
}
runFailingBuild()
runFailingBuild()
bKt.writeText(bKtContent.replace("fun b", "open fun b"))
build("assemble") {
val expectedSources = getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/B.kt", "bar/barUseAB.kt", "bar/barUseB.kt"),
appSources = listOf("foo/BB.kt", "foo/fooUseB.kt")
)
assertCompiledKotlinSources(expectedSources, output)
}
}
}
@DisplayName("Remove library from classpath") @DisplayName("Remove library from classpath")
@GradleTest @GradleTest
fun testRemoveLibFromClasspath(gradleVersion: GradleVersion) { fun testRemoveLibFromClasspath(gradleVersion: GradleVersion) {
@@ -687,63 +648,80 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
} }
} }
@DisplayName("KT-49780: Valid outputs after cache corruption exception") @DisplayName("Test handling of failures caused by user errors")
@GradleTest @GradleTest
open fun testValidOutputsWithCacheCorrupted(gradleVersion: GradleVersion) { fun testFailureHandling_UserError(gradleVersion: GradleVersion) {
defaultProject(gradleVersion) { defaultProject(gradleVersion) {
breakCachesAfterCompileKotlinExecution(this) // Perform the first non-incremental build
build(":lib:compileKotlin")
build("assemble") // Make a compile error in the source code
var classAKtContents: String? = null
subProject("lib").kotlinSourcesDir().resolve("bar/B.kt").modify { subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify {
it.replace("fun b() {}", "fun b() = 123") classAKtContents = it
it.replace("fun a() {}", "fun a() { // Compile error: Missing closing bracket")
} }
build("assemble") { // In the next build, compilation should be incremental and fail, and not fall back to non-incremental compilation
assertOutputContains("Non-incremental compilation will be performed: INCREMENTAL_COMPILATION_FAILED") buildAndFail(":lib:compileKotlin") {
assertTasksFailed(":lib:$compileKotlinTaskName")
assertOutputContains("Compilation error. See log for more details")
assertOutputDoesNotContain("Non-incremental compilation will be performed: ${BuildAttribute.INCREMENTAL_COMPILATION_FAILED.name}")
} }
val lookupFile = projectPath.resolve("lib/build/kotlin/${compileKotlinTaskName}/cacheable/${compileCacheFolderName}/lookups/file-to-id.tab") // Fix the compile error in the source code
assertTrue("Output is empty", lookupFile.exists()) subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").writeText(classAKtContents!!)
changeMethodSignatureInLib()
} // In the next build, compilation should be incremental and succeed
} build(":lib:compileKotlin") {
assertIncrementalCompilation(
@DisplayName("KT-49780: No need to rebuild invalid code due to cache corruption") expectedCompiledKotlinFiles = getExpectedKotlinSourcesForDefaultProject(
@GradleTest libSources = listOf("bar/A.kt", "bar/B.kt", "bar/barUseA.kt")
open fun testIncrementalBuildWithCompilationError(gradleVersion: GradleVersion) { )
defaultProject(gradleVersion) { )
breakCachesAfterCompileKotlinExecution(this)
build("assemble")
subProject("lib").kotlinSourcesDir().resolve("bar/B.kt").modify {
it.replace("fun b() {}", "fun a() = 123")
}
buildAndFail("assemble") {
printBuildOutput()
assertOutputDoesNotContain("Possible caches corruption:")
assertOutputDoesNotContain("Non-incremental compilation will be performed:")
} }
} }
} }
private fun breakCachesAfterCompileKotlinExecution(testProject: TestProject) { @DisplayName("Test handling of failures caused by tool errors")
listOf("app", "lib").forEach { @GradleTest
testProject.subProject(it).buildGradle.appendText( open fun testFailureHandling_ToolError(gradleVersion: GradleVersion) {
""" defaultProject(gradleVersion) {
$compileKotlinTaskName { // Simulate a tool error by registering a doLast that breaks caches in the KotlinCompile task
doLast { val lookupFile =
def file = new File(projectDir.path, "/build/kotlin/${compileKotlinTaskName}/cacheable/${compileCacheFolderName}/lookups/file-to-id.tab") projectPath.resolve("lib/build/kotlin/${compileKotlinTaskName}/cacheable/${compileCacheFolderName}/lookups/file-to-id.tab")
println("Update lookup file " + file.path) breakCachesAfterKotlinCompile(subProject("lib"), lookupFile)
file.write("la-la")
} // Perform the first non-incremental build
} build(":lib:compileKotlin") {
""".trimIndent() // Caches should be in a corrupted state, which will ensure the next build will fail
) assertFileContains(lookupFile, "Invalid contents")
}
// Make a change in the source code
changeMethodSignatureInLib()
// In the next build, compilation should be incremental and fail, then fall back to non-incremental compilation and succeed
build(":lib:compileKotlin") {
assertOutputContains("Non-incremental compilation will be performed: ${BuildAttribute.INCREMENTAL_COMPILATION_FAILED.name}")
// Also check that the output is not deleted (regression test for KT-49780)
assertFileExists(lookupFile)
}
} }
} }
private fun breakCachesAfterKotlinCompile(gradleProject: GradleProject, lookupFile: Path) {
gradleProject.buildGradle.appendText(
"""
$compileKotlinTaskName {
doLast {
new File("$lookupFile").write("Invalid contents")
}
}
""".trimIndent()
)
}
} }
@@ -101,9 +101,9 @@ fun BuildResult.assertNonIncrementalCompilation() {
* *
* Note: Log level of output must be set to [LogLevel.DEBUG]. * Note: Log level of output must be set to [LogLevel.DEBUG].
*/ */
fun BuildResult.assertIncrementalCompilation(expectedCompiledKotlinFiles: Iterable<String>) { fun BuildResult.assertIncrementalCompilation(expectedCompiledKotlinFiles: Iterable<Path>) {
assertOutputDoesNotContain("Non-incremental compilation will be performed") assertOutputDoesNotContain("Non-incremental compilation will be performed")
val actualCompiledKotlinFiles = extractCompiledKotlinFiles(output) val actualCompiledKotlinFiles = extractCompiledKotlinFiles(output)
assertSameFiles(expectedCompiledKotlinFiles.toPaths(), actualCompiledKotlinFiles, "Compiled Kotlin files differ:\n") assertSameFiles(expectedCompiledKotlinFiles, actualCompiledKotlinFiles, "Compiled Kotlin files differ:\n")
} }