[IR, Native] KLIB ABI tests: add support to new test infrastructure
^KT-50775
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
+56
@@ -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/");
|
||||
}
|
||||
}
|
||||
+8
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+126
@@ -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)
|
||||
}
|
||||
}
|
||||
+26
@@ -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()
|
||||
}
|
||||
}
|
||||
+2
@@ -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
|
||||
|
||||
-351
@@ -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 simplicity’s 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
|
||||
}
|
||||
}
|
||||
+415
@@ -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 simplicity’s 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
|
||||
}
|
||||
-418
@@ -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")
|
||||
+74
@@ -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
|
||||
)
|
||||
+297
@@ -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")
|
||||
+138
@@ -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
|
||||
}
|
||||
}
|
||||
+2
-11
@@ -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(
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
-1
@@ -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
|
||||
|
||||
+48
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -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
|
||||
|
||||
+20
@@ -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)
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -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
|
||||
+14
-68
@@ -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)
|
||||
+42
@@ -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()
|
||||
}
|
||||
}
|
||||
+11
-1
@@ -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)
|
||||
|
||||
+28
@@ -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)
|
||||
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+1
-1
@@ -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()
|
||||
}
|
||||
|
||||
+10
@@ -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()
|
||||
|
||||
+2
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user