migrated FatFrameworkIT to junit 5 and gradle TestKit

This commit is contained in:
Dmitrii Krasnov
2023-04-25 08:24:49 +02:00
committed by Space Team
parent 1591869991
commit 617dddba70
4 changed files with 197 additions and 175 deletions
@@ -1,42 +1,38 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.gradle.embedProject
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.tasks.FrameworkLayout
import org.jetbrains.kotlin.gradle.transformProjectWithPluginsDsl
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.*
import org.jetbrains.kotlin.konan.target.HostManager
import org.junit.Assume
import org.junit.BeforeClass
import org.junit.Test
import java.io.File
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.condition.OS
import java.util.*
import kotlin.io.path.absolutePathString
import kotlin.io.path.appendText
import kotlin.test.assertTrue
class FatFrameworkIT : BaseGradleIT() {
@OsCondition(supportedOn = [OS.MAC], enabledOnCI = [OS.MAC])
@DisplayName("Tests for K/N builds with Fat Framework")
@NativeGradlePluginTests
class FatFrameworkIT : KGPBaseTest() {
override val defaultGradleVersion: GradleVersionRequired
get() = GradleVersionRequired.FOR_MPP_SUPPORT
@Test
fun smokeIos() {
transformProjectWithPluginsDsl(
"smoke",
directoryPrefix = "native-fat-framework"
).build("fat") {
@DisplayName("Fat framework for iOS smoke test")
@GradleTest
fun smokeIos(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
checkSmokeBuild(
archs = listOf("x64", "arm64", "arm32"),
targetPrefix = "ios",
expectedPlistPlatform = "iPhoneOS"
)
val binary = fileInWorkingDir("build/fat-framework/smoke.framework/smoke")
with(runProcess(listOf("file", binary.absolutePath), project.projectDir)) {
val binary = projectPath.resolve("build/fat-framework/smoke.framework/smoke").absolutePathString()
assertProcessRunResult(runProcess(listOf("file", binary), projectPath.toFile())) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex()))
assertTrue(output.contains("\\(for architecture armv7\\):\\s+Mach-O dynamically linked shared library arm_v7".toRegex()))
@@ -45,216 +41,198 @@ class FatFrameworkIT : BaseGradleIT() {
}
}
@Test
fun smokeWatchos() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
@DisplayName("Fat framework for watchOS smoke test")
@GradleTest
fun smokeWatchos(gradleVersion: GradleVersion) {
nativeProject(
"native-fat-framework/smoke",
gradleVersion,
// TODO(d.krasnov): return default statisticsForceValidation, when KT-58104 will be fixed
buildOptions = defaultBuildOptions.copy(statisticsForceValidation = false)
) {
buildGradleKts.modify {
it.checkedReplace("iosArm32()", "watchosArm32()")
.checkedReplace("iosArm64()", "watchosArm64(); watchosDeviceArm64()")
.checkedReplace("iosX64()", "watchosX64()")
}
build("fat") {
checkSmokeBuild(
archs = listOf("x64", "arm64", "arm32", "deviceArm64"),
targetPrefix = "watchos",
expectedPlistPlatform = "WatchOS"
)
val binary = fileInWorkingDir("build/fat-framework/smoke.framework/smoke")
with(runProcess(listOf("file", binary.absolutePath), projectDir)) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex()))
assertTrue(output.contains("\\(for architecture armv7k\\):\\s+Mach-O dynamically linked shared library arm_v7k".toRegex()))
assertTrue(output.contains("\\(for architecture arm64_32\\):\\s+Mach-O dynamically linked shared library arm64_32_v8".toRegex()))
assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex()))
}
checkSmokeBuild(
archs = listOf("x64", "arm64", "arm32", "deviceArm64"),
targetPrefix = "watchos",
expectedPlistPlatform = "WatchOS"
)
val binary = projectPath.resolve("build/fat-framework/smoke.framework/smoke").absolutePathString()
assertProcessRunResult(runProcess(listOf("file", binary), projectPath.toFile())) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex()))
assertTrue(output.contains("\\(for architecture armv7k\\):\\s+Mach-O dynamically linked shared library arm_v7k".toRegex()))
assertTrue(output.contains("\\(for architecture arm64_32\\):\\s+Mach-O dynamically linked shared library arm64_32_v8".toRegex()))
assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex()))
}
}
}
@Test
fun smokeMacos() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
@DisplayName("Fat framework with MacOS smoke test")
@GradleTest
fun smokeMacos(gradleVersion: GradleVersion) {
nativeProject(
"native-fat-framework/smoke",
gradleVersion
) {
buildGradleKts.modify {
it.checkedReplace("iosArm32()", "")
.checkedReplace("iosArm64()", "macosArm64()")
.checkedReplace("iosX64()", "macosX64()")
}
build("fat") {
checkSmokeBuild(
archs = listOf("x64", "arm64"),
targetPrefix = "macos",
expectedPlistPlatform = "MacOSX",
true
)
val binary = fileInWorkingDir("build/fat-framework/smoke.framework/Versions/A/smoke")
with(runProcess(listOf("file", binary.absolutePath), projectDir)) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex()))
assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex()))
}
checkSmokeBuild(
archs = listOf("x64", "arm64"),
targetPrefix = "macos",
expectedPlistPlatform = "MacOSX",
true
)
val binary = projectPath.resolve("build/fat-framework/smoke.framework/Versions/A/smoke").absolutePathString()
assertProcessRunResult(runProcess(listOf("file", binary), projectPath.toFile())) {
assertTrue(isSuccessful)
assertTrue(output.contains("\\(for architecture x86_64\\):\\s+Mach-O 64-bit dynamically linked shared library x86_64".toRegex()))
assertTrue(output.contains("\\(for architecture arm64\\):\\s+Mach-O 64-bit dynamically linked shared library arm64".toRegex()))
}
}
}
private fun CompiledProject.checkSmokeBuild(
private fun TestProject.checkSmokeBuild(
archs: List<String>,
targetPrefix: String,
expectedPlistPlatform: String,
isMacosFramework: Boolean = false
) {
assertSuccessful()
val linkTasks = archs.map {
":linkDebugFramework${targetPrefix.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }}${
it.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.getDefault()
) else it.toString()
}
}" }
build("fat") {
val linkTasks = archs.map {
":linkDebugFramework${targetPrefix.capitalize()}${it.capitalize()}"
}
assertTasksExecuted(linkTasks)
assertTasksExecuted(":fat")
assertTasksExecuted(linkTasks)
assertTasksExecuted(":fat")
val frameworkLayout = FrameworkLayout(fileInWorkingDir("build/fat-framework/smoke.framework"), isMacosFramework)
val frameworkLayout = FrameworkLayout(projectPath.resolve("build/fat-framework/smoke.framework").toFile(), isMacosFramework)
fun File.projectRelative() = relativeTo(File(workingDir, project.projectName)).path
assertFileExists(frameworkLayout.binary.toPath())
assertFileExists(frameworkLayout.header.toPath())
assertFileExists(frameworkLayout.dSYM.binary.toPath())
assertFileExists(frameworkLayout.binary.projectRelative())
assertFileExists(frameworkLayout.header.projectRelative())
assertFileExists(frameworkLayout.dSYM.binary.projectRelative())
assertFileContains(
frameworkLayout.header.toPath(),
"+ (int32_t)foo __attribute__((swift_name(\"foo()\")));"
)
val headerContent = frameworkLayout.header.readText()
assertTrue(
headerContent.contains("+ (int32_t)foo __attribute__((swift_name(\"foo()\")));"),
"Unexpected header content:\n$headerContent"
)
val plistContent = frameworkLayout.infoPlist
.readLines()
.joinToString(separator = "\n") { it.trim() }
assertTrue(
plistContent.contains(
assertFileContains(
frameworkLayout.infoPlist.toPath(),
"""
<key>CFBundleSupportedPlatforms</key>
<array>
<string>$expectedPlistPlatform</string>
</array>
""".trimIndent()
),
"Unexpected Info.plist content:\n$plistContent"
)
| <key>CFBundleSupportedPlatforms</key>
| <array>
| <string>$expectedPlistPlatform</string>
| </array>
""".trimMargin()
.replace(Regex(" {4}"), "\t")
)
}
}
@Test
fun testDuplicatedArchitecture() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
it + """
val anotherDeviceTarget = kotlin.iosArm64("another") {
binaries.framework("DEBUG")
}
fat.from(anotherDeviceTarget.binaries.getFramework("DEBUG"))
""".trimIndent()
@DisplayName("Fat framework with duplicated iOS Arm 64")
@GradleTest
fun testDuplicatedArchitecture(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
buildGradleKts.modify {
it +
//language=kotlin
"""
val anotherDeviceTarget = kotlin.iosArm64("another") {
binaries.framework("DEBUG")
}
fat.from(anotherDeviceTarget.binaries.getFramework("DEBUG"))
""".trimIndent()
}
build("fat") {
assertFailed()
assertContains("This fat framework already has a binary for architecture `arm64`")
buildAndFail("fat") {
assertOutputContains("This fat framework already has a binary for architecture `arm64`")
}
}
}
@Test
fun testIncorrectFamily() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
it + """
val macos = kotlin.macosX64 {
binaries.framework("DEBUG")
}
fat.from(macos.binaries.getFramework("DEBUG"))
""".trimIndent()
@DisplayName("Fat framework with incorrect 'osx' family")
@GradleTest
fun testIncorrectFamily(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
buildGradleKts.modify {
it +
//language=kotlin
"""
val macos = kotlin.macosX64 {
binaries.framework("DEBUG")
}
fat.from(macos.binaries.getFramework("DEBUG"))
""".trimIndent()
}
build("fat") {
assertFailed()
assertContains("Cannot add a binary with platform family 'osx' to the fat framework")
buildAndFail("fat") {
assertOutputContains("Cannot add a binary with platform family 'osx' to the fat framework")
}
}
}
@Test
fun testCustomName() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
it.addBeforeSubstring("baseName = \"custom\"\n","from(frameworksToMerge)")
@DisplayName("Fat framework with custom base name")
@GradleTest
fun testCustomName(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
buildGradleKts.modify {
it.addBeforeSubstring("baseName = \"custom\"\n", "from(frameworksToMerge)")
}
build("fat") {
val binary = fileInWorkingDir("build/fat-framework/custom.framework/custom")
with(runProcess(listOf("otool", "-D", binary.absolutePath), project.projectDir)) {
assertSuccessful()
val binary = projectPath.resolve("build/fat-framework/custom.framework/custom").absolutePathString()
assertProcessRunResult(runProcess(listOf("otool", "-D", binary), projectPath.toFile())) {
assertTrue(isSuccessful)
assertTrue { output.lines().any { it.contains("@rpath/custom.framework/custom") } }
}
}
}
}
@Test
fun testDifferentTypes() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
@DisplayName("Fat framework with different input types")
@GradleTest
fun testDifferentTypes(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
buildGradleKts.modify {
it.checkedReplace("iosArm32()", "iosArm32{binaries.framework {isStatic = true}}")
.checkedReplace("iosArm64()", "iosArm64{binaries.framework {isStatic = false}}")
.checkedReplace("iosX64()", "iosX64{binaries.framework {isStatic = false}}")
.addBeforeSubstring("//", "binaries.framework(listOf(DEBUG))")
}
build("fat") {
assertFailed()
assertContains("All input frameworks must be either static or dynamic")
buildAndFail("fat") {
assertOutputContains("All input frameworks must be either static or dynamic")
}
}
}
@Test
fun testAllStatic() {
with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
gradleBuildScript().modify {
@DisplayName("Fat framework with all static inputs")
@GradleTest
fun testAllStatic(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
buildGradleKts.modify {
it.checkedReplace("iosArm32()", "iosArm32{binaries.framework {isStatic = true}}")
.checkedReplace("iosArm64()", "iosArm64{binaries.framework {isStatic = true}}")
.checkedReplace("iosX64()", "iosX64{binaries.framework {isStatic = true}}")
.addBeforeSubstring("//", "binaries.framework(listOf(DEBUG))")
}
build("fat") {
assertSuccessful()
}
build("fat")
}
}
/**
* Test that the configurations exposing the frameworks don't interfere with variant-aware dependency resolution
*/
@Test
fun testDependencyResolution() = with(transformProjectWithPluginsDsl("smoke", directoryPrefix = "native-fat-framework")) {
setupWorkingDir()
val nestedProjectName = "nested"
embedProject(this, nestedProjectName)
gradleBuildScript(nestedProjectName).modify { it.replace(".version(\"$KOTLIN_VERSION\")", "") }
gradleBuildScript().appendText("dependencies { \"commonMainImplementation\"(project(\":$nestedProjectName\")) }")
testResolveAllConfigurations()
}
companion object {
@BeforeClass
@JvmStatic
fun assumeItsMac() {
Assume.assumeTrue(HostManager.hostIsMac)
@DisplayName("Test that the configurations exposing the frameworks don't interfere with variant-aware dependency resolution")
@GradleTest
fun testDependencyResolution(gradleVersion: GradleVersion) {
nativeProject("native-fat-framework/smoke", gradleVersion) {
val nestedProjectName = "nested"
includeOtherProjectAsSubmodule("smoke", "native-fat-framework", nestedProjectName, true)
buildGradleKts.appendText("dependencies { \"commonMainImplementation\"(project(\":$nestedProjectName\")) }")
testResolveAllConfigurations()
}
}
}
}
@@ -14,13 +14,15 @@ import org.jetbrains.kotlin.gradle.BaseGradleIT.Companion.acceptAndroidSdkLicens
import org.jetbrains.kotlin.gradle.model.ModelContainer
import org.jetbrains.kotlin.gradle.model.ModelFetcherBuildAction
import org.jetbrains.kotlin.gradle.native.disableKotlinNativeCaches
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.gradle.report.BuildReportType
import org.jetbrains.kotlin.gradle.util.modify
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.presetName
import org.jetbrains.kotlin.test.util.KtTestUtil
import java.io.File
import java.nio.file.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.*
import kotlin.test.assertTrue
@@ -316,7 +318,7 @@ class TestProject(
fun subProject(name: String) = GradleProject(name, projectPath.resolve(name))
fun addKotlinCompilerArgumentsPlugin() {
if (buildOptions.languageVersion != null || buildOptions.languageApiVersion != null ) {
if (buildOptions.languageVersion != null || buildOptions.languageApiVersion != null) {
projectPath.toFile().walkTopDown().forEach { file ->
when {
file.name.equals("build.gradle") -> file.modify {
@@ -336,17 +338,28 @@ class TestProject(
}
}
/**
* Includes another project as a submodule in the current project.
* @param otherProjectName The name of the other project to include as a submodule.
* @param pathPrefix An optional prefix to prepend to the submodule's path. Defaults to an empty string.
* @param newSubmoduleName An optional new name for the submodule. Defaults to the otherProjectName.
* @param isKts Whether to update a .kts settings file instead of a .gradle settings file. Defaults to false.
*/
fun includeOtherProjectAsSubmodule(
otherProjectName: String,
pathPrefix: String
pathPrefix: String,
newSubmoduleName: String = otherProjectName,
isKts: Boolean = false
) {
val otherProjectPath = "$pathPrefix/$otherProjectName".testProjectPath
otherProjectPath.copyRecursively(projectPath.resolve(otherProjectName))
otherProjectPath.copyRecursively(projectPath.resolve(newSubmoduleName))
settingsGradle.append(
val gradleSettingToUpdate = if (isKts) settingsGradleKts else settingsGradle
gradleSettingToUpdate.append(
"""
include ':$otherProjectName'
include(":$newSubmoduleName")
""".trimIndent()
)
}
@@ -60,3 +60,34 @@ fun createGradleCommand(wrapperDir: File, tailParameters: List<String>): List<St
}
val isWindows: Boolean = System.getProperty("os.name")!!.contains("Windows")
/**
* Asserts the result of running a process by calling a set of assertions on the result object.
* If any of the assertions fail, an [AssertionError] is thrown and the process output information is printed.
*
* @param result The result of running a process.
* @param assertions A lambda expression that performs a set of assertions on it.
*
* @throws AssertionError If any of the assertions fail.
*/
fun assertProcessRunResult(result: ProcessRunResult, assertions: ProcessRunResult.() -> Unit) {
try {
result.assertions()
} catch (e: AssertionError) {
println(
"""
|Process info:
|#######################
|$result
|#######################
|
|Process output:
|#######################
|${result.output}
|#######################
|
""".trimMargin()
)
throw e
}
}
@@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask
plugins {
id("org.jetbrains.kotlin.multiplatform").version("<pluginMarkerVersion>")
id("org.jetbrains.kotlin.multiplatform")
}
repositories {