[Native][tests] Initial support for CExport in the new test infra
Very trivial implementation of CExport test infrastructure based on work from LLDB tests and ObjCExport tests.
This commit is contained in:
committed by
Space Team
parent
13618fc03b
commit
1c24f994db
@@ -0,0 +1,7 @@
|
|||||||
|
#include "smoke0_api.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (foo() != 42) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class)
|
||||||
|
|
||||||
|
import kotlin.native.CName
|
||||||
|
|
||||||
|
@CName("foo")
|
||||||
|
fun foo(): Int {
|
||||||
|
return 42
|
||||||
|
}
|
||||||
+29
@@ -445,6 +445,35 @@ fun main() {
|
|||||||
model("standalone")
|
model("standalone")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// C Export
|
||||||
|
testGroup("native/native.tests/tests-gen", "native/native.tests/testData") {
|
||||||
|
testClass<AbstractNativeCExportDynamicTest>(
|
||||||
|
suiteTestClassName = "CExportTestDynamicGenerated"
|
||||||
|
) {
|
||||||
|
model("CExport", pattern = "^([^_](.+))$", recursive = false)
|
||||||
|
}
|
||||||
|
testClass<AbstractNativeCExportDynamicTest>(
|
||||||
|
suiteTestClassName = "FirCExportTestDynamicGenerated",
|
||||||
|
annotations = listOf(
|
||||||
|
*frontendFir()
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
model("CExport", pattern = "^([^_](.+))$", recursive = false)
|
||||||
|
}
|
||||||
|
testClass<AbstractNativeCExportStaticTest>(
|
||||||
|
suiteTestClassName = "CExportTestStaticGenerated"
|
||||||
|
) {
|
||||||
|
model("CExport", pattern = "^([^_](.+))$", recursive = false)
|
||||||
|
}
|
||||||
|
testClass<AbstractNativeCExportStaticTest>(
|
||||||
|
suiteTestClassName = "FirCExportTestStaticGenerated",
|
||||||
|
annotations = listOf(
|
||||||
|
*frontendFir()
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
model("CExport", pattern = "^([^_](.+))$", recursive = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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.TestDataFile
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.*
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.PackageName
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.TestCase
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.TestCaseId
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.TestCompilerArgs
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.TestFile
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.TestKind
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.TestModule
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationArtifact
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationFactory
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.compilation.TestCompilationResult.Companion.assertSuccess
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestExecutable
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.runner.TestRunChecks
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.Timeouts
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.settings.configurables
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.util.DEFAULT_MODULE_NAME
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.util.compileWithClang
|
||||||
|
import org.jetbrains.kotlin.konan.test.blackbox.support.util.getAbsoluteFile
|
||||||
|
import org.junit.jupiter.api.Tag
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Tag("cexport")
|
||||||
|
abstract class AbstractNativeCExportTest(
|
||||||
|
protected val libraryKind: BinaryLibraryKind,
|
||||||
|
) : AbstractNativeSimpleTest() {
|
||||||
|
|
||||||
|
enum class BinaryLibraryKind {
|
||||||
|
STATIC, DYNAMIC
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open val kindSpecificClangFlags: List<String> = emptyList()
|
||||||
|
|
||||||
|
private val testCompilationFactory = TestCompilationFactory()
|
||||||
|
|
||||||
|
protected fun runTest(@TestDataFile testDir: String) {
|
||||||
|
val testPathFull = getAbsoluteFile(testDir)
|
||||||
|
val ktSources = testPathFull.list()!!
|
||||||
|
.filter { it.endsWith(".kt") }
|
||||||
|
.map { testPathFull.resolve(it) }
|
||||||
|
ktSources.forEach { muteTestIfNecessary(it) }
|
||||||
|
|
||||||
|
val cSources = testPathFull.list()!!
|
||||||
|
.filter { it.endsWith(".c") }
|
||||||
|
.map { testPathFull.resolve(it) }
|
||||||
|
|
||||||
|
val testCase = generateCExportTestCase(testPathFull, ktSources)
|
||||||
|
val binaryLibrary = testCompilationFactory.testCaseToBinaryLibrary(
|
||||||
|
testCase,
|
||||||
|
testRunSettings,
|
||||||
|
kind = libraryKind.mapToArtifactKind(),
|
||||||
|
).result.assertSuccess().resultingArtifact
|
||||||
|
|
||||||
|
val clangExecutableName = "clangMain"
|
||||||
|
val executableFile = File(buildDir, clangExecutableName)
|
||||||
|
val includeDirectories = binaryLibrary.headerFile?.let { listOf(it.parentFile) } ?: emptyList()
|
||||||
|
val libraryName = binaryLibrary.libraryFile.nameWithoutExtension.substringAfter("lib")
|
||||||
|
compileWithClang(
|
||||||
|
sourceFiles = cSources,
|
||||||
|
includeDirectories = includeDirectories,
|
||||||
|
outputFile = executableFile,
|
||||||
|
libraryDirectories = listOf(binaryLibrary.libraryFile.parentFile),
|
||||||
|
libraries = listOf(libraryName),
|
||||||
|
additionalLinkerFlags = kindSpecificClangFlags,
|
||||||
|
)
|
||||||
|
|
||||||
|
val testExecutable = TestExecutable(
|
||||||
|
TestCompilationArtifact.Executable(executableFile),
|
||||||
|
loggedCompilationToolCall = LoggedData.NoopCompilerCall(buildDir),
|
||||||
|
testNames = listOf(TestName("TMP")),
|
||||||
|
)
|
||||||
|
|
||||||
|
runExecutableAndVerify(testCase, testExecutable)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateCExportTestCase(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_NO_TR,
|
||||||
|
modules = setOf(module),
|
||||||
|
freeCompilerArgs = TestCompilerArgs(listOf()),
|
||||||
|
nominalPackageName = PackageName(moduleName),
|
||||||
|
checks = TestRunChecks(
|
||||||
|
executionTimeoutCheck = TestRunCheck.ExecutionTimeout.ShouldNotExceed(testRunSettings.get<Timeouts>().executionTimeout),
|
||||||
|
exitCodeCheck = TestRunCheck.ExitCode.Expected(0),
|
||||||
|
outputDataFile = goldenData?.let { TestRunCheck.OutputDataFile(file = it) },
|
||||||
|
outputMatcher = null,
|
||||||
|
fileCheckMatcher = null,
|
||||||
|
),
|
||||||
|
extras = TestCase.NoTestRunnerExtras(entryPoint = "main")
|
||||||
|
).apply {
|
||||||
|
initialize(null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BinaryLibraryKind.mapToArtifactKind(): TestCompilationArtifact.BinaryLibrary.Kind = when (this) {
|
||||||
|
BinaryLibraryKind.STATIC -> TestCompilationArtifact.BinaryLibrary.Kind.STATIC
|
||||||
|
BinaryLibraryKind.DYNAMIC -> TestCompilationArtifact.BinaryLibrary.Kind.DYNAMIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractNativeCExportStaticTest() : AbstractNativeCExportTest(libraryKind = BinaryLibraryKind.STATIC) {
|
||||||
|
override val kindSpecificClangFlags: List<String>
|
||||||
|
get() = testRunSettings.configurables.linkerKonanFlags.flatMap { listOf("-Xlinker", it) }
|
||||||
|
}
|
||||||
|
abstract class AbstractNativeCExportDynamicTest() : AbstractNativeCExportTest(libraryKind = BinaryLibraryKind.DYNAMIC)
|
||||||
+39
@@ -271,6 +271,45 @@ internal class ObjCFrameworkCompilation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class BinaryLibraryCompilation(
|
||||||
|
settings: Settings,
|
||||||
|
freeCompilerArgs: TestCompilerArgs,
|
||||||
|
sourceModules: Collection<TestModule>,
|
||||||
|
dependencies: Iterable<TestCompilationDependency<*>>,
|
||||||
|
expectedArtifact: BinaryLibrary
|
||||||
|
) : SourceBasedCompilation<BinaryLibrary>(
|
||||||
|
targets = settings.get(),
|
||||||
|
home = settings.get(),
|
||||||
|
classLoader = settings.get(),
|
||||||
|
optimizationMode = settings.get(),
|
||||||
|
compilerOutputInterceptor = settings.get(),
|
||||||
|
threadStateChecker = settings.get(),
|
||||||
|
sanitizer = settings.get(),
|
||||||
|
gcType = settings.get(),
|
||||||
|
gcScheduler = settings.get(),
|
||||||
|
allocator = settings.get(),
|
||||||
|
pipelineType = settings.getStageDependentPipelineType(),
|
||||||
|
freeCompilerArgs = freeCompilerArgs,
|
||||||
|
compilerPlugins = settings.get(),
|
||||||
|
sourceModules = sourceModules,
|
||||||
|
dependencies = CategorizedDependencies(dependencies),
|
||||||
|
expectedArtifact = expectedArtifact
|
||||||
|
) {
|
||||||
|
override val binaryOptions get() = BinaryOptions.RuntimeAssertionsMode.defaultForTesting(optimizationMode, freeCompilerArgs.assertionsMode)
|
||||||
|
|
||||||
|
override fun applySpecificArgs(argsBuilder: ArgsBuilder) = with(argsBuilder) {
|
||||||
|
val libraryKind = when (expectedArtifact.kind) {
|
||||||
|
BinaryLibrary.Kind.STATIC -> "static"
|
||||||
|
BinaryLibrary.Kind.DYNAMIC -> "dynamic"
|
||||||
|
}
|
||||||
|
add(
|
||||||
|
"-produce", libraryKind,
|
||||||
|
"-output", expectedArtifact.libraryFile.absolutePath
|
||||||
|
)
|
||||||
|
super.applySpecificArgs(argsBuilder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal class GivenLibraryCompilation(givenArtifact: KLIB) : TestCompilation<KLIB>() {
|
internal class GivenLibraryCompilation(givenArtifact: KLIB) : TestCompilation<KLIB>() {
|
||||||
override val result = TestCompilationResult.Success(givenArtifact, LoggedData.NoopCompilerCall(givenArtifact.klibFile))
|
override val result = TestCompilationResult.Success(givenArtifact, LoggedData.NoopCompilerCall(givenArtifact.klibFile))
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -39,4 +39,22 @@ internal sealed interface TestCompilationArtifact {
|
|||||||
val headersDir: File get () = frameworkDir.resolve("Headers")
|
val headersDir: File get () = frameworkDir.resolve("Headers")
|
||||||
val mainHeader: File get() = headersDir.resolve("$frameworkName.h")
|
val mainHeader: File get() = headersDir.resolve("$frameworkName.h")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class BinaryLibrary(val libraryFile: File, val kind: Kind) : TestCompilationArtifact {
|
||||||
|
|
||||||
|
enum class Kind {
|
||||||
|
STATIC, DYNAMIC
|
||||||
|
}
|
||||||
|
|
||||||
|
override val logFile: File get() = libraryFile.resolveSibling("${libraryFile.name}.log")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Might not exist if the library was compiled without a header.
|
||||||
|
*/
|
||||||
|
val headerFile: File?
|
||||||
|
get() {
|
||||||
|
val expectedFile = libraryFile.resolveSibling("${libraryFile.nameWithoutExtension}_api.h")
|
||||||
|
return expectedFile.takeIf { it.exists() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
@@ -28,10 +28,12 @@ internal class TestCompilationFactory {
|
|||||||
private val cachedKlibCompilations = ThreadSafeCache<KlibCacheKey, KlibCompilations>()
|
private val cachedKlibCompilations = ThreadSafeCache<KlibCacheKey, KlibCompilations>()
|
||||||
private val cachedExecutableCompilations = ThreadSafeCache<ExecutableCacheKey, TestCompilation<Executable>>()
|
private val cachedExecutableCompilations = ThreadSafeCache<ExecutableCacheKey, TestCompilation<Executable>>()
|
||||||
private val cachedObjCFrameworkCompilations = ThreadSafeCache<ObjCFrameworkCacheKey, ObjCFrameworkCompilation>()
|
private val cachedObjCFrameworkCompilations = ThreadSafeCache<ObjCFrameworkCacheKey, ObjCFrameworkCompilation>()
|
||||||
|
private val cachedBinaryLibraryCompilations = ThreadSafeCache<BinaryLibraryCacheKey, BinaryLibraryCompilation>()
|
||||||
|
|
||||||
private data class KlibCacheKey(val sourceModules: Set<TestModule>, val freeCompilerArgs: TestCompilerArgs)
|
private data class KlibCacheKey(val sourceModules: Set<TestModule>, val freeCompilerArgs: TestCompilerArgs)
|
||||||
private data class ExecutableCacheKey(val sourceModules: Set<TestModule>)
|
private data class ExecutableCacheKey(val sourceModules: Set<TestModule>)
|
||||||
private data class ObjCFrameworkCacheKey(val sourceModules: Set<TestModule>)
|
private data class ObjCFrameworkCacheKey(val sourceModules: Set<TestModule>)
|
||||||
|
private data class BinaryLibraryCacheKey(val sourceModules: Set<TestModule>, val kind: BinaryLibrary.Kind)
|
||||||
|
|
||||||
// A pair of compilations for a KLIB itself and for its static cache that are created together.
|
// A pair of compilations for a KLIB itself and for its static cache that are created together.
|
||||||
private data class KlibCompilations(val klib: TestCompilation<KLIB>, val staticCache: TestCompilation<KLIBStaticCache>?)
|
private data class KlibCompilations(val klib: TestCompilation<KLIB>, val staticCache: TestCompilation<KLIBStaticCache>?)
|
||||||
@@ -84,6 +86,29 @@ internal class TestCompilationFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testCaseToBinaryLibrary(testCase: TestCase, settings: Settings, kind: BinaryLibrary.Kind): BinaryLibraryCompilation {
|
||||||
|
val rootModules = testCase.rootModules
|
||||||
|
val cacheKey = BinaryLibraryCacheKey(testCase.rootModules, kind)
|
||||||
|
cachedBinaryLibraryCompilations[cacheKey]?.let { return it }
|
||||||
|
|
||||||
|
val (
|
||||||
|
dependencies: Iterable<CompiledDependency<*>>,
|
||||||
|
sourceModules: Set<TestModule.Exclusive>
|
||||||
|
) = getDependenciesAndSourceModules(settings, testCase.rootModules, testCase.freeCompilerArgs) {
|
||||||
|
ProduceStaticCache.No
|
||||||
|
}
|
||||||
|
val expectedArtifact = BinaryLibrary(settings.artifactFileForBinaryLibrary(rootModules, kind), kind = kind)
|
||||||
|
return cachedBinaryLibraryCompilations.computeIfAbsent(cacheKey) {
|
||||||
|
BinaryLibraryCompilation(
|
||||||
|
settings = settings,
|
||||||
|
freeCompilerArgs = testCase.freeCompilerArgs,
|
||||||
|
sourceModules = sourceModules,
|
||||||
|
dependencies = dependencies,
|
||||||
|
expectedArtifact = expectedArtifact
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun testCaseToObjCFrameworkCompilation(testCase: TestCase, settings: Settings): ObjCFrameworkCompilation {
|
fun testCaseToObjCFrameworkCompilation(testCase: TestCase, settings: Settings): ObjCFrameworkCompilation {
|
||||||
val cacheKey = ObjCFrameworkCacheKey(testCase.rootModules)
|
val cacheKey = ObjCFrameworkCacheKey(testCase.rootModules)
|
||||||
cachedObjCFrameworkCompilations[cacheKey]?.let { return it }
|
cachedObjCFrameworkCompilations[cacheKey]?.let { return it }
|
||||||
@@ -298,6 +323,19 @@ internal class TestCompilationFactory {
|
|||||||
private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) =
|
private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) =
|
||||||
singleModuleArtifactFile(module, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
|
singleModuleArtifactFile(module, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
|
||||||
|
|
||||||
|
private fun Settings.pickBinaryLibrarySuffix(kind: BinaryLibrary.Kind) = when (kind) {
|
||||||
|
BinaryLibrary.Kind.STATIC -> get<KotlinNativeTargets>().testTarget.family.staticSuffix
|
||||||
|
BinaryLibrary.Kind.DYNAMIC -> get<KotlinNativeTargets>().testTarget.family.dynamicSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Settings.artifactFileForBinaryLibrary(modules: Set<TestModule.Exclusive>, kind: BinaryLibrary.Kind) = when (modules.size) {
|
||||||
|
1 -> artifactFileForBinaryLibrary(modules.first(), kind)
|
||||||
|
else -> multiModuleArtifactFile(modules, pickBinaryLibrarySuffix(kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Settings.artifactFileForBinaryLibrary(module: TestModule.Exclusive, kind: BinaryLibrary.Kind) =
|
||||||
|
singleModuleArtifactFile(module, pickBinaryLibrarySuffix(kind))
|
||||||
|
|
||||||
private fun Settings.artifactFileForKlib(modules: Set<TestModule>, freeCompilerArgs: TestCompilerArgs): File =
|
private fun Settings.artifactFileForKlib(modules: Set<TestModule>, freeCompilerArgs: TestCompilerArgs): File =
|
||||||
when (modules.size) {
|
when (modules.size) {
|
||||||
1 -> when (val module = modules.first()) {
|
1 -> when (val module = modules.first()) {
|
||||||
|
|||||||
+9
-1
@@ -14,15 +14,23 @@ import java.io.File
|
|||||||
internal fun AbstractNativeSimpleTest.compileWithClang(
|
internal fun AbstractNativeSimpleTest.compileWithClang(
|
||||||
sourceFiles: List<File>,
|
sourceFiles: List<File>,
|
||||||
outputFile: File,
|
outputFile: File,
|
||||||
frameworkDirectories: List<File>
|
includeDirectories: List<File> = emptyList(),
|
||||||
|
frameworkDirectories: List<File> = emptyList(),
|
||||||
|
libraryDirectories: List<File> = emptyList(),
|
||||||
|
libraries: List<String> = emptyList(),
|
||||||
|
additionalLinkerFlags: List<String> = emptyList(),
|
||||||
) {
|
) {
|
||||||
val process = ProcessBuilder(
|
val process = ProcessBuilder(
|
||||||
"${testRunSettings.configurables.absoluteTargetToolchain}/bin/clang",
|
"${testRunSettings.configurables.absoluteTargetToolchain}/bin/clang",
|
||||||
*sourceFiles.map { it.absolutePath }.toTypedArray(),
|
*sourceFiles.map { it.absolutePath }.toTypedArray(),
|
||||||
|
*includeDirectories.flatMap { listOf("-I", it.absolutePath) }.toTypedArray(),
|
||||||
"-isysroot", testRunSettings.configurables.absoluteTargetSysRoot,
|
"-isysroot", testRunSettings.configurables.absoluteTargetSysRoot,
|
||||||
"-target", testRunSettings.configurables.targetTriple.toString(),
|
"-target", testRunSettings.configurables.targetTriple.toString(),
|
||||||
"-g", "-fmodules",
|
"-g", "-fmodules",
|
||||||
*frameworkDirectories.flatMap { listOf("-F", it.absolutePath) }.toTypedArray(),
|
*frameworkDirectories.flatMap { listOf("-F", it.absolutePath) }.toTypedArray(),
|
||||||
|
*libraryDirectories.flatMap { listOf("-L", it.absolutePath) }.toTypedArray(),
|
||||||
|
*libraries.map { "-l$it" }.toTypedArray(),
|
||||||
|
*additionalLinkerFlags.toTypedArray(),
|
||||||
"-o", outputFile.absolutePath
|
"-o", outputFile.absolutePath
|
||||||
).redirectErrorStream(true).start()
|
).redirectErrorStream(true).start()
|
||||||
val clangOutput = process.inputStream.readBytes()
|
val clangOutput = process.inputStream.readBytes()
|
||||||
|
|||||||
Reference in New Issue
Block a user