[IC] Multi-module incremental compilation smoke tests for KMP
Part 2. Main multi-module KMP smoke tests for incremental compilation ^KT-56963 Fixed ^KT-63876 Fixed Merge-request: KT-MR-13193 Merged-by: Evgenii Mazhukin <evgenii.mazhukin@jetbrains.com>
This commit is contained in:
committed by
Space Team
parent
c4fe7e4453
commit
befa921fd9
+27
-27
@@ -9,15 +9,13 @@ import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.testbase.relativizeTo
|
||||
import org.jetbrains.kotlin.gradle.util.replaceWithVersion
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.io.path.appendText
|
||||
|
||||
/**
|
||||
* KmpIncrementalITBase is used as a "limited scope" for experimental test utils.
|
||||
* If some of them can be reused widely, consider moving them to KGPBaseTest
|
||||
* or the appropriate util package. //TODO: KT-63876
|
||||
* Convenience class for KMP incremental compilation tests.
|
||||
*
|
||||
* Consider moving all general-purpose logic to `org.jetbrains.kotlin.gradle.testbase` package.
|
||||
*/
|
||||
abstract class KmpIncrementalITBase : KGPBaseTest() {
|
||||
|
||||
@@ -27,9 +25,11 @@ abstract class KmpIncrementalITBase : KGPBaseTest() {
|
||||
protected open val gradleTask = "assemble"
|
||||
protected open val projectName = "generic-kmp-app-plus-lib-with-tests"
|
||||
protected open val mainCompileTasks = setOf(
|
||||
":app:compileCommonMainKotlinMetadata",
|
||||
":app:compileKotlinJvm",
|
||||
":app:compileKotlinJs",
|
||||
":app:compileKotlinNative",
|
||||
":lib:compileCommonMainKotlinMetadata",
|
||||
":lib:compileKotlinJvm",
|
||||
":lib:compileKotlinJs",
|
||||
":lib:compileKotlinNative",
|
||||
@@ -48,30 +48,30 @@ abstract class KmpIncrementalITBase : KGPBaseTest() {
|
||||
return subProject(subproject).kotlinSourcesDir(srcDir).resolve(name)
|
||||
}
|
||||
|
||||
protected fun Path.addPrivateVal(): Path {
|
||||
this@addPrivateVal.appendText("private val nothingMuch${changeCounter.incrementAndGet()} = 24")
|
||||
return this@addPrivateVal
|
||||
}
|
||||
|
||||
protected open fun BuildResult.assertSuccessOrUTD(allTasks: Set<String>, executedTasks: Set<String>) {
|
||||
assertTasksExecuted(executedTasks)
|
||||
assertTasksUpToDate(allTasks - executedTasks)
|
||||
}
|
||||
|
||||
protected open fun TestProject.testCase(incrementalPath: Path? = null, executedTasks: Set<String>, assertions: BuildResult.() -> Unit = {}) {
|
||||
protected open fun TestProject.checkIncrementalBuild(
|
||||
tasksExpectedToExecute: Set<String>,
|
||||
assertions: BuildResult.() -> Unit = {}
|
||||
) {
|
||||
build(gradleTask, buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
assertSuccessOrUTD(
|
||||
allTasks = mainCompileTasks,
|
||||
executedTasks = executedTasks
|
||||
)
|
||||
incrementalPath?.let {
|
||||
assertIncrementalCompilation(listOf(it).relativizeTo(projectPath))
|
||||
}
|
||||
assertTasksExecuted(tasksExpectedToExecute)
|
||||
assertTasksUpToDate(mainCompileTasks - tasksExpectedToExecute)
|
||||
assertions()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val changeCounter = AtomicInteger(0)
|
||||
protected open fun TestProject.multiStepCheckIncrementalBuilds(
|
||||
incrementalPath: Path,
|
||||
steps: List<String>,
|
||||
tasksExpectedToExecuteOnEachStep: Set<String>,
|
||||
afterEachStep: BuildResult.() -> Unit = {}
|
||||
) {
|
||||
for (step in steps) {
|
||||
incrementalPath.replaceWithVersion(step)
|
||||
build(gradleTask, buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
assertTasksExecuted(tasksExpectedToExecuteOnEachStep)
|
||||
assertTasksUpToDate(mainCompileTasks - tasksExpectedToExecuteOnEachStep)
|
||||
afterEachStep()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-32
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.mpp.smoke
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.mpp.KmpIncrementalITBase
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.test.TestMetadata
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
||||
/**
|
||||
@@ -19,6 +20,7 @@ open class BasicIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
|
||||
@DisplayName("Base test case - local change, local recompilation")
|
||||
@GradleTest
|
||||
@TestMetadata("generic-kmp-app-plus-lib-with-tests")
|
||||
fun testStrictlyLocalChange(gradleVersion: GradleVersion): Unit = withProject(gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
@@ -26,84 +28,99 @@ open class BasicIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
* Step 1: touch app:common, no abi change
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("app", "commonMain", "Unused.kt").addPrivateVal(),
|
||||
executedTasks = setOf(
|
||||
val sourceInAppCommon = resolvePath("app", "commonMain", "Unused.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileCommonMainKotlinMetadata",
|
||||
":app:compileKotlinJvm",
|
||||
":app:compileKotlinJs",
|
||||
":app:compileKotlinNative"
|
||||
)
|
||||
)
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(sourceInAppCommon).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: touch app:jvm, no abi change
|
||||
*/
|
||||
|
||||
val appJvmClassKt = resolvePath("app", "jvmMain", "UnusedJvm.kt").addPrivateVal()
|
||||
testCase(
|
||||
incrementalPath = null, //TODO: it just doesn't print "Incremental compilation completed", why? (KT-63476)
|
||||
executedTasks = setOf(":app:compileKotlinJvm")
|
||||
val sourceInAppJvm = resolvePath("app", "jvmMain", "UnusedJvm.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(":app:compileKotlinJvm")
|
||||
) {
|
||||
assertCompiledKotlinSources(listOf(appJvmClassKt).relativizeTo(projectPath), output)
|
||||
//TODO: it just doesn't print "Incremental compilation completed", why? (KT-63476)
|
||||
assertCompiledKotlinSources(listOf(sourceInAppJvm).relativizeTo(projectPath), output)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: touch app:js, no abi change
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("app", "jsMain", "UnusedJs.kt").addPrivateVal(),
|
||||
executedTasks = setOf(":app:compileKotlinJs"),
|
||||
)
|
||||
val sourceInAppJs = resolvePath("app", "jsMain", "UnusedJs.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(":app:compileKotlinJs"),
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(sourceInAppJs).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 4: touch app:native, no abi change
|
||||
*/
|
||||
|
||||
resolvePath("app", "nativeMain", "UnusedNative.kt").addPrivateVal()
|
||||
testCase(
|
||||
incrementalPath = null, // no native incremental compilation - see KT-62824
|
||||
executedTasks = setOf(":app:compileKotlinNative"),
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(":app:compileKotlinNative"),
|
||||
)
|
||||
|
||||
/**
|
||||
* Step 5: touch lib:common, no abi change
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("lib", "commonMain", "UsedInLibPlatformTests.kt").addPrivateVal(),
|
||||
executedTasks = mainCompileTasks // TODO: KT-62642 - bad compile avoidance here
|
||||
)
|
||||
val sourceInLibCommon = resolvePath("lib", "commonMain", "UsedInLibPlatformTests.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = mainCompileTasks // TODO: KT-62642 - bad compile avoidance here
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(sourceInLibCommon).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 6: touch lib:jvm, no abi change
|
||||
*/
|
||||
|
||||
val libJvmUtilKt = resolvePath("lib", "jvmMain", "UsedInAppJvmAndLibTests.kt").addPrivateVal()
|
||||
testCase(
|
||||
incrementalPath = null, //TODO: it just doesn't print "Incremental compilation completed", why? (KT-63476)
|
||||
executedTasks = setOf(":app:compileKotlinJvm", ":lib:compileKotlinJvm"),
|
||||
val sourceInLibJvm = resolvePath("lib", "jvmMain", "UsedInAppJvmAndLibTests.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileKotlinJvm",
|
||||
":lib:compileKotlinJvm"
|
||||
),
|
||||
) {
|
||||
assertCompiledKotlinSources(listOf(libJvmUtilKt).relativizeTo(projectPath), output)
|
||||
assertCompiledKotlinSources(listOf(sourceInLibJvm).relativizeTo(projectPath), output)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 7: touch lib:js, no abi change
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("lib", "jsMain", "UsedInAppJsAndLibTests.kt").addPrivateVal(),
|
||||
executedTasks = setOf(":app:compileKotlinJs", ":lib:compileKotlinJs"),
|
||||
)
|
||||
val sourceInLibJs = resolvePath("lib", "jsMain", "UsedInAppJsAndLibTests.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileKotlinJs",
|
||||
":lib:compileKotlinJs"
|
||||
),
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(sourceInLibJs).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 8: touch lib:native, no abi change
|
||||
*/
|
||||
|
||||
resolvePath("lib", "nativeMain", "UsedInAppNativeAndLibTests.kt").addPrivateVal()
|
||||
testCase(
|
||||
incrementalPath = null,
|
||||
executedTasks = setOf(":app:compileKotlinNative", ":lib:compileKotlinNative"),
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileKotlinNative",
|
||||
":lib:compileKotlinNative"
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+28
-18
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.mpp.smoke
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.mpp.KmpIncrementalITBase
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.test.TestMetadata
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
||||
@DisplayName("Basic incremental scenarios with tests in KMP - K2")
|
||||
@@ -15,6 +16,9 @@ import org.junit.jupiter.api.DisplayName
|
||||
open class BasicTestIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
override val mainCompileTasks: Set<String>
|
||||
get() = setOf(
|
||||
":app:compileCommonMainKotlinMetadata",
|
||||
":lib:compileCommonMainKotlinMetadata",
|
||||
|
||||
":app:compileTestKotlinJvm",
|
||||
":lib:compileTestKotlinJvm",
|
||||
|
||||
@@ -38,6 +42,7 @@ open class BasicTestIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
|
||||
@DisplayName("KMP tests are rebuilt when affected")
|
||||
@GradleTest
|
||||
@TestMetadata("generic-kmp-app-plus-lib-with-tests")
|
||||
fun testAffectingTestDependencies(gradleVersion: GradleVersion): Unit = withProject(gradleVersion) {
|
||||
build("build")
|
||||
|
||||
@@ -45,18 +50,21 @@ open class BasicTestIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
* Step 1 - touch lib/common, affect all tests in app and lib
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("lib", "commonMain", "UsedInLibPlatformTests.kt").addPrivateVal(),
|
||||
executedTasks = mainCompileTasks,
|
||||
)
|
||||
val changedInLibCommon = resolvePath("lib", "commonMain", "UsedInLibPlatformTests.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = mainCompileTasks,
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(changedInLibCommon).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2 - touch app/common, affect all tests in app
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("app", "commonMain", "Unused.kt").addPrivateVal(),
|
||||
executedTasks = setOf(
|
||||
val changedInAppCommon = resolvePath("app", "commonMain", "Unused.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileCommonMainKotlinMetadata",
|
||||
":app:compileTestKotlinJvm",
|
||||
":app:compileTestKotlinNative",
|
||||
":app:compileTestKotlinJs",
|
||||
@@ -64,16 +72,17 @@ open class BasicTestIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
":app:jvmTest",
|
||||
":app:nativeTest",
|
||||
),
|
||||
)
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(changedInAppCommon).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3 - touch app/jvm, affect jvm tests in app
|
||||
*/
|
||||
|
||||
val touchedAppJvm = resolvePath("app", "jvmMain", "UnusedJvm.kt").addPrivateVal()
|
||||
testCase(
|
||||
incrementalPath = null,
|
||||
executedTasks = setOf(
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileTestKotlinJvm",
|
||||
":app:jvmTest",
|
||||
),
|
||||
@@ -85,22 +94,23 @@ open class BasicTestIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
* Step 4 - touch app/js, affect js tests in app
|
||||
*/
|
||||
|
||||
testCase(
|
||||
incrementalPath = resolvePath("app", "jsMain", "UnusedJs.kt").addPrivateVal(),
|
||||
executedTasks = setOf(
|
||||
val changedInAppJs = resolvePath("app", "jsMain", "UnusedJs.kt").addPrivateVal()
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileTestKotlinJs",
|
||||
":app:jsTest",
|
||||
),
|
||||
)
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(changedInAppJs).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 5 - touch app/native, affect native tests in app
|
||||
*/
|
||||
|
||||
resolvePath("app", "nativeMain", "UnusedNative.kt").addPrivateVal()
|
||||
testCase(
|
||||
incrementalPath = null,
|
||||
executedTasks = setOf(
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(
|
||||
":app:compileTestKotlinNative",
|
||||
":app:nativeTest",
|
||||
),
|
||||
|
||||
+14
-13
@@ -8,17 +8,15 @@ package org.jetbrains.kotlin.gradle.mpp.smoke
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.util.replaceFirst
|
||||
import org.jetbrains.kotlin.gradle.util.replaceWithVersion
|
||||
import org.jetbrains.kotlin.test.TestMetadata
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import kotlin.io.path.appendText
|
||||
|
||||
/**
|
||||
*
|
||||
* Touch file with expect fun, target IC adds actual to the build set
|
||||
* Variants: expect class
|
||||
* Touch file with actual fun, target IC adds expect to the build set
|
||||
* Variants: expect class
|
||||
*
|
||||
*/
|
||||
@DisplayName("Incremental scenarios with expect/actual - K2")
|
||||
@MppGradlePluginTests
|
||||
@@ -28,17 +26,21 @@ open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
|
||||
|
||||
@DisplayName("File with actual declaration needs recompiling")
|
||||
@GradleTest
|
||||
@TestMetadata("expect-actual-fun-or-class-ic")
|
||||
fun testRecompilationOfActualFun(gradleVersion: GradleVersion) {
|
||||
nativeProject("expect-actual-fun-or-class-ic", gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
listOf(
|
||||
kotlinSourcesDir("jvmMain").resolve("ActualFunFoo.kt"),
|
||||
kotlinSourcesDir("nativeMain").resolve("ActualFunFoo.kt"),
|
||||
kotlinSourcesDir("jsMain").resolve("ActualFunFoo.kt")
|
||||
).forEach {
|
||||
it.replaceFirst("\"foo", "\"bar")
|
||||
// just touch the file. expected logic is this:
|
||||
// actual declaration needs to be recompiled -> we add expect declaration to the list of compiled files
|
||||
it.addPrivateVal()
|
||||
}
|
||||
//TODO: KT-63970 - native compilation fails, if val is private
|
||||
kotlinSourcesDir("nativeMain").resolve("ActualFunFoo.kt").addPublicVal()
|
||||
|
||||
build("assemble", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
assertTasksExecuted(":compileKotlinJvm", ":compileKotlinJs", ":compileKotlinNative")
|
||||
@@ -55,15 +57,13 @@ open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
|
||||
|
||||
@DisplayName("File with expect declaration needs recompiling indirectly")
|
||||
@GradleTest
|
||||
@TestMetadata("expect-actual-fun-or-class-ic")
|
||||
fun testRecompilationOfExpectFun(gradleVersion: GradleVersion) {
|
||||
nativeProject("expect-actual-fun-or-class-ic", gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
val unusedKtPath = kotlinSourcesDir("commonMain").resolve("Unused.kt")
|
||||
unusedKtPath.replaceFirst(
|
||||
"val secret = 1",
|
||||
"val secret = \"k2\""
|
||||
)
|
||||
val commonSourceKt = kotlinSourcesDir("commonMain").resolve("UsedInFileWithExpectFun.kt")
|
||||
commonSourceKt.replaceWithVersion("sourceCompatibleAbiChange")
|
||||
|
||||
build("assemble", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
assertTasksExecuted(":compileKotlinJvm", ":compileKotlinJs", ":compileKotlinNative")
|
||||
@@ -72,7 +72,7 @@ open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
|
||||
kotlinSourcesDir("jvmMain").resolve("ActualFunFoo.kt"),
|
||||
kotlinSourcesDir("jsMain").resolve("ActualFunFoo.kt"),
|
||||
kotlinSourcesDir("commonMain").resolve("ExpectFunFoo.kt"),
|
||||
unusedKtPath
|
||||
commonSourceKt
|
||||
).relativizeTo(projectPath)
|
||||
)
|
||||
}
|
||||
@@ -81,11 +81,12 @@ open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
|
||||
|
||||
@DisplayName("File with expect class declaration needs recompiling")
|
||||
@GradleTest
|
||||
@TestMetadata("expect-actual-fun-or-class-ic")
|
||||
fun testRecompilationOfExpectClass(gradleVersion: GradleVersion) {
|
||||
nativeProject("expect-actual-fun-or-class-ic", gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
kotlinSourcesDir("commonMain").resolve("ExpectClassBar.kt").appendText("val irrelevant = 2")
|
||||
kotlinSourcesDir("commonMain").resolve("ExpectClassBar.kt").addPrivateVal()
|
||||
|
||||
build("assemble", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
assertTasksExecuted(":compileKotlinJvm", ":compileKotlinJs", ":compileKotlinNative")
|
||||
|
||||
+207
-32
@@ -5,62 +5,237 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.mpp.smoke
|
||||
|
||||
import org.gradle.api.logging.LogLevel
|
||||
import org.gradle.testkit.runner.BuildResult
|
||||
import org.gradle.util.GradleVersion
|
||||
import org.jetbrains.kotlin.gradle.testbase.BuildOptions
|
||||
import org.jetbrains.kotlin.gradle.testbase.GradleTest
|
||||
import org.jetbrains.kotlin.gradle.testbase.KGPBaseTest
|
||||
import org.jetbrains.kotlin.gradle.testbase.MppGradlePluginTests
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.jetbrains.kotlin.gradle.mpp.KmpIncrementalITBase
|
||||
import org.jetbrains.kotlin.gradle.testbase.*
|
||||
import org.jetbrains.kotlin.gradle.util.replaceWithVersion
|
||||
import org.jetbrains.kotlin.test.TestMetadata
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
|
||||
@Disabled("KT-56963")
|
||||
/**
|
||||
* Multi-module KMP tests assert that IC is fine, when API changes between two modules, or common/platform parts of a module.
|
||||
*/
|
||||
@DisplayName("Basic multi-module incremental scenarios with KMP - K2")
|
||||
@MppGradlePluginTests
|
||||
open class MultiModuleIncrementalCompilationIT : KGPBaseTest() {
|
||||
override val defaultBuildOptions: BuildOptions
|
||||
get() = super.defaultBuildOptions.copyEnsuringK2()
|
||||
open class MultiModuleIncrementalCompilationIT : KmpIncrementalITBase() {
|
||||
|
||||
/**
|
||||
* Tests api change across the module + sourceSet boundary
|
||||
*/
|
||||
@DisplayName("Verify IC builds on change in lib/commonMain")
|
||||
@GradleTest
|
||||
fun testTouchLibCommon(gradleVersion: GradleVersion) {
|
||||
/**
|
||||
* [Cross-Module] touch libCommon, affect appCommon, appJs
|
||||
* Variants: 1. code-compatible ABI breakage (change deduced return type), 2. no ABI breakage, 3. error + fix
|
||||
*/
|
||||
@TestMetadata("generic-kmp-app-plus-lib-with-tests")
|
||||
fun testTouchLibCommon(gradleVersion: GradleVersion) = withProject(gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
/**
|
||||
* Step 1: touch util used in app common
|
||||
*/
|
||||
val usedInAppCommon = resolvePath("lib", "commonMain", "UsedInAppCommon.kt")
|
||||
|
||||
multiStepCheckIncrementalBuilds(
|
||||
incrementalPath = usedInAppCommon,
|
||||
steps = listOf(
|
||||
"1_addUnusedParameter",
|
||||
"2_changeReturnType"
|
||||
),
|
||||
tasksExpectedToExecuteOnEachStep = mainCompileTasks,
|
||||
afterEachStep = {
|
||||
assertIncrementalCompilation(
|
||||
listOf(
|
||||
usedInAppCommon,
|
||||
resolvePath("app", "commonMain", "DependsOnLibCommon.kt")
|
||||
).relativizeTo(projectPath)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Step 2: touch class extended in app platform, then build each platform
|
||||
*/
|
||||
val usedInAppPlatform = resolvePath("lib", "commonMain", "UsedInAppPlatform.kt")
|
||||
usedInAppPlatform.replaceWithVersion("1_addNewPublicApi")
|
||||
|
||||
fun testIndividualTarget(moduleTask: String, extraAssertions: BuildResult.() -> Unit = {}) {
|
||||
build(":app:$moduleTask", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
|
||||
val targetTasks = setOf(
|
||||
":app:$moduleTask",
|
||||
":lib:$moduleTask"
|
||||
)
|
||||
assertTasksExecuted(targetTasks)
|
||||
assertTasksAreNotInTaskGraph(*(mainCompileTasks - targetTasks).toTypedArray())
|
||||
extraAssertions()
|
||||
}
|
||||
}
|
||||
|
||||
testIndividualTarget("compileKotlinJvm") {
|
||||
assertCompiledKotlinSources(
|
||||
listOf(
|
||||
usedInAppPlatform,
|
||||
resolvePath("app", "jvmMain", "DependsOnLibCommon.kt")
|
||||
).relativizeTo(projectPath),
|
||||
output
|
||||
)
|
||||
}
|
||||
testIndividualTarget("compileKotlinJs") {
|
||||
assertIncrementalCompilation(
|
||||
listOf(
|
||||
usedInAppPlatform,
|
||||
resolvePath("app", "jsMain", "DependsOnLibCommon.kt")
|
||||
).relativizeTo(projectPath)
|
||||
)
|
||||
}
|
||||
testIndividualTarget("compileKotlinNative")
|
||||
}
|
||||
|
||||
/**
|
||||
* Three platforms, two steps for each. Do source-compatible changes: first add default parameter,
|
||||
* then change return type.
|
||||
* lib/platform utils are used in app/platform with deduced return type.
|
||||
*/
|
||||
@DisplayName("Verify IC builds on change in lib/platformMain")
|
||||
@GradleTest
|
||||
fun testTouchLibPlatform(gradleVersion: GradleVersion) {
|
||||
/**
|
||||
* [C-M] touch lbJs, affect appJs
|
||||
* Variants: 1. code-compatible ABI breakage (change deduced return type), 2. no ABI breakage, 3. error + fix
|
||||
*/
|
||||
@TestMetadata("generic-kmp-app-plus-lib-with-tests")
|
||||
fun testTouchLibPlatform(gradleVersion: GradleVersion) = withProject(gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
val commonSteps = listOf("1_addUnusedParameter", "2_changeReturnType")
|
||||
|
||||
/**
|
||||
* Step 1 - jvm
|
||||
*/
|
||||
val jvmUtil = resolvePath("lib", "jvmMain", "UsedInAppJvmAndLibTests.kt")
|
||||
multiStepCheckIncrementalBuilds(
|
||||
incrementalPath = jvmUtil,
|
||||
steps = commonSteps,
|
||||
tasksExpectedToExecuteOnEachStep = setOf(
|
||||
":app:compileKotlinJvm",
|
||||
":lib:compileKotlinJvm"
|
||||
),
|
||||
afterEachStep = {
|
||||
assertCompiledKotlinSources(
|
||||
expectedSources = listOf(
|
||||
jvmUtil,
|
||||
resolvePath("app", "jvmMain", "DependsOnLibJvm.kt")
|
||||
).relativizeTo(projectPath),
|
||||
output = output
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Step 2 - js
|
||||
*/
|
||||
val jsUtil = resolvePath("lib", "jsMain", "UsedInAppJsAndLibTests.kt")
|
||||
multiStepCheckIncrementalBuilds(
|
||||
incrementalPath = jsUtil,
|
||||
steps = commonSteps,
|
||||
tasksExpectedToExecuteOnEachStep = setOf(
|
||||
":app:compileKotlinJs",
|
||||
":lib:compileKotlinJs"
|
||||
),
|
||||
afterEachStep = {
|
||||
assertIncrementalCompilation(
|
||||
listOf(
|
||||
jsUtil,
|
||||
resolvePath("app", "jsMain", "DependsOnLibJs.kt")
|
||||
).relativizeTo(projectPath)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Step 3 - native
|
||||
*/
|
||||
val nativeUtil = resolvePath("lib", "nativeMain", "UsedInAppNativeAndLibTests.kt")
|
||||
multiStepCheckIncrementalBuilds(
|
||||
incrementalPath = nativeUtil,
|
||||
steps = commonSteps,
|
||||
tasksExpectedToExecuteOnEachStep = setOf(
|
||||
":app:compileKotlinNative",
|
||||
":lib:compileKotlinNative"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Main smoke tests for api changes on the source set boundary
|
||||
*/
|
||||
@DisplayName("Verify IC builds on change in app/commonMain")
|
||||
@GradleTest
|
||||
fun testTouchAppCommon(gradleVersion: GradleVersion) {
|
||||
/**
|
||||
* [C-M] touch appCommon, rebuild appJs
|
||||
* Variants: 1. code-compatible ABI breakage (change deduced return type), 2. no ABI breakage, 3. error + fix
|
||||
*/
|
||||
@TestMetadata("generic-kmp-app-plus-lib-with-tests")
|
||||
fun testTouchAppCommon(gradleVersion: GradleVersion) = withProject(gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
//TODO KT-56963 : confirm and create issues for these source-compatible changes
|
||||
//utilKtPath.replaceFirst("fun multiplyByTwo(n: Int): Int", "fun <T> multiplyByTwo(n: T): T") - breaks native
|
||||
//utilKtPath.replaceFirst("fun multiplyByTwo(n: Int): Int", "fun multiplyByTwo(n: Int, unused: Int = 42): Int") - breaks js
|
||||
val utilPath = resolvePath("app", "commonMain", "UsedInAppPlatform.kt")
|
||||
|
||||
multiStepCheckIncrementalBuilds(
|
||||
incrementalPath = utilPath,
|
||||
steps = listOf(
|
||||
"1_changeReturnType",
|
||||
"2_addUnusedParameter"
|
||||
// test changes in a different order for robustness
|
||||
),
|
||||
tasksExpectedToExecuteOnEachStep = setOf(
|
||||
":app:compileCommonMainKotlinMetadata",
|
||||
":app:compileKotlinJvm",
|
||||
":app:compileKotlinJs",
|
||||
":app:compileKotlinNative"
|
||||
),
|
||||
afterEachStep = {
|
||||
assertIncrementalCompilation(
|
||||
listOf(
|
||||
utilPath,
|
||||
resolvePath("app", "jsMain", "DependsOnAppCommon.kt"),
|
||||
resolvePath("app", "jvmMain", "DependsOnAppCommon.kt")
|
||||
).relativizeTo(projectPath)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Platform changes in a non-dependency shouldn't affect anything else
|
||||
*/
|
||||
@DisplayName("Verify IC builds on change in app/platformMain")
|
||||
@GradleTest
|
||||
fun testTouchAppPlatform(gradleVersion: GradleVersion) {
|
||||
/**
|
||||
* [C-M] touch appJs, rebuild appJs
|
||||
* Variants: 1. code-compatible ABI breakage (change deduced return type), 2. no ABI breakage, 3. error + fix
|
||||
*/
|
||||
@TestMetadata("generic-kmp-app-plus-lib-with-tests")
|
||||
fun testTouchAppPlatform(gradleVersion: GradleVersion) = withProject(gradleVersion) {
|
||||
build("assemble")
|
||||
|
||||
/**
|
||||
* Step 1 - jvm
|
||||
*/
|
||||
val changedJvmSource = resolvePath("app", "jvmMain", "UnusedJvm.kt")
|
||||
.replaceWithVersion("addParent")
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(":app:compileKotlinJvm")
|
||||
) {
|
||||
assertCompiledKotlinSources(listOf(changedJvmSource).relativizeTo(projectPath), output)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2 - js
|
||||
*/
|
||||
val changedJsSource = resolvePath("app", "jsMain", "UnusedJs.kt")
|
||||
.replaceWithVersion("addParent")
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(":app:compileKotlinJs")
|
||||
) {
|
||||
assertIncrementalCompilation(listOf(changedJsSource).relativizeTo(projectPath))
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3 - native
|
||||
*/
|
||||
resolvePath("app", "nativeMain", "UnusedNative.kt")
|
||||
.replaceWithVersion("addParent")
|
||||
checkIncrementalBuild(
|
||||
tasksExpectedToExecute = setOf(":app:compileKotlinNative")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.gradle.testbase
|
||||
|
||||
import org.jetbrains.kotlin.gradle.testbase.TransformationsGeneratorState.changeCounter
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.io.path.appendText
|
||||
|
||||
|
||||
fun Path.addPrivateVal(): Path {
|
||||
appendText("\nprivate val integerValue${changeCounter.incrementAndGet()} = 24\n")
|
||||
return this
|
||||
}
|
||||
|
||||
fun Path.addPublicVal(): Path {
|
||||
appendText("\nval integerValue${changeCounter.incrementAndGet()} = 25\n")
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that generated names are unique.
|
||||
*/
|
||||
private object TransformationsGeneratorState {
|
||||
val changeCounter = AtomicInteger(0)
|
||||
}
|
||||
+19
-2
@@ -35,7 +35,12 @@ fun BuildResult.assertTasksExecuted(vararg tasks: String) {
|
||||
tasks.forEach { task ->
|
||||
assert(task(task)?.outcome == TaskOutcome.SUCCESS) {
|
||||
printBuildOutput()
|
||||
"Task $task didn't have 'SUCCESS' state: ${task(task)?.outcome}"
|
||||
"""
|
||||
Task $task didn't have 'SUCCESS' state: ${task(task)?.outcome}
|
||||
|
||||
Actual task states:
|
||||
${getActualTasksAsString()}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +97,12 @@ fun BuildResult.assertTasksUpToDate(vararg tasks: String) {
|
||||
tasks.forEach { task ->
|
||||
assert(task(task)?.outcome == TaskOutcome.UP_TO_DATE) {
|
||||
printBuildOutput()
|
||||
"Task $task didn't have 'UP-TO-DATE' state: ${task(task)?.outcome}"
|
||||
"""
|
||||
Task $task didn't have 'UP-TO-DATE' state: ${task(task)?.outcome}
|
||||
|
||||
Actual task states:
|
||||
${getActualTasksAsString()}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,6 +214,13 @@ fun BuildResult.assertTasksInBuildOutput(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns printable list of tasks that actually executed.
|
||||
*/
|
||||
private fun BuildResult.getActualTasksAsString(): String {
|
||||
return tasks.joinToString("\n") { "${it.path} - ${it.outcome}" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Method parses the output of a 'tasks --all' build
|
||||
* and returns a list of all the tasks mentioned in it.
|
||||
|
||||
+7
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.util
|
||||
import org.jetbrains.kotlin.gradle.testbase.createTempDirDeleteOnExit
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.copyTo
|
||||
import kotlin.io.path.readText
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@@ -116,3 +117,9 @@ fun File.replaceText(regex: Regex, replacement: String) {
|
||||
fun Path.replaceFirst(oldValue: String, newValue: String) {
|
||||
writeText(readText().replaceFirst(oldValue, newValue))
|
||||
}
|
||||
|
||||
fun Path.replaceWithVersion(versionSuffix: String): Path {
|
||||
val otherVersion = resolveSibling("$fileName.$versionSuffix")
|
||||
otherVersion.copyTo(this, overwrite = true)
|
||||
return this
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
expect fun foo(): Any
|
||||
|
||||
fun fooImpl() = secret
|
||||
fun fooImpl() = commonMainValue
|
||||
-1
@@ -1 +0,0 @@
|
||||
val secret = 1
|
||||
+1
@@ -0,0 +1 @@
|
||||
val commonMainValue = 1
|
||||
+1
@@ -0,0 +1 @@
|
||||
val commonMainValue = "it's a string now"
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
class AppCommonUnusedClass {
|
||||
open class AppCommonUnusedClass {
|
||||
fun bar() = 234
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun appCommonFunForAppPlatform() = "Yes"
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun appCommonFunForAppPlatform() = 90
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun appCommonFunForAppPlatform(delta: Double = 1.0) = 90
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AppCommonTest {
|
||||
|
||||
@Test
|
||||
fun sayYesSaysYes() {
|
||||
assertEquals("Yes", appCommonFunForAppPlatformAndAppCommonTest())
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class AppCommonTest {
|
||||
|
||||
@Test
|
||||
fun threeIsThree() {
|
||||
assertEquals(3, 3)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
class AppJsUnusedClassWithDependencies {
|
||||
val line = appCommonFunForAppPlatformAndAppCommonTest()
|
||||
val line = appCommonFunForAppPlatform()
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class AppJsUnusedClass : AppCommonUnusedClass() {
|
||||
fun foo() = 234
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
class AppJvmUnusedClassWithDependencies {
|
||||
val line = appCommonFunForAppPlatformAndAppCommonTest()
|
||||
val line = appCommonFunForAppPlatform()
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class AppJvmUnusedClass : AppCommonUnusedClass() {
|
||||
fun doBar() = 2
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
class AppNativeUnusedClassWithDependencies {
|
||||
val line = appCommonFunForAppPlatformAndAppCommonTest()
|
||||
val line = appCommonFunForAppPlatform()
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
class AppNativeUnusedClass : AppCommonUnusedClass() {
|
||||
fun queueSomething() = Unit
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libCommonFunForAppCommon(unused: Any? = null) = Double.NaN
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libCommonFunForAppCommon(unused: Any? = null): String? = null
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
open class LibCommonClassForAppPlatform(
|
||||
val length: Int,
|
||||
val builder: String,
|
||||
) {
|
||||
val introduceNewPublicApi = "Yes, please"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libJsPlatformUtil(unused: Boolean = true): Int = 0
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libJsPlatformUtil(unused: Boolean = true): Double = 20.0
|
||||
+1
-1
@@ -5,7 +5,7 @@ class LibJsTest {
|
||||
|
||||
@Test
|
||||
fun jsUtilReturns0() {
|
||||
assertEquals(0, libJsPlatformUtil())
|
||||
assertEquals(0, libJsPlatformUtil().toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libJvmPlatformUtil(unused: Boolean = true): Int = 400
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libJvmPlatformUtil(unused: Boolean = true): Double = 20.0
|
||||
+1
-1
@@ -5,7 +5,7 @@ class LibJvmTest {
|
||||
|
||||
@Test
|
||||
fun jvmUtilReturns400() {
|
||||
assertEquals(400, libJvmPlatformUtil())
|
||||
assertEquals(400, libJvmPlatformUtil().toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libNativePlatformUtil(unused: Boolean = true): Int = 800
|
||||
+1
@@ -0,0 +1 @@
|
||||
fun libNativePlatformUtil(unused: Boolean = true): Double = 20.0
|
||||
+1
-1
@@ -5,7 +5,7 @@ class LibNativeTest {
|
||||
|
||||
@Test
|
||||
fun nativeUtilReturns800() {
|
||||
assertEquals(800, libNativePlatformUtil())
|
||||
assertEquals(800, libNativePlatformUtil().toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user