[Native][tests] Fix parallel test execution
This commit is contained in:
+1
-3
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseExtTestCaseGroup
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseStandardTestCaseGroupProvider
|
||||
import org.jetbrains.kotlin.test.TargetBackend
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
fun main() {
|
||||
System.setProperty("java.awt.headless", "true")
|
||||
@@ -23,7 +22,7 @@ fun main() {
|
||||
testGroup("native/native.tests/tests-gen", "compiler/testData") {
|
||||
testClass<AbstractExternalNativeBlackBoxTest>(
|
||||
suiteTestClassName = "ExternalTestGenerated",
|
||||
annotations = listOf(external(), provider<UseExtTestCaseGroupProvider>(), testInstancePerClass())
|
||||
annotations = listOf(external(), provider<UseExtTestCaseGroupProvider>())
|
||||
) {
|
||||
model("codegen/box", targetBackend = TargetBackend.NATIVE)
|
||||
model("codegen/boxInline", targetBackend = TargetBackend.NATIVE)
|
||||
@@ -47,4 +46,3 @@ private inline fun <reified T : Annotation> provider() = annotation(T::class.jav
|
||||
|
||||
private fun external() = annotation(Tag::class.java, "external")
|
||||
private fun infrastructure() = annotation(Tag::class.java, "infrastructure")
|
||||
private fun testInstancePerClass() = annotation(TestInstance::class.java, TestInstance.Lifecycle.PER_CLASS)
|
||||
|
||||
+1
-3
@@ -6,15 +6,13 @@
|
||||
package org.jetbrains.kotlin.konan.blackboxtest
|
||||
|
||||
import com.intellij.testFramework.TestDataFile
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.ExternalSourceTransformersProvider
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ExternalSourceTransformer
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ExternalSourceTransformers
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ExternalSourceTransformersProvider
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeCache
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.io.File
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // Create only one instance of every test class for consistent caching of source transformers.
|
||||
abstract class AbstractExternalNativeBlackBoxTest : ExternalSourceTransformersProvider, AbstractNativeBlackBoxTest() {
|
||||
private val registeredSourceTransformers: ThreadSafeCache<File, MutableList<ExternalSourceTransformer>> = ThreadSafeCache()
|
||||
|
||||
|
||||
+5
-3
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.konan.blackboxtest
|
||||
|
||||
import com.intellij.testFramework.TestDataFile
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.*
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestRunSettings
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TreeNode
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.joinPackageNames
|
||||
@@ -17,6 +18,7 @@ import org.junit.jupiter.api.extension.ExtendWith
|
||||
|
||||
@ExtendWith(NativeBlackBoxTestSupport::class)
|
||||
abstract class AbstractNativeBlackBoxTest {
|
||||
internal lateinit var testRunSettings: TestRunSettings
|
||||
internal lateinit var testRunProvider: TestRunProvider
|
||||
|
||||
/**
|
||||
@@ -35,7 +37,7 @@ abstract class AbstractNativeBlackBoxTest {
|
||||
* This function should be called from a method annotated with [org.junit.jupiter.api.Test].
|
||||
*/
|
||||
internal fun runTestCase(testCaseId: TestCaseId) {
|
||||
val testRun = testRunProvider.getSingleTestRun(testCaseId)
|
||||
val testRun = testRunProvider.getSingleTestRun(testCaseId, testRunSettings)
|
||||
performTestRun(testRun)
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ abstract class AbstractNativeBlackBoxTest {
|
||||
* This function should be called from a method annotated with [org.junit.jupiter.api.TestFactory].
|
||||
*/
|
||||
internal fun dynamicTestCase(testCaseId: TestCaseId): Collection<DynamicNode> {
|
||||
val testRunNodes = testRunProvider.getTestRuns(testCaseId)
|
||||
val testRunNodes = testRunProvider.getTestRuns(testCaseId, testRunSettings)
|
||||
return buildJUnitDynamicNodes(testRunNodes)
|
||||
}
|
||||
|
||||
@@ -90,7 +92,7 @@ abstract class AbstractNativeBlackBoxTest {
|
||||
}
|
||||
|
||||
private fun performTestRun(testRun: TestRun) {
|
||||
val testRunner = testRunProvider.createRunner(testRun)
|
||||
val testRunner = testRunProvider.createRunner(testRun, testRunSettings)
|
||||
testRunner.run()
|
||||
}
|
||||
}
|
||||
|
||||
+189
-73
@@ -5,11 +5,10 @@
|
||||
|
||||
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.GlobalSettings
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
|
||||
import org.jetbrains.kotlin.konan.target.HostManager
|
||||
import org.jetbrains.kotlin.test.TestMetadata
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertEquals
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||
@@ -20,9 +19,12 @@ import org.junit.jupiter.api.extension.BeforeEachCallback
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
import org.junit.jupiter.api.extension.TestInstancePostProcessor
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class NativeBlackBoxTestSupport : BeforeEachCallback {
|
||||
/**
|
||||
@@ -32,81 +34,169 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
|
||||
* not allow accessing its parent test instance in case there are inner test classes in the generated test suite.
|
||||
*/
|
||||
override fun beforeEach(extensionContext: ExtensionContext): Unit = with(extensionContext) {
|
||||
enclosingTestInstance.testRunProvider = getOrCreateTestRunProvider()
|
||||
val settings = createTestRunSettings()
|
||||
|
||||
// Set the essential compiler property.
|
||||
System.setProperty("kotlin.native.home", getOrCreateGlobalSettings().kotlinNativeHome.path)
|
||||
System.setProperty("kotlin.native.home", settings.get<KotlinNativeHome>().path)
|
||||
|
||||
// Inject the required properties to test instance.
|
||||
with(settings.get<TestInstances>().enclosingTestInstance) {
|
||||
testRunSettings = settings
|
||||
testRunProvider = getOrCreateTestRunProvider()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val NAMESPACE = ExtensionContext.Namespace.create(NativeBlackBoxTestSupport::class.java.simpleName)
|
||||
|
||||
/** Creates a single instance of [TestRunProvider] per test class. */
|
||||
private fun ExtensionContext.getOrCreateTestRunProvider(): TestRunProvider {
|
||||
val enclosingTestClass = enclosingTestClass
|
||||
/*************** Test process settings ***************/
|
||||
|
||||
return root.getStore(NAMESPACE).getOrComputeIfAbsent(enclosingTestClass.sanitizedName) {
|
||||
val computedTestSettings = computeTestSettings(enclosingTestClass)
|
||||
val requiredSettings: Set<KClass<*>> = buildSet {
|
||||
// Always required:
|
||||
this += GlobalSettings::class
|
||||
this += Binaries::class
|
||||
|
||||
// Custom:
|
||||
addAll(computedTestSettings.testSettings.requiredSettings)
|
||||
}
|
||||
|
||||
val globalSettings = getOrCreateGlobalSettings()
|
||||
|
||||
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, computedTestSettings, enclosingTestInstance)
|
||||
|
||||
TestRunProvider(settings, testCaseGroupProvider)
|
||||
private fun ExtensionContext.getOrCreateTestProcessSettings(): TestProcessSettings =
|
||||
root.getStore(NAMESPACE).getOrComputeIfAbsent(TestProcessSettings::class.java.name) {
|
||||
TestProcessSettings(
|
||||
computeNativeTargets(),
|
||||
computeNativeHome(),
|
||||
computeNativeClassLoader(),
|
||||
computeTestMode(),
|
||||
CacheKind::class to computeCacheKind(),
|
||||
computeBaseDirs(),
|
||||
computeTimeouts()
|
||||
)
|
||||
}.cast()
|
||||
|
||||
private fun computeNativeTargets(): KotlinNativeTargets {
|
||||
val hostTarget = HostManager.host
|
||||
return KotlinNativeTargets(testTarget = hostTarget, hostTarget = hostTarget)
|
||||
}
|
||||
|
||||
private val ExtensionContext.enclosingTestInstance: AbstractNativeBlackBoxTest
|
||||
get() = requiredTestInstances.allInstances.firstOrNull().cast()
|
||||
private fun computeNativeHome(): KotlinNativeHome = KotlinNativeHome(File(requiredSystemProperty(KOTLIN_NATIVE_HOME)))
|
||||
|
||||
private val ExtensionContext.enclosingTestClass: Class<*>
|
||||
get() = generateSequence(requiredTestClass) { it.enclosingClass }.last()
|
||||
private fun computeNativeClassLoader(): KotlinNativeClassLoader = KotlinNativeClassLoader(
|
||||
lazy {
|
||||
val nativeClassPath = requiredSystemProperty(COMPILER_CLASSPATH)
|
||||
.split(':', ';')
|
||||
.map { File(it).toURI().toURL() }
|
||||
.toTypedArray()
|
||||
|
||||
private fun ExtensionContext.getOrCreateGlobalSettings(): GlobalSettings =
|
||||
root.getStore(NAMESPACE).getOrComputeIfAbsent(GlobalSettings::class.java.sanitizedName) {
|
||||
// Create with the default settings.
|
||||
GlobalSettings()
|
||||
URLClassLoader(nativeClassPath, /* no parent class loader */ null).apply { setDefaultAssertionStatus(true) }
|
||||
}
|
||||
)
|
||||
|
||||
private fun computeTestMode(): TestMode = systemProperty(
|
||||
name = TEST_MODE,
|
||||
transform = { testModeName ->
|
||||
TestMode.values().firstOrNull { it.name == testModeName } ?: fail {
|
||||
buildString {
|
||||
appendLine("Unknown test mode name $testModeName.")
|
||||
appendLine("One of the following test modes should be passed through $TEST_MODE system property:")
|
||||
TestMode.values().forEach { testMode ->
|
||||
appendLine("- ${testMode.name}: ${testMode.description}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
default = TestMode.WITH_MODULES
|
||||
)
|
||||
|
||||
private fun computeCacheKind(): CacheKind {
|
||||
val useCache = systemProperty(
|
||||
name = USE_CACHE,
|
||||
transform = { useCacheValue ->
|
||||
useCacheValue.toBooleanStrictOrNull() ?: fail { "Invalid value for $USE_CACHE system property: $useCacheValue" }
|
||||
},
|
||||
default = true
|
||||
)
|
||||
|
||||
return if (useCache) CacheKind.WithStaticCache else CacheKind.WithoutCache
|
||||
}
|
||||
|
||||
private fun computeBaseDirs(): BaseDirs = BaseDirs(File(requiredEnvironmentVariable(PROJECT_BUILD_DIR)))
|
||||
|
||||
private fun computeTimeouts(): Timeouts {
|
||||
val executionTimeout = systemProperty(
|
||||
name = EXECUTION_TIMEOUT,
|
||||
transform = { executionTimeoutValue ->
|
||||
executionTimeoutValue.toLongOrNull()?.milliseconds
|
||||
?: fail { "Invalid value for $EXECUTION_TIMEOUT system property: $executionTimeoutValue" }
|
||||
},
|
||||
default = 10.seconds
|
||||
)
|
||||
|
||||
return Timeouts(executionTimeout)
|
||||
}
|
||||
|
||||
private fun requiredSystemProperty(name: String): String =
|
||||
System.getProperty(name) ?: fail { "Unspecified $name system property" }
|
||||
|
||||
private fun <T> systemProperty(name: String, transform: (String) -> T, default: T): T =
|
||||
System.getProperty(name)?.let(transform) ?: default
|
||||
|
||||
private fun requiredEnvironmentVariable(name: String): String =
|
||||
System.getenv(name) ?: fail { "Unspecified $name environment variable" }
|
||||
|
||||
private val NAMESPACE = ExtensionContext.Namespace.create(NativeBlackBoxTestSupport::class.java.simpleName)
|
||||
|
||||
private const val KOTLIN_NATIVE_HOME = "kotlin.internal.native.test.nativeHome"
|
||||
private const val COMPILER_CLASSPATH = "kotlin.internal.native.test.compilerClasspath"
|
||||
private const val TEST_MODE = "kotlin.internal.native.test.mode"
|
||||
private const val USE_CACHE = "kotlin.internal.native.test.useCache"
|
||||
private const val EXECUTION_TIMEOUT = "kotlin.internal.native.test.executionTimeout"
|
||||
private const val PROJECT_BUILD_DIR = "PROJECT_BUILD_DIR"
|
||||
|
||||
/*************** Test class settings ***************/
|
||||
|
||||
private fun ExtensionContext.getOrCreateTestClassSettings(): TestClassSettings =
|
||||
root.getStore(NAMESPACE).getOrComputeIfAbsent(testClassKeyFor<TestClassSettings>()) {
|
||||
val enclosingTestClass = enclosingTestClass
|
||||
|
||||
val testProcessSettings = getOrCreateTestProcessSettings()
|
||||
val computedTestConfiguration = computeTestConfiguration(enclosingTestClass)
|
||||
|
||||
/// Put settings that are always required:
|
||||
val settings = mutableListOf(
|
||||
computedTestConfiguration,
|
||||
computeBinariesDirs(testProcessSettings.get(), testProcessSettings.get(), enclosingTestClass)
|
||||
)
|
||||
|
||||
// Add custom settings:
|
||||
computedTestConfiguration.configuration.requiredSettings.mapTo(settings) { clazz ->
|
||||
when (clazz) {
|
||||
TestRoots::class -> computeTestRoots(enclosingTestClass)
|
||||
GeneratedSources::class -> computeGeneratedSourceDirs(
|
||||
testProcessSettings.get(),
|
||||
testProcessSettings.get(),
|
||||
enclosingTestClass
|
||||
)
|
||||
else -> fail { "Unknown test class setting type: $clazz" }
|
||||
}
|
||||
}
|
||||
|
||||
TestClassSettings(parent = testProcessSettings, settings)
|
||||
}.cast()
|
||||
|
||||
private fun computeTestSettings(enclosingTestClass: Class<*>): ComputedTestSettings {
|
||||
val findTestSettings: Class<*>.() -> ComputedTestSettings? = {
|
||||
private fun computeTestConfiguration(enclosingTestClass: Class<*>): ComputedTestConfiguration {
|
||||
val findTestConfiguration: Class<*>.() -> ComputedTestConfiguration? = {
|
||||
annotations.asSequence().mapNotNull { annotation ->
|
||||
val testSettings = annotation.annotationClass.findAnnotation<TestSettings>() ?: return@mapNotNull null
|
||||
ComputedTestSettings(testSettings, annotation)
|
||||
val testConfiguration = annotation.annotationClass.findAnnotation<TestConfiguration>() ?: return@mapNotNull null
|
||||
ComputedTestConfiguration(testConfiguration, annotation)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
return enclosingTestClass.findTestSettings()
|
||||
?: enclosingTestClass.declaredClasses.firstNotNullOfOrNull { it.findTestSettings() }
|
||||
?: fail { "No @${TestSettings::class.simpleName} annotation found on test classes" }
|
||||
return enclosingTestClass.findTestConfiguration()
|
||||
?: enclosingTestClass.declaredClasses.firstNotNullOfOrNull { it.findTestConfiguration() }
|
||||
?: fail { "No @${TestConfiguration::class.simpleName} annotation found on test classes" }
|
||||
}
|
||||
|
||||
private fun computeTestRoots(enclosingTestClass: Class<*>): TestRoots {
|
||||
fun TestMetadata.testRoot() = getAbsoluteFile(localPath = value)
|
||||
|
||||
val testRoots: Set<File> = when (val outermostTestMetadata = enclosingTestClass.getAnnotation(TestMetadata::class.java)) {
|
||||
null -> {
|
||||
enclosingTestClass.declaredClasses.mapNotNullToSet { nestedClass ->
|
||||
nestedClass.getAnnotation(TestMetadata::class.java)?.testRoot
|
||||
nestedClass.getAnnotation(TestMetadata::class.java)?.testRoot()
|
||||
}
|
||||
}
|
||||
else -> setOf(outermostTestMetadata.testRoot)
|
||||
else -> setOf(outermostTestMetadata.testRoot())
|
||||
}
|
||||
|
||||
val baseDir: File = when (testRoots.size) {
|
||||
@@ -125,10 +215,14 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
|
||||
return TestRoots(testRoots, baseDir)
|
||||
}
|
||||
|
||||
private fun computeGeneratedSourceDirs(globalSettings: GlobalSettings, enclosingTestClass: Class<*>): GeneratedSources {
|
||||
val testSourcesDir = globalSettings.baseBuildDir
|
||||
private fun computeGeneratedSourceDirs(
|
||||
baseDirs: BaseDirs,
|
||||
targets: KotlinNativeTargets,
|
||||
enclosingTestClass: Class<*>
|
||||
): GeneratedSources {
|
||||
val testSourcesDir = baseDirs.buildDir
|
||||
.resolve("bbtest.src")
|
||||
.resolve("${globalSettings.target.compressedName}_${enclosingTestClass.compressedSimpleName}")
|
||||
.resolve("${targets.testTarget.compressedName}_${enclosingTestClass.compressedSimpleName}")
|
||||
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources.
|
||||
|
||||
val sharedSourcesDir = testSourcesDir
|
||||
@@ -138,10 +232,10 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
|
||||
return GeneratedSources(testSourcesDir, sharedSourcesDir)
|
||||
}
|
||||
|
||||
private fun computeBinariesDirs(globalSettings: GlobalSettings, enclosingTestClass: Class<*>): Binaries {
|
||||
val testBinariesDir = globalSettings.baseBuildDir
|
||||
private fun computeBinariesDirs(baseDirs: BaseDirs, targets: KotlinNativeTargets, enclosingTestClass: Class<*>): Binaries {
|
||||
val testBinariesDir = baseDirs.buildDir
|
||||
.resolve("bbtest.bin")
|
||||
.resolve("${globalSettings.target.compressedName}_${enclosingTestClass.compressedSimpleName}")
|
||||
.resolve("${targets.testTarget.compressedName}_${enclosingTestClass.compressedSimpleName}")
|
||||
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
|
||||
|
||||
val sharedBinariesDir = testBinariesDir
|
||||
@@ -151,26 +245,45 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
|
||||
return Binaries(testBinariesDir, sharedBinariesDir)
|
||||
}
|
||||
|
||||
private fun createTestCaseGroupProvider(
|
||||
settings: Settings,
|
||||
computedTestSettings: ComputedTestSettings,
|
||||
enclosingTestInstance: AbstractNativeBlackBoxTest
|
||||
): TestCaseGroupProvider {
|
||||
val (testSettings: TestSettings, testSettingsAnnotation: Annotation?) = computedTestSettings
|
||||
val providerClass: KClass<out TestCaseGroupProvider> = testSettings.providerClass
|
||||
/*************** Test run settings ***************/
|
||||
|
||||
// Note: TestRunSettings is not cached!
|
||||
private fun ExtensionContext.createTestRunSettings(): TestRunSettings {
|
||||
val testInstances = computeTestInstances()
|
||||
|
||||
return TestRunSettings(
|
||||
parent = getOrCreateTestClassSettings(),
|
||||
listOfNotNull(
|
||||
testInstances,
|
||||
ExternalSourceTransformersProvider::class to testInstances.enclosingTestInstance.safeAs<ExternalSourceTransformersProvider>()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun ExtensionContext.computeTestInstances(): TestInstances = TestInstances(requiredTestInstances.allInstances)
|
||||
|
||||
/*************** Test run provider ***************/
|
||||
|
||||
private fun ExtensionContext.getOrCreateTestRunProvider(): TestRunProvider =
|
||||
root.getStore(NAMESPACE).getOrComputeIfAbsent(testClassKeyFor<TestRunProvider>()) {
|
||||
val testCaseGroupProvider = createTestCaseGroupProvider(getOrCreateTestClassSettings().get())
|
||||
TestRunProvider(testCaseGroupProvider)
|
||||
}.cast()
|
||||
|
||||
private fun createTestCaseGroupProvider(computedTestConfiguration: ComputedTestConfiguration): TestCaseGroupProvider {
|
||||
val (testConfiguration: TestConfiguration, testConfigurationAnnotation: Annotation) = computedTestConfiguration
|
||||
val providerClass: KClass<out TestCaseGroupProvider> = testConfiguration.providerClass
|
||||
|
||||
// Assumption: For simplicity’s sake TestCaseGroupProvider has just one constructor.
|
||||
val constructor = providerClass.constructors.singleOrNull()
|
||||
?: fail { "No or multiple constructors found for $providerClass" }
|
||||
|
||||
val testSettingsAnnotationClass: KClass<out Annotation>? = testSettingsAnnotation?.annotationClass
|
||||
val sourceTransformersProvider = enclosingTestInstance.safeAs<ExternalSourceTransformersProvider>()
|
||||
val testConfigurationAnnotationClass: KClass<out Annotation> = testConfigurationAnnotation.annotationClass
|
||||
|
||||
val arguments = constructor.parameters.map { parameter ->
|
||||
when {
|
||||
parameter.hasTypeOf<Settings>() -> settings
|
||||
sourceTransformersProvider != null && parameter.hasTypeOf<ExternalSourceTransformersProvider>() -> sourceTransformersProvider
|
||||
testSettingsAnnotationClass != null && parameter.hasTypeOf(testSettingsAnnotationClass) -> testSettingsAnnotation
|
||||
parameter.hasTypeOf(testConfigurationAnnotationClass) -> testConfigurationAnnotation
|
||||
// maybe add other arguments???
|
||||
else -> fail { "Can't provide all arguments for $constructor" }
|
||||
}
|
||||
}
|
||||
@@ -178,11 +291,14 @@ class NativeBlackBoxTestSupport : BeforeEachCallback {
|
||||
return constructor.call(*arguments.toTypedArray()).cast()
|
||||
}
|
||||
|
||||
private inline fun <reified T : Any> KParameter.hasTypeOf(): Boolean = hasTypeOf(T::class)
|
||||
private fun KParameter.hasTypeOf(clazz: KClass<*>): Boolean = (type.classifier as? KClass<*>)?.qualifiedName == clazz.qualifiedName
|
||||
|
||||
private val TestMetadata.testRoot: File get() = getAbsoluteFile(localPath = value)
|
||||
/*************** Common ***************/
|
||||
|
||||
private val ExtensionContext.enclosingTestClass: Class<*>
|
||||
get() = generateSequence(requiredTestClass) { it.enclosingClass }.last()
|
||||
|
||||
private inline fun <reified T : Any> ExtensionContext.testClassKeyFor(): String =
|
||||
enclosingTestClass.name + "#" + T::class.java.name
|
||||
}
|
||||
}
|
||||
|
||||
private data class ComputedTestSettings(val testSettings: TestSettings, val annotation: Annotation?)
|
||||
|
||||
+34
-32
@@ -16,9 +16,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.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.settings.*
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||
import java.io.*
|
||||
@@ -26,7 +24,7 @@ import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
internal class TestCompilationFactory(private val settings: Settings) {
|
||||
internal class TestCompilationFactory {
|
||||
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
|
||||
|
||||
private sealed interface TestCompilationCacheKey {
|
||||
@@ -34,7 +32,7 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
data class Executable(val sourceModules: Set<TestModule>) : TestCompilationCacheKey
|
||||
}
|
||||
|
||||
fun testCasesToExecutable(testCases: Collection<TestCase>): TestCompilation {
|
||||
fun testCasesToExecutable(testCases: Collection<TestCase>, settings: Settings): TestCompilation {
|
||||
val rootModules = testCases.flatMapToSet { testCase -> testCase.rootModules }
|
||||
val cacheKey = TestCompilationCacheKey.Executable(rootModules)
|
||||
|
||||
@@ -44,16 +42,18 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
// Long pass.
|
||||
val freeCompilerArgs = rootModules.first().testCase.freeCompilerArgs // Should be identical inside the same test case group.
|
||||
val extras = testCases.first().extras // Should be identical inside the same test case group.
|
||||
val libraries = rootModules.flatMapToSet { it.allDependencies }.map { moduleToKlib(it, freeCompilerArgs) }
|
||||
val friends = rootModules.flatMapToSet { it.allFriends }.map { moduleToKlib(it, freeCompilerArgs) }
|
||||
val libraries = rootModules.flatMapToSet { it.allDependencies }.map { moduleToKlib(it, freeCompilerArgs, settings) }
|
||||
val friends = rootModules.flatMapToSet { it.allFriends }.map { moduleToKlib(it, freeCompilerArgs, settings) }
|
||||
|
||||
return cachedCompilations.computeIfAbsent(cacheKey) {
|
||||
TestCompilationImpl(
|
||||
settings = settings,
|
||||
targets = settings.get(),
|
||||
home = settings.get(),
|
||||
classLoader = settings.get(),
|
||||
freeCompilerArgs = freeCompilerArgs,
|
||||
sourceModules = rootModules,
|
||||
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
|
||||
expectedArtifactFile = artifactFileForExecutable(rootModules),
|
||||
expectedArtifactFile = settings.artifactFileForExecutable(rootModules),
|
||||
specificCompilerArgs = {
|
||||
add("-produce", "program")
|
||||
when (extras) {
|
||||
@@ -67,7 +67,7 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
add(testRunnerArg)
|
||||
}
|
||||
}
|
||||
settings.get<GlobalSettings>().getRootCacheDirectory(debuggable = true)?.let { rootCacheDir ->
|
||||
settings.getRootCacheDirectory(debuggable = true)?.let { rootCacheDir ->
|
||||
add("-Xcache-directory=$rootCacheDir")
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun moduleToKlib(sourceModule: TestModule, freeCompilerArgs: TestCompilerArgs): TestCompilation {
|
||||
private fun moduleToKlib(sourceModule: TestModule, freeCompilerArgs: TestCompilerArgs, settings: Settings): TestCompilation {
|
||||
val sourceModules = setOf(sourceModule)
|
||||
val cacheKey = TestCompilationCacheKey.Klib(sourceModules, freeCompilerArgs)
|
||||
|
||||
@@ -83,35 +83,37 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
cachedCompilations[cacheKey]?.let { return it }
|
||||
|
||||
// Long pass.
|
||||
val libraries = sourceModule.allDependencies.map { moduleToKlib(it, freeCompilerArgs) }
|
||||
val friends = sourceModule.allFriends.map { moduleToKlib(it, freeCompilerArgs) }
|
||||
val libraries = sourceModule.allDependencies.map { moduleToKlib(it, freeCompilerArgs, settings) }
|
||||
val friends = sourceModule.allFriends.map { moduleToKlib(it, freeCompilerArgs, settings) }
|
||||
|
||||
return cachedCompilations.computeIfAbsent(cacheKey) {
|
||||
TestCompilationImpl(
|
||||
settings = settings,
|
||||
targets = settings.get(),
|
||||
home = settings.get(),
|
||||
classLoader = settings.get(),
|
||||
freeCompilerArgs = freeCompilerArgs,
|
||||
sourceModules = sourceModules,
|
||||
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
|
||||
expectedArtifactFile = artifactFileForKlib(sourceModule, freeCompilerArgs),
|
||||
expectedArtifactFile = settings.artifactFileForKlib(sourceModule, freeCompilerArgs),
|
||||
specificCompilerArgs = { add("-produce", "library") }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
|
||||
private fun Settings.artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
|
||||
1 -> artifactFileForExecutable(modules.first())
|
||||
else -> multiModuleArtifactFile(modules, settings.get<GlobalSettings>().target.family.exeSuffix)
|
||||
else -> multiModuleArtifactFile(modules, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
|
||||
}
|
||||
|
||||
private fun artifactFileForExecutable(module: TestModule.Exclusive) =
|
||||
singleModuleArtifactFile(module, settings.get<GlobalSettings>().target.family.exeSuffix)
|
||||
private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) =
|
||||
singleModuleArtifactFile(module, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
|
||||
|
||||
private fun artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) {
|
||||
private fun Settings.artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) {
|
||||
is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib")
|
||||
is TestModule.Shared -> settings.get<Binaries>().sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
|
||||
is TestModule.Shared -> get<Binaries>().sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
|
||||
}
|
||||
|
||||
private fun singleModuleArtifactFile(module: TestModule.Exclusive, extension: String): File {
|
||||
private fun Settings.singleModuleArtifactFile(module: TestModule.Exclusive, extension: String): File {
|
||||
val artifactFileName = buildString {
|
||||
append(module.testCase.nominalPackageName.compressedPackageName).append('.')
|
||||
if (extension == "klib") append(module.name).append('.')
|
||||
@@ -120,7 +122,7 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
return artifactDirForPackageName(module.testCase.nominalPackageName).resolve(artifactFileName)
|
||||
}
|
||||
|
||||
private fun multiModuleArtifactFile(modules: Collection<TestModule>, extension: String): File {
|
||||
private fun Settings.multiModuleArtifactFile(modules: Collection<TestModule>, extension: String): File {
|
||||
var filesCount = 0
|
||||
var hash = 0
|
||||
val uniquePackageNames = hashSetOf<PackageName>()
|
||||
@@ -153,8 +155,8 @@ internal class TestCompilationFactory(private val settings: Settings) {
|
||||
return artifactDirForPackageName(commonPackageName).resolve(artifactFileName)
|
||||
}
|
||||
|
||||
private fun artifactDirForPackageName(packageName: PackageName?): File {
|
||||
val baseDir = settings.get<Binaries>().testBinariesDir
|
||||
private fun Settings.artifactDirForPackageName(packageName: PackageName?): File {
|
||||
val baseDir = get<Binaries>().testBinariesDir
|
||||
val outputDir = if (packageName != null) baseDir.resolve(packageName.compressedPackageName) else baseDir
|
||||
|
||||
outputDir.mkdirs()
|
||||
@@ -234,7 +236,9 @@ internal class TestCompilationDependencies(
|
||||
}
|
||||
|
||||
private class TestCompilationImpl(
|
||||
private val settings: Settings,
|
||||
private val targets: KotlinNativeTargets,
|
||||
private val home: KotlinNativeHome,
|
||||
private val classLoader: KotlinNativeClassLoader,
|
||||
private val freeCompilerArgs: TestCompilerArgs,
|
||||
private val sourceModules: Collection<TestModule>,
|
||||
private val dependencies: TestCompilationDependencies,
|
||||
@@ -254,8 +258,8 @@ private class TestCompilationImpl(
|
||||
add(
|
||||
"-enable-assertions",
|
||||
"-g",
|
||||
"-target", settings.get<GlobalSettings>().target.name,
|
||||
"-repo", settings.get<GlobalSettings>().kotlinNativeHome.resolve("klib").path,
|
||||
"-target", targets.testTarget.name,
|
||||
"-repo", home.dir.resolve("klib").path,
|
||||
"-output", expectedArtifactFile.path,
|
||||
"-Xskip-prerelease-check",
|
||||
"-Xverify-ir",
|
||||
@@ -286,7 +290,7 @@ private class TestCompilationImpl(
|
||||
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
|
||||
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
|
||||
compilerArgs = compilerArgs,
|
||||
lazyKotlinNativeClassLoader = settings.get<GlobalSettings>().lazyKotlinNativeClassLoader
|
||||
kotlinNativeClassLoader = classLoader.classLoader
|
||||
)
|
||||
|
||||
val loggedCompilerCall =
|
||||
@@ -341,14 +345,12 @@ private inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array<Strin
|
||||
return ArgsBuilder().apply(builderAction).build()
|
||||
}
|
||||
|
||||
private fun callCompiler(compilerArgs: Array<String>, lazyKotlinNativeClassLoader: Lazy<ClassLoader>): CompilerCallResult {
|
||||
private fun callCompiler(compilerArgs: Array<String>, kotlinNativeClassLoader: ClassLoader): CompilerCallResult {
|
||||
val compilerXmlOutput: ByteArrayOutputStream
|
||||
val exitCode: ExitCode
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
val duration = measureTime {
|
||||
val kotlinNativeClassLoader by lazyKotlinNativeClassLoader
|
||||
|
||||
val servicesClass = Class.forName(Services::class.java.canonicalName, true, kotlinNativeClassLoader)
|
||||
val emptyServices = servicesClass.getField("EMPTY").get(servicesClass)
|
||||
|
||||
|
||||
+40
-25
@@ -7,6 +7,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.konan.blackboxtest.support
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
|
||||
@@ -15,8 +16,9 @@ import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvid
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestNameExtractor
|
||||
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.KotlinNativeTargets
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Timeouts
|
||||
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.buildTree
|
||||
@@ -27,10 +29,9 @@ import org.junit.jupiter.api.Assumptions.assumeTrue
|
||||
import org.junit.jupiter.api.extension.ExtensionContext
|
||||
|
||||
internal class TestRunProvider(
|
||||
private val settings: Settings,
|
||||
private val testCaseGroupProvider: TestCaseGroupProvider
|
||||
) : ExtensionContext.Store.CloseableResource {
|
||||
private val compilationFactory = TestCompilationFactory(settings)
|
||||
private val compilationFactory = TestCompilationFactory()
|
||||
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
|
||||
private val cachedTestNames = ThreadSafeCache<TestCompilationCacheKey, Collection<TestName>>()
|
||||
|
||||
@@ -59,7 +60,10 @@ internal class TestRunProvider(
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
fun getSingleTestRun(testCaseId: TestCaseId): TestRun = withTestExecutable(testCaseId) { testCase, executable, _ ->
|
||||
fun getSingleTestRun(
|
||||
testCaseId: TestCaseId,
|
||||
settings: Settings
|
||||
): TestRun = withTestExecutable(testCaseId, settings) { testCase, executable, _ ->
|
||||
val runParameters = getRunParameters(testCase, testName = null)
|
||||
TestRun(displayName = /* Unimportant. Used only in dynamic tests. */ "", executable, runParameters, testCase.id)
|
||||
}
|
||||
@@ -95,8 +99,9 @@ internal class TestRunProvider(
|
||||
* }
|
||||
*/
|
||||
fun getTestRuns(
|
||||
testCaseId: TestCaseId
|
||||
): Collection<TreeNode<TestRun>> = withTestExecutable(testCaseId) { testCase, executable, cacheKey ->
|
||||
testCaseId: TestCaseId,
|
||||
settings: Settings
|
||||
): Collection<TreeNode<TestRun>> = withTestExecutable(testCaseId, settings) { testCase, executable, cacheKey ->
|
||||
fun createTestRun(testRunName: String, testName: TestName?): TestRun {
|
||||
val runParameters = getRunParameters(testCase, testName)
|
||||
return TestRun(testRunName, executable, runParameters, testCase.id)
|
||||
@@ -110,7 +115,7 @@ internal class TestRunProvider(
|
||||
}
|
||||
TestKind.REGULAR, TestKind.STANDALONE -> {
|
||||
val testNames = cachedTestNames.computeIfAbsent(cacheKey) {
|
||||
extractTestNames(executable)
|
||||
extractTestNames(executable, settings)
|
||||
}.filterIrrelevant(testCase)
|
||||
|
||||
testNames.buildTree(TestName::packageName) { testName ->
|
||||
@@ -120,10 +125,14 @@ internal class TestRunProvider(
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> withTestExecutable(testCaseId: TestCaseId, action: (TestCase, TestExecutable, TestCompilationCacheKey) -> T): T {
|
||||
settings.assertNotDisposed()
|
||||
private fun <T> withTestExecutable(
|
||||
testCaseId: TestCaseId,
|
||||
settings: Settings,
|
||||
action: (TestCase, TestExecutable, TestCompilationCacheKey) -> T
|
||||
): T {
|
||||
val testCaseGroup = testCaseGroupProvider.getTestCaseGroup(testCaseId.testCaseGroupId, settings)
|
||||
?: fail { "No test case for $testCaseId" }
|
||||
|
||||
val testCaseGroup = testCaseGroupProvider.getTestCaseGroup(testCaseId.testCaseGroupId) ?: fail { "No test case for $testCaseId" }
|
||||
assumeTrue(testCaseGroup.isEnabled(testCaseId), "Test case is disabled")
|
||||
|
||||
val testCase = testCaseGroup.getByName(testCaseId) ?: fail { "No test case for $testCaseId" }
|
||||
@@ -133,7 +142,7 @@ internal class TestRunProvider(
|
||||
// Create a separate compilation for each standalone test case.
|
||||
val cacheKey = TestCompilationCacheKey.Standalone(testCaseId)
|
||||
val testCompilation = cachedCompilations.computeIfAbsent(cacheKey) {
|
||||
compilationFactory.testCasesToExecutable(listOf(testCase))
|
||||
compilationFactory.testCasesToExecutable(listOf(testCase), settings)
|
||||
}
|
||||
testCompilation to cacheKey
|
||||
}
|
||||
@@ -144,7 +153,7 @@ internal class TestRunProvider(
|
||||
val testCompilation = cachedCompilations.computeIfAbsent(cacheKey) {
|
||||
val testCases = testCaseGroup.getRegularOnly(testCase.freeCompilerArgs, testRunnerType)
|
||||
assertTrue(testCases.isNotEmpty())
|
||||
compilationFactory.testCasesToExecutable(testCases)
|
||||
compilationFactory.testCasesToExecutable(testCases, settings)
|
||||
}
|
||||
testCompilation to cacheKey
|
||||
}
|
||||
@@ -179,19 +188,23 @@ internal class TestRunProvider(
|
||||
}
|
||||
|
||||
// Currently, only local test runner is supported.
|
||||
fun createRunner(testRun: TestRun): AbstractRunner<*> = with(settings.get<GlobalSettings>()) {
|
||||
if (target == hostTarget)
|
||||
LocalTestRunner(testRun, executionTimeout)
|
||||
else
|
||||
runningAtNonHostTarget()
|
||||
fun createRunner(testRun: TestRun, settings: Settings): AbstractRunner<*> = with(settings) {
|
||||
with(get<KotlinNativeTargets>()) {
|
||||
if (testTarget == hostTarget)
|
||||
LocalTestRunner(testRun, get<Timeouts>().executionTimeout)
|
||||
else
|
||||
runningAtNonHostTarget()
|
||||
}
|
||||
}
|
||||
|
||||
// Currently, only local test name extractor is supported.
|
||||
private fun extractTestNames(executable: TestExecutable): Collection<TestName> = with(settings.get<GlobalSettings>()) {
|
||||
if (target == hostTarget)
|
||||
LocalTestNameExtractor(executable, executionTimeout).run()
|
||||
else
|
||||
runningAtNonHostTarget()
|
||||
private fun extractTestNames(executable: TestExecutable, settings: Settings): Collection<TestName> = with(settings) {
|
||||
with(get<KotlinNativeTargets>()) {
|
||||
if (testTarget == hostTarget)
|
||||
LocalTestNameExtractor(executable, get<Timeouts>().executionTimeout).run()
|
||||
else
|
||||
runningAtNonHostTarget()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Collection<TestName>.filterIrrelevant(testCase: TestCase) =
|
||||
@@ -200,16 +213,18 @@ internal class TestRunProvider(
|
||||
else
|
||||
this
|
||||
|
||||
private fun GlobalSettings.runningAtNonHostTarget(): Nothing = fail {
|
||||
private fun KotlinNativeTargets.runningAtNonHostTarget(): Nothing = fail {
|
||||
"""
|
||||
Running at non-host target is not supported yet.
|
||||
Compilation target: $target
|
||||
Compilation target: $testTarget
|
||||
Host target: $hostTarget
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
Disposer.dispose(settings)
|
||||
if (testCaseGroupProvider is Disposable) {
|
||||
Disposer.dispose(testCaseGroupProvider)
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestCompilationCacheKey {
|
||||
|
||||
+50
-46
@@ -19,6 +19,7 @@ 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.ExternalSourceTransformersProvider
|
||||
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
|
||||
@@ -37,48 +38,51 @@ import org.jetbrains.kotlin.test.InTextDirectivesUtils.isIgnoredTarget
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import java.io.File
|
||||
|
||||
internal class ExtTestCaseGroupProvider(
|
||||
private val settings: Settings,
|
||||
private val sourceTransformersProvider: ExternalSourceTransformersProvider
|
||||
) : TestCaseGroupProvider, TestDisposable(parentDisposable = settings) {
|
||||
internal class ExtTestCaseGroupProvider : TestCaseGroupProvider, TestDisposable(parentDisposable = null) {
|
||||
private val structureFactory = ExtTestDataFileStructureFactory(parentDisposable = this)
|
||||
private val sharedModules = ThreadSafeCache<String, TestModule.Shared?>()
|
||||
|
||||
private val lazyTestCaseGroups = ThreadSafeFactory<TestCaseGroupId.TestDataDir, TestCaseGroup?> { testCaseGroupId ->
|
||||
if (testCaseGroupId.dir in excludes) return@ThreadSafeFactory TestCaseGroup.ALL_DISABLED
|
||||
private val cachedTestCaseGroups = ThreadSafeCache<TestCaseGroupId.TestDataDir, TestCaseGroup?>()
|
||||
|
||||
val (excludedTestDataFiles, testDataFiles) = testCaseGroupId.dir.listFiles()
|
||||
?.filter { file -> file.isFile && file.extension == "kt" }
|
||||
?.partition { file -> file in excludes }
|
||||
?: return@ThreadSafeFactory null
|
||||
|
||||
val disabledTestCaseIds = hashSetOf<TestCaseId>()
|
||||
excludedTestDataFiles.mapTo(disabledTestCaseIds, TestCaseId::TestDataFile)
|
||||
|
||||
val testCases = mutableListOf<TestCase>()
|
||||
|
||||
testDataFiles.forEach { testDataFile ->
|
||||
val extTestDataFile = ExtTestDataFile(settings, structureFactory, sourceTransformersProvider, testDataFile)
|
||||
|
||||
if (extTestDataFile.isRelevant)
|
||||
testCases += extTestDataFile.createTestCase(
|
||||
definitelyStandaloneTest = testDataFile in standalones,
|
||||
sharedModules = sharedModules
|
||||
)
|
||||
else
|
||||
disabledTestCaseIds += TestCaseId.TestDataFile(testDataFile)
|
||||
}
|
||||
|
||||
TestCaseGroup.Default(disabledTestCaseIds, testCases)
|
||||
}
|
||||
|
||||
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId): TestCaseGroup? {
|
||||
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId, settings: Settings): TestCaseGroup? {
|
||||
assertNotDisposed()
|
||||
assertTrue(testCaseGroupId is TestCaseGroupId.TestDataDir)
|
||||
return lazyTestCaseGroups[testCaseGroupId.cast()]
|
||||
check(testCaseGroupId is TestCaseGroupId.TestDataDir)
|
||||
|
||||
return cachedTestCaseGroups.computeIfAbsent(testCaseGroupId) {
|
||||
if (testCaseGroupId.dir in excludes) return@computeIfAbsent TestCaseGroup.ALL_DISABLED
|
||||
|
||||
val (excludedTestDataFiles, testDataFiles) = testCaseGroupId.dir.listFiles()
|
||||
?.filter { file -> file.isFile && file.extension == "kt" }
|
||||
?.partition { file -> file in excludes }
|
||||
?: return@computeIfAbsent null
|
||||
|
||||
val disabledTestCaseIds = hashSetOf<TestCaseId>()
|
||||
excludedTestDataFiles.mapTo(disabledTestCaseIds, TestCaseId::TestDataFile)
|
||||
|
||||
val testCases = mutableListOf<TestCase>()
|
||||
|
||||
testDataFiles.forEach { testDataFile ->
|
||||
val extTestDataFile = ExtTestDataFile(
|
||||
testDataFile = testDataFile,
|
||||
structureFactory = structureFactory,
|
||||
customSourceTransformers = settings.get<ExternalSourceTransformersProvider>().getSourceTransformers(testDataFile),
|
||||
testRoots = settings.get(),
|
||||
generatedSources = settings.get()
|
||||
)
|
||||
|
||||
if (extTestDataFile.isRelevant)
|
||||
testCases += extTestDataFile.createTestCase(
|
||||
definitelyStandaloneTest = testDataFile in standalones,
|
||||
sharedModules = sharedModules
|
||||
)
|
||||
else
|
||||
disabledTestCaseIds += TestCaseId.TestDataFile(testDataFile)
|
||||
}
|
||||
|
||||
TestCaseGroup.Default(disabledTestCaseIds, testCases)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -122,19 +126,19 @@ internal class ExtTestCaseGroupProvider(
|
||||
}
|
||||
|
||||
private class ExtTestDataFile(
|
||||
private val settings: Settings,
|
||||
private val testDataFile: File,
|
||||
structureFactory: ExtTestDataFileStructureFactory,
|
||||
sourceTransformersProvider: ExternalSourceTransformersProvider,
|
||||
private val testDataFile: File
|
||||
customSourceTransformers: ExternalSourceTransformers?,
|
||||
testRoots: TestRoots,
|
||||
private val generatedSources: GeneratedSources
|
||||
) {
|
||||
private val structure by lazy {
|
||||
val customTransformers: ExternalSourceTransformers? = sourceTransformersProvider.getSourceTransformers(testDataFile)
|
||||
val allTransformers: ExternalSourceTransformers = if (customTransformers.isNullOrEmpty())
|
||||
val allSourceTransformers: ExternalSourceTransformers = if (customSourceTransformers.isNullOrEmpty())
|
||||
MANDATORY_SOURCE_TRANSFORMERS
|
||||
else
|
||||
MANDATORY_SOURCE_TRANSFORMERS + customTransformers
|
||||
MANDATORY_SOURCE_TRANSFORMERS + customSourceTransformers
|
||||
|
||||
structureFactory.ExtTestDataFileStructure(testDataFile, allTransformers)
|
||||
structureFactory.ExtTestDataFileStructure(testDataFile, allSourceTransformers)
|
||||
}
|
||||
|
||||
private val testDataFileSettings by lazy {
|
||||
@@ -152,12 +156,12 @@ private class ExtTestDataFile(
|
||||
optInsForCompiler = optInsForCompiler,
|
||||
expectActualLinker = EXPECT_ACTUAL_LINKER_DIRECTIVE in structure.directives,
|
||||
generatedSourcesDir = computeGeneratedSourcesDir(
|
||||
testDataBaseDir = settings.get<TestRoots>().baseDir,
|
||||
testDataBaseDir = testRoots.baseDir,
|
||||
testDataFile = testDataFile,
|
||||
generatedSourcesBaseDir = settings.get<GeneratedSources>().testSourcesDir
|
||||
generatedSourcesBaseDir = generatedSources.testSourcesDir
|
||||
),
|
||||
nominalPackageName = computePackageName(
|
||||
testDataBaseDir = settings.get<TestRoots>().baseDir,
|
||||
testDataBaseDir = testRoots.baseDir,
|
||||
testDataFile = testDataFile
|
||||
)
|
||||
)
|
||||
@@ -575,7 +579,7 @@ private class ExtTestDataFile(
|
||||
testCaseDir = testDataFileSettings.generatedSourcesDir,
|
||||
findOrGenerateSharedModule = { moduleName: String, generator: SharedModuleGenerator ->
|
||||
sharedModules.computeIfAbsent(moduleName) {
|
||||
generator(settings.get<GeneratedSources>().sharedSourcesDir)
|
||||
generator(generatedSources.sharedSourcesDir)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
+38
-38
@@ -7,17 +7,16 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group
|
||||
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.*
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.GlobalSettings
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeFactory
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ThreadSafeCache
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.expandGlobTo
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import org.junit.jupiter.api.fail
|
||||
import java.io.File
|
||||
|
||||
internal class PredefinedTestCaseGroupProvider(private val settings: Settings, annotation: PredefinedTestCases) : TestCaseGroupProvider {
|
||||
internal class PredefinedTestCaseGroupProvider(annotation: PredefinedTestCases) : TestCaseGroupProvider {
|
||||
private val testCaseIdToPredefinedTestCase: Map<TestCaseId.Named, PredefinedTestCase> = buildMap {
|
||||
annotation.testCases.forEach { predefinedTestCase ->
|
||||
val testCaseId = TestCaseId.Named(predefinedTestCase.name)
|
||||
@@ -36,55 +35,56 @@ internal class PredefinedTestCaseGroupProvider(private val settings: Settings, a
|
||||
}
|
||||
}
|
||||
|
||||
private val lazyTestCaseGroups = ThreadSafeFactory<TestCaseGroupId.Named, TestCaseGroup?> { testCaseGroupId ->
|
||||
val testCaseId = testCaseGroupIdToTestCaseId[testCaseGroupId] ?: return@ThreadSafeFactory null
|
||||
val predefinedTestCase = testCaseIdToPredefinedTestCase[testCaseId] ?: return@ThreadSafeFactory null
|
||||
private val cachedTestCaseGroups = ThreadSafeCache<TestCaseGroupId.Named, TestCaseGroup?>()
|
||||
|
||||
val module = TestModule.Exclusive(
|
||||
name = testCaseId.uniqueName,
|
||||
directDependencySymbols = emptySet(),
|
||||
directFriendSymbols = emptySet()
|
||||
)
|
||||
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId, settings: Settings): TestCaseGroup? {
|
||||
check(testCaseGroupId is TestCaseGroupId.Named)
|
||||
|
||||
predefinedTestCase.sourceLocations
|
||||
.expandGlobs { "No files found for test case $testCaseId" }
|
||||
.forEach { file -> module.files += TestFile.createCommitted(file, module) }
|
||||
return cachedTestCaseGroups.computeIfAbsent(testCaseGroupId) {
|
||||
val testCaseId = testCaseGroupIdToTestCaseId[testCaseGroupId] ?: return@computeIfAbsent null
|
||||
val predefinedTestCase = testCaseIdToPredefinedTestCase[testCaseId] ?: return@computeIfAbsent null
|
||||
|
||||
val testCase = TestCase(
|
||||
id = testCaseId,
|
||||
kind = TestKind.STANDALONE,
|
||||
modules = setOf(module),
|
||||
freeCompilerArgs = predefinedTestCase.freeCompilerArgs
|
||||
.parseCompilerArgs { "Failed to parse free compiler arguments for test case $testCaseId" },
|
||||
nominalPackageName = PackageName(testCaseId.uniqueName),
|
||||
expectedOutputDataFile = null,
|
||||
extras = WithTestRunnerExtras(runnerType = predefinedTestCase.runnerType)
|
||||
)
|
||||
testCase.initialize(null)
|
||||
val module = TestModule.Exclusive(
|
||||
name = testCaseId.uniqueName,
|
||||
directDependencySymbols = emptySet(),
|
||||
directFriendSymbols = emptySet()
|
||||
)
|
||||
|
||||
TestCaseGroup.Default(disabledTestCaseIds = emptySet(), testCases = listOf(testCase))
|
||||
predefinedTestCase.sourceLocations
|
||||
.expandGlobs(settings) { "No files found for test case $testCaseId" }
|
||||
.forEach { file -> module.files += TestFile.createCommitted(file, module) }
|
||||
|
||||
val testCase = TestCase(
|
||||
id = testCaseId,
|
||||
kind = TestKind.STANDALONE,
|
||||
modules = setOf(module),
|
||||
freeCompilerArgs = predefinedTestCase.freeCompilerArgs
|
||||
.parseCompilerArgs(settings) { "Failed to parse free compiler arguments for test case $testCaseId" },
|
||||
nominalPackageName = PackageName(testCaseId.uniqueName),
|
||||
expectedOutputDataFile = null,
|
||||
extras = WithTestRunnerExtras(runnerType = predefinedTestCase.runnerType)
|
||||
)
|
||||
testCase.initialize(null)
|
||||
|
||||
TestCaseGroup.Default(disabledTestCaseIds = emptySet(), testCases = listOf(testCase))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId): TestCaseGroup? {
|
||||
assertTrue(testCaseGroupId is TestCaseGroupId.Named)
|
||||
return lazyTestCaseGroups[testCaseGroupId.cast()]
|
||||
}
|
||||
|
||||
private fun Array<String>.expandGlobs(noExpandedFilesErrorMessage: () -> String): Set<File> {
|
||||
private fun Array<String>.expandGlobs(settings: Settings, noExpandedFilesErrorMessage: () -> String): Set<File> {
|
||||
val files = buildSet {
|
||||
this@expandGlobs.forEach { pathPattern ->
|
||||
expandGlobTo(getAbsoluteFile(substituteRealPaths(pathPattern)), this)
|
||||
expandGlobTo(getAbsoluteFile(substituteRealPaths(pathPattern, settings)), this)
|
||||
}
|
||||
}
|
||||
assertTrue(files.isNotEmpty(), noExpandedFilesErrorMessage)
|
||||
return files
|
||||
}
|
||||
|
||||
private fun Array<String>.parseCompilerArgs(parsingErrorMessage: () -> String): TestCompilerArgs =
|
||||
private fun Array<String>.parseCompilerArgs(settings: Settings, parsingErrorMessage: () -> String): TestCompilerArgs =
|
||||
if (isEmpty())
|
||||
TestCompilerArgs.EMPTY
|
||||
else {
|
||||
val freeCompilerArgs = map { arg -> substituteRealPaths(arg) }
|
||||
val freeCompilerArgs = map { arg -> substituteRealPaths(arg, settings) }
|
||||
val forbiddenCompilerArgs = TestCompilerArgs.findForbiddenArgs(freeCompilerArgs)
|
||||
assertTrue(forbiddenCompilerArgs.isEmpty()) {
|
||||
"""
|
||||
@@ -98,10 +98,10 @@ internal class PredefinedTestCaseGroupProvider(private val settings: Settings, a
|
||||
TestCompilerArgs(freeCompilerArgs)
|
||||
}
|
||||
|
||||
private fun substituteRealPaths(value: String): String =
|
||||
private fun substituteRealPaths(value: String, settings: Settings): String =
|
||||
if ('$' in value) {
|
||||
// N.B. Here, more substitutions can be supported in the future if it would be necessary.
|
||||
value.replace(PredefinedPaths.KOTLIN_NATIVE_DISTRIBUTION, settings.get<GlobalSettings>().kotlinNativeHome.path)
|
||||
value.replace(PredefinedPaths.KOTLIN_NATIVE_DISTRIBUTION, settings.get<KotlinNativeHome>().path)
|
||||
} else
|
||||
value
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,10 +6,10 @@
|
||||
package org.jetbrains.kotlin.konan.blackboxtest.support.group
|
||||
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestRunnerType
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestSettings
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestConfiguration
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@TestSettings(providerClass = PredefinedTestCaseGroupProvider::class)
|
||||
@TestConfiguration(providerClass = PredefinedTestCaseGroupProvider::class)
|
||||
internal annotation class PredefinedTestCases(vararg val testCases: PredefinedTestCase)
|
||||
|
||||
@Target()
|
||||
|
||||
+17
-19
@@ -20,33 +20,31 @@ import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertNotEquals
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||
import org.jetbrains.kotlin.test.services.impl.RegisteredDirectivesParser
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import java.io.File
|
||||
|
||||
internal class StandardTestCaseGroupProvider(private val settings: Settings) : TestCaseGroupProvider {
|
||||
val sourceTransformers: MutableMap<String, List<(String) -> String>> = mutableMapOf()
|
||||
internal class StandardTestCaseGroupProvider : TestCaseGroupProvider {
|
||||
// Create and cache test cases in groups on demand.
|
||||
private val cachedTestCaseGroups = ThreadSafeCache<TestCaseGroupId.TestDataDir, TestCaseGroup?>()
|
||||
|
||||
// Load test cases in groups on demand.
|
||||
private val lazyTestCaseGroups = ThreadSafeFactory<TestCaseGroupId.TestDataDir, TestCaseGroup?> { testCaseGroupId ->
|
||||
val testDataFiles = testCaseGroupId.dir.listFiles()
|
||||
?: return@ThreadSafeFactory null // `null` means that there is no such testDataDir.
|
||||
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId, settings: Settings): TestCaseGroup? {
|
||||
check(testCaseGroupId is TestCaseGroupId.TestDataDir)
|
||||
|
||||
val testCases = testDataFiles.mapNotNull { testDataFile ->
|
||||
if (!testDataFile.isFile || testDataFile.extension != "kt")
|
||||
return@mapNotNull null
|
||||
return cachedTestCaseGroups.computeIfAbsent(testCaseGroupId) {
|
||||
val testDataFiles = testCaseGroupId.dir.listFiles()
|
||||
?: return@computeIfAbsent null // `null` means that there is no such testDataDir.
|
||||
|
||||
createTestCase(testDataFile)
|
||||
val testCases = testDataFiles.mapNotNull { testDataFile ->
|
||||
if (!testDataFile.isFile || testDataFile.extension != "kt")
|
||||
return@mapNotNull null
|
||||
|
||||
createTestCase(testDataFile, settings)
|
||||
}
|
||||
|
||||
TestCaseGroup.Default(disabledTestCaseIds = emptySet(), testCases = testCases)
|
||||
}
|
||||
|
||||
TestCaseGroup.Default(disabledTestCaseIds = emptySet(), testCases = testCases)
|
||||
}
|
||||
|
||||
override fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId): TestCaseGroup? {
|
||||
assertTrue(testCaseGroupId is TestCaseGroupId.TestDataDir)
|
||||
return lazyTestCaseGroups[testCaseGroupId.cast()]
|
||||
}
|
||||
|
||||
private fun createTestCase(testDataFile: File): TestCase {
|
||||
private fun createTestCase(testDataFile: File, settings: Settings): TestCase {
|
||||
val generatedSourcesDir = computeGeneratedSourcesDir(
|
||||
testDataBaseDir = settings.get<TestRoots>().baseDir,
|
||||
testDataFile = testDataFile,
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@ package org.jetbrains.kotlin.konan.blackboxtest.support.group
|
||||
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseGroup
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseGroupId
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
|
||||
|
||||
internal interface TestCaseGroupProvider {
|
||||
fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId): TestCaseGroup?
|
||||
fun getTestCaseGroup(testCaseGroupId: TestCaseGroupId, settings: Settings): TestCaseGroup?
|
||||
}
|
||||
+2
-2
@@ -7,8 +7,8 @@ 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
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestConfiguration
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@TestSettings(providerClass = ExtTestCaseGroupProvider::class, requiredSettings = [TestRoots::class, GeneratedSources::class])
|
||||
@TestConfiguration(providerClass = ExtTestCaseGroupProvider::class, requiredSettings = [TestRoots::class, GeneratedSources::class])
|
||||
annotation class UseExtTestCaseGroupProvider
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ 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
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.TestConfiguration
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@TestSettings(providerClass = StandardTestCaseGroupProvider::class, requiredSettings = [TestRoots::class, GeneratedSources::class])
|
||||
@TestConfiguration(providerClass = StandardTestCaseGroupProvider::class, requiredSettings = [TestRoots::class, GeneratedSources::class])
|
||||
annotation class UseStandardTestCaseGroupProvider
|
||||
|
||||
-105
@@ -1,105 +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.target.HostManager
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class GlobalSettings(
|
||||
val target: KonanTarget = HostManager.host,
|
||||
val kotlinNativeHome: File = defaultKotlinNativeHome,
|
||||
val lazyKotlinNativeClassLoader: Lazy<ClassLoader> = defaultKotlinNativeClassLoader,
|
||||
val testMode: TestMode = defaultTestMode,
|
||||
val cacheSettings: CacheSettings = defaultCacheSettings,
|
||||
val executionTimeout: Duration = defaultExecutionTimeout,
|
||||
val baseBuildDir: File = projectBuildDir
|
||||
) {
|
||||
val hostTarget: KonanTarget = HostManager.host
|
||||
|
||||
fun getRootCacheDirectory(debuggable: Boolean): File? =
|
||||
(cacheSettings as? CacheSettings.WithCache)?.getRootCacheDirectory(this, debuggable)
|
||||
|
||||
companion object {
|
||||
private val defaultKotlinNativeHome: File
|
||||
get() = System.getProperty(KOTLIN_NATIVE_HOME)?.let(::File) ?: fail { "Non-specified $KOTLIN_NATIVE_HOME system property" }
|
||||
|
||||
// Use isolated cached class loader.
|
||||
private val defaultKotlinNativeClassLoader: Lazy<ClassLoader> = lazy {
|
||||
val nativeClassPath = System.getProperty(COMPILER_CLASSPATH)
|
||||
?.split(':', ';')
|
||||
?.map { File(it).toURI().toURL() }
|
||||
?.toTypedArray()
|
||||
?: fail { "Non-specified $COMPILER_CLASSPATH system property" }
|
||||
|
||||
URLClassLoader(nativeClassPath, /* no parent class loader */ null).apply { setDefaultAssertionStatus(true) }
|
||||
}
|
||||
|
||||
private val defaultTestMode: TestMode = run {
|
||||
val testModeName = System.getProperty(TEST_MODE) ?: return@run TestMode.WITH_MODULES
|
||||
|
||||
TestMode.values().firstOrNull { it.name == testModeName } ?: fail {
|
||||
buildString {
|
||||
appendLine("Unknown test mode name $testModeName.")
|
||||
appendLine("One of the following test modes should be passed through $TEST_MODE system property:")
|
||||
TestMode.values().forEach { testMode ->
|
||||
appendLine("- ${testMode.name}: ${testMode.description}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultCacheSettings: CacheSettings = run {
|
||||
val useCacheValue = System.getProperty(USE_CACHE)
|
||||
val useCache = if (useCacheValue != null) {
|
||||
useCacheValue.toBooleanStrictOrNull() ?: fail { "Invalid value for $USE_CACHE system property: $useCacheValue" }
|
||||
} else
|
||||
true
|
||||
|
||||
if (useCache) CacheSettings.WithCache else CacheSettings.WithoutCache
|
||||
}
|
||||
|
||||
private val defaultExecutionTimeout: Duration = run {
|
||||
val executionTimeoutValue = System.getProperty(EXECUTION_TIMEOUT)
|
||||
if (executionTimeoutValue != null) {
|
||||
executionTimeoutValue.toLongOrNull()?.milliseconds
|
||||
?: fail { "Invalid value for $EXECUTION_TIMEOUT system property: $executionTimeoutValue" }
|
||||
} else
|
||||
DEFAULT_EXECUTION_TIMEOUT
|
||||
}
|
||||
|
||||
private val projectBuildDir: File
|
||||
get() = System.getenv(PROJECT_BUILD_DIR)?.let(::File) ?: fail { "Non-specified $PROJECT_BUILD_DIR environment variable" }
|
||||
|
||||
private const val KOTLIN_NATIVE_HOME = "kotlin.internal.native.test.nativeHome"
|
||||
private const val COMPILER_CLASSPATH = "kotlin.internal.native.test.compilerClasspath"
|
||||
private const val TEST_MODE = "kotlin.internal.native.test.mode"
|
||||
private const val USE_CACHE = "kotlin.internal.native.test.useCache"
|
||||
private const val EXECUTION_TIMEOUT = "kotlin.internal.native.test.executionTimeout"
|
||||
private const val PROJECT_BUILD_DIR = "PROJECT_BUILD_DIR"
|
||||
|
||||
private val DEFAULT_EXECUTION_TIMEOUT get() = 10.seconds // Use no backing field to avoid null-initialized value.
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed interface CacheSettings {
|
||||
object WithoutCache : CacheSettings
|
||||
|
||||
object WithCache : CacheSettings {
|
||||
fun getRootCacheDirectory(settings: GlobalSettings, debuggable: Boolean): File? = with(settings) {
|
||||
kotlinNativeHome.resolve("klib/cache").resolve(getCacheDirName(target, debuggable)).takeIf { it.exists() }
|
||||
}
|
||||
|
||||
private const val DEFAULT_CACHE_KIND = "STATIC"
|
||||
|
||||
private fun getCacheDirName(target: KonanTarget, debuggable: Boolean) = "$target${if (debuggable) "-g" else ""}$DEFAULT_CACHE_KIND"
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 gnu.trove.THashMap
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
|
||||
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal abstract class Settings(private val parent: Settings?, settings: Iterable<Any>) {
|
||||
private val map: Map<KClass<*>, Any> = THashMap<KClass<*>, Any>().apply {
|
||||
settings.forEach { it ->
|
||||
val (settingClass: KClass<*>, setting: Any) = if (it is Pair<*, *>) it.cast() else it::class to it
|
||||
val previous = put(settingClass, setting)
|
||||
assertTrue(previous == null) { "Duplicated settings: $settingClass, $previous, $setting" }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> get(clazz: KClass<out T>): T = map[clazz] as T?
|
||||
?: parent?.get(clazz)
|
||||
?: fail { "No such setting: $clazz" }
|
||||
|
||||
inline fun <reified T : Any> get(): T = get(T::class)
|
||||
}
|
||||
|
||||
/**
|
||||
* The hierarchy of settings containers:
|
||||
*
|
||||
* | Settings container | Parent | Scope |
|
||||
* | --------------------- | ---------------------- | ------------------------------------------- |
|
||||
* | [TestProcessSettings] | `null` | The whole Gradle test executor process |
|
||||
* | [TestClassSettings] | [TestProcessSettings] | The single top-level (enclosing) test class |
|
||||
* | [TestRunSettings] | [TestClassSettings] | The single test run of a test function |
|
||||
*/
|
||||
internal class TestProcessSettings(vararg settings: Any) : Settings(parent = null, settings.asIterable())
|
||||
internal class TestClassSettings(parent: TestProcessSettings, settings: Iterable<Any>) : Settings(parent, settings)
|
||||
internal class TestRunSettings(parent: TestClassSettings, settings: Iterable<Any>) : Settings(parent, settings)
|
||||
+5
-21
@@ -5,28 +5,7 @@
|
||||
|
||||
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(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" }
|
||||
}
|
||||
|
||||
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).
|
||||
@@ -44,3 +23,8 @@ internal class GeneratedSources(val testSourcesDir: File, val sharedSourcesDir:
|
||||
* [sharedBinariesDir] - The directory with compiled shared modules (klibs).
|
||||
*/
|
||||
internal class Binaries(val testBinariesDir: File, val sharedBinariesDir: File)
|
||||
|
||||
/**
|
||||
* The [TestConfiguration] of the current test class and the [Annotation] with specific parameters.
|
||||
*/
|
||||
internal data class ComputedTestConfiguration(val configuration: TestConfiguration, val annotation: Annotation)
|
||||
+1
-1
@@ -14,7 +14,7 @@ import java.lang.annotation.*;
|
||||
*/
|
||||
@Target(ElementType.ANNOTATION_TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TestSettings {
|
||||
public @interface TestConfiguration {
|
||||
Class<? extends TestCaseGroupProvider> providerClass();
|
||||
Class<?>[] requiredSettings() default {};
|
||||
}
|
||||
-20
@@ -1,20 +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
|
||||
|
||||
// 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."
|
||||
)
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.target.KonanTarget
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import java.io.File
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* The tested and the host Kotlin/Native targets.
|
||||
*/
|
||||
internal class KotlinNativeTargets(val testTarget: KonanTarget, val hostTarget: KonanTarget)
|
||||
|
||||
/**
|
||||
* The Kotlin/Native home.
|
||||
*/
|
||||
internal class KotlinNativeHome(val dir: File) {
|
||||
val path: String get() = dir.path
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-initialized class loader with the Kotlin/Native embedded compiler.
|
||||
*/
|
||||
internal class KotlinNativeClassLoader(private val lazyClassLoader: Lazy<ClassLoader>) {
|
||||
val classLoader: ClassLoader get() = lazyClassLoader.value
|
||||
}
|
||||
|
||||
// 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."
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Current project's directories.
|
||||
*/
|
||||
internal class BaseDirs(val buildDir: File)
|
||||
|
||||
/**
|
||||
* Timeouts.
|
||||
*/
|
||||
internal class Timeouts(val executionTimeout: Duration)
|
||||
|
||||
/**
|
||||
* Used cache kind.
|
||||
*/
|
||||
internal sealed interface CacheKind {
|
||||
object WithoutCache : CacheKind
|
||||
|
||||
object WithStaticCache : CacheKind {
|
||||
fun getRootCacheDirectory(
|
||||
kotlinNativeHome: KotlinNativeHome,
|
||||
kotlinNativeTargets: KotlinNativeTargets,
|
||||
debuggable: Boolean
|
||||
): File? = kotlinNativeHome.dir
|
||||
.resolve("klib/cache")
|
||||
.resolve(computeCacheDirName(kotlinNativeTargets.testTarget, CACHE_KIND, debuggable))
|
||||
.takeIf { it.exists() }
|
||||
|
||||
private const val CACHE_KIND = "STATIC"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun computeCacheDirName(testTarget: KonanTarget, cacheKind: String, debuggable: Boolean) =
|
||||
"$testTarget${if (debuggable) "-g" else ""}$cacheKind"
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Settings.getRootCacheDirectory(debuggable: Boolean): File? =
|
||||
get<CacheKind>().safeAs<CacheKind.WithStaticCache>()?.getRootCacheDirectory(get(), get(), debuggable)
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.AbstractNativeBlackBoxTest
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.support.util.ExternalSourceTransformers
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* All instances of test classes.
|
||||
*
|
||||
* [allInstances] - all test class instances ordered from innermost to outermost
|
||||
* [enclosingTestInstance] - the outermost test instance
|
||||
*/
|
||||
internal class TestInstances(val allInstances: List<Any>) {
|
||||
val enclosingTestInstance: AbstractNativeBlackBoxTest
|
||||
get() = allInstances.firstOrNull().cast()
|
||||
}
|
||||
|
||||
internal interface ExternalSourceTransformersProvider {
|
||||
fun getSourceTransformers(testDataFile: File): ExternalSourceTransformers?
|
||||
}
|
||||
-5
@@ -6,7 +6,6 @@
|
||||
package org.jetbrains.kotlin.konan.blackboxtest.support.util
|
||||
|
||||
import org.jetbrains.kotlin.konan.blackboxtest.AbstractExternalNativeBlackBoxTest
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* This relates only to external codegen tests (see [AbstractExternalNativeBlackBoxTest]) that may have their own source transformers.
|
||||
@@ -15,10 +14,6 @@ import java.io.File
|
||||
internal typealias ExternalSourceTransformer = (/* file contents */ String) -> /* patched file contents */ String
|
||||
internal typealias ExternalSourceTransformers = List<ExternalSourceTransformer>
|
||||
|
||||
internal interface ExternalSourceTransformersProvider {
|
||||
fun getSourceTransformers(testDataFile: File): ExternalSourceTransformers?
|
||||
}
|
||||
|
||||
internal object DiagnosticsRemovingSourceTransformer : ExternalSourceTransformer {
|
||||
override fun invoke(source: String) = source.lineSequence().joinToString("\n") { line ->
|
||||
// Remove all diagnostic parameters from the text. Examples:
|
||||
|
||||
Reference in New Issue
Block a user