[JS IR] Test the incremental rebuild after an error from JS IR BE

Move JS IR BE IC integration tests in a separate file

 Test case for KT-56282
This commit is contained in:
Alexander Korepanov
2023-02-03 10:04:43 +00:00
committed by Space Team
parent 8cfe234140
commit 3a42b9846e
10 changed files with 251 additions and 138 deletions
@@ -9,11 +9,9 @@ import com.google.gson.Gson
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.configuration.WarningMode
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX
import org.jetbrains.kotlin.gradle.targets.js.ir.KLIB_TYPE
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectModules
@@ -128,132 +126,6 @@ class Kotlin2JsIrGradlePluginIT : AbstractKotlin2JsGradlePluginIT(true) {
}
}
@DisplayName("Check IR incremental cache invalidation by compiler args")
@GradleTest
fun testJsIrIncrementalCacheInvalidationByArgs(gradleVersion: GradleVersion) {
project("kotlin2JsIrICProject", gradleVersion) {
val buildConfig = buildGradleKts.readText()
fun setLazyInitializationArg(value: Boolean) {
buildGradleKts.writeText(buildConfig)
buildGradleKts.appendText(
"""
|
|tasks.named<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink>("compileDevelopmentExecutableKotlinJs") {
| kotlinOptions {
| freeCompilerArgs += "-Xir-property-lazy-initialization=$value"
| }
|}
""".trimMargin()
)
}
fun String.testScriptOutLines() = this.lines().mapNotNull {
val trimmed = it.removePrefix(">>> TEST OUT: ")
if (trimmed == it) null else trimmed
}
// -Xir-property-lazy-initialization default is true
build("nodeRun") {
assertTasksExecuted(":compileDevelopmentExecutableKotlinJs")
assertEquals(listOf("Hello, Gradle."), output.testScriptOutLines())
}
setLazyInitializationArg(false)
build("nodeRun") {
assertTasksExecuted(":compileDevelopmentExecutableKotlinJs")
assertEquals(listOf("TOP LEVEL!", "Hello, Gradle."), output.testScriptOutLines())
}
setLazyInitializationArg(true)
build("nodeRun") {
assertTasksExecuted(":compileDevelopmentExecutableKotlinJs")
assertEquals(listOf("Hello, Gradle."), output.testScriptOutLines())
}
}
}
@DisplayName("incremental compilation for JS IR consider multiple artifacts in one project")
@GradleTest
fun testJsIrIncrementalMultipleArtifacts(gradleVersion: GradleVersion) {
project("kotlin-js-ir-ic-multiple-artifacts", gradleVersion) {
build("compileDevelopmentExecutableKotlinJs") {
val cacheDir = projectPath.resolve("app/build/klib/cache/").toFile()
val cacheRootDirName = cacheDir.list()?.singleOrNull()
assertTrue("Lib cache root dir should contain 1 element 'version.hash'") {
cacheRootDirName?.startsWith("version.") ?: false
}
val cacheRootDir = cacheDir.resolve(cacheRootDirName!!)
val klibCacheDirs = cacheRootDir.list()
// 2 for lib.klib + 1 for stdlib + 1 for main
assertEquals(4, klibCacheDirs?.size, "cache should contain 4 dirs")
val libKlibCacheDirs = klibCacheDirs?.filter { dir -> dir.startsWith("lib.klib.") }
assertEquals(2, libKlibCacheDirs?.size, "cache should contain 2 dirs for lib.klib")
var lib = false
var libOther = false
cacheRootDir.listFiles()!!
.forEach {
it.listFiles()!!
.filter { it.isFile }
.forEach {
val text = it.readText()
// cache keeps the js code of compiled module, this substring from that js code
if (text.contains("root['kotlin-js-ir-ic-multiple-artifacts-lib']")) {
if (lib) {
error("lib should be only once in cache")
}
lib = true
}
// cache keeps the js code of compiled module, this substring from that js code
if (text.contains("root['kotlin-js-ir-ic-multiple-artifacts-lib-other']")) {
if (libOther) {
error("libOther should be only once in cache")
}
libOther = true
}
}
}
assertTrue("lib and libOther should be once in cache") {
lib && libOther
}
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
}
}
}
@DisplayName("Remove unused dependency from klib")
@GradleTest
fun testJsIrIncrementalKlibRemoveUnusedDependency(gradleVersion: GradleVersion) {
project("kotlin-js-ir-ic-remove-unused-dep", gradleVersion) {
val appBuildGradleKts = subProject("app").buildGradleKts
val buildGradleKtsWithoutDependency = appBuildGradleKts.readText()
appBuildGradleKts.appendText(
"""
|
|dependencies {
| implementation(project(":lib"))
|}
|
""".trimMargin()
)
build("compileDevelopmentExecutableKotlinJs") {
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
}
appBuildGradleKts.writeText(buildGradleKtsWithoutDependency)
build("compileDevelopmentExecutableKotlinJs") {
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
}
}
}
@DisplayName("falsify kotlin js compiler args")
@GradleTest
fun testFalsifyKotlinJsCompilerArgs(gradleVersion: GradleVersion) {
@@ -1545,10 +1417,11 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
Gson().fromJson(it.readText(), PackageJson::class.java)
}
@DisplayName("incremental compilation with multiple js modules after compilation error works")
@DisplayName("incremental compilation with multiple js modules after frontend compilation error works")
@GradleTest
fun testIncrementalCompilationWithMultipleModulesAfterCompilationError(gradleVersion: GradleVersion) {
project("kotlin-js-ir-ic-multiple-artifacts", gradleVersion) {
val buildOptions = defaultBuildOptions.copy(jsOptions = defaultJsOptions.copy(incrementalJsKlib = true))
project("kotlin-js-ir-ic-multiple-artifacts", gradleVersion, buildOptions = buildOptions) {
build("compileKotlinJs")
val libKt = subProject("lib").kotlinSourcesDir().resolve("Lib.kt") ?: error("No Lib.kt file in test project")
@@ -1558,7 +1431,7 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
buildAndFail("compileKotlinJs")
libKt.modify { it.replace("func answe", "fun answer") } // revert compilation error
appKt.modify { it.replace("Sheldon:", "Sheldon :") } // some change for incremental compilation
build("compileKotlinJs", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) {
build("compileKotlinJs", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
assertOutputContains(USING_JS_INCREMENTAL_COMPILATION_MESSAGE)
assertTasksUpToDate(":lib:compileKotlinJs")
assertTasksExecuted(":app:compileKotlinJs")
@@ -0,0 +1,201 @@
/*
* 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
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName
import kotlin.io.path.appendText
import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
@DisplayName("Incremental compilation tests for Kotlin JS IR backend")
@JsGradlePluginTests
class Kotlin2JsIrBeIncrementalCompilationIT : KGPBaseTest() {
override val defaultBuildOptions = BuildOptions(
jsOptions = BuildOptions.JsOptions(
jsCompilerType = KotlinJsCompilerType.IR,
incrementalJsKlib = true,
incrementalJsIr = true
)
)
@DisplayName("Test rebuild after backend error")
@GradleTest
fun testRebuildAfterError(gradleVersion: GradleVersion) {
project("kotlin-js-ir-ic-rebuild-after-error", gradleVersion) {
fun readCacheFiles(): Map<String, Int> {
val cacheFiles = mutableMapOf<String, Int>()
projectPath.resolve("app/build/klib/cache/").toFile().walk().forEach { cachedFile ->
if (cachedFile.isFile) {
cacheFiles[cachedFile.absolutePath] = cachedFile.readBytes().contentHashCode()
}
}
return cacheFiles
}
val srcFile = projectPath.resolve("app/src/main/kotlin/App.kt").toFile()
val badCode = srcFile.readText()
var successfulBuildCacheFiles = emptyMap<String, Int>()
srcFile.appendText("\nfun unknownFunction() = 1\n")
build("nodeDevelopmentRun") {
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
assertOutputContains("Hello, World!")
successfulBuildCacheFiles = readCacheFiles()
assertTrue("Cache should not be empty after successful build") { successfulBuildCacheFiles.isNotEmpty() }
}
srcFile.writeText(badCode)
for (i in 0..1) {
buildAndFail("nodeDevelopmentRun") {
assertTasksFailed(":app:compileDevelopmentExecutableKotlinJs")
val failedBuildCacheFiles = readCacheFiles()
assertEquals(successfulBuildCacheFiles, failedBuildCacheFiles, "The cache files should not be modified")
}
}
srcFile.writeText(badCode.replace("Hello, World!", "Hello, Kotlin!") + "\nfun unknownFunction() = 2\n")
build("nodeDevelopmentRun") {
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
assertOutputContains("Hello, Kotlin!")
val successfulRebuildCacheFiles = readCacheFiles()
assertEquals(successfulBuildCacheFiles.size, successfulRebuildCacheFiles.size, "The number of files must be the same")
assertNotEquals(successfulBuildCacheFiles, successfulRebuildCacheFiles, "The cache files should be modified")
}
}
}
@DisplayName("Test cache invalidation after modifying compiler args")
@GradleTest
fun testCacheInvalidationAfterCompilerArgModifying(gradleVersion: GradleVersion) {
project("kotlin2JsIrICProject", gradleVersion) {
val buildConfig = buildGradleKts.readText()
fun setLazyInitializationArg(value: Boolean) {
buildGradleKts.writeText(buildConfig)
buildGradleKts.appendText(
"""
|
|tasks.named<org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink>("compileDevelopmentExecutableKotlinJs") {
| kotlinOptions {
| freeCompilerArgs += "-Xir-property-lazy-initialization=$value"
| }
|}
""".trimMargin()
)
}
fun String.testScriptOutLines() = this.lines().mapNotNull {
val trimmed = it.removePrefix(">>> TEST OUT: ")
if (trimmed == it) null else trimmed
}
// -Xir-property-lazy-initialization default is true
build("nodeRun") {
assertTasksExecuted(":compileDevelopmentExecutableKotlinJs")
assertEquals(listOf("Hello, Gradle."), output.testScriptOutLines())
}
setLazyInitializationArg(false)
build("nodeRun") {
assertTasksExecuted(":compileDevelopmentExecutableKotlinJs")
assertEquals(listOf("TOP LEVEL!", "Hello, Gradle."), output.testScriptOutLines())
}
setLazyInitializationArg(true)
build("nodeRun") {
assertTasksExecuted(":compileDevelopmentExecutableKotlinJs")
assertEquals(listOf("Hello, Gradle."), output.testScriptOutLines())
}
}
}
@DisplayName("Test multiple artifacts in one project")
@GradleTest
fun testMultipleArtifacts(gradleVersion: GradleVersion) {
project("kotlin-js-ir-ic-multiple-artifacts", gradleVersion) {
build("compileDevelopmentExecutableKotlinJs") {
val cacheDir = projectPath.resolve("app/build/klib/cache/").toFile()
val cacheRootDirName = cacheDir.list()?.singleOrNull()
assertTrue("Lib cache root dir should contain 1 element 'version.hash'") {
cacheRootDirName?.startsWith("version.") ?: false
}
val cacheRootDir = cacheDir.resolve(cacheRootDirName!!)
val klibCacheDirs = cacheRootDir.list()
// 2 for lib.klib + 1 for stdlib + 1 for main
assertEquals(4, klibCacheDirs?.size, "cache should contain 4 dirs")
val libKlibCacheDirs = klibCacheDirs?.filter { dir -> dir.startsWith("lib.klib.") }
assertEquals(2, libKlibCacheDirs?.size, "cache should contain 2 dirs for lib.klib")
var lib = false
var libOther = false
cacheRootDir.listFiles()!!
.forEach {
it.listFiles()!!
.filter { it.isFile }
.forEach {
val text = it.readText()
// cache keeps the js code of compiled module, this substring from that js code
if (text.contains("root['kotlin-js-ir-ic-multiple-artifacts-lib']")) {
if (lib) {
error("lib should be only once in cache")
}
lib = true
}
// cache keeps the js code of compiled module, this substring from that js code
if (text.contains("root['kotlin-js-ir-ic-multiple-artifacts-lib-other']")) {
if (libOther) {
error("libOther should be only once in cache")
}
libOther = true
}
}
}
assertTrue("lib and libOther should be once in cache") {
lib && libOther
}
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
}
}
}
@DisplayName("Test removing unused dependency from klib")
@GradleTest
fun testRemoveUnusedDependency(gradleVersion: GradleVersion) {
project("kotlin-js-ir-ic-remove-unused-dep", gradleVersion) {
val appBuildGradleKts = subProject("app").buildGradleKts
val buildGradleKtsWithoutDependency = appBuildGradleKts.readText()
appBuildGradleKts.appendText(
"""
|
|dependencies {
| implementation(project(":lib"))
|}
|
""".trimMargin()
)
build("compileDevelopmentExecutableKotlinJs") {
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
}
appBuildGradleKts.writeText(buildGradleKtsWithoutDependency)
build("compileDevelopmentExecutableKotlinJs") {
assertTasksExecuted(":app:compileDevelopmentExecutableKotlinJs")
}
}
}
}
@@ -57,6 +57,7 @@ data class BuildOptions(
val jsCompilerType: KotlinJsCompilerType? = null,
val incrementalJs: Boolean? = null,
val incrementalJsKlib: Boolean? = null,
val incrementalJsIr: Boolean? = null,
val compileNoWarn: Boolean = true
)
@@ -119,6 +120,7 @@ data class BuildOptions(
if (jsOptions != null) {
jsOptions.incrementalJs?.let { arguments.add("-Pkotlin.incremental.js=$it") }
jsOptions.incrementalJsKlib?.let { arguments.add("-Pkotlin.incremental.js.klib=$it") }
jsOptions.incrementalJsIr?.let { arguments.add("-Pkotlin.incremental.js.ir=$it") }
jsOptions.useIrBackend?.let { arguments.add("-Pkotlin.js.useIrBackend=$it") }
jsOptions.jsCompilerType?.let { arguments.add("-Pkotlin.js.compiler=$it") }
// because we have legacy compiler tests, we need nowarn for compiler testing
@@ -1,3 +1 @@
kotlin.incremental.js.klib=true
kotlin.incremental.js.ir=true
kotlin.daemon.jvmargs=-Dkotlin.js.compiler.legacy.force_enabled=true
@@ -0,0 +1,11 @@
plugins {
kotlin("js")
}
kotlin {
js {
nodejs {
}
binaries.executable()
}
}
@@ -0,0 +1,17 @@
/*
* 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 com.example
private fun someFunction(x: Any? = null) {}
fun getSomething(x: dynamic) = with(x) {
someFunction(::unknownFunction)
"Hello, World!"
}
fun main() {
println(getSomething(null))
}
@@ -0,0 +1,13 @@
plugins {
kotlin("js").apply(false)
}
group = "com.example"
version = "1.0"
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
@@ -0,0 +1,3 @@
rootProject.name = "kotlin-js-ir-ic-rebuild-after-error"
include("app")
@@ -1,2 +0,0 @@
kotlin.incremental.js.klib=true
kotlin.incremental.js.ir=true
@@ -1,3 +0,0 @@
kotlin.code.style=official
kotlin.incremental.js.ir=true
kotlin.incremental.js.klib=true