[IC] Smoke tests for KMP IC, part 1

Assert reasonable compile avoidance when basic changes are made in a
multiplatform project

^Relates to KT-56963

Merge-request: KT-MR-13032
Merged-by: Evgenii Mazhukin <evgenii.mazhukin@jetbrains.com>
This commit is contained in:
Evgenii Mazhukin
2023-11-20 17:17:27 +00:00
committed by Space Team
parent 3ff16f7798
commit 5973951f71
34 changed files with 530 additions and 17 deletions
@@ -0,0 +1,64 @@
/*
* 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.mpp
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 java.nio.file.Path
import kotlin.io.path.appendText
abstract class KmpIncrementalITBase : KGPBaseTest() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copyEnsuringK2()
protected open val gradleTask = "assemble"
protected open val projectName = "generic-kmp-app-plus-lib-with-tests"
protected open val mainCompileTasks = setOf(
":app:compileKotlinJvm",
":app:compileKotlinJs",
":app:compileKotlinNative",
":lib:compileKotlinJvm",
":lib:compileKotlinJs",
":lib:compileKotlinNative",
)
protected open fun withProject(gradleVersion: GradleVersion, test: TestProject.() -> Unit): Unit {
nativeProject(
projectName,
gradleVersion,
configureSubProjects = true,
test = test
)
}
protected open fun TestProject.touchAndGet(subproject: String, srcDir: String, name: String): Path {
val path = subProject(subproject).kotlinSourcesDir(srcDir).resolve(name)
path.appendText("private val nothingMuch = 24")
return path
}
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 = {}) {
build(gradleTask, buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertSuccessOrUTD(
allTasks = mainCompileTasks,
executedTasks = executedTasks
)
incrementalPath?.let {
assertIncrementalCompilation(listOf(it).relativizeTo(projectPath))
}
assertions()
}
}
}
@@ -0,0 +1,112 @@
/*
* 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.mpp.smoke
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.mpp.KmpIncrementalITBase
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
@DisplayName("Basic incremental scenarios with KMP - K2")
@MppGradlePluginTests
open class BasicIncrementalCompilationIT : KmpIncrementalITBase() {
@DisplayName("Base test case - local change, local recompilation")
@GradleTest
fun testStrictlyLocalChange(gradleVersion: GradleVersion): Unit = withProject(gradleVersion) {
build("assemble")
/**
* Step 1: touch app:common, no abi change
*/
testCase(
incrementalPath = touchAndGet("app", "commonMain", "PlainPublicClass.kt"),
executedTasks = setOf(
":app:compileKotlinJvm",
":app:compileKotlinJs",
":app:compileKotlinNative"
)
)
/**
* Step 2: touch app:jvm, no abi change
*/
val appJvmClassKt = touchAndGet("app", "jvmMain", "PlainPublicClassJvm.kt")
testCase(
incrementalPath = null, //TODO: it just doesn't print "Incremental compilation completed", why? (KT-63476)
executedTasks = setOf(":app:compileKotlinJvm")
) {
assertCompiledKotlinSources(listOf(appJvmClassKt).relativizeTo(projectPath), output)
}
/**
* Step 3: touch app:js, no abi change
*/
testCase(
incrementalPath = touchAndGet("app", "jsMain", "PlainPublicClassJs.kt"),
executedTasks = setOf(":app:compileKotlinJs"),
)
/**
* Step 4: touch app:native, no abi change
*/
touchAndGet("app", "nativeMain", "PlainPublicClassNative.kt")
testCase(
incrementalPath = null, // no native incremental compilation - see KT-62824
executedTasks = setOf(":app:compileKotlinNative"),
)
/**
* Step 5: touch lib:common, no abi change
*/
testCase(
incrementalPath = touchAndGet("lib", "commonMain", "CommonUtil.kt"),
executedTasks = mainCompileTasks // TODO: KT-62642 - bad compile avoidance here
)
/**
* Step 6: touch lib:jvm, no abi change
*/
val libJvmUtilKt = touchAndGet("lib", "jvmMain", "libJvmPlatformUtil.kt")
testCase(
incrementalPath = null, //TODO: it just doesn't print "Incremental compilation completed", why? (KT-63476)
executedTasks = setOf(":app:compileKotlinJvm", ":lib:compileKotlinJvm"),
) {
assertCompiledKotlinSources(listOf(libJvmUtilKt).relativizeTo(projectPath), output)
}
/**
* Step 7: touch lib:js, no abi change
*/
testCase(
incrementalPath = touchAndGet("lib", "jsMain", "libJsPlatformUtil.kt"),
executedTasks = setOf(":app:compileKotlinJs", ":lib:compileKotlinJs"),
)
/**
* Step 8: touch lib:native, no abi change
*/
touchAndGet("lib", "nativeMain", "libNativePlatformUtil.kt")
testCase(
incrementalPath = null,
executedTasks = setOf(":app:compileKotlinNative", ":lib:compileKotlinNative"),
)
}
}
@DisplayName("Basic incremental scenarios with Kotlin Multiplatform - K1")
class BasicIncrementalCompilationK1IT : BasicIncrementalCompilationIT() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copyEnsuringK1()
}
@@ -0,0 +1,115 @@
/*
* 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.mpp.smoke
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.mpp.KmpIncrementalITBase
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
@DisplayName("Basic incremental scenarios with tests in KMP - K2")
@MppGradlePluginTests
open class BasicTestIncrementalCompilationIT : KmpIncrementalITBase() {
override val mainCompileTasks: Set<String>
get() = setOf(
":app:compileTestKotlinJvm",
":lib:compileTestKotlinJvm",
":app:compileTestKotlinNative",
":lib:compileTestKotlinNative",
":app:compileTestKotlinJs",
":lib:compileTestKotlinJs",
":app:jsTest",
":lib:jsTest",
":app:jvmTest",
":lib:jvmTest",
":app:nativeTest",
":lib:nativeTest",
)
override val gradleTask: String
get() = "build"
@DisplayName("KMP tests are rebuilt when affected")
@GradleTest
fun testAffectingTestDependencies(gradleVersion: GradleVersion): Unit = withProject(gradleVersion) {
build("build")
/**
* Step 1 - touch lib/common, affect all tests in app and lib
*/
testCase(
incrementalPath = touchAndGet("lib", "commonMain", "CommonUtil.kt"),
executedTasks = mainCompileTasks,
)
/**
* Step 2 - touch app/common, affect all tests in app
*/
testCase(
incrementalPath = touchAndGet("app", "commonMain", "PlainPublicClass.kt"),
executedTasks = setOf(
":app:compileTestKotlinJvm",
":app:compileTestKotlinNative",
":app:compileTestKotlinJs",
":app:jsTest",
":app:jvmTest",
":app:nativeTest",
),
)
/**
* Step 3 - touch app/jvm, affect jvm tests in app
*/
val touchedAppJvm = touchAndGet("app", "jvmMain", "PlainPublicClassJvm.kt")
testCase(
incrementalPath = null,
executedTasks = setOf(
":app:compileTestKotlinJvm",
":app:jvmTest",
),
) {
assertCompiledKotlinSources(listOf(touchedAppJvm).relativizeTo(projectPath), output) //KT-63476
}
/**
* Step 4 - touch app/js, affect js tests in app
*/
testCase(
incrementalPath = touchAndGet("app", "jsMain", "PlainPublicClassJs.kt"),
executedTasks = setOf(
":app:compileTestKotlinJs",
":app:jsTest",
),
)
/**
* Step 5 - touch app/native, affect native tests in app
*/
touchAndGet("app", "nativeMain", "PlainPublicClassNative.kt")
testCase(
incrementalPath = null,
executedTasks = setOf(
":app:compileTestKotlinNative",
":app:nativeTest",
),
)
}
}
@DisplayName("Basic incremental scenarios with tests in KMP - K1")
class BasicTestIncrementalCompilationK1IT : BasicTestIncrementalCompilationIT() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copyEnsuringK1()
}
@@ -3,7 +3,7 @@
* 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.mpp
package org.jetbrains.kotlin.gradle.mpp.smoke
import org.gradle.api.logging.LogLevel
import org.gradle.util.GradleVersion
@@ -12,28 +12,20 @@ import org.jetbrains.kotlin.gradle.util.replaceFirst
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
open class ExpectActualIncrementalCompilationIT : KGPBaseTest() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copyEnsuringK2()
@DisplayName("Base test case - local change, local recompilation")
@GradleTest
fun testStrictlyLocalChange(gradleVersion: GradleVersion) {
nativeProject("expect-actual-fun-or-class-ic", gradleVersion) {
build("assemble")
val valueKtPath = kotlinSourcesDir("commonMain").resolve("Value.kt")
valueKtPath.appendText("val irrelevant = 2")
build("assemble", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertTasksExecuted(":compileKotlinJvm", ":compileKotlinJs", ":compileKotlinNative")
assertIncrementalCompilation(listOf(valueKtPath).relativizeTo(projectPath))
}
}
}
@DisplayName("File with actual declaration needs recompiling")
@GradleTest
fun testRecompilationOfActualFun(gradleVersion: GradleVersion) {
@@ -0,0 +1,71 @@
/*
* 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.mpp.smoke
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.junit.jupiter.api.DisplayName
@Disabled("KT-56963")
@DisplayName("Basic multi-module incremental scenarios with KMP - K2")
@MppGradlePluginTests
open class MultiModuleIncrementalCompilationIT : KGPBaseTest() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copyEnsuringK2()
@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
*/
}
@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
*/
}
@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
*/
//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
}
@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
*/
}
}
@DisplayName("Incremental scenarios with expect/actual - K1")
class MultiModuleIncrementalCompilationK1IT : MultiModuleIncrementalCompilationIT() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copyEnsuringK1()
}
@@ -0,0 +1,28 @@
plugins {
kotlin("multiplatform")
}
kotlin {
jvm()
js()
<SingleNativeTarget>("native")
}
kotlin {
js {
nodejs()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":lib"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}
@@ -0,0 +1,10 @@
import kotlin.test.Test
import kotlin.test.assertEquals
class TrivialTest {
@Test
fun sayYesSaysYes() {
assertEquals("Yes", sayYes())
}
}
@@ -0,0 +1,3 @@
actual class Detail {
actual val key: String = "definitely not ios"
}
@@ -0,0 +1,6 @@
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
@@ -0,0 +1,23 @@
plugins {
kotlin("multiplatform")
}
kotlin {
jvm()
js()
<SingleNativeTarget>("native")
}
kotlin {
js {
nodejs()
}
sourceSets {
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
}
}
@@ -0,0 +1,3 @@
actual fun calc(expression: String): Double {
return Double.NaN
}
@@ -0,0 +1,15 @@
import kotlin.test.Test
import kotlin.test.assertEquals
class AdvancedTest {
@Test
fun jsUtilReturns0() {
assertEquals(0, libJsPlatformUtil())
}
@Test
fun commonUtilTest() {
assertEquals(2, multiplyByTwo(1))
}
}
@@ -0,0 +1,3 @@
actual fun calc(expression: String): Double {
return expression.toDoubleOrNull()?.let { it * 2 } ?: 0.0
}
@@ -0,0 +1,15 @@
import kotlin.test.Test
import kotlin.test.assertEquals
class AdvancedTest {
@Test
fun jvmUtilReturns400() {
assertEquals(400, libJvmPlatformUtil())
}
@Test
fun commonUtilTest() {
assertEquals(2, multiplyByTwo(1))
}
}
@@ -0,0 +1,15 @@
import kotlin.test.Test
import kotlin.test.assertEquals
class AdvancedTest {
@Test
fun nativeUtilReturns800() {
assertEquals(800, libNativePlatformUtil())
}
@Test
fun commonUtilTest() {
assertEquals(2, multiplyByTwo(1))
}
}