KT-45777: Refactor incremental compilation integration tests

to organize tested scenarios better and make it easier to see the
effects of the next change when the classpath snapshot feature is
enabled.
This commit is contained in:
Hung Nguyen
2021-08-25 09:47:37 +01:00
committed by nataliya.valtman
parent 3f857cdef1
commit e3d7b7a30e
4 changed files with 176 additions and 207 deletions
@@ -122,7 +122,7 @@ abstract class BaseGradleIT {
// gradle wrapper version to wrapper directory // gradle wrapper version to wrapper directory
private val gradleWrappers = hashMapOf<String, File>() private val gradleWrappers = hashMapOf<String, File>()
private const val MAX_DAEMON_RUNS = 30 private const val MAX_DAEMON_RUNS = 100
private const val MAX_ACTIVE_GRADLE_PROCESSES = 1 private const val MAX_ACTIVE_GRADLE_PROCESSES = 1
private fun getEnvJDK_18() = System.getenv()["JDK_18"] private fun getEnvJDK_18() = System.getenv()["JDK_18"]
@@ -162,6 +162,8 @@ abstract class BaseGradleIT {
} }
if (DaemonRegistry.runCountForDaemon(version) >= MAX_DAEMON_RUNS) { if (DaemonRegistry.runCountForDaemon(version) >= MAX_DAEMON_RUNS) {
// Warning: Stopping the Gradle daemon while the test suite has not finished running can break tests in weird ways.
// TODO: Find a safe way to do this or consider deleting this code.
stopDaemon(version, environmentVariables) stopDaemon(version, environmentVariables)
} }
@@ -756,7 +758,7 @@ Finished executing task ':$taskName'|
} }
fun CompiledProject.assertCompiledKotlinSources( fun CompiledProject.assertCompiledKotlinSources(
expectedSources: Iterable<String>, expectedSourcesRelativePaths: Iterable<String>,
weakTesting: Boolean = false, weakTesting: Boolean = false,
output: String = this.output, output: String = this.output,
suffix: String = "" suffix: String = ""
@@ -764,12 +766,15 @@ Finished executing task ':$taskName'|
val messagePrefix = "Compiled Kotlin files differ${suffix}:\n " val messagePrefix = "Compiled Kotlin files differ${suffix}:\n "
val actualSources = getCompiledKotlinSources(output).projectRelativePaths(this.project) val actualSources = getCompiledKotlinSources(output).projectRelativePaths(this.project)
return if (weakTesting) { return if (weakTesting) {
assertContainFiles(expectedSources, actualSources, messagePrefix) assertContainFiles(expectedSourcesRelativePaths, actualSources, messagePrefix)
} else { } else {
assertSameFiles(expectedSources, actualSources, messagePrefix) assertSameFiles(expectedSourcesRelativePaths, actualSources, messagePrefix)
} }
} }
fun CompiledProject.assertCompiledKotlinFiles(expectedFiles: Iterable<File>): CompiledProject =
assertCompiledKotlinSources(project.relativize(expectedFiles))
val Project.allKotlinFiles: Iterable<File> val Project.allKotlinFiles: Iterable<File>
get() = projectDir.allKotlinFiles() get() = projectDir.allKotlinFiles()
@@ -17,39 +17,46 @@ abstract class IncrementalCompilationBaseIT : BaseGradleIT() {
protected fun doTest( protected fun doTest(
fileToModify: String, fileToModify: String,
modifyFileContents: (String) -> String, modifyFileContents: (originalContents: String) -> String,
expectedAffectedFileNames: Collection<String>, expectedCompiledFileNames: Collection<String>,
) { ) {
doTest( doTest(
{ it.projectDir.getFileByName(fileToModify).modify(modifyFileContents) }, modifyProject = { projectDir.getFileByName(fileToModify).modify(modifyFileContents) },
expectedAffectedFileNames expectedCompiledFileNames = expectedCompiledFileNames
) )
} }
protected fun doTest( protected fun doTest(
modifyProject: (Project) -> Unit, project: Project = defaultProject(),
expectedAffectedFileNames: Collection<String>, task: String = "build",
options: BuildOptions = defaultBuildOptions(),
modifyProject: Project.() -> Unit,
expectedCompiledFileNames: Collection<String>,
) { ) {
doTest(defaultBuildOptions(), modifyProject, expectedAffectedFileNames) doTest(
project, task, options, modifyProject,
assertResults = {
assertCompiledKotlinFiles(project.projectDir.getFilesByNames(*expectedCompiledFileNames.toTypedArray()))
}
)
} }
protected fun doTest( protected fun doTest(
options: BuildOptions, project: Project = defaultProject(),
modifyProject: (Project) -> Unit, task: String = "build",
expectedAffectedFileNames: Collection<String>, options: BuildOptions = defaultBuildOptions(),
modifyProject: Project.() -> Unit,
assertResults: CompiledProject.() -> Unit
) { ) {
val project = defaultProject() project.build(task, options = options) {
project.build("build") {
assertSuccessful() assertSuccessful()
} }
modifyProject(project) modifyProject(project)
project.build("build", options = options) { project.build(task, options = options) {
assertSuccessful() assertSuccessful()
val expectedAffectedFiles = project.projectDir.getFilesByNames(*expectedAffectedFileNames.toTypedArray()) assertResults()
val expectedAffectedFileRelativePaths = project.relativize(expectedAffectedFiles)
assertCompiledKotlinSources(expectedAffectedFileRelativePaths)
} }
} }
} }
@@ -1,7 +1,6 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.gradle.util.*
import org.junit.Assert
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
@@ -91,15 +90,18 @@ open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationM
assertSuccessful() assertSuccessful()
} }
project.projectDir.getFileByName("barUseB.kt").delete() project.changeMethodBodyInLib()
project.build("build") { project.build("build") {
assertSuccessful() assertSuccessful()
val affectedSources = File(project.projectDir, "app").allKotlinFiles() assertCompiledKotlinFiles(testCompileLibWithGroovy_expectedFiles(project))
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths)
} }
} }
/** Expected files to be recompiled for [testCompileLibWithGroovy], which may be overridden in subclasses. */
open fun testCompileLibWithGroovy_expectedFiles(project: Project): Iterable<File> {
return File(project.projectDir, "app").allKotlinFiles() + File(project.projectDir, "lib").getFileByName("A.kt")
}
/** Regression test for KT-43489. Make sure build history mapping is not initialized too early. */ /** Regression test for KT-43489. Make sure build history mapping is not initialized too early. */
@Test @Test
fun testBuildHistoryMappingLazilyComputedWithWorkers() { fun testBuildHistoryMappingLazilyComputedWithWorkers() {
@@ -151,67 +153,109 @@ class IncrementalCompilationClasspathSnapshotJvmMultiProjectIT : IncrementalComp
abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilationBaseIT() { abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilationBaseIT() {
protected abstract val compileKotlinTaskName: String
protected abstract val additionalLibDependencies: String
protected val changeMethodSignatureInLib: Project.() -> Unit = {
File(projectDir, "lib").getFileByName("A.kt").modify {
it.replace("fun a() {}", "fun a(): Int = 1")
}
}
protected val changeMethodBodyInLib: Project.() -> Unit = {
File(projectDir, "lib").getFileByName("A.kt").modify {
it.replace("fun a() {}", "fun a() { println() }")
}
}
@Test @Test
fun testAbiChangeInLib_changeMethodSignature() { fun testAbiChangeInLib_changeMethodSignature() {
doTest( doTest(
"A.kt", modifyProject = changeMethodSignatureInLib,
{ it.replace("fun a() {}", "fun a(): Int = 1") }, expectedCompiledFileNames = listOf(
expectedAffectedFileNames = listOf("A.kt", "B.kt", "AA.kt", "AAA.kt", "BB.kt", "barUseA.kt", "fooUseA.kt") "A.kt", "B.kt", "barUseA.kt", // In lib
"AA.kt", "AAA.kt", "BB.kt", "fooUseA.kt" // In app
)
) )
} }
@Test @Test
fun testAbiChangeInLib_addNewMethod() { fun testAbiChangeInLib_addNewMethod() {
doTest( doTest(
"A.kt", modifyProject = {
{ it.replace("fun a() {}", "fun a() {}\nfun newA() {}") }, File(projectDir, "lib").getFileByName("A.kt").modify {
expectedAffectedFileNames = listOf("A.kt", "B.kt", "AA.kt", "AAA.kt", "BB.kt") it.replace("fun a() {}", "fun a() {}\nfun newA() {}")
}
},
expectedCompiledFileNames = listOf(
"A.kt", "B.kt", // In lib
"AA.kt", "AAA.kt", "BB.kt" // In app
)
) )
} }
@Test @Test
fun testNonAbiChangeInLib_changeMethodBody() { fun testNonAbiChangeInLib_changeMethodBody() {
doTest( doTest(
"A.kt", modifyProject = changeMethodBodyInLib,
{ it.replace("fun a() {}", "fun a() { println() }") }, expectedCompiledFileNames = listOf("A.kt") // In lib
expectedAffectedFileNames = listOf("A.kt")
) )
} }
@Test
fun testAddDependencyInLib() {
doTest(
modifyProject = {
File(projectDir, "lib/build.gradle").modify {
"""
$it
dependencies {
$additionalLibDependencies
}
""".trimIndent()
}
},
assertResults = {
assertCompiledKotlinFiles(testAddDependencyInLib_expectedFiles(project))
}
)
}
/** Expected files to be recompiled for [testAddDependencyInLib], which may be overridden in subclasses. */
open fun testAddDependencyInLib_expectedFiles(project: Project): Iterable<File> {
return File(project.projectDir, "lib").allKotlinFiles()
}
@Test
fun testAbiChangeInLib_afterLibClean() { // To see if app compilation can be incremental after non-incremental lib compilation
doTest(
modifyProject = {
build(":lib:clean") { assertSuccessful() }
changeMethodSignatureInLib()
},
assertResults = {
assertCompiledKotlinFiles(testAbiChangeInLib_afterLibClean_expectedFiles(project))
}
)
}
/** Expected files for [testAbiChangeInLib_afterLibClean], which may be overridden in subclasses. */
open fun testAbiChangeInLib_afterLibClean_expectedFiles(project: Project): Iterable<File> {
return project.projectDir.allKotlinFiles()
}
@Test @Test
fun testMoveFunctionFromLibToApp() { fun testMoveFunctionFromLibToApp() {
doTest( doTest(
{ project -> modifyProject = {
val barUseABKt = project.projectDir.getFileByName("barUseAB.kt") val barUseABKt = projectDir.getFileByName("barUseAB.kt")
val barInApp = File(project.projectDir, "app/src/main/kotlin/bar").apply { mkdirs() } val barInApp = File(projectDir, "app/src/main/kotlin/bar").apply { mkdirs() }
barUseABKt.copyTo(File(barInApp, barUseABKt.name)) barUseABKt.copyTo(File(barInApp, barUseABKt.name))
barUseABKt.delete() barUseABKt.delete()
}, },
expectedAffectedFileNames = listOf("fooCallUseAB.kt", "barUseAB.kt") expectedCompiledFileNames = listOf("fooCallUseAB.kt", "barUseAB.kt")
)
}
@Test
fun testAddNewMethodToLib() {
doTest(
options = defaultBuildOptions().copy(abiSnapshot = true),
{ project ->
val aKt = project.projectDir.getFileByName("A.kt")
aKt.writeText(
"""
package bar
open class A {
fun a() {}
fun newA() {}
}
"""
)
},
//TODO for abi-snapshot "BB.kt" should not be recompiled
expectedAffectedFileNames = listOf(
"A.kt", "B.kt", "AA.kt", "BB.kt", "AAA.kt"
)
) )
} }
@@ -239,139 +283,6 @@ open class A {
} }
} }
@Test
fun testCleanBuildLib() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
project.build(":lib:clean") {
assertSuccessful()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
//don't need to recompile app classes because lib's proto stays the same
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt))
}
}
@Test
fun testCleanBuildLibForAbiSnapshot() {
val options = defaultBuildOptions().copy(abiSnapshot = true)
val project = defaultProject()
project.build("build", options = options) {
assertSuccessful()
}
project.build(":lib:clean", options = options) {
assertSuccessful()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
//don't need to recompile app classes because lib's proto stays the same
project.build("build", options = options) {
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build", options = options) {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt))
}
}
protected abstract val additionalLibDependencies: String
protected abstract val compileKotlinTaskName: String
@Test
fun testAddMethodToLibForAbiSnapshot() {
val project = defaultProject()
val options = defaultBuildOptions().copy(abiSnapshot = true)
project.build("build", options = options) {
assertSuccessful()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
val barDummyClassFile = project.projectFile("BarDummy.kt")
barDummyClassFile.modify { "$it { fun m() = 42}" }
project.build("build", options = options) {
assertSuccessful()
val relativePaths = project.relativize(barDummyClassFile)
assertCompiledKotlinSources(relativePaths)
}
}
@Test
fun testAddDependencyToLib() {
val project = defaultProject()
project.build("build") {
assertSuccessful()
}
val libBuildGradle = File(project.projectDir, "lib/build.gradle")
Assert.assertTrue("$libBuildGradle does not exist", libBuildGradle.exists())
libBuildGradle.modify {
"""
$it
dependencies {
$additionalLibDependencies
}
""".trimIndent()
}
// Change file so Gradle won't skip :app:compile
project.projectFile("BarDummy.kt").modify {
it.replace("class BarDummy", "open class BarDummy")
}
project.build("build") {
assertSuccessful()
val affectedSources = project.projectDir.allKotlinFiles()
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths)
}
val aaKt = project.projectFile("AA.kt")
aaKt.modify { "$it " }
project.build("build") {
assertSuccessful()
assertCompiledKotlinSources(project.relativize(aaKt))
}
}
@Test @Test
fun testCompileErrorInLib() { fun testCompileErrorInLib() {
val project = defaultProject() val project = defaultProject()
@@ -439,12 +350,14 @@ open class A {
fun testMoveFunctionFromLibWithRemappedBuildDirs() { fun testMoveFunctionFromLibWithRemappedBuildDirs() {
val project = defaultProject() val project = defaultProject()
project.setupWorkingDir() project.setupWorkingDir()
project.projectDir.resolve("build.gradle").appendText(""" project.projectDir.resolve("build.gradle").appendText(
"""
allprojects { allprojects {
it.buildDir = new File(rootDir, "../out" + it.path.replace(":", "/") + "/build") it.buildDir = new File(rootDir, "../out" + it.path.replace(":", "/") + "/build")
} }
""".trimIndent()) """.trimIndent()
)
project.build("build") { project.build("build") {
assertSuccessful() assertSuccessful()
} }
@@ -461,5 +374,49 @@ open class A {
assertCompiledKotlinSources(relativePaths) assertCompiledKotlinSources(relativePaths)
} }
} }
}
@Test
fun testAbiChangeInLib_addNewMethod_withAbiSnapshot() {
doTest(
options = defaultBuildOptions().copy(abiSnapshot = true),
modifyProject = {
File(projectDir, "lib").getFileByName("A.kt").modify {
it.replace("fun a() {}", "fun a() {}\nfun newA() {}")
}
},
expectedCompiledFileNames = listOf(
"A.kt", "B.kt", // In lib
// TODO(valtman): for abi-snapshot "BB.kt" should not be recompiled
"AA.kt", "AAA.kt", "BB.kt" // In app
)
)
}
@Test
fun testAbiChangeInLib_afterLibClean_withAbiSnapshot() {
doTest(
options = defaultBuildOptions().copy(abiSnapshot = true),
modifyProject = {
build(":lib:clean") { assertSuccessful() }
changeMethodSignatureInLib()
},
assertResults = {
// TODO: With ABI snapshot, app compilation should be incremental, currently it is not.
assertCompiledKotlinFiles(testAbiChangeInLib_afterLibClean_expectedFiles(project))
}
)
}
@Test
fun testChangeIsolatedClassInLib_withAbiSnapshot() {
doTest(
options = defaultBuildOptions().copy(abiSnapshot = true),
modifyProject = {
File(projectDir, "lib").getFileByName("BarDummy.kt").modify {
"$it { fun m() = 42}"
}
},
expectedCompiledFileNames = listOf("BarDummy.kt") // In lib
)
}
}
@@ -10,12 +10,12 @@ import org.junit.Test
open class IncrementalJavaChangeDefaultIT : IncrementalCompilationJavaChangesBase(usePreciseJavaTracking = null) { open class IncrementalJavaChangeDefaultIT : IncrementalCompilationJavaChangesBase(usePreciseJavaTracking = null) {
@Test @Test
override fun testAbiChangeInLib_changeMethodSignature_tracked() { override fun testAbiChangeInLib_changeMethodSignature_tracked() {
doTest(trackedJavaClass, changeSignature, expectedAffectedFileNames = listOf("TrackedJavaClassChild.kt", "useTrackedJavaClass.kt")) doTest(trackedJavaClass, changeSignature, expectedCompiledFileNames = listOf("TrackedJavaClassChild.kt", "useTrackedJavaClass.kt"))
} }
@Test @Test
override fun testNonAbiChangeInLib_changeMethodBody_tracked() { override fun testNonAbiChangeInLib_changeMethodBody_tracked() {
doTest(trackedJavaClass, changeBody, expectedAffectedFileNames = listOf()) doTest(trackedJavaClass, changeBody, expectedCompiledFileNames = listOf())
} }
} }
@@ -26,12 +26,12 @@ class IncrementalJavaChangeClasspathSnapshotIT : IncrementalJavaChangeDefaultIT(
class IncrementalJavaChangePreciseIT : IncrementalCompilationJavaChangesBase(usePreciseJavaTracking = true) { class IncrementalJavaChangePreciseIT : IncrementalCompilationJavaChangesBase(usePreciseJavaTracking = true) {
@Test @Test
override fun testAbiChangeInLib_changeMethodSignature_tracked() { override fun testAbiChangeInLib_changeMethodSignature_tracked() {
doTest(trackedJavaClass, changeSignature, expectedAffectedFileNames = listOf("TrackedJavaClassChild.kt", "useTrackedJavaClass.kt")) doTest(trackedJavaClass, changeSignature, expectedCompiledFileNames = listOf("TrackedJavaClassChild.kt", "useTrackedJavaClass.kt"))
} }
@Test @Test
override fun testNonAbiChangeInLib_changeMethodBody_tracked() { override fun testNonAbiChangeInLib_changeMethodBody_tracked() {
doTest(trackedJavaClass, changeBody, expectedAffectedFileNames = listOf()) doTest(trackedJavaClass, changeBody, expectedCompiledFileNames = listOf())
} }
} }
@@ -40,7 +40,7 @@ open class IncrementalJavaChangeDisablePreciseIT : IncrementalCompilationJavaCha
override fun testAbiChangeInLib_changeMethodSignature_tracked() { override fun testAbiChangeInLib_changeMethodSignature_tracked() {
doTest( doTest(
trackedJavaClass, changeSignature, trackedJavaClass, changeSignature,
expectedAffectedFileNames = listOf( expectedCompiledFileNames = listOf(
"TrackedJavaClassChild.kt", "useTrackedJavaClass.kt", "useTrackedJavaClassFooMethodUsage.kt", "TrackedJavaClassChild.kt", "useTrackedJavaClass.kt", "useTrackedJavaClassFooMethodUsage.kt",
"useTrackedJavaClassSameModule.kt" "useTrackedJavaClassSameModule.kt"
) )
@@ -51,7 +51,7 @@ open class IncrementalJavaChangeDisablePreciseIT : IncrementalCompilationJavaCha
override fun testNonAbiChangeInLib_changeMethodBody_tracked() { override fun testNonAbiChangeInLib_changeMethodBody_tracked() {
doTest( doTest(
trackedJavaClass, changeBody, trackedJavaClass, changeBody,
expectedAffectedFileNames = listOf( expectedCompiledFileNames = listOf(
"TrackedJavaClassChild.kt", "useTrackedJavaClass.kt", "useTrackedJavaClassFooMethodUsage.kt", "TrackedJavaClassChild.kt", "useTrackedJavaClass.kt", "useTrackedJavaClassFooMethodUsage.kt",
"useTrackedJavaClassSameModule.kt" "useTrackedJavaClassSameModule.kt"
) )
@@ -77,8 +77,8 @@ abstract class IncrementalCompilationJavaChangesBase(val usePreciseJavaTracking:
@Test @Test
fun testAbiChangeInLib_changeMethodSignature() { fun testAbiChangeInLib_changeMethodSignature() {
doTest( doTest(
javaClass, changeBody, javaClass, changeSignature,
expectedAffectedFileNames = listOf("JavaClassChild.kt", "useJavaClass.kt", "useJavaClassFooMethodUsage.kt") expectedCompiledFileNames = listOf("JavaClassChild.kt", "useJavaClass.kt", "useJavaClassFooMethodUsage.kt")
) )
} }
@@ -86,7 +86,7 @@ abstract class IncrementalCompilationJavaChangesBase(val usePreciseJavaTracking:
fun testNonAbiChangeInLib_changeMethodBody() { fun testNonAbiChangeInLib_changeMethodBody() {
doTest( doTest(
javaClass, changeBody, javaClass, changeBody,
expectedAffectedFileNames = listOf("JavaClassChild.kt", "useJavaClass.kt", "useJavaClassFooMethodUsage.kt") expectedCompiledFileNames = listOf("JavaClassChild.kt", "useJavaClass.kt", "useJavaClassFooMethodUsage.kt")
) )
} }