From 0bc6885b2c25347417f29ab72a1e7378044e0be5 Mon Sep 17 00:00:00 2001 From: Dmitriy Dolovov Date: Fri, 18 Feb 2022 11:29:27 +0300 Subject: [PATCH] [Native][tests] W/A: Generate test suites for included KLIB with tests Relates to KT-51375 --- .../blackboxtest/AbstractNativeKlibABITest.kt | 1 + .../support/compilation/TestCompilation.kt | 169 +++++++++++------- .../compilation/TestCompilationFactory.kt | 76 ++++++-- 3 files changed, 167 insertions(+), 79 deletions(-) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt index 1ad02f8e4be..e3a611036b2 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt @@ -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() ) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt index 2b6f6cc96b5..ec126f6a227 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt @@ -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( private val classLoader: KotlinNativeClassLoader, private val optimizationMode: OptimizationMode, protected val freeCompilerArgs: TestCompilerArgs, - protected val dependencies: Iterable>, + protected val dependencies: CategorizedDependencies, protected val expectedArtifact: A ) : TestCompilation() { protected abstract val sourceModules: Collection // Runs the compiler and memorizes the result on property access. final override val result: TestCompilationResult 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( addFlattenedTwice(sourceModules, { it.files }) { it.location.path } } + protected open fun postCompileCheck() = Unit + private fun doCompile(): TestCompilationResult.ImmediateResult { val compilerArgs = buildArgs { applyCommonArgs() @@ -100,39 +105,10 @@ internal abstract class BasicCompilation( expectedArtifact.logFile.writeText(loggedCompilerCall.toString()) + postCompileCheck() + return result } - - companion object { - private fun Iterable>.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 > Iterable>.collectArtifacts(): List { - 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>.cachedLibraries: List - get() = collectArtifacts() - - @JvmStatic - protected val Iterable.uniqueCacheDirs: Set - get() = mapToSet { (libraryCacheDir, _) -> libraryCacheDir } // Avoid repeating the same directory more than once. - } } internal abstract class SourceBasedCompilation( @@ -146,7 +122,7 @@ internal abstract class SourceBasedCompilation( private val gcScheduler: GCScheduler, freeCompilerArgs: TestCompilerArgs, override val sourceModules: Collection, - dependencies: Iterable>, + dependencies: CategorizedDependencies, expectedArtifact: A ) : BasicCompilation( targets = targets, @@ -172,12 +148,6 @@ internal abstract class SourceBasedCompilation( } add(dependencies.includedLibraries) { include -> "-Xinclude=${include.path}" } } - - companion object { - private val Iterable>.libraries get() = collectArtifacts() - private val Iterable>.friends get() = collectArtifacts() - private val Iterable>.includedLibraries get() = collectArtifacts() - } } 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>, expectedArtifact: KLIBStaticCache ) : BasicCompilation( @@ -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() 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>.libraryToCache: KLIB - get() { - val libraries = collectArtifacts>() - 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>) { + val failures: Set 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 by lazy { uncategorizedDependencies.collectArtifacts() } + val friends: List by lazy { uncategorizedDependencies.collectArtifacts() } + val includedLibraries: List by lazy { uncategorizedDependencies.collectArtifacts() } + + val cachedLibraries: List by lazy { uncategorizedDependencies.collectArtifacts() } + + val libraryToCache: KLIB by lazy { + val libraries = uncategorizedDependencies.collectArtifacts>() + libraries.singleOrNull() + ?: fail { "Only one library is expected as input for ${StaticCacheCompilation::class.java}, found: $libraries" } + } + + val uniqueCacheDirs: Set by lazy { + cachedLibraries.mapToSet { (libraryCacheDir, _) -> libraryCacheDir } // Avoid repeating the same directory more than once. + } + + private inline fun > Iterable>.collectArtifacts(): List { + 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 } } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt index 3479c810019..e6443cbb79a 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt @@ -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, val staticCache: TestCompilation?) - private data class SourceBasedCompilationDependencies( + private data class CompilationDependencies( private val klibDependencies: List>, private val staticCacheDependencies: List> ) { @@ -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().staticCacheRequiredForEveryLibrary) Yes.Regular else No + + fun decideForIncludedKlib(settings: Settings, expectedExecutableArtifact: Executable, extras: Extras): ProduceStaticCache = + if (!settings.get().staticCacheRequiredForEveryLibrary) + No + else + when (extras) { + is NoTestRunnerExtras -> Yes.Regular + is WithTestRunnerExtras -> Yes.ForIncludedKlibWithTests( + StaticCacheCompilation.Options.ForIncludedLibraryWithTests(expectedExecutableArtifact, extras) + ) + } + } + } + fun testCasesToExecutable(testCases: Collection, settings: Settings): TestCompilation { 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, freeCompilerArgs: TestCompilerArgs, settings: Settings): KlibCompilations { + private fun modulesToKlib( + sourceModules: Set, + 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().staticCacheRequiredForEveryLibrary) - KLIBStaticCache(cacheDir = klibArtifact.cacheDirForStaticCache(), klib = klibArtifact) - else - null // No artifact means no static cache should be compiled. + val staticCacheArtifactAndOptions: Pair? = 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, freeCompilerArgs: TestCompilerArgs, settings: Settings - ): SourceBasedCompilationDependencies { + ): CompilationDependencies { val klibDependencies = mutableListOf>() val staticCacheDependencies = mutableListOf>() + val produceStaticCache = ProduceStaticCache.decideForRegularKlib(settings) + fun > Set.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 {