[Native][tests] W/A: Generate test suites for included KLIB with tests

Relates to KT-51375
This commit is contained in:
Dmitriy Dolovov
2022-02-18 11:29:27 +03:00
parent f635e450df
commit 0bc6885b2c
3 changed files with 167 additions and 79 deletions
@@ -97,6 +97,7 @@ abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
val compilation = StaticCacheCompilation(
settings = testRunSettings,
freeCompilerArgs = COMPILER_ARGS_FOR_STATIC_CACHE_AND_EXECUTABLE,
options = StaticCacheCompilation.Options.Regular,
dependencies = createLibraryCacheDependencies(moduleDependencies) + klibArtifact.toDependency(),
expectedArtifact = klibArtifact.toStaticCacheArtifact()
)
@@ -8,6 +8,8 @@ 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.ExecutableCompilation.Companion.applyTestRunnerSpecificArgs
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.ExecutableCompilation.Companion.assertTestDumpFileNotEmptyIfExists
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationArtifact.*
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencyType.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
@@ -16,6 +18,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.util.buildArgs
import org.jetbrains.kotlin.konan.blackboxtest.support.util.flatMapToSet
import org.jetbrains.kotlin.konan.blackboxtest.support.util.mapToSet
import org.jetbrains.kotlin.konan.properties.resolvablePropertyList
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.File
@@ -29,14 +32,14 @@ internal abstract class BasicCompilation<A : TestCompilationArtifact>(
private val classLoader: KotlinNativeClassLoader,
private val optimizationMode: OptimizationMode,
protected val freeCompilerArgs: TestCompilerArgs,
protected val dependencies: Iterable<TestCompilationDependency<*>>,
protected val dependencies: CategorizedDependencies,
protected val expectedArtifact: A
) : TestCompilation<A>() {
protected abstract val sourceModules: Collection<TestModule>
// Runs the compiler and memorizes the result on property access.
final override val result: TestCompilationResult<out A> by lazy {
val failures = dependencies.collectFailures()
val failures = dependencies.failures
if (failures.isNotEmpty())
TestCompilationResult.DependencyFailures(causes = failures)
else
@@ -65,6 +68,8 @@ internal abstract class BasicCompilation<A : TestCompilationArtifact>(
addFlattenedTwice(sourceModules, { it.files }) { it.location.path }
}
protected open fun postCompileCheck() = Unit
private fun doCompile(): TestCompilationResult.ImmediateResult<out A> {
val compilerArgs = buildArgs {
applyCommonArgs()
@@ -100,39 +105,10 @@ internal abstract class BasicCompilation<A : TestCompilationArtifact>(
expectedArtifact.logFile.writeText(loggedCompilerCall.toString())
postCompileCheck()
return result
}
companion object {
private fun Iterable<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()
}
}
@JvmStatic
protected inline fun <reified A : TestCompilationArtifact, reified T : TestCompilationDependencyType<A>> Iterable<TestCompilationDependency<*>>.collectArtifacts(): List<A> {
val concreteDependencyType = T::class.objectInstance
val dependencyTypeMatcher: (TestCompilationDependencyType<*>) -> Boolean = if (concreteDependencyType != null) {
{ it == concreteDependencyType }
} else {
{ it.canYield(A::class.java) }
}
return mapNotNull { dependency -> if (dependencyTypeMatcher(dependency.type)) dependency.artifact as A else null }
}
@JvmStatic
protected val Iterable<TestCompilationDependency<*>>.cachedLibraries: List<KLIBStaticCache>
get() = collectArtifacts<KLIBStaticCache, LibraryStaticCache>()
@JvmStatic
protected val Iterable<KLIBStaticCache>.uniqueCacheDirs: Set<File>
get() = mapToSet { (libraryCacheDir, _) -> libraryCacheDir } // Avoid repeating the same directory more than once.
}
}
internal abstract class SourceBasedCompilation<A : TestCompilationArtifact>(
@@ -146,7 +122,7 @@ internal abstract class SourceBasedCompilation<A : TestCompilationArtifact>(
private val gcScheduler: GCScheduler,
freeCompilerArgs: TestCompilerArgs,
override val sourceModules: Collection<TestModule>,
dependencies: Iterable<TestCompilationDependency<*>>,
dependencies: CategorizedDependencies,
expectedArtifact: A
) : BasicCompilation<A>(
targets = targets,
@@ -172,12 +148,6 @@ internal abstract class SourceBasedCompilation<A : TestCompilationArtifact>(
}
add(dependencies.includedLibraries) { include -> "-Xinclude=${include.path}" }
}
companion object {
private val Iterable<TestCompilationDependency<*>>.libraries get() = collectArtifacts<KLIB, Library>()
private val Iterable<TestCompilationDependency<*>>.friends get() = collectArtifacts<KLIB, FriendLibrary>()
private val Iterable<TestCompilationDependency<*>>.includedLibraries get() = collectArtifacts<KLIB, IncludedLibrary>()
}
}
internal class LibraryCompilation(
@@ -197,7 +167,7 @@ internal class LibraryCompilation(
gcScheduler = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
dependencies = CategorizedDependencies(dependencies),
expectedArtifact = expectedArtifact
) {
override fun applySpecificArgs(argsBuilder: ArgsBuilder) = with(argsBuilder) {
@@ -227,7 +197,7 @@ internal class ExecutableCompilation(
gcScheduler = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
dependencies = CategorizedDependencies(dependencies),
expectedArtifact = expectedArtifact
) {
private val cacheMode: CacheMode = settings.get()
@@ -240,13 +210,20 @@ internal class ExecutableCompilation(
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"
val testDumpFile: File? = if (sourceModules.isEmpty()
&& dependencies.includedLibraries.isNotEmpty()
&& cacheMode.staticCacheRequiredForEveryLibrary
) {
// If there are no source modules passed to the compiler, but there is an included library with the static cache, then
// this should be two-stage test mode: Test functions are already stored in the included library, and they should
// already have been dumped during generation of library's static cache.
null // No, don't need to dump tests.
} else {
expectedArtifact.testDumpFile // Yes, need to dump tests.
}
add(testRunnerArg)
add("-Xdump-tests-to=${expectedArtifact.testDumpFile}")
applyTestRunnerSpecificArgs(extras, testDumpFile)
}
}
super.applySpecificArgs(argsBuilder)
@@ -255,13 +232,38 @@ internal class ExecutableCompilation(
override fun applyDependencies(argsBuilder: ArgsBuilder): Unit = with(argsBuilder) {
super.applyDependencies(argsBuilder)
cacheMode.staticCacheRootDir?.let { cacheRootDir -> add("-Xcache-directory=$cacheRootDir") }
add(dependencies.cachedLibraries.uniqueCacheDirs) { libraryCacheDir -> "-Xcache-directory=${libraryCacheDir.path}" }
add(dependencies.uniqueCacheDirs) { libraryCacheDir -> "-Xcache-directory=${libraryCacheDir.path}" }
}
override fun postCompileCheck() {
expectedArtifact.assertTestDumpFileNotEmptyIfExists()
}
companion object {
internal fun ArgsBuilder.applyTestRunnerSpecificArgs(extras: WithTestRunnerExtras, testDumpFile: File?) {
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)
testDumpFile?.let { add("-Xdump-tests-to=$it") }
}
internal fun Executable.assertTestDumpFileNotEmptyIfExists() {
if (testDumpFile.exists()) {
testDumpFile.useLines { lines ->
assertTrue(lines.filter(String::isNotBlank).any()) { "Test dump file is empty: $testDumpFile" }
}
}
}
}
}
internal class StaticCacheCompilation(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
private val options: Options,
dependencies: Iterable<TestCompilationDependency<*>>,
expectedArtifact: KLIBStaticCache
) : BasicCompilation<KLIBStaticCache>(
@@ -270,9 +272,14 @@ internal class StaticCacheCompilation(
classLoader = settings.get(),
optimizationMode = settings.get(),
freeCompilerArgs = freeCompilerArgs,
dependencies = dependencies,
dependencies = CategorizedDependencies(dependencies),
expectedArtifact = expectedArtifact
) {
sealed interface Options {
object Regular : Options
class ForIncludedLibraryWithTests(val expectedExecutableArtifact: Executable, val extras: WithTestRunnerExtras) : Options
}
override val sourceModules get() = emptyList<TestModule>()
private val cacheRootDir: File = run {
@@ -283,6 +290,14 @@ internal class StaticCacheCompilation(
override fun applySpecificArgs(argsBuilder: ArgsBuilder): Unit = with(argsBuilder) {
add("-produce", "static_cache")
when (options) {
is Options.Regular -> Unit /* Nothing to do. */
is Options.ForIncludedLibraryWithTests -> {
applyTestRunnerSpecificArgs(options.extras, options.expectedExecutableArtifact.testDumpFile)
add("-Xinclude=${dependencies.libraryToCache.path}")
}
}
// The following line adds "-Xembed-bitcode-marker" for certain iOS device targets:
add(home.properties.resolvablePropertyList("additionalCacheFlags", targets.testTarget.visibleName))
add(
@@ -293,17 +308,51 @@ internal class StaticCacheCompilation(
}
override fun applyDependencies(argsBuilder: ArgsBuilder): Unit = with(argsBuilder) {
val cachedLibraries = dependencies.cachedLibraries
addFlattened(cachedLibraries) { (_, library) -> listOf("-l", library.path) }
add(cachedLibraries.uniqueCacheDirs) { libraryCacheDir -> "-Xcache-directory=${libraryCacheDir.path}" }
addFlattened(dependencies.cachedLibraries) { (_, library) -> listOf("-l", library.path) }
add(dependencies.uniqueCacheDirs) { libraryCacheDir -> "-Xcache-directory=${libraryCacheDir.path}" }
}
companion object {
private val Iterable<TestCompilationDependency<*>>.libraryToCache: KLIB
get() {
val libraries = collectArtifacts<KLIB, TestCompilationDependencyType<KLIB>>()
return libraries.singleOrNull()
?: fail { "Only one library is expected as input for ${StaticCacheCompilation::class.java}, found: $libraries" }
}
override fun postCompileCheck() {
(options as? Options.ForIncludedLibraryWithTests)?.expectedExecutableArtifact?.assertTestDumpFileNotEmptyIfExists()
}
}
internal class CategorizedDependencies(uncategorizedDependencies: Iterable<TestCompilationDependency<*>>) {
val failures: Set<TestCompilationResult.Failure> by lazy {
uncategorizedDependencies.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()
}
}
}
val libraries: List<KLIB> by lazy { uncategorizedDependencies.collectArtifacts<KLIB, Library>() }
val friends: List<KLIB> by lazy { uncategorizedDependencies.collectArtifacts<KLIB, FriendLibrary>() }
val includedLibraries: List<KLIB> by lazy { uncategorizedDependencies.collectArtifacts<KLIB, IncludedLibrary>() }
val cachedLibraries: List<KLIBStaticCache> by lazy { uncategorizedDependencies.collectArtifacts<KLIBStaticCache, LibraryStaticCache>() }
val libraryToCache: KLIB by lazy {
val libraries = uncategorizedDependencies.collectArtifacts<KLIB, TestCompilationDependencyType<KLIB>>()
libraries.singleOrNull<KLIB>()
?: fail { "Only one library is expected as input for ${StaticCacheCompilation::class.java}, found: $libraries" }
}
val uniqueCacheDirs: Set<File> by lazy {
cachedLibraries.mapToSet { (libraryCacheDir, _) -> libraryCacheDir } // Avoid repeating the same directory more than once.
}
private inline fun <reified A : TestCompilationArtifact, reified T : TestCompilationDependencyType<A>> Iterable<TestCompilationDependency<*>>.collectArtifacts(): List<A> {
val concreteDependencyType = T::class.objectInstance
val dependencyTypeMatcher: (TestCompilationDependencyType<*>) -> Boolean = if (concreteDependencyType != null) {
{ it == concreteDependencyType }
} else {
{ it.canYield(A::class.java) }
}
return mapNotNull { dependency -> if (dependencyTypeMatcher(dependency.type)) dependency.artifact as A else null }
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.compilation
import org.jetbrains.kotlin.konan.blackboxtest.support.PackageName
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.*
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
@@ -29,7 +30,7 @@ internal class TestCompilationFactory {
// 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 SourceBasedCompilationDependencies(
private data class CompilationDependencies(
private val klibDependencies: List<CompiledDependency<KLIB>>,
private val staticCacheDependencies: List<CompiledDependency<KLIBStaticCache>>
) {
@@ -52,6 +53,31 @@ internal class TestCompilationFactory {
(klibDependencies.asSequence() + staticCacheDependencies + listOfNotNull(includedKlib, includedKlibStaticCache)).asIterable()
}
private sealed interface ProduceStaticCache {
object No : ProduceStaticCache
sealed class Yes(val options: StaticCacheCompilation.Options) : ProduceStaticCache {
object Regular : Yes(StaticCacheCompilation.Options.Regular)
class ForIncludedKlibWithTests(options: StaticCacheCompilation.Options.ForIncludedLibraryWithTests) : Yes(options)
}
companion object {
fun decideForRegularKlib(settings: Settings): ProduceStaticCache =
if (settings.get<CacheMode>().staticCacheRequiredForEveryLibrary) Yes.Regular else No
fun decideForIncludedKlib(settings: Settings, expectedExecutableArtifact: Executable, extras: Extras): ProduceStaticCache =
if (!settings.get<CacheMode>().staticCacheRequiredForEveryLibrary)
No
else
when (extras) {
is NoTestRunnerExtras -> Yes.Regular
is WithTestRunnerExtras -> Yes.ForIncludedKlibWithTests(
StaticCacheCompilation.Options.ForIncludedLibraryWithTests(expectedExecutableArtifact, extras)
)
}
}
}
fun testCasesToExecutable(testCases: Collection<TestCase>, settings: Settings): TestCompilation<Executable> {
val rootModules = testCases.flatMapToSet { testCase -> testCase.rootModules }
val cacheKey = ExecutableCacheKey(rootModules)
@@ -75,7 +101,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, settings)
val produceStaticCache = ProduceStaticCache.decideForIncludedKlib(settings, executableArtifact, extras)
val klibCompilations = modulesToKlib(rootModules, freeCompilerArgs, produceStaticCache, settings)
sourceModulesToCompileExecutable = emptySet() // No sources.
@@ -99,7 +126,12 @@ internal class TestCompilationFactory {
}
}
private fun modulesToKlib(sourceModules: Set<TestModule>, freeCompilerArgs: TestCompilerArgs, settings: Settings): KlibCompilations {
private fun modulesToKlib(
sourceModules: Set<TestModule>,
freeCompilerArgs: TestCompilerArgs,
produceStaticCache: ProduceStaticCache,
settings: Settings
): KlibCompilations {
val cacheKey = KlibCacheKey(sourceModules, freeCompilerArgs)
// Fast pass.
@@ -109,10 +141,13 @@ internal class TestCompilationFactory {
val dependencies = collectDependencies(sourceModules, freeCompilerArgs, settings)
val klibArtifact = KLIB(settings.artifactFileForKlib(sourceModules, freeCompilerArgs))
val staticCacheArtifact: KLIBStaticCache? = if (settings.get<CacheMode>().staticCacheRequiredForEveryLibrary)
KLIBStaticCache(cacheDir = klibArtifact.cacheDirForStaticCache(), klib = klibArtifact)
else
null // No artifact means no static cache should be compiled.
val staticCacheArtifactAndOptions: Pair<KLIBStaticCache, StaticCacheCompilation.Options>? = when (produceStaticCache) {
is ProduceStaticCache.No -> null // No artifact means no static cache should be compiled.
is ProduceStaticCache.Yes -> KLIBStaticCache(
cacheDir = klibArtifact.cacheDirForStaticCache(),
klib = klibArtifact
) to produceStaticCache.options
}
return cachedKlibCompilations.computeIfAbsent(cacheKey) {
val klibCompilation = LibraryCompilation(
@@ -123,15 +158,16 @@ internal class TestCompilationFactory {
expectedArtifact = klibArtifact
)
val staticCacheCompilation: StaticCacheCompilation? = if (staticCacheArtifact != null) {
StaticCacheCompilation(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
dependencies = dependencies.forStaticCache(klibCompilation.asKlibDependency(type = /* does not matter in fact*/ Library)),
expectedArtifact = staticCacheArtifact
)
} else
null
val staticCacheCompilation: StaticCacheCompilation? =
staticCacheArtifactAndOptions?.let { (staticCacheArtifact, staticCacheOptions) ->
StaticCacheCompilation(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
options = staticCacheOptions,
dependencies = dependencies.forStaticCache(klibCompilation.asKlibDependency(type = /* does not matter in fact*/ Library)),
expectedArtifact = staticCacheArtifact
)
}
KlibCompilations(klibCompilation, staticCacheCompilation)
}
@@ -141,13 +177,15 @@ internal class TestCompilationFactory {
sourceModules: Set<TestModule>,
freeCompilerArgs: TestCompilerArgs,
settings: Settings
): SourceBasedCompilationDependencies {
): CompilationDependencies {
val klibDependencies = mutableListOf<CompiledDependency<KLIB>>()
val staticCacheDependencies = mutableListOf<CompiledDependency<KLIBStaticCache>>()
val produceStaticCache = ProduceStaticCache.decideForRegularKlib(settings)
fun <T : TestCompilationDependencyType<KLIB>> Set<TestModule>.collectDependencies(type: T) =
forEach { dependencyModule: TestModule ->
val klibCompilations = modulesToKlib(setOf(dependencyModule), freeCompilerArgs, settings)
val klibCompilations = modulesToKlib(setOf(dependencyModule), freeCompilerArgs, produceStaticCache, settings)
klibDependencies += klibCompilations.klib.asKlibDependency(type)
if (type == Library || type == IncludedLibrary)
@@ -157,7 +195,7 @@ internal class TestCompilationFactory {
sourceModules.allDependencies().collectDependencies(Library)
sourceModules.allFriends().collectDependencies(FriendLibrary)
return SourceBasedCompilationDependencies(klibDependencies, staticCacheDependencies)
return CompilationDependencies(klibDependencies, staticCacheDependencies)
}
companion object {