[Native][tests] Refactoring: Rework Settings to add more flexibility

This commit is contained in:
Dmitriy Dolovov
2021-11-26 12:13:46 +03:00
parent 30e51452b4
commit 6d52d21631
11 changed files with 162 additions and 98 deletions
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5
import org.jetbrains.kotlin.generators.model.annotation
import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest
import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseExtTestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseStandardTestCaseGroupProvider
import org.jetbrains.kotlin.test.TargetBackend
fun main() {
@@ -28,7 +29,9 @@ fun main() {
// Samples (how to utilize the abilities of new test infrastructure).
testGroup("native/native.tests/tests-gen", "native/native.tests/testData") {
testClass<AbstractNativeBlackBoxTest> {
testClass<AbstractNativeBlackBoxTest>(
annotations = listOf(annotation(UseStandardTestCaseGroupProvider::class.java))
) {
model("samples")
model("samples2")
}
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.konan.blackboxtest.support
import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GlobalSettings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
@@ -48,39 +50,21 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
val enclosingTestClass = enclosingTestClass
return root.getStore(NAMESPACE).getOrComputeIfAbsent(enclosingTestClass.sanitizedName) {
val globalSettings = getOrCreateGlobalSettings()
val (testSettings, testSettingsAnnotation) = computeTestSettings(enclosingTestClass)
val requiredSettings: Set<KClass<*>> =
/* Always required */ setOf(GlobalSettings::class, Binaries::class) +/* Custom */ testSettings.requiredSettings
val testRoots = computeTestRoots(enclosingTestClass)
val globalSettings = getOrCreateGlobalSettings()
val uniqueEnclosingClassDirName = globalSettings.target.compressedName + "_" + enclosingTestClass.compressedSimpleName
val testSourcesDir = globalSettings.baseBuildDir
.resolve("bbtest.src")
.resolve(uniqueEnclosingClassDirName)
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources.
val sharedSourcesDir = testSourcesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
val testBinariesDir = globalSettings.baseBuildDir
.resolve("bbtest.bin")
.resolve(uniqueEnclosingClassDirName)
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
val sharedBinariesDir = testBinariesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
val settings = Settings(
global = globalSettings,
testRoots = testRoots,
testSourcesDir = testSourcesDir,
sharedSourcesDir = sharedSourcesDir,
testBinariesDir = testBinariesDir,
sharedBinariesDir = sharedBinariesDir
)
val settings = Settings(requiredSettings.map { clazz ->
when (clazz) {
GlobalSettings::class -> globalSettings
TestRoots::class -> computeTestRoots(enclosingTestClass)
GeneratedSources::class -> computeGeneratedSourceDirs(globalSettings, enclosingTestClass)
Binaries::class -> computeBinariesDirs(globalSettings, enclosingTestClass)
else -> fail { "Unknown test setting type: $clazz" }
}
})
val testCaseGroupProvider = createTestCaseGroupProvider(settings, testSettings, testSettingsAnnotation)
@@ -88,17 +72,30 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
}.cast()
}
private val ExtensionContext.enclosingTestInstance: AbstractNativeBlackBoxTest
get() = requiredTestInstances.allInstances.firstOrNull().cast()
private val ExtensionContext.enclosingTestClass: Class<*>
get() = generateSequence(requiredTestClass) { it.enclosingClass }.last()
private fun ExtensionContext.getOrCreateGlobalSettings(): GlobalSettings =
root.getStore(NAMESPACE).getOrComputeIfAbsent(GlobalSettings::class.java.sanitizedName) {
// Create with the default settings.
GlobalSettings()
}.cast()
private val ExtensionContext.enclosingTestInstance: AbstractNativeBlackBoxTest
get() = requiredTestInstances.allInstances.firstOrNull().cast()
private fun computeTestSettings(enclosingTestClass: Class<*>): Pair<TestSettings, Annotation?> {
val findTestSettings: Class<*>.() -> Pair<TestSettings, Annotation>? = {
annotations.asSequence().mapNotNull { annotation ->
val testSettings = annotation.annotationClass.findAnnotation<TestSettings>() ?: return@mapNotNull null
testSettings to annotation
}.firstOrNull()
}
private val ExtensionContext.enclosingTestClass: Class<*>
get() = generateSequence(requiredTestClass) { it.enclosingClass }.last()
return enclosingTestClass.findTestSettings()
?: enclosingTestClass.declaredClasses.firstNotNullOfOrNull { it.findTestSettings() }
?: fail { "No @${TestSettings::class.simpleName} annotation found on test classes" }
}
private fun computeTestRoots(enclosingTestClass: Class<*>): TestRoots {
val testRoots: Set<File> = when (val outermostTestMetadata = enclosingTestClass.getAnnotation(TestMetadata::class.java)) {
@@ -126,17 +123,30 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
return TestRoots(testRoots, baseDir)
}
private fun computeTestSettings(enclosingTestClass: Class<*>): Pair<TestSettings, Annotation?> {
val findTestSettings: Class<*>.() -> Pair<TestSettings, Annotation>? = {
annotations.asSequence().mapNotNull { annotation ->
val testSettings = annotation.annotationClass.findAnnotation<TestSettings>() ?: return@mapNotNull null
testSettings to annotation
}.firstOrNull()
}
private fun computeGeneratedSourceDirs(globalSettings: GlobalSettings, enclosingTestClass: Class<*>): GeneratedSources {
val testSourcesDir = globalSettings.baseBuildDir
.resolve("bbtest.src")
.resolve("${globalSettings.target.compressedName}_${enclosingTestClass.compressedSimpleName}")
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources.
return enclosingTestClass.findTestSettings()
?: enclosingTestClass.declaredClasses.firstNotNullOfOrNull { it.findTestSettings() }
?: (TestSettings.DEFAULT to null) // Fallback if there is no annotation.
val sharedSourcesDir = testSourcesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
return GeneratedSources(testSourcesDir, sharedSourcesDir)
}
private fun computeBinariesDirs(globalSettings: GlobalSettings, enclosingTestClass: Class<*>): Binaries {
val testBinariesDir = globalSettings.baseBuildDir
.resolve("bbtest.bin")
.resolve("${globalSettings.target.compressedName}_${enclosingTestClass.compressedSimpleName}")
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
val sharedBinariesDir = testBinariesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
return Binaries(testBinariesDir, sharedBinariesDir)
}
private fun createTestCaseGroupProvider(
@@ -15,6 +15,8 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilation.Companion
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilationResult.Companion.assertSuccess
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.settings.Binaries
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GlobalSettings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
@@ -55,7 +57,7 @@ internal class TestCompilationFactory(private val settings: Settings) {
specificCompilerArgs = {
add("-produce", "program")
if (entryPoint != null) add("-entry", entryPoint) else add("-generate-test-runner")
settings.global.getRootCacheDirectory(debuggable = true)?.let { rootCacheDir ->
settings.get<GlobalSettings>().getRootCacheDirectory(debuggable = true)?.let { rootCacheDir ->
add("-Xcache-directory=$rootCacheDir")
}
}
@@ -88,15 +90,15 @@ internal class TestCompilationFactory(private val settings: Settings) {
private fun artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
1 -> artifactFileForExecutable(modules.first())
else -> multiModuleArtifactFile(modules, settings.global.target.family.exeSuffix)
else -> multiModuleArtifactFile(modules, settings.get<GlobalSettings>().target.family.exeSuffix)
}
private fun artifactFileForExecutable(module: TestModule.Exclusive) =
singleModuleArtifactFile(module, settings.global.target.family.exeSuffix)
singleModuleArtifactFile(module, settings.get<GlobalSettings>().target.family.exeSuffix)
private fun artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) {
is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib")
is TestModule.Shared -> settings.sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
is TestModule.Shared -> settings.get<Binaries>().sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
}
private fun singleModuleArtifactFile(module: TestModule.Exclusive, extension: String): File {
@@ -142,7 +144,7 @@ internal class TestCompilationFactory(private val settings: Settings) {
}
private fun artifactDirForPackageName(packageName: PackageFQN?): File {
val baseDir = settings.testBinariesDir
val baseDir = settings.get<Binaries>().testBinariesDir
val outputDir = if (packageName != null) baseDir.resolve(packageName.compressedPackageName) else baseDir
outputDir.mkdirs()
@@ -242,8 +244,8 @@ private class TestCompilationImpl(
add(
"-enable-assertions",
"-g",
"-target", settings.global.target.name,
"-repo", settings.global.kotlinNativeHome.resolve("klib").path,
"-target", settings.get<GlobalSettings>().target.name,
"-repo", settings.get<GlobalSettings>().kotlinNativeHome.resolve("klib").path,
"-output", expectedArtifactFile.path,
"-Xskip-prerelease-check",
"-Xverify-ir",
@@ -274,7 +276,7 @@ private class TestCompilationImpl(
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
compilerArgs = compilerArgs,
lazyKotlinNativeClassLoader = settings.global.lazyKotlinNativeClassLoader
lazyKotlinNativeClassLoader = settings.get<GlobalSettings>().lazyKotlinNativeClassLoader
)
val loggedCompilerCall =
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilationResult.Com
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestRunner
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GlobalSettings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeCache
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TreeNode
@@ -174,14 +175,16 @@ internal class TestRunProvider(
}
// Currently, only local test runner is supported.
fun createRunner(testRun: TestRun): AbstractRunner<*> = when (val target = settings.global.target) {
settings.global.hostTarget -> LocalTestRunner(testRun, settings.global.executionTimeout)
else -> fail {
"""
Running at non-host target is not supported yet.
Compilation target: $target
Host target: ${settings.global.hostTarget}
""".trimIndent()
fun createRunner(testRun: TestRun): AbstractRunner<*> = with(settings.get<GlobalSettings>()) {
when (val target = target) {
hostTarget -> LocalTestRunner(testRun, executionTimeout)
else -> fail {
"""
Running at non-host target is not supported yet.
Compilation target: $target
Host target: $hostTarget
""".trimIndent()
}
}
}
@@ -19,7 +19,9 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
@@ -155,12 +157,12 @@ private class ExtTestDataFile(
optInsForCompiler = optInsForCompiler,
expectActualLinker = EXPECT_ACTUAL_LINKER_DIRECTIVE in structure.directives,
generatedSourcesDir = computeGeneratedSourcesDir(
testDataBaseDir = settings.testRoots.baseDir,
testDataBaseDir = settings.get<TestRoots>().baseDir,
testDataFile = testDataFile,
generatedSourcesBaseDir = settings.testSourcesDir
generatedSourcesBaseDir = settings.get<GeneratedSources>().testSourcesDir
),
nominalPackageName = computePackageName(
testDataBaseDir = settings.testRoots.baseDir,
testDataBaseDir = settings.get<TestRoots>().baseDir,
testDataFile = testDataFile
)
)
@@ -578,7 +580,7 @@ private class ExtTestDataFile(
testCaseDir = testDataFileSettings.generatedSourcesDir,
findOrGenerateSharedModule = { moduleName: String, generator: SharedModuleGenerator ->
sharedModules.computeIfAbsent(moduleName) {
generator(settings.sharedSourcesDir)
generator(settings.get<GeneratedSources>().sharedSourcesDir)
}
}
)
@@ -8,7 +8,9 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group
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.settings.GeneratedSources
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
import org.jetbrains.kotlin.konan.blackboxtest.support.util.DEFAULT_FILE_NAME
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeFactory
import org.jetbrains.kotlin.konan.blackboxtest.support.util.computeGeneratedSourcesDir
@@ -52,13 +54,13 @@ internal class StandardTestCaseGroupProvider(private val settings: Settings) : T
private fun createTestCase(testDataFile: File): TestCase {
val generatedSourcesDir = computeGeneratedSourcesDir(
testDataBaseDir = settings.testRoots.baseDir,
testDataBaseDir = settings.get<TestRoots>().baseDir,
testDataFile = testDataFile,
generatedSourcesBaseDir = settings.testSourcesDir
generatedSourcesBaseDir = settings.get<GeneratedSources>().testSourcesDir
)
val nominalPackageName = computePackageName(
testDataBaseDir = settings.testRoots.baseDir,
testDataBaseDir = settings.get<TestRoots>().baseDir,
testDataFile = testDataFile
)
@@ -5,8 +5,10 @@
package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestSettings
@Target(AnnotationTarget.CLASS)
@TestSettings(ExtTestCaseGroupProvider::class)
@TestSettings(providerClass = ExtTestCaseGroupProvider::class, requiredSettings = [TestRoots::class, GeneratedSources::class])
annotation class UseExtTestCaseGroupProvider
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2021 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.group
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GeneratedSources
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRoots
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestSettings
@Target(AnnotationTarget.CLASS)
@TestSettings(providerClass = StandardTestCaseGroupProvider::class, requiredSettings = [TestRoots::class, GeneratedSources::class])
annotation class UseStandardTestCaseGroupProvider
@@ -5,19 +5,42 @@
package org.jetbrains.kotlin.konan.blackboxtest.support.settings
import gnu.trove.THashMap
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestDisposable
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.File
import kotlin.reflect.KClass
internal class Settings(
val global: GlobalSettings,
val testRoots: TestRoots, // The directories with original sources (aka testData).
val testSourcesDir: File, // The directory with generated (preprocessed) test sources.
val sharedSourcesDir: File, // The directory with the sources of the shared modules (i.e. the modules that are widely used in multiple tests).
val testBinariesDir: File, // The directory with compiled test binaries (klibs) and executable files).
val sharedBinariesDir: File // The directory with compiled shared modules (klibs).
) : TestDisposable(parentDisposable = null)
internal class Settings(settings: Iterable<Any>) : TestDisposable(parentDisposable = null) {
private val map: Map<KClass<*>, Any> = THashMap<KClass<*>, Any>().apply {
settings.forEach { setting ->
val previous = put(setting::class, setting)
assertTrue(previous == null) { "Duplicated settings: ${setting::class}, $previous, $setting" }
}
internal class TestRoots(
val roots: Set<File>,
val baseDir: File
)
compact()
}
@Suppress("UNCHECKED_CAST")
fun <T : Any> get(clazz: KClass<out T>): T = map[clazz] as T? ?: fail { "No such setting: $clazz" }
inline fun <reified T : Any> get(): T = get(T::class)
}
/**
* The directories with original sources (aka testData).
*/
internal class TestRoots(val roots: Set<File>, val baseDir: File)
/**
* [testSourcesDir] - The directory with generated (preprocessed) test sources.
* [sharedSourcesDir] - The directory with the sources of the shared modules (i.e. the modules that are widely used in multiple tests).
*/
internal class GeneratedSources(val testSourcesDir: File, val sharedSourcesDir: File)
/**
* [testBinariesDir] - The directory with compiled test binaries (klibs and executable files).
* [sharedBinariesDir] - The directory with compiled shared modules (klibs).
*/
internal class Binaries(val testBinariesDir: File, val sharedBinariesDir: File)
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2021 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.settings;
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider;
import java.lang.annotation.*;
/**
* Has to use Java annotation here instead of Kotlin annotation because of KT-49920.
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestSettings {
Class<? extends TestCaseGroupProvider> providerClass();
Class<?>[] requiredSettings() default {};
}
@@ -1,17 +0,0 @@
/*
* Copyright 2010-2021 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.settings
import org.jetbrains.kotlin.konan.blackboxtest.support.group.StandardTestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import kotlin.reflect.KClass
@Target(AnnotationTarget.ANNOTATION_CLASS)
internal annotation class TestSettings(val providerClass: KClass<out TestCaseGroupProvider>) {
companion object {
val DEFAULT = TestSettings(StandardTestCaseGroupProvider::class)
}
}