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 94fe5785a8e..614419ff2bd 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,20 +5,12 @@ 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.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 java.util.UUID @DisplayName("tests for the K/N XCode simulator test infrastructure") @NativeGradlePluginTests @@ -27,18 +19,20 @@ class NativeXcodeSimulatorTestsIT : KGPBaseTest() { @DisplayName("A user-friendly error message is produced when the standalone mode is disabled and no simulator has booted") @GradleTest fun checkNoSimulatorErrorMessage(gradleVersion: GradleVersion) { - val unbootedSimulator = createSimulator() - project("native-test-ios-https-request", gradleVersion) { - buildGradleKts.append( - """ + XCTestHelpers().use { + val unbootedSimulator = it.createSimulator() + project("native-test-ios-https-request", gradleVersion) { + buildGradleKts.append( + """ tasks.withType { device.set("${unbootedSimulator.udid}") standalone.set(false) } """.trimIndent() - ) - buildAndFail("check") { - assertOutputContains("The problem can be that you have not booted the required device or have configured the task to a different simulator. Please check the task output and its device configuration.") + ) + buildAndFail("check") { + assertOutputContains("The problem can be that you have not booted the required device or have configured the task to a different simulator. Please check the task output and its device configuration.") + } } } } @@ -57,82 +51,28 @@ class NativeXcodeSimulatorTestsIT : KGPBaseTest() { @DisplayName("iOS simulator test with an https request doesn't fail with the standalone mode disabled") @GradleTest fun checkSimulatorTestDoesNotFailInNonStandaloneMode(gradleVersion: GradleVersion) { - project("native-test-ios-https-request", gradleVersion) { - val simulator = createSimulator() - simulator.boot() - buildGradleKts.append( - """ + XCTestHelpers().use { + val simulator = it.createSimulator().apply { + boot() + } + + project("native-test-ios-https-request", gradleVersion) { + buildGradleKts.append( + """ tasks.withType { device.set("${simulator.udid}") standalone.set(false) } """.trimIndent() - ) - build("check") { - when (HostManager.host) { - KonanTarget.MACOS_ARM64 -> assertTasksExecuted(":iosSimulatorArm64Test") - KonanTarget.MACOS_X64 -> assertTasksExecuted(":iosX64Test") - else -> error("Unexpected host") + ) + 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 removeSimulatorsCreatedForTests() { - simulators().devices.values.toList().flatMap { it }.filter { - it.name == testSimulatorName - }.forEach { - processOutput( - listOf("/usr/bin/xcrun", "simctl", "delete", it.udid) - ) - } - } - - private fun simulators(): Simulators { - return Json { - ignoreUnknownKeys = true - }.decodeFromString( - processOutput( - listOf("/usr/bin/xcrun", "simctl", "list", "devices", "-j") - ) - ) - } - - 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/testbase/xcodeTestHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/xcodeTestHelpers.kt index 5155135ca36..354f2d34de4 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/xcodeTestHelpers.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/xcodeTestHelpers.kt @@ -12,12 +12,19 @@ import java.nio.file.Path import kotlin.io.path.exists import kotlin.test.assertEquals +internal enum class XcodeBuildMode { + NORMAL, + TEST +} -fun TestProject.buildXcodeProject( +internal fun TestProject.buildXcodeProject( xcodeproj: Path, scheme: String = "iosApp", configuration: String = "Debug", - destination: String = "generic/platform=iOS Simulator" + destination: String = "generic/platform=iOS Simulator", + sdk: String = "iphonesimulator", + buildMode: XcodeBuildMode = XcodeBuildMode.NORMAL, + extraArguments: Map = emptyMap(), ) { prepareForXcodebuild() @@ -25,11 +32,14 @@ fun TestProject.buildXcodeProject( xcodeproj = xcodeproj, scheme = scheme, configuration = configuration, + sdk = sdk, destination = destination, + buildMode = buildMode, + extraArguments = extraArguments ) } -fun TestProject.xcodebuild( +internal fun TestProject.xcodebuild( workingDir: Path = projectPath, xcodeproj: Path? = null, workspace: Path? = null, @@ -38,6 +48,8 @@ fun TestProject.xcodebuild( sdk: String? = null, arch: String? = null, destination: String? = null, + buildMode: XcodeBuildMode = XcodeBuildMode.NORMAL, + extraArguments: Map = emptyMap(), derivedDataPath: Path? = projectPath.resolve("xcodeDerivedData"), ) { xcodebuild( @@ -49,6 +61,12 @@ fun TestProject.xcodebuild( } } + infix fun String.eq(value: Any?) { + if (value != null) { + add("${this}=$value") + } + } + add("xcodebuild") "-project" set xcodeproj "-workspace" set workspace @@ -58,12 +76,20 @@ fun TestProject.xcodebuild( "-arch" set arch "-destination" set destination "-derivedDataPath" set derivedDataPath + + extraArguments.forEach { + it.key eq it.value + } + + if (buildMode == XcodeBuildMode.TEST) { + add("test") + } }, workingDir, ) } -fun TestProject.prepareForXcodebuild() { +internal fun TestProject.prepareForXcodebuild() { overrideMavenLocalIfNeeded() gradleProperties diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/xctestHelpers.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/xctestHelpers.kt new file mode 100644 index 00000000000..2b786e7b171 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/xctestHelpers.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.testbase + +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import org.jetbrains.kotlin.gradle.util.assertProcessRunResult +import org.jetbrains.kotlin.gradle.util.runProcess +import java.io.Closeable +import java.io.File +import java.util.* + +internal class XCTestHelpers : Closeable { + @Serializable + data class Device(val name: String, val udid: String) + + @Serializable + 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" + + fun createSimulator(): Device { + return Device( + testSimulatorName, + processOutput( + listOf("/usr/bin/xcrun", "simctl", "create", testSimulatorName, deviceIdentifier) + ).dropLast(1) + ) + } + + private val json = Json { + ignoreUnknownKeys = true + } + + private fun simulators(): Simulators { + return json.decodeFromString( + processOutput( + listOf("/usr/bin/xcrun", "simctl", "list", "devices", "-j") + ) + ) + } + + override fun close() { + simulators().devices.values.toList().flatten().filter { + it.name == testSimulatorName + }.forEach { + processOutput( + listOf("/usr/bin/xcrun", "simctl", "delete", it.udid) + ) + } + } +} + +internal fun XCTestHelpers.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