[K/N][Tests] Clean up framework testing code

^KT-61259
This commit is contained in:
Vladimir Sukharev
2024-01-31 14:56:37 +01:00
committed by Space Team
parent c72443e4be
commit 95b1fba37f
3 changed files with 4 additions and 252 deletions
@@ -177,15 +177,9 @@ tasks.named("clean", Delete.class) {
tasks.named("run") {
dependsOn(tasks.withType(KonanTest).matching { it.enabled })
// Add framework tests
dependsOn(tasks.withType(FrameworkTest).matching { it.enabled })
dependsOn(tasks.withType(FileCheckTest).matching { it.enabled })
dependsOn(tasks.withType(KonanTest).matching { it.enabled })
// Add framework tests
dependsOn(tasks.withType(FrameworkTest).matching { it.enabled })
// Add regular gradle test tasks
dependsOn(tasks.withType(Test).matching { it.enabled })
dependsOn(tasks.withType(FileCheckTest).matching { it.enabled })
dependsOn(":kotlin-native:Interop:Indexer:check")
dependsOn(":kotlin-native:Interop:StubGenerator:check")
dependsOn(":native:kotlin-native-utils:check")
@@ -1836,42 +1830,6 @@ standaloneTest("split_compilation_pipeline") {
useGoldenData = true
}
Task frameworkTest(String name, Closure<FrameworkTest> configurator) {
return KotlinNativeTestKt.createTest(project, name, FrameworkTest) { task ->
configurator.delegate = task
UtilsKt.dependsOnDist(task)
configurator()
if (task.enabled) {
konanArtifacts {
task.frameworks.forEach { fr ->
framework(fr.name, targets: [target.name]) {
fr.sources.forEach { src ->
srcFiles src
}
baseDir "$testOutputFramework/${task.name}"
fr.libraries.forEach { library ->
dependsOn konanArtifacts[library].getByTarget(target.name)
libraries {
klibFile konanArtifacts[library].getArtifactByTarget(target.name)
}
linkerOpts "-L${project.layout.buildDirectory.get().asFile}"
UtilsKt.dependsOnKonanBuildingTask(task, library, target)
}
if (fr.isStatic) extraOpts "-Xstatic-framework"
extraOpts fr.opts
extraOpts project.globalTestArgs
artifactName fr.artifact
}
UtilsKt.dependsOnDist(project.tasks.findByName("compileKonan${fr.name.capitalize()}${UtilsKt.getTestTargetSuffix(project)}"))
}
}
}
}
}
tasks.register("override_konan_properties0", KonanDriverTest) {
disabled = isAggressiveGC // No need to test with different GC schedulers
def overrides = PlatformInfo.isWindows()
@@ -1,204 +0,0 @@
package org.jetbrains.kotlin
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.file.FileTree
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.konan.target.*
import java.io.File
import java.io.FileWriter
import java.io.Serializable
import java.nio.file.Path
import java.nio.file.Paths
/**
* Test task for -produce framework testing. Requires a framework to be built by the Konan plugin
* with konanArtifacts { framework(frameworkName, targets: [ testTarget] ) } and a dependency set
* according to a pattern "compileKonan${frameworkName}".
*
* @property swiftSources Swift-language test sources that use a given framework
* @property frameworks names of frameworks
*/
open class FrameworkTest : DefaultTask(), KonanTestExecutable {
@Input
lateinit var swiftSources: List<String>
@Input
var swiftExtraOpts: List<String> = emptyList()
@Input
lateinit var frameworks: MutableList<Framework>
@Input
var codesign: Boolean = true
@Input
val testOutput: String = project.testOutputFramework
@Input @Optional
var expectedExitStatus: Int? = null
/**
* Framework description.
*
* @param name is the framework name,
* @param sources framework sources,
* @param isStatic determines that framework is static
* @param artifact the name of the resulting artifact,
* @param library list of library dependency names,
* @param opts additional options for the compiler.
*/
class Framework(
val name: String,
var sources: List<String> = emptyList(),
var isStatic: Boolean = false,
var artifact: String = name,
var libraries: List<String> = emptyList(),
var opts: List<String> = emptyList()
) : Serializable // Required for Gradle when using Framework as task input.
/**
* Used for the framework configuration in the task's closure.
*/
fun framework(name: String, closure: Closure<Framework>): Framework {
val f = Framework(name).apply {
closure.delegate = this
closure.resolveStrategy = Closure.DELEGATE_FIRST
closure.call()
// map to file paths
sources = sources.toFiles(Language.Kotlin).map { it.path }
}
if (!::frameworks.isInitialized) {
frameworks = mutableListOf(f)
} else {
frameworks.add(f)
}
return f
}
enum class Language(val extension: String) {
Kotlin(".kt"), ObjC(".m"), Swift(".swift")
}
fun Language.filesFrom(dir: String): FileTree = project.fileTree(dir) {
// include only files with the language extension
include("*${this@filesFrom.extension}")
}
fun List<String>.toFiles(language: Language): List<File> =
this.map { language.filesFrom(it) }
.flatMap { it.files }
@get:Internal
override val executable: String
get() = Paths.get(testOutput, name, "swiftTestExecutable").toString()
@Internal
override var doBeforeRun: Action<in Task>? = null
@Internal
override var doBeforeBuild: Action<in Task>? = null
@get:Internal
override val buildTasks: List<Task>
get() = frameworks.map { project.tasks.getByName("compileKonan${it.name}") }
@Suppress("UnstableApiUsage")
override fun configure(config: Closure<*>): Task {
super.configure(config)
// set crossdist build dependency if custom konan.home wasn't set
this.dependsOnDist()
// Set Gradle properties for the better navigation
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Kotlin/Native test infrastructure task"
check(::frameworks.isInitialized) { "Frameworks should be set" }
return this
}
private fun buildTestExecutable() {
val frameworkParentDirPath = "$testOutput/$name/${project.testTarget.name}"
frameworks.forEach { framework ->
val frameworkArtifact = framework.artifact
val frameworkPath = "$frameworkParentDirPath/$frameworkArtifact.framework"
if (codesign) codesign(project, frameworkPath)
}
// create a test provider and get main entry point
val provider = Paths.get(testOutput, name, "provider.swift")
FileWriter(provider.toFile()).use { writer ->
val providers = swiftSources.toFiles(Language.Swift)
.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())
}
val testHome = project.file("framework").toPath()
val swiftMain = Paths.get(testHome.toString(), "main.swift").toString()
// Compile swift sources
val sources = swiftSources.toFiles(Language.Swift)
.map { it.path } + listOf(provider.toString(), swiftMain)
val options = listOf(
"-g",
"-Xlinker", "-rpath", "-Xlinker", "@executable_path/Frameworks",
"-Xlinker", "-rpath", "-Xlinker", frameworkParentDirPath,
"-F", frameworkParentDirPath,
"-Xcc", "-Werror" // To fail compilation on warnings in framework header.
)
// As of Xcode 13.1 swift passes wrong libclang_rt to simulator targets (similar to KT-47333).
// To workaround this problem, we explicitly provide the correct one.
val simulatorHack = if (project.testTargetConfigurables.targetTriple.isSimulator) {
project.platformManager.platform(project.testTarget).linker.provideCompilerRtLibrary("")?.let {
listOf("-Xlinker", it)
} ?: emptyList()
} else {
emptyList()
}
compileSwift(project, project.testTarget, sources, options + simulatorHack + swiftExtraOpts, Paths.get(executable))
}
@TaskAction
fun run() {
// Build test executable as a first action of the task before executing the test
buildTestExecutable()
doBeforeRun?.execute(this)
val testExecutable = Paths.get(executable)
val (stdOut, stdErr, exitCode) = runProcess(
executor = {
project.executor.execute {
it.execute(this)
workingDir = Paths.get(testOutput).toFile()
}
},
executable = testExecutable.toString())
val testExecName = testExecutable.fileName
println("""
|$testExecName
|stdout: $stdOut
|stderr: $stdErr
""".trimMargin())
val timeoutMessage = if (exitCode == -1) {
"WARNING: probably a timeout\n"
} else ""
val expectedExitCode = expectedExitStatus?.takeUnless { project.compileOnlyTests } ?: 0
check(exitCode == expectedExitCode) { "${timeoutMessage}Execution of $testExecName failed with exit code: $exitCode (expected exit code: $expectedExitCode)" }
}
}
@@ -189,6 +189,8 @@ internal fun invokeSwiftC(
File(configs.absoluteTargetToolchain).resolveSibling("ExtraFrameworks").absolutePath
}
} catch (rpe: RunProcessException) {
if (rpe.exitCode == null)
throw rpe // Treat compiler timeouts as fatal errors
return CompilationToolCallResult(
exitCode = ExitCode.COMPILATION_ERROR,
toolOutput = "ARGS: $args\nSTDOUT: ${rpe.stdout}\nSTDERR: ${rpe.stderr}",
@@ -207,11 +209,7 @@ internal fun invokeSwiftC(
internal fun codesign(path: String) {
val executableAbsolutePath = "/usr/bin/codesign"
val args = arrayOf("--verbose", "-s", "-", path)
try {
runProcess(executableAbsolutePath, *args) {
timeout = Duration.parse("30s")
}
} catch (rpe: RunProcessException) {
throw AssertionError("`$executableAbsolutePath ${args.joinToString(" ")}` failed with exitCode=${rpe.exitCode}: ${rpe.message}")
runProcess(executableAbsolutePath, *args) {
timeout = Duration.parse("30s")
}
}