diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index 9d80c8ec1b3..ac97d0495b6 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -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("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") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsIrBeIncrementalCompilationIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsIrBeIncrementalCompilationIT.kt new file mode 100644 index 00000000000..ef4647f8194 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsIrBeIncrementalCompilationIT.kt @@ -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 { + val cacheFiles = mutableMapOf() + 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() + 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("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") + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index cb00d0ada7f..b97a7ff4f67 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-multiple-artifacts/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-multiple-artifacts/gradle.properties index e0746025cb7..5bb57d50f23 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-multiple-artifacts/gradle.properties +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-multiple-artifacts/gradle.properties @@ -1,3 +1 @@ -kotlin.incremental.js.klib=true -kotlin.incremental.js.ir=true kotlin.daemon.jvmargs=-Dkotlin.js.compiler.legacy.force_enabled=true diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/app/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/app/build.gradle.kts new file mode 100644 index 00000000000..a4a8be0cccc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/app/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + kotlin("js") +} + +kotlin { + js { + nodejs { + } + binaries.executable() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/app/src/main/kotlin/App.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/app/src/main/kotlin/App.kt new file mode 100644 index 00000000000..ba7b11df162 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/app/src/main/kotlin/App.kt @@ -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)) +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/build.gradle.kts new file mode 100644 index 00000000000..877c415742c --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + kotlin("js").apply(false) +} + +group = "com.example" +version = "1.0" + +allprojects { + repositories { + mavenLocal() + mavenCentral() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/settings.gradle new file mode 100644 index 00000000000..fbba8326f05 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-rebuild-after-error/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = "kotlin-js-ir-ic-rebuild-after-error" + +include("app") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-remove-unused-dep/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-remove-unused-dep/gradle.properties deleted file mode 100644 index 5de000d1dda..00000000000 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-ir-ic-remove-unused-dep/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -kotlin.incremental.js.klib=true -kotlin.incremental.js.ir=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsIrICProject/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsIrICProject/gradle.properties deleted file mode 100644 index 1ad40858996..00000000000 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsIrICProject/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -kotlin.code.style=official -kotlin.incremental.js.ir=true -kotlin.incremental.js.klib=true