[K/N][Tests] Migrate framework and objcexport tests

^KT-61259
This commit is contained in:
Vladimir Sukharev
2024-01-22 20:12:14 +01:00
committed by Space Team
parent f00a145dd7
commit 025771460c
136 changed files with 698 additions and 416 deletions
@@ -34,7 +34,13 @@ abstract class AbstractNativeObjCExportTest : AbstractNativeSimpleTest() {
.map { testPathFull.resolve(it) }
ktSources.forEach { muteTestIfNecessary(it) }
val testCase: TestCase = generateObjCFrameworkTestCase(testPathFull, ktSources)
val testCase: TestCase = generateObjCFrameworkTestCase(
TestKind.STANDALONE,
TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT),
testPathFull.name,
ktSources,
TestCompilerArgs(listOf("-Xexport-kdoc"))
)
val objCFramework: ObjCFramework = testCase.toObjCFramework().assertSuccess().resultingArtifact
val mainHeaderContents = objCFramework.mainHeader.readText()
@@ -58,22 +64,4 @@ abstract class AbstractNativeObjCExportTest : AbstractNativeSimpleTest() {
private fun TestCase.toObjCFramework(): TestCompilationResult<out ObjCFramework> {
return testCompilationFactory.testCaseToObjCFrameworkCompilation(this, testRunSettings).result
}
private fun generateObjCFrameworkTestCase(testPathFull: File, sources: List<File>): TestCase {
val moduleName: String = testPathFull.name
val module = TestModule.Exclusive(DEFAULT_MODULE_NAME, emptySet(), emptySet(), emptySet())
sources.forEach { module.files += TestFile.createCommitted(it, module) }
return TestCase(
id = TestCaseId.Named(moduleName),
kind = TestKind.STANDALONE,
modules = setOf(module),
freeCompilerArgs = TestCompilerArgs(listOf("-Xexport-kdoc")),
nominalPackageName = PackageName(moduleName),
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
extras = TestCase.WithTestRunnerExtras(TestRunnerType.DEFAULT)
).apply {
initialize(null, null)
}
}
}
@@ -0,0 +1,490 @@
/*
* 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.konan.test.blackbox
import com.intellij.testFramework.TestDataPath
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.test.blackbox.support.*
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.*
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.test.blackbox.support.group.FirPipeline
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestExecutable
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestRunCheck
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestRunChecks
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.*
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import java.io.File
import java.io.FileWriter
import kotlin.test.assertTrue
@TestDataPath("\$PROJECT_ROOT")
class ClassicFrameworkTest : FrameworkTestBase()
@FirPipeline
@Tag("frontend-fir")
@TestDataPath("\$PROJECT_ROOT")
class FirFrameworkTest : FrameworkTestBase()
abstract class FrameworkTestBase : AbstractNativeSimpleTest() {
private val testSuiteDir = File("native/native.tests/testData/framework")
private val extras = TestCase.NoTestRunnerExtras("There's no entrypoint in Swift program")
private val testCompilationFactory = TestCompilationFactory()
@Test
fun testValuesGenerics() {
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val testName = "values_generics"
val testCase = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, "ValuesGenerics",
listOf(
testSuiteDir.resolve(testName).resolve("$testName.kt"),
testSuiteDir.resolve("objcexport/values.kt"),
),
freeCompilerArgs = TestCompilerArgs(listOf("-opt-in=kotlinx.cinterop.ExperimentalForeignApi"))
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase, testRunSettings).result.assertSuccess()
compileAndRunSwift(testName, testCase)
}
@Test
fun testStdlib() {
val testName = "stdlib"
val testCase = generateObjCFramework(testName)
compileAndRunSwift(testName, testCase)
}
@Test
fun testMultipleFrameworks() {
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val testName = "multiple"
val testDir = testSuiteDir.resolve(testName)
val framework1Dir = testDir.resolve("framework1")
val sharedDir = testDir.resolve("shared")
val moduleName1st = "First"
val testCase1 = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, moduleName1st,
listOf(
framework1Dir.resolve("first.kt"),
framework1Dir.resolve("test.kt"),
sharedDir.resolve("shared.kt"),
),
TestCompilerArgs("-Xbinary=bundleId=$moduleName1st")
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase1, testRunSettings).result.assertSuccess()
val framework2Dir = testDir.resolve("framework2")
val moduleName2nd = "Second"
val testCase2 = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, moduleName2nd,
listOf(
framework2Dir.resolve("second.kt"),
framework2Dir.resolve("test.kt"),
sharedDir.resolve("shared.kt"),
),
TestCompilerArgs("-Xbinary=bundleId=$moduleName2nd")
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase2, testRunSettings).result.assertSuccess()
compileAndRunSwift(testName, testCase1) // testCase1 provides testRun parameters. testCase2 should have the same.
}
@Test
fun testMultipleFrameworksStatic() {
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val testName = "multiple"
val testDir = testSuiteDir.resolve(testName)
val framework1Dir = testDir.resolve("framework1")
val sharedDir = testDir.resolve("shared")
val freeCompilerArgs = listOf("-Xstatic-framework", "-Xpre-link-caches=enable")
val moduleName1st = "First"
val defaultChecks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout)
val checks = if (testRunSettings.get<CacheMode>() != CacheMode.WithoutCache) {
// KT-34262, KT-65289: one assert in testIsolation4() fails with caches:
// try assertFalse(obj2 is First.KotlinBase)
defaultChecks.copy(exitCodeCheck = TestRunCheck.ExitCode.Expected(134))
} else defaultChecks
val testCase1 = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, moduleName1st,
listOf(
framework1Dir.resolve("first.kt"),
framework1Dir.resolve("test.kt"),
sharedDir.resolve("shared.kt"),
),
freeCompilerArgs = TestCompilerArgs(freeCompilerArgs + "-Xbinary=bundleId=$moduleName1st"),
checks = checks,
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase1, testRunSettings).result.assertSuccess()
val framework2Dir = testDir.resolve("framework2")
val moduleName2nd = "Second"
val testCase2 = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, moduleName2nd,
listOf(
framework2Dir.resolve("second.kt"),
framework2Dir.resolve("test.kt"),
sharedDir.resolve("shared.kt"),
), freeCompilerArgs = TestCompilerArgs(freeCompilerArgs + "-Xbinary=bundleId=$moduleName2nd")
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase2, testRunSettings).result.assertSuccess()
compileAndRunSwift(testName, testCase1)
}
@Test
fun testGH3343() {
val testName = "gh3343"
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val freeCInteropArgs = TestCompilerArgs(emptyList(), cinteropArgs = listOf("-header", "$testName.h"))
val interopLibrary = compileCInterop(testName, freeCInteropArgs)
val testCase = generateObjCFramework(testName, emptyList(), setOf(TestModule.Given(interopLibrary.klibFile)))
compileAndRunSwift(testName, testCase)
}
@Test
fun testKT42397() {
val testName = "kt42397"
val testCase = generateObjCFramework(testName)
compileAndRunSwift(testName, testCase)
}
@Test
fun testKT43517() {
val testName = "kt43517"
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val interopLibrary = compileCInterop(testName)
val testCase = generateObjCFramework(testName, emptyList(), setOf(TestModule.Given(interopLibrary.klibFile)))
compileAndRunSwift(testName, testCase)
}
@Test
fun testStacktrace() {
val testName = "stacktrace"
Assumptions.assumeFalse(testRunSettings.get<OptimizationMode>() == OptimizationMode.OPT)
val testCase = generateObjCFramework(testName, listOf("-g"))
compileAndRunSwift(testName, testCase)
}
@Test
fun testStacktraceBridges() {
val testName = "stacktraceBridges"
Assumptions.assumeFalse(testRunSettings.get<OptimizationMode>() == OptimizationMode.OPT)
val testCase = generateObjCFramework(testName, listOf("-g"))
compileAndRunSwift(testName, testCase)
}
@Test
fun testStacktraceByLibbacktrace() {
Assumptions.assumeFalse(testRunSettings.get<OptimizationMode>() == OptimizationMode.OPT)
val testName = "stacktraceByLibbacktrace"
val testCase = generateObjCFramework(testName, listOf("-g", "-Xbinary=sourceInfoType=libbacktrace"))
compileAndRunSwift(testName, testCase)
}
@Test
fun testAbstractInstantiation() {
val testName = "abstractInstantiation"
val checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout).copy(
exitCodeCheck = TestRunCheck.ExitCode.Expected(134)
)
val testCase = generateObjCFramework(testName, checks = checks)
compileAndRunSwift(testName, testCase)
}
@Test
fun testFrameworkBundleId() {
Assumptions.assumeTrue(testRunSettings.get<KotlinNativeTargets>().testTarget.family == Family.OSX)
val testName = "bundle_id"
val testDir = testSuiteDir.resolve(testName)
val freeCompilerArgs = TestCompilerArgs(
listOf(
"-Xbinary=bundleId=$testName",
"-Xbinary=bundleVersion=FooBundleVersion",
"-Xbinary=bundleShortVersionString=FooBundleShortVersionString"
)
)
val testCase = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, testName,
listOf(
testDir.resolve("main.kt"),
testDir.resolve("lib.kt"),
),
freeCompilerArgs
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase, testRunSettings).result.assertSuccess()
val buildDir = testRunSettings.get<Binaries>().testBinariesDir
val infoPlist = buildDir.resolve("$testName.framework/Resources/Info.plist")
val infoPlistContents = infoPlist.readText()
listOf(
"<key>CFBundleIdentifier</key>\\s*<string>$testName</string>",
"<key>CFBundleShortVersionString</key>\\s*<string>FooBundleShortVersionString</string>",
"<key>CFBundleVersion</key>\\s*<string>FooBundleVersion</string>",
).forEach {
assertTrue(infoPlistContents.contains(Regex(it))) {
"${infoPlist.absolutePath} does not contain pattern `$it`:\n$infoPlistContents"
}
}
}
@Test
fun testForwardDeclarations() {
val testName = "forwardDeclarations"
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val interopLibrary = compileCInterop(testName)
val testCase = generateObjCFramework(testName, emptyList(), setOf(TestModule.Given(interopLibrary.klibFile)))
compileAndRunSwift(testName, testCase)
}
private fun compileCInterop(testName: String, freeCInteropArgs: TestCompilerArgs = TestCompilerArgs.EMPTY) =
cinteropToLibrary(
targets = targets,
defFile = testSuiteDir.resolve(testName).resolve("$testName.def"),
outputDir = buildDir,
freeCompilerArgs = freeCInteropArgs
).assertSuccess().resultingArtifact
@Test
fun testUseFoundationModule() {
val testName = "use_foundation_module"
generateObjCFramework(testName)
val modulemap = buildDir.resolve("$testName.framework/Modules/module.modulemap")
val modulemapContents = modulemap.readText()
val expectedPattern = "use Foundation"
assertTrue(modulemapContents.contains(expectedPattern)) {
"${modulemap.absolutePath} must contain `$expectedPattern`:\n$modulemapContents"
}
}
@Test
fun testKT56233() {
val testName = "kt56233"
// test must make huge amount of repetitions to make sure there's no race conditions, so bigger timeout is needed. Double is not enough
val checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout * 10)
val testCase = generateObjCFramework(testName, checks = checks)
val swiftExtraOpts = if (testRunSettings.get<GCScheduler>() != GCScheduler.AGGRESSIVE) listOf() else
listOf("-D", "AGGRESSIVE_GC")
compileAndRunSwift(testName, testCase, swiftExtraOpts)
}
@Test
fun testKT57791() {
val testName = "kt57791"
val testCase = generateObjCFramework(testName)
compileAndRunSwift(testName, testCase)
}
@Test
fun testPermanentObjects() {
val testName = "permanentObjects"
Assumptions.assumeFalse(testRunSettings.get<GCType>() == GCType.NOOP) { "Test requires GC to actually happen" }
val testCase = generateObjCFramework(testName, listOf("-opt-in=kotlin.native.internal.InternalForKotlinNative"))
compileAndRunSwift(testName, testCase)
}
@Test
fun objCExportTest() {
objCExportTestImpl("", emptyList(), emptyList(), false)
}
@Test
fun objCExportTestNoGenerics() {
objCExportTestImpl("NoGenerics", listOf("-Xno-objc-generics"),
listOf("-D", "NO_GENERICS"), false)
}
@Test
fun objCExportTestLegacySuspendUnit() {
objCExportTestImpl("LegacySuspendUnit", listOf("-Xbinary=unitSuspendFunctionObjCExport=legacy"),
listOf("-D", "LEGACY_SUSPEND_UNIT_FUNCTION_EXPORT"), false)
}
@Test
fun objCExportTestNoSwiftMemberNameMangling() {
objCExportTestImpl("NoSwiftMemberNameMangling", listOf("-Xbinary=objcExportDisableSwiftMemberNameMangling=true"),
listOf("-D", "DISABLE_MEMBER_NAME_MANGLING"), false)
}
@Test
fun objCExportTestNoInterfaceMemberNameMangling() {
objCExportTestImpl("NoInterfaceMemberNameMangling", listOf("-Xbinary=objcExportIgnoreInterfaceMethodCollisions=true"),
listOf("-D", "DISABLE_INTERFACE_METHOD_NAME_MANGLING"), false)
}
@Test
fun objCExportTestStatic() {
objCExportTestImpl("Static", listOf("-Xbinary=objcExportSuspendFunctionLaunchThreadRestriction=none"),
listOf("-D", "ALLOW_SUSPEND_ANY_THREAD"), true)
}
private fun objCExportTestImpl(
suffix: String,
frameworkOpts: List<String>,
swiftOpts: List<String>,
isStaticFramework: Boolean,
) {
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
// Compile a couple of KLIBs
val library = compileToLibrary(
testSuiteDir.resolve("objcexport/library"),
buildDir,
TestCompilerArgs("-Xshort-module-name=MyLibrary", "-module-name", "org.jetbrains.kotlin.native.test-library"),
emptyList(),
)
val noEnumEntries = compileToLibrary(
testSuiteDir.resolve("objcexport/noEnumEntries"),
buildDir,
TestCompilerArgs(
"-Xshort-module-name=NoEnumEntriesLibrary", "-XXLanguage:-EnumEntries",
"-module-name", "org.jetbrains.kotlin.native.test-no-enum-entries-library",
),
emptyList(),
)
// Convert KT sources into ObjC framework using two KLIbs
val objcExportTestSuiteDir = testSuiteDir.resolve("objcexport")
val ktFiles = objcExportTestSuiteDir.listFiles { file: File -> file.name.endsWith(".kt") }
assertTrue(ktFiles != null && ktFiles.isNotEmpty()) {
"Some .kt files must be in test folder $objcExportTestSuiteDir"
}
val frameworkName = "Kt"
val testCase = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR, extras, "Kt",
ktFiles!!.toList(),
freeCompilerArgs = TestCompilerArgs(
frameworkOpts + listOfNotNull(
"-Xstatic-framework".takeIf { isStaticFramework },
"-opt-in=kotlinx.cinterop.ExperimentalForeignApi",
"-Xexport-kdoc",
"-Xbinary=bundleId=foo.bar",
"-module-name", frameworkName,
)
),
givenDependencies = setOf(TestModule.Given(library.klibFile), TestModule.Given(noEnumEntries.klibFile)),
// test must make huge amount of repetitions to make sure there's no race conditions, so bigger timeout is needed.
checks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout * 2),
)
testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase, testRunSettings, listOf(noEnumEntries)).result.assertSuccess()
// compile Swift sources using generated ObjC framework
val swiftFiles = objcExportTestSuiteDir.listFiles { file: File -> file.name.endsWith(".swift") }
assertTrue(swiftFiles != null && swiftFiles.isNotEmpty()) {
"Some .swift files must be in test folder $objcExportTestSuiteDir"
}
val swiftExtraOpts = buildList {
addAll(swiftOpts)
if (testRunSettings.get<GCScheduler>() == GCScheduler.AGGRESSIVE) {
add("-D")
add("AGGRESSIVE_GC")
}
if (testRunSettings.get<GCType>() == GCType.NOOP) {
add("-D")
add("NOOP_GC")
}
}
val successExecutable = compileSwift(swiftFiles!!.toList(), swiftExtraOpts)
val testExecutable = TestExecutable(
successExecutable.resultingArtifact,
successExecutable.loggedData,
listOf(TestName("objCExportTest$suffix"))
)
runExecutableAndVerify(testCase, testExecutable)
// check Info.plist for expected bundle identifier
val plistFName = if (targets.testTarget.family == Family.OSX) "Resources/Info.plist" else "Info.plist"
val infoPlist = buildDir.resolve("$frameworkName.framework/$plistFName")
val infoPlistContents = infoPlist.readText()
assertTrue(infoPlistContents.contains(Regex("<key>CFBundleIdentifier</key>\\s*<string>foo.bar</string>"))) {
"${infoPlist.absolutePath} does not contain expected pattern with `foo.bar`:\n$infoPlistContents"
}
}
private fun generateObjCFramework(
name: String,
testCompilerArgs: List<String> = emptyList(),
givenDependencies: Set<TestModule.Given> = emptySet(),
checks: TestRunChecks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
): TestCase {
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val testCase = generateObjCFrameworkTestCase(
TestKind.STANDALONE_NO_TR,
extras,
name.replaceFirstChar { it.uppercase() },
listOf(testSuiteDir.resolve(name).resolve("$name.kt")),
TestCompilerArgs(testCompilerArgs + listOf("-Xbinary=bundleId=$name")),
givenDependencies,
checks = checks,
)
val objCFrameworkCompilation = testCompilationFactory.testCaseToObjCFrameworkCompilation(testCase, testRunSettings)
val success = objCFrameworkCompilation.result.assertSuccess()
codesign(success.resultingArtifact.frameworkDir.absolutePath)
return testCase
}
private fun compileAndRunSwift(testName: String, testCase: TestCase, swiftExtraOpts: List<String> = emptyList()) {
val success = compileSwift(testName, swiftExtraOpts)
val testExecutable = TestExecutable(
success.resultingArtifact,
success.loggedData,
listOf(TestName(testName))
)
runExecutableAndVerify(testCase, testExecutable)
}
private fun compileSwift(
name: String,
swiftExtraOpts: List<String>,
): TestCompilationResult.Success<out TestCompilationArtifact.Executable> =
compileSwift(listOf(testSuiteDir.resolve(name).resolve("$name.swift")), swiftExtraOpts)
private fun compileSwift(
testSources: List<File>,
swiftExtraOpts: List<String>,
): TestCompilationResult.Success<out TestCompilationArtifact.Executable> {
// create a test provider and get main entry point
val provider = buildDir.resolve("provider.swift")
FileWriter(provider).use { writer ->
val providers = testSources
.map { file ->
file.name.toString().removeSuffix(".swift").replaceFirstChar { it.uppercase() }
}
.map { "${it}Tests" }
writer.write(
"""
|// THIS IS AUTOGENERATED FILE
|// This method is invoked by the main routine to get a list of tests
|func registerProviders() {
| ${providers.joinToString("\n ") { "$it()" }}
|}
""".trimMargin()
)
}
return SwiftCompilation(
testRunSettings,
testSources + listOf(
provider,
testSuiteDir.resolve("main.swift")
),
TestCompilationArtifact.Executable(buildDir.resolve("swiftTestExecutable")),
swiftExtraOpts,
).result.assertSuccess()
}
}
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Binaries
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.PipelineType
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Timeouts
import org.jetbrains.kotlin.konan.test.blackbox.support.util.DEFAULT_MODULE_NAME
import org.jetbrains.kotlin.konan.test.blackbox.support.util.LAUNCHER_MODULE_NAME
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.junit.jupiter.api.Assumptions
@@ -270,6 +271,31 @@ internal fun AbstractNativeSimpleTest.generateCInteropTestCaseFromSingleDefFile(
}
}
internal fun AbstractNativeSimpleTest.generateObjCFrameworkTestCase(
kind: TestKind,
extras: TestCase.Extras,
moduleName: String,
sources: List<File>,
freeCompilerArgs: TestCompilerArgs = TestCompilerArgs.EMPTY,
givenDependencies: Set<TestModule.Given>? = null,
checks: TestRunChecks = TestRunChecks.Default(testRunSettings.get<Timeouts>().executionTimeout),
): TestCase {
val module = TestModule.Exclusive(DEFAULT_MODULE_NAME, emptySet(), emptySet(), emptySet())
sources.forEach { module.files += TestFile.createCommitted(it, module) }
return TestCase(
id = TestCaseId.Named(moduleName),
kind = kind,
modules = setOf(module),
freeCompilerArgs = freeCompilerArgs,
nominalPackageName = PackageName(moduleName),
checks = checks,
extras = extras,
).apply {
initialize(givenDependencies, null)
}
}
private fun AbstractNativeSimpleTest.compileToLibrary(
testCase: TestCase,
outputDir: File,
@@ -93,17 +93,28 @@ internal abstract class LoggedData {
abstract class CompilerCall : LoggedData()
class CInteropParameters(
private val extraArgs: Array<String>,
private val args: List<String>,
private val defFile: File,
private val environment: JVMEnvironment = JVMEnvironment() // Capture environment.
) : LoggedData() {
override fun computeText() = buildString {
appendArguments("CINTEROP INVOCATION EXTRA ARGUMENTS:", extraArgs.toList())
appendLine("TEST DEF FILE: ${defFile.canonicalPath}")
appendArguments("CINTEROP INVOCATION ARGUMENTS:", args)
appendLine()
appendLine(environment)
}
}
class SwiftCParameters(
private val args: List<String>,
private val sources: List<File>,
private val environment: JVMEnvironment = JVMEnvironment() // Capture environment.
) : LoggedData() {
override fun computeText() = buildString {
appendList("TEST SWIFT SOURCES:", sources.map { it.absolutePath })
appendArguments("SWIFTC INVOCATION ARGUMENTS:", args)
appendLine()
appendLine("TEST DEF FILE: ${defFile.canonicalPath}")
appendLine(environment)
}
}
@@ -10,10 +10,17 @@ import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.compilerRunner.processCompilerOutput
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.konan.target.AppleConfigurables
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.test.blackbox.support.NativeTestSupport
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.native.executors.RunProcessException
import org.jetbrains.kotlin.native.executors.runProcess
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Settings
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.configurables
import org.jetbrains.kotlin.native.executors.*
import org.jetbrains.kotlin.test.KtAssert.fail
import org.junit.jupiter.api.Assumptions
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
@@ -119,12 +126,9 @@ internal fun callCompilerWithoutOutputInterceptor(
@OptIn(ExperimentalTime::class)
internal fun invokeCInterop(
kotlinNativeClassLoader: ClassLoader,
targets: KotlinNativeTargets,
inputDef: File,
outputLib: File,
extraArgs: Array<String>
args: Array<String>
): CompilationToolCallResult {
val args = arrayOf("-o", outputLib.canonicalPath, "-def", inputDef.canonicalPath, "-no-default-libs", "-target", targets.testTarget.name)
val buildDir = KonanFile("${outputLib.canonicalPath}-build")
val generatedDir = KonanFile(buildDir, "kotlin")
val nativesDir = KonanFile(buildDir, "natives")
@@ -138,7 +142,7 @@ internal fun invokeCInterop(
entryPoint.invoke(
interopClass.getDeclaredConstructor().newInstance(),
"native",
args + extraArgs,
args,
false,
generatedDir.absolutePath, nativesDir.absolutePath, manifest.path, cstubsName // args for InternalInteropOptions()
)
@@ -170,6 +174,36 @@ internal fun invokeCInterop(
}
}
internal fun invokeSwiftC(
settings: Settings,
args: List<String>,
): CompilationToolCallResult {
val targets = settings.get<KotlinNativeTargets>()
Assumptions.assumeTrue(targets.testTarget.family.isAppleFamily)
val configs = settings.configurables as AppleConfigurables
val swiftCompiler = configs.absoluteTargetToolchain + "/bin/swiftc"
val result = try {
runProcess(swiftCompiler, *args.toTypedArray()) {
timeout = Duration.parse("5m")
environment["DYLD_FALLBACK_FRAMEWORK_PATH"] =
File(configs.absoluteTargetToolchain).resolveSibling("ExtraFrameworks").absolutePath
}
} catch (rpe: RunProcessException) {
return CompilationToolCallResult(
exitCode = ExitCode.COMPILATION_ERROR,
toolOutput = "ARGS: $args\nSTDOUT: ${rpe.stdout}\nSTDERR: ${rpe.stderr}",
toolOutputHasErrors = true,
rpe.executionTime
)
}
return CompilationToolCallResult(
exitCode = ExitCode.OK,
toolOutput = "ARGS: $args\nSTDOUT: ${result.stdout}\nSTDERR=${result.stderr}}",
toolOutputHasErrors = false,
result.executionTime
)
}
internal fun codesign(path: String) {
val executableAbsolutePath = "/usr/bin/codesign"
val args = arrayOf("--verbose", "-s", "-", path)
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.konan.test.blackbox.support.compilation
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.konan.properties.resolvablePropertyList
import org.jetbrains.kotlin.konan.target.AppleConfigurables
import org.jetbrains.kotlin.konan.target.withOSVersion
import org.jetbrains.kotlin.konan.test.blackbox.support.*
import org.jetbrains.kotlin.konan.test.blackbox.support.TestCase.*
import org.jetbrains.kotlin.konan.test.blackbox.support.TestModule.Companion.allDependsOn
@@ -76,7 +78,16 @@ internal abstract class BasicCompilation<A : TestCompilationArtifact>(
protected abstract fun applyDependencies(argsBuilder: ArgsBuilder)
private fun ArgsBuilder.applyFreeArgs() {
add(freeCompilerArgs.compilerArgs)
when (this@BasicCompilation) {
is LibraryCompilation, is StaticCacheCompilation ->
// In TWO_STAGE_MULTI_MODULE mode, source->framework compilation is split to
// - `source -> klib` stage,
// - `klib -> static_cache stage(if caches enabled) and
// - klib+static_cache -> framework
// option `-Xstatic-framework` can be supplied only to `-p framework` stage, so must be filtered out for other stages
add(freeCompilerArgs.compilerArgs.filter { it != "-Xstatic-framework" })
else -> add(freeCompilerArgs.compilerArgs)
}
}
private fun ArgsBuilder.applyCompilerPlugins() {
@@ -159,6 +170,7 @@ internal abstract class SourceBasedCompilation<A : TestCompilationArtifact>(
private val gcScheduler: GCScheduler,
private val allocator: Allocator,
private val pipelineType: PipelineType,
private val cacheMode: CacheMode,
freeCompilerArgs: TestCompilerArgs,
compilerPlugins: CompilerPlugins,
override val sourceModules: Collection<TestModule>,
@@ -190,6 +202,9 @@ internal abstract class SourceBasedCompilation<A : TestCompilationArtifact>(
add("-friend-modules", friends.joinToString(File.pathSeparator) { friend -> friend.path })
}
add(dependencies.includedLibraries) { include -> "-Xinclude=${include.path}" }
// static caches of distlibs should be used, like in old testinfra. Relates to KT-65289 for ObjC frameworks
cacheMode.staticCacheForDistributionLibrariesRootDir
?.let { cacheRootDir -> add("-Xcache-directory=$cacheRootDir") }
}
private fun applyK2MPPArgs(argsBuilder: ArgsBuilder): Unit = with(argsBuilder) {
@@ -222,6 +237,7 @@ internal class LibraryCompilation(
gcScheduler = settings.get(),
allocator = settings.get(),
pipelineType = settings.get(),
cacheMode = settings.get(),
freeCompilerArgs = freeCompilerArgs,
compilerPlugins = settings.get(),
sourceModules = sourceModules,
@@ -244,7 +260,8 @@ internal class ObjCFrameworkCompilation(
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
dependencies: Iterable<TestCompilationDependency<*>>,
expectedArtifact: ObjCFramework
expectedArtifact: ObjCFramework,
val exportedLibraries: Iterable<KLIB> = emptyList(),
) : SourceBasedCompilation<ObjCFramework>(
targets = settings.get(),
home = settings.get(),
@@ -257,6 +274,7 @@ internal class ObjCFrameworkCompilation(
gcScheduler = settings.get(),
allocator = settings.get(),
pipelineType = settings.getStageDependentPipelineType(),
cacheMode = settings.get(),
freeCompilerArgs = freeCompilerArgs,
compilerPlugins = settings.get(),
sourceModules = sourceModules,
@@ -272,6 +290,14 @@ internal class ObjCFrameworkCompilation(
)
super.applySpecificArgs(argsBuilder)
}
override fun applyDependencies(argsBuilder: ArgsBuilder) = with(argsBuilder) {
exportedLibraries.forEach {
assertTrue(it in dependencies.libraries)
add("-Xexport-library=${it.path}")
}
super.applyDependencies(argsBuilder)
}
}
internal class BinaryLibraryCompilation(
@@ -292,6 +318,7 @@ internal class BinaryLibraryCompilation(
gcScheduler = settings.get(),
allocator = settings.get(),
pipelineType = settings.getStageDependentPipelineType(),
cacheMode = settings.get(),
freeCompilerArgs = freeCompilerArgs,
compilerPlugins = settings.get(),
sourceModules = sourceModules,
@@ -328,7 +355,14 @@ internal class CInteropCompilation(
) : TestCompilation<KLIB>() {
override val result: TestCompilationResult<out KLIB> by lazy {
val extraArgsArray = buildList {
val args = buildList {
add("-def")
add(defFile.canonicalPath)
add("-target")
add(targets.testTarget.name)
add("-o")
add(expectedArtifact.klibFile.canonicalPath)
add("-no-default-libs")
dependencies.forEach {
add("-l")
add(it.artifact.path)
@@ -344,16 +378,14 @@ internal class CInteropCompilation(
add("-DNS_FORMAT_ARGUMENT(A)=")
add("-compiler-option")
add("-I${defFile.parentFile}")
}.toTypedArray()
}
val loggedCInteropParameters = LoggedData.CInteropParameters(extraArgs = extraArgsArray, defFile = defFile)
val loggedCInteropParameters = LoggedData.CInteropParameters(args, defFile)
val (loggedCall: LoggedData, immediateResult: TestCompilationResult.ImmediateResult<out KLIB>) = try {
val (exitCode, cinteropOutput, cinteropOutputHasErrors, duration) = invokeCInterop(
classLoader.classLoader,
targets,
defFile,
expectedArtifact.klibFile,
extraArgsArray
args.toTypedArray()
)
val loggedInteropCall = LoggedData.CompilationToolCall(
@@ -383,6 +415,56 @@ internal class CInteropCompilation(
}
}
internal class SwiftCompilation(
testRunSettings: Settings,
sources: List<File>,
expectedArtifact: Executable,
swiftExtraOpts: List<String>,
) : TestCompilation<Executable>() {
override val result: TestCompilationResult<out Executable> by lazy {
val buildDir = testRunSettings.get<Binaries>().testBinariesDir
val configs = testRunSettings.configurables as AppleConfigurables
val swiftTarget = configs.targetTriple.withOSVersion(configs.osVersionMin).toString()
val args = swiftExtraOpts + sources.map { it.absolutePath } + listOf(
"-sdk", configs.absoluteTargetSysRoot, "-target", swiftTarget,
"-g", "-o", expectedArtifact.executableFile.absolutePath,
"-Xlinker", "-rpath", "-Xlinker", "@executable_path/Frameworks",
"-Xlinker", "-rpath", "-Xlinker", buildDir.absolutePath,
"-F", buildDir.absolutePath,
"-Xcc", "-Werror", // To fail compilation on warnings in framework header.
)
val loggedSwiftCParameters = LoggedData.SwiftCParameters(args, sources)
val (loggedCall: LoggedData, immediateResult: TestCompilationResult.ImmediateResult<out Executable>) = try {
val (exitCode, swiftcOutput, swiftcOutputHasErrors, duration) =
invokeSwiftC(testRunSettings, args)
val loggedSwiftCCall = LoggedData.CompilationToolCall(
toolName = "SWIFTC",
input = null,
parameters = loggedSwiftCParameters,
exitCode = exitCode,
toolOutput = swiftcOutput,
toolOutputHasErrors = swiftcOutputHasErrors,
duration = duration
)
val res = if (exitCode != ExitCode.OK || swiftcOutputHasErrors)
TestCompilationResult.CompilationToolFailure(loggedSwiftCCall)
else
TestCompilationResult.Success(expectedArtifact, loggedSwiftCCall)
loggedSwiftCCall to res
} catch (unexpectedThrowable: Throwable) {
val loggedFailure = LoggedData.CompilationToolCallUnexpectedFailure(loggedSwiftCParameters, unexpectedThrowable)
val res = TestCompilationResult.UnexpectedFailure(loggedFailure)
loggedFailure to res
}
expectedArtifact.logFile.writeText(loggedCall.toString())
immediateResult
}
}
internal class ExecutableCompilation(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
@@ -403,6 +485,7 @@ internal class ExecutableCompilation(
gcScheduler = settings.get(),
allocator = settings.get(),
pipelineType = settings.getStageDependentPipelineType(),
cacheMode = settings.get(),
freeCompilerArgs = freeCompilerArgs,
compilerPlugins = settings.get(),
sourceModules = sourceModules,
@@ -109,7 +109,11 @@ internal class TestCompilationFactory {
}
}
fun testCaseToObjCFrameworkCompilation(testCase: TestCase, settings: Settings): ObjCFrameworkCompilation {
fun testCaseToObjCFrameworkCompilation(
testCase: TestCase,
settings: Settings,
exportedLibraries: Iterable<KLIB> = emptyList(),
): ObjCFrameworkCompilation {
val cacheKey = ObjCFrameworkCacheKey(testCase.rootModules)
cachedObjCFrameworkCompilations[cacheKey]?.let { return it }
@@ -126,8 +130,9 @@ internal class TestCompilationFactory {
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
exportedLibraries = exportedLibraries,
expectedArtifact = ObjCFramework(
settings.artifactDirForPackageName(testCase.nominalPackageName),
settings.get<Binaries>().testBinariesDir,
testCase.nominalPackageName.compressedPackageName
)
)
@@ -184,7 +189,8 @@ internal class TestCompilationFactory {
}
TestMode.TWO_STAGE_MULTI_MODULE -> {
// Compile root modules to KLIB. Pass this KLIB as included dependency to executable compilation.
val klibCompilations = modulesToKlib(rootModules, freeCompilerArgs, produceStaticCache(), settings)
val klibCompilations =
modulesToKlib(rootModules, freeCompilerArgs, produceStaticCache(), settings)
Pair(
// Include just compiled KLIB as -Xinclude dependency.