diff --git a/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/mppNativeBinaryDSLCodegen.kt b/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/mppNativeBinaryDSLCodegen.kt index b39abe7a689..f85bdc8e799 100644 --- a/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/mppNativeBinaryDSLCodegen.kt +++ b/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/mppNativeBinaryDSLCodegen.kt @@ -149,7 +149,7 @@ fun generateAbstractKotlinNativeBinaryContainer() { val classProperties = listOf( "abstract val project: Project", - "abstract val target: ${typeName(KOTLIN_NATIVE_TARGET_CLASS_FQNAME).shortName()}" + "abstract val target: ${typeName(NativeFQNames.Targets.base).shortName()}" ).joinToString(separator = "\n") { it.indented(4) } val nativeBinary = nativeBinaryBaseClass.renderShort() diff --git a/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/presetEntries.kt b/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/presetEntries.kt index db917e111cd..48f74b9230b 100644 --- a/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/presetEntries.kt +++ b/libraries/tools/kotlin-gradle-plugin-dsl-codegen/src/main/kotlin/org/jetbrains/kotlin/generators/gradle/dsl/presetEntries.kt @@ -5,6 +5,9 @@ package org.jetbrains.kotlin.generators.gradle.dsl +import org.jetbrains.kotlin.generators.gradle.dsl.NativeFQNames.Presets +import org.jetbrains.kotlin.generators.gradle.dsl.NativeFQNames.Targets +import org.jetbrains.kotlin.generators.gradle.dsl.NativeFQNames.TestRuns import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.presetName @@ -19,11 +22,25 @@ internal fun KotlinPresetEntry.typeNames(): Set = setOf(presetType, ta internal const val MPP_PACKAGE = "org.jetbrains.kotlin.gradle.plugin.mpp" -internal const val KOTLIN_NATIVE_TARGET_PRESET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTargetPreset" -internal const val KOTLIN_NATIVE_TARGET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTarget" +internal object NativeFQNames { + const val nativePackage = "org.jetbrains.kotlin.gradle.targets.native" -internal const val KOTLIN_NATIVE_TARGET_WITH_TESTS_PRESET_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTargetWithTestsPreset" -internal const val KOTLIN_NATIVE_TARGET_WITH_TESTS_CLASS_FQNAME = "$MPP_PACKAGE.KotlinNativeTargetWithTests" + object Targets { + const val base = "$MPP_PACKAGE.KotlinNativeTarget" + const val withTests = "$MPP_PACKAGE.KotlinNativeTargetWithTests" + } + + object Presets { + const val simple = "$MPP_PACKAGE.KotlinNativeTargetPreset" + const val withHostTests = "$MPP_PACKAGE.KotlinNativeTargetWithHostTestsPreset" + const val withSimulatorTests = "$MPP_PACKAGE.KotlinNativeTargetWithSimulatorTestsPreset" + } + + object TestRuns { + const val host = "$nativePackage.KotlinNativeHostTestRun" + const val simulator = "$nativePackage.KotlinNativeSimulatorTestRun" + } +} internal val jvmPresetEntry = KotlinPresetEntry( "jvm", @@ -44,27 +61,24 @@ internal val androidPresetEntry = KotlinPresetEntry( ) // Note: modifying these sets should also be reflected in the MPP plugin code, see 'setupDefaultPresets' -private val testableNativeTargets = setOf(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64, KonanTarget.MINGW_X64) +private val nativeTargetsWithHostTests = setOf(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64, KonanTarget.MINGW_X64) +private val nativeTargetsWithSimulatorTests = setOf(KonanTarget.IOS_X64, KonanTarget.WATCHOS_X86, KonanTarget.TVOS_X64) private val disabledNativeTargets = setOf(KonanTarget.WATCHOS_X64) internal val nativePresetEntries = HostManager().targets .filter { (_, target) -> target !in disabledNativeTargets } .map { (_, target) -> - KotlinPresetEntry( - target.presetName, - typeName( - if (target in testableNativeTargets) - KOTLIN_NATIVE_TARGET_WITH_TESTS_PRESET_CLASS_FQNAME - else - KOTLIN_NATIVE_TARGET_PRESET_CLASS_FQNAME - ), - typeName( - if (target in testableNativeTargets) - KOTLIN_NATIVE_TARGET_WITH_TESTS_CLASS_FQNAME - else - KOTLIN_NATIVE_TARGET_CLASS_FQNAME - ) - ) + + val (presetType, targetType) = when (target) { + in nativeTargetsWithHostTests -> + typeName(Presets.withHostTests) to typeName(Targets.withTests, TestRuns.host) + in nativeTargetsWithSimulatorTests -> + typeName(Presets.withSimulatorTests) to typeName(Targets.withTests, TestRuns.simulator) + else -> + typeName(Presets.simple) to typeName(Targets.base) + } + + KotlinPresetEntry(target.presetName, presetType, targetType) } internal val allPresetEntries = listOf( 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 20c6bf3b23d..00f0346c7cd 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 @@ -1280,7 +1280,7 @@ class NewMultiplatformIT : BaseGradleIT() { @Test fun testNativeTests() = with(Project("new-mpp-native-tests", gradleVersion)) { - val testTasks = listOf("macos64Test", "linux64Test", "mingw64Test") + val testTasks = listOf("macos64Test", "linux64Test", "mingw64Test", "iosTest") val hostTestTask = "${nativeHostTargetName}Test" val suffix = if (isWindows) "exe" else "kexe" @@ -1305,7 +1305,16 @@ class NewMultiplatformIT : BaseGradleIT() { build("check") { assertSuccessful() - assertTasksExecuted(":$hostTestTask") + + val testsToExecute = mutableListOf(":$hostTestTask") + if (HostManager.hostIsMac) { + testsToExecute.add(":iosTest") + } + val testsToSkip = testTasks.map { ":$it" } - testsToExecute + + assertTasksExecuted(*testsToExecute.toTypedArray()) + assertTasksSkipped(*testsToSkip.toTypedArray()) + assertFileExists(defaultOutputFile) assertTestResults("testProject/new-mpp-native-tests/TEST-TestKt.xml", hostTestTask) } @@ -2122,8 +2131,8 @@ class NewMultiplatformIT : BaseGradleIT() { gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) // TOOD: add Kotlin/JS tests once they can be tested without much performance overhead - val testTasks = - arrayOf(":jvmTest", ":${nativeHostTargetName}Test", ":jvmIntegrationTest", ":${nativeHostTargetName}IntegrationTest") + val targetsToTest = listOf("jvm", nativeHostTargetName) + listOf("ios").takeIf { HostManager.hostIsMac }.orEmpty() + val testTasks = targetsToTest.flatMap { listOf(":${it}Test", ":${it}IntegrationTest") }.toTypedArray() build(*testTasks) { assertSuccessful() @@ -2139,11 +2148,12 @@ class NewMultiplatformIT : BaseGradleIT() { .resolve("build/reports/tests/${targetName}Test/classes/com.example.HelloTest.html") .readText() - if (targetName != nativeHostTargetName) // TODO: fix exclude patterns for the Kotlin/Native test tasks + if (targetName != nativeHostTargetName && targetName != "ios") // TODO: fix exclude patterns for the Kotlin/Native test tasks assertTrue("secondTest" !in classReportHtml, "Test report should not contain 'secondTest':\n$classReportHtml") } - checkUnitTestOutput("jvm") - checkUnitTestOutput(nativeHostTargetName) + targetsToTest.forEach { + checkUnitTestOutput(it) + } fun checkIntegrationTestOutput(targetName: String) { val classReportHtml = projectDir @@ -2154,8 +2164,9 @@ class NewMultiplatformIT : BaseGradleIT() { assertTrue("secondTest" !in classReportHtml, "Test report should not contain 'secondTest':\n$classReportHtml") assertTrue("thirdTest" !in classReportHtml, "Test report should not contain 'thirdTest':\n$classReportHtml") } - checkIntegrationTestOutput("jvm") - checkIntegrationTestOutput(nativeHostTargetName) + targetsToTest.forEach { + checkIntegrationTestOutput(it) + } } } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-associate-compilations/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-associate-compilations/build.gradle index e3891e64792..1d4a3874749 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-associate-compilations/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-associate-compilations/build.gradle @@ -49,6 +49,7 @@ kotlin { mingwX64("mingw64") {} linuxX64("linux64") {} macosX64("macos64") {} + iosX64("ios") {} targets.matching { it.name != "metadata" }.all { compilations.create("integrationTest") { @@ -89,7 +90,7 @@ kotlin { } } - targets.matching { it.name == "mingw64" || it.name == "linux64" || it.name == "macos64" }.all { + targets.matching { it.name == "mingw64" || it.name == "linux64" || it.name == "macos64" || it.name == "ios" }.all { testRuns { getByName("test").filter { excludeTestsMatching("*.secondTest") } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/build.gradle index fb057b7da07..01c12c80b58 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-native-tests/build.gradle @@ -25,6 +25,8 @@ kotlin { fromPreset(presets.linuxX64, 'linux64') fromPreset(presets.mingwX64, 'mingw64') + fromPreset(presets.iosX64, 'ios') + // Test creating and accessing test binaries for cross-targets fromPreset(presets.wasm32, 'wasm') diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetContainerWithPresetFunctions.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetContainerWithPresetFunctions.kt index 3c40d86a121..de12ef49c46 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetContainerWithPresetFunctions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetContainerWithPresetFunctions.kt @@ -9,10 +9,13 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsTargetPreset import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmTargetPreset import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetPreset +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTestsPreset +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTestsPreset import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTestsPreset import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTarget import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget +import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeHostTestRun +import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeSimulatorTestRun // DO NOT EDIT MANUALLY! Generated by org.jetbrains.kotlin.generators.gradle.dsl.MppPresetFunctionsCodegenKt @@ -155,11 +158,11 @@ interface KotlinTargetContainerWithPresetFunctions : KotlinTargetsContainerWithP fun iosX64( name: String = "iosX64", - configure: KotlinNativeTarget.() -> Unit = { } - ): KotlinNativeTarget = + configure: KotlinNativeTargetWithTests.() -> Unit = { } + ): KotlinNativeTargetWithTests = configureOrCreate( name, - presets.getByName("iosX64") as KotlinNativeTargetPreset, + presets.getByName("iosX64") as KotlinNativeTargetWithSimulatorTestsPreset, configure ) @@ -200,11 +203,11 @@ interface KotlinTargetContainerWithPresetFunctions : KotlinTargetsContainerWithP fun watchosX86( name: String = "watchosX86", - configure: KotlinNativeTarget.() -> Unit = { } - ): KotlinNativeTarget = + configure: KotlinNativeTargetWithTests.() -> Unit = { } + ): KotlinNativeTargetWithTests = configureOrCreate( name, - presets.getByName("watchosX86") as KotlinNativeTargetPreset, + presets.getByName("watchosX86") as KotlinNativeTargetWithSimulatorTestsPreset, configure ) @@ -230,11 +233,11 @@ interface KotlinTargetContainerWithPresetFunctions : KotlinTargetsContainerWithP fun tvosX64( name: String = "tvosX64", - configure: KotlinNativeTarget.() -> Unit = { } - ): KotlinNativeTarget = + configure: KotlinNativeTargetWithTests.() -> Unit = { } + ): KotlinNativeTargetWithTests = configureOrCreate( name, - presets.getByName("tvosX64") as KotlinNativeTargetPreset, + presets.getByName("tvosX64") as KotlinNativeTargetWithSimulatorTestsPreset, configure ) @@ -245,11 +248,11 @@ interface KotlinTargetContainerWithPresetFunctions : KotlinTargetsContainerWithP fun linuxX64( name: String = "linuxX64", - configure: KotlinNativeTargetWithTests.() -> Unit = { } - ): KotlinNativeTargetWithTests = + configure: KotlinNativeTargetWithTests.() -> Unit = { } + ): KotlinNativeTargetWithTests = configureOrCreate( name, - presets.getByName("linuxX64") as KotlinNativeTargetWithTestsPreset, + presets.getByName("linuxX64") as KotlinNativeTargetWithHostTestsPreset, configure ) @@ -320,11 +323,11 @@ interface KotlinTargetContainerWithPresetFunctions : KotlinTargetsContainerWithP fun mingwX64( name: String = "mingwX64", - configure: KotlinNativeTargetWithTests.() -> Unit = { } - ): KotlinNativeTargetWithTests = + configure: KotlinNativeTargetWithTests.() -> Unit = { } + ): KotlinNativeTargetWithTests = configureOrCreate( name, - presets.getByName("mingwX64") as KotlinNativeTargetWithTestsPreset, + presets.getByName("mingwX64") as KotlinNativeTargetWithHostTestsPreset, configure ) @@ -350,11 +353,11 @@ interface KotlinTargetContainerWithPresetFunctions : KotlinTargetsContainerWithP fun macosX64( name: String = "macosX64", - configure: KotlinNativeTargetWithTests.() -> Unit = { } - ): KotlinNativeTargetWithTests = + configure: KotlinNativeTargetWithTests.() -> Unit = { } + ): KotlinNativeTargetWithTests = configureOrCreate( name, - presets.getByName("macosX64") as KotlinNativeTargetWithTestsPreset, + presets.getByName("macosX64") as KotlinNativeTargetWithHostTestsPreset, configure ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 64e6e6f212e..6290f28fab5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -32,7 +32,7 @@ import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.utils.* import org.jetbrains.kotlin.konan.target.HostManager -import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.konan.target.KonanTarget.* import org.jetbrains.kotlin.konan.target.presetName class KotlinMultiplatformPlugin( @@ -139,18 +139,22 @@ class KotlinMultiplatformPlugin( add(KotlinJvmWithJavaTargetPreset(project, kotlinPluginVersion)) // Note: modifying these sets should also be reflected in the DSL code generator, see 'presetEntries.kt' - val testableNativeTargets = setOf(KonanTarget.LINUX_X64, KonanTarget.MACOS_X64, KonanTarget.MINGW_X64) - val disabledNativeTargets = setOf(KonanTarget.WATCHOS_X64) + val nativeTargetsWithHostTests = setOf(LINUX_X64, MACOS_X64, MINGW_X64) + val nativeTargetsWithSimulatorTests = setOf(IOS_X64, WATCHOS_X86, TVOS_X64) + val disabledNativeTargets = setOf(WATCHOS_X64) HostManager().targets - .filter { (_, target) -> target !in disabledNativeTargets } - .forEach { (_, target) -> - add( - if (target in testableNativeTargets) - KotlinNativeTargetWithTestsPreset(target.presetName, project, target, kotlinPluginVersion) - else - KotlinNativeTargetPreset(target.presetName, project, target, kotlinPluginVersion) - ) + .filter { (_, konanTarget) -> konanTarget !in disabledNativeTargets } + .forEach { (_, konanTarget) -> + val targetToAdd = when (konanTarget) { + in nativeTargetsWithHostTests -> + KotlinNativeTargetWithHostTestsPreset(konanTarget.presetName, project, konanTarget, kotlinPluginVersion) + in nativeTargetsWithSimulatorTests -> + KotlinNativeTargetWithSimulatorTestsPreset(konanTarget.presetName, project, konanTarget, kotlinPluginVersion) + else -> KotlinNativeTargetPreset(konanTarget.presetName, project, konanTarget, kotlinPluginVersion) + } + + add(targetToAdd) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeBinaryTestRun.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeBinaryTestRun.kt index e6a29738069..360c98c5cef 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeBinaryTestRun.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeBinaryTestRun.kt @@ -10,6 +10,8 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinTargetTestRun import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeHostTest +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.jetbrains.kotlin.gradle.testing.KotlinTaskTestRun @@ -29,8 +31,18 @@ interface KotlinNativeBinaryTestRun : KotlinTargetTestRun(testRunName, target), +interface KotlinNativeHostTestRun : KotlinNativeBinaryTestRun + +interface KotlinNativeSimulatorTestRun : KotlinNativeBinaryTestRun { + /** + * Determines which simulated device will be used to execute the test run. + * To get list of all available devices, run `xcrun simctl list`. + */ + var deviceId: String +} + +abstract class AbstractKotlinNativeTestRun(testRunName: String, target: KotlinNativeTarget) : + KotlinTaskTestRun(testRunName, target), KotlinNativeBinaryTestRun { private lateinit var _executionSource: NativeBinaryTestRunSource @@ -46,10 +58,25 @@ open class DefaultKotlinNativeTestRun(testRunName: String, target: KotlinNativeT override fun setExecutionSourceFrom(testExecutable: TestExecutable) { require(testExecutable.target === target) { - "Expected a test fbinary of target ${target.name}, " + + "Expected a test binary of target ${target.name}, " + "got the binary ${testExecutable.name} of target ${testExecutable.target.name}" } executionSource = NativeBinaryTestRunSource(testExecutable) } +} + +open class DefaultHostTestRun(testRunName: String, target: KotlinNativeTarget) : + AbstractKotlinNativeTestRun(testRunName, target), + KotlinNativeHostTestRun + +open class DefaultSimulatorTestRun(testRunName: String, target: KotlinNativeTarget) : + AbstractKotlinNativeTestRun(testRunName, target), + KotlinNativeSimulatorTestRun { + + override var deviceId: String + get() = executionTask.get().deviceId + set(value) { + executionTask.get().deviceId = value + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt index 7b18ecb899e..65311ba7fb2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt @@ -65,11 +65,12 @@ open class KotlinNativeTarget @Inject constructor( } } -open class KotlinNativeTargetWithTests @Inject constructor( +// TODO: Add separate classes for corresponding targets? +open class KotlinNativeTargetWithTests @Inject constructor( project: Project, konanTarget: KonanTarget -) : KotlinNativeTarget(project, konanTarget), KotlinTargetWithTests { +) : KotlinNativeTarget(project, konanTarget), KotlinTargetWithTests { - override lateinit var testRuns: NamedDomainObjectContainer + override lateinit var testRuns: NamedDomainObjectContainer internal set } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt index f841ed0e8f4..f6c29cc5cf0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt @@ -21,13 +21,16 @@ import org.gradle.language.base.plugins.LifecycleBasePlugin import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.targets.native.DefaultKotlinNativeTestRun -import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeBinaryTestRun +import org.jetbrains.kotlin.gradle.targets.native.* +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeHostTest +import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.testing.internal.configureConventions import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry import org.jetbrains.kotlin.gradle.testing.testTaskName +import org.jetbrains.kotlin.konan.target.HostManager +import org.jetbrains.kotlin.konan.target.KonanTarget import java.io.File import java.util.* @@ -279,7 +282,10 @@ open class KotlinNativeTargetConfigurator( } /** - * We create test binaries for all platforms but test runs only for host platforms, see [KotlinNativeTargetWithTestsConfigurator] + * We create test binaries for all platforms but test runs only for: + * - host platforms: macosX64, linuxX64, mingwX64; + * - simulated platforms: iosX64, tvosX64, watchosX64. + * See more in [KotlinNativeTargetWithTestsConfigurator] and its subclasses. */ target.binaries.test(listOf(NativeBuildType.DEBUG)) { } } @@ -381,34 +387,40 @@ open class KotlinNativeTargetConfigurator( usesPlatformOf(target) isVisible = false isCanBeConsumed = false - attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target)) + attributes.attribute(USAGE_ATTRIBUTE, KotlinUsages.consumerApiUsage(target)) description = "Dependencies for cinterop '${cinterop.name}' (compilation '${compilation.name}')." } } } } -class KotlinNativeTargetWithTestsConfigurator(kotlinPluginVersion: String) : - KotlinNativeTargetConfigurator(kotlinPluginVersion), - KotlinTargetWithTestsConfigurator { +abstract class KotlinNativeTargetWithTestsConfigurator( + kotlinPluginVersion: String +) : KotlinNativeTargetConfigurator>(kotlinPluginVersion), + KotlinTargetWithTestsConfigurator> { - override val testRunClass: Class - get() = KotlinNativeBinaryTestRun::class.java + abstract val testTaskClass: Class - override fun createTestRun(name: String, target: KotlinNativeTargetWithTests): KotlinNativeBinaryTestRun = - DefaultKotlinNativeTestRun(name, target).apply { + abstract fun isTestTaskEnabled(target: KotlinNativeTargetWithTests<*>): Boolean + + protected open fun configureTestTask(target: KotlinNativeTargetWithTests<*>, testTask: TaskType) { + testTask.group = LifecycleBasePlugin.VERIFICATION_GROUP + testTask.description = "Executes Kotlin/Native unit tests for target ${target.name}." + testTask.targetName = target.name + + testTask.enabled = isTestTaskEnabled(target) + + testTask.workingDir = target.project.projectDir.absolutePath + + testTask.configureConventions() + } + + protected open fun configureTestRun(target: KotlinNativeTargetWithTests<*>, testRun: AbstractKotlinNativeTestRun) { + with(testRun) { val project = target.project - val testTaskOrProvider = project.registerTask(testTaskName) { testTask -> - testTask.group = LifecycleBasePlugin.VERIFICATION_GROUP - testTask.description = "Executes Kotlin/Native unit tests for target ${target.name}." - testTask.targetName = target.name - - testTask.enabled = target.konanTarget.isCurrentHost - - testTask.workingDir = project.projectDir.absolutePath - - testTask.configureConventions() + val testTaskOrProvider = project.registerTask(testTaskName, testTaskClass) { testTask -> + configureTestTask(target, testTask) } executionTask = testTaskOrProvider @@ -417,4 +429,54 @@ class KotlinNativeTargetWithTestsConfigurator(kotlinPluginVersion: String) : project.kotlinTestRegistry.registerTestTask(testTaskOrProvider) } + } +} + +class KotlinNativeTargetWithHostTestsConfigurator(kotlinPluginVersion: String) : + KotlinNativeTargetWithTestsConfigurator(kotlinPluginVersion) { + + override val testTaskClass: Class + get() = KotlinNativeHostTest::class.java + + override val testRunClass: Class + get() = KotlinNativeHostTestRun::class.java + + override fun isTestTaskEnabled(target: KotlinNativeTargetWithTests<*>): Boolean = + target.konanTarget.isCurrentHost + + override fun createTestRun( + name: String, + target: KotlinNativeTargetWithTests + ): KotlinNativeHostTestRun = + DefaultHostTestRun(name, target).apply { configureTestRun(target, this) } +} + +class KotlinNativeTargetWithSimulatorTestsConfigurator(kotlinPluginVersion: String) : + KotlinNativeTargetWithTestsConfigurator(kotlinPluginVersion) { + + override val testTaskClass: Class + get() = KotlinNativeSimulatorTest::class.java + + override val testRunClass: Class + get() = KotlinNativeSimulatorTestRun::class.java + + override fun isTestTaskEnabled(target: KotlinNativeTargetWithTests<*>): Boolean = + HostManager.hostIsMac + + override fun configureTestTask(target: KotlinNativeTargetWithTests<*>, testTask: KotlinNativeSimulatorTest) { + super.configureTestTask(target, testTask) + + testTask.deviceId = when (target.konanTarget) { + KonanTarget.IOS_X64 -> "iPhone 8" + KonanTarget.WATCHOS_X86 -> "Apple Watch Series 5 - 44mm" + KonanTarget.TVOS_X64 -> "Apple TV" + else -> error("Simulator tests are not supported for platform ${target.konanTarget.name}") + } + } + + override fun createTestRun( + name: String, + target: KotlinNativeTargetWithTests + ): KotlinNativeSimulatorTestRun = + DefaultSimulatorTestRun(name, target).apply { configureTestRun(target, this) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt index 8cca0b0e3d1..9f6a21e1168 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetPreset.kt @@ -16,6 +16,8 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.targets.native.DisabledNativeTargetsReporter +import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeHostTestRun +import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeSimulatorTestRun import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild import org.jetbrains.kotlin.konan.target.HostManager @@ -181,15 +183,28 @@ open class KotlinNativeTargetPreset(name: String, project: Project, konanTarget: } } -open class KotlinNativeTargetWithTestsPreset(name: String, project: Project, konanTarget: KonanTarget, kotlinPluginVersion: String) : - AbstractKotlinNativeTargetPreset(name, project, konanTarget, kotlinPluginVersion) { +open class KotlinNativeTargetWithHostTestsPreset(name: String, project: Project, konanTarget: KonanTarget, kotlinPluginVersion: String) : + AbstractKotlinNativeTargetPreset>(name, project, konanTarget, kotlinPluginVersion) { - override fun createTargetConfigurator(): KotlinTargetConfigurator = - KotlinNativeTargetWithTestsConfigurator(kotlinPluginVersion) + override fun createTargetConfigurator(): KotlinTargetConfigurator> = + KotlinNativeTargetWithHostTestsConfigurator(kotlinPluginVersion) - override fun instantiateTarget(name: String): KotlinNativeTargetWithTests { - return project.objects.newInstance(KotlinNativeTargetWithTests::class.java, project, konanTarget) - } + @Suppress("UNCHECKED_CAST") + override fun instantiateTarget(name: String): KotlinNativeTargetWithTests = + project.objects.newInstance(KotlinNativeTargetWithTests::class.java, project, konanTarget) + as KotlinNativeTargetWithTests +} + +open class KotlinNativeTargetWithSimulatorTestsPreset(name: String, project: Project, konanTarget: KonanTarget, kotlinPluginVersion: String) : + AbstractKotlinNativeTargetPreset>(name, project, konanTarget, kotlinPluginVersion) { + + override fun createTargetConfigurator(): KotlinTargetConfigurator> = + KotlinNativeTargetWithSimulatorTestsConfigurator(kotlinPluginVersion) + + @Suppress("UNCHECKED_CAST") + override fun instantiateTarget(name: String): KotlinNativeTargetWithTests = + project.objects.newInstance(KotlinNativeTargetWithTests::class.java, project, konanTarget) + as KotlinNativeTargetWithTests } internal val KonanTarget.isCurrentHost: Boolean diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTest.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTest.kt index 7a6491a8125..ee5a0abd821 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/tasks/KotlinNativeTest.kt @@ -11,6 +11,7 @@ import org.gradle.api.file.FileCollection import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.* +import org.gradle.api.tasks.options.Option import org.gradle.process.ProcessForkOptions import org.gradle.process.internal.DefaultProcessForkOptions import org.jetbrains.kotlin.compilerRunner.konanVersion @@ -22,7 +23,7 @@ import org.jetbrains.kotlin.konan.CompilerVersion import java.io.File import java.util.concurrent.Callable -open class KotlinNativeTest : KotlinTest() { +abstract class KotlinNativeTest : KotlinTest() { @Suppress("LeakingThis") private val processOptions: ProcessForkOptions = DefaultProcessForkOptions(fileResolver) @@ -97,6 +98,9 @@ open class KotlinNativeTest : KotlinTest() { processOptions.environment(name, value) } + @get:Internal + protected abstract val testCommand: TestCommand + // KonanVersion doesn't provide an API to compare versions, // so we have to transform it to KotlinVersion first. // Note: this check doesn't take into account the meta version (release, eap, dev). @@ -106,7 +110,7 @@ open class KotlinNativeTest : KotlinTest() { override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec { val extendedForkOptions = DefaultProcessForkOptions(fileResolver) processOptions.copyTo(extendedForkOptions) - extendedForkOptions.executable = executable.absolutePath + extendedForkOptions.executable = testCommand.executable val clientSettings = TCServiceMessagesClientSettings( name, @@ -122,24 +126,28 @@ open class KotlinNativeTest : KotlinTest() { // Thus we check the exit code only for newer versions. val checkExitCode = project.konanVersion.isAtLeast(1, 3, 0) - val cliArgs = CliArgs("TEAMCITY", checkExitCode, includePatterns, excludePatterns, args) + val cliArgs = testCommand.cliArgs("TEAMCITY", checkExitCode, includePatterns, excludePatterns, args) - return TCServiceMessagesTestExecutionSpec( - extendedForkOptions, - cliArgs.toList(), - checkExitCode, - clientSettings - ) + return TCServiceMessagesTestExecutionSpec(extendedForkOptions, cliArgs, checkExitCode, clientSettings) } - private class CliArgs( - val testLogger: String? = null, - val checkExitCode: Boolean = true, - val testGradleFilter: Set = setOf(), - val testNegativeGradleFilter: Set = setOf(), - val userArgs: List = emptyList() - ) { - fun toList() = mutableListOf().also { + protected abstract class TestCommand() { + abstract val executable: String + abstract fun cliArgs( + testLogger: String?, + checkExitCode: Boolean, + testGradleFilter: Set, + testNegativeGradleFilter: Set, + userArgs: List + ): List + + protected fun testArgs( + testLogger: String?, + checkExitCode: Boolean, + testGradleFilter: Set, + testNegativeGradleFilter: Set, + userArgs: List + ): List = mutableListOf().also { if (checkExitCode) { // Avoid returning a non-zero exit code in case of failed tests. @@ -162,3 +170,48 @@ open class KotlinNativeTest : KotlinTest() { } } } + +/** + * A task running Kotlin/Native tests on a host machine. + */ +open class KotlinNativeHostTest : KotlinNativeTest() { + @get:Internal + override val testCommand: TestCommand = object : TestCommand() { + override val executable: String + get() = this@KotlinNativeHostTest.executable.absolutePath + + override fun cliArgs( + testLogger: String?, + checkExitCode: Boolean, + testGradleFilter: Set, + testNegativeGradleFilter: Set, + userArgs: List + ): List = testArgs(testLogger, checkExitCode, testGradleFilter, testNegativeGradleFilter, userArgs) + } +} + +/** + * A task running Kotlin/Native tests on a simulator (iOS/watchOS/tvOS). + */ +// TODO: Support debugging. +open class KotlinNativeSimulatorTest : KotlinNativeTest() { + @Input + @Option(option = "device", description = "Sets a simulated device used to execute tests.") + lateinit var deviceId: String + + @get:Internal + override val testCommand: TestCommand = object : TestCommand() { + override val executable: String + get() = "/usr/bin/xcrun" + + override fun cliArgs( + testLogger: String?, + checkExitCode: Boolean, + testGradleFilter: Set, + testNegativeGradleFilter: Set, + userArgs: List + ): List = + listOf("simctl", "spawn", "--standalone", deviceId, this@KotlinNativeSimulatorTest.executable.absolutePath, "--") + + testArgs(testLogger, checkExitCode, testGradleFilter, testNegativeGradleFilter, userArgs) + } +} \ No newline at end of file