[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")
|
||||
}
|
||||
}
|
||||
// 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>() {
|
||||
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 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 cachedExecutableCompilations = ThreadSafeCache<ExecutableCacheKey, TestCompilation<Executable>>()
|
||||
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 ExecutableCacheKey(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.
|
||||
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 {
|
||||
val cacheKey = ObjCFrameworkCacheKey(testCase.rootModules)
|
||||
cachedObjCFrameworkCompilations[cacheKey]?.let { return it }
|
||||
@@ -298,6 +323,19 @@ internal class TestCompilationFactory {
|
||||
private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) =
|
||||
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 =
|
||||
when (modules.size) {
|
||||
1 -> when (val module = modules.first()) {
|
||||
|
||||
+9
-1
@@ -14,15 +14,23 @@ import java.io.File
|
||||
internal fun AbstractNativeSimpleTest.compileWithClang(
|
||||
sourceFiles: List<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(
|
||||
"${testRunSettings.configurables.absoluteTargetToolchain}/bin/clang",
|
||||
*sourceFiles.map { it.absolutePath }.toTypedArray(),
|
||||
*includeDirectories.flatMap { listOf("-I", it.absolutePath) }.toTypedArray(),
|
||||
"-isysroot", testRunSettings.configurables.absoluteTargetSysRoot,
|
||||
"-target", testRunSettings.configurables.targetTriple.toString(),
|
||||
"-g", "-fmodules",
|
||||
*frameworkDirectories.flatMap { listOf("-F", it.absolutePath) }.toTypedArray(),
|
||||
*libraryDirectories.flatMap { listOf("-L", it.absolutePath) }.toTypedArray(),
|
||||
*libraries.map { "-l$it" }.toTypedArray(),
|
||||
*additionalLinkerFlags.toTypedArray(),
|
||||
"-o", outputFile.absolutePath
|
||||
).redirectErrorStream(true).start()
|
||||
val clangOutput = process.inputStream.readBytes()
|
||||
|
||||
Reference in New Issue
Block a user