From 14dc20186dac9df05946b2e601a96bdfa045bce0 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 21 Feb 2022 16:58:53 +0100 Subject: [PATCH] Migrate IncrementalCompilationMultiProjectIT test to new dsl ^KT-45745 In Progress --- .../gradle/IncrementalCompilationBaseIT.kt | 3 +- .../IncrementalCompilationMultiProjectIT.kt | 892 +++++++++++------- 2 files changed, 527 insertions(+), 368 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationBaseIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationBaseIT.kt index 924c998467d..01bab06cd65 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationBaseIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationBaseIT.kt @@ -16,8 +16,9 @@ abstract class IncrementalCompilationBaseIT : KGPBaseTest() { protected abstract val defaultProjectName: String - fun defaultProject( + open fun defaultProject( gradleVersion: GradleVersion, + buildOptions: BuildOptions = defaultBuildOptions, test: TestProject.() -> Unit = {} ): TestProject = project( defaultProjectName, diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt index f2f7f3669b3..f83a5f54ccc 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/IncrementalCompilationMultiProjectIT.kt @@ -1,24 +1,29 @@ package org.jetbrains.kotlin.gradle -import org.jetbrains.kotlin.gradle.util.* -import org.junit.Test -import java.io.File +import org.gradle.testkit.runner.BuildResult +import org.gradle.util.GradleVersion +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() { - override fun defaultProject(): Project { - val project = Project("incrementalMultiproject") - project.setupWorkingDir() + override val defaultProjectName: String = "incrementalMultiproject" - for (subProject in arrayOf("app", "lib")) { - val subProjectDir = project.projectDir.resolve(subProject) - subProjectDir.resolve("src/main/java").deleteRecursively() - val buildGradle = subProjectDir.resolve("build.gradle") - val buildJsGradle = subProjectDir.resolve("build-js.gradle") - buildJsGradle.copyTo(buildGradle, overwrite = true) - buildJsGradle.delete() + override fun defaultProject( + gradleVersion: GradleVersion, + buildOptions: BuildOptions, + test: TestProject.() -> Unit + ): TestProject = project(defaultProjectName, gradleVersion) { + listOf("app", "lib").forEach { + 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 = @@ -28,6 +33,7 @@ class IncrementalCompilationJsMultiProjectIT : BaseIncrementalCompilationMultiPr get() = "compileKotlin2Js" } +@JvmGradlePluginTests open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationMultiProjectIT() { override val additionalLibDependencies: String = "implementation \"org.jetbrains.kotlin:kotlin-test:${'$'}kotlin_version\"" @@ -35,192 +41,219 @@ open class IncrementalCompilationJvmMultiProjectIT : BaseIncrementalCompilationM override val compileKotlinTaskName: String get() = "compileKotlin" - override fun defaultProject(): Project = - Project("incrementalMultiproject") + override val defaultProjectName: String = "incrementalMultiproject" // todo: do the same for js backend - @Test - fun testDuplicatedClass() { - val project = Project("duplicatedClass") - project.build("build") { - assertSuccessful() - } + @DisplayName("Duplicated class") + @GradleTest + fun testDuplicatedClass(gradleVersion: GradleVersion) { + project("duplicatedClass", gradleVersion) { + build("assemble") - val usagesFiles = listOf("useBuzz.kt", "useA.kt").map { project.projectFile(it) } - usagesFiles.forEach { file -> file.modify { "$it\n " } } + val usagesFiles = listOf("useBuzz.kt", "useA.kt").map { + subProject("app").kotlinSourcesDir().resolve(it) + } + usagesFiles.forEach { file -> file.modify { "$it\n " } } - project.build("build") { - assertSuccessful() - assertCompiledKotlinSources(project.relativize(usagesFiles)) + build("assemble") { + assertCompiledKotlinSources( + usagesFiles.map { it.relativeTo(projectPath) }, + output + ) + } } } - // checks that multi-project ic is disabled when there is a task that outputs to javaDestination dir - // that is not JavaCompile or KotlinCompile - @Test - open fun testCompileLibWithGroovy() { - testCompileLibWithGroovy_doTest { - assertCompiledKotlinFiles( - File(project.projectDir, "app").allKotlinFiles() + File(project.projectDir, "lib").getFileByName("A.kt") + @DisplayName( + "checks that multi-project ic is disabled when there is a task that outputs to javaDestination dir " + + "that is not JavaCompile or KotlinCompile" + ) + @GradleTest + open fun testCompileLibWithGroovy(gradleVersion: GradleVersion) { + 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) { - val project = defaultProject() - project.setupWorkingDir() - val lib = File(project.projectDir, "lib") - val libBuildGradle = File(lib, "build.gradle") - libBuildGradle.modify { - """ - plugins { - id 'groovy' - id 'org.jetbrains.kotlin.jvm' + protected fun testCompileLibWithGroovy_doTest( + gradleVersion: GradleVersion, + assertResults: (TestProject, BuildResult) -> Unit + ) { + defaultProject(gradleVersion) { + subProject("lib").buildGradle.modify { + """ + plugins { + id 'groovy' + id 'org.jetbrains.kotlin.jvm' + } + + dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:${"$"}kotlin_version" + implementation 'org.codehaus.groovy:groovy-all:2.4.8' + } + """.trimIndent() } - dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:${"$"}kotlin_version" - implementation 'org.codehaus.groovy:groovy-all:2.4.8' + val libGroovySrcBar = subProject("lib") + .projectPath + .resolve("src/main/groovy/bar") + .apply { createDirectories() } + val groovyClass = libGroovySrcBar.resolve("GroovyClass.groovy") + groovyClass.writeText( + """ + package bar + + class GroovyClass {} + """.trimIndent() + ) + + build("assemble") + + changeMethodBodyInLib() + build("build") { + assertResults(this@defaultProject, this) } - """.trimIndent() - } - - val libGroovySrcBar = File(lib, "src/main/groovy/bar").apply { mkdirs() } - val groovyClass = File(libGroovySrcBar, "GroovyClass.groovy") - groovyClass.writeText( - """ - package bar - - class GroovyClass {} - """ - ) - - project.build("build") { - assertSuccessful() - } - - project.changeMethodBodyInLib() - project.build("build") { - assertSuccessful() - assertResults() } } - /** Regression test for KT-43489. Make sure build history mapping is not initialized too early. */ - @Test - fun testBuildHistoryMappingLazilyComputedWithWorkers() { - val project = defaultProject() - project.setupWorkingDir() - project.projectDir.resolve("app/build.gradle").appendText( - """ + @DisplayName("KT-43489: Make sure build history mapping is not initialized too early") + @GradleTest + fun testBuildHistoryMappingLazilyComputedWithWorkers(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + subProject("app").buildGradle.appendText( + """ // added to force eager configuration tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } - """.trimIndent() - ) - val options = defaultBuildOptions().copy(parallelTasksInProject = true) - project.build(options = options, params = arrayOf("build")) { - assertSuccessful() - } + """.trimIndent() + ) - val aKt = project.projectDir.getFileByName("A.kt") - aKt.writeText( - """ -package bar + build("assemble") -open class A { - fun a() {} - fun newA() {} -} -""" - ) + val aKt = subProject("lib").kotlinSourcesDir().resolve("bar/A.kt") + aKt.writeText( + """ + package bar + + open class A { + fun a() {} + fun newA() {} + } + """.trimIndent() + ) - project.build(options = options, params = arrayOf("build")) { - assertSuccessful() - val affectedSources = project.projectDir.getFilesByNames("A.kt", "B.kt", "AA.kt", "AAA.kt", "BB.kt") - val relativePaths = project.relativize(affectedSources) - assertCompiledKotlinSources(relativePaths) + build("assemble") { + val expectedSources = getExpectedKotlinSourcesForDefaultProject( + libSources = listOf("bar/A.kt", "bar/B.kt"), + appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt") + ) + assertCompiledKotlinSources(expectedSources, output) + } } } } class IncrementalCompilationFirJvmMultiProjectIT : IncrementalCompilationJvmMultiProjectIT() { - override fun defaultBuildOptions(): BuildOptions { - return super.defaultBuildOptions().copy(useFir = true) - } + override val defaultBuildOptions: BuildOptions = super.defaultBuildOptions.copy(useFir = true) } class IncrementalCompilationClasspathSnapshotJvmMultiProjectIT : IncrementalCompilationJvmMultiProjectIT() { - override fun defaultBuildOptions() = super.defaultBuildOptions().copy(useClasspathSnapshot = true) + override val defaultBuildOptions = super.defaultBuildOptions.copy(useGradleClasspathSnapshot = true) - @Test - override fun testNonAbiChangeInLib_changeMethodBody() { - doTest( - modifyProject = changeMethodBodyInLib, - assertResults = { - assertTasksExecuted(":lib:$compileKotlinTaskName") - assertTasksUpToDate(":app:$compileKotlinTaskName") // App compilation has 'compile avoidance' - assertCompiledKotlinFiles(File(project.projectDir, "lib").getFilesByNames("A.kt")) - } - ) - } + @DisplayName("Lib: Non ABI change in method body") + @GradleTest + override fun testNonAbiChangeInLib_changeMethodBody(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") - @Test - override fun testAddDependencyInLib() { - doTest( - modifyProject = { testAddDependencyInLib_modifyProject() }, - assertResults = { + changeMethodBodyInLib() + + build("assemble") { assertTasksExecuted(":lib:$compileKotlinTaskName") assertTasksUpToDate(":app:$compileKotlinTaskName") - assertCompiledKotlinFiles(emptyList()) // Lib compilation is incremental (no files are recompiled) - } - ) - } - - @Test - override fun testAbiChangeInLib_afterLibClean() { - doTest( - modifyProject = { - build(":lib:clean") { assertSuccessful() } - 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() + assertCompiledKotlinSources( + getExpectedKotlinSourcesForDefaultProject(libSources = listOf("bar/A.kt")), + output ) } - ) - } - - @Test - override fun testCompileLibWithGroovy() { - testCompileLibWithGroovy_doTest { - assertTasksExecuted(":lib:$compileKotlinTaskName") - assertTasksUpToDate(":app:$compileKotlinTaskName") // App compilation has 'compile avoidance' - assertCompiledKotlinFiles(listOf(File(project.projectDir, "lib").getFileByName("A.kt"))) } } - @Test - override fun testAbiChangeInLib_afterLibClean_withAbiSnapshot() { - doTest( - options = defaultBuildOptions().copy(abiSnapshot = true), - modifyProject = { - build(":lib:clean") { assertSuccessful() } - 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() - ) + @DisplayName("Add dependency in lib subproject") + @GradleTest + override fun testAddDependencyInLib(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") + + testAddDependencyInLib_modifyProject() + + build("assemble") { + assertTasksExecuted(":lib:$compileKotlinTaskName") + assertTasksUpToDate(":app:$compileKotlinTaskName") + // Lib compilation is incremental (no files are recompiled) + assertCompiledKotlinSources(emptyList(), output) } - ) + } + } + + @DisplayName("after lib project clean") + @GradleTest + override fun testAbiChangeInLib_afterLibClean(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) + } + } + } + + @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 val changeMethodSignatureInLib: Project.() -> Unit = { - File(projectDir, "lib").getFileByName("A.kt").modify { + protected fun TestProject.changeMethodSignatureInLib() { + subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify { it.replace("fun a() {}", "fun a(): Int = 1") } } - protected val changeMethodBodyInLib: Project.() -> Unit = { - File(projectDir, "lib").getFileByName("A.kt").modify { + protected fun TestProject.changeMethodBodyInLib() { + subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify { it.replace("fun a() {}", "fun a() { println() }") } } - @Test - fun testAbiChangeInLib_changeMethodSignature() { - doTest( - modifyProject = changeMethodSignatureInLib, - expectedCompiledFileNames = listOf( - "A.kt", "B.kt", "barUseA.kt", // In lib - "AA.kt", "AAA.kt", "BB.kt", "fooUseA.kt" // In app + protected fun TestProject.getExpectedKotlinSourcesForDefaultProject( + libSources: List = emptyList(), + appSources: List = emptyList() + ): Iterable { + val expectedLibSources = if (libSources.isNotEmpty()) { + sourceFilesRelativeToProject( + libSources, + sourcesDir = { kotlinSourcesDir() }, + subProjectName = "lib" ) - ) + } else { + emptyList() + } + + val expectedAppSources = if (appSources.isNotEmpty()) { + sourceFilesRelativeToProject( + appSources, + sourcesDir = { kotlinSourcesDir() }, + subProjectName = "app" + ) + } else { + emptyList() + } + + return expectedLibSources + expectedAppSources } - @Test - fun testAbiChangeInLib_addNewMethod() { - doTest( - modifyProject = { - File(projectDir, "lib").getFileByName("A.kt").modify { - it.replace("fun a() {}", "fun a() {}\nfun newA() {}") - } - }, - expectedCompiledFileNames = listOf( - "A.kt", "B.kt", // In lib - "AA.kt", "AAA.kt", "BB.kt" // In app - ) - ) - } + @DisplayName("Lib: method signature ABI change") + @GradleTest + fun testAbiChangeInLib_changeMethodSignature(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") - @Test - open fun testNonAbiChangeInLib_changeMethodBody() { - doTest( - modifyProject = changeMethodBodyInLib, - assertResults = { - assertCompiledKotlinFiles(File(project.projectDir, "lib").getFilesByNames("A.kt")) + 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) } - ) + } } - @Test - open fun testAddDependencyInLib() { - doTest( - modifyProject = { testAddDependencyInLib_modifyProject() }, - assertResults = { + @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() {}") + } + + build("assemble") { + val expectedSources = getExpectedKotlinSourcesForDefaultProject( + libSources = listOf("bar/A.kt", "bar/B.kt"), + appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt") + ) + assertCompiledKotlinSources(expectedSources, output) + } + } + } + + @DisplayName("Lib: change method body with non-ABI change") + @GradleTest + open fun testNonAbiChangeInLib_changeMethodBody(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") + + changeMethodBodyInLib() + + build("assemble") { + assertCompiledKotlinSources( + getExpectedKotlinSourcesForDefaultProject( + libSources = listOf("bar/A.kt") + ), + output + ) + } + } + } + + @DisplayName("Add new dependency in lib project") + @GradleTest + open fun testAddDependencyInLib(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") + + testAddDependencyInLib_modifyProject() + + build("assemble") { assertTasksExecuted(":lib:$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() { - File(projectDir, "lib/build.gradle").modify { + protected fun TestProject.testAddDependencyInLib_modifyProject() { + subProject("lib").buildGradle.modify { """ $it @@ -302,191 +391,260 @@ abstract class BaseIncrementalCompilationMultiProjectIT : IncrementalCompilation } } - @Test - open fun testAbiChangeInLib_afterLibClean() { // To see if app compilation can be incremental after non-incremental lib compilation - doTest( - modifyProject = { - build(":lib:clean") { assertSuccessful() } - changeMethodSignatureInLib() - }, - assertResults = { - // App compilation is non-incremental - assertCompiledKotlinFiles(project.projectDir.allKotlinFiles()) - } - ) - } + @DisplayName("ABI change in lib after lib clean") + @GradleTest + open fun testAbiChangeInLib_afterLibClean(gradleVersion: GradleVersion) { + // To see if app compilation can be incremental after non-incremental lib compilation + defaultProject(gradleVersion) { + build("assemble") - @Test - fun testMoveFunctionFromLibToApp() { - doTest( - modifyProject = { - val barUseABKt = projectDir.getFileByName("barUseAB.kt") - val barInApp = File(projectDir, "app/src/main/kotlin/bar").apply { mkdirs() } - barUseABKt.copyTo(File(barInApp, barUseABKt.name)) - barUseABKt.delete() - }, - expectedCompiledFileNames = listOf("fooCallUseAB.kt", "barUseAB.kt") - ) - } + build(":lib:clean") + changeMethodSignatureInLib() - @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") - bKt.modify { it.replace("open class", "class") } - - project.build("build") { - assertFailed() - val affectedSources = project.projectDir.getFilesByNames( - "B.kt", "barUseAB.kt", "barUseB.kt", - "BB.kt", "fooCallUseAB.kt", "fooUseB.kt" - ) - val relativePaths = project.relativize(affectedSources) - assertCompiledKotlinSources(relativePaths) - } - } - - @Test - fun testCompileErrorInLib() { - val project = defaultProject() - project.build("build") { - assertSuccessful() - } - - val bKt = project.projectDir.getFileByName("B.kt") - val bKtContent = bKt.readText() - bKt.delete() - - fun runFailingBuild() { - project.build("build") { - assertFailed() - assertContains("B.kt has been removed") - assertTasksFailed(":lib:$compileKotlinTaskName") - val affectedFiles = project.projectDir.getFilesByNames("barUseAB.kt", "barUseB.kt") - assertCompiledKotlinSources(project.relativize(affectedFiles)) + build("assemble") { + assertCompiledKotlinSources( + subProject("lib") + .projectPath + .resolve("src") + .allKotlinSources + .relativizeTo(projectPath) + + subProject("app") + .projectPath + .resolve("src") + .allKotlinSources + .relativizeTo(projectPath), + output + ) } } - - runFailingBuild() - runFailingBuild() - - bKt.writeText(bKtContent.replace("fun b", "open fun b")) - - project.build("build") { - assertSuccessful() - val affectedFiles = project.projectDir.getFilesByNames( - "B.kt", "barUseAB.kt", "barUseB.kt", - "BB.kt", "fooUseB.kt" - ) - assertCompiledKotlinSources(project.relativize(affectedFiles)) - } } - @Test - fun testRemoveLibFromClasspath() { - val project = defaultProject() - project.build("build") { - assertSuccessful() - } + @DisplayName("Move function from lib module into app module") + @GradleTest + fun testMoveFunctionFromLibToApp(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") - val appBuildGradle = project.projectDir.resolve("app/build.gradle") - val appBuildGradleContent = appBuildGradle.readText() - appBuildGradle.modify { it.checkedReplace("implementation project(':lib')", "") } - val aaKt = project.projectDir.getFileByName("AA.kt") - aaKt.addNewLine() - - project.build("build") { - assertFailed() - } - - appBuildGradle.writeText(appBuildGradleContent) - aaKt.addNewLine() - - project.build("build") { - assertSuccessful() - assertCompiledKotlinSources(project.relativize(aaKt)) - } - } - - /** Regression test for KT-40875. */ - @Test - fun testMoveFunctionFromLibWithRemappedBuildDirs() { - val project = defaultProject() - project.setupWorkingDir() - project.projectDir.resolve("build.gradle").appendText( - """ - - allprojects { - it.buildDir = new File(rootDir, "../out" + it.path.replace(":", "/") + "/build") + val origFile = subProject("lib").kotlinSourcesDir().resolve("bar/barUseAB.kt") + subProject("app").kotlinSourcesDir().run { + resolve("bar").createDirectory() + origFile.copyTo(resolve("bar/barUseAB.kt")) + origFile.deleteExisting() } - """.trimIndent() - ) - project.build("build") { - assertSuccessful() - } - val barUseABKt = project.projectDir.getFileByName("barUseAB.kt") - val barInApp = File(project.projectDir, "app/src/main/kotlin/bar").apply { mkdirs() } - barUseABKt.copyTo(File(barInApp, barUseABKt.name)) - barUseABKt.delete() - - project.build("build") { - assertSuccessful() - val affectedSources = project.projectDir.getFilesByNames("fooCallUseAB.kt", "barUseAB.kt") - val relativePaths = project.relativize(affectedSources) - assertCompiledKotlinSources(relativePaths) + build("assemble") { + assertCompiledKotlinSources( + getExpectedKotlinSourcesForDefaultProject( + appSources = listOf("foo/fooCallUseAB.kt", "bar/barUseAB.kt") + ), + output + ) + } } } - @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() {}") + @DisplayName("Lib project classes became final") + @GradleTest + fun testLibClassBecameFinal(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + build("assemble") + + subProject("lib").kotlinSourcesDir().resolve("bar/B.kt").modify { + it.replace("open class", "class") + } + + 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") + ) + assertCompiledKotlinSources(expectedSources, output) + } + } + } + + @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 + ) } - }, - 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 - ) - ) + } + + 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) + } + } } - @Test - open fun testAbiChangeInLib_afterLibClean_withAbiSnapshot() { - doTest( - options = defaultBuildOptions().copy(abiSnapshot = true), - modifyProject = { - build(":lib:clean") { assertSuccessful() } - changeMethodSignatureInLib() - }, - assertResults = { + @DisplayName("Remove library from classpath") + @GradleTest + fun testRemoveLibFromClasspath(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + 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() + } + + buildAndFail("assemble") + + subProject("app").buildGradle.writeText(appBuildGradleContent) + aaKt.modify { + """ + $it + + """.trimIndent() + } + + build("assemble") { + assertCompiledKotlinSources( + listOf(aaKt.relativeTo(projectPath)), + output + ) + } + } + } + + @DisplayName("KT-40875: move function from lib with remapped build dirs") + @GradleTest + fun testMoveFunctionFromLibWithRemappedBuildDirs(gradleVersion: GradleVersion) { + defaultProject(gradleVersion) { + buildGradle.appendText( + """ + + allprojects { + it.buildDir = new File(rootDir, "../out" + it.path.replace(":", "/") + "/build") + } + """.trimIndent() + ) + + 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() + } + + build("assemble") { + assertCompiledKotlinSources( + getExpectedKotlinSourcesForDefaultProject( + appSources = listOf("foo/fooCallUseAB.kt", "bar/barUseAB.kt") + ), + output + ) + } + } + } + + @DisplayName("Lib with ABI snapshot: add new ABI method") + @GradleTest + fun testAbiChangeInLib_addNewMethod_withAbiSnapshot(gradleVersion: GradleVersion) { + defaultProject( + gradleVersion, + buildOptions = defaultBuildOptions.copy(useGradleClasspathSnapshot = true) + ) { + build("assemble") + + subProject("lib").kotlinSourcesDir().resolve("bar/A.kt").modify { + it.replace("fun a() {}", "fun a() {}\nfun newA() {}") + } + + build("assemble") { + val expectedSources = getExpectedKotlinSourcesForDefaultProject( + libSources = listOf("bar/A.kt", "bar/B.kt"), + // TODO(valtman): for abi-snapshot "BB.kt" should not be recompiled + appSources = listOf("foo/AA.kt", "foo/AAA.kt", "foo/BB.kt") + ) + + assertCompiledKotlinSources(expectedSources, output) + } + } + } + + @DisplayName("Lib with abi snapshot: after clean build") + @GradleTest + open fun testAbiChangeInLib_afterLibClean_withAbiSnapshot(gradleVersion: GradleVersion) { + defaultProject( + gradleVersion, + buildOptions = defaultBuildOptions.copy(useGradleClasspathSnapshot = true) + ) { + build("assemble") + + build(":lib:clean") + changeMethodSignatureInLib() + + build("assemble") { // 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 - 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 - ) + @DisplayName("Lib with classpath snapshot: change isolated class") + @GradleTest + fun testChangeIsolatedClassInLib_withAbiSnapshot(gradleVersion: GradleVersion) { + defaultProject( + gradleVersion, + buildOptions = defaultBuildOptions.copy(useGradleClasspathSnapshot = true) + ) { + build("assemble") + + subProject("lib").kotlinSourcesDir().resolve("bar/BarDummy.kt").modify { + "$it { fun m() = 42}" + } + + build("assemble") { + assertCompiledKotlinSources( + getExpectedKotlinSourcesForDefaultProject( + libSources = listOf("bar/BarDummy.kt") + ), + output + ) + } + } } }