Gradle, native: Support simulator tests

This commit is contained in:
Ilya Matveev
2019-11-29 12:40:44 +07:00
parent 8225c5a9ce
commit e6ab70c18d
12 changed files with 305 additions and 112 deletions
@@ -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()
@@ -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<TypeName> = 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(
@@ -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)
}
}
}
}
@@ -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") }
@@ -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')
@@ -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<KotlinNativeSimulatorTestRun>.() -> Unit = { }
): KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun> =
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<KotlinNativeSimulatorTestRun>.() -> Unit = { }
): KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun> =
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<KotlinNativeSimulatorTestRun>.() -> Unit = { }
): KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun> =
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<KotlinNativeHostTestRun>.() -> Unit = { }
): KotlinNativeTargetWithTests<KotlinNativeHostTestRun> =
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<KotlinNativeHostTestRun>.() -> Unit = { }
): KotlinNativeTargetWithTests<KotlinNativeHostTestRun> =
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<KotlinNativeHostTestRun>.() -> Unit = { }
): KotlinNativeTargetWithTests<KotlinNativeHostTestRun> =
configureOrCreate(
name,
presets.getByName("macosX64") as KotlinNativeTargetWithTestsPreset,
presets.getByName("macosX64") as KotlinNativeTargetWithHostTestsPreset,
configure
)
@@ -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)
}
}
}
@@ -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<NativeBinaryTestRunSou
fun setExecutionSourceFrom(testExecutable: TestExecutable)
}
open class DefaultKotlinNativeTestRun(testRunName: String, target: KotlinNativeTarget) :
KotlinTaskTestRun<NativeBinaryTestRunSource, KotlinNativeTest>(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<T : KotlinNativeTest>(testRunName: String, target: KotlinNativeTarget) :
KotlinTaskTestRun<NativeBinaryTestRunSource, T>(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<KotlinNativeHostTest>(testRunName, target),
KotlinNativeHostTestRun
open class DefaultSimulatorTestRun(testRunName: String, target: KotlinNativeTarget) :
AbstractKotlinNativeTestRun<KotlinNativeSimulatorTest>(testRunName, target),
KotlinNativeSimulatorTestRun {
override var deviceId: String
get() = executionTask.get().deviceId
set(value) {
executionTask.get().deviceId = value
}
}
@@ -65,11 +65,12 @@ open class KotlinNativeTarget @Inject constructor(
}
}
open class KotlinNativeTargetWithTests @Inject constructor(
// TODO: Add separate classes for corresponding targets?
open class KotlinNativeTargetWithTests<T : KotlinNativeBinaryTestRun> @Inject constructor(
project: Project,
konanTarget: KonanTarget
) : KotlinNativeTarget(project, konanTarget), KotlinTargetWithTests<NativeBinaryTestRunSource, KotlinNativeBinaryTestRun> {
) : KotlinNativeTarget(project, konanTarget), KotlinTargetWithTests<NativeBinaryTestRunSource, T> {
override lateinit var testRuns: NamedDomainObjectContainer<KotlinNativeBinaryTestRun>
override lateinit var testRuns: NamedDomainObjectContainer<T>
internal set
}
@@ -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<T : KotlinNativeTarget>(
}
/**
* 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<T : KotlinNativeTarget>(
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<KotlinNativeTargetWithTests>(kotlinPluginVersion),
KotlinTargetWithTestsConfigurator<KotlinNativeBinaryTestRun, KotlinNativeTargetWithTests> {
abstract class KotlinNativeTargetWithTestsConfigurator<TaskType : KotlinNativeTest, TestRunType : KotlinNativeBinaryTestRun>(
kotlinPluginVersion: String
) : KotlinNativeTargetConfigurator<KotlinNativeTargetWithTests<TestRunType>>(kotlinPluginVersion),
KotlinTargetWithTestsConfigurator<TestRunType, KotlinNativeTargetWithTests<TestRunType>> {
override val testRunClass: Class<KotlinNativeBinaryTestRun>
get() = KotlinNativeBinaryTestRun::class.java
abstract val testTaskClass: Class<TaskType>
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<TaskType>) {
with(testRun) {
val project = target.project
val testTaskOrProvider = project.registerTask<KotlinNativeTest>(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<KotlinNativeHostTest, KotlinNativeHostTestRun>(kotlinPluginVersion) {
override val testTaskClass: Class<KotlinNativeHostTest>
get() = KotlinNativeHostTest::class.java
override val testRunClass: Class<KotlinNativeHostTestRun>
get() = KotlinNativeHostTestRun::class.java
override fun isTestTaskEnabled(target: KotlinNativeTargetWithTests<*>): Boolean =
target.konanTarget.isCurrentHost
override fun createTestRun(
name: String,
target: KotlinNativeTargetWithTests<KotlinNativeHostTestRun>
): KotlinNativeHostTestRun =
DefaultHostTestRun(name, target).apply { configureTestRun(target, this) }
}
class KotlinNativeTargetWithSimulatorTestsConfigurator(kotlinPluginVersion: String) :
KotlinNativeTargetWithTestsConfigurator<KotlinNativeSimulatorTest, KotlinNativeSimulatorTestRun>(kotlinPluginVersion) {
override val testTaskClass: Class<KotlinNativeSimulatorTest>
get() = KotlinNativeSimulatorTest::class.java
override val testRunClass: Class<KotlinNativeSimulatorTestRun>
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>
): KotlinNativeSimulatorTestRun =
DefaultSimulatorTestRun(name, target).apply { configureTestRun(target, this) }
}
@@ -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<KotlinNativeTargetWithTests>(name, project, konanTarget, kotlinPluginVersion) {
open class KotlinNativeTargetWithHostTestsPreset(name: String, project: Project, konanTarget: KonanTarget, kotlinPluginVersion: String) :
AbstractKotlinNativeTargetPreset<KotlinNativeTargetWithTests<KotlinNativeHostTestRun>>(name, project, konanTarget, kotlinPluginVersion) {
override fun createTargetConfigurator(): KotlinTargetConfigurator<KotlinNativeTargetWithTests> =
KotlinNativeTargetWithTestsConfigurator(kotlinPluginVersion)
override fun createTargetConfigurator(): KotlinTargetConfigurator<KotlinNativeTargetWithTests<KotlinNativeHostTestRun>> =
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<KotlinNativeHostTestRun> =
project.objects.newInstance(KotlinNativeTargetWithTests::class.java, project, konanTarget)
as KotlinNativeTargetWithTests<KotlinNativeHostTestRun>
}
open class KotlinNativeTargetWithSimulatorTestsPreset(name: String, project: Project, konanTarget: KonanTarget, kotlinPluginVersion: String) :
AbstractKotlinNativeTargetPreset<KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun>>(name, project, konanTarget, kotlinPluginVersion) {
override fun createTargetConfigurator(): KotlinTargetConfigurator<KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun>> =
KotlinNativeTargetWithSimulatorTestsConfigurator(kotlinPluginVersion)
@Suppress("UNCHECKED_CAST")
override fun instantiateTarget(name: String): KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun> =
project.objects.newInstance(KotlinNativeTargetWithTests::class.java, project, konanTarget)
as KotlinNativeTargetWithTests<KotlinNativeSimulatorTestRun>
}
internal val KonanTarget.isCurrentHost: Boolean
@@ -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<String> = setOf(),
val testNegativeGradleFilter: Set<String> = setOf(),
val userArgs: List<String> = emptyList()
) {
fun toList() = mutableListOf<String>().also {
protected abstract class TestCommand() {
abstract val executable: String
abstract fun cliArgs(
testLogger: String?,
checkExitCode: Boolean,
testGradleFilter: Set<String>,
testNegativeGradleFilter: Set<String>,
userArgs: List<String>
): List<String>
protected fun testArgs(
testLogger: String?,
checkExitCode: Boolean,
testGradleFilter: Set<String>,
testNegativeGradleFilter: Set<String>,
userArgs: List<String>
): List<String> = mutableListOf<String>().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<String>,
testNegativeGradleFilter: Set<String>,
userArgs: List<String>
): List<String> = 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<String>,
testNegativeGradleFilter: Set<String>,
userArgs: List<String>
): List<String> =
listOf("simctl", "spawn", "--standalone", deviceId, this@KotlinNativeSimulatorTest.executable.absolutePath, "--") +
testArgs(testLogger, checkExitCode, testGradleFilter, testNegativeGradleFilter, userArgs)
}
}