From 3074b50e86782eb243a8e52783141691e49f9d3d Mon Sep 17 00:00:00 2001 From: Dmitrii Krasnov Date: Fri, 1 Sep 2023 12:52:44 +0200 Subject: [PATCH] Migrated GeneralNativeIT to junit 5 and gradle TestKit --- .../jetbrains/kotlin/gradle/BaseGradleIT.kt | 67 +- .../kotlin/gradle/BuildFusStatisticsIT.kt | 1 - .../kotlin/gradle/CompilerOptionsIT.kt | 15 +- .../kotlin/gradle/CompilerOptionsProjectIT.kt | 2 +- .../jetbrains/kotlin/gradle/ExplicitApiIT.kt | 2 +- .../kotlin/gradle/NewMultiplatformIT.kt | 14 +- .../jetbrains/kotlin/gradle/ResourcesIT.kt | 2 +- .../kotlin/gradle/experimental/TryK2IT.kt | 2 +- .../mpp/AggregatingKotlinTestReportIT.kt | 2 - .../jetbrains/kotlin/gradle/mpp/MppTestsIT.kt | 1 - .../kotlin/gradle/native/AppleFrameworkIT.kt | 18 +- .../kotlin/gradle/native/CocoaPodsIT.kt | 22 +- .../kotlin/gradle/native/GeneralNativeIT.kt | 1666 ++++++++--------- .../gradle/native/KotlinNativeLinkIT.kt | 5 +- .../native/NativeDownloadAndPlatformLibsIT.kt | 66 +- .../native/NativeEmbeddableCompilerJarIT.kt | 3 - .../gradle/native/NativeLibraryDslIT.kt | 5 +- .../native/NativeLibraryDslWithCocoapodsIT.kt | 2 +- .../kotlin/gradle/testbase/BuildOptions.kt | 3 +- .../gradle/testbase/compilationAssertions.kt | 1 - .../gradle/testbase/nativeTestHelpers.kt | 21 +- .../gradle/testbase/outputAssertions.kt | 91 + .../kotlin/gradle/testbase/outputHelpers.kt | 63 +- .../kotlin/gradle/testbase/pathHelpers.kt | 1 - .../kotlin/gradle/testbase/tasksAssertions.kt | 16 - .../kotlin/gradle/testbase/testAssertions.kt | 11 +- .../kotlin/gradle/testbase/testDsl.kt | 12 +- .../export-published-lib/lib/build.gradle.kts | 2 +- .../shared/build.gradle.kts | 2 +- .../frameworks/build.gradle.kts | 2 +- .../kotlin-dsl/build.gradle.kts | 2 +- .../libraries/build.gradle.kts | 2 +- .../testProject/native-cinterop/build.gradle | 2 +- .../native-compiler-version/build.gradle.kts | 2 +- .../native-kotlin-options/build.gradle | 2 +- .../native-parallel/build.gradle.kts | 5 +- .../native-parallel/one/build.gradle.kts | 3 +- .../native-parallel/two/build.gradle.kts | 3 +- .../build.gradle.kts | 2 +- .../gradle/plugin/PropertiesProvider.kt | 2 +- 40 files changed, 1106 insertions(+), 1039 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt index 2a447f0038b..f6b41e0dc6e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BaseGradleIT.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.* import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.konan.target.presetName import org.jetbrains.kotlin.test.RunnerWithMuteInDatabase import org.junit.After import org.junit.AfterClass @@ -273,7 +274,7 @@ abstract class BaseGradleIT { val useParsableDiagnosticsFormatting: Boolean = true, val showDiagnosticsStacktrace: Boolean? = false, // false by default to not clutter the testdata + stacktraces change often val stacktraceMode: String? = StacktraceOption.FULL_STACKTRACE_LONG_OPTION, - val konanDataDir: Path = Paths.get("build/.konan"), + val konanDataDir: Path = konanDir, ) { val safeAndroidGradlePluginVersion: AGPVersion get() = androidGradlePluginVersion ?: error("AGP version is expected to be set") @@ -847,6 +848,68 @@ abstract class BaseGradleIT { assertEquals(expectedTestResults, actualTestResults) } + /** + * Filter output for specific task with given [taskPath] + * + * Requires using [LogLevel.DEBUG]. + */ + fun CompiledProject.getOutputForTask(taskPath: String): String = getOutputForTask(taskPath, output) + + fun CompiledProject.withNativeCommandLineArguments( + vararg taskPaths: String, + toolName: NativeToolKind = NativeToolKind.KONANC, + check: (List) -> Unit, + ) = taskPaths.forEach { taskPath -> check(extractNativeCompilerCommandLineArguments(getOutputForTask(taskPath), toolName)) } + + internal fun transformNativeTestProject( + projectName: String, + wrapperVersion: GradleVersionRequired = defaultGradleVersion, + directoryPrefix: String? = null, + ): BaseGradleIT.Project { + val project = Project(projectName, wrapperVersion, directoryPrefix = directoryPrefix) + project.setupWorkingDir() + project.configureSingleNativeTarget() + project.gradleProperties().apply { + configureJvmMemory() + disableKotlinNativeCaches() + } + return project + } + + internal fun transformNativeTestProjectWithPluginDsl( + projectName: String, + wrapperVersion: GradleVersionRequired = defaultGradleVersion, + directoryPrefix: String? = null, + ): BaseGradleIT.Project { + val project = transformProjectWithPluginsDsl(projectName, wrapperVersion, directoryPrefix = directoryPrefix) + project.configureSingleNativeTarget() + project.gradleProperties().apply { + configureJvmMemory() + disableKotlinNativeCaches() + } + return project + } + + internal fun File.configureJvmMemory() { + appendText("\norg.gradle.jvmargs=-Xmx1g\n") + } + + internal fun File.disableKotlinNativeCaches() { + appendText("\nkotlin.native.cacheKind=none\n") + } + + private val SINGLE_NATIVE_TARGET_PLACEHOLDER = "" + + private fun Project.configureSingleNativeTarget(preset: String = HostManager.host.presetName) { + projectDir.walk() + .filter { it.isFile && (it.name == "build.gradle.kts" || it.name == "build.gradle") } + .forEach { file -> + file.modify { + it.replace(SINGLE_NATIVE_TARGET_PLACEHOLDER, preset) + } + } + } + private fun Project.createGradleTailParameters(options: BuildOptions, params: Array = arrayOf()): List = params.toMutableList().apply { when (minLogLevel) { @@ -939,7 +1002,7 @@ abstract class BaseGradleIT { add("-Pkotlin.internal.suppressGradlePluginErrors=PreHMPPFlagsError") } - add("-Pkonan.data.dir=${options.konanDataDir.absolutePathString()}") + add("-Pkonan.data.dir=${options.konanDataDir.absolutePathString().normalize()}") // Workaround: override a console type set in the user machine gradle.properties (since Gradle 4.3): add("--console=plain") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildFusStatisticsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildFusStatisticsIT.kt index 9b2edd03c13..585de4d7cf2 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildFusStatisticsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/BuildFusStatisticsIT.kt @@ -28,7 +28,6 @@ class BuildFusStatisticsIT : KGPDaemonsBaseTest() { // https://docs.gradle.org/8.0/release-notes.html#kotlin-dsl-updated-to-kotlin-api-level-1.8 , // and it registers old service, so we don't need check with re-registering old version service. if (gradleVersion < GradleVersion.version(TestVersions.Gradle.G_8_0)) { - // TODO(Dmitrii Krasnov): you can remove this check, when min gradle version becomes 8 or greater //kotlin 1.4 in kotlinDsl does not create jmx service yet assertOutputContains("Register JMX service for backward compatibility") } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsIT.kt index c97d1214591..da4a9863ddf 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsIT.kt @@ -223,9 +223,6 @@ internal class CompilerOptionsIT : KGPBaseTest() { project( projectName = "new-mpp-lib-and-app/sample-lib", gradleVersion = gradleVersion, - // We need to get specific task output as commonizer may run first producing - // arguments as well in output - buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG) ) { buildGradle.appendText( //language=Groovy @@ -256,7 +253,7 @@ internal class CompilerOptionsIT : KGPBaseTest() { build("compileNativeMainKotlinMetadata") { assertTasksExecuted(":compileNativeMainKotlinMetadata") - val taskOutput = getOutputForTask(":compileNativeMainKotlinMetadata") + val taskOutput = getOutputForTask(":compileNativeMainKotlinMetadata", logLevel = LogLevel.INFO) val arguments = parseCompilerArgumentsFromBuildOutput(K2NativeCompilerArguments::class, taskOutput) assertEquals( setOf("another.custom.UnderOptIn", "my.custom.OptInAnnotation"), arguments.optIn?.toSet(), @@ -266,7 +263,7 @@ internal class CompilerOptionsIT : KGPBaseTest() { build("compileKotlinLinux64") { assertTasksExecuted(":compileKotlinLinux64") - val taskOutput = getOutputForTask(":compileKotlinLinux64") + val taskOutput = getOutputForTask(":compileKotlinLinux64", logLevel = LogLevel.INFO) val arguments = parseCompilerArgumentsFromBuildOutput(K2NativeCompilerArguments::class, taskOutput) assertEquals( setOf("another.custom.UnderOptIn", "my.custom.OptInAnnotation"), arguments.optIn?.toSet(), @@ -293,7 +290,7 @@ internal class CompilerOptionsIT : KGPBaseTest() { """.trimMargin() ) - build("compileKotlinHost", forceOutput = true) { + build("compileKotlinHost") { val expectedOptIn = listOf("kotlin.RequiresOptIn", "my.CustomOptIn") val arguments = parseCompilerArguments() if (arguments.optIn?.toList() != listOf("kotlin.RequiresOptIn", "my.CustomOptIn")) { @@ -446,9 +443,6 @@ internal class CompilerOptionsIT : KGPBaseTest() { project( projectName = "new-mpp-lib-and-app/sample-lib", gradleVersion = gradleVersion, - // We need to get specific task output as commonizer may run first producing - // arguments as well in output - buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG) ) { buildGradle.appendText( //language=Groovy @@ -492,9 +486,6 @@ internal class CompilerOptionsIT : KGPBaseTest() { project( projectName = "new-mpp-lib-and-app/sample-lib", gradleVersion = gradleVersion, - // We need to get specific task output as commonizer may run first producing - // arguments as well in output - buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG) ) { buildGradle.modify { val buildScript = """ diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsProjectIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsProjectIT.kt index 2944e3ff7a6..ddfd7b364ca 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsProjectIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerOptionsProjectIT.kt @@ -546,7 +546,7 @@ class CompilerOptionsProjectIT : KGPBaseTest() { } build(":compileKotlinLinuxX64") { - extractNativeTasksCommandLineArgumentsFromOutput(":compileKotlinLinuxX64") { + extractNativeTasksCommandLineArgumentsFromOutput(":compileKotlinLinuxX64", logLevel = LogLevel.DEBUG) { assertCommandLineArgumentsContain("-language-version", "1.7") assertCommandLineArgumentsContain("-api-version", "1.7") assertCommandLineArgumentsContain("-progressive") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ExplicitApiIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ExplicitApiIT.kt index 25490a76256..0bfb4091185 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ExplicitApiIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ExplicitApiIT.kt @@ -143,7 +143,7 @@ class ExplicitApiIT : KGPBaseTest() { if (nativeTaskName != null) { build(nativeTaskName) { assertTasksExecuted(nativeTaskName) - extractNativeTasksCommandLineArgumentsFromOutput(nativeTaskName) { + extractNativeTasksCommandLineArgumentsFromOutput(nativeTaskName, logLevel = LogLevel.DEBUG) { assertCommandLineArgumentsContain("-Xexplicit-api=warning") } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt index bbf50de3ffc..fe3672c232c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/NewMultiplatformIT.kt @@ -7,17 +7,12 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.logging.LogLevel import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments -import org.jetbrains.kotlin.gradle.native.GeneralNativeIT.Companion.containsSequentially -import org.jetbrains.kotlin.gradle.native.GeneralNativeIT.Companion.withNativeCommandLineArguments -import org.jetbrains.kotlin.gradle.native.MPPNativeTargets -import org.jetbrains.kotlin.gradle.native.configureJvmMemory -import org.jetbrains.kotlin.gradle.native.transformNativeTestProject -import org.jetbrains.kotlin.gradle.native.transformNativeTestProjectWithPluginDsl import org.jetbrains.kotlin.gradle.plugin.ProjectLocalConfigurations import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin import org.jetbrains.kotlin.gradle.plugin.sources.METADATA_CONFIGURATION_NAME_SUFFIX import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget +import org.jetbrains.kotlin.gradle.testbase.MPPNativeTargets import org.jetbrains.kotlin.gradle.testbase.TestVersions import org.jetbrains.kotlin.gradle.testbase.assertHasDiagnostic import org.jetbrains.kotlin.gradle.testbase.assertNoDiagnostic @@ -1791,4 +1786,11 @@ open class NewMultiplatformIT : BaseGradleIT() { HostManager.hostIsMac -> "macosX64" else -> throw AssertionError("Host ${HostManager.host} is not supported for this test") } + + companion object { + fun List.containsSequentially(vararg elements: String): Boolean { + check(elements.isNotEmpty()) + return Collections.indexOfSubList(this, elements.toList()) != -1 + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ResourcesIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ResourcesIT.kt index 406914dac73..d71928a5e64 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ResourcesIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ResourcesIT.kt @@ -99,7 +99,7 @@ class ResourcesIT : KGPBaseTest() { """.trimMargin() ) - build("jar", forceOutput = true) { + build("jar") { assertFileInProjectExists("build/libs/simpleProject.jar") projectPath.resolve("build/libs/simpleProject.jar").assertZipArchiveContainsFilesOnce( listOf(mainResFile.name, additionalResFile.name) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/experimental/TryK2IT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/experimental/TryK2IT.kt index c092b5f1c8f..3e656b7e93a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/experimental/TryK2IT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/experimental/TryK2IT.kt @@ -144,7 +144,7 @@ class TryK2IT : KGPBaseTest() { """.trimMargin() ) - buildAndFail("build", forceOutput = true) { + buildAndFail("build") { assertOutputContains( """ |##### 'kotlin.experimental.tryK2' results ##### diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/AggregatingKotlinTestReportIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/AggregatingKotlinTestReportIT.kt index 9861850571e..a0a44e76a49 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/AggregatingKotlinTestReportIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/AggregatingKotlinTestReportIT.kt @@ -7,10 +7,8 @@ package org.jetbrains.kotlin.gradle.mpp import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.native.MPPNativeTargets import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.capitalize -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.DisplayName @MppGradlePluginTests diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppTestsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppTestsIT.kt index 7f11b6987d5..7d0e20a58da 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppTestsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/mpp/MppTestsIT.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.gradle.mpp import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.native.MPPNativeTargets import org.jetbrains.kotlin.gradle.testbase.* import org.junit.jupiter.api.DisplayName diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt index f9f2a1e0aeb..f2e32ccbe57 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/AppleFrameworkIT.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.gradle.native import org.gradle.api.JavaVersion -import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.replaceText @@ -460,32 +459,23 @@ class AppleFrameworkIT : KGPBaseTest() { ":iosApp:dependencyInsight", "--configuration", configuration, "--dependency", "iosLib" ) - fun variant(variantName: String) = - if (gradleVersion >= GradleVersion.version(TestVersions.Gradle.G_7_5)) { - "Variant $variantName" - } else { - "variant \"$variantName\"" - } - - fun BuildResult.assertContainsVariant(variantName: String) = assertOutputContains(variant(variantName)) - subProject("iosApp").buildGradleKts.replaceText("", "\"${TestVersions.AppleGradlePlugin.V222_0_21}\"") build(*dependencyInsight("iosAppIosX64DebugImplementation")) { - assertContainsVariant("mainDynamicDebugFrameworkIos") + assertOutputContainsNativeFrameworkVariant("mainDynamicDebugFrameworkIos", gradleVersion) } build(*dependencyInsight("iosAppIosX64ReleaseImplementation")) { - assertContainsVariant("mainDynamicReleaseFrameworkIos") + assertOutputContainsNativeFrameworkVariant("mainDynamicReleaseFrameworkIos", gradleVersion) } // NB: '0' is required at the end since dependency is added with custom attribute, and it creates new configuration build(*dependencyInsight("iosAppIosX64DebugImplementation0"), "-PmultipleFrameworks") { - assertContainsVariant("mainStaticDebugFrameworkIos") + assertOutputContainsNativeFrameworkVariant("mainStaticDebugFrameworkIos", gradleVersion) } build(*dependencyInsight("iosAppIosX64ReleaseImplementation0"), "-PmultipleFrameworks") { - assertOutputDoesNotContain(variant("mainStaticReleaseFrameworkIos")) + assertOutputContainsNativeFrameworkVariant("mainStaticReleaseFrameworkIos", gradleVersion) } } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt index 6f7c44508e0..c71b24d4c04 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/CocoaPodsIT.kt @@ -705,24 +705,10 @@ class CocoaPodsIT : KGPBaseTest() { assertTasksExecuted(":cinteropSDWebImageIOS") - // TODO(Dmitrii Krasnov): rewrite it, when GeneralNativeIT will be migrated to new test dsl - assertOutputContains( - """ - | -linker-option - | -framework - | -linker-option - | AFNetworking - """.trimMargin() - ) - - assertOutputContains( - """ - | -linker-option - | -framework - | -linker-option - | SSZipArchive - """.trimMargin() - ) + extractNativeTasksCommandLineArgumentsFromOutput(":linkPodDebugFrameworkIOS") { + assertCommandLineArgumentsContainSequentially("-linker-option", "-framework", "-linker-option", "AFNetworking") + assertCommandLineArgumentsContainSequentially("-linker-option", "-framework", "-linker-option", "SSZipArchive") + } } } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt index 9e45cca8d3f..afe20f4a9cb 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/GeneralNativeIT.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -7,426 +7,359 @@ package org.jetbrains.kotlin.gradle.native import com.intellij.testFramework.TestDataFile import org.gradle.api.logging.LogLevel +import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion import org.jdom.input.SAXBuilder -import org.jetbrains.kotlin.gradle.* import org.jetbrains.kotlin.gradle.internals.KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS_PROPERTY import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.plugin.mpp.NativeOutputKind import org.jetbrains.kotlin.gradle.testbase.* -import org.jetbrains.kotlin.gradle.testbase.TestVersions.Kotlin.STABLE_RELEASE -import org.jetbrains.kotlin.gradle.util.modify +import org.jetbrains.kotlin.gradle.util.capitalize +import org.jetbrains.kotlin.gradle.util.replaceText import org.jetbrains.kotlin.gradle.util.runProcess import org.jetbrains.kotlin.konan.target.* -import org.junit.Assume -import org.junit.Ignore -import org.junit.Test -import java.io.File -import java.nio.file.Files +import org.jetbrains.kotlin.tooling.core.KotlinToolingVersion +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.condition.OS +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path import java.util.* import kotlin.io.path.absolutePathString +import kotlin.io.path.appendText +import kotlin.io.path.deleteIfExists +import kotlin.io.path.readText import kotlin.test.assertEquals -import kotlin.test.assertFalse import kotlin.test.assertTrue +import kotlin.test.fail -internal object MPPNativeTargets { - val current = when (HostManager.host) { - KonanTarget.LINUX_X64 -> "linux64" - KonanTarget.MACOS_X64 -> "macos64" - KonanTarget.MACOS_ARM64 -> "macosArm64" - KonanTarget.MINGW_X64 -> "mingw64" - else -> error("Unsupported host") - } +@DisplayName("Tests for general K/N builds") +@NativeGradlePluginTests +class GeneralNativeIT : KGPBaseTest() { - val unsupported = when { - HostManager.hostIsMingw -> setOf("macos64") - HostManager.hostIsLinux -> setOf("macos64") - HostManager.hostIsMac -> emptySet() - else -> error("Unknown host") - } + private val nativeHostTargetName = MPPNativeTargets.current - val supported = listOf("linux64", "macos64", "mingw64").filter { !unsupported.contains(it) } -} + @DisplayName("K/N compiler can be started in-process in parallel") + @GradleTest + fun shouldCheckKNativeCompilerStartedInParallel(gradleVersion: GradleVersion) { + nativeProject("native-parallel", gradleVersion) { + val synchronisationBlock = + """ + tasks.getByPath("compileKotlinLinux").doFirst { + val countDownLatch = rootProject.extra["countDownLatch"] as CountDownLatch + countDownLatch.countDown() + countDownLatch.await() + } + """.trimIndent() + buildGradleKts.appendText( + """ + val countDownLatch by extra(CountDownLatch(2)) + """.trimIndent() + ) + // we are adding synchronisation before compileKotlinLinux execution in each subproject for making + // at least the execution of these tasks parallel + subProject("one").buildGradleKts.appendText(synchronisationBlock) + subProject("two").buildGradleKts.appendText(synchronisationBlock) -internal fun BaseGradleIT.transformNativeTestProject( - projectName: String, - wrapperVersion: GradleVersionRequired = defaultGradleVersion, - directoryPrefix: String? = null -): BaseGradleIT.Project { - val project = Project(projectName, wrapperVersion, directoryPrefix = directoryPrefix) - project.setupWorkingDir() - project.configureSingleNativeTarget() - project.gradleProperties().apply { - configureJvmMemory() - disableKotlinNativeCaches() - } - return project -} - -internal fun BaseGradleIT.transformNativeTestProjectWithPluginDsl( - projectName: String, - wrapperVersion: GradleVersionRequired = defaultGradleVersion, - directoryPrefix: String? = null -): BaseGradleIT.Project { - val project = transformProjectWithPluginsDsl(projectName, wrapperVersion, directoryPrefix = directoryPrefix) - project.configureSingleNativeTarget() - project.gradleProperties().apply { - configureJvmMemory() - disableKotlinNativeCaches() - } - return project -} - -internal fun File.configureJvmMemory() { - appendText("\norg.gradle.jvmargs=-Xmx1g\n") -} - -internal fun File.disableKotlinNativeCaches() { - appendText("\nkotlin.native.cacheKind=none\n") -} - -private const val SINGLE_NATIVE_TARGET_PLACEHOLDER = "" - -private fun BaseGradleIT.Project.configureSingleNativeTarget(preset: String = HostManager.host.presetName) { - projectDir.walk() - .filter { it.isFile && (it.name == "build.gradle.kts" || it.name == "build.gradle") } - .forEach { file -> - file.modify { - it.replace(SINGLE_NATIVE_TARGET_PLACEHOLDER, preset) - } - } -} - -@OptIn(InternalKotlinGradlePluginApi::class) -class GeneralNativeIT : BaseGradleIT() { - - val nativeHostTargetName = MPPNativeTargets.current - - private fun Project.targetClassesDir(targetName: String, sourceSetName: String = "main") = - classesDir(sourceSet = "$targetName/$sourceSetName") - - override val defaultGradleVersion: GradleVersionRequired - get() = GradleVersionRequired.FOR_MPP_SUPPORT - - @Test - fun testParallelExecutionSmoke(): Unit = with(transformNativeTestProjectWithPluginDsl("native-parallel")) { - // Check that the K/N compiler can be started in-process in parallel. - build(":one:compileKotlinLinux", ":two:compileKotlinLinux") { - assertSuccessful() + build(":one:compileKotlinLinux", ":two:compileKotlinLinux") } } - @Test - fun testIncorrectDependenciesWarning() = with(transformNativeTestProject("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) { - gradleBuildScript().modify { - it.replace( + @DisplayName("Build with ignoreIncorrectDependencies turned on") + @GradleTest + fun testIncorrectDependenciesWarning(gradleVersion: GradleVersion) { + nativeProject("new-mpp-lib-and-app/sample-lib", gradleVersion) { + buildGradle.replaceText( "api 'org.jetbrains.kotlin:kotlin-stdlib-common'", "compileOnly 'org.jetbrains.kotlin:kotlin-stdlib-common'" ) - } - build { - assertSuccessful() - assertContains("A compileOnly dependency is used in the Kotlin/Native target") - } - build("-Pkotlin.native.ignoreIncorrectDependencies=true") { - assertSuccessful() - assertNotContains("A compileOnly dependency is used in the Kotlin/Native target") + build { + assertOutputContains("A compileOnly dependency is used in the Kotlin/Native target") + } + build("-Pkotlin.native.ignoreIncorrectDependencies=true") { + assertOutputDoesNotContain("A compileOnly dependency is used in the Kotlin/Native target") + } } } - @Test - fun testCanProduceNativeLibraries() = with(transformNativeTestProjectWithPluginDsl("libraries", directoryPrefix = "native-binaries")) { - val baseName = "native_library" + @DisplayName("Can produce native libraries") + @GradleTest + fun testCanProduceNativeLibraries(gradleVersion: GradleVersion) { + nativeProject( + "native-binaries/libraries", + gradleVersion, + configureSubProjects = true + ) { + val baseName = "native_library" - val sharedPrefix = CompilerOutputKind.DYNAMIC.prefix(HostManager.host) - val sharedSuffix = CompilerOutputKind.DYNAMIC.suffix(HostManager.host) - val sharedPaths = listOf( - "build/bin/host/debugShared/$sharedPrefix$baseName$sharedSuffix", - "build/bin/host/releaseShared/$sharedPrefix$baseName$sharedSuffix", - ) + val sharedPrefix = CompilerOutputKind.DYNAMIC.prefix(HostManager.host) + val sharedSuffix = CompilerOutputKind.DYNAMIC.suffix(HostManager.host) + val sharedPaths = listOf( + "build/bin/host/debugShared/$sharedPrefix$baseName$sharedSuffix", + "build/bin/host/releaseShared/$sharedPrefix$baseName$sharedSuffix", + ) - val staticPrefix = CompilerOutputKind.STATIC.prefix(HostManager.host) - val staticSuffix = CompilerOutputKind.STATIC.suffix(HostManager.host) - val staticPaths = listOf( - "build/bin/host/debugStatic/$staticPrefix$baseName$staticSuffix", - "build/bin/host/releaseStatic/$staticPrefix$baseName$staticSuffix", - ) + val staticPrefix = CompilerOutputKind.STATIC.prefix(HostManager.host) + val staticSuffix = CompilerOutputKind.STATIC.suffix(HostManager.host) + val staticPaths = listOf( + "build/bin/host/debugStatic/$staticPrefix$baseName$staticSuffix", + "build/bin/host/releaseStatic/$staticPrefix$baseName$staticSuffix", + ) - val headerPaths = listOf( - "build/bin/host/debugShared/$sharedPrefix${baseName}_api.h", - "build/bin/host/debugStatic/$staticPrefix${baseName}_api.h", - "build/bin/host/releaseShared/$sharedPrefix${baseName}_api.h", - "build/bin/host/releaseStatic/$staticPrefix${baseName}_api.h", - ) + val headerPaths = listOf( + "build/bin/host/debugShared/$sharedPrefix${baseName}_api.h", + "build/bin/host/debugStatic/$staticPrefix${baseName}_api.h", + "build/bin/host/releaseShared/$sharedPrefix${baseName}_api.h", + "build/bin/host/releaseStatic/$staticPrefix${baseName}_api.h", + ) - val klibPrefix = CompilerOutputKind.LIBRARY.prefix(HostManager.host) - val klibSuffix = CompilerOutputKind.LIBRARY.suffix(HostManager.host) - val klibPath = "${targetClassesDir("host")}${klibPrefix}/klib/native-library$klibSuffix" + val klibPrefix = CompilerOutputKind.LIBRARY.prefix(HostManager.host) + val klibSuffix = CompilerOutputKind.LIBRARY.suffix(HostManager.host) + val klibPath = "${kotlinClassesDir(targetName = "host")}${klibPrefix}/klib/native-library$klibSuffix" - val linkTasks = listOf( - ":linkDebugSharedHost", - ":linkDebugStaticHost", - ":linkReleaseSharedHost", - ":linkReleaseStaticHost", - ) + val linkTasks = listOf( + ":linkDebugSharedHost", + ":linkDebugStaticHost", + ":linkReleaseSharedHost", + ":linkReleaseStaticHost", + ) - val klibTask = ":compileKotlinHost" + val klibTask = ":compileKotlinHost" - build(":assemble") { - assertSuccessful() - assertTasksExecuted(linkTasks + klibTask) + build(":assemble") { + assertTasksExecuted(linkTasks + klibTask) - sharedPaths.forEach { assertFileExists(it) } - staticPaths.forEach { assertFileExists(it) } - headerPaths.forEach { - assertFileExists(it) - assertFileContains(it, "_KInt (*exported)();") + sharedPaths.forEach { assertFileInProjectExists(it) } + staticPaths.forEach { assertFileInProjectExists(it) } + headerPaths.forEach { + assertFileInProjectExists(it) + assertFileInProjectContains(it, "_KInt (*exported)();") + } + assertFileInProjectExists(klibPath) } - assertFileExists(klibPath) - } - // Test that all up-to date checks are correct - build(":assemble") { - assertSuccessful() - assertTasksUpToDate(linkTasks) - assertTasksUpToDate(klibTask) - } + // Test that all up-to date checks are correct + build(":assemble") { + assertTasksUpToDate(linkTasks) + assertTasksUpToDate(klibTask) + } - // Remove header of one of libraries and check that it is rebuilt. - assertTrue(projectDir.resolve(headerPaths[0]).delete()) - build(":assemble") { - assertSuccessful() - assertTasksUpToDate(linkTasks.drop(1)) - assertTasksUpToDate(klibTask) - assertTasksExecuted(linkTasks[0]) + // Remove header of one of libraries and check that it is rebuilt. + assertTrue(projectPath.resolve(headerPaths[0]).deleteIfExists()) + build(":assemble") { + assertTasksUpToDate(linkTasks.drop(1)) + assertTasksUpToDate(klibTask) + assertTasksExecuted(linkTasks[0]) + } } } - @Test - fun testCanProvideNativeFrameworkArtifact() = with( - transformNativeTestProjectWithPluginDsl("frameworks", directoryPrefix = "native-binaries") - ) { - Assume.assumeTrue(HostManager.hostIsMac) - - gradleBuildScript().appendText( - """ - val frameworkTargets = Attribute.of( - "org.jetbrains.kotlin.native.framework.targets", - Set::class.java - ) - val kotlinNativeBuildTypeAttribute = Attribute.of( - "org.jetbrains.kotlin.native.build.type", - String::class.java - ) - - fun validateConfiguration(conf: Configuration, targets: Set, expectedBuildType: String) { - if (conf.artifacts.files.count() != 1 || conf.artifacts.files.singleFile.name != "main.framework") { - throw IllegalStateException("No single artifact with proper name \"main.framework\"") + @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) + @DisplayName("Can provide native framework artifact") + @GradleTest + fun testCanProvideNativeFrameworkArtifact(gradleVersion: GradleVersion) { + nativeProject("native-binaries/frameworks", gradleVersion = gradleVersion) { + buildGradleKts.appendText( + //language=kotlin + """ + val frameworkTargets = Attribute.of( + "org.jetbrains.kotlin.native.framework.targets", + Set::class.java + ) + val kotlinNativeBuildTypeAttribute = Attribute.of( + "org.jetbrains.kotlin.native.build.type", + String::class.java + ) + + fun validateConfiguration(conf: Configuration, targets: Set, expectedBuildType: String) { + if (conf.artifacts.files.count() != 1 || conf.artifacts.files.singleFile.name != "main.framework") { + throw IllegalStateException("No single artifact with proper name \"main.framework\"") + } + val confTargets = conf.attributes.getAttribute(frameworkTargets)!! + val buildType = conf.attributes.getAttribute(kotlinNativeBuildTypeAttribute)!! + if (confTargets.size != targets.size || !confTargets.containsAll(targets)) { + throw IllegalStateException("Framework has incorrect attributes. Expected targets: \"${'$'}targets\", actual: \"${'$'}confTargets\"") + } + if (buildType != expectedBuildType) { + throw IllegalStateException("Framework has incorrect attributes. Expected build type: \"${'$'}expectedBuildType\", actual: \"${'$'}buildType\"") + } } - val confTargets = conf.attributes.getAttribute(frameworkTargets)!! - val buildType = conf.attributes.getAttribute(kotlinNativeBuildTypeAttribute)!! - if (confTargets.size != targets.size || !confTargets.containsAll(targets)) { - throw IllegalStateException("Framework has incorrect attributes. Expected targets: \"${'$'}targets\", actual: \"${'$'}confTargets\"") + + tasks.register("validateThinArtifacts") { + doLast { + val targets = listOf("ios" to "ios_arm64", "iosSim" to "ios_x64") + val buildTypes = listOf("release", "debug") + targets.forEach { (name, target) -> + buildTypes.forEach { buildType -> + val conf = project.configurations.getByName("main${'$'}{buildType.capitalize()}Framework${'$'}{name.capitalize()}") + validateConfiguration(conf, setOf(target), buildType.toUpperCase()) + } + } + } } - if (buildType != expectedBuildType) { - throw IllegalStateException("Framework has incorrect attributes. Expected build type: \"${'$'}expectedBuildType\", actual: \"${'$'}buildType\"") - } - } - - tasks.register("validateThinArtifacts") { - doLast { - val targets = listOf("ios" to "ios_arm64", "iosSim" to "ios_x64") - val buildTypes = listOf("release", "debug") - targets.forEach { (name, target) -> + + tasks.register("validateFatArtifacts") { + doLast { + val buildTypes = listOf("release", "debug") buildTypes.forEach { buildType -> - val conf = project.configurations.getByName("main${'$'}{buildType.capitalize()}Framework${'$'}{name.capitalize()}") - validateConfiguration(conf, setOf(target), buildType.toUpperCase()) + val conf = project.configurations.getByName("main${'$'}{buildType.capitalize()}FrameworkIosFat") + validateConfiguration(conf, setOf("ios_x64", "ios_arm64"), buildType.toUpperCase()) } } } - } - - tasks.register("validateFatArtifacts") { - doLast { - val buildTypes = listOf("release", "debug") - buildTypes.forEach { buildType -> - val conf = project.configurations.getByName("main${'$'}{buildType.capitalize()}FrameworkIosFat") - validateConfiguration(conf, setOf("ios_x64", "ios_arm64"), buildType.toUpperCase()) + + tasks.register("validateCustomAttributesSetting") { + doLast { + val conf = project.configurations.getByName("customReleaseFrameworkIos") + val attr1Value = conf.attributes.getAttribute(disambiguation1Attribute) + if (attr1Value != "someValue") { + throw IllegalStateException("myDisambiguation1Attribute has incorrect value. Expected: \"someValue\", actual: \"${'$'}attr1Value\"") + } + val attr2Value = conf.attributes.getAttribute(disambiguation2Attribute) + if (attr2Value != "someValue2") { + throw IllegalStateException("myDisambiguation2Attribute has incorrect value. Expected: \"someValue2\", actual: \"${'$'}attr2Value\"") + } } } - } - - tasks.register("validateCustomAttributesSetting") { - doLast { - val conf = project.configurations.getByName("customReleaseFrameworkIos") - val attr1Value = conf.attributes.getAttribute(disambiguation1Attribute) - if (attr1Value != "someValue") { - throw IllegalStateException("myDisambiguation1Attribute has incorrect value. Expected: \"someValue\", actual: \"${'$'}attr1Value\"") - } - val attr2Value = conf.attributes.getAttribute(disambiguation2Attribute) - if (attr2Value != "someValue2") { - throw IllegalStateException("myDisambiguation2Attribute has incorrect value. Expected: \"someValue2\", actual: \"${'$'}attr2Value\"") - } - } - } - """.trimIndent() - ) - - build(":validateThinArtifacts") { - assertSuccessful() - } - - build(":validateFatArtifacts") { - assertSuccessful() - } - - build(":validateCustomAttributesSetting") { - assertSuccessful() - } - } - - @Test - fun testCanProduceNativeFrameworks() = with( - transformNativeTestProjectWithPluginDsl("frameworks", directoryPrefix = "native-binaries") - ) { - fun assemble(check: CompiledProject.() -> Unit) { - build( - "assemble", - check = check + """.trimIndent() ) - } - Assume.assumeTrue(HostManager.hostIsMac) - - data class BinaryMeta(val name: String, val isStatic: Boolean = false) - - val frameworkPrefix = CompilerOutputKind.FRAMEWORK.prefix(HostManager.host) - val frameworkSuffix = CompilerOutputKind.FRAMEWORK.suffix(HostManager.host) - val targets = listOf("ios", "iosSim") - val binaries = mapOf( - "ios" to listOf(BinaryMeta("main"), BinaryMeta("custom", true)), - "iosSim" to listOf(BinaryMeta("main")) - ) - val frameworkPaths = targets.flatMap { target -> - binaries.getValue(target).flatMap { - val list = listOf( - "build/bin/$target/${it.name}DebugFramework/$frameworkPrefix${it.name}$frameworkSuffix", - "build/bin/$target/${it.name}ReleaseFramework/$frameworkPrefix${it.name}$frameworkSuffix", - ) - if (it.isStatic) { - list - } else { - list + "build/bin/$target/${it.name}DebugFramework/$frameworkPrefix${it.name}$frameworkSuffix.dSYM" - } - } - } - - val headerPaths = targets.flatMap { target -> - binaries.getValue(target).flatMap { - listOf( - "build/bin/$target/${it.name}DebugFramework/$frameworkPrefix${it.name}$frameworkSuffix/headers/${it.name}.h", - "build/bin/$target/${it.name}ReleaseFramework/$frameworkPrefix${it.name}$frameworkSuffix/headers/${it.name}.h", - ) - } - } - - val frameworkTasks = targets.flatMap { target -> - binaries.getValue(target).flatMap { - listOf( - ":link${it.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}DebugFramework${ - target.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() - } - }", - ":link${it.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}ReleaseFramework${ - target.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() - } - }", - ) - } - } - - // Check building - // Check dependency exporting and bitcode embedding in frameworks. - assemble { - assertSuccessful() - headerPaths.forEach { assertFileExists(it) } - frameworkPaths.forEach { assertFileExists(it) } - - assertTrue(fileInWorkingDir(headerPaths[0]).readText().contains("+ (int32_t)exported")) - val xcodeMajorVersion = Xcode.findCurrent().version.major - - // Check that by default release frameworks have bitcode embedded. - withNativeCommandLineArguments(":linkMainReleaseFrameworkIos") { arguments -> - if (xcodeMajorVersion < 14) { - assertTrue("-Xembed-bitcode" in arguments) - } else { - assertFalse("-Xembed-bitcode" in arguments) - } - assertTrue("-opt" in arguments) - } - // Check that by default debug frameworks have bitcode marker embedded. - withNativeCommandLineArguments(":linkMainDebugFrameworkIos") { arguments -> - if (xcodeMajorVersion < 14) { - assertTrue("-Xembed-bitcode-marker" in arguments) - } else { - assertFalse("-Xembed-bitcode-marker" in arguments) - } - assertTrue("-g" in arguments) - } - // Check that bitcode can be disabled by setting custom compiler options - withNativeCommandLineArguments(":linkCustomDebugFrameworkIos") { arguments -> - assertTrue(arguments.containsSequentially("-linker-option", "-L.")) - assertTrue("-Xtime" in arguments) - assertTrue("-Xstatic-framework" in arguments) - assertFalse("-Xembed-bitcode-marker" in arguments) - assertFalse("-Xembed-bitcode" in arguments) - } - // Check that bitcode is disabled for iOS simulator. - withNativeCommandLineArguments(":linkMainReleaseFrameworkIosSim", ":linkMainDebugFrameworkIosSim") { arguments -> - assertFalse("-Xembed-bitcode" in arguments) - assertFalse("-Xembed-bitcode-marker" in arguments) - } - } - - assemble { - assertSuccessful() - assertTasksUpToDate(frameworkTasks) - } - - assertTrue(projectDir.resolve(headerPaths[0]).delete()) - assemble { - assertSuccessful() - assertTasksUpToDate(frameworkTasks.drop(1)) - assertTasksExecuted(frameworkTasks[0]) + build(":validateThinArtifacts") + build(":validateFatArtifacts") + build(":validateCustomAttributesSetting") } } - @Test - fun testExportApiOnlyToLibraries() { - val project = transformNativeTestProjectWithPluginDsl("libraries", directoryPrefix = "native-binaries") + @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) + @DisplayName("Can provide native framework") + @GradleTest + fun testCanProduceNativeFrameworks(gradleVersion: GradleVersion) { + nativeProject("native-binaries/frameworks", gradleVersion = gradleVersion) { + fun assemble(assertions: BuildResult.() -> Unit) { + build("assemble", assertions = assertions) + } + data class BinaryMeta(val name: String, val isStatic: Boolean = false) + + val frameworkPrefix = CompilerOutputKind.FRAMEWORK.prefix(HostManager.host) + val frameworkSuffix = CompilerOutputKind.FRAMEWORK.suffix(HostManager.host) + val targets = listOf("ios", "iosSim") + val binaries = mapOf( + "ios" to listOf(BinaryMeta("main"), BinaryMeta("custom", true)), + "iosSim" to listOf(BinaryMeta("main")) + ) + val frameworkPaths = targets.flatMap { target -> + binaries.getValue(target).flatMap { + val list = listOf( + "build/bin/$target/${it.name}DebugFramework/$frameworkPrefix${it.name}$frameworkSuffix", + "build/bin/$target/${it.name}ReleaseFramework/$frameworkPrefix${it.name}$frameworkSuffix", + ) + if (it.isStatic) { + list + } else { + list + "build/bin/$target/${it.name}DebugFramework/$frameworkPrefix${it.name}$frameworkSuffix.dSYM" + } + } + } + + val headerPaths = targets.flatMap { target -> + binaries.getValue(target).flatMap { + listOf( + "build/bin/$target/${it.name}DebugFramework/$frameworkPrefix${it.name}$frameworkSuffix/headers/${it.name}.h", + "build/bin/$target/${it.name}ReleaseFramework/$frameworkPrefix${it.name}$frameworkSuffix/headers/${it.name}.h", + ) + } + } + + val frameworkTasks = targets.flatMap { target -> + binaries.getValue(target).flatMap { + listOf( + ":link${it.name.capitalize()}DebugFramework${target.capitalize()}", + ":link${it.name.capitalize()}ReleaseFramework${target.capitalize()}", + ) + } + } + + // Check building + // Check dependency exporting and bitcode embedding in frameworks. + assemble { + headerPaths.forEach { assertFileInProjectExists(it) } + frameworkPaths.forEach { assertDirectoryInProjectExists(it) } + + assertFileInProjectContains(headerPaths[0], "+ (int32_t)exported") + val xcodeMajorVersion = Xcode.findCurrent().version.major + + // Check that by default release frameworks have bitcode embedded. + extractNativeTasksCommandLineArgumentsFromOutput(":linkMainReleaseFrameworkIos") { + if (xcodeMajorVersion < 14) { + assertCommandLineArgumentsContain("-Xembed-bitcode") + } else { + assertCommandLineArgumentsDoNotContain("-Xembed-bitcode") + } + assertCommandLineArgumentsContain("-opt") + } + // Check that by default debug frameworks have bitcode marker embedded. + extractNativeTasksCommandLineArgumentsFromOutput(":linkMainDebugFrameworkIos") { + if (xcodeMajorVersion < 14) { + assertCommandLineArgumentsContain("-Xembed-bitcode-marker") + } else { + assertCommandLineArgumentsDoNotContain("-Xembed-bitcode-marker") + } + assertCommandLineArgumentsContain("-g") + } + // Check that bitcode can be disabled by setting custom compiler options + extractNativeTasksCommandLineArgumentsFromOutput(":linkCustomDebugFrameworkIos") { + assertCommandLineArgumentsContainSequentially("-linker-option", "-L.") + assertCommandLineArgumentsContain( + "-Xtime", + "-Xstatic-framework", + ) + assertCommandLineArgumentsDoNotContain( + "-Xembed-bitcode-marker", + "-Xembed-bitcode", + ) + } + // Check that bitcode is disabled for iOS simulator. + extractNativeTasksCommandLineArgumentsFromOutput(":linkMainReleaseFrameworkIosSim", ":linkMainDebugFrameworkIosSim") { + assertCommandLineArgumentsDoNotContain( + "-Xembed-bitcode-marker", + "-Xembed-bitcode" + ) + } + } + + assemble { + assertTasksUpToDate(frameworkTasks) + } + + assertTrue(projectPath.resolve(headerPaths[0]).deleteIfExists()) + assemble { + assertTasksUpToDate(frameworkTasks.drop(1)) + assertTasksExecuted(frameworkTasks[0]) + } + } + } + + @DisplayName("Checks exporting non api library") + @GradleTest + fun shouldFailOnExportingNonApiLibrary(gradleVersion: GradleVersion) { testExportApi( - project, listOf( + nativeProject("native-binaries/libraries", gradleVersion, configureSubProjects = true), + listOf( ExportApiTestData("linkDebugSharedHost", "debugShared"), ExportApiTestData("linkDebugStaticHost", "debugStatic"), ) ) } - @Test - fun testExportApiOnlyToFrameworks() { - Assume.assumeTrue(HostManager.hostIsMac) - val project = transformNativeTestProjectWithPluginDsl("frameworks", directoryPrefix = "native-binaries") - + @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) + @DisplayName("Checks exporting non api framework") + @GradleTest + fun testExportApiOnlyToFrameworks(gradleVersion: GradleVersion) { testExportApi( - project, listOf( + nativeProject("native-binaries/frameworks", gradleVersion), + listOf( ExportApiTestData("linkMainDebugFrameworkIos", "mainDebugFramework") ) ) @@ -434,170 +367,159 @@ class GeneralNativeIT : BaseGradleIT() { private data class ExportApiTestData(val taskName: String, val binaryName: String) - private fun testExportApi(project: Project, testData: List) = with(project) { + private fun testExportApi(project: TestProject, testData: List) = with(project) { // Check that plugin doesn't allow exporting dependencies not added in the API configuration. - gradleBuildScript().modify { - it.replace("api(project(\":exported\"))", "") - } + buildGradleKts.replaceText("api(project(\":exported\"))", "") fun failureMsgFor(binaryName: String) = "Following dependencies exported in the $binaryName binary are not specified as API-dependencies of a corresponding source set" testData.forEach { - build(it.taskName) { - assertFailed() - assertContains(failureMsgFor(it.binaryName)) + buildAndFail(it.taskName) { + assertOutputContains(failureMsgFor(it.binaryName)) } } } - @Test - fun testTransitiveExportIsNotRequiredForExportingVariant() = with( - transformNativeTestProjectWithPluginDsl( - wrapperVersion = GradleVersionRequired.AtLeast("6.8"), // See https://youtrack.jetbrains.com/issue/KT-52447 - projectName = "export-published-lib", - directoryPrefix = "native-binaries" - ) - ) { - val binaryName = "shared" - val headerPath = "shared/build/bin/linuxX64/debugStatic/lib${binaryName}_api.h" - val binaryBuildTask = "linkDebugStaticLinuxX64" + @DisplayName("Transitive export is not required for exporting variant") + @GradleTest + fun testTransitiveExportIsNotRequiredForExportingVariant(gradleVersion: GradleVersion) { + project("native-binaries/export-published-lib", gradleVersion) { + val headerPath = "shared/build/bin/linuxX64/debugStatic/libshared_api.h" - build(":lib:publish") { - assertSuccessful() - } + build(":lib:publish") - build(":shared:$binaryBuildTask") { - assertSuccessful() - assertFileExists(headerPath) - val headerContents = fileInWorkingDir(headerPath).readText() + build(":shared:linkDebugStaticLinuxX64") { + assertFileInProjectExists(headerPath) - assertTrue(headerContents.contains("funInShared")) - - // Check that the function from exported published library (:lib) is included to the header: - assertTrue(headerContents.contains("funToExport")) + // Check that the function from exported published library (:lib) is included to the header: + assertFileContains(projectPath.resolve(headerPath), "funInShared", "funToExport") + } } } - @Test - fun testNativeExecutables() = with(transformNativeTestProjectWithPluginDsl("executables", directoryPrefix = "native-binaries")) { - val binaries = mutableListOf( - "debugExecutable" to "native-binary", - "releaseExecutable" to "native-binary", - "bazDebugExecutable" to "my-baz", - ) - val linkTasks = - binaries.map { (name, _) -> "link${name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}Host" } - val outputFiles = binaries.map { (name, fileBaseName) -> - val outputKind = NativeOutputKind.values().single { name.endsWith(it.taskNameClassifier, true) }.compilerOutputKind - val prefix = outputKind.prefix(HostManager.host) - val suffix = outputKind.suffix(HostManager.host) - val fileName = "$prefix$fileBaseName$suffix" - name to "build/bin/host/$name/$fileName" - }.toMap() - val runTasks = listOf( - "runDebugExecutable", - "runReleaseExecutable", - "runBazDebugExecutable", - ).map { it + "Host" }.toMutableList() + @DisplayName("Checking native executables") + @GradleTest + fun testNativeExecutables(gradleVersion: GradleVersion) { + nativeProject("native-binaries/executables", gradleVersion) { + val binaries = mutableListOf( + "debugExecutable" to "native-binary", + "releaseExecutable" to "native-binary", + "bazDebugExecutable" to "my-baz", + ) + val linkTasks = + binaries.map { (name, _) -> "link${name.capitalize()}Host" } + val outputFiles = binaries.map { (name, fileBaseName) -> + val outputKind = NativeOutputKind.values().single { name.endsWith(it.taskNameClassifier, true) }.compilerOutputKind + val prefix = outputKind.prefix(HostManager.host) + val suffix = outputKind.suffix(HostManager.host) + val fileName = "$prefix$fileBaseName$suffix" + name to "build/bin/host/$name/$fileName" + }.toMap() + val runTasks = listOf( + "runDebugExecutable", + "runReleaseExecutable", + "runBazDebugExecutable", + ).map { it + "Host" }.toMutableList() - // Check building - build("hostMainBinaries") { - assertSuccessful() - assertTasksExecuted(linkTasks.map { ":$it" }) - assertTasksExecuted(":compileKotlinHost") - outputFiles.forEach { (_, file) -> - assertFileExists(file) + // Check building + build("hostMainBinaries") { + assertTasksExecuted(linkTasks.map { ":$it" }) + assertTasksExecuted(":compileKotlinHost") + outputFiles.forEach { (_, file) -> + assertFileInProjectExists(file) + } } - } - // Check run tasks are generated. - build("tasks") { - assertSuccessful() - runTasks.forEach { - assertTrue(output.contains(it), "The 'tasks' output doesn't contain a task ${it}") + // Check run tasks are generated. + build("tasks") { + runTasks.forEach { + assertOutputContains((it), "The 'tasks' output doesn't contain a task ${it}") + } } - } - // Check that run tasks work fine and an entry point can be specified. - build("runDebugExecutableHost") { - assertSuccessful() - assertTrue(output.contains(".main")) - } + // Check that run tasks work fine and an entry point can be specified. + build("runDebugExecutableHost") { + assertOutputContains(".main") + } - build("runBazDebugExecutableHost") { - assertSuccessful() - assertTrue(output.contains("foo.main")) + build("runBazDebugExecutableHost") { + assertOutputContains("foo.main") + } } } - private fun testNativeBinaryDsl(project: String) = with( - transformNativeTestProjectWithPluginDsl(project, directoryPrefix = "native-binaries") - ) { - val hostSuffix = - nativeHostTargetName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + private fun testNativeBinaryDsl(project: String, gradleVersion: GradleVersion) { + nativeProject("native-binaries" + "/" + project, gradleVersion) + { + val hostSuffix = nativeHostTargetName.capitalize() - build("tasks") { - assertSuccessful() + build("tasks") { + + // Check that getters work fine. + assertOutputContains("Check link task: linkReleaseShared$hostSuffix") + assertOutputContains("Check run task: runFooReleaseExecutable$hostSuffix") + } - // Check that getters work fine. - assertTrue(output.contains("Check link task: linkReleaseShared$hostSuffix")) - assertTrue(output.contains("Check run task: runFooReleaseExecutable$hostSuffix")) } } - @Test - fun testNativeBinaryKotlinDSL() = testNativeBinaryDsl("kotlin-dsl") + @DisplayName("Native binaries with kotlin-dsl") + @GradleTest + fun testNativeBinaryKotlinDSL(gradleVersion: GradleVersion) { + testNativeBinaryDsl("kotlin-dsl", gradleVersion) + } - @Test - fun testNativeBinaryGroovyDSL() = testNativeBinaryDsl("groovy-dsl") + @DisplayName("Native binaries with groovy-dsl") + @GradleTest + fun testNativeBinaryGroovyDSL(gradleVersion: GradleVersion) { + testNativeBinaryDsl("groovy-dsl", gradleVersion) + } - @Test - fun testKotlinOptions() = with( - transformNativeTestProjectWithPluginDsl("native-kotlin-options") - ) { - build(":compileKotlinHost") { - assertSuccessful() - withNativeCommandLineArguments(":compileKotlinHost") { arguments -> - assertFalse("-verbose" in arguments) + @DisplayName("Checking kotlinOptions property") + @GradleTest + fun testKotlinOptions(gradleVersion: GradleVersion) { + nativeProject("native-kotlin-options", gradleVersion) { + build(":compileKotlinHost") { + extractNativeTasksCommandLineArgumentsFromOutput(":compileKotlinHost") { + assertCommandLineArgumentsDoNotContain("-verbose") + } } - } - build("clean") { - assertSuccessful() - } + build("clean") - gradleBuildScript().appendText( - """kotlin.targets["host"].compilations["main"].kotlinOptions.verbose = true""" - ) - build(":compileKotlinHost") { - assertSuccessful() - withNativeCommandLineArguments(":compileKotlinHost") { arguments -> - assertTrue("-verbose" in arguments) + buildGradle.appendText( + """kotlin.targets["host"].compilations["main"].kotlinOptions.verbose = true""" + ) + build(":compileKotlinHost") { + extractNativeTasksCommandLineArgumentsFromOutput(":compileKotlinHost") { + assertCommandLineArgumentsContain("-verbose") + } } } } // We propagate compilation args to link tasks for now (see KT-33717). // TODO: Reenable the test when the args are separated. - @Ignore - @Test - fun testNativeFreeArgsWarning() = with(transformNativeTestProjectWithPluginDsl("kotlin-dsl", directoryPrefix = "native-binaries")) { - gradleBuildScript().appendText( - """kotlin.targets["macos64"].compilations["main"].kotlinOptions.freeCompilerArgs += "-opt"""" - ) - gradleBuildScript("exported").appendText( - """ + @Disabled + @DisplayName("Native free args warning check") + @GradleTest + fun testNativeFreeArgsWarning(gradleVersion: GradleVersion) { + nativeProject("native-binaries/kotlin-dsl", gradleVersion) { + buildGradleKts.appendText( + """kotlin.targets["macos64"].compilations["main"].kotlinOptions.freeCompilerArgs += "-opt"""" + ) + subProject("exported").buildGradleKts.appendText( + """ kotlin.targets["macos64"].compilations["main"].kotlinOptions.freeCompilerArgs += "-opt" kotlin.targets["macos64"].compilations["test"].kotlinOptions.freeCompilerArgs += "-g" kotlin.targets["linux64"].compilations["main"].kotlinOptions.freeCompilerArgs += listOf("-g", "-Xdisable-phases=Devirtualization,BuildDFG") """.trimIndent() - ) - build("tasks") { - assertSuccessful() - assertContains( - """ + ) + build("tasks") { + assertOutputContains( + """ The following free compiler arguments must be specified for a binary instead of a compilation: * In project ':': * In target 'macos64': @@ -612,13 +534,89 @@ class GeneralNativeIT : BaseGradleIT() { Please move them into final binary declarations. E.g. binaries.executable { freeCompilerArgs += "..." } See more about final binaries: https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#building-final-native-binaries. """.trimIndent() - ) + ) + } } } - private fun getBootedSimulators(workingDirectory: File): Set? = + + @OptIn(EnvironmentalVariablesOverride::class) + @DisplayName("Checking native tests") + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + @GradleTest + fun testNativeTests(gradleVersion: GradleVersion) { + nativeProject("native-tests", gradleVersion) { + val hostTestTask = "hostTest" + val testTasks = listOf(hostTestTask, "iosTest", "iosArm64Test") + + val testsToExecute = mutableListOf(":$hostTestTask") + when (HostManager.host) { + KonanTarget.MACOS_X64 -> testsToExecute.add(":iosTest") + KonanTarget.MACOS_ARM64 -> testsToExecute.add(":iosArm64Test") + else -> {} + } + val testsToSkip = testTasks.map { ":$it" } - testsToExecute + + val suffix = HostManager.host.family.exeSuffix + val defaultOutputFile = "build/bin/host/debugTest/test.$suffix" + val anotherOutputFile = "build/bin/host/anotherDebugTest/another.$suffix" + + build("tasks") { + testTasks.forEach { + // We need to create tasks for all hosts + assertOutputContains("$it - ", "There is no test task '$it' in the task list.") + } + } + + // Perform all following checks in a single test to avoid running the K/N compiler several times. + // Check that tests are not built during the ":assemble" execution + build("assemble") { + assertFileInProjectNotExists(defaultOutputFile) + assertFileInProjectNotExists(anotherOutputFile) + } + + // Store currently booted simulators to check that they don't leak (MacOS only). + val bootedSimulatorsBefore = getBootedSimulators() + + // Check the case when all tests pass. + build("check", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) { + + assertTasksExecuted(*testsToExecute.toTypedArray()) + assertTasksSkipped(*testsToSkip.toTypedArray()) + + if (gradleVersion < GradleVersion.version(TestVersions.Gradle.G_8_0)) { + assertOutputContains("org\\.foo\\.test\\.TestKt\\.fooTest\\s+PASSED".toRegex()) + assertOutputContains("org\\.foo\\.test\\.TestKt\\.barTest\\s+PASSED".toRegex()) + } else { + assertOutputContains("org\\.foo\\.test\\.TestKt\\.fooTest\\[host]\\s+PASSED".toRegex()) + assertOutputContains("org\\.foo\\.test\\.TestKt\\.barTest\\[host]\\s+PASSED".toRegex()) + } + + assertFileInProjectExists(defaultOutputFile) + } + + checkTestsUpToDate( + testsToExecute, + testsToSkip, + EnvironmentalVariables(mapOf("ANDROID_HOME" to projectPath.absolutePathString())) + ) + + // Check simulator process leaking. + val bootedSimulatorsAfter = getBootedSimulators() + assertEquals(bootedSimulatorsBefore, bootedSimulatorsAfter) + + // Check the case with failed tests. + checkFailedTests(hostTestTask, testsToExecute, testsToSkip) + + build("linkAnotherDebugTestHost") { + assertFileInProjectExists(anotherOutputFile) + } + } + } + + private fun TestProject.getBootedSimulators(): Set? = if (HostManager.hostIsMac) { - val simulators = runProcess(listOf("xcrun", "simctl", "list"), workingDirectory, System.getenv()).also { + val simulators = runProcess(listOf("xcrun", "simctl", "list"), projectPath.toFile(), System.getenv()).also { assertTrue(it.isSuccessful, "xcrun exection failed") }.output @@ -627,103 +625,41 @@ class GeneralNativeIT : BaseGradleIT() { null } - @Test - fun testNativeTests() = with(transformNativeTestProject("native-tests")) { - val hostTestTask = "hostTest" - val testTasks = listOf(hostTestTask, "iosTest", "iosArm64Test") + private fun TestProject.checkTestsUpToDate( + testsToExecute: List, + testsToSkip: List, + newEnv: EnvironmentalVariables, + ) { - val testsToExecute = mutableListOf(":$hostTestTask") - when (HostManager.host) { - KonanTarget.MACOS_X64 -> testsToExecute.add(":iosTest") - KonanTarget.MACOS_ARM64 -> testsToExecute.add(":iosArm64Test") - else -> {} - } - val testsToSkip = testTasks.map { ":$it" } - testsToExecute - - val suffix = HostManager.host.family.exeSuffix - val defaultOutputFile = "build/bin/host/debugTest/test.$suffix" - val anotherOutputFile = "build/bin/host/anotherDebugTest/another.$suffix" - - build("tasks") { - assertSuccessful() - testTasks.forEach { - // We need to create tasks for all hosts - assertTrue(output.contains("$it - "), "There is no test task '$it' in the task list.") - } - } - - // Perform all following checks in a single test to avoid running the K/N compiler several times. - // Check that tests are not built during the ":assemble" execution - build("assemble") { - assertSuccessful() - assertNoSuchFile(defaultOutputFile) - assertNoSuchFile(anotherOutputFile) - } - - // Store currently booted simulators to check that they don't leak (MacOS only). - val bootedSimulatorsBefore = getBootedSimulators(projectDir) - - // Check the case when all tests pass. - build("check") { - assertSuccessful() - - assertTasksExecuted(*testsToExecute.toTypedArray()) - assertTasksSkipped(*testsToSkip.toTypedArray()) - - val currentGradleVersion = GradleVersion.version(chooseWrapperVersionOrFinishTest()) - if (currentGradleVersion < GradleVersion.version(TestVersions.Gradle.G_8_0)) { - assertContainsRegex("org\\.foo\\.test\\.TestKt\\.fooTest\\s+PASSED".toRegex()) - assertContainsRegex("org\\.foo\\.test\\.TestKt\\.barTest\\s+PASSED".toRegex()) - } else { - assertContainsRegex("org\\.foo\\.test\\.TestKt\\.fooTest\\[host]\\s+PASSED".toRegex()) - assertContainsRegex("org\\.foo\\.test\\.TestKt\\.barTest\\[host]\\s+PASSED".toRegex()) - } - - assertFileExists(defaultOutputFile) - } - - checkTestsUpToDate(testsToExecute, testsToSkip) - - // Check simulator process leaking. - val bootedSimulatorsAfter = getBootedSimulators(projectDir) - assertEquals(bootedSimulatorsBefore, bootedSimulatorsAfter) - - // Check the case with failed tests. - checkFailedTests(hostTestTask, testsToExecute, testsToSkip) - - build("linkAnotherDebugTestHost") { - assertSuccessful() - assertFileExists(anotherOutputFile) - } - } - - private fun Project.checkTestsUpToDate(testsToExecute: List, testsToSkip: List) { // Check that test tasks are up-to-date on second run build("check") { - assertSuccessful() assertTasksUpToDate(*testsToExecute.toTypedArray()) assertTasksSkipped(*testsToSkip.toTypedArray()) } // Check that setting new value to tracked environment variable triggers tests rerun - build("check", options = defaultBuildOptions().copy(androidHome = projectDir)) { - assertSuccessful() + build( + "check", + environmentVariables = newEnv + ) { assertTasksExecuted(*testsToExecute.toTypedArray()) assertTasksSkipped(*testsToSkip.toTypedArray()) } - build("check", options = defaultBuildOptions().copy(androidHome = projectDir)) { - assertSuccessful() + build( + "check", + environmentVariables = newEnv + ) { assertTasksUpToDate(*testsToExecute.toTypedArray()) assertTasksSkipped(*testsToSkip.toTypedArray()) } } - private fun Project.checkFailedTests(hostTestTask: String, testsToExecute: List, testsToSkip: List) { - projectDir.resolve("src/commonTest/kotlin/test.kt").appendText( + private fun TestProject.checkFailedTests(hostTestTask: String, testsToExecute: List, testsToSkip: List) { + projectPath.resolve("src/commonTest/kotlin/test.kt").appendText( """ @Test fun fail() { @@ -731,8 +667,7 @@ class GeneralNativeIT : BaseGradleIT() { } """.trimIndent() ) - build("check") { - assertFailed() + buildAndFail("check") { assertTasksFailed(":allTests") // In the aggregation report mode platform-specific tasks @@ -740,17 +675,15 @@ class GeneralNativeIT : BaseGradleIT() { assertTasksExecuted(*testsToExecute.toTypedArray()) assertTasksSkipped(*testsToSkip.toTypedArray()) - val currentGradleVersion = GradleVersion.version(chooseWrapperVersionOrFinishTest()) - if (currentGradleVersion < GradleVersion.version(TestVersions.Gradle.G_8_0)) { - assertContainsRegex("org\\.foo\\.test\\.TestKt\\.fail\\s+FAILED".toRegex()) + if (gradleVersion < GradleVersion.version(TestVersions.Gradle.G_8_0)) { + assertOutputContains("org\\.foo\\.test\\.TestKt\\.fail\\s+FAILED".toRegex()) } else { - assertContainsRegex("org\\.foo\\.test\\.TestKt\\.fail\\[host]\\s+FAILED".toRegex()) + assertOutputContains("org\\.foo\\.test\\.TestKt\\.fail\\[host]\\s+FAILED".toRegex()) } } // Check that individual test reports are created correctly. - build("check", "-Pkotlin.tests.individualTaskReports=true", "--continue") { - assertFailed() + buildAndFail("check", "-Pkotlin.tests.individualTaskReports=true", "--continue") { // In the individual report mode platform-specific tasks // fail if there are failing tests. @@ -759,7 +692,7 @@ class GeneralNativeIT : BaseGradleIT() { fun assertStacktrace(taskName: String, targetName: String) { - val testReport = projectDir.resolve("build/test-results/$taskName/TEST-org.foo.test.TestKt.xml") + val testReport = projectPath.resolve("build/test-results/$taskName/TEST-org.foo.test.TestKt.xml").toFile() val stacktrace = SAXBuilder().build(testReport).rootElement .getChildren("testcase") .single { it.getAttribute("name").value == "fail" || it.getAttribute("name").value == "fail[$targetName]" } @@ -771,22 +704,28 @@ class GeneralNativeIT : BaseGradleIT() { fun assertTestResultsAnyOf( @TestDataFile assertionFile1Name: String, @TestDataFile assertionFile2Name: String, - vararg testReportNames: String + vararg testReportNames: String, ) { try { - assertTestResults(assertionFile1Name, *testReportNames) + assertTestResults(projectPath.resolve(assertionFile1Name), *testReportNames) { s -> + val excl = "Invalid connection: com.apple.coresymbolicationd" + s.lines().filter { it != excl }.joinToString("\n") + } } catch (e: AssertionError) { - assertTestResults(assertionFile2Name, *testReportNames) + assertTestResults(projectPath.resolve(assertionFile2Name), *testReportNames) { s -> + val excl = "Invalid connection: com.apple.coresymbolicationd" + s.lines().filter { it != excl }.joinToString("\n") + } } } - val expectedHostTestResult = "testProject/native-tests/TEST-TestKt.xml" + val expectedHostTestResult = "TEST-TestKt.xml" val expectedIOSTestResults = listOf( - "testProject/native-tests/TEST-TestKt-iOSsim.xml", - "testProject/native-tests/TEST-TestKt-iOSArm64sim.xml", + "TEST-TestKt-iOSsim.xml", + "TEST-TestKt-iOSArm64sim.xml", ) - assertTestResults(expectedHostTestResult, hostTestTask) + assertTestResults(projectPath.resolve(expectedHostTestResult), hostTestTask) // K/N doesn't report line numbers correctly on Linux (see KT-35408). // TODO: Move assertStacktrace(hostTestTask, "host") out of if clause if (HostManager.hostIsMac) { @@ -807,410 +746,319 @@ class GeneralNativeIT : BaseGradleIT() { } } - @Test - fun testNativeTestGetters() = with(transformNativeTestProject("native-tests")) { - // Check that test binaries can be accessed in a buildscript. - build("checkGetters") { - assertSuccessful() - val suffix = if (HostManager.hostIsMingw) "exe" else "kexe" - val names = listOf("test", "another") - val files = names.map { "$it.$suffix" } + @DisplayName("Checking work with tests' getters") + @GradleTest + fun testNativeTestGetters(gradleVersion: GradleVersion) { + nativeProject("native-tests", gradleVersion) { + // Check that test binaries can be accessed in a buildscript. + build("checkGetters") { + val suffix = if (HostManager.hostIsMingw) "exe" else "kexe" + val names = listOf("test", "another") + val files = names.map { "$it.$suffix" } - files.forEach { - assertContains("Get test: $it") - assertContains("Find test: $it") + files.forEach { + assertOutputContains("Get test: $it") + assertOutputContains("Find test: $it") + } } } } - @Test - fun kt33750() { - // Check that build fails if a test executable crashes. - with(transformNativeTestProject("native-tests")) { - projectDir.resolve("src/commonTest/kotlin/test.kt").appendText("\nval fail: Int = error(\"\")\n") - build("check") { - assertFailed() - output.contains("exited with errors \\(exit code: \\d+\\)".toRegex()) + @DisplayName("Checks that build fails if a test executable crashes") + @GradleTest + fun kt33750(gradleVersion: GradleVersion) { + nativeProject("native-tests", gradleVersion) { + projectPath.resolve("src/commonTest/kotlin/test.kt") + .appendText("\nval fail: Int = error(\"\")\n") + buildAndFail("check") { + assertOutputContains("Execution failed for task ':allTests'") } } } - @Test - fun testCinterop() = with(transformNativeTestProjectWithPluginDsl("native-cinterop")) { - fun libraryFiles(projectName: String, cinteropName: String) = listOf( - "$projectName/build/classes/kotlin/host/main/cinterop/${projectName}-cinterop-$cinteropName.klib", - "$projectName/build/classes/kotlin/host/main/klib/${projectName}.klib", - "$projectName/build/classes/kotlin/host/test/klib/${projectName}_test.klib", - ) + @DisplayName("Checks builds with cinterop tool") + @GradleTest + fun testCinterop(gradleVersion: GradleVersion) { + nativeProject("native-cinterop", gradleVersion, configureSubProjects = true) { + fun libraryFiles(projectName: String, cinteropName: String) = listOf( + projectPath.resolve("$projectName/build/classes/kotlin/host/main/cinterop/${projectName}-cinterop-$cinteropName.klib"), + projectPath.resolve("$projectName/build/classes/kotlin/host/main/klib/${projectName}.klib"), + projectPath.resolve("$projectName/build/classes/kotlin/host/test/klib/${projectName}_test.klib") + ) - // Enable info log to see cinterop environment variables. - build(":projectLibrary:build", "--info") { - assertSuccessful() - assertTasksExecuted(":projectLibrary:cinteropAnotherNumberHost") - libraryFiles("projectLibrary", "anotherNumber").forEach { assertFileExists(it) } - withNativeCustomEnvironment(":projectLibrary:cinteropAnotherNumberHost", toolName = NativeToolKind.C_INTEROP) { env -> - assertEquals("1", env["LIBCLANG_DISABLE_CRASH_RECOVERY"]) + // Enable info log to see cinterop environment variables. + build( + ":projectLibrary:build", + ) { + assertTasksExecuted(":projectLibrary:cinteropAnotherNumberHost") + libraryFiles("projectLibrary", "anotherNumber").forEach { assertFileExists(it) } + assertNativeTasksCustomEnvironment( + ":projectLibrary:cinteropAnotherNumberHost", + toolName = NativeToolKind.C_INTEROP + ) { env -> + assertEquals("1", env["LIBCLANG_DISABLE_CRASH_RECOVERY"]) + } } - } - build(":publishedLibrary:build", ":publishedLibrary:publish") { - assertSuccessful() - assertTasksExecuted(":publishedLibrary:cinteropNumberHost") - libraryFiles("publishedLibrary", "number").forEach { assertFileExists(it) } - } + build(":publishedLibrary:build", ":publishedLibrary:publish") { + assertTasksExecuted(":publishedLibrary:cinteropNumberHost") + libraryFiles("publishedLibrary", "number").forEach { assertFileExists(it) } + } - build(":build") { - assertSuccessful() + build(":build") } } - @Test - fun testCompilerVersionChange() = with(transformNativeTestProjectWithPluginDsl("native-compiler-version")) { - val compileTasks = listOf(":compileKotlinHost") - val compileTasksArray = compileTasks.toTypedArray() + @DisplayName("Checks builds with changing compiler version") + @OptIn(EnvironmentalVariablesOverride::class) + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + @GradleTest + fun testCompilerVersionChange(gradleVersion: GradleVersion) { + if (KotlinToolingVersion(TestVersions.Kotlin.STABLE_RELEASE) < KotlinToolingVersion("1.9.20-Beta")) { + val konanDataDir = defaultBuildOptions.konanDataDir?.toAbsolutePath()?.normalize()?.absolutePathString() + ?: error("konanDataDir must not be null in this test. Please set a custom konanDataDir property.") - build(*compileTasksArray) { - assertSuccessful() - assertTasksExecuted(compileTasks) - } + nativeProject( + "native-compiler-version", + gradleVersion, + environmentVariables = EnvironmentalVariables(mapOf("KONAN_DATA_DIR" to konanDataDir)), + ) { + val compileTasks = ":compileKotlinHost" - build(*compileTasksArray) { - assertSuccessful() - assertTasksUpToDate(compileTasks) - } + build(compileTasks) { + assertTasksExecuted(compileTasks) + } - // Check that changing K/N version lead to tasks rerun - build(*compileTasksArray, "-Porg.jetbrains.kotlin.native.version=1.6.10") { - assertSuccessful() - assertTasksExecuted(compileTasks) - } - } + build(compileTasks) { + assertTasksUpToDate(compileTasks) + } - @Test - fun testNativeCompilerDownloading() { - // The plugin shouldn't download the K/N compiler if there are no corresponding targets in the project. - with(Project("sample-old-style-app", directoryPrefix = "new-mpp-lib-and-app")) { - build("tasks") { - assertSuccessful() - assertFalse(output.contains("Kotlin/Native distribution: ")) + // Check that changing K/N version lead to tasks rerun + build(compileTasks, "-Porg.jetbrains.kotlin.native.version=${TestVersions.Kotlin.STABLE_RELEASE}") { + assertTasksExecuted(compileTasks) + } } - } - with(Project("native-libraries")) { - build("tasks") { - assertSuccessful() - assertTrue(output.contains("Kotlin/Native distribution: ")) - // Check for KT-30258. - assertFalse(output.contains("Deprecated Gradle features were used in this build, making it incompatible with Gradle 6.0.")) - } - - // This directory actually doesn't contain a K/N distribution - // but we still can run a project configuration and check logs. - val currentDir = projectDir.absolutePath - build("tasks", "-Pkotlin.native.home=$currentDir") { - assertSuccessful() - assertContains("User-provided Kotlin/Native distribution: $currentDir") - assertNotContains("Project property 'org.jetbrains.kotlin.native.home' is deprecated") - assertHasDiagnostic(KotlinToolingDiagnostics.NativeStdlibIsMissingDiagnostic, withSubstring = "kotlin.native.home") - } - - // Deprecated property. - build("tasks", "-Porg.jetbrains.kotlin.native.home=$currentDir", "-Pkotlin.native.nostdlib=true") { - assertSuccessful() - assertContains("User-provided Kotlin/Native distribution: $currentDir") - assertContains("Project property 'org.jetbrains.kotlin.native.home' is deprecated") - assertNoDiagnostic(KotlinToolingDiagnostics.NativeStdlibIsMissingDiagnostic) - } - - - val platform = HostManager.platformName() - val version = STABLE_RELEASE - val escapedRegexVersion = Regex.escape(STABLE_RELEASE) - build("tasks", "-Pkotlin.native.version=$version") { - assertSuccessful() - assertContainsRegex("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platform-$escapedRegexVersion".toRegex()) - assertNotContains("Project property 'org.jetbrains.kotlin.native.version' is deprecated") - } - - // Deprecated property - build("tasks", "-Porg.jetbrains.kotlin.native.version=$version") { - assertSuccessful() - assertContainsRegex("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platform-$escapedRegexVersion".toRegex()) - assertContains("Project property 'org.jetbrains.kotlin.native.version' is deprecated") - } - } - } - - @Test - fun testKt29725() { - with(Project("native-libraries")) { - // Assert that a project with a native target can be configured with Gradle 5.2 - build("tasks") { - assertSuccessful() - } - } - } - - @Test - fun testIgnoreDisabledNativeTargets() = with(Project("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) { - hostHaveUnsupportedTarget() - build { - assertSuccessful() - assertHasDiagnostic(KotlinToolingDiagnostics.DisabledKotlinNativeTargets) - } - build("-P$KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS_PROPERTY=true") { - assertSuccessful() - assertNoDiagnostic(KotlinToolingDiagnostics.DisabledKotlinNativeTargets) - } - } - - @Test - fun testNativeArgsWithSpaces() = with(transformNativeTestProject("sample-lib", directoryPrefix = "new-mpp-lib-and-app")) { - val complicatedDirectoryName = if (HostManager.hostIsMingw) { - // Windows doesn't allow creating a file with " in its name. - "path with spaces" } else { - "path with spaces and \"" + fail( + "Please remove setting KONAN_DATA_DIR environment variable," + + " because Kotlin stable release now supports konan.data.dir Gradle property." + ) } - val fileWithSpacesInPath = projectDir.resolve("src/commonMain/kotlin/$complicatedDirectoryName") - .apply { mkdirs() } - .resolve("B.kt") - fileWithSpacesInPath.writeText("fun foo() = 42") + } - build("compileKotlin${nativeHostTargetName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}") { - assertSuccessful() - withNativeCommandLineArguments( - ":compileKotlin${ - nativeHostTargetName.replaceFirstChar { - if (it.isLowerCase()) it.titlecase( - Locale.getDefault() - ) else it.toString() + @DisplayName("Assert that a project with a native target can be configure") + @GradleTest + fun testKt29725(gradleVersion: GradleVersion) { + nativeProject("native-compiler-version", gradleVersion) { + build("tasks") + } + } + + @DisplayName("Assert that a project with a native target can be configure") + @GradleTest + @OsCondition(supportedOn = [OS.LINUX, OS.WINDOWS]) + fun testIgnoreDisabledNativeTargets(gradleVersion: GradleVersion) { + nativeProject("new-mpp-lib-and-app/sample-lib", gradleVersion) { + build { + assertHasDiagnostic(KotlinToolingDiagnostics.DisabledKotlinNativeTargets) + } + build("-P$KOTLIN_NATIVE_IGNORE_DISABLED_TARGETS_PROPERTY=true") { + assertNoDiagnostic(KotlinToolingDiagnostics.DisabledKotlinNativeTargets) + } + } + } + + @DisplayName("Checks native arguments with the spaces in it") + @GradleTest + fun testNativeArgsWithSpaces(gradleVersion: GradleVersion) { + nativeProject("new-mpp-lib-and-app/sample-lib", gradleVersion) { + val complicatedDirectoryName = if (HostManager.hostIsMingw) { + // Windows doesn't allow creating a file with " in its name. + "path with spaces" + } else { + "path with spaces and \"" + } + + val fileWithSpacesInPath = projectPath.resolve("src/commonMain/kotlin/$complicatedDirectoryName").toFile() + .apply { mkdirs() } + .canonicalFile + .resolve("B.kt") + fileWithSpacesInPath.writeText("fun foo() = 42") + + build( + "compileKotlin${nativeHostTargetName.capitalize()}", + ) { + extractNativeTasksCommandLineArgumentsFromOutput(":compileKotlin${nativeHostTargetName.capitalize()}") { + val escapedQuotedPath = + "\"${fileWithSpacesInPath.absolutePath.replace("\\", "\\\\").replace("\"", "\\\"")}\"" + assertCommandLineArgumentsContain(escapedQuotedPath) + } + } + } + } + + @DisplayName("Checks binary options dsl") + @GradleTest + fun testBinaryOptionsDSL(gradleVersion: GradleVersion) { + nativeProject("native-binaries/executables", gradleVersion) { + buildGradleKts.appendText( + """ + kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) { + binaries.all { binaryOptions["memoryModel"] = "experimental" } } - }") { arguments -> - val escapedQuotedPath = - "\"${fileWithSpacesInPath.absolutePath.replace("\\", "\\\\").replace("\"", "\\\"")}\"" - assertTrue( - escapedQuotedPath in arguments, - """ - Command-line arguments do not contain path with spaces. - Raw path = ${fileWithSpacesInPath.absolutePath} - Escaped quoted path = $escapedQuotedPath - Arguments: ${arguments.joinToString(separator = " ")} - """.trimIndent() - ) - } - } - } - - @Test - fun testBinaryOptionsDSL() = with(transformNativeTestProjectWithPluginDsl("executables", directoryPrefix = "native-binaries")) { - gradleBuildScript().appendText( - """ - kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) { - binaries.all { binaryOptions["memoryModel"] = "experimental" } + """.trimIndent() + ) + build(":linkDebugExecutableHost") { + extractNativeTasksCommandLineArgumentsFromOutput(":linkDebugExecutableHost") { + assertCommandLineArgumentsContain("-Xbinary=memoryModel=experimental") } - """.trimIndent() - ) - build(":linkDebugExecutableHost") { - assertSuccessful() - withNativeCommandLineArguments(":linkDebugExecutableHost") { - assertTrue(it.contains("-Xbinary=memoryModel=experimental")) } } } - @Test - fun testBinaryOptionsProperty() = with(transformNativeTestProjectWithPluginDsl("executables", directoryPrefix = "native-binaries")) { - build(":linkDebugExecutableHost", "-Pkotlin.native.binary.memoryModel=experimental") { - assertSuccessful() - withNativeCommandLineArguments(":linkDebugExecutableHost") { - assertTrue(it.contains("-Xbinary=memoryModel=experimental")) - } - } - } - - @Test - fun testBinaryOptionsPriority() = with(transformNativeTestProjectWithPluginDsl("executables", directoryPrefix = "native-binaries")) { - gradleBuildScript().appendText( - """ - kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) { - binaries.all { binaryOptions["memoryModel"] = "experimental" } + @DisplayName("Checks binary options property") + @GradleTest + fun testBinaryOptionsProperty(gradleVersion: GradleVersion) { + nativeProject("native-binaries/executables", gradleVersion) { + build( + ":linkDebugExecutableHost", + "-Pkotlin.native.binary.memoryModel=experimental", + ) { + extractNativeTasksCommandLineArgumentsFromOutput(":linkDebugExecutableHost") { + assertCommandLineArgumentsContain("-Xbinary=memoryModel=experimental") } - """.trimIndent() - ) - build(":linkDebugExecutableHost", "-Pkotlin.native.binary.memoryModel=strict") { - assertSuccessful() - withNativeCommandLineArguments(":linkDebugExecutableHost") { - // Options set in the DSL have higher priority than options set in project properties. - assertTrue(it.contains("-Xbinary=memoryModel=experimental")) } } } - @Test - fun testCinteropConfigurationsVariantAwareResolution() = with(transformNativeTestProjectWithPluginDsl("native-cinterop")) { - build(":publishedLibrary:publish") { - assertSuccessful() - } - - fun CompiledProject.assertVariantInDependencyInsight(variantName: String) { - val testGradleVersion = chooseWrapperVersionOrFinishTest() - val isAtLeastGradle75 = GradleVersion.version(testGradleVersion) >= GradleVersion.version("7.5") - try { - if (isAtLeastGradle75) { - assertContains("Variant $variantName") - } else { - assertContains("variant \"$variantName\" [") + @DisplayName("Checks binary options priority") + @GradleTest + fun testBinaryOptionsPriority(gradleVersion: GradleVersion) { + nativeProject("native-binaries/executables", gradleVersion) { + buildGradleKts.appendText( + """ + kotlin.targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java) { + binaries.all { binaryOptions["memoryModel"] = "experimental" } + } + """.trimIndent() + ) + build( + ":linkDebugExecutableHost", + "-Pkotlin.native.binary.memoryModel=strict", + ) { + extractNativeTasksCommandLineArgumentsFromOutput(":linkDebugExecutableHost") { + // Options set in the DSL have higher priority than options set in project properties. + assertCommandLineArgumentsContain("-Xbinary=memoryModel=experimental") } - } catch (originalError: AssertionError) { - val regexPattern = if (isAtLeastGradle75) { - "Variant (.*?):" - } else { - "variant \"(.*?)\" \\[" - } - val matchedVariants = Regex(regexPattern).findAll(output).toList() - throw AssertionError( - "Expected variant $variantName. " + - if (matchedVariants.isNotEmpty()) - "Matched instead: " + matchedVariants.joinToString { it.groupValues[1] } - else "No match.", - originalError - ) } } - - build(":dependencyInsight", "--configuration", "hostTestCInterop", "--dependency", "org.example:publishedLibrary") { - assertSuccessful() - assertVariantInDependencyInsight("hostApiElements-published") - } - - gradleBuildScript("projectLibrary").appendText( - "\n" + """ - configurations.create("ktlint") { - def bundlingAttribute = Attribute.of("org.gradle.dependency.bundling", String) - attributes.attribute(bundlingAttribute, "external") - } - """.trimIndent() - ) - - build(":dependencyInsight", "--configuration", "hostTestCInterop", "--dependency", ":projectLibrary") { - assertSuccessful() - assertVariantInDependencyInsight("hostCInteropApiElements") - } - build(":dependencyInsight", "--configuration", "hostCompileKlibraries", "--dependency", ":projectLibrary") { - assertSuccessful() - assertVariantInDependencyInsight("hostApiElements") - } } - @Test - fun allowToOverrideDownloadUrl() { - with(transformNativeTestProjectWithPluginDsl("native-parallel")) { - gradleProperties().appendText( + @DisplayName("Checks citerop configuration variant aware resolution") + @GradleTest + fun testCinteropConfigurationsVariantAwareResolution(gradleVersion: GradleVersion) { + nativeProject("native-cinterop", gradleVersion, configureSubProjects = true) { + build(":publishedLibrary:publish") + + build(":dependencyInsight", "--configuration", "hostTestCInterop", "--dependency", "org.example:publishedLibrary") { + assertOutputContainsNativeFrameworkVariant("hostApiElements-published", gradleVersion) + } + + subProject("projectLibrary").buildGradle.appendText( + "\n" + """ + configurations.create("ktlint") { + def bundlingAttribute = Attribute.of("org.gradle.dependency.bundling", String) + attributes.attribute(bundlingAttribute, "external") + } + """.trimIndent() + ) + + build(":dependencyInsight", "--configuration", "hostTestCInterop", "--dependency", ":projectLibrary") { + assertOutputContainsNativeFrameworkVariant("hostCInteropApiElements", gradleVersion) + } + build(":dependencyInsight", "--configuration", "hostCompileKlibraries", "--dependency", ":projectLibrary") { + assertOutputContainsNativeFrameworkVariant("hostApiElements", gradleVersion) + } + } + } + + @DisplayName("Checks allowing to override download url") + @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0) + @GradleTest + fun shouldAllowToOverrideDownloadUrl(gradleVersion: GradleVersion, @TempDir customKonanDir: Path) { + nativeProject("native-parallel", gradleVersion) { + gradleProperties.appendText( """ kotlin.native.distribution.baseDownloadUrl=https://non-existent.net """.trimIndent() ) - gradleProperties().modify { - it.replace("cacheRedirectorEnabled=true", "cacheRedirectorEnabled=false") - } + gradleProperties.replaceText("cacheRedirectorEnabled=true", "cacheRedirectorEnabled=false") - build( + buildAndFail( "build", - options = defaultBuildOptions().copy( - forceOutputToStdout = true, - konanDataDir = Files.createTempDirectory("konan-data-dir").toAbsolutePath() + buildOptions = defaultBuildOptions.copy( + konanDataDir = customKonanDir.toAbsolutePath() ) ) { - assertFailed() - assertContains("Could not HEAD 'https://non-existent.net") + assertOutputContains("Could not HEAD 'https://non-existent.net") } } } // KT-52303 - @Test - fun testBuildDirChangeAppliedToBinaries() = - with(transformNativeTestProjectWithPluginDsl("executables", directoryPrefix = "native-binaries")) { - gradleBuildScript().appendText( + @DisplayName("Checks that changing build dir applied to binaries") + @GradleTest + fun testBuildDirChangeAppliedToBinaries(gradleVersion: GradleVersion) { + nativeProject("native-binaries/executables", gradleVersion) { + buildGradleKts.appendText( """ - project.buildDir = file("${'$'}{project.buildDir.absolutePath}/mydir") - """.trimIndent() + project.buildDir = file("${'$'}{project.buildDir.absolutePath}/mydir") + """.trimIndent() ) build(":linkDebugExecutableHost") { - assertSuccessful() - assertDirectoryExists("build/mydir/bin/host/debugExecutable") - assertNoSuchFile("build/bin") + assertDirectoryInProjectExists("build/mydir/bin/host/debugExecutable") + assertFileInProjectNotExists("build/bin") } } + } // KT-54439 - @Test - fun testLanguageSettingsSyncToNativeTasks() = with(transformNativeTestProjectWithPluginDsl("native-kotlin-options")) { - gradleBuildScript().modify { - """ - |${it.substringBefore("kotlin {")} - | - |kotlin { - | ${HostManager.host.presetName}("host") { - | compilations.named("main").configure { - | kotlinOptions.freeCompilerArgs += ["-Xverbose-phases=Linker"] - | } - | } - |} - """.trimMargin() - } + @DisplayName("Checks Language settings sync ") + @GradleTest + fun testLanguageSettingsSyncToNativeTasks(gradleVersion: GradleVersion) { + nativeProject("native-kotlin-options", gradleVersion) { + buildGradle.modify { + """ + |${it.substringBefore("kotlin {")} + | + |kotlin { + | ${HostManager.host.presetName}("host") { + | compilations.named("main").configure { + | kotlinOptions.freeCompilerArgs += ["-Xverbose-phases=Linker"] + | } + | } + |} + """.trimMargin() + } - build("assemble") { - assertSuccessful() - assertContains("-Xverbose-phases=Linker") + build("assemble") { + assertOutputContains("-Xverbose-phases=Linker") + } } } // KT-58537 - @Test - @Ignore("Requires update to the newer version with changes") - fun testProjectNameWithSpaces() = with(transformNativeTestProjectWithPluginDsl("native-root-project-name-with-space")) { - build("assemble") { - assertNotContains("Could not find \"Contains\" in") - assertSuccessful() + @DisplayName("Build native project with name containing space") + @GradleTest + fun testProjectNameWithSpaces(gradleVersion: GradleVersion) { + nativeProject("native-root-project-name-with-space", gradleVersion, configureSubProjects = true) { + build("assemble") { + assertOutputDoesNotContain("Could not find \"Contains\" in") + } } } - - companion object { - fun List.containsSequentially(vararg elements: String): Boolean { - check(elements.isNotEmpty()) - return Collections.indexOfSubList(this, elements.toList()) != -1 - } - - /** - * Filter output for specific task with given [taskPath] - * - * Requires using [LogLevel.DEBUG]. - */ - fun CompiledProject.getOutputForTask(taskPath: String): String = getOutputForTask(taskPath, output) - - fun CompiledProject.extractNativeCustomEnvironment(taskPath: String? = null, toolName: NativeToolKind): Map = - extractNativeToolSettings(taskPath?.let { getOutputForTask(taskPath) } ?: output, - toolName, - NativeToolSettingsKind.CUSTOM_ENV_VARIABLES).map { - val (key, value) = it.split("=") - key.trim() to value.trim() - }.toMap() - - fun CompiledProject.withNativeCommandLineArguments( - vararg taskPaths: String, - toolName: NativeToolKind = NativeToolKind.KONANC, - check: (List) -> Unit - ) = taskPaths.forEach { taskPath -> check(extractNativeCompilerCommandLineArguments(getOutputForTask(taskPath), toolName)) } - - fun CompiledProject.withNativeCustomEnvironment( - vararg taskPaths: String, - toolName: NativeToolKind = NativeToolKind.KONANC, - check: (Map) -> Unit - ) = taskPaths.forEach { taskPath -> check(extractNativeCustomEnvironment(taskPath, toolName)) } - } -} +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/KotlinNativeLinkIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/KotlinNativeLinkIT.kt index 24e78bd2a50..ab177bba49c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/KotlinNativeLinkIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/KotlinNativeLinkIT.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.native -import org.gradle.api.logging.LogLevel import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.dsl.NativeCacheKind import org.jetbrains.kotlin.gradle.testbase.* @@ -30,8 +29,7 @@ internal class KotlinNativeLinkIT : KGPBaseTest() { fun shouldUseCompilationFreeCompilerArgs(gradleVersion: GradleVersion) { nativeProject( "native-link-simple", - gradleVersion, - buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG) + gradleVersion ) { buildGradle.appendText( """ @@ -70,7 +68,6 @@ internal class KotlinNativeLinkIT : KGPBaseTest() { "kt-60839-native-link-cache-builder", gradleVersion = gradleVersion, buildOptions = defaultBuildOptions.copy( - logLevel = LogLevel.DEBUG, // KT-60839 only reproduces when the build cache is enabled, // but we must ignore it when running this test in order to // ensure we actually try to pass -Xpartial-linkage to konanc. diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt index 304501ec3a3..c3ba74f1ba8 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeDownloadAndPlatformLibsIT.kt @@ -5,9 +5,9 @@ package org.jetbrains.kotlin.gradle.native -import org.gradle.api.logging.LogLevel import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.util.replaceFirst import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader @@ -150,7 +150,7 @@ class NativeDownloadAndPlatformLibsIT : KGPBaseTest() { """.trimMargin() ) - build("linkDebugSharedLinuxX64", buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)) { + build("linkDebugSharedLinuxX64") { assertTasksExecuted( ":compileKotlinLinuxX64", ":linkDebugSharedLinuxX64" @@ -280,6 +280,68 @@ class NativeDownloadAndPlatformLibsIT : KGPBaseTest() { } } + @DisplayName("The plugin shouldn't download the K/N compiler if there are no corresponding targets in the project.") + @GradleTest + fun shouldNotDownloadKonanWithoutCorrespondingTargets(gradleVersion: GradleVersion) { + nativeProject("new-mpp-lib-and-app/sample-old-style-app", gradleVersion) { + build("tasks") { + assertOutputDoesNotContain("Kotlin/Native distribution: ") + } + } + } + + @DisplayName("The plugin shouldn't download the K/N compiler if there is konan home property override and no konan.data.dir property override.") + @GradleTest + fun testNativeCompilerDownloadingWithDifferentKNHomeOptions(gradleVersion: GradleVersion) { + nativeProject("native-libraries", gradleVersion) { + + // This directory actually doesn't contain a K/N distribution + // but we still can run a project configuration and check logs. + val currentDir = projectPath + build("tasks", "-Pkotlin.native.home=$currentDir", buildOptions = defaultBuildOptions.copy(konanDataDir = null)) { + assertOutputContains("User-provided Kotlin/Native distribution: $currentDir") + assertOutputDoesNotContain("Project property 'org.jetbrains.kotlin.native.home' is deprecated") + assertHasDiagnostic(KotlinToolingDiagnostics.NativeStdlibIsMissingDiagnostic, withSubstring = "kotlin.native.home") + } + + // Deprecated property. + build( + "tasks", + "-Porg.jetbrains.kotlin.native.home=$currentDir", + "-Pkotlin.native.nostdlib=true", + buildOptions = defaultBuildOptions.copy(konanDataDir = null) + ) { + assertOutputContains("User-provided Kotlin/Native distribution: $currentDir") + assertOutputContains("Project property 'org.jetbrains.kotlin.native.home' is deprecated") + assertNoDiagnostic(KotlinToolingDiagnostics.NativeStdlibIsMissingDiagnostic) + } + } + } + + @DisplayName("Checks downloading K/N compiler with different version options") + @GradleTest + fun testNativeCompilerDownloadingWithDifferentVersionOptions(gradleVersion: GradleVersion) { + nativeProject("native-libraries", gradleVersion) { + val platform = HostManager.platformName() + build("tasks") { + assertOutputContains("Kotlin/Native distribution:") + } + + val version = TestVersions.Kotlin.STABLE_RELEASE + val escapedRegexVersion = Regex.escape(TestVersions.Kotlin.STABLE_RELEASE) + build("tasks", "-Pkotlin.native.version=$version") { + assertOutputContains("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platform-$escapedRegexVersion".toRegex()) + assertOutputDoesNotContain("Project property 'org.jetbrains.kotlin.native.version' is deprecated") + } + + // Deprecated property + build("tasks", "-Porg.jetbrains.kotlin.native.version=$version") { + assertOutputContains("Kotlin/Native distribution: .*kotlin-native-prebuilt-$platform-$escapedRegexVersion".toRegex()) + assertOutputContains("Project property 'org.jetbrains.kotlin.native.version' is deprecated") + } + } + } + private fun platformLibrariesProject( vararg targets: String, gradleVersion: GradleVersion, diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeEmbeddableCompilerJarIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeEmbeddableCompilerJarIT.kt index a75504e26d5..7b539843046 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeEmbeddableCompilerJarIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeEmbeddableCompilerJarIT.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.native -import org.gradle.api.logging.LogLevel import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* @@ -15,8 +14,6 @@ import org.junit.jupiter.api.DisplayName @NativeGradlePluginTests internal class NativeEmbeddableCompilerJarIT : KGPBaseTest() { - override val defaultBuildOptions = super.defaultBuildOptions.copy(logLevel = LogLevel.DEBUG) - private fun String.isRegularJar() = this.endsWith("/kotlin-native.jar") private fun String.isEmbeddableJar() = this.endsWith("/kotlin-native-compiler-embeddable.jar") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslIT.kt index 5bc463dba2d..0d6d263bbff 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslIT.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.native -import org.gradle.api.logging.LogLevel import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* import org.junit.jupiter.api.DisplayName @@ -57,7 +56,7 @@ class NativeLibraryDslIT : KGPBaseTest() { @GradleTest fun shouldLinkSharedLibrariesFromSingleModule(gradleVersion: GradleVersion) { nativeProject("new-kn-library-dsl", gradleVersion) { - build(":shared:assembleMylibDebugSharedLibraryLinuxX64", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { + build(":shared:assembleMylibDebugSharedLibraryLinuxX64") { assertTasksExecuted( ":shared:compileKotlinLinuxX64", ":shared:assembleMylibDebugSharedLibraryLinuxX64" @@ -80,7 +79,7 @@ class NativeLibraryDslIT : KGPBaseTest() { fun shouldLinkSharedLibrariesFromSingleModuleWithAdditionalLinkArgs(gradleVersion: GradleVersion) { nativeProject("new-kn-library-dsl", gradleVersion) { gradleProperties.appendText("\nkotlin.native.linkArgs=-Xfoo=bar -Xbaz=qux") - build(":shared:assembleMylibDebugSharedLibraryLinuxX64", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { + build(":shared:assembleMylibDebugSharedLibraryLinuxX64") { assertTasksExecuted( ":shared:compileKotlinLinuxX64", ":shared:assembleMylibDebugSharedLibraryLinuxX64" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt index 96ceca38567..154a1a15580 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeLibraryDslWithCocoapodsIT.kt @@ -185,7 +185,7 @@ class NativeLibraryDslWithCocoapodsIT : KGPBaseTest() { private fun buildNewKnLibraryDslCocoapodsProjectWithTasks( gradleVersion: GradleVersion, - buildBlock: TestProject.() -> Unit + buildBlock: TestProject.() -> Unit, ) { nativeProject("new-kn-library-dsl-cocoapods", gradleVersion, test = buildBlock) } 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 0c4a8d4c49e..1d8152f3bf8 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 @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy import org.jetbrains.kotlin.gradle.report.BuildReportType import org.junit.jupiter.api.condition.OS import java.nio.file.Path -import java.nio.file.Paths import java.util.* import kotlin.io.path.absolutePathString @@ -54,7 +53,7 @@ data class BuildOptions( val nativeOptions: NativeOptions = NativeOptions(), val compilerExecutionStrategy: KotlinCompilerExecutionStrategy? = null, val runViaBuildToolsApi: Boolean? = null, - val konanDataDir: Path? = konanDir, + val konanDataDir: Path? = konanDir, // null can be used only if you are using custom 'kotlin.native.home' or 'org.jetbrains.kotlin.native.home' property instead of konanDir ) { val isK2ByDefault get() = KotlinVersion.DEFAULT >= KotlinVersion.KOTLIN_2_0 diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt index a0fc4ae669c..27bdef0b686 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/compilationAssertions.kt @@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.testbase import org.gradle.api.logging.LogLevel import org.gradle.testkit.runner.BuildResult -import org.intellij.lang.annotations.Language import org.jetbrains.kotlin.build.report.metrics.BuildAttribute import java.io.File import java.nio.file.Path diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/nativeTestHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/nativeTestHelpers.kt index 2383d533024..b7054f26756 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/nativeTestHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/nativeTestHelpers.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.gradle.testbase import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.presetName import java.util.* @@ -35,7 +36,6 @@ fun extractNativeCompilerCommandLineArguments(taskOutput: String, toolName: Nati enum class NativeToolKind(val title: String) { KONANC("konanc"), - GENERATE_PLATFORM_LIBRARIES("generatePlatformLibraries"), C_INTEROP("cinterop") } @@ -69,4 +69,23 @@ fun extractNativeToolSettings( emptySequence() // No parameters. else settings.drop(1).map { it.trim() }.takeWhile { it != "]" } +} + +internal object MPPNativeTargets { + val current = when (HostManager.host) { + KonanTarget.LINUX_X64 -> "linux64" + KonanTarget.MACOS_X64 -> "macos64" + KonanTarget.MACOS_ARM64 -> "macosArm64" + KonanTarget.MINGW_X64 -> "mingw64" + else -> error("Unsupported host") + } + + val unsupported = when { + HostManager.hostIsMingw -> setOf("macos64") + HostManager.hostIsLinux -> setOf("macos64") + HostManager.hostIsMac -> emptySet() + else -> error("Unknown host") + } + + val supported = listOf("linux64", "macos64", "mingw64").filter { !unsupported.contains(it) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt index 865c5736e0d..56ab9ae528e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputAssertions.kt @@ -5,8 +5,11 @@ package org.jetbrains.kotlin.gradle.testbase +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 java.util.* /** * Asserts Gradle output contains [expectedSubString] string. @@ -259,6 +262,21 @@ fun BuildResult.assertCompilerArgument( } } +/** + * Asserts classpath of the given K/N compiler tool for given tasks' paths. + * + * Note: Log level of output must be set to [LogLevel.INFO]. + * + * @param tasksPaths tasks' paths, for which classpath should be checked with give assertions + * @param toolName name of build tool + * @param assertions assertions, with will be applied to each classpath of each given task + */ +fun BuildResult.assertNativeTasksClasspath( + vararg tasksPaths: String, + toolName: NativeToolKind = NativeToolKind.KONANC, + assertions: (List) -> Unit, +) = tasksPaths.forEach { taskPath -> assertions(extractNativeCompilerClasspath(getOutputForTask(taskPath, LogLevel.INFO), toolName)) } + fun BuildResult.assertCompilerArguments( taskPath: String, vararg expectedArguments: String, @@ -296,6 +314,21 @@ fun BuildResult.assertNoCompilerArgument( } } +/** + * Asserts environment variables of the given K/N compiler for given tasks' paths + * + * Note: Log level of output must be set to [LogLevel.INFO]. + * + * @param tasksPaths tasks' paths, for which command line arguments should be checked with give assertions. + * @param toolName name of build tool + * @param assertions assertions, with will be applied to each command line arguments of each given task + */ +fun BuildResult.assertNativeTasksCustomEnvironment( + vararg tasksPaths: String, + toolName: NativeToolKind = NativeToolKind.KONANC, + assertions: (Map) -> Unit, +) = tasksPaths.forEach { taskPath -> assertions(extractNativeCustomEnvironment(taskPath, toolName)) } + /** * Asserts that the given list of command line arguments does not contain any of the expected arguments. * @@ -330,6 +363,56 @@ fun CommandLineArguments.assertCommandLineArgumentsContain( } } +/** + * Asserts that the given list of command line arguments contains sequentially all the expected arguments. + * + * @param expectedArgs the list of expected arguments + * @throws AssertionError if any of the expected arguments are missing from the actual arguments list + */ +fun CommandLineArguments.assertCommandLineArgumentsContainSequentially( + vararg expectedArgs: String, +) { + expectedArgs.forEach { + assert(expectedArgs.isNotEmpty() && Collections.indexOfSubList(args, expectedArgs.toList()) != -1) { + this.buildResult.printBuildOutput() + "There is no sequential arguments ${it} in actual command line arguments are: ${args}" + } + } +} + +/** + * Asserts that the output of a Gradle build contains a variant with the given name. + * + * @param variantName The name of the variant to look for in the output. + * @param gradleVersion The version of Gradle used to build the variant. + * @throws AssertionError if no variant with the given name and Gradle version is found in the output. + */ + +fun BuildResult.assertOutputContainsNativeFrameworkVariant(variantName: String, gradleVersion: GradleVersion) { + val isAtLeastGradle75 = gradleVersion >= GradleVersion.version(TestVersions.Gradle.G_7_5) + try { + assertOutputContains( + if (isAtLeastGradle75) + "Variant $variantName" + else "variant \"$variantName\" [" + ) + } catch (originalError: AssertionError) { + val regexPattern = if (isAtLeastGradle75) { + "Variant (.*?):" + } else { + "variant \"(.*?)\" \\[" + } + val matchedVariants = Regex(regexPattern).findAll(output).toList() + throw AssertionError( + "Expected variant $variantName. " + + if (matchedVariants.isNotEmpty()) + "Found instead: " + matchedVariants.joinToString { it.groupValues[1] } + else "No match.", + originalError + ) + } +} + /** * Asserts that the command line arguments do not contain any duplicates. */ @@ -339,3 +422,11 @@ fun CommandLineArguments.assertNoDuplicates() { "Link task has duplicated arguments: ${args.joinToString()}" } } + + +private fun BuildResult.extractNativeCustomEnvironment(taskPath: String, toolName: NativeToolKind): Map = + extractNativeToolSettings(getOutputForTask(taskPath, LogLevel.INFO), toolName, NativeToolSettingsKind.CUSTOM_ENV_VARIABLES).map { + val (key, value) = it.split("=") + key.trim() to value.trim() + }.toMap() + diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputHelpers.kt index 03ef47c9225..4b2afef361b 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/outputHelpers.kt @@ -10,7 +10,7 @@ import org.gradle.testkit.runner.BuildResult import org.intellij.lang.annotations.Language @Language("RegExp") -private fun taskOutputRegex( +private fun taskOutputRegexForDebugLog( taskName: String, ) = """ \[org\.gradle\.internal\.operations\.DefaultBuildOperationRunner] Build operation 'Task $taskName' started @@ -20,22 +20,58 @@ private fun taskOutputRegex( .replace("\n", "") .toRegex() -/** - * Filter [BuildResult.getOutput] for specific task with given [taskPath] - * - * Requires using [LogLevel.DEBUG]. - */ -fun BuildResult.getOutputForTask(taskPath: String): String = getOutputForTask(taskPath, output) +@Language("RegExp") +private fun taskOutputRegexForInfoLog( + taskName: String, +) = """ + ^\s*$ + ^> Task $taskName$ + ([\s\S]+?) + ^\s*$ + """.trimIndent() + .toRegex(RegexOption.MULTILINE) /** - * Filter give output for specific task with given [taskPath] + * Gets the output produced by a specific task during a Gradle build. * - * Requires using [LogLevel.DEBUG]. + * @param taskPath The path of the task whose output should be retrieved. + * @param logLevel The given output contains no more than the [logLevel] logs. + * + * @return The output produced by the specified task during the build. + * + * @throws IllegalStateException if the specified task path does not match any tasks in the build. */ -fun getOutputForTask(taskPath: String, output: String): String = taskOutputRegex(taskPath) +fun BuildResult.getOutputForTask(taskPath: String, logLevel: LogLevel = LogLevel.DEBUG): String = + getOutputForTask(taskPath, output, logLevel) + +/** + * Gets the output produced by a specific task during a Gradle build. + * + * @param taskPath The path of the task whose output should be retrieved. + * @param output The output from which we should extract task's output + * @param logLevel The given output contains no more than the [logLevel] logs. + * + * @return The output produced by the specified task during the build. + * + * @throws IllegalStateException if the specified task path does not match any tasks in the build. + */ +fun getOutputForTask(taskPath: String, output: String, logLevel: LogLevel = LogLevel.DEBUG): String = ( + when (logLevel) { + LogLevel.INFO -> taskOutputRegexForInfoLog(taskPath) + LogLevel.DEBUG -> taskOutputRegexForDebugLog(taskPath) + else -> throw throw IllegalStateException("Unsupported log lever for task output was given: $logLevel") + }) .find(output) ?.let { it.groupValues[1] } - ?: error("Could not find output for task $taskPath") + ?: error( + """ + Could not find output for task $taskPath. + ================= + Build output is: + $output + ================= + """.trimIndent() + ) class CommandLineArguments( val args: List, @@ -49,15 +85,18 @@ class CommandLineArguments( * * @param tasksPaths The paths of the tasks for which the command line arguments should be checked against the provided assertions. * @param toolName The name of the build tool used. + * @param logLevel The given output contains no more than the [logLevel] logs. * @param assertions The assertions to be applied to each command line argument of each given task. * These assertions validate the expected properties of the command line arguments. + * These assertions validate the expected properties of the command line arguments. */ fun BuildResult.extractNativeTasksCommandLineArgumentsFromOutput( vararg tasksPaths: String, toolName: NativeToolKind = NativeToolKind.KONANC, + logLevel: LogLevel = LogLevel.INFO, assertions: CommandLineArguments.() -> Unit, ) = tasksPaths.forEach { taskPath -> - val taskOutput = getOutputForTask(taskPath) + val taskOutput = getOutputForTask(taskPath, logLevel) val commandLineArguments = extractNativeCompilerCommandLineArguments(taskOutput, toolName) assertions( CommandLineArguments(commandLineArguments, this) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt index 5776c922b7e..650f832f16f 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/pathHelpers.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.testbase -import java.io.IOException import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes import kotlin.io.path.* diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt index 6fb5c3310f7..e9a7c8011b5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/tasksAssertions.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.gradle.testbase -import org.gradle.api.logging.LogLevel import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.TaskOutcome @@ -150,21 +149,6 @@ fun BuildResult.assertTasksPackedToCache(vararg tasks: String) { } } -/** - * Asserts classpath of the given K/N compiler tool for given tasks' paths. - * - * Note: Log level of output must be set to [LogLevel.DEBUG]. - * - * @param tasksPaths tasks' paths, for which classpath should be checked with give assertions - * @param toolName name of build tool - * @param assertions assertions, with will be applied to each classpath of each given task - */ -fun BuildResult.assertNativeTasksClasspath( - vararg tasksPaths: String, - toolName: NativeToolKind = NativeToolKind.KONANC, - assertions: (List) -> Unit, -) = tasksPaths.forEach { taskPath -> assertions(extractNativeCompilerClasspath(getOutputForTask(taskPath), toolName)) } - /** * Builds test project with 'tasks --all' arguments and then * asserts that [registeredTasks] of the given tasks have been registered diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testAssertions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testAssertions.kt index e7871d3c818..e90f8883e02 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testAssertions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testAssertions.kt @@ -12,19 +12,18 @@ import org.jdom.input.SAXBuilder import org.jdom.output.Format import org.jdom.output.XMLOutputter import org.jetbrains.kotlin.test.util.trimTrailingWhitespaces -import java.nio.file.Files import java.nio.file.Path -import kotlin.io.path.* -import kotlin.streams.asSequence -import kotlin.streams.toList +import kotlin.io.path.absolutePathString +import kotlin.io.path.name +import kotlin.io.path.readText import kotlin.test.assertEquals -fun GradleProject.assertTestResults(expectedTestReport: Path, vararg testReportNames: String) { +fun GradleProject.assertTestResults(expectedTestReport: Path, vararg testReportNames: String, cleanupStdOut: (String) -> String = { it }) { val testReportDirs = testReportNames.map { projectPath.resolve("build/test-results/$it") } assertDirectoriesExist(*testReportDirs.toTypedArray()) - val actualTestResults = readAndCleanupTestResults(testReportDirs, projectPath) + val actualTestResults = readAndCleanupTestResults(testReportDirs, projectPath, cleanupStdOut) val expectedTestResults = prettyPrintXml(expectedTestReport.readText()) assertEquals(expectedTestResults, actualTestResults) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt index 735f8944850..38e212ab3ce 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/testDsl.kt @@ -304,12 +304,14 @@ open class GradleProject( fun classesDir( sourceSet: String = "main", - language: String = "kotlin", - ): Path = projectPath.resolve("build/classes/$language/$sourceSet/") + targetName: String? = null, + language: String = "kotlin" + ): Path = projectPath.resolve("build/classes/$language/${targetName.orEmpty()}/$sourceSet/") fun kotlinClassesDir( sourceSet: String = "main", - ): Path = classesDir(sourceSet, language = "kotlin") + targetName: String? = null + ): Path = classesDir(sourceSet, targetName, language = "kotlin") fun javaClassesDir( sourceSet: String = "main", @@ -328,6 +330,9 @@ open class GradleProject( ): List = files.map { projectPath.relativize(it) } } +/** + * You need at least [TestVersions.Gradle.G_7_0] for supporting environment variables with gradle runner + */ @JvmInline value class EnvironmentalVariables @EnvironmentalVariablesOverride constructor(val environmentalVariables: Map = emptyMap()) { @EnvironmentalVariablesOverride constructor(vararg environmentVariables: Pair) : this(mapOf(*environmentVariables)) @@ -579,7 +584,6 @@ internal fun Path.enableAndroidSdk() { applyAndroidTestFixes() } - internal fun Path.enableCacheRedirector() { // Path relative to the current gradle module project dir val redirectorScript = Paths.get("../../../repo/scripts/cache-redirector.settings.gradle.kts") diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/lib/build.gradle.kts index 0b6c8d2c417..17144544d3c 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/lib/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/lib/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform").version("") + kotlin("multiplatform") `maven-publish` } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/shared/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/shared/build.gradle.kts index 62dcc10847c..aaa78123c67 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/shared/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/export-published-lib/shared/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform").version("") + kotlin("multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/frameworks/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/frameworks/build.gradle.kts index cac66402276..2da7e3a6974 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/frameworks/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/frameworks/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/kotlin-dsl/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/kotlin-dsl/build.gradle.kts index 868aaf1299a..3898047c7fe 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/kotlin-dsl/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/kotlin-dsl/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/libraries/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/libraries/build.gradle.kts index 13b5a71434f..b187092a608 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/libraries/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-binaries/libraries/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cinterop/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cinterop/build.gradle index c103d616d4e..91f6bb6cf3a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cinterop/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-cinterop/build.gradle @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-compiler-version/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-compiler-version/build.gradle.kts index d65004eb4da..2176c963c8a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-compiler-version/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-compiler-version/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-kotlin-options/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-kotlin-options/build.gradle index d65004eb4da..2176c963c8a 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-kotlin-options/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-kotlin-options/build.gradle @@ -1,5 +1,5 @@ plugins { - id("org.jetbrains.kotlin.multiplatform").version("") + id("org.jetbrains.kotlin.multiplatform") } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/build.gradle.kts index 63d039d03b8..ab486b8a624 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/build.gradle.kts @@ -1,5 +1,6 @@ +import java.util.concurrent.CountDownLatch plugins { - id("org.jetbrains.kotlin.multiplatform").version("").apply(false) + id("org.jetbrains.kotlin.multiplatform").apply(false) } allprojects { @@ -7,4 +8,4 @@ allprojects { mavenLocal() mavenCentral() } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/one/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/one/build.gradle.kts index 2e8717ef308..3a9f3b1cba5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/one/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/one/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.concurrent.CountDownLatch plugins { id("org.jetbrains.kotlin.multiplatform") } @@ -8,4 +9,4 @@ kotlin { sourceSets["commonMain"].dependencies { implementation(kotlin("stdlib")) } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/two/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/two/build.gradle.kts index 2e8717ef308..3a9f3b1cba5 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/two/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-parallel/two/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.concurrent.CountDownLatch plugins { id("org.jetbrains.kotlin.multiplatform") } @@ -8,4 +9,4 @@ kotlin { sourceSets["commonMain"].dependencies { implementation(kotlin("stdlib")) } -} \ No newline at end of file +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-root-project-name-with-space/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-root-project-name-with-space/build.gradle.kts index f20fa808430..d23f2cbfab6 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-root-project-name-with-space/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-root-project-name-with-space/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - kotlin("multiplatform").version("").apply(false) + kotlin("multiplatform").apply(false) } allprojects { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index 82ea02ecefd..10c57bc0524 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -558,7 +558,7 @@ internal class PropertiesProvider private constructor(private val project: Proje private fun propertyWithDeprecatedVariant(propName: String, deprecatedPropName: String): String? { val deprecatedProperty = property(deprecatedPropName).orNull if (deprecatedProperty != null) { - project.reportDiagnosticOncePerBuild(KotlinToolingDiagnostics.DeprecatedPropertyWithReplacement(deprecatedProperty, propName)) + project.reportDiagnosticOncePerBuild(KotlinToolingDiagnostics.DeprecatedPropertyWithReplacement(deprecatedPropName, propName)) } return property(propName).orNull ?: deprecatedProperty }