[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 package org.jetbrains.kotlin.test.services
import org.jetbrains.kotlin.test.model.TestModule
import java.io.File import java.io.File
abstract class RuntimeClasspathProvider { abstract class RuntimeClasspathProvider(val testServices: TestServices) {
abstract fun runtimeClassPaths(): List<File> 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> 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.ArrayMapAccessor
import org.jetbrains.kotlin.fir.utils.ComponentArrayOwner import org.jetbrains.kotlin.fir.utils.ComponentArrayOwner
import org.jetbrains.kotlin.fir.utils.NullableArrayMapAccessor
import org.jetbrains.kotlin.fir.utils.TypeRegistry import org.jetbrains.kotlin.fir.utils.TypeRegistry
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -31,6 +32,10 @@ class TestServices : ComponentArrayOwner<TestService, TestService>(){
inline fun <reified T : TestService> testServiceAccessor(): ArrayMapAccessor<TestService, TestService, T> { inline fun <reified T : TestService> testServiceAccessor(): ArrayMapAccessor<TestService, TestService, T> {
return generateAccessor(T::class) return generateAccessor(T::class)
} }
inline fun <reified T : TestService> nullableTestServiceAccessor(): NullableArrayMapAccessor<TestService, TestService, T> {
return generateNullableAccessor(T::class)
}
} }
fun register(data: ServiceRegistrationData) { 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.TestJdkKind
import org.jetbrains.kotlin.test.backend.codegenSuppressionChecker import org.jetbrains.kotlin.test.backend.codegenSuppressionChecker
import org.jetbrains.kotlin.test.clientserver.TestProxy 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.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.JvmEnvironmentConfigurationDirectives.JDK_KIND
import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives.ENABLE_JVM_PREVIEW import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives.ENABLE_JVM_PREVIEW
import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue 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.DependencyKind
import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.model.nameWithoutExtension 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.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.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
import org.jetbrains.kotlin.test.services.sourceProviders.MainFunctionForBlackBoxTestsSourceProvider.Companion.containsBoxMethod import org.jetbrains.kotlin.test.services.sourceProviders.MainFunctionForBlackBoxTestsSourceProvider.Companion.containsBoxMethod
import org.jetbrains.kotlin.test.util.KtTestUtil 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 org.jetbrains.kotlin.utils.addToStdlib.runIf
import java.io.File import java.io.File
import java.lang.reflect.Method import java.lang.reflect.Method
import java.net.URI
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.Paths
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) { class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testServices) {
@@ -134,8 +136,8 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
) )
} else { } else {
val jdkKind = module.directives.singleOrZeroValue(JDK_KIND) val jdkKind = module.directives.singleOrZeroValue(JDK_KIND)
if (jdkKind?.requiresSeparateProcess == true) { if (jdkKind?.requiresSeparateProcess == true || REQUIRES_SEPARATE_PROCESS in module.directives) {
runSeparateJvmInstance(module, jdkKind, classLoader, classFileFactory) runSeparateJvmInstance(module, jdkKind ?: TestJdkKind.FULL_JDK, classLoader, classFileFactory)
} else { } else {
runBoxInCurrentProcess( runBoxInCurrentProcess(
classLoader, classLoader,
@@ -181,6 +183,7 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
classFileFactory: ClassFileFactory classFileFactory: ClassFileFactory
): String { ): String {
val jdkHome = when (jdkKind) { val jdkHome = when (jdkKind) {
TestJdkKind.FULL_JDK -> KtTestUtil.getJdk8Home()
TestJdkKind.FULL_JDK_11 -> KtTestUtil.getJdk11Home() TestJdkKind.FULL_JDK_11 -> KtTestUtil.getJdk11Home()
TestJdkKind.FULL_JDK_17 -> KtTestUtil.getJdk17Home() TestJdkKind.FULL_JDK_17 -> KtTestUtil.getJdk17Home()
else -> error("Unsupported JDK kind: $jdkKind") else -> error("Unsupported JDK kind: $jdkKind")
@@ -192,14 +195,18 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
val classPath = extractClassPath(module, classLoader, classFileFactory) val classPath = extractClassPath(module, classLoader, classFileFactory)
val mainFile = module.files.firstOrNull { val mainClassAndArguments = testServices.jvmBoxMainClassProvider?.getMainClassNameAndAdditionalArguments() ?: run {
it.name == MainFunctionForBlackBoxTestsSourceProvider.BOX_MAIN_FILE_NAME && it.isAdditional val mainFile = module.files.firstOrNull {
} ?: error("No file with main function was generated. Please check TODO source provider") 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( val mainFqName = listOfNotNull(
MainFunctionForBlackBoxTestsSourceProvider.detectPackage(mainFile), MainFunctionForBlackBoxTestsSourceProvider.detectPackage(mainFile),
"${mainFile.nameWithoutExtension}Kt" "${mainFile.nameWithoutExtension}Kt"
).joinToString(".") ).joinToString(".")
listOf(mainFqName)
}
val command = listOfNotNull( val command = listOfNotNull(
javaExe.absolutePath, javaExe.absolutePath,
@@ -208,8 +215,8 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
runIf(ENABLE_JVM_PREVIEW in module.directives) { "--enable-preview" }, runIf(ENABLE_JVM_PREVIEW in module.directives) { "--enable-preview" },
"-classpath", "-classpath",
classPath.joinToString(File.pathSeparator, transform = { File(it.toURI()).absolutePath }), classPath.joinToString(File.pathSeparator, transform = { File(it.toURI()).absolutePath }),
mainFqName, ) + mainClassAndArguments
)
val process = ProcessBuilder(command).start() val process = ProcessBuilder(command).start()
process.waitFor(1, TimeUnit.MINUTES) process.waitFor(1, TimeUnit.MINUTES)
return try { return try {
@@ -268,7 +275,7 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
reportProblems: Boolean reportProblems: Boolean
): GeneratedClassLoader { ): GeneratedClassLoader {
val classLoader = createClassLoader(module, classFileFactory) 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) val verificationSucceeded = CodegenTestUtil.verifyAllFilesWithAsm(classFileFactory, classLoader, reportProblems)
if (!verificationSucceeded) { if (!verificationSucceeded) {
assertions.fail { "Verification failed: see exceptions above" } assertions.fail { "Verification failed: see exceptions above" }
@@ -308,7 +315,7 @@ class JvmBoxRunner(testServices: TestServices) : JvmBinaryArtifactHandler(testSe
} }
computeClasspath(rootModule, true) computeClasspath(rootModule, true)
testServices.runtimeClasspathProviders.flatMapTo(result) { it.runtimeClassPaths() } testServices.runtimeClasspathProviders.flatMapTo(result) { it.runtimeClassPaths(rootModule) }
return result return result
} }
@@ -184,7 +184,7 @@ class TestConfigurationBuilder {
compilerConfigurationProvider = provider compilerConfigurationProvider = provider
} }
fun useCustomRuntimeClasspathProvider(provider: Constructor<RuntimeClasspathProvider>) { fun useCustomRuntimeClasspathProviders(vararg provider: Constructor<RuntimeClasspathProvider>) {
runtimeClasspathProviders += provider runtimeClasspathProviders += provider
} }
@@ -164,4 +164,11 @@ object CodegenTestDirectives : SimpleDirectivesContainer() {
Please don't forget to remove this directive after debug session is over Please don't forget to remove this directive after debug session is over
""".trimIndent() """.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 { init {
testServices.register(KotlinTestInfo::class, testInfo) testServices.register(KotlinTestInfo::class, testInfo)
val runtimeClassPathProviders = runtimeClasspathProviders.map { it.invoke(testServices) } val runtimeClassPathProviders = runtimeClasspathProviders.map { it.invoke(testServices) }
testServices.register(RuntimeClasspathProviderHolder::class, RuntimeClasspathProviderHolder(runtimeClassPathProviders)) testServices.register(RuntimeClasspathProvidersContainer::class, RuntimeClasspathProvidersContainer(runtimeClassPathProviders))
additionalServices.forEach { testServices.register(it) } 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 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.JvmEnvironmentConfigurationDirectives.JDK_KIND
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.directives.model.singleOrZeroValue 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> { override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List<TestFile> {
val jdkKind = module.directives.singleOrZeroValue(JDK_KIND) ?: return emptyList() if (REQUIRES_SEPARATE_PROCESS !in module.directives && module.directives.singleOrZeroValue(JDK_KIND)?.requiresSeparateProcess != true) {
if (!jdkKind.requiresSeparateProcess) return emptyList() return emptyList()
}
val fileWithBox = module.files.firstOrNull { containsBoxMethod(it) } ?: return emptyList() val fileWithBox = module.files.firstOrNull { containsBoxMethod(it) } ?: return emptyList()
@@ -63,9 +63,12 @@ internal fun TestConfigurationBuilder.configureForKotlinxSerialization(libraries
} }
} }
}) })
useCustomRuntimeClasspathProvider {
object : RuntimeClasspathProvider() { useCustomRuntimeClasspathProviders(
override fun runtimeClassPaths(): List<File> = librariesPaths {
object : RuntimeClasspathProvider(it) {
override fun runtimeClassPaths(module: TestModule): List<File> = librariesPaths
}
} }
} )
} }