diff --git a/native/native.tests/build.gradle.kts b/native/native.tests/build.gradle.kts index 63bdf1ccbc6..f749b99b26d 100644 --- a/native/native.tests/build.gradle.kts +++ b/native/native.tests/build.gradle.kts @@ -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") } diff --git a/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeKlibABITestGenerated.java b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeKlibABITestGenerated.java new file mode 100644 index 00000000000..49fad877e0c --- /dev/null +++ b/native/native.tests/tests-gen/org/jetbrains/kotlin/konan/blackboxtest/NativeKlibABITestGenerated.java @@ -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/"); + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt similarity index 86% rename from native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt rename to native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt index 7af269f71d0..1d7d1cb7991 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeBlackboxTests.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/generators/tests/GenerateNativeTests.kt @@ -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 { + model("klibABI/", pattern = "^([^_](.+))$", recursive = false) + } + } } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeBlackBoxTest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeBlackBoxTest.kt index 51cfe8ff1e5..a347c25fd1f 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeBlackBoxTest.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeBlackBoxTest.kt @@ -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() } } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt new file mode 100644 index 00000000000..f735c875376 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeKlibABITest.kt @@ -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 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, 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) { + val (sourceDir, outputDir) = AbstractKlibABITestCase.createModuleDirs(buildDir, LAUNCHER_MODULE_NAME) + val executableFile = outputDir.resolve("app." + testRunSettings.get().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) = TestCompilationDependencies( + libraries = dependencies.map(TestCompilation.Companion::createForExistingArtifact) + ) + + private val buildDir: File get() = testRunSettings.get().testBuildDir + private val stdlibFile: File get() = testRunSettings.get().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) + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeSimpleTest.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeSimpleTest.kt new file mode 100644 index 00000000000..9b18648958c --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/AbstractNativeSimpleTest.kt @@ -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() + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/LoggedData.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/LoggedData.kt index 404f080bf52..a31c66359a5 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/LoggedData.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/LoggedData.kt @@ -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 diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt deleted file mode 100644 index 3fb7d63e241..00000000000 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeBlackBoxTestSupport.kt +++ /dev/null @@ -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().path) - - // Inject the required properties to test instance. - with(settings.get().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 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 > enumSystemProperty(propertyName: String, values: Array, 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()) { - 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() ?: 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 = 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() - ) - ) - } - - private fun ExtensionContext.computeTestInstances(): TestInstances = TestInstances(requiredTestInstances.allInstances) - - /*************** Test run provider ***************/ - - private fun ExtensionContext.getOrCreateTestRunProvider(): TestRunProvider = - root.getStore(NAMESPACE).getOrComputeIfAbsent(testClassKeyFor()) { - val testCaseGroupProvider = createTestCaseGroupProvider(getOrCreateTestClassSettings().get()) - TestRunProvider(testCaseGroupProvider) - }.cast() - - private fun createTestCaseGroupProvider(computedTestConfiguration: ComputedTestConfiguration): TestCaseGroupProvider { - val (testConfiguration: TestConfiguration, testConfigurationAnnotation: Annotation) = computedTestConfiguration - val providerClass: KClass = 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 = 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 ExtensionContext.testClassKeyFor(): String = - enclosingTestClass.name + "#" + T::class.java.name - } -} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt new file mode 100644 index 00000000000..6ab039be98b --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/NativeTestSupport.kt @@ -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().path) + + // Inject the required properties to test instance. + with(settings.get().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().path) + + // Inject the required properties to test instance. + with(settings.get().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 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 > enumSystemProperty(propertyName: String, values: Array, 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()) { + 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() ?: 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 = 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() + ) + ) + } + + 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()) { + val testCaseGroupProvider = createTestCaseGroupProvider(getOrCreateTestClassSettings().get()) + TestRunProvider(testCaseGroupProvider) + }.cast() + + private fun createTestCaseGroupProvider(computedTestConfiguration: ComputedTestConfiguration): TestCaseGroupProvider { + val (testConfiguration: TestConfiguration, testConfigurationAnnotation: Annotation) = computedTestConfiguration + val providerClass: KClass = 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 = 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> + get() = generateSequence(requiredTestClass) { it.enclosingClass } + + private val ExtensionContext.enclosingTestClass: Class<*> + get() = testClasses.last() + + private inline fun ExtensionContext.testClassKeyFor(): String = + enclosingTestClass.name + "#" + T::class.java.name +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCompiler.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCompiler.kt deleted file mode 100644 index 9b42588f374..00000000000 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestCompiler.kt +++ /dev/null @@ -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() - - private sealed interface TestCompilationCacheKey { - data class Klib(val sourceModules: Set, val freeCompilerArgs: TestCompilerArgs) : TestCompilationCacheKey - data class Executable(val sourceModules: Set) : TestCompilationCacheKey - } - - fun testCasesToExecutable(testCases: Collection, 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().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) = when (modules.size) { - 1 -> artifactFileForExecutable(modules.first()) - else -> multiModuleArtifactFile(modules, get().testTarget.family.exeSuffix) - } - - private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) = - singleModuleArtifactFile(module, get().testTarget.family.exeSuffix) - - private fun Settings.artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) { - is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib") - is TestModule.Shared -> get().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, extension: String): File { - var filesCount = 0 - var hash = 0 - val uniquePackageNames = hashSetOf() - - 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().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) : 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 = emptyList(), - val friends: Collection = emptyList(), - val includedLibraries: Collection = emptyList() -) { - fun collectFailures(): Set = 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, - 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() - - fun add(vararg args: String) { - this.args += args - } - - fun add(args: Iterable) { - this.args += args - } - - inline fun add(rawArgs: Iterable, transform: (T) -> String) { - rawArgs.mapTo(args) { transform(it) } - } - - inline fun addFlattened(rawArgs: Iterable, transform: (T) -> Iterable) { - rawArgs.flatMapTo(args) { transform(it) } - } - - inline fun addFlattenedTwice(rawArgs: Iterable, transform1: (T) -> Iterable, transform2: (R) -> String) { - rawArgs.forEach { add(transform1(it), transform2) } - } - - fun build(): Array = args.toTypedArray() -} - -private inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array { - return ArgsBuilder().apply(builderAction).build() -} - -private fun callCompiler(compilerArgs: Array, 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::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") diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/CompilerCall.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/CompilerCall.kt new file mode 100644 index 00000000000..e5d5e8e1ea8 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/CompilerCall.kt @@ -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, 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::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 +) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt new file mode 100644 index 00000000000..586f1a7aba8 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilation.kt @@ -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, + 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, + 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) : 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 = emptyList(), + val friends: Collection = emptyList(), + val includedLibraries: Collection = emptyList() +) { + fun collectFailures(): Set = 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, + 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, + 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() + + fun add(vararg args: String) { + this.args += args + } + + fun add(args: Iterable) { + this.args += args + } + + inline fun add(rawArgs: Iterable, transform: (T) -> String) { + rawArgs.mapTo(args) { transform(it) } + } + + inline fun addFlattened(rawArgs: Iterable, transform: (T) -> Iterable) { + rawArgs.flatMapTo(args) { transform(it) } + } + + inline fun addFlattenedTwice(rawArgs: Iterable, transform1: (T) -> Iterable, transform2: (R) -> String) { + rawArgs.forEach { add(transform1(it), transform2) } + } + + fun build(): Array = args.toTypedArray() +} + +private inline fun buildArgs(builderAction: ArgsBuilder.() -> Unit): Array { + return ArgsBuilder().apply(builderAction).build() +} + +private fun getLogFile(expectedArtifactFile: File): File = expectedArtifactFile.resolveSibling(expectedArtifactFile.name + ".log") diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt new file mode 100644 index 00000000000..1da23884ce9 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/compilation/TestCompilationFactory.kt @@ -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() + + private sealed interface TestCompilationCacheKey { + data class Klib(val sourceModules: Set, val freeCompilerArgs: TestCompilerArgs) : TestCompilationCacheKey + data class Executable(val sourceModules: Set) : TestCompilationCacheKey + } + + fun testCasesToExecutable(testCases: Collection, 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) = when (modules.size) { + 1 -> artifactFileForExecutable(modules.first()) + else -> multiModuleArtifactFile(modules, get().testTarget.family.exeSuffix) + } + + private fun Settings.artifactFileForExecutable(module: TestModule.Exclusive) = + singleModuleArtifactFile(module, get().testTarget.family.exeSuffix) + + private fun Settings.artifactFileForKlib(module: TestModule, freeCompilerArgs: TestCompilerArgs) = when (module) { + is TestModule.Exclusive -> singleModuleArtifactFile(module, "klib") + is TestModule.Shared -> get().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, extension: String): File { + var filesCount = 0 + var hash = 0 + val uniquePackageNames = hashSetOf() + + 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().testBinariesDir + val outputDir = if (packageName != null) baseDir.resolve(packageName.compressedPackageName) else baseDir + + outputDir.mkdirs() + + return outputDir + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt index 0bc096d7e58..374cd93203a 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/ExtTestCaseGroupProvider.kt @@ -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( diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/PredefinedTestCaseGroupProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/PredefinedTestCaseGroupProvider.kt index e5f0f156daf..003c8db2970 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/PredefinedTestCaseGroupProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/group/PredefinedTestCaseGroupProvider.kt @@ -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 { diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt index 848426eabe5..f9e146cac6a 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/AbstractLocalProcessRunner.kt @@ -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 diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt new file mode 100644 index 00000000000..8969e6b72a8 --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/BaseTestRunProvider.kt @@ -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 = with(testCase) { + when (kind) { + TestKind.STANDALONE_NO_TR -> { + JUnit5Assertions.assertTrue(testName == null) + listOfNotNull( + extras().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) + ) + } + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt index 7f2f5be9e5a..b5e3774bbfe 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/LocalTestNameExtractor.kt @@ -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 diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/SimpleTestRunProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/SimpleTestRunProvider.kt new file mode 100644 index 00000000000..c744c711abe --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/SimpleTestRunProvider.kt @@ -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) + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt similarity index 85% rename from native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt rename to native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt index 0c074780333..1e4f4ae5682 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestExecutable.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRun.kt @@ -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 diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt similarity index 71% rename from native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt rename to native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt index e0e4d0fe2ca..948062fe1e9 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/TestRunProvider.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunProvider.kt @@ -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() private val cachedTestNames = ThreadSafeCache>() @@ -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> = 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 = with(testCase) { - when (kind) { - TestKind.STANDALONE_NO_TR -> { - assertTrue(testName == null) - listOfNotNull( - extras().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()) { - if (testTarget == hostTarget) - LocalTestRunner(testRun, get().executionTimeout) - else - runningAtNonHostTarget() - } - } - - // Currently, only local test name extractor is supported. - private fun extractTestNames(executable: TestExecutable, settings: Settings): Collection = with(settings) { - with(get()) { - if (testTarget == hostTarget) - LocalTestNameExtractor(executable, get().executionTimeout).run() - else - runningAtNonHostTarget() - } - } - private fun Collection.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) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt new file mode 100644 index 00000000000..c3409902f6c --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/runner/TestRunners.kt @@ -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()) { + if (testTarget == hostTarget) + LocalTestRunner(testRun, get().executionTimeout) + else + runningAtNonHostTarget() + } + } + + // Currently, only local test name extractor is supported. + fun extractTestNames(executable: TestExecutable, settings: Settings): Collection = with(settings) { + with(get()) { + if (testTarget == hostTarget) + LocalTestNameExtractor(executable, get().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() + } +} diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SettingsContainers.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SettingsContainers.kt index eefa9d29d13..5713c04ef5c 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SettingsContainers.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SettingsContainers.kt @@ -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) : Settings(parent, settings) internal class TestRunSettings(parent: TestClassSettings, settings: Iterable) : 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) : Settings(parent, settings) diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SimpleTestRunSettings.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SimpleTestRunSettings.kt new file mode 100644 index 00000000000..5abc35a81da --- /dev/null +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/SimpleTestRunSettings.kt @@ -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) { + val enclosingTestInstance: AbstractNativeSimpleTest + get() = allInstances.firstOrNull().cast() +} + +/** + * The build directory specifically for the current test function. + */ +internal class SimpleTestDirectories(val testBuildDir: File) + + diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt index 8b01cebc772..cc24fa02e57 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestProcessSettings.kt @@ -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. diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestRunSettings.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestRunSettings.kt index d05aa4fc5dd..a9b010a245e 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestRunSettings.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/settings/TestRunSettings.kt @@ -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) { +internal class BlackBoxTestInstances(val allInstances: List) { val enclosingTestInstance: AbstractNativeBlackBoxTest get() = allInstances.firstOrNull().cast() } diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Files.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Files.kt index 21f706d2a1c..de271609f93 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Files.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Files.kt @@ -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() diff --git a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt index 2a62aec88bc..72431c0cd8e 100644 --- a/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt +++ b/native/native.tests/tests/org/jetbrains/kotlin/konan/blackboxtest/support/util/Names.kt @@ -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')