[Native][tests] Fix parallel test execution

This commit is contained in:
Dmitriy Dolovov
2021-12-15 23:45:43 +03:00
parent f145076245
commit 37d866c95a
21 changed files with 539 additions and 401 deletions
@@ -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)
@@ -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()
@@ -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()
}
}
@@ -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 simplicitys 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?)
@@ -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)
@@ -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 {
@@ -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)
}
}
)
@@ -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
}
@@ -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()
@@ -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,
@@ -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?
}
@@ -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
@@ -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
@@ -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"
}
}
@@ -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,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)
@@ -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 {};
}
@@ -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."
)
}
@@ -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)
@@ -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?
}
@@ -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: