From f20cc10660ae4ddae14355391c0b3ff3988f0bc4 Mon Sep 17 00:00:00 2001 From: Timofey Solonin Date: Thu, 24 Aug 2023 12:11:18 +0200 Subject: [PATCH] [MPP] Add intel macOS support and create simulators for the test run ^KT-61439 --- .../build.gradle.kts | 2 + .../native/NativeXcodeSimulatorTestsIT.kt | 104 ++++++++++++------ .../kotlin/gradle/util/gradleRunningUtils.kt | 5 +- .../build.gradle.kts | 10 +- .../kotlin/tests.kt | 0 5 files changed, 81 insertions(+), 40 deletions(-) rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/src/{iosSimulatorArm64Test => commonTest}/kotlin/tests.kt (100%) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts index cf6a74902c4..7d435a257b2 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.pill.PillExtension plugins { kotlin("jvm") + kotlin("plugin.serialization") id("jps-compatible") } @@ -85,6 +86,7 @@ dependencies { testImplementation(kotlinStdlib("jdk8")) testImplementation(project(":kotlin-parcelize-compiler")) testImplementation(commonDependency("org.jetbrains.intellij.deps", "trove4j")) + testImplementation(commonDependency("org.jetbrains.kotlinx", "kotlinx-serialization-json")) testImplementation(libs.ktor.client.cio) testImplementation(libs.ktor.client.mock) testImplementation(libs.ktor.server.core) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt index 10a738547e6..94fe5785a8e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/native/NativeXcodeSimulatorTestsIT.kt @@ -5,36 +5,34 @@ package org.jetbrains.kotlin.gradle.native +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.util.assertProcessRunResult import org.jetbrains.kotlin.gradle.util.runProcess -import org.jetbrains.kotlin.gradle.utils.XcodeUtils +import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.KonanTarget import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.condition.OS import java.io.File -import kotlin.test.assertEquals +import java.util.UUID @DisplayName("tests for the K/N XCode simulator test infrastructure") @NativeGradlePluginTests @OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC]) class NativeXcodeSimulatorTestsIT : KGPBaseTest() { - private val defaultIosSimulator by lazy { - XcodeUtils.getDefaultTestDeviceId(KonanTarget.IOS_SIMULATOR_ARM64) ?: error("No simulator found for iOS ARM64") - } - @DisplayName("A user-friendly error message is produced when the standalone mode is disabled and no simulator has booted") @GradleTest fun checkNoSimulatorErrorMessage(gradleVersion: GradleVersion) { - shutDownSimulators() // based on the tests order there no simulator should be booted, but anyway to be sure - // Note the test still may fail if you have booted the required simulator manually before running the test - // We don't shut down such simulators in the tests + val unbootedSimulator = createSimulator() project("native-test-ios-https-request", gradleVersion) { buildGradleKts.append( """ tasks.withType { - device.set("$defaultIosSimulator") + device.set("${unbootedSimulator.udid}") standalone.set(false) } """.trimIndent() @@ -60,47 +58,81 @@ class NativeXcodeSimulatorTestsIT : KGPBaseTest() { @GradleTest fun checkSimulatorTestDoesNotFailInNonStandaloneMode(gradleVersion: GradleVersion) { project("native-test-ios-https-request", gradleVersion) { - bootXcodeSimulator(defaultIosSimulator) + val simulator = createSimulator() + simulator.boot() buildGradleKts.append( """ tasks.withType { - device.set("$defaultIosSimulator") + device.set("${simulator.udid}") standalone.set(false) } """.trimIndent() ) - build("check") + build("check") { + when (HostManager.host) { + KonanTarget.MACOS_ARM64 -> assertTasksExecuted(":iosSimulatorArm64Test") + KonanTarget.MACOS_X64 -> assertTasksExecuted(":iosX64Test") + else -> error("Unexpected host") + } + } } } + @Serializable + private data class Device(val name: String, val udid: String) + @Serializable + private data class Simulators(val devices: Map>) + + private val deviceIdentifier = "com.apple.CoreSimulator.SimDeviceType.iPhone-12-Pro-Max" + private val uuid = UUID.randomUUID() + private val testSimulatorName = "NativeXcodeSimulatorTestsIT_${uuid}_simulator" + @AfterAll - fun shutDownSimulators() { - synchronized(bootedXcodeSimulators) { - for (device in bootedXcodeSimulators) { - val command = listOf("/usr/bin/xcrun", "simctl", "shutdown", device) - val processResult = runProcess(command, File(".")) - assertEquals(0, processResult.exitCode) - bootedXcodeSimulators.remove(device) - } - bootedXcodeSimulators.clear() + fun removeSimulatorsCreatedForTests() { + simulators().devices.values.toList().flatMap { it }.filter { + it.name == testSimulatorName + }.forEach { + processOutput( + listOf("/usr/bin/xcrun", "simctl", "delete", it.udid) + ) } } - private val bootedXcodeSimulators: MutableSet = HashSet() + private fun simulators(): Simulators { + return Json { + ignoreUnknownKeys = true + }.decodeFromString( + processOutput( + listOf("/usr/bin/xcrun", "simctl", "list", "devices", "-j") + ) + ) + } - private fun bootXcodeSimulator(device: String) { - synchronized(bootedXcodeSimulators) { - if (device !in bootedXcodeSimulators) { - val command = listOf("/usr/bin/xcrun", "simctl", "boot", device) - val processResult = runProcess(command, File(".")) - assert(processResult.exitCode == 0 || "current state: Booted" in processResult.output) { - "Failed to boot a simulator $device and it wasn't started before, exit code = ${processResult.exitCode}" - } - if (processResult.exitCode == 0) { - // if the simulator was booted not by us, we should not shut it down - bootedXcodeSimulators.add(device) - } - } + private fun createSimulator(): Device { + return Device( + testSimulatorName, + processOutput( + listOf("/usr/bin/xcrun", "simctl", "create", testSimulatorName, deviceIdentifier) + ).dropLast(1) + ) + } + + private fun Device.boot() { + processOutput( + listOf("/usr/bin/xcrun", "simctl", "bootstatus", udid, "-bd") + ) + } + + private fun processOutput(arguments: List): String { + val result = runProcess( + arguments, File("."), + redirectErrorStream = false, + ) + assertProcessRunResult( + result + ) { + assert(isSuccessful) } + return result.output } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt index c39b3e71573..e5ff1445776 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/util/gradleRunningUtils.kt @@ -31,13 +31,14 @@ fun runProcess( cmd: List, workingDir: File, environmentVariables: Map = mapOf(), - options: BaseGradleIT.BuildOptions? = null + options: BaseGradleIT.BuildOptions? = null, + redirectErrorStream: Boolean = true, ): ProcessRunResult { val builder = ProcessBuilder(cmd) builder.environment().putAll(environmentVariables) builder.directory(workingDir) // redirectErrorStream merges stdout and stderr, so it can be get from process.inputStream - builder.redirectErrorStream(true) + builder.redirectErrorStream(redirectErrorStream) val process = builder.start() // important to read inputStream, otherwise the process may hang on some systems diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/build.gradle.kts index 123a9945ec3..e67fedb3022 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/build.gradle.kts @@ -8,12 +8,18 @@ repositories { } kotlin { - iosSimulatorArm64() { + iosSimulatorArm64 { + compilations.all() { + compilerOptions.options.apply { + optIn.add("kotlinx.cinterop.ExperimentalForeignApi") + } + } + } + iosX64 { compilations.all() { compilerOptions.options.apply { optIn.add("kotlinx.cinterop.ExperimentalForeignApi") } } } - } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/src/iosSimulatorArm64Test/kotlin/tests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/src/commonTest/kotlin/tests.kt similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/src/iosSimulatorArm64Test/kotlin/tests.kt rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/native-test-ios-https-request/src/commonTest/kotlin/tests.kt