[Native][tests] Refactoring: Use typed compilations and artifacts

^KT-50775
This commit is contained in:
Dmitriy Dolovov
2022-01-21 15:21:10 +03:00
parent 7df68af664
commit ca27eac254
8 changed files with 293 additions and 244 deletions
@@ -9,11 +9,13 @@ import com.intellij.testFramework.TestDataFile
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencies
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.ExecutableCompilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.ExistingLibraryDependency
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.LibraryCompilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.Executable
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.Library
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccessfullyCompiled
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
@@ -27,10 +29,6 @@ import java.io.File
@Tag("klib")
abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
private val compilationResults: MutableMap<File, TestCompilationResult.Success> by lazy {
mutableMapOf(stdlibFile to TestCompilationResult.ExistingArtifact(stdlibFile))
}
protected fun runTest(@TestDataFile testPath: String): Unit = AbstractKlibABITestCase.doTest(
testDir = getAbsoluteFile(testPath),
buildDir = buildDir,
@@ -48,15 +46,15 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
val testCase = createTestCase(module, COMPILER_ARGS_FOR_KLIB)
val compilation = TestCompilation.createForKlib(
val compilation = LibraryCompilation(
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
dependencies = createCompilationDependencies(moduleDependencies),
expectedKlibFile = klibFile,
dependencies = createExistingDependencies(moduleDependencies),
expectedArtifact = TestCompilationArtifact.KLIB(klibFile),
)
compilationResults[klibFile] = compilation.result.assertSuccess() // <-- trigger compilation
compilation.result.assertSuccess() // <-- trigger compilation
}
private fun buildBinaryAndRun(allDependencies: Collection<File>) {
@@ -72,16 +70,16 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
val testCase = createTestCase(module, COMPILER_ARGS_FOR_EXECUTABLE)
val compilation = TestCompilation.createForExecutable(
val compilation = ExecutableCompilation(
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
extras = testCase.extras,
dependencies = createCompilationDependencies(allDependencies),
expectedExecutableFile = executableFile
dependencies = createExistingDependencies(allDependencies),
expectedArtifact = Executable(executableFile)
)
val compilationResult = compilation.result.assertSuccessfullyCompiled() // <-- trigger compilation
val compilationResult = compilation.result.assertSuccess() // <-- trigger compilation
val executable = TestExecutable(executableFile, compilationResult.loggedData)
runExecutableAndVerify(testCase, executable) // <-- run executable and verify
@@ -106,9 +104,8 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
initialize(null)
}
private fun createCompilationDependencies(dependencies: Collection<File>) = TestCompilationDependencies(
libraries = dependencies.map(TestCompilation.Companion::createForExistingArtifact)
)
private fun createExistingDependencies(dependencies: Collection<File>) =
dependencies.map { ExistingLibraryDependency(TestCompilationArtifact.KLIB(it), Library) }
private val buildDir: File get() = testRunSettings.get<SimpleTestDirectories>().testBuildDir
private val stdlibFile: File get() = testRunSettings.get<KotlinNativeHome>().dir.resolve("klib/common/stdlib")
@@ -8,133 +8,21 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.compilation
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.*
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilation.Companion.resultingArtifactPath
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.Executable
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.KLIB
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheKind.Companion.rootCacheDir
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ArgsBuilder
import org.jetbrains.kotlin.konan.blackboxtest.support.util.buildArgs
import org.jetbrains.kotlin.konan.blackboxtest.support.util.flatMapToSet
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.File
internal interface TestCompilation {
val result: TestCompilationResult
companion object {
val TestCompilation.resultingArtifactPath: String
get() = result.assertSuccess().resultingArtifact.path
fun createForKlib(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
dependencies: TestCompilationDependencies,
expectedKlibFile: File
): TestCompilation = TestCompilationImpl.create(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifactFile = expectedKlibFile,
specificCompilerArgs = TestCompilationImpl.compilerArgsForKlib()
)
fun createForExecutable(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
extras: Extras,
dependencies: TestCompilationDependencies,
expectedExecutableFile: File
): TestCompilation = TestCompilationImpl.create(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifactFile = expectedExecutableFile,
specificCompilerArgs = TestCompilationImpl.compilerArgsForExecutable(settings.get(), extras)
)
fun createForExistingArtifact(resultingArtifact: File): TestCompilation =
object : TestCompilation {
override val result = TestCompilationResult.ExistingArtifact(resultingArtifact)
}
}
internal abstract class TestCompilation<A : TestCompilationArtifact> {
abstract val result: TestCompilationResult<out A>
}
internal sealed interface TestCompilationResult {
sealed interface ImmediateResult : TestCompilationResult {
val loggedData: LoggedData
}
sealed interface Success : TestCompilationResult {
val resultingArtifact: File
}
sealed interface Failure : ImmediateResult
data class ExistingArtifact(override val resultingArtifact: File) : Success
data class CompiledArtifact(override val resultingArtifact: File, override val loggedData: LoggedData.CompilerCall) :
ImmediateResult, Success
data class CompilerFailure(override val loggedData: LoggedData.CompilerCall) : Failure
data class UnexpectedFailure(override val loggedData: LoggedData.CompilerCallUnexpectedFailure) : Failure
data class DependencyFailures(val causes: Set<Failure>) : TestCompilationResult
companion object {
fun TestCompilationResult.assertSuccess(): Success = when (this) {
is Success -> this
is Failure -> fail { describeFailure() }
is DependencyFailures -> fail { describeDependencyFailures() }
}
fun TestCompilationResult.assertSuccessfullyCompiled(): CompiledArtifact = when (val result = assertSuccess()) {
is CompiledArtifact -> result
is ExistingArtifact -> fail { "Test compilation result contains pre-existing artifact: ${result.resultingArtifact}" }
}
private fun Failure.describeFailure() = loggedData.withErrorMessage(
when (this@describeFailure) {
is CompilerFailure -> "Compilation failed."
is UnexpectedFailure -> "Compilation failed with unexpected exception."
}
)
private fun DependencyFailures.describeDependencyFailures() =
buildString {
appendLine("Compilation aborted due to errors in dependency compilations (${causes.size} items). See details below.")
appendLine()
causes.forEachIndexed { index, cause ->
append("#").append(index + 1).append(". ")
appendLine(cause.describeFailure())
}
}
}
}
/**
* The dependencies of a particular [TestCompilation].
*
* [libraries] - the [TestCompilation]s (modules) that should yield KLIBs to be consumed as dependency libraries in the current compilation.
* [friends] - similarly but friend modules (-friend-modules).
* [includedLibraries] - similarly but included modules (-Xinclude).
*/
internal class TestCompilationDependencies(
val libraries: Collection<TestCompilation> = emptyList(),
val friends: Collection<TestCompilation> = emptyList(),
val includedLibraries: Collection<TestCompilation> = emptyList()
) {
fun collectFailures(): Set<TestCompilationResult.Failure> = listOf(libraries, friends, includedLibraries)
.flatten()
.flatMapToSet { compilation ->
when (val result = compilation.result) {
is TestCompilationResult.Failure -> listOf(result)
is TestCompilationResult.DependencyFailures -> result.causes
is TestCompilationResult.Success -> emptyList()
}
}
}
private class TestCompilationImpl(
internal abstract class BaseCompilation<A : TestCompilationArtifact>(
private val targets: KotlinNativeTargets,
private val home: KotlinNativeHome,
private val classLoader: KotlinNativeClassLoader,
@@ -144,12 +32,11 @@ private class TestCompilationImpl(
private val gcType: GCType,
private val freeCompilerArgs: TestCompilerArgs,
private val sourceModules: Collection<TestModule>,
private val dependencies: TestCompilationDependencies,
private val expectedArtifactFile: File,
private val specificCompilerArgs: ArgsBuilder.() -> Unit
) : TestCompilation {
private val dependencies: Collection<TestCompilationDependency<*>>,
private val expectedArtifact: A
) : TestCompilation<A>() {
// Runs the compiler and memorizes the result on property access.
override val result: TestCompilationResult by lazy {
final override val result: TestCompilationResult<out A> by lazy {
val failures = dependencies.collectFailures()
if (failures.isNotEmpty())
TestCompilationResult.DependencyFailures(causes = failures)
@@ -162,7 +49,7 @@ private class TestCompilationImpl(
"-enable-assertions",
"-target", targets.testTarget.name,
"-repo", home.dir.resolve("klib").path,
"-output", expectedArtifactFile.path,
"-output", expectedArtifact.path,
"-Xskip-prerelease-check",
"-Xverify-ir",
"-Xbinary=runtimeAssertionsMode=panic"
@@ -172,28 +59,30 @@ private class TestCompilationImpl(
threadStateChecker.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
gcType.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
addFlattened(dependencies.libraries) { library -> listOf("-l", library.resultingArtifactPath) }
addFlattened(dependencies.libraries) { library -> listOf("-l", library.path) }
dependencies.friends.takeIf(Collection<*>::isNotEmpty)?.let { friends ->
add("-friend-modules", friends.joinToString(File.pathSeparator) { friend -> friend.resultingArtifactPath })
add("-friend-modules", friends.joinToString(File.pathSeparator) { friend -> friend.path })
}
add(dependencies.includedLibraries) { include -> "-Xinclude=${include.resultingArtifactPath}" }
add(dependencies.includedLibraries) { include -> "-Xinclude=${include.path}" }
add(freeCompilerArgs.compilerArgs)
}
protected abstract fun ArgsBuilder.applySpecificCompilerArgs()
private fun ArgsBuilder.applySources() {
addFlattenedTwice(sourceModules, { it.files }) { it.location.path }
}
private fun doCompile(): TestCompilationResult.ImmediateResult {
private fun doCompile(): TestCompilationResult.ImmediateResult<out A> {
val compilerArgs = buildArgs {
applyCommonArgs()
specificCompilerArgs()
applySpecificCompilerArgs()
applySources()
}
val loggedCompilerParameters = LoggedData.CompilerParameters(compilerArgs, sourceModules)
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult<out A>) = try {
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
compilerArgs = compilerArgs,
kotlinNativeClassLoader = classLoader.classLoader
@@ -205,7 +94,7 @@ private class TestCompilationImpl(
val result = if (exitCode != ExitCode.OK || compilerOutputHasErrors)
TestCompilationResult.CompilerFailure(loggedCompilerCall)
else
TestCompilationResult.CompiledArtifact(expectedArtifactFile, loggedCompilerCall)
TestCompilationResult.Success(expectedArtifact, loggedCompilerCall)
loggedCompilerCall to result
} catch (unexpectedThrowable: Throwable) {
@@ -215,83 +104,91 @@ private class TestCompilationImpl(
loggedFailure to result
}
getLogFile(expectedArtifactFile).writeText(loggedCompilerCall.toString())
getLogFile(expectedArtifact.file).writeText(loggedCompilerCall.toString())
return result
}
companion object {
fun compilerArgsForKlib(): ArgsBuilder.() -> Unit = { add("-produce", "library") }
fun compilerArgsForExecutable(cacheKind: CacheKind, extras: Extras): ArgsBuilder.() -> Unit = {
add("-produce", "program")
when (extras) {
is NoTestRunnerExtras -> add("-entry", extras.entryPoint)
is WithTestRunnerExtras -> {
val testRunnerArg = when (extras.runnerType) {
TestRunnerType.DEFAULT -> "-generate-test-runner"
TestRunnerType.WORKER -> "-generate-worker-test-runner"
TestRunnerType.NO_EXIT -> "-generate-no-exit-test-runner"
}
add(testRunnerArg)
}
private fun Collection<TestCompilationDependency<*>>.collectFailures() = flatMapToSet { dependency ->
when (val result = (dependency as? TestCompilation<*>)?.result) {
is TestCompilationResult.Failure -> listOf(result)
is TestCompilationResult.DependencyFailures -> result.causes
is TestCompilationResult.Success -> emptyList()
null -> emptyList()
}
cacheKind.rootCacheDir?.let { rootCacheDir -> add("-Xcache-directory=$rootCacheDir") }
}
// Just a shortcut to reduce the number of arguments.
fun create(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
dependencies: TestCompilationDependencies,
expectedArtifactFile: File,
specificCompilerArgs: ArgsBuilder.() -> Unit
) = TestCompilationImpl(
targets = settings.get(),
home = settings.get(),
classLoader = settings.get(),
optimizationMode = settings.get(),
memoryModel = settings.get(),
threadStateChecker = settings.get(),
gcType = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifactFile = expectedArtifactFile,
specificCompilerArgs = specificCompilerArgs
)
private inline fun <reified A : TestCompilationArtifact, reified T : TestCompilationDependencyType<A>> Collection<TestCompilationDependency<*>>.collectArtifacts() =
mapNotNull { dependency -> if (dependency.type is T) dependency.artifact as A else null }
private val Collection<TestCompilationDependency<*>>.libraries get() = collectArtifacts<KLIB, Library>()
private val Collection<TestCompilationDependency<*>>.friends get() = collectArtifacts<KLIB, FriendLibrary>()
private val Collection<TestCompilationDependency<*>>.includedLibraries get() = collectArtifacts<KLIB, IncludedLibrary>()
private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log")
}
}
private class ArgsBuilder {
private val args = mutableListOf<String>()
fun add(vararg args: String) {
this.args += args
internal class LibraryCompilation(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
dependencies: Collection<TestCompilationDependency<*>>,
expectedArtifact: KLIB
) : BaseCompilation<KLIB>(
targets = settings.get(),
home = settings.get(),
classLoader = settings.get(),
optimizationMode = settings.get(),
memoryModel = settings.get(),
threadStateChecker = settings.get(),
gcType = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifact = expectedArtifact
) {
override fun ArgsBuilder.applySpecificCompilerArgs() {
add("-produce", "library")
}
fun add(args: Iterable<String>) {
this.args += args
}
inline fun <T> add(rawArgs: Iterable<T>, transform: (T) -> String) {
rawArgs.mapTo(args) { transform(it) }
}
inline fun <T> addFlattened(rawArgs: Iterable<T>, transform: (T) -> Iterable<String>) {
rawArgs.flatMapTo(args) { transform(it) }
}
inline fun <T, R> addFlattenedTwice(rawArgs: Iterable<T>, transform1: (T) -> Iterable<R>, transform2: (R) -> String) {
rawArgs.forEach { add(transform1(it), transform2) }
}
fun build(): Array<String> = args.toTypedArray()
}
private inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array<String> {
return ArgsBuilder().apply(builderAction).build()
}
internal class ExecutableCompilation(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
private val extras: Extras,
dependencies: Collection<TestCompilationDependency<*>>,
expectedArtifact: Executable
) : BaseCompilation<Executable>(
targets = settings.get(),
home = settings.get(),
classLoader = settings.get(),
optimizationMode = settings.get(),
memoryModel = settings.get(),
threadStateChecker = settings.get(),
gcType = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifact = expectedArtifact
) {
private val cacheKind: CacheKind = settings.get()
private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log")
override fun ArgsBuilder.applySpecificCompilerArgs() {
add("-produce", "program")
when (extras) {
is NoTestRunnerExtras -> add("-entry", extras.entryPoint)
is WithTestRunnerExtras -> {
val testRunnerArg = when (extras.runnerType) {
TestRunnerType.DEFAULT -> "-generate-test-runner"
TestRunnerType.WORKER -> "-generate-worker-test-runner"
TestRunnerType.NO_EXIT -> "-generate-no-exit-test-runner"
}
add(testRunnerArg)
}
}
cacheKind.rootCacheDir?.let { rootCacheDir -> add("-Xcache-directory=$rootCacheDir") }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright 2010-2022 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.blackboxtest.support.compilation
import java.io.File
internal sealed interface TestCompilationArtifact {
val file: File
val path: String get() = file.path
data class KLIB(override val file: File) : TestCompilationArtifact
data class Executable(override val file: File) : TestCompilationArtifact
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2022 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.blackboxtest.support.compilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
/**
* The type of dependency for a particular [TestCompilation].
*
* [Library] - the [TestCompilation]s (modules) that should yield KLIBs to be consumed as dependency libraries in the current compilation.
* [FriendLibrary] - similarly but friend modules (-friend-modules).
* [IncludedLibrary] - similarly but included modules (-Xinclude).
*/
internal sealed interface TestCompilationDependencyType<A : TestCompilationArtifact> {
object Library : TestCompilationDependencyType<TestCompilationArtifact.KLIB>
object FriendLibrary : TestCompilationDependencyType<TestCompilationArtifact.KLIB>
object IncludedLibrary : TestCompilationDependencyType<TestCompilationArtifact.KLIB>
}
internal sealed interface TestCompilationDependency<A : TestCompilationArtifact> {
val artifact: A
val type: TestCompilationDependencyType<A>
}
internal class CompiledDependency<A : TestCompilationArtifact>(
val compilation: TestCompilation<A>,
override val type: TestCompilationDependencyType<A>
) : TestCompilationDependency<A> {
override val artifact: A get() = compilation.result.assertSuccess().resultingArtifact
}
internal class ExistingLibraryDependency(
override val artifact: TestCompilationArtifact.KLIB,
override val type: TestCompilationDependencyType<TestCompilationArtifact.KLIB>
) : TestCompilationDependency<TestCompilationArtifact.KLIB>
@@ -11,6 +11,10 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilerArgs
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allFriends
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.Executable
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.KLIB
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.FriendLibrary
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.Library
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Binaries
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
@@ -18,60 +22,71 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import java.io.File
internal class TestCompilationFactory {
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
private val cachedKlibCompilations = ThreadSafeCache<KlibCacheKey, TestCompilation<KLIB>>()
private val cachedExecutableCompilations = ThreadSafeCache<ExecutableCacheKey, TestCompilation<Executable>>()
private sealed interface TestCompilationCacheKey {
data class Klib(val sourceModules: Set<TestModule>, val freeCompilerArgs: TestCompilerArgs) : TestCompilationCacheKey
data class Executable(val sourceModules: Set<TestModule>) : TestCompilationCacheKey
}
private data class KlibCacheKey(val sourceModules: Set<TestModule>, val freeCompilerArgs: TestCompilerArgs)
private data class ExecutableCacheKey(val sourceModules: Set<TestModule>)
fun testCasesToExecutable(testCases: Collection<TestCase>, settings: Settings): TestCompilation {
fun testCasesToExecutable(testCases: Collection<TestCase>, settings: Settings): TestCompilation<Executable> {
val rootModules = testCases.flatMapToSet { testCase -> testCase.rootModules }
val cacheKey = TestCompilationCacheKey.Executable(rootModules)
val cacheKey = ExecutableCacheKey(rootModules)
// Fast pass.
cachedCompilations[cacheKey]?.let { return it }
cachedExecutableCompilations[cacheKey]?.let { return it }
// Long pass.
val freeCompilerArgs = rootModules.first().testCase.freeCompilerArgs // Should be identical inside the same test case group.
val extras = testCases.first().extras // Should be identical inside the same test case group.
val libraries = rootModules.flatMapToSet { it.allDependencies }.map { moduleToKlib(it, freeCompilerArgs, settings) }
val friends = rootModules.flatMapToSet { it.allFriends }.map { moduleToKlib(it, freeCompilerArgs, settings) }
val dependencies = buildList {
rootModules.flatMapToSet { it.allDependencies }.mapTo(this) { it.asKlibDependency(freeCompilerArgs, settings, Library) }
rootModules.flatMapToSet { it.allFriends }.mapTo(this) { it.asKlibDependency(freeCompilerArgs, settings, FriendLibrary) }
}
val expectedArtifact = Executable(settings.artifactFileForExecutable(rootModules))
return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilation.createForExecutable(
return cachedExecutableCompilations.computeIfAbsent(cacheKey) {
ExecutableCompilation(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = rootModules,
extras = extras,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
expectedExecutableFile = settings.artifactFileForExecutable(rootModules),
dependencies = dependencies,
expectedArtifact = expectedArtifact
)
}
}
private fun moduleToKlib(sourceModule: TestModule, freeCompilerArgs: TestCompilerArgs, settings: Settings): TestCompilation {
private fun moduleToKlib(sourceModule: TestModule, freeCompilerArgs: TestCompilerArgs, settings: Settings): TestCompilation<KLIB> {
val sourceModules = setOf(sourceModule)
val cacheKey = TestCompilationCacheKey.Klib(sourceModules, freeCompilerArgs)
val cacheKey = KlibCacheKey(sourceModules, freeCompilerArgs)
// Fast pass.
cachedCompilations[cacheKey]?.let { return it }
cachedKlibCompilations[cacheKey]?.let { return it }
// Long pass.
val libraries = sourceModule.allDependencies.map { moduleToKlib(it, freeCompilerArgs, settings) }
val friends = sourceModule.allFriends.map { moduleToKlib(it, freeCompilerArgs, settings) }
val dependencies = buildList {
sourceModule.allDependencies.mapTo(this) { it.asKlibDependency(freeCompilerArgs, settings, Library) }
sourceModule.allFriends.mapTo(this) { it.asKlibDependency(freeCompilerArgs, settings, FriendLibrary) }
}
val expectedArtifact = KLIB(settings.artifactFileForKlib(sourceModule, freeCompilerArgs))
return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilation.createForKlib(
return cachedKlibCompilations.computeIfAbsent(cacheKey) {
LibraryCompilation(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
expectedKlibFile = settings.artifactFileForKlib(sourceModule, freeCompilerArgs)
dependencies = dependencies,
expectedArtifact = expectedArtifact
)
}
}
private fun <T : TestCompilationDependencyType<KLIB>> TestModule.asKlibDependency(
freeCompilerArgs: TestCompilerArgs,
settings: Settings,
type: T
) = CompiledDependency(moduleToKlib(this, freeCompilerArgs, settings), type)
private fun Settings.artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
1 -> artifactFileForExecutable(modules.first())
else -> multiModuleArtifactFile(modules, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2022 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.blackboxtest.support.compilation
import org.jetbrains.kotlin.konan.blackboxtest.support.LoggedData
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
internal sealed interface TestCompilationResult<A : TestCompilationArtifact> {
sealed interface ImmediateResult<A : TestCompilationArtifact> : TestCompilationResult<A> {
val loggedData: LoggedData
}
sealed interface Failure : ImmediateResult<Nothing>
data class Success<A : TestCompilationArtifact>(val resultingArtifact: A, override val loggedData: LoggedData.CompilerCall) :
ImmediateResult<A>
data class CompilerFailure(override val loggedData: LoggedData.CompilerCall) : Failure
data class UnexpectedFailure(override val loggedData: LoggedData.CompilerCallUnexpectedFailure) : Failure
data class DependencyFailures(val causes: Set<Failure>) : TestCompilationResult<Nothing>
companion object {
fun <A : TestCompilationArtifact> TestCompilationResult<A>.assertSuccess(): Success<A> = when (this) {
is Success -> this
is Failure -> fail { describeFailure() }
is DependencyFailures -> fail { describeDependencyFailures() }
}
private fun Failure.describeFailure() = loggedData.withErrorMessage(
when (this@describeFailure) {
is CompilerFailure -> "Compilation failed."
is UnexpectedFailure -> "Compilation failed with unexpected exception."
}
)
private fun DependencyFailures.describeDependencyFailures() =
buildString {
appendLine("Compilation aborted due to errors in dependency compilations (${causes.size} items). See details below.")
appendLine()
causes.forEachIndexed { index, cause ->
append("#").append(index + 1).append(". ")
appendLine(cause.describeFailure())
}
}
}
}
@@ -11,8 +11,9 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.Executable
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationFactory
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccessfullyCompiled
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunners.extractTestNames
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
@@ -32,7 +33,7 @@ internal class TestRunProvider(
private val testCaseGroupProvider: TestCaseGroupProvider
) : BaseTestRunProvider(), ExtensionContext.Store.CloseableResource {
private val compilationFactory = TestCompilationFactory()
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation<Executable>>()
private val cachedTestNames = ThreadSafeCache<TestCompilationCacheKey, Collection<TestName>>()
/**
@@ -155,8 +156,8 @@ internal class TestRunProvider(
}
}
val (executableFile, loggedCompilerCall) = testCompilation.result.assertSuccessfullyCompiled() // <-- Compilation happens here.
val executable = TestExecutable(executableFile, loggedCompilerCall)
val (artifact, loggedCompilerCall) = testCompilation.result.assertSuccess() // <-- Compilation happens here.
val executable = TestExecutable(artifact.file, loggedCompilerCall)
return action(testCase, executable, cacheKey)
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2022 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.blackboxtest.support.util
internal class ArgsBuilder {
private val args = mutableListOf<String>()
fun add(vararg args: String) {
this.args += args
}
fun add(args: Iterable<String>) {
this.args += args
}
inline fun <T> add(rawArgs: Iterable<T>, transform: (T) -> String) {
rawArgs.mapTo(args) { transform(it) }
}
inline fun <T> addFlattened(rawArgs: Iterable<T>, transform: (T) -> Iterable<String>) {
rawArgs.flatMapTo(args) { transform(it) }
}
inline fun <T, R> addFlattenedTwice(rawArgs: Iterable<T>, transform1: (T) -> Iterable<R>, transform2: (R) -> String) {
rawArgs.forEach { add(transform1(it), transform2) }
}
fun build(): Array<String> = args.toTypedArray()
}
internal inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array<String> {
return ArgsBuilder().apply(builderAction).build()
}