[Native][tests] Refactoring: Rework Settings/GlobalSettings

This commit is contained in:
Dmitriy Dolovov
2021-11-25 12:53:49 +03:00
parent 3c47880eba
commit 30e51452b4
12 changed files with 178 additions and 137 deletions
@@ -6,9 +6,11 @@
package org.jetbrains.kotlin.konan.blackboxtest.support package org.jetbrains.kotlin.konan.blackboxtest.support
import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest
import org.jetbrains.kotlin.konan.blackboxtest.support.group.StandardTestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProviding 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
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestSettings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.test.TestMetadata import org.jetbrains.kotlin.test.TestMetadata
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEquals import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEquals
@@ -35,7 +37,7 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
enclosingTestInstance.onRunProviderSet() enclosingTestInstance.onRunProviderSet()
// Set the essential compiler property. // Set the essential compiler property.
System.setProperty("kotlin.native.home", getOrCreateGlobalEnvironment().kotlinNativeHome.path) System.setProperty("kotlin.native.home", getOrCreateGlobalSettings().kotlinNativeHome.path)
} }
companion object { companion object {
@@ -46,13 +48,14 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
val enclosingTestClass = enclosingTestClass val enclosingTestClass = enclosingTestClass
return root.getStore(NAMESPACE).getOrComputeIfAbsent(enclosingTestClass.sanitizedName) { return root.getStore(NAMESPACE).getOrComputeIfAbsent(enclosingTestClass.sanitizedName) {
val globalEnvironment = getOrCreateGlobalEnvironment() val globalSettings = getOrCreateGlobalSettings()
val (testSettings, testSettingsAnnotation) = computeTestSettings(enclosingTestClass)
val testRoots = computeTestRoots() val testRoots = computeTestRoots(enclosingTestClass)
val uniqueEnclosingClassDirName = globalEnvironment.target.compressedName + "_" + enclosingTestClass.compressedSimpleName val uniqueEnclosingClassDirName = globalSettings.target.compressedName + "_" + enclosingTestClass.compressedSimpleName
val testSourcesDir = globalEnvironment.baseBuildDir val testSourcesDir = globalSettings.baseBuildDir
.resolve("bbtest.src") .resolve("bbtest.src")
.resolve(uniqueEnclosingClassDirName) .resolve(uniqueEnclosingClassDirName)
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources. .ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources.
@@ -61,7 +64,7 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
.resolve("__shared_modules__") .resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory() .ensureExistsAndIsEmptyDirectory()
val testBinariesDir = globalEnvironment.baseBuildDir val testBinariesDir = globalSettings.baseBuildDir
.resolve("bbtest.bin") .resolve("bbtest.bin")
.resolve(uniqueEnclosingClassDirName) .resolve(uniqueEnclosingClassDirName)
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts. .ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
@@ -70,8 +73,8 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
.resolve("__shared_modules__") .resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory() .ensureExistsAndIsEmptyDirectory()
val environment = TestEnvironment( val settings = Settings(
globalEnvironment = globalEnvironment, global = globalSettings,
testRoots = testRoots, testRoots = testRoots,
testSourcesDir = testSourcesDir, testSourcesDir = testSourcesDir,
sharedSourcesDir = sharedSourcesDir, sharedSourcesDir = sharedSourcesDir,
@@ -79,16 +82,16 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
sharedBinariesDir = sharedBinariesDir sharedBinariesDir = sharedBinariesDir
) )
val testCaseGroupProvider = requiredTestClass.createTestCaseGroupProvider(environment) val testCaseGroupProvider = createTestCaseGroupProvider(settings, testSettings, testSettingsAnnotation)
TestRunProvider(environment, testCaseGroupProvider) TestRunProvider(settings, testCaseGroupProvider)
}.cast() }.cast()
} }
private fun ExtensionContext.getOrCreateGlobalEnvironment(): GlobalTestEnvironment = private fun ExtensionContext.getOrCreateGlobalSettings(): GlobalSettings =
root.getStore(NAMESPACE).getOrComputeIfAbsent(GlobalTestEnvironment::class.java.sanitizedName) { root.getStore(NAMESPACE).getOrComputeIfAbsent(GlobalSettings::class.java.sanitizedName) {
// Create with the default settings. // Create with the default settings.
GlobalTestEnvironment() GlobalSettings()
}.cast() }.cast()
private val ExtensionContext.enclosingTestInstance: AbstractNativeBlackBoxTest private val ExtensionContext.enclosingTestInstance: AbstractNativeBlackBoxTest
@@ -97,9 +100,7 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
private val ExtensionContext.enclosingTestClass: Class<*> private val ExtensionContext.enclosingTestClass: Class<*>
get() = generateSequence(requiredTestClass) { it.enclosingClass }.last() get() = generateSequence(requiredTestClass) { it.enclosingClass }.last()
private fun ExtensionContext.computeTestRoots(): TestRoots { private fun computeTestRoots(enclosingTestClass: Class<*>): TestRoots {
val enclosingTestClass = enclosingTestClass
val testRoots: Set<File> = when (val outermostTestMetadata = enclosingTestClass.getAnnotation(TestMetadata::class.java)) { val testRoots: Set<File> = when (val outermostTestMetadata = enclosingTestClass.getAnnotation(TestMetadata::class.java)) {
null -> { null -> {
enclosingTestClass.declaredClasses.mapNotNullToSet { nestedClass -> enclosingTestClass.declaredClasses.mapNotNullToSet { nestedClass ->
@@ -125,35 +126,48 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
return TestRoots(testRoots, baseDir) return TestRoots(testRoots, baseDir)
} }
private fun Class<*>.createTestCaseGroupProvider(environment: TestEnvironment): TestCaseGroupProvider { private fun computeTestSettings(enclosingTestClass: Class<*>): Pair<TestSettings, Annotation?> {
val (providerAnnotation, providerAnnotationClass, providerClass) = annotations.asSequence() val findTestSettings: Class<*>.() -> Pair<TestSettings, Annotation>? = {
.mapNotNull { annotation -> annotations.asSequence().mapNotNull { annotation ->
val annotationClass = annotation.annotationClass val testSettings = annotation.annotationClass.findAnnotation<TestSettings>() ?: return@mapNotNull null
val providerClass = annotationClass.findAnnotation<TestCaseGroupProviding>()?.providerClass ?: return@mapNotNull null testSettings to annotation
Triple(annotation, annotationClass, providerClass)
}.firstOrNull() }.firstOrNull()
?: return StandardTestCaseGroupProvider(environment) // Fallback if there is no annotation. }
return enclosingTestClass.findTestSettings()
?: enclosingTestClass.declaredClasses.firstNotNullOfOrNull { it.findTestSettings() }
?: (TestSettings.DEFAULT to null) // Fallback if there is no annotation.
}
private fun createTestCaseGroupProvider(
settings: Settings,
testSettings: TestSettings,
testSettingsAnnotation: Annotation?
): TestCaseGroupProvider {
// First, try to find two-argument constructor. // First, try to find two-argument constructor.
providerClass.constructors.asSequence() if (testSettingsAnnotation != null) {
.forEach { c -> val testSettingsAnnotationClass = testSettingsAnnotation.annotationClass
val (p1, p2) = c.parameters.takeIf { it.size == 2 } ?: return@forEach
val provider = when { testSettings.providerClass.constructors.asSequence()
p1.hasTypeOf<TestEnvironment>() && p2.hasTypeOf(providerAnnotationClass) -> c.call(environment, providerAnnotation) .forEach { c ->
p1.hasTypeOf(providerAnnotationClass) && p2.hasTypeOf<TestEnvironment>() -> c.call(providerAnnotation, environment) val (p1, p2) = c.parameters.takeIf { it.size == 2 } ?: return@forEach
else -> return@forEach @Suppress("Reformat") val provider = when {
p1.hasTypeOf<Settings>() && p2.hasTypeOf(testSettingsAnnotationClass) -> c.call(settings, testSettingsAnnotation)
p1.hasTypeOf(testSettingsAnnotationClass) && p2.hasTypeOf<Settings>() -> c.call(testSettingsAnnotation, settings)
else -> return@forEach
}
return provider.cast()
} }
return provider.cast() }
}
// Next, try to find a single-argument constructor. // Next, try to find a single-argument constructor.
providerClass.constructors.asSequence() testSettings.providerClass.constructors.asSequence()
.forEach { c -> .forEach { c ->
val p = c.parameters.singleOrNull() ?: return@forEach val p = c.parameters.singleOrNull() ?: return@forEach
if (p.hasTypeOf<TestEnvironment>()) return c.call(environment).cast() if (p.hasTypeOf<Settings>()) return c.call(settings).cast()
} }
fail { "No suitable constructor for $providerClass" } fail { "No suitable constructor for ${testSettings.providerClass}" }
} }
private inline fun <reified T : Any> KParameter.hasTypeOf(): Boolean = hasTypeOf(T::class) private inline fun <reified T : Any> KParameter.hasTypeOf(): Boolean = hasTypeOf(T::class)
@@ -8,6 +8,7 @@
package org.jetbrains.kotlin.konan.blackboxtest.support package org.jetbrains.kotlin.konan.blackboxtest.support
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestMode
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
@@ -15,6 +15,7 @@ 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.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies 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.TestModule.Companion.allFriends
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.* import java.io.*
@@ -22,7 +23,7 @@ import kotlin.time.Duration
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.measureTime import kotlin.time.measureTime
internal class TestCompilationFactory(private val environment: TestEnvironment) { internal class TestCompilationFactory(private val settings: Settings) {
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>() private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
private sealed interface TestCompilationCacheKey { private sealed interface TestCompilationCacheKey {
@@ -46,7 +47,7 @@ internal class TestCompilationFactory(private val environment: TestEnvironment)
val entryPoint = testCases.singleOrNull()?.safeExtras<NoTestRunnerExtras>()?.entryPoint val entryPoint = testCases.singleOrNull()?.safeExtras<NoTestRunnerExtras>()?.entryPoint
TestCompilationImpl( TestCompilationImpl(
environment = environment, settings = settings,
freeCompilerArgs = freeCompilerArgs, freeCompilerArgs = freeCompilerArgs,
sourceModules = rootModules, sourceModules = rootModules,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends), dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
@@ -54,7 +55,7 @@ internal class TestCompilationFactory(private val environment: TestEnvironment)
specificCompilerArgs = { specificCompilerArgs = {
add("-produce", "program") add("-produce", "program")
if (entryPoint != null) add("-entry", entryPoint) else add("-generate-test-runner") if (entryPoint != null) add("-entry", entryPoint) else add("-generate-test-runner")
environment.globalEnvironment.getRootCacheDirectory(debuggable = true)?.let { rootCacheDir -> settings.global.getRootCacheDirectory(debuggable = true)?.let { rootCacheDir ->
add("-Xcache-directory=$rootCacheDir") add("-Xcache-directory=$rootCacheDir")
} }
} }
@@ -75,7 +76,7 @@ internal class TestCompilationFactory(private val environment: TestEnvironment)
return cachedCompilations.computeIfAbsent(cacheKey) { return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilationImpl( TestCompilationImpl(
environment = environment, settings = settings,
freeCompilerArgs = freeCompilerArgs, freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules, sourceModules = sourceModules,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends), dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
@@ -87,15 +88,15 @@ internal class TestCompilationFactory(private val environment: TestEnvironment)
private fun artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) { private fun artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
1 -> artifactFileForExecutable(modules.first()) 1 -> artifactFileForExecutable(modules.first())
else -> multiModuleArtifactFile(modules, environment.globalEnvironment.target.family.exeSuffix) else -> multiModuleArtifactFile(modules, settings.global.target.family.exeSuffix)
} }
private fun artifactFileForExecutable(module: TestModule.Exclusive) = private fun artifactFileForExecutable(module: TestModule.Exclusive) =
singleModuleArtifactFile(module, environment.globalEnvironment.target.family.exeSuffix) singleModuleArtifactFile(module, settings.global.target.family.exeSuffix)
private fun artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) { private fun artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) {
is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib") is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib")
is TestModule.Shared -> environment.sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib") is TestModule.Shared -> settings.sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
} }
private fun singleModuleArtifactFile(module: TestModule.Exclusive, extension: String): File { private fun singleModuleArtifactFile(module: TestModule.Exclusive, extension: String): File {
@@ -141,7 +142,7 @@ internal class TestCompilationFactory(private val environment: TestEnvironment)
} }
private fun artifactDirForPackageName(packageName: PackageFQN?): File { private fun artifactDirForPackageName(packageName: PackageFQN?): File {
val baseDir = environment.testBinariesDir val baseDir = settings.testBinariesDir
val outputDir = if (packageName != null) baseDir.resolve(packageName.compressedPackageName) else baseDir val outputDir = if (packageName != null) baseDir.resolve(packageName.compressedPackageName) else baseDir
outputDir.mkdirs() outputDir.mkdirs()
@@ -221,7 +222,7 @@ internal class TestCompilationDependencies(
} }
private class TestCompilationImpl( private class TestCompilationImpl(
private val environment: TestEnvironment, private val settings: Settings,
private val freeCompilerArgs: TestCompilerArgs, private val freeCompilerArgs: TestCompilerArgs,
private val sourceModules: Collection<TestModule>, private val sourceModules: Collection<TestModule>,
private val dependencies: TestCompilationDependencies, private val dependencies: TestCompilationDependencies,
@@ -241,8 +242,8 @@ private class TestCompilationImpl(
add( add(
"-enable-assertions", "-enable-assertions",
"-g", "-g",
"-target", environment.globalEnvironment.target.name, "-target", settings.global.target.name,
"-repo", environment.globalEnvironment.kotlinNativeHome.resolve("klib").path, "-repo", settings.global.kotlinNativeHome.resolve("klib").path,
"-output", expectedArtifactFile.path, "-output", expectedArtifactFile.path,
"-Xskip-prerelease-check", "-Xskip-prerelease-check",
"-Xverify-ir", "-Xverify-ir",
@@ -273,7 +274,7 @@ private class TestCompilationImpl(
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try { val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler( val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
compilerArgs = compilerArgs, compilerArgs = compilerArgs,
lazyKotlinNativeClassLoader = environment.globalEnvironment.lazyKotlinNativeClassLoader lazyKotlinNativeClassLoader = settings.global.lazyKotlinNativeClassLoader
) )
val loggedCompilerCall = 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.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner 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.runner.LocalTestRunner
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.ThreadSafeCache
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TreeNode import org.jetbrains.kotlin.konan.blackboxtest.support.util.TreeNode
import org.jetbrains.kotlin.konan.blackboxtest.support.util.buildTree import org.jetbrains.kotlin.konan.blackboxtest.support.util.buildTree
@@ -24,10 +25,10 @@ import org.junit.jupiter.api.extension.ExtensionContext
import java.io.File import java.io.File
internal class TestRunProvider( internal class TestRunProvider(
private val environment: TestEnvironment, private val settings: Settings,
private val testCaseGroupProvider: TestCaseGroupProvider private val testCaseGroupProvider: TestCaseGroupProvider
) : ExtensionContext.Store.CloseableResource { ) : ExtensionContext.Store.CloseableResource {
private val compilationFactory = TestCompilationFactory(environment) private val compilationFactory = TestCompilationFactory(settings)
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>() private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
fun setProcessors(testDataFile: File, sourceTransformers: List<(String) -> String>) { fun setProcessors(testDataFile: File, sourceTransformers: List<(String) -> String>) {
@@ -114,7 +115,7 @@ internal class TestRunProvider(
} }
private fun <T> withTestExecutable(testDataFile: File, action: (TestCase, TestExecutable) -> T): T { private fun <T> withTestExecutable(testDataFile: File, action: (TestCase, TestExecutable) -> T): T {
environment.assertNotDisposed() settings.assertNotDisposed()
val testDataDir = testDataFile.parentFile val testDataDir = testDataFile.parentFile
val testDataFileName = testDataFile.name val testDataFileName = testDataFile.name
@@ -173,19 +174,19 @@ internal class TestRunProvider(
} }
// Currently, only local test runner is supported. // Currently, only local test runner is supported.
fun createRunner(testRun: TestRun): AbstractRunner<*> = when (val target = environment.globalEnvironment.target) { fun createRunner(testRun: TestRun): AbstractRunner<*> = when (val target = settings.global.target) {
environment.globalEnvironment.hostTarget -> LocalTestRunner(testRun, environment.globalEnvironment.executionTimeout) settings.global.hostTarget -> LocalTestRunner(testRun, settings.global.executionTimeout)
else -> fail { else -> fail {
""" """
Running at non-host target is not supported yet. Running at non-host target is not supported yet.
Compilation target: $target Compilation target: $target
Host target: ${environment.globalEnvironment.hostTarget} Host target: ${settings.global.hostTarget}
""".trimIndent() """.trimIndent()
} }
} }
override fun close() { override fun close() {
Disposer.dispose(environment) Disposer.dispose(settings)
} }
private sealed class TestCompilationCacheKey { private sealed class TestCompilationCacheKey {
@@ -19,6 +19,7 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.* import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
@@ -37,8 +38,8 @@ import org.jetbrains.kotlin.utils.addIfNotNull
import java.io.File import java.io.File
internal class ExtTestCaseGroupProvider( internal class ExtTestCaseGroupProvider(
private val environment: TestEnvironment private val settings: Settings
) : TestCaseGroupProvider, TestDisposable(parentDisposable = environment) { ) : TestCaseGroupProvider, TestDisposable(parentDisposable = settings) {
private val sourceTransformers: MutableMap<String, List<(String) -> String>> = mutableMapOf() private val sourceTransformers: MutableMap<String, List<(String) -> String>> = mutableMapOf()
private val structureFactory = ExtTestDataFileStructureFactory(parentDisposable = this) private val structureFactory = ExtTestDataFileStructureFactory(parentDisposable = this)
private val sharedModules = ThreadSafeCache<String, TestModule.Shared?>() private val sharedModules = ThreadSafeCache<String, TestModule.Shared?>()
@@ -58,7 +59,7 @@ internal class ExtTestCaseGroupProvider(
testDataFiles.forEach { testDataFile -> testDataFiles.forEach { testDataFile ->
val extTestDataFile = ExtTestDataFile( val extTestDataFile = ExtTestDataFile(
environment, structureFactory, testDataFile, sourceTransformers[testDataFile.canonicalPath] ?: listOf() settings, structureFactory, testDataFile, sourceTransformers[testDataFile.canonicalPath] ?: listOf()
) )
if (extTestDataFile.isRelevant) if (extTestDataFile.isRelevant)
@@ -126,7 +127,7 @@ internal class ExtTestCaseGroupProvider(
} }
private class ExtTestDataFile( private class ExtTestDataFile(
private val environment: TestEnvironment, private val settings: Settings,
structureFactory: ExtTestDataFileStructureFactory, structureFactory: ExtTestDataFileStructureFactory,
private val testDataFile: File, private val testDataFile: File,
sourceTransformers: List<(String) -> String> sourceTransformers: List<(String) -> String>
@@ -139,7 +140,7 @@ private class ExtTestDataFile(
} }
} }
private val settings by lazy { private val testDataFileSettings by lazy {
val optIns = structure.directives.multiValues(OPT_IN_DIRECTIVE) val optIns = structure.directives.multiValues(OPT_IN_DIRECTIVE)
val optInsForSourceCode = optIns subtract OPT_INS_PURELY_FOR_COMPILER val optInsForSourceCode = optIns subtract OPT_INS_PURELY_FOR_COMPILER
val optInsForCompiler = optIns intersect OPT_INS_PURELY_FOR_COMPILER val optInsForCompiler = optIns intersect OPT_INS_PURELY_FOR_COMPILER
@@ -154,12 +155,12 @@ private class ExtTestDataFile(
optInsForCompiler = optInsForCompiler, optInsForCompiler = optInsForCompiler,
expectActualLinker = EXPECT_ACTUAL_LINKER_DIRECTIVE in structure.directives, expectActualLinker = EXPECT_ACTUAL_LINKER_DIRECTIVE in structure.directives,
generatedSourcesDir = computeGeneratedSourcesDir( generatedSourcesDir = computeGeneratedSourcesDir(
testDataBaseDir = environment.testRoots.baseDir, testDataBaseDir = settings.testRoots.baseDir,
testDataFile = testDataFile, testDataFile = testDataFile,
generatedSourcesBaseDir = environment.testSourcesDir generatedSourcesBaseDir = settings.testSourcesDir
), ),
nominalPackageName = computePackageName( nominalPackageName = computePackageName(
testDataBaseDir = environment.testRoots.baseDir, testDataBaseDir = settings.testRoots.baseDir,
testDataFile = testDataFile testDataFile = testDataFile
) )
) )
@@ -168,16 +169,16 @@ private class ExtTestDataFile(
val isRelevant: Boolean = val isRelevant: Boolean =
isCompatibleTarget(TargetBackend.NATIVE, testDataFile) // Checks TARGET_BACKEND/DONT_TARGET_EXACT_BACKEND directives. isCompatibleTarget(TargetBackend.NATIVE, testDataFile) // Checks TARGET_BACKEND/DONT_TARGET_EXACT_BACKEND directives.
&& !isIgnoredTarget(TargetBackend.NATIVE, testDataFile) // Checks IGNORE_BACKEND directive. && !isIgnoredTarget(TargetBackend.NATIVE, testDataFile) // Checks IGNORE_BACKEND directive.
&& settings.languageSettings.none { it in INCOMPATIBLE_LANGUAGE_SETTINGS } && testDataFileSettings.languageSettings.none { it in INCOMPATIBLE_LANGUAGE_SETTINGS }
&& INCOMPATIBLE_DIRECTIVES.none { it in structure.directives } && INCOMPATIBLE_DIRECTIVES.none { it in structure.directives }
&& structure.directives[API_VERSION_DIRECTIVE] !in INCOMPATIBLE_API_VERSIONS && structure.directives[API_VERSION_DIRECTIVE] !in INCOMPATIBLE_API_VERSIONS
&& structure.directives[LANGUAGE_VERSION_DIRECTIVE] !in INCOMPATIBLE_LANGUAGE_VERSIONS && structure.directives[LANGUAGE_VERSION_DIRECTIVE] !in INCOMPATIBLE_LANGUAGE_VERSIONS
private fun assembleFreeCompilerArgs(): TestCompilerArgs { private fun assembleFreeCompilerArgs(): TestCompilerArgs {
val args = mutableListOf<String>() val args = mutableListOf<String>()
settings.languageSettings.sorted().mapTo(args) { "-XXLanguage:$it" } testDataFileSettings.languageSettings.sorted().mapTo(args) { "-XXLanguage:$it" }
settings.optInsForCompiler.sorted().mapTo(args) { "-Xopt-in=$it" } testDataFileSettings.optInsForCompiler.sorted().mapTo(args) { "-Xopt-in=$it" }
if (settings.expectActualLinker) args += "-Xexpect-actual-linker" if (testDataFileSettings.expectActualLinker) args += "-Xexpect-actual-linker"
return TestCompilerArgs(args) return TestCompilerArgs(args)
} }
@@ -274,7 +275,7 @@ private class ExtTestDataFile(
private fun patchPackageNames(isStandaloneTest: Boolean) = with(structure) { private fun patchPackageNames(isStandaloneTest: Boolean) = with(structure) {
if (isStandaloneTest) return // Don't patch packages for standalone tests. if (isStandaloneTest) return // Don't patch packages for standalone tests.
val basePackageName = FqName(settings.nominalPackageName) val basePackageName = FqName(testDataFileSettings.nominalPackageName)
val oldPackageNames: Set<FqName> = filesToTransform.mapToSet { it.packageFqName } val oldPackageNames: Set<FqName> = filesToTransform.mapToSet { it.packageFqName }
val oldToNewPackageNameMapping: Map<FqName, FqName> = oldPackageNames.associateWith { oldPackageName -> val oldToNewPackageNameMapping: Map<FqName, FqName> = oldPackageNames.associateWith { oldPackageName ->
@@ -450,7 +451,7 @@ private class ExtTestDataFile(
fun getAnnotationText(fullyQualifiedName: String) = "@file:${OPT_IN_ANNOTATION_NAME.asString()}($fullyQualifiedName::class)" fun getAnnotationText(fullyQualifiedName: String) = "@file:${OPT_IN_ANNOTATION_NAME.asString()}($fullyQualifiedName::class)"
// Every OptIn specified in test directive should be represented as a file-level annotation. // Every OptIn specified in test directive should be represented as a file-level annotation.
settings.optInsForSourceCode.mapTo(allFileLevelAnnotations, ::getAnnotationText) testDataFileSettings.optInsForSourceCode.mapTo(allFileLevelAnnotations, ::getAnnotationText)
// Now, collect file-level annotations already present in test files. // Now, collect file-level annotations already present in test files.
filesToTransform.forEach { handler -> filesToTransform.forEach { handler ->
@@ -550,7 +551,7 @@ private class ExtTestDataFile(
} }
if (!isStandaloneTest) { if (!isStandaloneTest) {
append("package ").appendLine(settings.nominalPackageName) append("package ").appendLine(testDataFileSettings.nominalPackageName)
appendLine() appendLine()
} }
@@ -574,10 +575,10 @@ private class ExtTestDataFile(
sharedModules: ThreadSafeCache<String, TestModule.Shared?> sharedModules: ThreadSafeCache<String, TestModule.Shared?>
): TestCase = with(structure) { ): TestCase = with(structure) {
val modules = generateModules( val modules = generateModules(
testCaseDir = settings.generatedSourcesDir, testCaseDir = testDataFileSettings.generatedSourcesDir,
findOrGenerateSharedModule = { moduleName: String, generator: SharedModuleGenerator -> findOrGenerateSharedModule = { moduleName: String, generator: SharedModuleGenerator ->
sharedModules.computeIfAbsent(moduleName) { sharedModules.computeIfAbsent(moduleName) {
generator(environment.sharedSourcesDir) generator(settings.sharedSourcesDir)
} }
} }
) )
@@ -587,7 +588,7 @@ private class ExtTestDataFile(
modules = modules, modules = modules,
freeCompilerArgs = assembleFreeCompilerArgs(), freeCompilerArgs = assembleFreeCompilerArgs(),
origin = TestOrigin.SingleTestDataFile(testDataFile), origin = TestOrigin.SingleTestDataFile(testDataFile),
nominalPackageName = settings.nominalPackageName, nominalPackageName = testDataFileSettings.nominalPackageName,
expectedOutputDataFile = null, expectedOutputDataFile = null,
extras = WithTestRunnerExtras.EMPTY extras = WithTestRunnerExtras.EMPTY
) )
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.* import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras 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.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.DEFAULT_FILE_NAME 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.ThreadSafeFactory
import org.jetbrains.kotlin.konan.blackboxtest.support.util.computeGeneratedSourcesDir import org.jetbrains.kotlin.konan.blackboxtest.support.util.computeGeneratedSourcesDir
@@ -22,7 +23,7 @@ import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import org.jetbrains.kotlin.test.services.impl.RegisteredDirectivesParser import org.jetbrains.kotlin.test.services.impl.RegisteredDirectivesParser
import java.io.File import java.io.File
internal class StandardTestCaseGroupProvider(private val environment: TestEnvironment) : TestCaseGroupProvider { internal class StandardTestCaseGroupProvider(private val settings: Settings) : TestCaseGroupProvider {
val sourceTransformers: MutableMap<String, List<(String) -> String>> = mutableMapOf() val sourceTransformers: MutableMap<String, List<(String) -> String>> = mutableMapOf()
// Load test cases in groups on demand. // Load test cases in groups on demand.
@@ -51,13 +52,13 @@ internal class StandardTestCaseGroupProvider(private val environment: TestEnviro
private fun createTestCase(testDataFile: File): TestCase { private fun createTestCase(testDataFile: File): TestCase {
val generatedSourcesDir = computeGeneratedSourcesDir( val generatedSourcesDir = computeGeneratedSourcesDir(
testDataBaseDir = environment.testRoots.baseDir, testDataBaseDir = settings.testRoots.baseDir,
testDataFile = testDataFile, testDataFile = testDataFile,
generatedSourcesBaseDir = environment.testSourcesDir generatedSourcesBaseDir = settings.testSourcesDir
) )
val nominalPackageName = computePackageName( val nominalPackageName = computePackageName(
testDataBaseDir = environment.testRoots.baseDir, testDataBaseDir = settings.testRoots.baseDir,
testDataFile = testDataFile testDataFile = testDataFile
) )
@@ -1,11 +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.group
import kotlin.reflect.KClass
@Target(AnnotationTarget.ANNOTATION_CLASS)
internal annotation class TestCaseGroupProviding(val providerClass: KClass<out TestCaseGroupProvider>)
@@ -5,6 +5,8 @@
package org.jetbrains.kotlin.konan.blackboxtest.support.group package org.jetbrains.kotlin.konan.blackboxtest.support.group
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestSettings
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
@TestCaseGroupProviding(ExtTestCaseGroupProvider::class) @TestSettings(ExtTestCaseGroupProvider::class)
annotation class UseExtTestCaseGroupProvider annotation class UseExtTestCaseGroupProvider
@@ -3,44 +3,34 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * 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 package org.jetbrains.kotlin.konan.blackboxtest.support.settings
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestDisposable import org.jetbrains.kotlin.test.services.JUnit5Assertions
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.File import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
internal class TestEnvironment( internal class GlobalSettings(
val globalEnvironment: GlobalTestEnvironment,
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 GlobalTestEnvironment(
val target: KonanTarget = HostManager.host, val target: KonanTarget = HostManager.host,
val kotlinNativeHome: File = defaultKotlinNativeHome, val kotlinNativeHome: File = defaultKotlinNativeHome,
val lazyKotlinNativeClassLoader: Lazy<ClassLoader> = defaultKotlinNativeClassLoader, val lazyKotlinNativeClassLoader: Lazy<ClassLoader> = defaultKotlinNativeClassLoader,
val testMode: TestMode = defaultTestMode, val testMode: TestMode = defaultTestMode,
val cacheSettings: TestCacheSettings = defaultCacheSettings, val cacheSettings: CacheSettings = defaultCacheSettings,
val executionTimeout: Duration = defaultExecutionTimeout, val executionTimeout: Duration = defaultExecutionTimeout,
val baseBuildDir: File = projectBuildDir val baseBuildDir: File = projectBuildDir
) { ) {
val hostTarget: KonanTarget = HostManager.host val hostTarget: KonanTarget = HostManager.host
fun getRootCacheDirectory(debuggable: Boolean): File? = fun getRootCacheDirectory(debuggable: Boolean): File? =
(cacheSettings as? TestCacheSettings.WithCache)?.getRootCacheDirectory(this, debuggable) (cacheSettings as? CacheSettings.WithCache)?.getRootCacheDirectory(this, debuggable)
companion object { companion object {
private val defaultKotlinNativeHome: File private val defaultKotlinNativeHome: File
get() = System.getProperty(KOTLIN_NATIVE_HOME)?.let(::File) ?: fail { "Non-specified $KOTLIN_NATIVE_HOME system property" } get() = System.getProperty(KOTLIN_NATIVE_HOME)?.let(::File) ?: JUnit5Assertions.fail { "Non-specified $KOTLIN_NATIVE_HOME system property" }
// Use isolated cached class loader. // Use isolated cached class loader.
private val defaultKotlinNativeClassLoader: Lazy<ClassLoader> = lazy { private val defaultKotlinNativeClassLoader: Lazy<ClassLoader> = lazy {
@@ -48,7 +38,7 @@ internal class GlobalTestEnvironment(
?.split(':', ';') ?.split(':', ';')
?.map { File(it).toURI().toURL() } ?.map { File(it).toURI().toURL() }
?.toTypedArray() ?.toTypedArray()
?: fail { "Non-specified $COMPILER_CLASSPATH system property" } ?: JUnit5Assertions.fail { "Non-specified $COMPILER_CLASSPATH system property" }
URLClassLoader(nativeClassPath, /* no parent class loader */ null).apply { setDefaultAssertionStatus(true) } URLClassLoader(nativeClassPath, /* no parent class loader */ null).apply { setDefaultAssertionStatus(true) }
} }
@@ -56,7 +46,7 @@ internal class GlobalTestEnvironment(
private val defaultTestMode: TestMode = run { private val defaultTestMode: TestMode = run {
val testModeName = System.getProperty(TEST_MODE) ?: return@run TestMode.WITH_MODULES val testModeName = System.getProperty(TEST_MODE) ?: return@run TestMode.WITH_MODULES
TestMode.values().firstOrNull { it.name == testModeName } ?: fail { TestMode.values().firstOrNull { it.name == testModeName } ?: JUnit5Assertions.fail {
buildString { buildString {
appendLine("Unknown test mode name $testModeName.") appendLine("Unknown test mode name $testModeName.")
appendLine("One of the following test modes should be passed through $TEST_MODE system property:") appendLine("One of the following test modes should be passed through $TEST_MODE system property:")
@@ -67,27 +57,27 @@ internal class GlobalTestEnvironment(
} }
} }
private val defaultCacheSettings: TestCacheSettings = run { private val defaultCacheSettings: CacheSettings = run {
val useCacheValue = System.getProperty(USE_CACHE) val useCacheValue = System.getProperty(USE_CACHE)
val useCache = if (useCacheValue != null) { val useCache = if (useCacheValue != null) {
useCacheValue.toBooleanStrictOrNull() ?: fail { "Invalid value for $USE_CACHE system property: $useCacheValue" } useCacheValue.toBooleanStrictOrNull() ?: JUnit5Assertions.fail { "Invalid value for $USE_CACHE system property: $useCacheValue" }
} else } else
true true
if (useCache) TestCacheSettings.WithCache else TestCacheSettings.WithoutCache if (useCache) CacheSettings.WithCache else CacheSettings.WithoutCache
} }
private val defaultExecutionTimeout: Duration = run { private val defaultExecutionTimeout: Duration = run {
val executionTimeoutValue = System.getProperty(EXECUTION_TIMEOUT) val executionTimeoutValue = System.getProperty(EXECUTION_TIMEOUT)
if (executionTimeoutValue != null) { if (executionTimeoutValue != null) {
executionTimeoutValue.toLongOrNull()?.milliseconds executionTimeoutValue.toLongOrNull()?.milliseconds
?: fail { "Invalid value for $EXECUTION_TIMEOUT system property: $executionTimeoutValue" } ?: JUnit5Assertions.fail { "Invalid value for $EXECUTION_TIMEOUT system property: $executionTimeoutValue" }
} else } else
DEFAULT_EXECUTION_TIMEOUT DEFAULT_EXECUTION_TIMEOUT
} }
private val projectBuildDir: File private val projectBuildDir: File
get() = System.getenv(PROJECT_BUILD_DIR)?.let(::File) ?: fail { "Non-specified $PROJECT_BUILD_DIR environment variable" } get() = System.getenv(PROJECT_BUILD_DIR)?.let(::File) ?: JUnit5Assertions.fail { "Non-specified $PROJECT_BUILD_DIR environment variable" }
private const val KOTLIN_NATIVE_HOME = "kotlin.internal.native.test.nativeHome" private const val KOTLIN_NATIVE_HOME = "kotlin.internal.native.test.nativeHome"
private const val COMPILER_CLASSPATH = "kotlin.internal.native.test.compilerClasspath" private const val COMPILER_CLASSPATH = "kotlin.internal.native.test.compilerClasspath"
@@ -100,30 +90,11 @@ internal class GlobalTestEnvironment(
} }
} }
internal class TestRoots( internal sealed interface CacheSettings {
val roots: Set<File>, object WithoutCache : CacheSettings
val baseDir: File
)
// TODO: in fact, only WITH_MODULES mode is supported now object WithCache : CacheSettings {
internal enum class TestMode(val description: String) { fun getRootCacheDirectory(settings: GlobalSettings, debuggable: Boolean): File? = with(settings) {
ONE_STAGE(
description = "Compile test files altogether without producing intermediate KLIBs."
),
TWO_STAGE(
description = "Compile test files altogether and produce an intermediate KLIB. Then produce a program from the KLIB using -Xinclude."
),
WITH_MODULES(
description = "Compile each test file as one or many modules (depending on MODULE directives declared in the file)." +
" Then link the KLIBs into the single executable file."
)
}
internal sealed interface TestCacheSettings {
object WithoutCache : TestCacheSettings
object WithCache : TestCacheSettings {
fun getRootCacheDirectory(globalEnvironment: GlobalTestEnvironment, debuggable: Boolean): File? = with(globalEnvironment) {
kotlinNativeHome.resolve("klib/cache").resolve(getCacheDirName(target, debuggable)).takeIf { it.exists() } kotlinNativeHome.resolve("klib/cache").resolve(getCacheDirName(target, debuggable)).takeIf { it.exists() }
} }
@@ -0,0 +1,23 @@
/*
* 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.util.TestDisposable
import java.io.File
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 TestRoots(
val roots: Set<File>,
val baseDir: 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
// TODO: in fact, only WITH_MODULES mode is supported now
internal enum class TestMode(val description: String) {
ONE_STAGE(
description = "Compile test files altogether without producing intermediate KLIBs."
),
TWO_STAGE(
description = "Compile test files altogether and produce an intermediate KLIB. Then produce a program from the KLIB using -Xinclude."
),
WITH_MODULES(
description = "Compile each test file as one or many modules (depending on MODULE directives declared in the file)." +
" Then link the KLIBs into the single executable file."
)
}
@@ -0,0 +1,17 @@
/*
* 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)
}
}