[Test] Add ability to specify main class in Box tests

Also slightly refactor some existing services to make them more idiomatic
This commit is contained in:
Dmitriy Novozhilov
2021-10-06 15:03:48 +03:00
committed by TeamCityServer
parent aab8870903
commit a68c4ec7be
9 changed files with 86 additions and 31 deletions
@@ -5,14 +5,15 @@
package org.jetbrains.kotlin.test.services
import org.jetbrains.kotlin.test.model.TestModule
import java.io.File
abstract class RuntimeClasspathProvider {
abstract fun runtimeClassPaths(): List<File>
abstract class RuntimeClasspathProvider(val testServices: TestServices) {
abstract fun runtimeClassPaths(module: TestModule): List<File>
}
class RuntimeClasspathProviderHolder(val providers: List<RuntimeClasspathProvider>) : TestService
class RuntimeClasspathProvidersContainer(val providers: List<RuntimeClasspathProvider>) : TestService
private val TestServices.runtimeClasspathProviderHolder: RuntimeClasspathProviderHolder by TestServices.testServiceAccessor()
private val TestServices.runtimeClasspathProviderContainer: RuntimeClasspathProvidersContainer by TestServices.testServiceAccessor()
val TestServices.runtimeClasspathProviders: List<RuntimeClasspathProvider>
get() = runtimeClasspathProviderHolder.providers
get() = runtimeClasspathProviderContainer.providers
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.test.services
import org.jetbrains.kotlin.fir.utils.ArrayMapAccessor
import org.jetbrains.kotlin.fir.utils.ComponentArrayOwner
import org.jetbrains.kotlin.fir.utils.NullableArrayMapAccessor
import org.jetbrains.kotlin.fir.utils.TypeRegistry
import kotlin.reflect.KClass
@@ -31,6 +32,10 @@ class TestServices : ComponentArrayOwner<TestService, TestService>(){
inline fun <reified T : TestService> testServiceAccessor(): ArrayMapAccessor<TestService, TestService, T> {
return generateAccessor(T::class)
}
inline fun <reified T : TestService> nullableTestServiceAccessor(): NullableArrayMapAccessor<TestService, TestService, T> {
return generateNullableAccessor(T::class)
}
}
fun register(data: ServiceRegistrationData) {
@@ -14,8 +14,8 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.TestJdkKind
import org.jetbrains.kotlin.test.backend.codegenSuppressionChecker
import org.jetbrains.kotlin.test.clientserver.TestProxy
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.ATTACH_DEBUGGER
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.REQUIRES_SEPARATE_PROCESS
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.JDK_KIND
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives.ENABLE_JVM_PREVIEW
import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue
@@ -23,9 +23,13 @@ import org.jetbrains.kotlin.test.model.BinaryArtifacts
import org.jetbrains.kotlin.test.model.DependencyKind
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.model.nameWithoutExtension
import org.jetbrains.kotlin.test.services.*
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import org.jetbrains.kotlin.test.services.configuration.JvmEnvironmentConfigurator.Companion.TEST_CONFIGURATION_KIND_KEY
import org.jetbrains.kotlin.test.services.dependencyProvider
import org.jetbrains.kotlin.test.services.jvm.compiledClassesManager
import org.jetbrains.kotlin.test.services.jvm.jvmBoxMainClassProvider
import org.jetbrains.kotlin.test.services.runtimeClasspathProviders
import org.jetbrains.kotlin.test.services.sourceProviders.MainFunctionForBlackBoxTestsSourceProvider
import org.jetbrains.kotlin.test.services.sourceProviders.MainFunctionForBlackBoxTestsSourceProvider.Companion.containsBoxMethod
import org.jetbrains.kotlin.test.util.KtTestUtil
@@ -33,10 +37,8 @@ import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.io.File
import java.lang.reflect.Method
import java.net.URI
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
@@ -134,8 +136,8 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
)
} else {
val jdkKind = module.directives.singleOrZeroValue(JDK_KIND)
if (jdkKind?.requiresSeparateProcess == true) {
runSeparateJvmInstance(module, jdkKind, classLoader, classFileFactory)
if (jdkKind?.requiresSeparateProcess == true || REQUIRES_SEPARATE_PROCESS in module.directives) {
runSeparateJvmInstance(module, jdkKind ?: TestJdkKind.FULL_JDK, classLoader, classFileFactory)
} else {
runBoxInCurrentProcess(
classLoader,
@@ -181,6 +183,7 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
classFileFactory: ClassFileFactory
): String {
val jdkHome = when (jdkKind) {
TestJdkKind.FULL_JDK -> KtTestUtil.getJdk8Home()
TestJdkKind.FULL_JDK_11 -> KtTestUtil.getJdk11Home()
TestJdkKind.FULL_JDK_17 -> KtTestUtil.getJdk17Home()
else -> error("Unsupported JDK kind: $jdkKind")
@@ -192,14 +195,18 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
val classPath = extractClassPath(module, classLoader, classFileFactory)
val mainFile = module.files.firstOrNull {
it.name == MainFunctionForBlackBoxTestsSourceProvider.BOX_MAIN_FILE_NAME && it.isAdditional
} ?: error("No file with main function was generated. Please check TODO source provider")
val mainClassAndArguments = testServices.jvmBoxMainClassProvider?.getMainClassNameAndAdditionalArguments() ?: run {
val mainFile = module.files.firstOrNull {
it.name == MainFunctionForBlackBoxTestsSourceProvider.BOX_MAIN_FILE_NAME && it.isAdditional
} ?: error("No file with main function was generated. Please check TODO source provider")
val mainFqName = listOfNotNull(
MainFunctionForBlackBoxTestsSourceProvider.detectPackage(mainFile),
"${mainFile.nameWithoutExtension}Kt"
).joinToString(".")
val mainFqName = listOfNotNull(
MainFunctionForBlackBoxTestsSourceProvider.detectPackage(mainFile),
"${mainFile.nameWithoutExtension}Kt"
).joinToString(".")
listOf(mainFqName)
}
val command = listOfNotNull(
javaExe.absolutePath,
@@ -208,8 +215,8 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
runIf(ENABLE_JVM_PREVIEW in module.directives) { "--enable-preview" },
"-classpath",
classPath.joinToString(File.pathSeparator, transform = { File(it.toURI()).absolutePath }),
mainFqName,
)
) + mainClassAndArguments
val process = ProcessBuilder(command).start()
process.waitFor(1, TimeUnit.MINUTES)
return try {
@@ -268,7 +275,7 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
reportProblems: Boolean
): GeneratedClassLoader {
val classLoader = createClassLoader(module, classFileFactory)
if (module.directives.singleOrZeroValue(JDK_KIND)?.requiresSeparateProcess != true) {
if (REQUIRES_SEPARATE_PROCESS !in module.directives && module.directives.singleOrZeroValue(JDK_KIND)?.requiresSeparateProcess != true) {
val verificationSucceeded = CodegenTestUtil.verifyAllFilesWithAsm(classFileFactory, classLoader, reportProblems)
if (!verificationSucceeded) {
assertions.fail { "Verification failed: see exceptions above" }
@@ -308,7 +315,7 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
}
computeClasspath(rootModule, true)
testServices.runtimeClasspathProviders.flatMapTo(result) { it.runtimeClassPaths() }
testServices.runtimeClasspathProviders.flatMapTo(result) { it.runtimeClassPaths(rootModule) }
return result
}
@@ -184,7 +184,7 @@ class TestConfigurationBuilder {
compilerConfigurationProvider = provider
}
fun useCustomRuntimeClasspathProvider(provider: Constructor<RuntimeClasspathProvider>) {
fun useCustomRuntimeClasspathProviders(vararg provider: Constructor<RuntimeClasspathProvider>) {
runtimeClasspathProviders += provider
}
@@ -164,4 +164,11 @@ object CodegenTestDirectives : SimpleDirectivesContainer() {
Please don't forget to remove this directive after debug session is over
""".trimIndent()
)
val REQUIRES_SEPARATE_PROCESS by directive(
description = """
Force run `box` method in separate process even if jdk of current process
is same as required jdk for test
""".trimIndent()
)
}
@@ -53,7 +53,7 @@ class TestConfigurationImpl(
init {
testServices.register(KotlinTestInfo::class, testInfo)
val runtimeClassPathProviders = runtimeClasspathProviders.map { it.invoke(testServices) }
testServices.register(RuntimeClasspathProviderHolder::class, RuntimeClasspathProviderHolder(runtimeClassPathProviders))
testServices.register(RuntimeClasspathProvidersContainer::class, RuntimeClasspathProvidersContainer(runtimeClassPathProviders))
additionalServices.forEach { testServices.register(it) }
}
@@ -0,0 +1,30 @@
/*
* 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.test.services.jvm
import org.jetbrains.kotlin.test.services.TestService
import org.jetbrains.kotlin.test.services.TestServices
/**
* This service is used to provide main class and arguments for invoke of `box` method
* in separate JVM instance if it somehow differs from default implementation, which is
*
* fun main() {
* val result = box()
* if (result != "OK") {
* throw AssertionError()
* }
* }
*
* Please note that can work only if running in separate JDK is enabled,
* see [CodegenTestDirectives.REQUIRES_SEPARATE_PROCESS] directive
*
*/
abstract class JvmBoxMainClassProvider(val testServices: TestServices) : TestService {
abstract fun getMainClassNameAndAdditionalArguments(): List<String>
}
val TestServices.jvmBoxMainClassProvider: JvmBoxMainClassProvider? by TestServices.nullableTestServiceAccessor()
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.test.services.sourceProviders
import org.jetbrains.kotlin.test.directives.CodegenTestDirectives.REQUIRES_SEPARATE_PROCESS
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.JDK_KIND
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue
@@ -36,8 +37,9 @@ class MainFunctionForBlackBoxTestsSourceProvider(testServices: TestServices) : A
}
override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List<TestFile> {
val jdkKind = module.directives.singleOrZeroValue(JDK_KIND) ?: return emptyList()
if (!jdkKind.requiresSeparateProcess) return emptyList()
if (REQUIRES_SEPARATE_PROCESS !in module.directives && module.directives.singleOrZeroValue(JDK_KIND)?.requiresSeparateProcess != true) {
return emptyList()
}
val fileWithBox = module.files.firstOrNull { containsBoxMethod(it) } ?: return emptyList()
@@ -63,9 +63,12 @@ internal fun TestConfigurationBuilder.configureForKotlinxSerialization(libraries
}
}
})
useCustomRuntimeClasspathProvider {
object : RuntimeClasspathProvider() {
override fun runtimeClassPaths(): List<File> = librariesPaths
useCustomRuntimeClasspathProviders(
{
object : RuntimeClasspathProvider(it) {
override fun runtimeClassPaths(module: TestModule): List<File> = librariesPaths
}
}
}
}
)
}