[IR, Native] KLIB ABI tests: add support to new test infrastructure

^KT-50775
This commit is contained in:
Dmitriy Dolovov
2022-01-19 16:13:57 +03:00
parent 9096725d1d
commit 3648ae222c
28 changed files with 1340 additions and 862 deletions
+5 -4
View File
@@ -66,7 +66,7 @@ enum class TestProperty(shortName: String) {
fun readGradleProperty(task: Test): String? = task.project.findProperty(propertyName)?.toString()
}
fun blackBoxTest(taskName: String, vararg tags: String) = projectTest(taskName, jUnitMode = JUnitMode.JUnit5) {
fun nativeTest(taskName: String, vararg tags: String) = projectTest(taskName, jUnitMode = JUnitMode.JUnit5) {
group = "verification"
if (kotlinBuildProperties.isKotlinNativeEnabled) {
@@ -162,8 +162,9 @@ fun groupingTest(taskName: String, vararg dependencyTasks: Any) = getOrCreateTas
}
// Tasks that run different sorts of tests. Most frequent use case: running specific tests from the IDE.
val infrastructureTest = blackBoxTest("infrastructureTest", "infrastructure")
val externalTest = blackBoxTest("externalTest", "external")
val infrastructureTest = nativeTest("infrastructureTest", "infrastructure")
val externalTest = nativeTest("externalTest", "external")
val klibAbiTest = nativeTest("klibAbiTest", "klib")
// "test" task is created by convention. We can't just remove it. So, let it be just an alias for external test task.
val test by groupingTest("test", externalTest)
@@ -187,7 +188,7 @@ gradle.taskGraph.whenReady {
}
}
val generateTests by generator("org.jetbrains.kotlin.generators.tests.GenerateNativeBlackboxTestsKt") {
val generateTests by generator("org.jetbrains.kotlin.generators.tests.GenerateNativeTestsKt") {
javaLauncher.set(project.getToolchainLauncherFor(JdkMajorVersion.JDK_11))
dependsOn(":compiler:generateTestData")
}
@@ -0,0 +1,56 @@
/*
* 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;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("compiler/testData/klibABI")
@TestDataPath("$PROJECT_ROOT")
public class NativeKlibABITestGenerated extends AbstractNativeKlibABITest {
@Test
public void testAllFilesPresentInKlibABI() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/klibABI"), Pattern.compile("^([^_](.+))$"), null, false);
}
@Test
@TestMetadata("removeClassAsParameterType")
public void testRemoveClassAsParameterType() throws Exception {
runTest("compiler/testData/klibABI/removeClassAsParameterType/");
}
@Test
@TestMetadata("removeClassAsReturnType")
public void testRemoveClassAsReturnType() throws Exception {
runTest("compiler/testData/klibABI/removeClassAsReturnType/");
}
@Test
@TestMetadata("removeClassAsSuperTypeArgument")
public void testRemoveClassAsSuperTypeArgument() throws Exception {
runTest("compiler/testData/klibABI/removeClassAsSuperTypeArgument/");
}
@Test
@TestMetadata("removeClassAsTypeArgument")
public void testRemoveClassAsTypeArgument() throws Exception {
runTest("compiler/testData/klibABI/removeClassAsTypeArgument/");
}
@Test
@TestMetadata("removeFunction")
public void testRemoveFunction() throws Exception {
runTest("compiler/testData/klibABI/removeFunction/");
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.generators.generateTestGroupSuiteWithJUnit5
import org.jetbrains.kotlin.generators.model.annotation
import org.jetbrains.kotlin.konan.blackboxtest.AbstractExternalNativeBlackBoxTest
import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeBlackBoxTest
import org.jetbrains.kotlin.konan.blackboxtest.AbstractNativeKlibABITest
import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseExtTestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.group.UseStandardTestCaseGroupProvider
import org.jetbrains.kotlin.test.TargetBackend
@@ -39,6 +40,13 @@ fun main() {
model("samples2")
}
}
// KLIB ABI tests.
testGroup("native/native.tests/tests-gen", "compiler/testData") {
testClass<AbstractNativeKlibABITest> {
model("klibABI/", pattern = "^([^_](.+))$", recursive = false)
}
}
}
}
@@ -6,7 +6,12 @@
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.NativeBlackBoxTestSupport
import org.jetbrains.kotlin.konan.blackboxtest.support.PackageName
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseId
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRun
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunners.createProperTestRunner
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
@@ -92,7 +97,7 @@ abstract class AbstractNativeBlackBoxTest {
}
private fun performTestRun(testRun: TestRun) {
val testRunner = testRunProvider.createRunner(testRun, testRunSettings)
val testRunner = createProperTestRunner(testRun, testRunSettings)
testRunner.run()
}
}
@@ -0,0 +1,126 @@
/*
* Copyright 2010-2022 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
import com.intellij.testFramework.TestDataFile
import org.jetbrains.kotlin.klib.AbstractKlibABITestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationDependencies
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccessfullyCompiled
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeHome
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.SimpleTestDirectories
import org.jetbrains.kotlin.konan.blackboxtest.support.util.LAUNCHER_FILE_NAME
import org.jetbrains.kotlin.konan.blackboxtest.support.util.LAUNCHER_MODULE_NAME
import org.jetbrains.kotlin.konan.blackboxtest.support.util.generateBoxFunctionLauncher
import org.jetbrains.kotlin.konan.blackboxtest.support.util.getAbsoluteFile
import org.junit.jupiter.api.Tag
import java.io.File
@Tag("klib")
abstract class AbstractNativeKlibABITest : AbstractNativeSimpleTest() {
private val compilationResults: MutableMap<File, TestCompilationResult.Success> by lazy {
mutableMapOf(stdlibFile to TestCompilationResult.ExistingArtifact(stdlibFile))
}
protected fun runTest(@TestDataFile testPath: String): Unit = AbstractKlibABITestCase.doTest(
testDir = getAbsoluteFile(testPath),
buildDir = buildDir,
stdlibFile = stdlibFile,
buildKlib = ::buildKlib,
buildBinaryAndRun = { _, allDependencies -> buildBinaryAndRun(allDependencies) }
)
private fun buildKlib(moduleName: String, moduleSourceDir: File, moduleDependencies: Collection<File>, klibFile: File) {
val module = createModule(moduleName)
moduleSourceDir.walk()
.filter { file -> file.isFile && file.extension == "kt" }
.forEach { file -> module.files += TestFile.createCommitted(file, module) }
val testCase = createTestCase(module, COMPILER_ARGS_FOR_KLIB)
val compilation = TestCompilation.createForKlib(
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
dependencies = createCompilationDependencies(moduleDependencies),
expectedKlibFile = klibFile,
)
compilationResults[klibFile] = compilation.result.assertSuccess() // <-- trigger compilation
}
private fun buildBinaryAndRun(allDependencies: Collection<File>) {
val (sourceDir, outputDir) = AbstractKlibABITestCase.createModuleDirs(buildDir, LAUNCHER_MODULE_NAME)
val executableFile = outputDir.resolve("app." + testRunSettings.get<KotlinNativeTargets>().testTarget.family.exeSuffix)
val module = createModule(LAUNCHER_MODULE_NAME)
module.files += TestFile.createUncommitted(
location = sourceDir.resolve(LAUNCHER_FILE_NAME),
module = module,
text = generateBoxFunctionLauncher("box")
)
val testCase = createTestCase(module, COMPILER_ARGS_FOR_EXECUTABLE)
val compilation = TestCompilation.createForExecutable(
settings = testRunSettings,
freeCompilerArgs = testCase.freeCompilerArgs,
sourceModules = testCase.modules,
extras = testCase.extras,
dependencies = createCompilationDependencies(allDependencies),
expectedExecutableFile = executableFile
)
val compilationResult = compilation.result.assertSuccessfullyCompiled() // <-- trigger compilation
val executable = TestExecutable(executableFile, compilationResult.loggedData)
runExecutableAndVerify(testCase, executable) // <-- run executable and verify
}
private fun createModule(moduleName: String) = TestModule.Exclusive(
name = moduleName,
directDependencySymbols = emptySet(), /* Don't need to pass any dependency symbols here.
Dependencies are already handled by the AbstractKlibABITestCase test case class. */
directFriendSymbols = emptySet()
)
private fun createTestCase(module: TestModule.Exclusive, compilerArgs: TestCompilerArgs) = TestCase(
id = TestCaseId.Named(module.name),
kind = TestKind.STANDALONE,
modules = setOf(module),
freeCompilerArgs = compilerArgs,
nominalPackageName = PackageName.EMPTY,
expectedOutputDataFile = null,
extras = DEFAULT_EXTRAS
).apply {
initialize(null)
}
private fun createCompilationDependencies(dependencies: Collection<File>) = TestCompilationDependencies(
libraries = dependencies.map(TestCompilation.Companion::createForExistingArtifact)
)
private val buildDir: File get() = testRunSettings.get<SimpleTestDirectories>().testBuildDir
private val stdlibFile: File get() = testRunSettings.get<KotlinNativeHome>().dir.resolve("klib/common/stdlib")
companion object {
private val COMPILER_ARGS_FOR_KLIB = TestCompilerArgs(
listOf("-nostdlib") // stdlib is passed explicitly.
)
private val COMPILER_ARGS_FOR_EXECUTABLE = TestCompilerArgs(
COMPILER_ARGS_FOR_KLIB.compilerArgs + "-Xpartial-linkage"
)
private val DEFAULT_EXTRAS = WithTestRunnerExtras(TestRunnerType.DEFAULT)
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2010-2022 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
import org.jetbrains.kotlin.konan.blackboxtest.support.NativeSimpleTestSupport
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.SimpleTestRunProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunners.createProperTestRunner
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.SimpleTestRunSettings
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(NativeSimpleTestSupport::class)
abstract class AbstractNativeSimpleTest {
internal lateinit var testRunSettings: SimpleTestRunSettings
internal lateinit var testRunProvider: SimpleTestRunProvider
internal fun runExecutableAndVerify(testCase: TestCase, executable: TestExecutable) {
val testRun = testRunProvider.getTestRun(testCase, executable)
val testRunner = createProperTestRunner(testRun, testRunSettings)
testRunner.run()
}
}
@@ -7,6 +7,8 @@ package org.jetbrains.kotlin.konan.blackboxtest.support
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.RunResult
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunParameter
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.get
import org.jetbrains.kotlin.konan.blackboxtest.support.util.SafeEnvVars
import org.jetbrains.kotlin.konan.blackboxtest.support.util.SafeProperties
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -1,351 +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
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.konan.target.Distribution
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
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.junit.jupiter.api.extension.BeforeAllCallback
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 {
/**
* Note: [BeforeEachCallback.beforeEach] allows accessing test instances while [BeforeAllCallback.beforeAll] which may look
* more preferable here does not allow it because it is called at the time when test instances are not created yet.
* Also, [TestInstancePostProcessor.postProcessTestInstance] allows accessing only the currently created test instance and does
* 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) {
val settings = createTestRunSettings()
// Set the essential compiler property.
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 {
/*************** Test process settings ***************/
private fun ExtensionContext.getOrCreateTestProcessSettings(): TestProcessSettings =
root.getStore(NAMESPACE).getOrComputeIfAbsent(TestProcessSettings::class.java.name) {
val optimizationMode = computeOptimizationMode()
val memoryModel = computeMemoryModel()
val threadStateChecker = computeThreadStateChecker()
if (threadStateChecker == ThreadStateChecker.ENABLED) {
assertEquals(MemoryModel.EXPERIMENTAL, memoryModel) {
"Thread state checker can be enabled only with experimental memory model"
}
assertEquals(OptimizationMode.DEBUG, optimizationMode) {
"Thread state checker can be enabled only with debug optimization mode"
}
}
val gcType = computeGCType()
if (gcType != GCType.UNSPECIFIED) {
assertEquals(MemoryModel.EXPERIMENTAL, memoryModel) {
"GC type can be specified only with experimental memory model"
}
}
val nativeHome = computeNativeHome()
val hostManager = HostManager(distribution = Distribution(nativeHome.path), experimental = false)
val nativeTargets = computeNativeTargets(hostManager)
TestProcessSettings(
nativeTargets,
nativeHome,
computeNativeClassLoader(),
computeTestMode(),
optimizationMode,
memoryModel,
threadStateChecker,
gcType,
CacheKind::class to computeCacheKind(nativeHome, nativeTargets, optimizationMode),
computeBaseDirs(),
computeTimeouts()
)
}.cast()
private fun computeNativeTargets(hostManager: HostManager): KotlinNativeTargets {
val hostTarget = HostManager.host
return KotlinNativeTargets(
testTarget = systemProperty(TEST_TARGET, hostManager::targetByName, default = hostTarget),
hostTarget = hostTarget
)
}
private fun computeNativeHome(): KotlinNativeHome = KotlinNativeHome(File(requiredSystemProperty(KOTLIN_NATIVE_HOME)))
private fun computeNativeClassLoader(): KotlinNativeClassLoader = KotlinNativeClassLoader(
lazy {
val nativeClassPath = requiredSystemProperty(COMPILER_CLASSPATH)
.split(':', ';')
.map { File(it).toURI().toURL() }
.toTypedArray()
URLClassLoader(nativeClassPath, /* no parent class loader */ null).apply { setDefaultAssertionStatus(true) }
}
)
private fun computeTestMode(): TestMode = enumSystemProperty(TEST_MODE, TestMode.values(), default = TestMode.WITH_MODULES)
private fun computeOptimizationMode(): OptimizationMode =
enumSystemProperty(OPTIMIZATION_MODE, OptimizationMode.values(), default = OptimizationMode.DEBUG)
private fun computeMemoryModel(): MemoryModel =
enumSystemProperty(MEMORY_MODEL, MemoryModel.values(), default = MemoryModel.DEFAULT)
private fun computeThreadStateChecker(): ThreadStateChecker {
val useThreadStateChecker = systemProperty(USE_THREAD_STATE_CHECKER, String::toBooleanStrictOrNull, default = false)
return if (useThreadStateChecker) ThreadStateChecker.ENABLED else ThreadStateChecker.DISABLED
}
private fun computeGCType(): GCType = enumSystemProperty(GC_TYPE, GCType.values(), default = GCType.UNSPECIFIED)
private fun computeCacheKind(
kotlinNativeHome: KotlinNativeHome,
kotlinNativeTargets: KotlinNativeTargets,
optimizationMode: OptimizationMode
): CacheKind {
val useCache = systemProperty(USE_CACHE, String::toBooleanStrictOrNull, default = true)
return if (useCache)
CacheKind.WithStaticCache(kotlinNativeHome, kotlinNativeTargets, optimizationMode)
else
CacheKind.WithoutCache
}
private fun computeBaseDirs(): BaseDirs = BaseDirs(File(requiredEnvironmentVariable(PROJECT_BUILD_DIR)))
private fun computeTimeouts(): Timeouts {
val executionTimeout = systemProperty(EXECUTION_TIMEOUT, { it.toLongOrNull()?.milliseconds }, default = 10.seconds)
return Timeouts(executionTimeout)
}
private fun requiredSystemProperty(name: String): String =
System.getProperty(name) ?: fail { "Unspecified $name system property" }
private fun <T> systemProperty(propertyName: String, transform: (String) -> T?, default: T): T {
val propertyValue = System.getProperty(propertyName)
return if (propertyValue != null) {
transform(propertyValue) ?: fail { "Invalid value for $propertyName system property: $propertyValue" }
} else
default
}
private inline fun <reified E : Enum<E>> enumSystemProperty(propertyName: String, values: Array<out E>, default: E): E {
val optionName = System.getProperty(propertyName)
return if (optionName != null) {
values.firstOrNull { it.name == optionName } ?: fail {
buildString {
appendLine("Unknown ${E::class.java.simpleName} name $optionName.")
appendLine("One of the following ${E::class.java.simpleName} should be passed through $propertyName system property:")
values.forEach { value -> appendLine("- ${value.name}: $value") }
}
}
} else
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_TARGET = "kotlin.internal.native.test.target"
private const val TEST_MODE = "kotlin.internal.native.test.mode"
private const val OPTIMIZATION_MODE = "kotlin.internal.native.test.optimizationMode"
private const val MEMORY_MODEL = "kotlin.internal.native.test.memoryModel"
private const val USE_THREAD_STATE_CHECKER = "kotlin.internal.native.test.useThreadStateChecker"
private const val GC_TYPE = "kotlin.internal.native.test.gcType"
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 computeTestConfiguration(enclosingTestClass: Class<*>): ComputedTestConfiguration {
val findTestConfiguration: Class<*>.() -> ComputedTestConfiguration? = {
annotations.asSequence().mapNotNull { annotation ->
val testConfiguration = annotation.annotationClass.findAnnotation<TestConfiguration>() ?: return@mapNotNull null
ComputedTestConfiguration(testConfiguration, annotation)
}.firstOrNull()
}
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()
}
}
else -> setOf(outermostTestMetadata.testRoot())
}
val baseDir: File = when (testRoots.size) {
0 -> fail { "No test roots found for $enclosingTestClass test class." }
1 -> testRoots.first().parentFile
else -> {
val baseDirs = testRoots.mapToSet { it.parentFile }
assertEquals(1, baseDirs.size) {
"Controversial base directories computed for test roots for $enclosingTestClass test class: $baseDirs"
}
baseDirs.first()
}
}
return TestRoots(testRoots, baseDir)
}
private fun computeGeneratedSourceDirs(
baseDirs: BaseDirs,
targets: KotlinNativeTargets,
enclosingTestClass: Class<*>
): GeneratedSources {
val testSourcesDir = baseDirs.buildDir
.resolve("bbtest.src")
.resolve("${targets.testTarget.compressedName}_${enclosingTestClass.compressedSimpleName}")
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources.
val sharedSourcesDir = testSourcesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
return GeneratedSources(testSourcesDir, sharedSourcesDir)
}
private fun computeBinariesDirs(baseDirs: BaseDirs, targets: KotlinNativeTargets, enclosingTestClass: Class<*>): Binaries {
val testBinariesDir = baseDirs.buildDir
.resolve("bbtest.bin")
.resolve("${targets.testTarget.compressedName}_${enclosingTestClass.compressedSimpleName}")
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
val sharedBinariesDir = testBinariesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
return Binaries(testBinariesDir, sharedBinariesDir)
}
/*************** 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 testConfigurationAnnotationClass: KClass<out Annotation> = testConfigurationAnnotation.annotationClass
val arguments = constructor.parameters.map { parameter ->
when {
parameter.hasTypeOf(testConfigurationAnnotationClass) -> testConfigurationAnnotation
// maybe add other arguments???
else -> fail { "Can't provide all arguments for $constructor" }
}
}
return constructor.call(*arguments.toTypedArray()).cast()
}
private fun KParameter.hasTypeOf(clazz: KClass<*>): Boolean = (type.classifier as? KClass<*>)?.qualifiedName == clazz.qualifiedName
/*************** 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
}
}
@@ -0,0 +1,415 @@
/*
* Copyright 2010-2022 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
import org.jetbrains.kotlin.konan.blackboxtest.support.NativeTestSupport.createSimpleTestRunSettings
import org.jetbrains.kotlin.konan.blackboxtest.support.NativeTestSupport.createTestRunSettings
import org.jetbrains.kotlin.konan.blackboxtest.support.NativeTestSupport.getOrCreateSimpleTestRunProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.NativeTestSupport.getOrCreateTestRunProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.SimpleTestRunProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheKind
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.konan.target.Distribution
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
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.junit.jupiter.api.extension.BeforeAllCallback
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 {
/**
* Note: [BeforeEachCallback.beforeEach] allows accessing test instances while [BeforeAllCallback.beforeAll] which may look
* more preferable here does not allow it because it is called at the time when test instances are not created yet.
* Also, [TestInstancePostProcessor.postProcessTestInstance] allows accessing only the currently created test instance and does
* 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) {
val settings = createTestRunSettings()
// Set the essential compiler property.
System.setProperty("kotlin.native.home", settings.get<KotlinNativeHome>().path)
// Inject the required properties to test instance.
with(settings.get<BlackBoxTestInstances>().enclosingTestInstance) {
testRunSettings = settings
testRunProvider = getOrCreateTestRunProvider()
}
}
}
class NativeSimpleTestSupport : BeforeEachCallback {
override fun beforeEach(extensionContext: ExtensionContext): Unit = with(extensionContext) {
val settings = createSimpleTestRunSettings()
// Set the essential compiler property.
System.setProperty("kotlin.native.home", settings.get<KotlinNativeHome>().path)
// Inject the required properties to test instance.
with(settings.get<SimpleTestInstances>().enclosingTestInstance) {
testRunSettings = settings
testRunProvider = getOrCreateSimpleTestRunProvider()
}
}
}
private object NativeTestSupport {
/*************** Test process settings ***************/
fun ExtensionContext.getOrCreateTestProcessSettings(): TestProcessSettings =
root.getStore(NAMESPACE).getOrComputeIfAbsent(TestProcessSettings::class.java.name) {
val optimizationMode = computeOptimizationMode()
val memoryModel = computeMemoryModel()
val threadStateChecker = computeThreadStateChecker()
if (threadStateChecker == ThreadStateChecker.ENABLED) {
assertEquals(MemoryModel.EXPERIMENTAL, memoryModel) {
"Thread state checker can be enabled only with experimental memory model"
}
assertEquals(OptimizationMode.DEBUG, optimizationMode) {
"Thread state checker can be enabled only with debug optimization mode"
}
}
val gcType = computeGCType()
if (gcType != GCType.UNSPECIFIED) {
assertEquals(MemoryModel.EXPERIMENTAL, memoryModel) {
"GC type can be specified only with experimental memory model"
}
}
val nativeHome = computeNativeHome()
val hostManager = HostManager(distribution = Distribution(nativeHome.path), experimental = false)
val nativeTargets = computeNativeTargets(hostManager)
TestProcessSettings(
nativeTargets,
nativeHome,
computeNativeClassLoader(),
computeTestMode(),
optimizationMode,
memoryModel,
threadStateChecker,
gcType,
CacheKind::class to computeCacheKind(nativeHome, nativeTargets, optimizationMode),
computeBaseDirs(),
computeTimeouts()
)
}.cast()
private fun computeNativeTargets(hostManager: HostManager): KotlinNativeTargets {
val hostTarget = HostManager.host
return KotlinNativeTargets(
testTarget = systemProperty(TEST_TARGET, hostManager::targetByName, default = hostTarget),
hostTarget = hostTarget
)
}
private fun computeNativeHome(): KotlinNativeHome = KotlinNativeHome(File(requiredSystemProperty(KOTLIN_NATIVE_HOME)))
private fun computeNativeClassLoader(): KotlinNativeClassLoader = KotlinNativeClassLoader(
lazy {
val nativeClassPath = requiredSystemProperty(COMPILER_CLASSPATH)
.split(':', ';')
.map { File(it).toURI().toURL() }
.toTypedArray()
URLClassLoader(nativeClassPath, /* no parent class loader */ null).apply { setDefaultAssertionStatus(true) }
}
)
private fun computeTestMode(): TestMode = enumSystemProperty(TEST_MODE, TestMode.values(), default = TestMode.WITH_MODULES)
private fun computeOptimizationMode(): OptimizationMode =
enumSystemProperty(OPTIMIZATION_MODE, OptimizationMode.values(), default = OptimizationMode.DEBUG)
private fun computeMemoryModel(): MemoryModel =
enumSystemProperty(MEMORY_MODEL, MemoryModel.values(), default = MemoryModel.DEFAULT)
private fun computeThreadStateChecker(): ThreadStateChecker {
val useThreadStateChecker = systemProperty(USE_THREAD_STATE_CHECKER, String::toBooleanStrictOrNull, default = false)
return if (useThreadStateChecker) ThreadStateChecker.ENABLED else ThreadStateChecker.DISABLED
}
private fun computeGCType(): GCType = enumSystemProperty(GC_TYPE, GCType.values(), default = GCType.UNSPECIFIED)
private fun computeCacheKind(
kotlinNativeHome: KotlinNativeHome,
kotlinNativeTargets: KotlinNativeTargets,
optimizationMode: OptimizationMode
): CacheKind {
val useCache = systemProperty(USE_CACHE, String::toBooleanStrictOrNull, default = true)
return if (useCache)
CacheKind.WithStaticCache(kotlinNativeHome, kotlinNativeTargets, optimizationMode)
else
CacheKind.WithoutCache
}
private fun computeBaseDirs(): BaseDirs {
val testBuildDir = File(requiredEnvironmentVariable(PROJECT_BUILD_DIR)).resolve("t")
testBuildDir.mkdirs() // Make sure it exists. Don't clean up.
return BaseDirs(testBuildDir)
}
private fun computeTimeouts(): Timeouts {
val executionTimeout = systemProperty(EXECUTION_TIMEOUT, { it.toLongOrNull()?.milliseconds }, default = 10.seconds)
return Timeouts(executionTimeout)
}
private fun requiredSystemProperty(name: String): String =
System.getProperty(name) ?: fail { "Unspecified $name system property" }
private fun <T> systemProperty(propertyName: String, transform: (String) -> T?, default: T): T {
val propertyValue = System.getProperty(propertyName)
return if (propertyValue != null) {
transform(propertyValue) ?: fail { "Invalid value for $propertyName system property: $propertyValue" }
} else
default
}
private inline fun <reified E : Enum<E>> enumSystemProperty(propertyName: String, values: Array<out E>, default: E): E {
val optionName = System.getProperty(propertyName)
return if (optionName != null) {
values.firstOrNull { it.name == optionName } ?: fail {
buildString {
appendLine("Unknown ${E::class.java.simpleName} name $optionName.")
appendLine("One of the following ${E::class.java.simpleName} should be passed through $propertyName system property:")
values.forEach { value -> appendLine("- ${value.name}: $value") }
}
}
} else
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_TARGET = "kotlin.internal.native.test.target"
private const val TEST_MODE = "kotlin.internal.native.test.mode"
private const val OPTIMIZATION_MODE = "kotlin.internal.native.test.optimizationMode"
private const val MEMORY_MODEL = "kotlin.internal.native.test.memoryModel"
private const val USE_THREAD_STATE_CHECKER = "kotlin.internal.native.test.useThreadStateChecker"
private const val GC_TYPE = "kotlin.internal.native.test.gcType"
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 (for black box tests only) ***************/
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 computeTestConfiguration(enclosingTestClass: Class<*>): ComputedTestConfiguration {
val findTestConfiguration: Class<*>.() -> ComputedTestConfiguration? = {
annotations.asSequence().mapNotNull { annotation ->
val testConfiguration = annotation.annotationClass.findAnnotation<TestConfiguration>() ?: return@mapNotNull null
ComputedTestConfiguration(testConfiguration, annotation)
}.firstOrNull()
}
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()
}
}
else -> setOf(outermostTestMetadata.testRoot())
}
val baseDir: File = when (testRoots.size) {
0 -> fail { "No test roots found for $enclosingTestClass test class." }
1 -> testRoots.first().parentFile
else -> {
val baseDirs = testRoots.mapToSet { it.parentFile }
assertEquals(1, baseDirs.size) {
"Controversial base directories computed for test roots for $enclosingTestClass test class: $baseDirs"
}
baseDirs.first()
}
}
return TestRoots(testRoots, baseDir)
}
private fun computeGeneratedSourceDirs(
baseDirs: BaseDirs,
targets: KotlinNativeTargets,
enclosingTestClass: Class<*>
): GeneratedSources {
val testSourcesDir = baseDirs.testBuildDir
.resolve("bb.src") // "bb" for black box
.resolve("${targets.testTarget.compressedName}_${enclosingTestClass.compressedSimpleName}")
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale generated sources.
val sharedSourcesDir = testSourcesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
return GeneratedSources(testSourcesDir, sharedSourcesDir)
}
private fun computeBinariesDirs(baseDirs: BaseDirs, targets: KotlinNativeTargets, enclosingTestClass: Class<*>): Binaries {
val testBinariesDir = baseDirs.testBuildDir
.resolve("bb.out") // "bb" for black box
.resolve("${targets.testTarget.compressedName}_${enclosingTestClass.compressedSimpleName}")
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
val sharedBinariesDir = testBinariesDir
.resolve("__shared_modules__")
.ensureExistsAndIsEmptyDirectory()
return Binaries(testBinariesDir, sharedBinariesDir)
}
/*************** Test run settings (for black box tests only) ***************/
// Note: TestRunSettings is not cached!
fun ExtensionContext.createTestRunSettings(): TestRunSettings {
val testInstances = computeBlackBoxTestInstances()
return TestRunSettings(
parent = getOrCreateTestClassSettings(),
listOfNotNull(
testInstances,
ExternalSourceTransformersProvider::class to testInstances.enclosingTestInstance.safeAs<ExternalSourceTransformersProvider>()
)
)
}
private fun ExtensionContext.computeBlackBoxTestInstances(): BlackBoxTestInstances =
BlackBoxTestInstances(requiredTestInstances.allInstances)
/*************** Test run settings (simplified) ***************/
// Note: SimpleTestRunSettings is not cached!
fun ExtensionContext.createSimpleTestRunSettings(): SimpleTestRunSettings {
val testProcessSettings = getOrCreateTestProcessSettings()
return SimpleTestRunSettings(
parent = testProcessSettings,
listOf(
computeSimpleTestInstances(),
computeSimpleTestDirectories(testProcessSettings.get(), testProcessSettings.get())
)
)
}
private fun ExtensionContext.computeSimpleTestInstances(): SimpleTestInstances = SimpleTestInstances(requiredTestInstances.allInstances)
private fun ExtensionContext.computeSimpleTestDirectories(baseDirs: BaseDirs, targets: KotlinNativeTargets): SimpleTestDirectories {
val compressedClassNames = testClasses.map(Class<*>::compressedSimpleName).joinToString(separator = "_")
val testBuildDir = baseDirs.testBuildDir
.resolve("s") // "s" for simple
.resolve("${targets.testTarget.compressedName}_$compressedClassNames")
.resolve(requiredTestMethod.name)
.ensureExistsAndIsEmptyDirectory() // Clean-up the directory with all potentially stale artifacts.
return SimpleTestDirectories(testBuildDir)
}
/*************** Test run provider (for black box tests only) ***************/
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 testConfigurationAnnotationClass: KClass<out Annotation> = testConfigurationAnnotation.annotationClass
val arguments = constructor.parameters.map { parameter ->
when {
parameter.hasTypeOf(testConfigurationAnnotationClass) -> testConfigurationAnnotation
// maybe add other arguments???
else -> fail { "Can't provide all arguments for $constructor" }
}
}
return constructor.call(*arguments.toTypedArray()).cast()
}
private fun KParameter.hasTypeOf(clazz: KClass<*>): Boolean = (type.classifier as? KClass<*>)?.qualifiedName == clazz.qualifiedName
/*************** Test run provider (for black box tests only) ***************/
// Currently, SimpleTestRunProvider is an object, so it does not need to be cached.
fun getOrCreateSimpleTestRunProvider(): SimpleTestRunProvider = SimpleTestRunProvider
/*************** Common ***************/
private val ExtensionContext.testClasses: Sequence<Class<*>>
get() = generateSequence(requiredTestClass) { it.enclosingClass }
private val ExtensionContext.enclosingTestClass: Class<*>
get() = testClasses.last()
private inline fun <reified T : Any> ExtensionContext.testClassKeyFor(): String =
enclosingTestClass.name + "#" + T::class.java.name
}
@@ -1,418 +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
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.*
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.compilerRunner.processCompilerOutput
import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilation.Companion.resultingArtifactPath
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.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheKind.Companion.rootCacheDir
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.*
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
internal class TestCompilationFactory {
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
private sealed interface TestCompilationCacheKey {
data class Klib(val sourceModules: Set<TestModule>, val freeCompilerArgs: TestCompilerArgs) : TestCompilationCacheKey
data class Executable(val sourceModules: Set<TestModule>) : TestCompilationCacheKey
}
fun testCasesToExecutable(testCases: Collection<TestCase>, settings: Settings): TestCompilation {
val rootModules = testCases.flatMapToSet { testCase -> testCase.rootModules }
val cacheKey = TestCompilationCacheKey.Executable(rootModules)
// Fast pass.
cachedCompilations[cacheKey]?.let { return it }
// 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, settings) }
val friends = rootModules.flatMapToSet { it.allFriends }.map { moduleToKlib(it, freeCompilerArgs, settings) }
return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilationImpl(
targets = settings.get(),
home = settings.get(),
classLoader = settings.get(),
optimizationMode = settings.get(),
memoryModel = settings.get(),
threadStateChecker = settings.get(),
gcType = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = rootModules,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
expectedArtifactFile = settings.artifactFileForExecutable(rootModules),
specificCompilerArgs = {
add("-produce", "program")
when (extras) {
is NoTestRunnerExtras -> add("-entry", extras.entryPoint)
is WithTestRunnerExtras -> {
val testRunnerArg = when (extras.runnerType) {
TestRunnerType.DEFAULT -> "-generate-test-runner"
TestRunnerType.WORKER -> "-generate-worker-test-runner"
TestRunnerType.NO_EXIT -> "-generate-no-exit-test-runner"
}
add(testRunnerArg)
}
}
settings.get<CacheKind>().rootCacheDir?.let { rootCacheDir ->
add("-Xcache-directory=$rootCacheDir")
}
}
)
}
}
private fun moduleToKlib(sourceModule: TestModule, freeCompilerArgs: TestCompilerArgs, settings: Settings): TestCompilation {
val sourceModules = setOf(sourceModule)
val cacheKey = TestCompilationCacheKey.Klib(sourceModules, freeCompilerArgs)
// Fast pass.
cachedCompilations[cacheKey]?.let { return it }
// Long pass.
val libraries = sourceModule.allDependencies.map { moduleToKlib(it, freeCompilerArgs, settings) }
val friends = sourceModule.allFriends.map { moduleToKlib(it, freeCompilerArgs, settings) }
return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilationImpl(
targets = settings.get(),
home = settings.get(),
classLoader = settings.get(),
optimizationMode = settings.get(),
memoryModel = settings.get(),
threadStateChecker = settings.get(),
gcType = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
expectedArtifactFile = settings.artifactFileForKlib(sourceModule, freeCompilerArgs),
specificCompilerArgs = { add("-produce", "library") }
)
}
}
private fun Settings.artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
1 -> artifactFileForExecutable(modules.first())
else -> multiModuleArtifactFile(modules, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
}
private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) =
singleModuleArtifactFile(module, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
private fun Settings.artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) {
is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib")
is TestModule.Shared -> get<Binaries>().sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
}
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('.')
append(extension)
}
return artifactDirForPackageName(module.testCase.nominalPackageName).resolve(artifactFileName)
}
private fun Settings.multiModuleArtifactFile(modules: Collection<TestModule>, extension: String): File {
var filesCount = 0
var hash = 0
val uniquePackageNames = hashSetOf<PackageName>()
modules.forEach { module ->
module.files.forEach { file ->
filesCount++
hash = hash * 31 + file.hashCode()
}
if (module is TestModule.Exclusive)
uniquePackageNames += module.testCase.nominalPackageName
}
val commonPackageName = uniquePackageNames.findCommonPackageName()
val artifactFileName = buildString {
val prefix = filesCount.toString()
repeat(4 - prefix.length) { append('0') }
append(prefix).append('-')
if (!commonPackageName.isEmpty())
append(commonPackageName.compressedPackageName).append('-')
append(prettyHash(hash))
append('.').append(extension)
}
return artifactDirForPackageName(commonPackageName).resolve(artifactFileName)
}
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()
return outputDir
}
}
internal interface TestCompilation {
val result: TestCompilationResult
companion object {
val TestCompilation.resultingArtifactPath: String
get() = result.assertSuccess().resultingArtifact.path
}
}
internal sealed interface TestCompilationResult {
sealed interface ImmediateResult : TestCompilationResult {
val loggedData: LoggedData
}
sealed interface Failure : ImmediateResult
data class Success(val resultingArtifact: File, override val loggedData: LoggedData.CompilerCall) : ImmediateResult
data class CompilerFailure(override val loggedData: LoggedData.CompilerCall) : Failure
data class UnexpectedFailure(override val loggedData: LoggedData.CompilerCallUnexpectedFailure) : Failure
data class DependencyFailures(val causes: Set<Failure>) : TestCompilationResult
companion object {
fun TestCompilationResult.assertSuccess(): Success = when (this) {
is Success -> this
is Failure -> fail { describeFailure() }
is DependencyFailures -> fail { describeDependencyFailures() }
}
private fun Failure.describeFailure() = loggedData.withErrorMessage(
when (this@describeFailure) {
is CompilerFailure -> "Compilation failed."
is UnexpectedFailure -> "Compilation failed with unexpected exception."
}
)
private fun DependencyFailures.describeDependencyFailures() =
buildString {
appendLine("Compilation aborted due to errors in dependency compilations (${causes.size} items). See details below.")
appendLine()
causes.forEachIndexed { index, cause ->
append("#").append(index + 1).append(". ")
appendLine(cause.describeFailure())
}
}
}
}
/**
* The dependencies of a particular [TestCompilationImpl].
*
* [libraries] - the [TestCompilation]s (modules) that should yield KLIBs to be consumed as dependency libraries in the current compilation.
* [friends] - similarly but friend modules (-friend-modules).
* [includedLibraries] - similarly but included modules (-Xinclude).
*/
internal class TestCompilationDependencies(
val libraries: Collection<TestCompilation> = emptyList(),
val friends: Collection<TestCompilation> = emptyList(),
val includedLibraries: Collection<TestCompilation> = emptyList()
) {
fun collectFailures(): Set<TestCompilationResult.Failure> = listOf(libraries, friends, includedLibraries)
.flatten()
.flatMapToSet { compilation ->
when (val result = compilation.result) {
is TestCompilationResult.Failure -> listOf(result)
is TestCompilationResult.DependencyFailures -> result.causes
is TestCompilationResult.Success -> emptyList()
}
}
}
private class TestCompilationImpl(
private val targets: KotlinNativeTargets,
private val home: KotlinNativeHome,
private val classLoader: KotlinNativeClassLoader,
private val optimizationMode: OptimizationMode,
private val memoryModel: MemoryModel,
private val threadStateChecker: ThreadStateChecker,
private val gcType: GCType,
private val freeCompilerArgs: TestCompilerArgs,
private val sourceModules: Collection<TestModule>,
private val dependencies: TestCompilationDependencies,
private val expectedArtifactFile: File,
private val specificCompilerArgs: ArgsBuilder.() -> Unit
) : TestCompilation {
// Runs the compiler and memorizes the result on property access.
override val result: TestCompilationResult by lazy {
val failures = dependencies.collectFailures()
if (failures.isNotEmpty())
TestCompilationResult.DependencyFailures(causes = failures)
else
doCompile()
}
private fun ArgsBuilder.applyCommonArgs() {
add(
"-enable-assertions",
"-target", targets.testTarget.name,
"-repo", home.dir.resolve("klib").path,
"-output", expectedArtifactFile.path,
"-Xskip-prerelease-check",
"-Xverify-ir",
"-Xbinary=runtimeAssertionsMode=panic"
)
optimizationMode.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
memoryModel.compilerFlags?.let { compilerFlags -> add(compilerFlags) }
threadStateChecker.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
gcType.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
addFlattened(dependencies.libraries) { library -> listOf("-l", library.resultingArtifactPath) }
dependencies.friends.takeIf(Collection<*>::isNotEmpty)?.let { friends ->
add("-friend-modules", friends.joinToString(File.pathSeparator) { friend -> friend.resultingArtifactPath })
}
add(dependencies.includedLibraries) { include -> "-Xinclude=${include.resultingArtifactPath}" }
add(freeCompilerArgs.compilerArgs)
}
private fun ArgsBuilder.applySources() {
addFlattenedTwice(sourceModules, { it.files }) { it.location.path }
}
private fun doCompile(): TestCompilationResult.ImmediateResult {
val compilerArgs = buildArgs {
applyCommonArgs()
specificCompilerArgs()
applySources()
}
val loggedCompilerParameters = LoggedData.CompilerParameters(compilerArgs, sourceModules)
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
compilerArgs = compilerArgs,
kotlinNativeClassLoader = classLoader.classLoader
)
val loggedCompilerCall =
LoggedData.CompilerCall(loggedCompilerParameters, exitCode, compilerOutput, compilerOutputHasErrors, duration)
val result = if (exitCode != ExitCode.OK || compilerOutputHasErrors)
TestCompilationResult.CompilerFailure(loggedCompilerCall)
else
TestCompilationResult.Success(expectedArtifactFile, loggedCompilerCall)
loggedCompilerCall to result
} catch (unexpectedThrowable: Throwable) {
val loggedFailure = LoggedData.CompilerCallUnexpectedFailure(loggedCompilerParameters, unexpectedThrowable)
val result = TestCompilationResult.UnexpectedFailure(loggedFailure)
loggedFailure to result
}
getLogFile(expectedArtifactFile).writeText(loggedCompilerCall.toString())
return result
}
}
private class ArgsBuilder {
private val args = mutableListOf<String>()
fun add(vararg args: String) {
this.args += args
}
fun add(args: Iterable<String>) {
this.args += args
}
inline fun <T> add(rawArgs: Iterable<T>, transform: (T) -> String) {
rawArgs.mapTo(args) { transform(it) }
}
inline fun <T> addFlattened(rawArgs: Iterable<T>, transform: (T) -> Iterable<String>) {
rawArgs.flatMapTo(args) { transform(it) }
}
inline fun <T, R> addFlattenedTwice(rawArgs: Iterable<T>, transform1: (T) -> Iterable<R>, transform2: (R) -> String) {
rawArgs.forEach { add(transform1(it), transform2) }
}
fun build(): Array<String> = args.toTypedArray()
}
private inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array<String> {
return ArgsBuilder().apply(builderAction).build()
}
private fun callCompiler(compilerArgs: Array<String>, kotlinNativeClassLoader: ClassLoader): CompilerCallResult {
val compilerXmlOutput: ByteArrayOutputStream
val exitCode: ExitCode
@OptIn(ExperimentalTime::class)
val duration = measureTime {
val servicesClass = Class.forName(Services::class.java.canonicalName, true, kotlinNativeClassLoader)
val emptyServices = servicesClass.getField("EMPTY").get(servicesClass)
val compilerClass = Class.forName("org.jetbrains.kotlin.cli.bc.K2Native", true, kotlinNativeClassLoader)
val entryPoint = compilerClass.getMethod(
"execAndOutputXml",
PrintStream::class.java,
servicesClass,
Array<String>::class.java
)
compilerXmlOutput = ByteArrayOutputStream()
exitCode = PrintStream(compilerXmlOutput).use { printStream ->
val result = entryPoint.invoke(compilerClass.getDeclaredConstructor().newInstance(), printStream, emptyServices, compilerArgs)
ExitCode.valueOf(result.toString())
}
}
val messageCollector: MessageCollector
val compilerOutput: String
ByteArrayOutputStream().use { outputStream ->
PrintStream(outputStream).use { printStream ->
messageCollector = GroupingMessageCollector(
PrintingMessageCollector(printStream, MessageRenderer.SYSTEM_INDEPENDENT_RELATIVE_PATHS, true),
false
)
processCompilerOutput(
messageCollector,
OutputItemsCollectorImpl(),
compilerXmlOutput,
exitCode
)
messageCollector.flush()
}
compilerOutput = outputStream.toString(Charsets.UTF_8.name())
}
return CompilerCallResult(exitCode, compilerOutput, messageCollector.hasErrors(), duration)
}
private data class CompilerCallResult(
val exitCode: ExitCode,
val compilerOutput: String,
val compilerOutputHasErrors: Boolean,
val duration: Duration
)
private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log")
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2022 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.compilation
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.messages.GroupingMessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl
import org.jetbrains.kotlin.compilerRunner.processCompilerOutput
import org.jetbrains.kotlin.config.Services
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
internal fun callCompiler(compilerArgs: Array<String>, kotlinNativeClassLoader: ClassLoader): CompilerCallResult {
val compilerXmlOutput: ByteArrayOutputStream
val exitCode: ExitCode
@OptIn(ExperimentalTime::class)
val duration = measureTime {
val servicesClass = Class.forName(Services::class.java.canonicalName, true, kotlinNativeClassLoader)
val emptyServices = servicesClass.getField("EMPTY").get(servicesClass)
val compilerClass = Class.forName("org.jetbrains.kotlin.cli.bc.K2Native", true, kotlinNativeClassLoader)
val entryPoint = compilerClass.getMethod(
"execAndOutputXml",
PrintStream::class.java,
servicesClass,
Array<String>::class.java
)
compilerXmlOutput = ByteArrayOutputStream()
exitCode = PrintStream(compilerXmlOutput).use { printStream ->
val result = entryPoint.invoke(compilerClass.getDeclaredConstructor().newInstance(), printStream, emptyServices, compilerArgs)
ExitCode.valueOf(result.toString())
}
}
val messageCollector: MessageCollector
val compilerOutput: String
ByteArrayOutputStream().use { outputStream ->
PrintStream(outputStream).use { printStream ->
messageCollector = GroupingMessageCollector(
PrintingMessageCollector(printStream, MessageRenderer.SYSTEM_INDEPENDENT_RELATIVE_PATHS, true),
false
)
processCompilerOutput(
messageCollector,
OutputItemsCollectorImpl(),
compilerXmlOutput,
exitCode
)
messageCollector.flush()
}
compilerOutput = outputStream.toString(Charsets.UTF_8.name())
}
return CompilerCallResult(exitCode, compilerOutput, messageCollector.hasErrors(), duration)
}
internal data class CompilerCallResult(
val exitCode: ExitCode,
val compilerOutput: String,
val compilerOutputHasErrors: Boolean,
val duration: Duration
)
@@ -0,0 +1,297 @@
/*
* Copyright 2010-2022 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.compilation
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.*
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilation.Companion.resultingArtifactPath
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.*
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.CacheKind.Companion.rootCacheDir
import org.jetbrains.kotlin.konan.blackboxtest.support.util.flatMapToSet
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.File
internal interface TestCompilation {
val result: TestCompilationResult
companion object {
val TestCompilation.resultingArtifactPath: String
get() = result.assertSuccess().resultingArtifact.path
fun createForKlib(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
dependencies: TestCompilationDependencies,
expectedKlibFile: File
): TestCompilation = TestCompilationImpl.create(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifactFile = expectedKlibFile,
specificCompilerArgs = TestCompilationImpl.compilerArgsForKlib()
)
fun createForExecutable(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
extras: Extras,
dependencies: TestCompilationDependencies,
expectedExecutableFile: File
): TestCompilation = TestCompilationImpl.create(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifactFile = expectedExecutableFile,
specificCompilerArgs = TestCompilationImpl.compilerArgsForExecutable(settings.get(), extras)
)
fun createForExistingArtifact(resultingArtifact: File): TestCompilation =
object : TestCompilation {
override val result = TestCompilationResult.ExistingArtifact(resultingArtifact)
}
}
}
internal sealed interface TestCompilationResult {
sealed interface ImmediateResult : TestCompilationResult {
val loggedData: LoggedData
}
sealed interface Success : TestCompilationResult {
val resultingArtifact: File
}
sealed interface Failure : ImmediateResult
data class ExistingArtifact(override val resultingArtifact: File) : Success
data class CompiledArtifact(override val resultingArtifact: File, override val loggedData: LoggedData.CompilerCall) :
ImmediateResult, Success
data class CompilerFailure(override val loggedData: LoggedData.CompilerCall) : Failure
data class UnexpectedFailure(override val loggedData: LoggedData.CompilerCallUnexpectedFailure) : Failure
data class DependencyFailures(val causes: Set<Failure>) : TestCompilationResult
companion object {
fun TestCompilationResult.assertSuccess(): Success = when (this) {
is Success -> this
is Failure -> fail { describeFailure() }
is DependencyFailures -> fail { describeDependencyFailures() }
}
fun TestCompilationResult.assertSuccessfullyCompiled(): CompiledArtifact = when (val result = assertSuccess()) {
is CompiledArtifact -> result
is ExistingArtifact -> fail { "Test compilation result contains pre-existing artifact: ${result.resultingArtifact}" }
}
private fun Failure.describeFailure() = loggedData.withErrorMessage(
when (this@describeFailure) {
is CompilerFailure -> "Compilation failed."
is UnexpectedFailure -> "Compilation failed with unexpected exception."
}
)
private fun DependencyFailures.describeDependencyFailures() =
buildString {
appendLine("Compilation aborted due to errors in dependency compilations (${causes.size} items). See details below.")
appendLine()
causes.forEachIndexed { index, cause ->
append("#").append(index + 1).append(". ")
appendLine(cause.describeFailure())
}
}
}
}
/**
* The dependencies of a particular [TestCompilation].
*
* [libraries] - the [TestCompilation]s (modules) that should yield KLIBs to be consumed as dependency libraries in the current compilation.
* [friends] - similarly but friend modules (-friend-modules).
* [includedLibraries] - similarly but included modules (-Xinclude).
*/
internal class TestCompilationDependencies(
val libraries: Collection<TestCompilation> = emptyList(),
val friends: Collection<TestCompilation> = emptyList(),
val includedLibraries: Collection<TestCompilation> = emptyList()
) {
fun collectFailures(): Set<TestCompilationResult.Failure> = listOf(libraries, friends, includedLibraries)
.flatten()
.flatMapToSet { compilation ->
when (val result = compilation.result) {
is TestCompilationResult.Failure -> listOf(result)
is TestCompilationResult.DependencyFailures -> result.causes
is TestCompilationResult.Success -> emptyList()
}
}
}
private class TestCompilationImpl(
private val targets: KotlinNativeTargets,
private val home: KotlinNativeHome,
private val classLoader: KotlinNativeClassLoader,
private val optimizationMode: OptimizationMode,
private val memoryModel: MemoryModel,
private val threadStateChecker: ThreadStateChecker,
private val gcType: GCType,
private val freeCompilerArgs: TestCompilerArgs,
private val sourceModules: Collection<TestModule>,
private val dependencies: TestCompilationDependencies,
private val expectedArtifactFile: File,
private val specificCompilerArgs: ArgsBuilder.() -> Unit
) : TestCompilation {
// Runs the compiler and memorizes the result on property access.
override val result: TestCompilationResult by lazy {
val failures = dependencies.collectFailures()
if (failures.isNotEmpty())
TestCompilationResult.DependencyFailures(causes = failures)
else
doCompile()
}
private fun ArgsBuilder.applyCommonArgs() {
add(
"-enable-assertions",
"-target", targets.testTarget.name,
"-repo", home.dir.resolve("klib").path,
"-output", expectedArtifactFile.path,
"-Xskip-prerelease-check",
"-Xverify-ir",
"-Xbinary=runtimeAssertionsMode=panic"
)
optimizationMode.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
memoryModel.compilerFlags?.let { compilerFlags -> add(compilerFlags) }
threadStateChecker.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
gcType.compilerFlag?.let { compilerFlag -> add(compilerFlag) }
addFlattened(dependencies.libraries) { library -> listOf("-l", library.resultingArtifactPath) }
dependencies.friends.takeIf(Collection<*>::isNotEmpty)?.let { friends ->
add("-friend-modules", friends.joinToString(File.pathSeparator) { friend -> friend.resultingArtifactPath })
}
add(dependencies.includedLibraries) { include -> "-Xinclude=${include.resultingArtifactPath}" }
add(freeCompilerArgs.compilerArgs)
}
private fun ArgsBuilder.applySources() {
addFlattenedTwice(sourceModules, { it.files }) { it.location.path }
}
private fun doCompile(): TestCompilationResult.ImmediateResult {
val compilerArgs = buildArgs {
applyCommonArgs()
specificCompilerArgs()
applySources()
}
val loggedCompilerParameters = LoggedData.CompilerParameters(compilerArgs, sourceModules)
val (loggedCompilerCall: LoggedData, result: TestCompilationResult.ImmediateResult) = try {
val (exitCode, compilerOutput, compilerOutputHasErrors, duration) = callCompiler(
compilerArgs = compilerArgs,
kotlinNativeClassLoader = classLoader.classLoader
)
val loggedCompilerCall =
LoggedData.CompilerCall(loggedCompilerParameters, exitCode, compilerOutput, compilerOutputHasErrors, duration)
val result = if (exitCode != ExitCode.OK || compilerOutputHasErrors)
TestCompilationResult.CompilerFailure(loggedCompilerCall)
else
TestCompilationResult.CompiledArtifact(expectedArtifactFile, loggedCompilerCall)
loggedCompilerCall to result
} catch (unexpectedThrowable: Throwable) {
val loggedFailure = LoggedData.CompilerCallUnexpectedFailure(loggedCompilerParameters, unexpectedThrowable)
val result = TestCompilationResult.UnexpectedFailure(loggedFailure)
loggedFailure to result
}
getLogFile(expectedArtifactFile).writeText(loggedCompilerCall.toString())
return result
}
companion object {
fun compilerArgsForKlib(): ArgsBuilder.() -> Unit = { add("-produce", "library") }
fun compilerArgsForExecutable(cacheKind: CacheKind, extras: Extras): ArgsBuilder.() -> Unit = {
add("-produce", "program")
when (extras) {
is NoTestRunnerExtras -> add("-entry", extras.entryPoint)
is WithTestRunnerExtras -> {
val testRunnerArg = when (extras.runnerType) {
TestRunnerType.DEFAULT -> "-generate-test-runner"
TestRunnerType.WORKER -> "-generate-worker-test-runner"
TestRunnerType.NO_EXIT -> "-generate-no-exit-test-runner"
}
add(testRunnerArg)
}
}
cacheKind.rootCacheDir?.let { rootCacheDir -> add("-Xcache-directory=$rootCacheDir") }
}
// Just a shortcut to reduce the number of arguments.
fun create(
settings: Settings,
freeCompilerArgs: TestCompilerArgs,
sourceModules: Collection<TestModule>,
dependencies: TestCompilationDependencies,
expectedArtifactFile: File,
specificCompilerArgs: ArgsBuilder.() -> Unit
) = TestCompilationImpl(
targets = settings.get(),
home = settings.get(),
classLoader = settings.get(),
optimizationMode = settings.get(),
memoryModel = settings.get(),
threadStateChecker = settings.get(),
gcType = settings.get(),
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = dependencies,
expectedArtifactFile = expectedArtifactFile,
specificCompilerArgs = specificCompilerArgs
)
}
}
private class ArgsBuilder {
private val args = mutableListOf<String>()
fun add(vararg args: String) {
this.args += args
}
fun add(args: Iterable<String>) {
this.args += args
}
inline fun <T> add(rawArgs: Iterable<T>, transform: (T) -> String) {
rawArgs.mapTo(args) { transform(it) }
}
inline fun <T> addFlattened(rawArgs: Iterable<T>, transform: (T) -> Iterable<String>) {
rawArgs.flatMapTo(args) { transform(it) }
}
inline fun <T, R> addFlattenedTwice(rawArgs: Iterable<T>, transform1: (T) -> Iterable<R>, transform2: (R) -> String) {
rawArgs.forEach { add(transform1(it), transform2) }
}
fun build(): Array<String> = args.toTypedArray()
}
private inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array<String> {
return ArgsBuilder().apply(builderAction).build()
}
private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log")
@@ -0,0 +1,138 @@
/*
* Copyright 2010-2022 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.compilation
import org.jetbrains.kotlin.konan.blackboxtest.support.PackageName
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilerArgs
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule
import org.jetbrains.kotlin.konan.blackboxtest.support.TestModule.Companion.allDependencies
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.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.Settings
import org.jetbrains.kotlin.konan.blackboxtest.support.util.*
import java.io.File
internal class TestCompilationFactory {
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
private sealed interface TestCompilationCacheKey {
data class Klib(val sourceModules: Set<TestModule>, val freeCompilerArgs: TestCompilerArgs) : TestCompilationCacheKey
data class Executable(val sourceModules: Set<TestModule>) : TestCompilationCacheKey
}
fun testCasesToExecutable(testCases: Collection<TestCase>, settings: Settings): TestCompilation {
val rootModules = testCases.flatMapToSet { testCase -> testCase.rootModules }
val cacheKey = TestCompilationCacheKey.Executable(rootModules)
// Fast pass.
cachedCompilations[cacheKey]?.let { return it }
// 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, settings) }
val friends = rootModules.flatMapToSet { it.allFriends }.map { moduleToKlib(it, freeCompilerArgs, settings) }
return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilation.createForExecutable(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = rootModules,
extras = extras,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
expectedExecutableFile = settings.artifactFileForExecutable(rootModules),
)
}
}
private fun moduleToKlib(sourceModule: TestModule, freeCompilerArgs: TestCompilerArgs, settings: Settings): TestCompilation {
val sourceModules = setOf(sourceModule)
val cacheKey = TestCompilationCacheKey.Klib(sourceModules, freeCompilerArgs)
// Fast pass.
cachedCompilations[cacheKey]?.let { return it }
// Long pass.
val libraries = sourceModule.allDependencies.map { moduleToKlib(it, freeCompilerArgs, settings) }
val friends = sourceModule.allFriends.map { moduleToKlib(it, freeCompilerArgs, settings) }
return cachedCompilations.computeIfAbsent(cacheKey) {
TestCompilation.createForKlib(
settings = settings,
freeCompilerArgs = freeCompilerArgs,
sourceModules = sourceModules,
dependencies = TestCompilationDependencies(libraries = libraries, friends = friends),
expectedKlibFile = settings.artifactFileForKlib(sourceModule, freeCompilerArgs)
)
}
}
private fun Settings.artifactFileForExecutable(modules: Set<TestModule.Exclusive>) = when (modules.size) {
1 -> artifactFileForExecutable(modules.first())
else -> multiModuleArtifactFile(modules, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
}
private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) =
singleModuleArtifactFile(module, get<KotlinNativeTargets>().testTarget.family.exeSuffix)
private fun Settings.artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) {
is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib")
is TestModule.Shared -> get<Binaries>().sharedBinariesDir.resolve("${module.name}-${prettyHash(freeCompilerArgs.hashCode())}.klib")
}
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('.')
append(extension)
}
return artifactDirForPackageName(module.testCase.nominalPackageName).resolve(artifactFileName)
}
private fun Settings.multiModuleArtifactFile(modules: Collection<TestModule>, extension: String): File {
var filesCount = 0
var hash = 0
val uniquePackageNames = hashSetOf<PackageName>()
modules.forEach { module ->
module.files.forEach { file ->
filesCount++
hash = hash * 31 + file.hashCode()
}
if (module is TestModule.Exclusive)
uniquePackageNames += module.testCase.nominalPackageName
}
val commonPackageName = uniquePackageNames.findCommonPackageName()
val artifactFileName = buildString {
val prefix = filesCount.toString()
repeat(4 - prefix.length) { append('0') }
append(prefix).append('-')
if (!commonPackageName.isEmpty())
append(commonPackageName.compressedPackageName).append('-')
append(prettyHash(hash))
append('.').append(extension)
}
return artifactDirForPackageName(commonPackageName).resolve(artifactFileName)
}
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()
return outputDir
}
}
@@ -528,19 +528,10 @@ private class ExtTestDataFile(
appendLine()
}
append(
"""
@kotlin.test.Test
fun runTest() {
val result = $entryPointFunctionFQN()
kotlin.test.assertEquals("OK", result, "Test failed with: ${'$'}result")
}
""".trimIndent()
)
append(generateBoxFunctionLauncher(entryPointFunctionFQN))
}
structure.addFileToMainModule(fileName = "__launcher__.kt", text = fileText)
structure.addFileToMainModule(fileName = LAUNCHER_FILE_NAME, text = fileText)
}
private fun doCreateTestCase(
@@ -13,7 +13,7 @@ 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.junit.jupiter.api.fail
import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import java.io.File
internal class PredefinedTestCaseGroupProvider(annotation: PredefinedTestCases) : TestCaseGroupProvider {
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import kotlinx.coroutines.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner.AbstractRun
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.UnfilteredProcessOutput.Companion.launchReader
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2022 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.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestKind
import org.jetbrains.kotlin.konan.blackboxtest.support.TestName
import org.jetbrains.kotlin.test.services.JUnit5Assertions
internal open class BaseTestRunProvider {
protected fun createTestRun(testCase: TestCase, executable: TestExecutable, testRunName: String, testName: TestName?): TestRun {
val runParameters = getTestRunParameters(testCase, testName)
return TestRun(displayName = testRunName, executable, runParameters, testCase.id)
}
protected fun createSingleTestRun(testCase: TestCase, executable: TestExecutable): TestRun = createTestRun(
testCase = testCase,
executable = executable,
testRunName = /* Unimportant. Used only in dynamic tests. */ "",
testName = null
)
private fun getTestRunParameters(testCase: TestCase, testName: TestName?): List<TestRunParameter> = with(testCase) {
when (kind) {
TestKind.STANDALONE_NO_TR -> {
JUnit5Assertions.assertTrue(testName == null)
listOfNotNull(
extras<NoTestRunnerExtras>().inputDataFile?.let(TestRunParameter::WithInputData),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
}
TestKind.STANDALONE -> listOfNotNull(
TestRunParameter.WithTCTestLogger,
testName?.let(TestRunParameter::WithTestFilter),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
TestKind.REGULAR -> listOfNotNull(
TestRunParameter.WithTCTestLogger,
testName?.let(TestRunParameter::WithTestFilter) ?: TestRunParameter.WithPackageFilter(nominalPackageName),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
}
}
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.LoggedData
import org.jetbrains.kotlin.konan.blackboxtest.support.TestExecutable
import org.jetbrains.kotlin.konan.blackboxtest.support.TestName
import org.jetbrains.kotlin.konan.blackboxtest.support.util.TestOutputFilter
import org.jetbrains.kotlin.konan.blackboxtest.support.util.parseGTestListing
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 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.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase
import org.jetbrains.kotlin.konan.blackboxtest.support.TestKind
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertTrue
/**
* Simplified [TestRun] provider. Used in Native KLIB tests.
*/
internal object SimpleTestRunProvider : BaseTestRunProvider() {
fun getTestRun(testCase: TestCase, executable: TestExecutable): TestRun {
assertTrue(testCase.kind != TestKind.REGULAR) { "Regular tests are not supported in ${SimpleTestRunProvider::class.java}" }
return super.createSingleTestRun(testCase, executable)
}
}
@@ -1,10 +1,14 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2022 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
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.LoggedData
import org.jetbrains.kotlin.konan.blackboxtest.support.PackageName
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCaseId
import org.jetbrains.kotlin.konan.blackboxtest.support.TestName
import org.jetbrains.kotlin.konan.blackboxtest.support.util.startsWith
import org.jetbrains.kotlin.test.services.JUnit5Assertions.assertFalse
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -1,24 +1,21 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2022 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.
*/
@file:Suppress("KDocUnresolvedReference")
package org.jetbrains.kotlin.konan.blackboxtest.support
package org.jetbrains.kotlin.konan.blackboxtest.support.runner
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.konan.blackboxtest.support.*
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.NoTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCase.WithTestRunnerExtras
import org.jetbrains.kotlin.konan.blackboxtest.support.TestCompilationResult.Companion.assertSuccess
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilation
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationFactory
import org.jetbrains.kotlin.konan.blackboxtest.support.compilation.TestCompilationResult.Companion.assertSuccessfullyCompiled
import org.jetbrains.kotlin.konan.blackboxtest.support.group.TestCaseGroupProvider
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.AbstractRunner
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestNameExtractor
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.LocalTestRunner
import org.jetbrains.kotlin.konan.blackboxtest.support.settings.KotlinNativeTargets
import org.jetbrains.kotlin.konan.blackboxtest.support.runner.TestRunners.extractTestNames
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
@@ -28,9 +25,12 @@ import org.jetbrains.kotlin.test.services.JUnit5Assertions.fail
import org.junit.jupiter.api.Assumptions.assumeTrue
import org.junit.jupiter.api.extension.ExtensionContext
/**
* [TestRun] provider that is used in Kotlin/Native black box tests together with the corresponding [TestCaseGroupProvider].
*/
internal class TestRunProvider(
private val testCaseGroupProvider: TestCaseGroupProvider
) : ExtensionContext.Store.CloseableResource {
) : BaseTestRunProvider(), ExtensionContext.Store.CloseableResource {
private val compilationFactory = TestCompilationFactory()
private val cachedCompilations = ThreadSafeCache<TestCompilationCacheKey, TestCompilation>()
private val cachedTestNames = ThreadSafeCache<TestCompilationCacheKey, Collection<TestName>>()
@@ -64,8 +64,7 @@ internal class TestRunProvider(
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)
createSingleTestRun(testCase, executable)
}
/**
@@ -102,10 +101,7 @@ internal class TestRunProvider(
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)
}
fun createTestRun(testRunName: String, testName: TestName?) = createTestRun(testCase, executable, testRunName, testName)
when (testCase.kind) {
TestKind.STANDALONE_NO_TR -> {
@@ -159,54 +155,12 @@ internal class TestRunProvider(
}
}
val (executableFile, loggedCompilerCall) = testCompilation.result.assertSuccess() // <-- Compilation happens here.
val (executableFile, loggedCompilerCall) = testCompilation.result.assertSuccessfullyCompiled() // <-- Compilation happens here.
val executable = TestExecutable(executableFile, loggedCompilerCall)
return action(testCase, executable, cacheKey)
}
private fun getRunParameters(testCase: TestCase, testName: TestName?): List<TestRunParameter> = with(testCase) {
when (kind) {
TestKind.STANDALONE_NO_TR -> {
assertTrue(testName == null)
listOfNotNull(
extras<NoTestRunnerExtras>().inputDataFile?.let(TestRunParameter::WithInputData),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
}
TestKind.STANDALONE -> listOfNotNull(
TestRunParameter.WithTCTestLogger,
testName?.let(TestRunParameter::WithTestFilter),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
TestKind.REGULAR -> listOfNotNull(
TestRunParameter.WithTCTestLogger,
testName?.let(TestRunParameter::WithTestFilter) ?: TestRunParameter.WithPackageFilter(nominalPackageName),
expectedOutputDataFile?.let(TestRunParameter::WithExpectedOutputData)
)
}
}
// Currently, only local test runner is supported.
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, 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) =
if (testCase.kind == TestKind.REGULAR)
filter { testName -> testName.packageName.startsWith(testCase.nominalPackageName) }
@@ -215,14 +169,6 @@ internal class TestRunProvider(
else
this
private fun KotlinNativeTargets.runningAtNonHostTarget(): Nothing = fail {
"""
Running at non-host target is not supported yet.
Compilation target: $testTarget
Host target: $hostTarget
""".trimIndent()
}
override fun close() {
if (testCaseGroupProvider is Disposable) {
Disposer.dispose(testCaseGroupProvider)
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2022 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.runner
import org.jetbrains.kotlin.konan.blackboxtest.support.TestName
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.test.services.JUnit5Assertions.fail
internal object TestRunners {
// Currently, only local test runner is supported.
fun createProperTestRunner(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.
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 KotlinNativeTargets.runningAtNonHostTarget(): Nothing = fail {
"""
Running at non-host target is not supported yet.
Compilation target: $testTarget
Host target: $hostTarget
""".trimIndent()
}
}
@@ -29,7 +29,7 @@ internal abstract class Settings(private val parent: Settings?, settings: Iterab
}
/**
* The hierarchy of settings containers:
* The hierarchy of settings containers for Native black box tests:
*
* | Settings container | Parent | Scope |
* | --------------------- | ---------------------- | ------------------------------------------- |
@@ -40,3 +40,13 @@ internal abstract class Settings(private val parent: Settings?, settings: Iterab
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)
/**
* The hierarchy of settings containers for simple Native tests (e.g. KLIB tests):
*
* | Settings container | Parent | Scope |
* | ----------------------- | --------------------- | ---------------------------------------|
* | [TestProcessSettings] | `null` | The whole Gradle test executor process |
* | [SimpleTestRunSettings] | [TestProcessSettings] | The single test run of a test function |
*/
internal class SimpleTestRunSettings(parent: TestProcessSettings, settings: Iterable<Any>) : Settings(parent, settings)
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2022 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.AbstractNativeSimpleTest
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 SimpleTestInstances(val allInstances: List<Any>) {
val enclosingTestInstance: AbstractNativeSimpleTest
get() = allInstances.firstOrNull().cast()
}
/**
* The build directory specifically for the current test function.
*/
internal class SimpleTestDirectories(val testBuildDir: File)
@@ -90,7 +90,7 @@ internal enum class GCType(val compilerFlag: String?) {
/**
* Current project's directories.
*/
internal class BaseDirs(val buildDir: File)
internal class BaseDirs(val testBuildDir: File)
/**
* Timeouts.
@@ -16,7 +16,7 @@ import java.io.File
* [allInstances] - all test class instances ordered from innermost to outermost
* [enclosingTestInstance] - the outermost test instance
*/
internal class TestInstances(val allInstances: List<Any>) {
internal class BlackBoxTestInstances(val allInstances: List<Any>) {
val enclosingTestInstance: AbstractNativeBlackBoxTest
get() = allInstances.firstOrNull().cast()
}
@@ -31,3 +31,13 @@ internal fun computeGeneratedSourcesDir(testDataBaseDir: File, testDataFile: Fil
.resolve(testDataFileDir.relativeTo(testDataBaseDir))
.resolve(testDataFile.nameWithoutExtension)
}
internal fun generateBoxFunctionLauncher(entryPointFunctionFQN: String): String =
"""
@kotlin.test.Test
fun runTest() {
val result = $entryPointFunctionFQN()
kotlin.test.assertEquals("OK", result, "Test failed with: ${'$'}result")
}
""".trimIndent()
@@ -22,8 +22,10 @@ private fun sanitize(s: String, allowDots: Boolean = false) = buildString {
}
internal const val DEFAULT_FILE_NAME = "main.kt"
internal const val LAUNCHER_FILE_NAME = "__launcher__.kt"
internal const val DEFAULT_MODULE_NAME = "default"
internal const val SUPPORT_MODULE_NAME = "support"
internal const val LAUNCHER_MODULE_NAME = "__launcher__" // Used only in KLIB tests.
internal fun prettyHash(hash: Int): String = hash.toUInt().toString(16).padStart(8, '0')