migrated cocoapods test with xcodebuild to new test dsl (junit 5 and gradle test kit)

#KT-51553 In Progress
This commit is contained in:
Dmitrii Krasnov
2023-05-25 10:58:41 +02:00
committed by Space Team
parent b8785fbd90
commit 65aa5fc02e
7 changed files with 249 additions and 191 deletions
@@ -13,8 +13,6 @@ import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Compan
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_INSTALL_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SETUP_BUILD_TASK_NAME
import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.POD_SPEC_TASK_NAME
import org.jetbrains.kotlin.gradle.testbase.TestVersions
import org.jetbrains.kotlin.gradle.testbase.normalizeCocoapadsFrameworkName
import org.jetbrains.kotlin.gradle.util.createTempDir
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.util.runProcess
@@ -109,58 +107,6 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
@Test
fun testXcodeUseFrameworksSingle() = doTestXcode(
cocoapodsSingleKtPod,
ImportMode.FRAMEWORKS,
"ios-app", mapOf("kotlin-library" to null)
)
@Test
fun testXcodeUseFrameworksWithCustomFrameworkNameSingle() = doTestXcode(
cocoapodsSingleKtPod,
ImportMode.FRAMEWORKS,
"ios-app",
mapOf("kotlin-library" to "MultiplatformLibrary")
)
@Test
fun testXcodeUseModularHeadersSingle() = doTestXcode(
cocoapodsSingleKtPod,
ImportMode.MODULAR_HEADERS,
"ios-app",
mapOf("kotlin-library" to null)
)
@Test
fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle() = doTestXcode(
cocoapodsSingleKtPod,
ImportMode.MODULAR_HEADERS,
"ios-app",
mapOf("kotlin-library" to "MultiplatformLibrary")
)
@Test
fun testXcodeBuildsWithKotlinLibraryFromRootProject() {
val project = transformProjectWithPluginsDsl(templateProjectName, GradleVersionRequired.AtLeast(TestVersions.Gradle.G_7_6))
project.gradleBuildScript().appendToCocoapodsBlock("""
framework {
baseName = "kotlin-library"
}
name = "kotlin-library"
podfile = project.file("ios-app/Podfile")
""".trimIndent())
doTestXcode(
project,
ImportMode.FRAMEWORKS,
"ios-app",
mapOf("" to null),
)
}
@Test
fun testPodImportSingle() {
val project = getProjectByName(cocoapodsSingleKtPod)
@@ -194,38 +140,6 @@ class CocoaPodsIT : BaseGradleIT() {
project.testSynthetic(":second-library:podImport")
}
@Test
fun testXcodeUseFrameworksMultiple() = doTestXcode(
cocoapodsMultipleKtPods,
ImportMode.FRAMEWORKS,
null,
mapOf("kotlin-library" to null, "second-library" to null)
)
@Test
fun testXcodeUseFrameworksWithCustomFrameworkNameMultiple() = doTestXcode(
cocoapodsMultipleKtPods,
ImportMode.FRAMEWORKS,
null,
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary")
)
@Test
fun testXcodeUseModularHeadersMultiple() = doTestXcode(
cocoapodsMultipleKtPods,
ImportMode.MODULAR_HEADERS,
null,
mapOf("kotlin-library" to null, "second-library" to null)
)
@Test
fun testXcodeUseModularHeadersWithCustomFrameworkNameMultiple() = doTestXcode(
cocoapodsMultipleKtPods,
ImportMode.MODULAR_HEADERS,
null,
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary")
)
@Test
fun testSpecReposImport() {
val podName = "example"
@@ -1512,19 +1426,6 @@ class CocoaPodsIT : BaseGradleIT() {
}
}
private fun Project.useCustomFrameworkName(subproject: String, frameworkName: String, iosAppLocation: String? = null) {
// Change the name at the Gradle side.
gradleBuildScript(subproject).appendToFrameworkBlock("baseName = \"$frameworkName\"")
// Change swift sources import if needed.
if (iosAppLocation != null) {
val iosAppDir = projectDir.resolve(iosAppLocation)
iosAppDir.resolve("ios-app/ViewController.swift").modify {
it.replace("import ${subproject.normalizeCocoapadsFrameworkName}", "import $frameworkName")
}
}
}
private enum class ImportMode(val directive: String) {
FRAMEWORKS("use_frameworks!"),
MODULAR_HEADERS("use_modular_headers!")
@@ -1565,90 +1466,6 @@ class CocoaPodsIT : BaseGradleIT() {
CommandResult(process.exitValue(), stdOut, stdErr).block()
}
private fun doTestXcode(
projectName: String,
mode: ImportMode,
iosAppLocation: String?,
subprojectsToFrameworkNamesMap: Map<String, String?>,
) {
val gradleProject = transformProjectWithPluginsDsl(projectName, gradleVersion)
doTestXcode(
gradleProject = gradleProject,
mode = mode,
iosAppLocation = iosAppLocation,
subprojectsToFrameworkNamesMap = subprojectsToFrameworkNamesMap,
)
}
private fun doTestXcode(
gradleProject: Project,
mode: ImportMode,
iosAppLocation: String?,
subprojectsToFrameworkNamesMap: Map<String, String?>,
arch: String = "x86_64",
) {
gradleProject.projectDir.resolve("gradle.properties")
.takeIf(File::exists)
?.let {
it.appendLine("kotlin_version=${defaultBuildOptions().kotlinVersion}")
it.appendLine("test_fixes_version=${defaultBuildOptions().kotlinVersion}")
}
with(gradleProject) {
setupWorkingDir()
for ((subproject, frameworkName) in subprojectsToFrameworkNamesMap) {
val taskPrefix = if (subproject.isNotEmpty()) ":$subproject" else ""
// Add property with custom framework name
frameworkName?.let {
useCustomFrameworkName(subproject, it, iosAppLocation)
}
// Generate podspec.
build("$taskPrefix:podspec", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
assertSuccessful()
}
iosAppLocation?.also {
// Set import mode for Podfile.
preparePodfile(it, mode)
// Install pods.
build("$taskPrefix:podInstall", "-Pkotlin.native.cocoapods.generate.wrapper=true") {
assertSuccessful()
}
projectDir.resolve(it).apply {
// Run Xcode build.
runCommand(
this, "xcodebuild",
"-sdk", "iphonesimulator",
"-configuration", "Release",
"-workspace", "$name.xcworkspace",
"-scheme", name,
"-arch", arch,
inheritIO = true // Xcode doesn't finish the process if the PIPE redirect is used.
) {
assertEquals(
0, exitCode, """
|Exit code mismatch for `xcodebuild`.
|stdout:
|$stdOut
|
|stderr:
|$stdErr
""".trimMargin()
)
}
}
}
}
}
}
private fun Project.preparePodfile(iosAppLocation: String, mode: ImportMode) {
val iosAppDir = projectDir.resolve(iosAppLocation)
@@ -13,7 +13,7 @@ import kotlin.io.path.readText
import kotlin.test.assertEquals
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("K/N tests cocoapods")
@DisplayName("K/N tests with cocoapods' podspec")
@NativeGradlePluginTests
class CocoaPodsPodspecIT : KGPBaseTest() {
@@ -0,0 +1,235 @@
/*
* 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.
*/
package org.jetbrains.kotlin.gradle.native
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.assertProcessRunResult
import org.jetbrains.kotlin.gradle.util.replaceText
import org.jetbrains.kotlin.gradle.util.runProcess
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.name
import kotlin.test.assertEquals
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("K/N tests cocoapods with xcodebuild")
@NativeGradlePluginTests
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_0)
@OptIn(EnvironmentalVariablesOverride::class)
class CocoaPodsXcodeIT : KGPBaseTest() {
private val podfileImportDirectivePlaceholder = "<import_mode_directive>"
private val cocoapodsSingleKtPod = "native-cocoapods-single"
private val cocoapodsMultipleKtPods = "native-cocoapods-multiple"
private val templateProjectName = "native-cocoapods-template"
private val environmentVariables = EnvironmentalVariables(
mapOf(
// CocoaPods 1.11 requires UTF-8 locale being set, more details: https://github.com/CocoaPods/CocoaPods/issues/10939
"LC_ALL" to "en_US.UTF-8"
)
)
@DisplayName("Checks xcodebuild for ios-app with a single framework")
@GradleTest
fun testXcodeUseFrameworksSingle(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsSingleKtPod,
gradleVersion,
ImportMode.FRAMEWORKS,
"ios-app",
mapOf("kotlin-library" to null)
)
@DisplayName("Checks xcodebuild for ios-app with a single framework with custom name")
@GradleTest
fun testXcodeUseFrameworksWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsSingleKtPod,
gradleVersion,
ImportMode.FRAMEWORKS,
"ios-app",
mapOf("kotlin-library" to "MultiplatformLibrary")
)
@DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework")
@GradleTest
fun testXcodeUseModularHeadersSingle(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsSingleKtPod,
gradleVersion,
ImportMode.MODULAR_HEADERS,
"ios-app",
mapOf("kotlin-library" to null)
)
@DisplayName("Checks xcodebuild for ios-app using modular headers with a single framework with custom name")
@GradleTest
fun testXcodeUseModularHeadersWithCustomFrameworkNameSingle(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsSingleKtPod,
gradleVersion,
ImportMode.MODULAR_HEADERS,
"ios-app",
mapOf("kotlin-library" to "MultiplatformLibrary")
)
@DisplayName("Checks xcodebuild for ios-app with kotlin library from root project")
@GradleTest
fun testXcodeBuildsWithKotlinLibraryFromRootProject(gradleVersion: GradleVersion) {
nativeProject(templateProjectName, gradleVersion, environmentVariables = environmentVariables) {
buildGradleKts.addCocoapodsBlock(
"""
framework {
baseName = "kotlin-library"
}
name = "kotlin-library"
podfile = project.file("ios-app/Podfile")
""".trimIndent()
)
doTestXcode(
ImportMode.FRAMEWORKS,
"ios-app",
mapOf("" to null),
)
}
}
@DisplayName("Checks xcodebuild for two libraries with framework")
@GradleTest
fun testXcodeUseFrameworksMultiple(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsMultipleKtPods,
gradleVersion,
ImportMode.FRAMEWORKS,
null,
mapOf("kotlin-library" to null, "second-library" to null)
)
@DisplayName("Checks xcodebuild for two libraries with framework and custom names")
@GradleTest
fun testXcodeUseFrameworksWithCustomFrameworkNameMultiple(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsMultipleKtPods,
gradleVersion,
ImportMode.FRAMEWORKS,
null,
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary")
)
@DisplayName("Checks xcodebuild for two libraries with modular headers")
@GradleTest
fun testXcodeUseModularHeadersMultiple(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsMultipleKtPods,
gradleVersion,
ImportMode.MODULAR_HEADERS,
null,
mapOf("kotlin-library" to null, "second-library" to null)
)
@DisplayName("Checks xcodebuild for two libraries with modular headers and custom names")
@GradleTest
fun testXcodeUseModularHeadersWithCustomFrameworkNameMultiple(gradleVersion: GradleVersion) = doTestXcode(
cocoapodsMultipleKtPods,
gradleVersion,
ImportMode.MODULAR_HEADERS,
null,
mapOf("kotlin-library" to "FirstMultiplatformLibrary", "second-library" to "SecondMultiplatformLibrary")
)
private enum class ImportMode(val directive: String) {
FRAMEWORKS("use_frameworks!"),
MODULAR_HEADERS("use_modular_headers!")
}
private fun TestProject.preparePodfile(iosAppLocation: String, mode: ImportMode) {
val iosAppDir = projectPath.resolve(iosAppLocation)
// Set import mode for Podfile.
iosAppDir.resolve("Podfile")
.takeIf { it.exists() }
?.replaceText(podfileImportDirectivePlaceholder, mode.directive)
}
private fun doTestXcode(
projectName: String,
gradleVersion: GradleVersion,
mode: ImportMode,
iosAppLocation: String?,
subprojectsToFrameworkNamesMap: Map<String, String?>,
) {
nativeProject(projectName, gradleVersion, environmentVariables = environmentVariables) {
doTestXcode(
mode = mode,
iosAppLocation = iosAppLocation,
subprojectsToFrameworkNamesMap = subprojectsToFrameworkNamesMap,
)
}
}
private fun TestProject.doTestXcode(
mode: ImportMode,
iosAppLocation: String?,
subprojectsToFrameworkNamesMap: Map<String, String?>,
arch: String = "x86_64",
) {
gradleProperties
.takeIf(Path::exists)
?.let {
it.append("kotlin_version=${defaultBuildOptions.kotlinVersion}")
it.append("test_fixes_version=${defaultBuildOptions.kotlinVersion}")
}
for ((subproject, frameworkName) in subprojectsToFrameworkNamesMap) {
val taskPrefix = if (subproject.isNotEmpty()) ":$subproject" else ""
// Add property with custom framework name
frameworkName?.let {
useCustomCocoapodsFrameworkName(subproject, it, iosAppLocation)
}
val buildOptions = defaultBuildOptions.copy(
nativeOptions = defaultBuildOptions.nativeOptions.copy(
cocoapodsGenerateWrapper = true
)
)
// Generate podspec.
build("$taskPrefix:podspec", buildOptions = buildOptions)
iosAppLocation?.also {
// Set import mode for Podfile.
preparePodfile(it, mode)
// Install pods.
build("$taskPrefix:podInstall", buildOptions = buildOptions)
projectPath.resolve(it).apply {
// Run Xcode build.
val xcodebuildResult = runProcess(
cmd = listOf(
"xcodebuild",
"-sdk", "iphonesimulator",
"-configuration", "Release",
"-workspace", "$name.xcworkspace",
"-scheme", name,
"-arch", arch
),
environmentVariables = environmentVariables.environmentalVariables,
workingDir = this.toFile(),
)
assertProcessRunResult(xcodebuildResult) {
assertEquals(0, exitCode, "Exit code mismatch for `xcodebuild`.")
}
}
}
}
}
}
@@ -34,9 +34,9 @@ fun TestProject.useCustomCocoapodsFrameworkName(
}
}
private fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin"))
fun Path.addCocoapodsBlock(str: String) = addKotlinBlock(str.wrapIntoBlock("cocoapods"))
private fun Path.addCocoapodsBlock(str: String) = addKotlinBlock(str.wrapIntoBlock("cocoapods"))
private fun Path.addKotlinBlock(str: String) = appendLine(str.wrapIntoBlock("kotlin"))
private fun Path.addFrameworkBlock(str: String) = addCocoapodsBlock(str.wrapIntoBlock("framework"))
@@ -131,7 +131,6 @@ fun KGPBaseTest.nativeProject(
/**
* Trigger test project build with given [buildArguments] and assert build is successful.
*/
@OptIn(EnvironmentalVariablesOverride::class)
fun TestProject.build(
vararg buildArguments: String,
forceOutput: Boolean = this.forceOutput,
@@ -13,7 +13,8 @@ class ProcessRunResult(
private val cmd: List<String>,
private val workingDir: File,
val exitCode: Int,
val output: String
val output: String,
val stdErr: String,
) {
val isSuccessful: Boolean
get() = exitCode == 0
@@ -47,9 +48,10 @@ fun runProcess(
}
sb.append(it).append(SYSTEM_LINE_SEPARATOR)
}
val stdErr = process.errorStream.bufferedReader().use { it.readText() }
val exitCode = process.waitFor()
return ProcessRunResult(cmd, workingDir, exitCode, sb.toString())
return ProcessRunResult(cmd, workingDir, exitCode, sb.toString(), stdErr)
}
fun createGradleCommand(wrapperDir: File, tailParameters: List<String>): List<String> {
@@ -86,6 +88,11 @@ fun assertProcessRunResult(result: ProcessRunResult, assertions: ProcessRunResul
|${result.output}
|#######################
|
|Process error output:
|#######################
|${result.stdErr}
|#######################
|
""".trimMargin()
)
throw e
@@ -1,6 +1,6 @@
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.native.cocoapods").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.multiplatform")
id("org.jetbrains.kotlin.native.cocoapods")
}
group = "com.example"