Migrate IncrementalCompilationMultiProjectIT test to new dsl

^KT-45745 In Progress
This commit is contained in:
Yahor Berdnikau
2022-02-21 16:58:53 +01:00
committed by teamcity
parent a5c1febf88
commit 14dc20186d
2 changed files with 527 additions and 368 deletions
@@ -16,8 +16,9 @@ abstract class IncrementalCompilationBaseIT : KGPBaseTest() {
protected abstract val defaultProjectName: String protected abstract val defaultProjectName: String
fun defaultProject( open fun defaultProject(
gradleVersion: GradleVersion, gradleVersion: GradleVersion,
buildOptions: BuildOptions = defaultBuildOptions,
test: TestProject.() -> Unit = {} test: TestProject.() -> Unit = {}
): TestProject = project( ): TestProject = project(
defaultProjectName, defaultProjectName,
@@ -1,24 +1,29 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.jetbrains.kotlin.gradle.util.* import org.gradle.testkit.runner.BuildResult
import org.junit.Test import org.gradle.util.GradleVersion
import java.io.File import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.checkedReplace
import org.junit.jupiter.api.DisplayName
import java.nio.file.Path
import kotlin.io.path.*
@JsGradlePluginTests
class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() { class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() {
override fun defaultProject(): Project { override val defaultProjectName: String = "incrementalMultiproject"
val project = Project("incrementalMultiproject")
project.setupWorkingDir()
for (subProject in arrayOf("app", "lib")) { override fun defaultProject(
val subProjectDir = project.projectDir.resolve(subProject) gradleVersion: GradleVersion,
subProjectDir.resolve("src/main/java").deleteRecursively() buildOptions: BuildOptions,
val buildGradle = subProjectDir.resolve("build.gradle") test: TestProject.() -> Unit
val buildJsGradle = subProjectDir.resolve("build-js.gradle") ): TestProject = project(defaultProjectName, gradleVersion) {
buildJsGradle.copyTo(buildGradle, overwrite = true) listOf("app", "lib").forEach {
buildJsGradle.delete() val subProject = subProject(it)
subProject.javaSourcesDir().deleteRecursively()
val buildGradleJs = subProject.projectPath.resolve("build-js.gradle")
subProject.buildGradle.writeText(buildGradleJs.readText())
buildGradleJs.deleteExisting()
} }
return project
} }
override val additionalLibDependencies: String = override val additionalLibDependencies: String =
@@ -28,6 +33,7 @@ class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiPr
get() = "compileKotlin2Js" get() = "compileKotlin2Js"
} }
@JvmGradlePluginTests
open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() { open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() {
override val additionalLibDependencies: String = override val additionalLibDependencies: String =
"implementation \"org.jetbrains.kotlin:kotlin-test:${'$'}kotlin_version\"" "implementation \"org.jetbrains.kotlin:kotlin-test:${'$'}kotlin_version\""
@@ -35,43 +41,52 @@ open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationM
override val compileKotlinTaskName: String override val compileKotlinTaskName: String
get() = "compileKotlin" get() = "compileKotlin"
override fun defaultProject(): Project = override val defaultProjectName: String = "incrementalMultiproject"
Project("incrementalMultiproject")
// todo: do the same for js backend // todo: do the same for js backend
@Test @DisplayName("Duplicated class")
fun testDuplicatedClass() { @GradleTest
val project = Project("duplicatedClass") fun testDuplicatedClass(gradleVersion: GradleVersion) {
project.build("build") { project("duplicatedClass", gradleVersion) {
assertSuccessful() build("assemble")
}
val usagesFiles = listOf("useBuzz.kt", "useA.kt").map { project.projectFile(it) } val usagesFiles = listOf("useBuzz.kt", "useA.kt").map {
subProject("app").kotlinSourcesDir().resolve(it)
}
usagesFiles.forEach { file -> file.modify { "$it\n " } } usagesFiles.forEach { file -> file.modify { "$it\n " } }
project.build("build") { build("assemble") {
assertSuccessful() assertCompiledKotlinSources(
assertCompiledKotlinSources(project.relativize(usagesFiles)) usagesFiles.map { it.relativeTo(projectPath) },
output
)
}
} }
} }
// checks that multi-project ic is disabled when there is a task that outputs to javaDestination dir @DisplayName(
// that is not JavaCompile or KotlinCompile "checks that multi-project ic is disabled when there is a task that outputs to javaDestination dir " +
@Test "that is not JavaCompile or KotlinCompile"
open fun testCompileLibWithGroovy() { )
testCompileLibWithGroovy_doTest { @GradleTest
assertCompiledKotlinFiles( open fun testCompileLibWithGroovy(gradleVersion: GradleVersion) {
File(project.projectDir, "app").allKotlinFiles() + File(project.projectDir, "lib").getFileByName("A.kt") testCompileLibWithGroovy_doTest(gradleVersion) { project, result ->
val expectedSources = project.subProject("app").projectPath.resolve("src").allKotlinSources +
listOf(project.subProject("lib").kotlinSourcesDir().resolve("bar/A.kt"))
assertCompiledKotlinSources(
expectedSources.map { it.relativeTo(project.projectPath) },
result.output
) )
} }
} }
protected fun testCompileLibWithGroovy_doTest(assertResults: CompiledProject.() -> Unit) { protected fun testCompileLibWithGroovy_doTest(
val project = defaultProject() gradleVersion: GradleVersion,
project.setupWorkingDir() assertResults: (TestProject, BuildResult) -> Unit
val lib = File(project.projectDir, "lib") ) {
val libBuildGradle = File(lib, "build.gradle") defaultProject(gradleVersion) {
libBuildGradle.modify { subProject("lib").buildGradle.modify {
""" """
plugins { plugins {
id 'groovy' id 'groovy'
@@ -85,33 +100,33 @@ open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationM
""".trimIndent() """.trimIndent()
} }
val libGroovySrcBar = File(lib, "src/main/groovy/bar").apply { mkdirs() } val libGroovySrcBar = subProject("lib")
val groovyClass = File(libGroovySrcBar, "GroovyClass.groovy") .projectPath
.resolve("src/main/groovy/bar")
.apply { createDirectories() }
val groovyClass = libGroovySrcBar.resolve("GroovyClass.groovy")
groovyClass.writeText( groovyClass.writeText(
""" """
package bar package bar
class GroovyClass {} class GroovyClass {}
""" """.trimIndent()
) )
project.build("build") { build("assemble")
assertSuccessful()
}
project.changeMethodBodyInLib() changeMethodBodyInLib()
project.build("build") { build("build") {
assertSuccessful() assertResults(this@defaultProject, this)
assertResults() }
} }
} }
/** Regression test for KT-43489. Make sure build history mapping is not initialized too early. */ @DisplayName("KT-43489: Make sure build history mapping is not initialized too early")
@Test @GradleTest
fun testBuildHistoryMappingLazilyComputedWithWorkers() { fun testBuildHistoryMappingLazilyComputedWithWorkers(gradleVersion: GradleVersion) {
val project = defaultProject() defaultProject(gradleVersion) {
project.setupWorkingDir() subProject("app").buildGradle.appendText(
project.projectDir.resolve("app/build.gradle").appendText(
""" """
// added to force eager configuration // added to force eager configuration
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
@@ -119,12 +134,10 @@ open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationM
} }
""".trimIndent() """.trimIndent()
) )
val options = defaultBuildOptions().copy(parallelTasksInProject = true)
project.build(options = options, params = arrayOf("build")) {
assertSuccessful()
}
val aKt = project.projectDir.getFileByName("A.kt") build("assemble")
val aKt = subProject("lib").kotlinSourcesDir().resolve("bar/A.kt")
aKt.writeText( aKt.writeText(
""" """
package bar package bar
@@ -133,94 +146,114 @@ open class A {
fun a() {} fun a() {}
fun newA() {} fun newA() {}
} }
""" """.trimIndent()
) )
project.build(options = options, params = arrayOf("build")) { build("assemble") {
assertSuccessful() val expectedSources = getExpectedKotlinSourcesForDefaultProject(
val affectedSources = project.projectDir.getFilesByNames("A.kt", "B.kt", "AA.kt", "AAA.kt", "BB.kt") libSources = listOf("bar/A.kt", "bar/B.kt"),
val relativePaths = project.relativize(affectedSources) appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt")
assertCompiledKotlinSources(relativePaths) )
assertCompiledKotlinSources(expectedSources, output)
}
} }
} }
} }
class IncrementalCompilationFirJvmMultiProjectIT : IncrementalCompilationJvmMultiProjectIT() { class IncrementalCompilationFirJvmMultiProjectIT : IncrementalCompilationJvmMultiProjectIT() {
override fun defaultBuildOptions(): BuildOptions { override val defaultBuildOptions: BuildOptions = super.defaultBuildOptions.copy(useFir = true)
return super.defaultBuildOptions().copy(useFir = true)
}
} }
class IncrementalCompilationClasspathSnapshotJvmMultiProjectIT : IncrementalCompilationJvmMultiProjectIT() { class IncrementalCompilationClasspathSnapshotJvmMultiProjectIT : IncrementalCompilationJvmMultiProjectIT() {
override fun defaultBuildOptions() = super.defaultBuildOptions().copy(useClasspathSnapshot = true) override val defaultBuildOptions = super.defaultBuildOptions.copy(useGradleClasspathSnapshot = true)
@Test @DisplayName("Lib: Non ABI change in method body")
override fun testNonAbiChangeInLib_changeMethodBody() { @GradleTest
doTest( override fun testNonAbiChangeInLib_changeMethodBody(gradleVersion: GradleVersion) {
modifyProject = changeMethodBodyInLib, defaultProject(gradleVersion) {
assertResults = { build("assemble")
assertTasksExecuted(":lib:$compileKotlinTaskName")
assertTasksUpToDate(":app:$compileKotlinTaskName") // App compilation has 'compile avoidance'
assertCompiledKotlinFiles(File(project.projectDir, "lib").getFilesByNames("A.kt"))
}
)
}
@Test changeMethodBodyInLib()
override fun testAddDependencyInLib() {
doTest( build("assemble") {
modifyProject = { testAddDependencyInLib_modifyProject() },
assertResults = {
assertTasksExecuted(":lib:$compileKotlinTaskName") assertTasksExecuted(":lib:$compileKotlinTaskName")
assertTasksUpToDate(":app:$compileKotlinTaskName") assertTasksUpToDate(":app:$compileKotlinTaskName")
assertCompiledKotlinFiles(emptyList()) // Lib compilation is incremental (no files are recompiled) assertCompiledKotlinSources(
} getExpectedKotlinSourcesForDefaultProject(libSources = listOf("bar/A.kt")),
output
) )
} }
}
}
@Test @DisplayName("Add dependency in lib subproject")
override fun testAbiChangeInLib_afterLibClean() { @GradleTest
doTest( override fun testAddDependencyInLib(gradleVersion: GradleVersion) {
modifyProject = { defaultProject(gradleVersion) {
build(":lib:clean") { assertSuccessful() } build("assemble")
changeMethodSignatureInLib()
},
assertResults = {
assertCompiledKotlinFiles(
// App compilation is incremental
File(project.projectDir, "app").getFilesByNames("AA.kt", "AAA.kt", "BB.kt", "fooUseA.kt") +
File(project.projectDir, "lib").allKotlinFiles()
)
}
)
}
@Test testAddDependencyInLib_modifyProject()
override fun testCompileLibWithGroovy() {
testCompileLibWithGroovy_doTest { build("assemble") {
assertTasksExecuted(":lib:$compileKotlinTaskName") assertTasksExecuted(":lib:$compileKotlinTaskName")
assertTasksUpToDate(":app:$compileKotlinTaskName") // App compilation has 'compile avoidance' assertTasksUpToDate(":app:$compileKotlinTaskName")
assertCompiledKotlinFiles(listOf(File(project.projectDir, "lib").getFileByName("A.kt"))) // Lib compilation is incremental (no files are recompiled)
assertCompiledKotlinSources(emptyList(), output)
}
} }
} }
@Test @DisplayName("after lib project clean")
override fun testAbiChangeInLib_afterLibClean_withAbiSnapshot() { @GradleTest
doTest( override fun testAbiChangeInLib_afterLibClean(gradleVersion: GradleVersion) {
options = defaultBuildOptions().copy(abiSnapshot = true), defaultProject(gradleVersion) {
modifyProject = { build("assemble")
build(":lib:clean") { assertSuccessful() }
build(":lib:clean")
changeMethodSignatureInLib() changeMethodSignatureInLib()
},
assertResults = { build("assemble") {
assertCompiledKotlinFiles( val expectedSources = getExpectedKotlinSourcesForDefaultProject(
// App compilation is incremental appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt", "foo/fooUseA.kt")
File(project.projectDir, "app").getFilesByNames("AA.kt", "AAA.kt", "BB.kt", "fooUseA.kt") + ) + subProject("lib").projectPath.resolve("src").allKotlinSources.map { it.relativeTo(projectPath) }
File(project.projectDir, "lib").allKotlinFiles()
assertCompiledKotlinSources(expectedSources, output)
}
}
}
@DisplayName("Compile lib with Groovy")
@GradleTest
override fun testCompileLibWithGroovy(gradleVersion: GradleVersion) {
testCompileLibWithGroovy_doTest(gradleVersion) { project, result ->
result.assertTasksExecuted(":lib:$compileKotlinTaskName")
result.assertTasksUpToDate(":app:$compileKotlinTaskName") // App compilation has 'compile avoidance'
assertCompiledKotlinSources(
project.getExpectedKotlinSourcesForDefaultProject(libSources = listOf("bar/A.kt")),
result.output
) )
} }
) }
@DisplayName("Lib: after cleaning lib project")
@GradleTest
override fun testAbiChangeInLib_afterLibClean_withAbiSnapshot(gradleVersion: GradleVersion) {
defaultProject(gradleVersion) {
build("assemble")
build(":lib:clean")
changeMethodSignatureInLib()
build("assemble") {
val expectedSources = getExpectedKotlinSourcesForDefaultProject(
appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt", "foo/fooUseA.kt")
) + subProject("lib").projectPath.resolve("src").allKotlinSources.map { it.relativeTo(projectPath) }
assertCompiledKotlinSources(expectedSources, output)
}
}
} }
} }
@@ -230,68 +263,124 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
protected abstract val additionalLibDependencies: String protected abstract val additionalLibDependencies: String
protected val changeMethodSignatureInLib: Project.() -> Unit = { protected fun TestProject.changeMethodSignatureInLib() {
File(projectDir, "lib").getFileByName("A.kt").modify { subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify {
it.replace("fun a() {}", "fun a(): Int = 1") it.replace("fun a() {}", "fun a(): Int = 1")
} }
} }
protected val changeMethodBodyInLib: Project.() -> Unit = { protected fun TestProject.changeMethodBodyInLib() {
File(projectDir, "lib").getFileByName("A.kt").modify { subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify {
it.replace("fun a() {}", "fun a() { println() }") it.replace("fun a() {}", "fun a() { println() }")
} }
} }
@Test protected fun TestProject.getExpectedKotlinSourcesForDefaultProject(
fun testAbiChangeInLib_changeMethodSignature() { libSources: List<String> = emptyList(),
doTest( appSources: List<String> = emptyList()
modifyProject = changeMethodSignatureInLib, ): Iterable<Path> {
expectedCompiledFileNames = listOf( val expectedLibSources = if (libSources.isNotEmpty()) {
"A.kt", "B.kt", "barUseA.kt", // In lib sourceFilesRelativeToProject(
"AA.kt", "AAA.kt", "BB.kt", "fooUseA.kt" // In app libSources,
) sourcesDir = { kotlinSourcesDir() },
subProjectName = "lib"
) )
} else {
emptyList()
} }
@Test val expectedAppSources = if (appSources.isNotEmpty()) {
fun testAbiChangeInLib_addNewMethod() { sourceFilesRelativeToProject(
doTest( appSources,
modifyProject = { sourcesDir = { kotlinSourcesDir() },
File(projectDir, "lib").getFileByName("A.kt").modify { subProjectName = "app"
)
} else {
emptyList()
}
return expectedLibSources + expectedAppSources
}
@DisplayName("Lib: method signature ABI change")
@GradleTest
fun testAbiChangeInLib_changeMethodSignature(gradleVersion: GradleVersion) {
defaultProject(gradleVersion) {
build("assemble")
changeMethodSignatureInLib()
build("assemble") {
val expectedSources = getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/A.kt", "bar/B.kt", "bar/barUseA.kt"),
appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt", "foo/fooUseA.kt")
)
assertCompiledKotlinSources(expectedSources, output)
}
}
}
@DisplayName("Lib: add new method changing ABI")
@GradleTest
fun testAbiChangeInLib_addNewMethod(gradleVersion: GradleVersion) {
defaultProject(gradleVersion) {
build("assemble")
subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify {
it.replace("fun a() {}", "fun a() {}\nfun newA() {}") it.replace("fun a() {}", "fun a() {}\nfun newA() {}")
} }
},
expectedCompiledFileNames = listOf( build("assemble") {
"A.kt", "B.kt", // In lib val expectedSources = getExpectedKotlinSourcesForDefaultProject(
"AA.kt", "AAA.kt", "BB.kt" // In app libSources = listOf("bar/A.kt", "bar/B.kt"),
) appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt")
) )
assertCompiledKotlinSources(expectedSources, output)
}
}
} }
@Test @DisplayName("Lib: change method body with non-ABI change")
open fun testNonAbiChangeInLib_changeMethodBody() { @GradleTest
doTest( open fun testNonAbiChangeInLib_changeMethodBody(gradleVersion: GradleVersion) {
modifyProject = changeMethodBodyInLib, defaultProject(gradleVersion) {
assertResults = { build("assemble")
assertCompiledKotlinFiles(File(project.projectDir, "lib").getFilesByNames("A.kt"))
} changeMethodBodyInLib()
build("assemble") {
assertCompiledKotlinSources(
getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/A.kt")
),
output
) )
} }
}
}
@Test @DisplayName("Add new dependency in lib project")
open fun testAddDependencyInLib() { @GradleTest
doTest( open fun testAddDependencyInLib(gradleVersion: GradleVersion) {
modifyProject = { testAddDependencyInLib_modifyProject() }, defaultProject(gradleVersion) {
assertResults = { build("assemble")
testAddDependencyInLib_modifyProject()
build("assemble") {
assertTasksExecuted(":lib:$compileKotlinTaskName") assertTasksExecuted(":lib:$compileKotlinTaskName")
assertTasksUpToDate(":app:$compileKotlinTaskName") assertTasksUpToDate(":app:$compileKotlinTaskName")
assertCompiledKotlinFiles(File(project.projectDir, "lib").allKotlinFiles()) assertCompiledKotlinSources(
} subProject("lib").projectPath.resolve("src").allKotlinSources.relativizeTo(projectPath),
output
) )
} }
}
}
protected fun Project.testAddDependencyInLib_modifyProject() { protected fun TestProject.testAddDependencyInLib_modifyProject() {
File(projectDir, "lib/build.gradle").modify { subProject("lib").buildGradle.modify {
""" """
$it $it
@@ -302,75 +391,98 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
} }
} }
@Test @DisplayName("ABI change in lib after lib clean")
open fun testAbiChangeInLib_afterLibClean() { // To see if app compilation can be incremental after non-incremental lib compilation @GradleTest
doTest( open fun testAbiChangeInLib_afterLibClean(gradleVersion: GradleVersion) {
modifyProject = { // To see if app compilation can be incremental after non-incremental lib compilation
build(":lib:clean") { assertSuccessful() } defaultProject(gradleVersion) {
build("assemble")
build(":lib:clean")
changeMethodSignatureInLib() changeMethodSignatureInLib()
},
assertResults = { build("assemble") {
// App compilation is non-incremental assertCompiledKotlinSources(
assertCompiledKotlinFiles(project.projectDir.allKotlinFiles()) subProject("lib")
} .projectPath
.resolve("src")
.allKotlinSources
.relativizeTo(projectPath) +
subProject("app")
.projectPath
.resolve("src")
.allKotlinSources
.relativizeTo(projectPath),
output
) )
} }
}
}
@Test @DisplayName("Move function from lib module into app module")
fun testMoveFunctionFromLibToApp() { @GradleTest
doTest( fun testMoveFunctionFromLibToApp(gradleVersion: GradleVersion) {
modifyProject = { defaultProject(gradleVersion) {
val barUseABKt = projectDir.getFileByName("barUseAB.kt") build("assemble")
val barInApp = File(projectDir, "app/src/main/kotlin/bar").apply { mkdirs() }
barUseABKt.copyTo(File(barInApp, barUseABKt.name)) val origFile = subProject("lib").kotlinSourcesDir().resolve("bar/barUseAB.kt")
barUseABKt.delete() subProject("app").kotlinSourcesDir().run {
}, resolve("bar").createDirectory()
expectedCompiledFileNames = listOf("fooCallUseAB.kt", "barUseAB.kt") origFile.copyTo(resolve("bar/barUseAB.kt"))
origFile.deleteExisting()
}
build("assemble") {
assertCompiledKotlinSources(
getExpectedKotlinSourcesForDefaultProject(
appSources = listOf("foo/fooCallUseAB.kt", "bar/barUseAB.kt")
),
output
) )
} }
}
@Test
fun testLibClassBecameFinal() {
// TODO: fix fir IC and remove
if (defaultBuildOptions().useFir) return
val project = defaultProject()
project.build("build") {
assertSuccessful()
} }
val bKt = project.projectDir.getFileByName("B.kt") @DisplayName("Lib project classes became final")
bKt.modify { it.replace("open class", "class") } @GradleTest
fun testLibClassBecameFinal(gradleVersion: GradleVersion) {
defaultProject(gradleVersion) {
build("assemble")
project.build("build") { subProject("lib").kotlinSourcesDir().resolve("bar/B.kt").modify {
assertFailed() it.replace("open class", "class")
val affectedSources = project.projectDir.getFilesByNames( }
"B.kt", "barUseAB.kt", "barUseB.kt",
"BB.kt", "fooCallUseAB.kt", "fooUseB.kt" buildAndFail("assemble") {
val expectedSources = getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/B.kt", "bar/barUseAB.kt", "bar/barUseB.kt"),
appSources = listOf("foo/BB.kt", "foo/fooCallUseAB.kt", "foo/fooUseB.kt")
) )
val relativePaths = project.relativize(affectedSources) assertCompiledKotlinSources(expectedSources, output)
assertCompiledKotlinSources(relativePaths) }
} }
} }
@Test @DisplayName("compile error in lib project")
fun testCompileErrorInLib() { @GradleTest
val project = defaultProject() fun testCompileErrorInLib(gradleVersion: GradleVersion) {
project.build("build") { defaultProject(gradleVersion) {
assertSuccessful() build("assemble")
}
val bKt = project.projectDir.getFileByName("B.kt") val bKt = subProject("lib").kotlinSourcesDir().resolve("bar/B.kt")
val bKtContent = bKt.readText() val bKtContent = bKt.readText()
bKt.delete() bKt.deleteExisting()
fun runFailingBuild() { fun runFailingBuild() {
project.build("build") { buildAndFail("assemble") {
assertFailed() assertOutputContains("B.kt has been removed")
assertContains("B.kt has been removed")
assertTasksFailed(":lib:$compileKotlinTaskName") assertTasksFailed(":lib:$compileKotlinTaskName")
val affectedFiles = project.projectDir.getFilesByNames("barUseAB.kt", "barUseB.kt") assertCompiledKotlinSources(
assertCompiledKotlinSources(project.relativize(affectedFiles)) getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/barUseAB.kt", "bar/barUseB.kt")
),
output
)
} }
} }
@@ -379,48 +491,56 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
bKt.writeText(bKtContent.replace("fun b", "open fun b")) bKt.writeText(bKtContent.replace("fun b", "open fun b"))
project.build("build") { build("assemble") {
assertSuccessful() val expectedSources = getExpectedKotlinSourcesForDefaultProject(
val affectedFiles = project.projectDir.getFilesByNames( libSources = listOf("bar/B.kt", "bar/barUseAB.kt", "bar/barUseB.kt"),
"B.kt", "barUseAB.kt", "barUseB.kt", appSources = listOf("foo/BB.kt", "foo/fooUseB.kt")
"BB.kt", "fooUseB.kt"
) )
assertCompiledKotlinSources(project.relativize(affectedFiles)) assertCompiledKotlinSources(expectedSources, output)
}
} }
} }
@Test @DisplayName("Remove library from classpath")
fun testRemoveLibFromClasspath() { @GradleTest
val project = defaultProject() fun testRemoveLibFromClasspath(gradleVersion: GradleVersion) {
project.build("build") { defaultProject(gradleVersion) {
assertSuccessful() build("assemble")
val appBuildGradleContent = subProject("app").buildGradle.readText()
subProject("app").buildGradle.modify { it.checkedReplace("implementation project(':lib')", "") }
val aaKt = subProject("app").kotlinSourcesDir().resolve("foo/AA.kt")
aaKt.modify {
"""
$it
""".trimIndent()
} }
val appBuildGradle = project.projectDir.resolve("app/build.gradle") buildAndFail("assemble")
val appBuildGradleContent = appBuildGradle.readText()
appBuildGradle.modify { it.checkedReplace("implementation project(':lib')", "") }
val aaKt = project.projectDir.getFileByName("AA.kt")
aaKt.addNewLine()
project.build("build") { subProject("app").buildGradle.writeText(appBuildGradleContent)
assertFailed() aaKt.modify {
"""
$it
""".trimIndent()
} }
appBuildGradle.writeText(appBuildGradleContent) build("assemble") {
aaKt.addNewLine() assertCompiledKotlinSources(
listOf(aaKt.relativeTo(projectPath)),
project.build("build") { output
assertSuccessful() )
assertCompiledKotlinSources(project.relativize(aaKt)) }
} }
} }
/** Regression test for KT-40875. */ @DisplayName("KT-40875: move function from lib with remapped build dirs")
@Test @GradleTest
fun testMoveFunctionFromLibWithRemappedBuildDirs() { fun testMoveFunctionFromLibWithRemappedBuildDirs(gradleVersion: GradleVersion) {
val project = defaultProject() defaultProject(gradleVersion) {
project.setupWorkingDir() buildGradle.appendText(
project.projectDir.resolve("build.gradle").appendText(
""" """
allprojects { allprojects {
@@ -428,65 +548,103 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation
} }
""".trimIndent() """.trimIndent()
) )
project.build("build") {
assertSuccessful() build("assemble")
val barUseABKt = subProject("lib").kotlinSourcesDir().resolve("bar/barUseAB.kt")
subProject("app").kotlinSourcesDir().run {
resolve("bar").createDirectory()
barUseABKt.copyTo(resolve("bar/barUseAB.kt"))
barUseABKt.deleteExisting()
} }
val barUseABKt = project.projectDir.getFileByName("barUseAB.kt") build("assemble") {
val barInApp = File(project.projectDir, "app/src/main/kotlin/bar").apply { mkdirs() } assertCompiledKotlinSources(
barUseABKt.copyTo(File(barInApp, barUseABKt.name)) getExpectedKotlinSourcesForDefaultProject(
barUseABKt.delete() appSources = listOf("foo/fooCallUseAB.kt", "bar/barUseAB.kt")
),
project.build("build") { output
assertSuccessful() )
val affectedSources = project.projectDir.getFilesByNames("fooCallUseAB.kt", "barUseAB.kt") }
val relativePaths = project.relativize(affectedSources)
assertCompiledKotlinSources(relativePaths)
} }
} }
@Test @DisplayName("Lib with ABI snapshot: add new ABI method")
fun testAbiChangeInLib_addNewMethod_withAbiSnapshot() { @GradleTest
doTest( fun testAbiChangeInLib_addNewMethod_withAbiSnapshot(gradleVersion: GradleVersion) {
options = defaultBuildOptions().copy(abiSnapshot = true), defaultProject(
modifyProject = { gradleVersion,
File(projectDir, "lib").getFileByName("A.kt").modify { buildOptions = defaultBuildOptions.copy(useGradleClasspathSnapshot = true)
) {
build("assemble")
subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify {
it.replace("fun a() {}", "fun a() {}\nfun newA() {}") it.replace("fun a() {}", "fun a() {}\nfun newA() {}")
} }
},
expectedCompiledFileNames = listOf( build("assemble") {
"A.kt", "B.kt", // In lib val expectedSources = getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/A.kt", "bar/B.kt"),
// TODO(valtman): for abi-snapshot "BB.kt" should not be recompiled // TODO(valtman): for abi-snapshot "BB.kt" should not be recompiled
"AA.kt", "AAA.kt", "BB.kt" // In app appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt")
)
) )
assertCompiledKotlinSources(expectedSources, output)
}
}
} }
@Test @DisplayName("Lib with abi snapshot: after clean build")
open fun testAbiChangeInLib_afterLibClean_withAbiSnapshot() { @GradleTest
doTest( open fun testAbiChangeInLib_afterLibClean_withAbiSnapshot(gradleVersion: GradleVersion) {
options = defaultBuildOptions().copy(abiSnapshot = true), defaultProject(
modifyProject = { gradleVersion,
build(":lib:clean") { assertSuccessful() } buildOptions = defaultBuildOptions.copy(useGradleClasspathSnapshot = true)
) {
build("assemble")
build(":lib:clean")
changeMethodSignatureInLib() changeMethodSignatureInLib()
},
assertResults = { build("assemble") {
// TODO: With ABI snapshot, app compilation should be incremental, currently it is not. // TODO: With ABI snapshot, app compilation should be incremental, currently it is not.
assertCompiledKotlinFiles(project.projectDir.allKotlinFiles()) assertCompiledKotlinSources(
} (subProject("lib")
.projectPath
.resolve("src")
.allKotlinSources +
subProject("app")
.projectPath
.resolve("src")
.allKotlinSources)
.map { it.relativeTo(projectPath) },
output
) )
} }
}
}
@Test @DisplayName("Lib with classpath snapshot: change isolated class")
fun testChangeIsolatedClassInLib_withAbiSnapshot() { @GradleTest
doTest( fun testChangeIsolatedClassInLib_withAbiSnapshot(gradleVersion: GradleVersion) {
options = defaultBuildOptions().copy(abiSnapshot = true), defaultProject(
modifyProject = { gradleVersion,
File(projectDir, "lib").getFileByName("BarDummy.kt").modify { buildOptions = defaultBuildOptions.copy(useGradleClasspathSnapshot = true)
) {
build("assemble")
subProject("lib").kotlinSourcesDir().resolve("bar/BarDummy.kt").modify {
"$it { fun m() = 42}" "$it { fun m() = 42}"
} }
},
expectedCompiledFileNames = listOf("BarDummy.kt") // In lib build("assemble") {
assertCompiledKotlinSources(
getExpectedKotlinSourcesForDefaultProject(
libSources = listOf("bar/BarDummy.kt")
),
output
) )
} }
} }
}
}