Remove experimental implementation of Kotlin Compiler Daemon
#KT-50846 Fixed
This commit is contained in:
@@ -15,7 +15,6 @@ dependencies {
|
||||
testCompileOnly(project(":kotlin-test:kotlin-test-jvm"))
|
||||
testCompileOnly(project(":kotlin-test:kotlin-test-junit"))
|
||||
testApi(project(":kotlin-daemon-client"))
|
||||
testApi(project(":kotlin-daemon-client-new"))
|
||||
testCompileOnly(project(":kotlin-daemon"))
|
||||
testApi(projectTests(":compiler:tests-common"))
|
||||
testApi(commonDependency("org.jetbrains.kotlinx", "kotlinx-coroutines-core")) { isTransitive = false }
|
||||
|
||||
-336
@@ -1,336 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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.daemon.experimental.integration
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.cli.common.CompilerSystemProperties
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.jetbrains.kotlin.config.Services
|
||||
import org.jetbrains.kotlin.daemon.captureOut
|
||||
import org.jetbrains.kotlin.daemon.client.DaemonReportingTargets
|
||||
import org.jetbrains.kotlin.daemon.client.KotlinCompilerDaemonClient
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
import org.jetbrains.kotlin.daemon.common.ReportSeverity
|
||||
import org.jetbrains.kotlin.daemon.loggerCompatiblePath
|
||||
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
||||
import org.jetbrains.kotlin.test.IgnoreAll
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.testFramework.resetApplicationToNull
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil
|
||||
import org.junit.Assert
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.util.logging.LogManager
|
||||
import java.util.logging.Logger
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.createTempFile
|
||||
|
||||
private val logFiles = arrayListOf<String>()
|
||||
|
||||
// TODO: remove ignore annotation from tests.
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
@RunWith(IgnoreAll::class)
|
||||
class CompilerApiTest : KotlinIntegrationTestBase() {
|
||||
|
||||
val kotlinCompilerClient = KotlinCompilerDaemonClient
|
||||
.instantiate(DaemonProtocolVariant.SOCKETS) // TODO(SOCKETS)
|
||||
|
||||
private val compilerLibDir = getCompilerLib()
|
||||
|
||||
private fun createNewLogFile(): Path {
|
||||
println("creating logFile")
|
||||
val newLogFile = createTempFile("kotlin-daemon-experimental-test.", ".log")
|
||||
println("logFile created (${newLogFile.loggerCompatiblePath})")
|
||||
logFiles.add(newLogFile.loggerCompatiblePath)
|
||||
return newLogFile
|
||||
}
|
||||
|
||||
private val currentLogFile: Path by lazy {
|
||||
val newLogFile = createNewLogFile()
|
||||
val cfg: String =
|
||||
"handlers = java.util.logging.FileHandler\n" +
|
||||
"java.util.logging.FileHandler.level = ALL\n" +
|
||||
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
|
||||
"java.util.logging.FileHandler.encoding = UTF-8\n" +
|
||||
"java.util.logging.FileHandler.limit = 0\n" + // if file is provided - disabled, else - 1Mb
|
||||
"java.util.logging.FileHandler.count = 1\n" +
|
||||
"java.util.logging.FileHandler.append = true\n" +
|
||||
"java.util.logging.FileHandler.pattern = ${newLogFile.loggerCompatiblePath}\n" +
|
||||
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s%n\n"
|
||||
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
|
||||
newLogFile
|
||||
}
|
||||
|
||||
private val externalLogFile: Path by lazy { createNewLogFile() }
|
||||
|
||||
private val log by lazy {
|
||||
currentLogFile
|
||||
Logger.getLogger("test")
|
||||
}
|
||||
|
||||
val compilerClassPath = listOf(
|
||||
File(compilerLibDir, "kotlin-compiler.jar"),
|
||||
File(compilerLibDir, "kotlin-daemon.jar")
|
||||
)
|
||||
val scriptRuntimeClassPath = listOf(
|
||||
File(compilerLibDir, "kotlin-runtime.jar"),
|
||||
File(compilerLibDir, "kotlin-script-runtime.jar")
|
||||
)
|
||||
val compilerId by lazy(LazyThreadSafetyMode.NONE) { CompilerId.makeCompilerId(compilerClassPath) }
|
||||
|
||||
private fun compileLocally(
|
||||
messageCollector: TestMessageCollector,
|
||||
vararg args: String
|
||||
): Pair<Int, Collection<OutputMessageUtil.Output>> {
|
||||
val application = ApplicationManager.getApplication()
|
||||
try {
|
||||
val code = K2JVMCompiler().exec(messageCollector,
|
||||
Services.EMPTY,
|
||||
K2JVMCompilerArguments().apply { K2JVMCompiler().parseArguments(args, this) }).code
|
||||
val outputs = messageCollector.messages.filter { it.severity == CompilerMessageSeverity.OUTPUT }.mapNotNull {
|
||||
OutputMessageUtil.parseOutputMessage(it.message)?.let { outs ->
|
||||
outs.outputFile?.let { OutputMessageUtil.Output(outs.sourceFiles, it) }
|
||||
}
|
||||
}
|
||||
return code to outputs
|
||||
} finally {
|
||||
resetApplicationToNull(application)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileOnDaemon(
|
||||
clientAliveFile: File,
|
||||
compilerId: CompilerId,
|
||||
daemonJVMOptions: DaemonJVMOptions,
|
||||
daemonOptions: DaemonOptions,
|
||||
messageCollector: MessageCollector,
|
||||
vararg args: String
|
||||
): Pair<Int, Collection<OutputMessageUtil.Output>> = runBlocking {
|
||||
|
||||
log.info("kotlinCompilerClient.connectToCompileService() call")
|
||||
val daemon = kotlinCompilerClient.connectToCompileService(
|
||||
compilerId,
|
||||
clientAliveFile,
|
||||
daemonJVMOptions,
|
||||
daemonOptions,
|
||||
DaemonReportingTargets(messageCollector = messageCollector),
|
||||
autostart = true
|
||||
)
|
||||
log.info("kotlinCompilerClient.connectToCompileService() called! (daemon = $daemon)")
|
||||
|
||||
assertNotNull("failed to connect daemon", daemon)
|
||||
|
||||
log.info("runBlocking { ")
|
||||
log.info("register client...")
|
||||
daemon?.registerClient(clientAliveFile.absolutePath)
|
||||
log.info(" client registered")
|
||||
log.info("} ^ runBlocking")
|
||||
|
||||
|
||||
val outputs = arrayListOf<OutputMessageUtil.Output>()
|
||||
|
||||
val code = kotlinCompilerClient.compile(
|
||||
daemon!!,
|
||||
CompileService.NO_SESSION,
|
||||
CompileService.TargetPlatform.JVM,
|
||||
args,
|
||||
messageCollector,
|
||||
{ outFile, srcFiles -> outputs.add(OutputMessageUtil.Output(srcFiles, outFile)) },
|
||||
reportSeverity = ReportSeverity.DEBUG
|
||||
)
|
||||
code to outputs
|
||||
}
|
||||
|
||||
private fun getHelloAppBaseDir(): String = KtTestUtil.getTestDataPathBase() + "/integration/smoke/helloApp"
|
||||
private fun getSimpleScriptBaseDir(): String = KtTestUtil.getTestDataPathBase() + "/integration/smoke/simpleScript"
|
||||
|
||||
private fun run(baseDir: String, logName: String, vararg args: String): Int = runJava(baseDir, logName, *args)
|
||||
|
||||
private fun runScriptWithArgs(
|
||||
testDataDir: String,
|
||||
logName: String?,
|
||||
scriptClassName: String,
|
||||
classpath: List<File>,
|
||||
vararg arguments: String
|
||||
) {
|
||||
|
||||
val cl = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray())
|
||||
val scriptClass = cl.loadClass(scriptClassName)
|
||||
|
||||
val scriptOut = captureOut { scriptClass.constructors.first().newInstance(arguments) }
|
||||
|
||||
if (logName != null) {
|
||||
val expectedFile = File(testDataDir, logName + ".expected")
|
||||
val normalizedContent = normalizeOutput(File(testDataDir), "OUT:\n$scriptOut\nReturn code: 0")
|
||||
|
||||
KotlinTestUtils.assertEqualsToFile(expectedFile, normalizedContent)
|
||||
}
|
||||
}
|
||||
|
||||
fun ignore_testHelloAppLocal() {
|
||||
val messageCollector = TestMessageCollector()
|
||||
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
|
||||
val (code, outputs) = compileLocally(
|
||||
messageCollector, "-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath,
|
||||
"-d", jar, "-Xreport-output-files"
|
||||
)
|
||||
Assert.assertEquals(0, code)
|
||||
Assert.assertTrue(outputs.isNotEmpty())
|
||||
Assert.assertEquals(jar, outputs.first().outputFile?.absolutePath)
|
||||
run(getHelloAppBaseDir(), "hello.run", "-cp", jar, "Hello.HelloKt")
|
||||
}
|
||||
|
||||
private fun terminate(@Suppress("UNUSED_PARAMETER") daemonOptions: DaemonOptions) {
|
||||
println("\n\nkillall -9 Console && open ${logFiles.joinToString(" ")}\n\n")
|
||||
log.info("in finally")
|
||||
// runBlocking {
|
||||
// log.info("in runBlocking")
|
||||
// delay(1000L)
|
||||
// kotlinCompilerClient.shutdownCompileService(compilerId, daemonOptions)
|
||||
// }
|
||||
// currentLogFile.delete()
|
||||
// externalLogFile.delete()
|
||||
}
|
||||
|
||||
fun ignore_testHelloApp() {
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
log.info("sarting test...")
|
||||
|
||||
log.info("assigning daemonOptions")
|
||||
val daemonOptions = DaemonOptions(
|
||||
runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
|
||||
verbose = true,
|
||||
reportPerf = true
|
||||
)
|
||||
log.info("daemonOptions assigned")
|
||||
|
||||
log.info("creating daemonJVMOptions")
|
||||
val daemonJVMOptions = configureDaemonJVMOptions(
|
||||
"D${CompilerSystemProperties.COMPILE_DAEMON_LOG_PATH_PROPERTY.property}=\"${externalLogFile.loggerCompatiblePath}\"",
|
||||
inheritMemoryLimits = false,
|
||||
inheritOtherJvmOptions = false,
|
||||
inheritAdditionalProperties = false
|
||||
)
|
||||
log.info("daemonJVMOptions created")
|
||||
|
||||
log.info("creating jar")
|
||||
val jar = tmpdir.absolutePath + File.separator + "hello.jar"
|
||||
log.info("jar created")
|
||||
|
||||
try {
|
||||
log.info("compileOnDaemon call")
|
||||
val (code, outputs) = compileOnDaemon(
|
||||
flagFile,
|
||||
compilerId,
|
||||
daemonJVMOptions,
|
||||
daemonOptions,
|
||||
TestMessageCollector(),
|
||||
"-include-runtime",
|
||||
File(getHelloAppBaseDir(), "hello.kt").absolutePath,
|
||||
"-d",
|
||||
jar,
|
||||
"-Xreport-output-files"
|
||||
)
|
||||
log.info("compileOnDaemon called")
|
||||
|
||||
Assert.assertEquals(0, code)
|
||||
Assert.assertTrue(outputs.isNotEmpty())
|
||||
Assert.assertEquals(jar, outputs.first().outputFile?.absolutePath)
|
||||
run(getHelloAppBaseDir(), "hello.run", "-cp", jar, "Hello.HelloKt")
|
||||
} finally {
|
||||
terminate(daemonOptions)
|
||||
}
|
||||
println("test passed")
|
||||
}
|
||||
}
|
||||
|
||||
fun ignore_testSimpleScriptLocal() {
|
||||
val messageCollector = TestMessageCollector()
|
||||
val (code, outputs) = compileLocally(
|
||||
messageCollector,
|
||||
File(getSimpleScriptBaseDir(), "script.kts").absolutePath,
|
||||
"-d",
|
||||
tmpdir.absolutePath,
|
||||
"-Xreport-output-files"
|
||||
)
|
||||
Assert.assertEquals(0, code)
|
||||
Assert.assertTrue(outputs.isNotEmpty())
|
||||
Assert.assertEquals(File(tmpdir, "Script.class").absolutePath, outputs.first().outputFile?.absolutePath)
|
||||
runScriptWithArgs(getSimpleScriptBaseDir(), "script", "Script", scriptRuntimeClassPath + tmpdir, "hi", "there")
|
||||
}
|
||||
|
||||
fun ignore_testSimpleScript() {
|
||||
withFlagFile(getTestName(true), ".alive") { flagFile ->
|
||||
val daemonOptions = DaemonOptions(
|
||||
runFilesPath = File(tmpdir, getTestName(true)).absolutePath,
|
||||
verbose = true,
|
||||
reportPerf = true
|
||||
)
|
||||
val daemonJVMOptions = configureDaemonJVMOptions(
|
||||
"D${CompilerSystemProperties.COMPILE_DAEMON_LOG_PATH_PROPERTY.property}=\"${externalLogFile.loggerCompatiblePath}\"",
|
||||
inheritMemoryLimits = false, inheritOtherJvmOptions = false, inheritAdditionalProperties = false
|
||||
)
|
||||
try {
|
||||
val (code, outputs) = compileOnDaemon(
|
||||
flagFile,
|
||||
compilerId,
|
||||
daemonJVMOptions,
|
||||
daemonOptions,
|
||||
TestMessageCollector(),
|
||||
File(getSimpleScriptBaseDir(), "script.kts").absolutePath,
|
||||
"-Xreport-output-files",
|
||||
"-d",
|
||||
tmpdir.absolutePath
|
||||
)
|
||||
Assert.assertEquals(0, code)
|
||||
Assert.assertTrue(outputs.isNotEmpty())
|
||||
Assert.assertEquals(File(tmpdir, "Script.class").absolutePath, outputs.first().outputFile?.absolutePath)
|
||||
runScriptWithArgs(getSimpleScriptBaseDir(), "script", "Script", scriptRuntimeClassPath + tmpdir, "hi", "there")
|
||||
} finally {
|
||||
terminate(daemonOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TestMessageCollector : MessageCollector {
|
||||
data class Message(val severity: CompilerMessageSeverity, val message: String, val location: CompilerMessageSourceLocation?)
|
||||
|
||||
val messages = arrayListOf<Message>()
|
||||
|
||||
override fun clear() {
|
||||
messages.clear()
|
||||
}
|
||||
|
||||
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageSourceLocation?) {
|
||||
messages.add(Message(severity, message, location))
|
||||
}
|
||||
|
||||
override fun hasErrors(): Boolean =
|
||||
messages.any { it.severity == CompilerMessageSeverity.EXCEPTION || it.severity == CompilerMessageSeverity.ERROR }
|
||||
|
||||
override fun toString(): String {
|
||||
return messages.joinToString("\n") { "${it.severity}: ${it.message}${it.location?.let { " at $it" } ?: ""}" }
|
||||
}
|
||||
}
|
||||
|
||||
fun TestMessageCollector.assertHasMessage(msg: String, desiredSeverity: CompilerMessageSeverity? = null) {
|
||||
assert(messages.any { it.message.contains(msg) && (desiredSeverity == null || it.severity == desiredSeverity) }) {
|
||||
"Expecting message \"$msg\" with severity ${desiredSeverity?.toString() ?: "Any"}, actual:\n" +
|
||||
messages.joinToString("\n") { it.severity.toString() + ": " + it.message }
|
||||
}
|
||||
}
|
||||
|
||||
-1487
File diff suppressed because it is too large
Load Diff
-100
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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.daemon.experimental.unit
|
||||
|
||||
import io.ktor.network.sockets.aSocket
|
||||
import io.ktor.util.*
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.CompilerServicesFacadeBaseClientSideImpl
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.LoopbackNetworkInterfaceKtor.selectorMgr
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.DefaultClient
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.ServerBase
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.openIO
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.socketInfrastructure.runWithTimeout
|
||||
import org.jetbrains.kotlin.daemon.common.toRMI
|
||||
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
||||
import org.jetbrains.kotlin.test.IgnoreAll
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.util.logging.Logger
|
||||
import kotlin.io.path.*
|
||||
|
||||
@OptIn(KtorExperimentalAPI::class)
|
||||
class TestServer(val serverPort: Int = 6999) {
|
||||
private val serverSocket = aSocket(selectorMgr).tcp().bind(InetSocketAddress(serverPort))
|
||||
private val log = Logger.getLogger("TestServer")
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun awaitClient() = GlobalScope.async {
|
||||
log.info("accepting clientSocket...")
|
||||
val client = serverSocket.accept()
|
||||
log.info("client accepted! (${client.remoteAddress})")
|
||||
client.openIO(log)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
val testServer = TestServer()
|
||||
|
||||
@RunWith(IgnoreAll::class)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
class ClientSerializationTest : KotlinIntegrationTestBase() {
|
||||
|
||||
val file = createTempFile()
|
||||
|
||||
val log = Logger.getLogger("ClientSerializationTest")
|
||||
|
||||
private inline fun <reified T> abstractSerializationTest(initClient: () -> T, vararg additionalTests: (T, T) -> Unit) {
|
||||
val client = initClient()
|
||||
log.info("created")
|
||||
file.outputStream().use {
|
||||
ObjectOutputStream(it).use {
|
||||
it.writeObject(client)
|
||||
}
|
||||
}
|
||||
log.info("printed")
|
||||
var client2: T?
|
||||
var connected = false
|
||||
runBlocking {
|
||||
val clientAwait = testServer.awaitClient()
|
||||
client2 = file.inputStream().use {
|
||||
ObjectInputStream(it).use {
|
||||
it.readObject() as T
|
||||
}
|
||||
}
|
||||
connected = runWithTimeout { clientAwait.await() } ?: false
|
||||
}
|
||||
assert(connected)
|
||||
log.info("read")
|
||||
assert(client2 != null)
|
||||
additionalTests.forEach { it(client, client2!!) }
|
||||
log.info("test passed")
|
||||
}
|
||||
|
||||
fun ignore_testDefaultClient() = abstractSerializationTest(
|
||||
{ DefaultClient<ServerBase>(testServer.serverPort) },
|
||||
{ client, client2 -> assert(client.serverPort == client2.serverPort) },
|
||||
{ _, client2 ->
|
||||
client2.log.info("abacaba (2)")
|
||||
log.info("test passed")
|
||||
}
|
||||
)
|
||||
|
||||
fun ignore_testCompilerServicesFacadeBaseClientSide() = abstractSerializationTest(
|
||||
{ CompilerServicesFacadeBaseClientSideImpl(testServer.serverPort) },
|
||||
{ client, client2 -> assert(client.serverPort == client2.serverPort) }
|
||||
)
|
||||
|
||||
fun ignore_testRMIWrapper() = abstractSerializationTest({ CompilerServicesFacadeBaseClientSideImpl(testServer.serverPort).toRMI() })
|
||||
|
||||
}
|
||||
-396
@@ -1,396 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. 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.daemon.experimental.unit
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.kotlin.cli.common.CLICompiler
|
||||
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
|
||||
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
|
||||
import org.jetbrains.kotlin.cli.js.K2JSCompiler
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.jetbrains.kotlin.cli.metadata.K2MetadataCompiler
|
||||
import org.jetbrains.kotlin.daemon.CompileServiceImpl
|
||||
import org.jetbrains.kotlin.daemon.CompilerSelector
|
||||
import org.jetbrains.kotlin.daemon.client.KotlinCompilerDaemonClient
|
||||
import org.jetbrains.kotlin.daemon.client.experimental.BasicCompilerServicesWithResultsFacadeServerServerSide
|
||||
import org.jetbrains.kotlin.daemon.common.*
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.DaemonWithMetadataAsync
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.findCallbackServerSocket
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.findPortForSocket
|
||||
import org.jetbrains.kotlin.daemon.common.experimental.walkDaemonsAsync
|
||||
import org.jetbrains.kotlin.daemon.experimental.CompileServiceServerSideImpl
|
||||
import org.jetbrains.kotlin.daemon.loggerCompatiblePath
|
||||
import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase
|
||||
import org.jetbrains.kotlin.test.IgnoreAll
|
||||
import org.jetbrains.kotlin.test.util.KtTestUtil
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.PrintStream
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.logging.LogManager
|
||||
import java.util.logging.Logger
|
||||
import kotlin.concurrent.schedule
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.createTempFile
|
||||
import kotlin.io.path.deleteIfExists
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
@RunWith(IgnoreAll::class)
|
||||
class ConnectionsTest : KotlinIntegrationTestBase() {
|
||||
|
||||
val kotlinCompilerClient = KotlinCompilerDaemonClient.instantiate(DaemonProtocolVariant.SOCKETS)
|
||||
|
||||
private val logFile = createTempFile("/Users/jetbrains/Documents/kotlin/my_fork/kotlin", ".txt")
|
||||
|
||||
private val cfg = "handlers = java.util.logging.FileHandler\n" +
|
||||
"java.util.logging.FileHandler.level = ALL\n" +
|
||||
"java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter\n" +
|
||||
"java.util.logging.FileHandler.encoding = UTF-8\n" +
|
||||
"java.util.logging.FileHandler.limit = 0\n" + // if file is provided - disabled, else - 1Mb
|
||||
"java.util.logging.FileHandler.count = 1\n" +
|
||||
"java.util.logging.FileHandler.append = true\n" +
|
||||
"java.util.logging.FileHandler.pattern = ${logFile.loggerCompatiblePath}\n" +
|
||||
"java.util.logging.SimpleFormatter.format = %1\$tF %1\$tT.%1\$tL [%3\$s] %4\$s: %5\$s%n\n"
|
||||
|
||||
init {
|
||||
LogManager.getLogManager().readConfiguration(cfg.byteInputStream())
|
||||
}
|
||||
|
||||
val log by lazy { Logger.getLogger("ConnectionsTest") }
|
||||
|
||||
private val daemonJVMOptions by lazy {
|
||||
configureDaemonJVMOptions(
|
||||
inheritMemoryLimits = true,
|
||||
inheritOtherJvmOptions = true,
|
||||
inheritAdditionalProperties = true
|
||||
)
|
||||
}
|
||||
|
||||
private val compilerId by lazy { CompilerId() }
|
||||
|
||||
private val daemonOptions by lazy { DaemonOptions() }
|
||||
|
||||
private val port
|
||||
get() = findPortForSocket(
|
||||
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
|
||||
COMPILE_DAEMON_PORTS_RANGE_START,
|
||||
COMPILE_DAEMON_PORTS_RANGE_END
|
||||
)
|
||||
|
||||
private val timer by lazy { Timer(true) }
|
||||
|
||||
private val runFile by lazy {
|
||||
val runFileDir = File(daemonOptions.runFilesPathOrDefault)
|
||||
runFileDir.mkdirs()
|
||||
File(
|
||||
runFileDir,
|
||||
makeRunFilenameString(
|
||||
timestamp = "%tFT%<tH-%<tM-%<tS.%<tLZ".format(Calendar.getInstance(TimeZone.getTimeZone("Z"))),
|
||||
digest = compilerId.compilerClasspath.map { File(it).absolutePath }.distinctStringsDigest().toHexString(),
|
||||
port = port.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private val onShutdown: () -> Unit = {
|
||||
if (daemonOptions.forceShutdownTimeoutMilliseconds != COMPILE_DAEMON_TIMEOUT_INFINITE_MS) {
|
||||
// running a watcher thread that ensures that if the daemon is not exited normally (may be due to RMI leftovers), it's forced to exit
|
||||
timer.schedule(daemonOptions.forceShutdownTimeoutMilliseconds) {
|
||||
cancel()
|
||||
org.jetbrains.kotlin.daemon.KotlinCompileDaemon.log.info("force JVM shutdown")
|
||||
System.exit(0)
|
||||
}
|
||||
} else {
|
||||
timer.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getNewDaemonsOrAsyncWrappers() = runBlocking {
|
||||
walkDaemonsAsync(
|
||||
File(daemonOptions.runFilesPathOrDefault),
|
||||
compilerId,
|
||||
runFile,
|
||||
filter = { _, _ -> true },
|
||||
report = { _, msg -> log.info(msg) },
|
||||
useRMI = true,
|
||||
useSockets = true
|
||||
).toList()
|
||||
}
|
||||
|
||||
private fun getOldDaemonsOrRMIWrappers() = runBlocking {
|
||||
walkDaemons(
|
||||
File(daemonOptions.runFilesPathOrDefault),
|
||||
compilerId,
|
||||
runFile,
|
||||
filter = { _, _ -> true },
|
||||
report = { _, msg -> log.info(msg) }
|
||||
).toList()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
}
|
||||
|
||||
private fun runNewServer(): Deferred<Unit> =
|
||||
port.let { serverPort ->
|
||||
CompileServiceServerSideImpl(
|
||||
serverPort,
|
||||
compilerId,
|
||||
daemonOptions,
|
||||
daemonJVMOptions,
|
||||
serverPort.port,
|
||||
timer,
|
||||
onShutdown
|
||||
).let {
|
||||
log.info("service created")
|
||||
it.startDaemonElections()
|
||||
it.configurePeriodicActivities()
|
||||
it.runServer()
|
||||
}
|
||||
}
|
||||
|
||||
private fun runOldServer() {
|
||||
val (registry, serverPort) = findPortAndCreateRegistry(
|
||||
COMPILE_DAEMON_FIND_PORT_ATTEMPTS,
|
||||
COMPILE_DAEMON_PORTS_RANGE_START,
|
||||
COMPILE_DAEMON_PORTS_RANGE_END
|
||||
)
|
||||
val compilerSelector = object : CompilerSelector {
|
||||
private val jvm by lazy { K2JVMCompiler() }
|
||||
private val js by lazy { K2JSCompiler() }
|
||||
private val metadata by lazy { K2MetadataCompiler() }
|
||||
override fun get(targetPlatform: CompileService.TargetPlatform): CLICompiler<*> = when (targetPlatform) {
|
||||
CompileService.TargetPlatform.JVM -> jvm
|
||||
CompileService.TargetPlatform.JS -> js
|
||||
CompileService.TargetPlatform.METADATA -> metadata
|
||||
}
|
||||
}
|
||||
log.info("old server run: (port= $serverPort, reg= $registry)")
|
||||
CompileServiceImpl(
|
||||
registry = registry,
|
||||
compiler = compilerSelector,
|
||||
compilerId = compilerId,
|
||||
daemonOptions = daemonOptions,
|
||||
daemonJVMOptions = daemonJVMOptions,
|
||||
port = serverPort,
|
||||
timer = timer,
|
||||
onShutdown = onShutdown
|
||||
).let {
|
||||
it.startDaemonElections()
|
||||
it.configurePeriodicActivities()
|
||||
}
|
||||
}
|
||||
|
||||
val comparator = compareByDescending<DaemonWithMetadataAsync, DaemonJVMOptions>(
|
||||
DaemonJVMOptionsMemoryComparator(),
|
||||
{ it.jvmOptions }
|
||||
)
|
||||
.thenBy {
|
||||
when (it.daemon) {
|
||||
is CompileServiceAsyncWrapper -> 0
|
||||
else -> 1
|
||||
}
|
||||
}
|
||||
.thenBy(FileAgeComparator()) { it.runFile }
|
||||
.thenBy { it.daemon.serverPort }
|
||||
|
||||
private fun <DaemonWithMeta, Daemon> expectDaemon(
|
||||
getDaemons: () -> List<DaemonWithMeta>,
|
||||
chooseDaemon: (List<DaemonWithMeta>) -> Daemon,
|
||||
getInfo: (Daemon) -> CompileService.CallResult<String>,
|
||||
registerClient: (Daemon) -> Unit,
|
||||
port: (Daemon) -> Int,
|
||||
expectedDaemonCount: Int?,
|
||||
extraAction: (Daemon) -> Unit = {}
|
||||
) {
|
||||
val daemons = getDaemons()
|
||||
log.info("daemons (${daemons.size}) : ${daemons.map { (it ?: 0)::class.java.name }.toList()}\n\n")
|
||||
expectedDaemonCount?.let {
|
||||
log.info("expected $it daemons, found ${daemons.size}")
|
||||
assertTrue(
|
||||
"daemons.size : ${daemons.size}, but expected : $expectedDaemonCount",
|
||||
daemons.size == it
|
||||
)
|
||||
}
|
||||
val daemon = chooseDaemon(daemons)
|
||||
log.info("chosen : $daemon (port = ${port(daemon)})")
|
||||
val info = getInfo(daemon)
|
||||
log.info("info : $info")
|
||||
assertTrue("bad info", info.isGood)
|
||||
registerClient(daemon)
|
||||
extraAction(daemon)
|
||||
}
|
||||
|
||||
private enum class ServerType(val instancesNumber: Int?) {
|
||||
OLD(1), NEW(2), ANY(null)
|
||||
}
|
||||
|
||||
private fun expectNewDaemon(serverType: ServerType, extraAction: (CompileServiceAsync) -> Unit = {}) = expectDaemon(
|
||||
getDaemons = ::getNewDaemonsOrAsyncWrappers,
|
||||
chooseDaemon = { daemons -> daemons.maxWithOrNull(comparator)!!.daemon },
|
||||
getInfo = { d -> runBlocking { d.getDaemonInfo() } },
|
||||
registerClient = { d -> runBlocking { d.registerClient(generateClient()) } },
|
||||
port = { d -> d.serverPort },
|
||||
expectedDaemonCount = serverType.instancesNumber,
|
||||
extraAction = extraAction
|
||||
)
|
||||
|
||||
private fun expectOldDaemon(shouldCheckNumber: Boolean = true, extraAction: (CompileService) -> Unit = {}) = expectDaemon(
|
||||
::getOldDaemonsOrRMIWrappers,
|
||||
{ daemons -> daemons[0].daemon },
|
||||
{ d -> d.getDaemonInfo() },
|
||||
{ d -> d.registerClient(generateClient()) },
|
||||
{ _ -> -1 },
|
||||
1.takeIf { shouldCheckNumber },
|
||||
extraAction
|
||||
)
|
||||
|
||||
private val clientFiles = mutableListOf<Path>()
|
||||
private fun generateClient(): String {
|
||||
val file = createTempFile(getTestName(true), ".alive")
|
||||
clientFiles.add(file)
|
||||
return file.toAbsolutePath().toString()
|
||||
}
|
||||
|
||||
private fun deleteClients() {
|
||||
clientFiles.forEach { it.deleteIfExists() }
|
||||
}
|
||||
|
||||
private fun endTest() {
|
||||
deleteClients()
|
||||
}
|
||||
|
||||
fun ignore_testConnectionMechanism_OldClient_OldServer() {
|
||||
runOldServer()
|
||||
expectOldDaemon()
|
||||
endTest()
|
||||
}
|
||||
|
||||
|
||||
fun ignore_testConnectionMechanism_NewClient_NewServer() {
|
||||
runNewServer()
|
||||
expectNewDaemon(ServerType.NEW)
|
||||
endTest()
|
||||
}
|
||||
|
||||
fun ignore_testConnectionMechanism_OldClient_NewServer() {
|
||||
runNewServer()
|
||||
expectOldDaemon()
|
||||
endTest()
|
||||
}
|
||||
|
||||
fun ignore_testConnectionMechanism_NewClient_OldServer() {
|
||||
runOldServer()
|
||||
expectNewDaemon(ServerType.OLD)
|
||||
endTest()
|
||||
}
|
||||
|
||||
|
||||
fun ignore_testConnections_OldDaemon_DifferentClients() {
|
||||
runOldServer()
|
||||
(0..20).forEach {
|
||||
expectNewDaemon(ServerType.OLD)
|
||||
expectOldDaemon()
|
||||
}
|
||||
endTest()
|
||||
}
|
||||
|
||||
fun ignore_testConnections_NewDaemon_DifferentClients() {
|
||||
runNewServer()
|
||||
(0..4).forEach {
|
||||
expectNewDaemon(ServerType.NEW)
|
||||
expectOldDaemon()
|
||||
}
|
||||
endTest()
|
||||
}
|
||||
|
||||
fun ignore_testConnections_MultipleDaemons_MultipleClients() {
|
||||
(0..3).forEach {
|
||||
runNewServer()
|
||||
runOldServer()
|
||||
}
|
||||
(0..4).forEach {
|
||||
expectNewDaemon(ServerType.ANY)
|
||||
expectOldDaemon(shouldCheckNumber = false)
|
||||
}
|
||||
endTest()
|
||||
}
|
||||
|
||||
fun ignore_testShutdown() {
|
||||
runNewServer()
|
||||
expectNewDaemon(ServerType.NEW) { daemon ->
|
||||
runBlocking {
|
||||
daemon.shutdown()
|
||||
delay(1000L)
|
||||
val mem: Long = try {
|
||||
daemon.getUsedMemory().get()
|
||||
} catch (e: IOException) {
|
||||
-100500L
|
||||
}
|
||||
assertTrue(mem == -100500L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun ignore_testCompile() {
|
||||
runNewServer()
|
||||
expectNewDaemon(ServerType.NEW) { daemon ->
|
||||
runBlocking {
|
||||
assertTrue("daemon is wrapper", daemon !is CompileServiceAsyncWrapper)
|
||||
val outStream = ByteArrayOutputStream()
|
||||
val msgCollector = PrintingMessageCollector(PrintStream(outStream), MessageRenderer.WITHOUT_PATHS, true)
|
||||
val codes = (0 until 10).toMutableList()
|
||||
val services = BasicCompilerServicesWithResultsFacadeServerServerSide(
|
||||
msgCollector,
|
||||
{ _, _ -> },
|
||||
findCallbackServerSocket()
|
||||
)
|
||||
services.runServer()
|
||||
val servicesClient = services.clientSide
|
||||
val compResultsClient = kotlinCompilerClient.createCompResults().clientSide
|
||||
val threadCount = 10
|
||||
@OptIn(ObsoleteCoroutinesApi::class)
|
||||
fun runThread(i: Int) =
|
||||
async(newSingleThreadContext("thread_$i")) {
|
||||
val jar = tmpdir.absolutePath + File.separator + "hello.$i.jar"
|
||||
val code =
|
||||
daemon.compile(
|
||||
CompileService.NO_SESSION,
|
||||
arrayOf(
|
||||
"-include-runtime",
|
||||
File(KtTestUtil.getTestDataPathBase() + "/integration/smoke/helloApp", "hello.kt").absolutePath,
|
||||
"-d",
|
||||
jar
|
||||
),
|
||||
CompilationOptions(
|
||||
CompilerMode.NON_INCREMENTAL_COMPILER,
|
||||
CompileService.TargetPlatform.JVM,
|
||||
arrayOf(
|
||||
ReportCategory.COMPILER_MESSAGE.code,
|
||||
ReportCategory.DAEMON_MESSAGE.code,
|
||||
ReportCategory.EXCEPTION.code,
|
||||
ReportCategory.OUTPUT_MESSAGE.code
|
||||
),
|
||||
ReportSeverity.INFO.code,
|
||||
emptyArray()
|
||||
),
|
||||
servicesClient,
|
||||
compResultsClient
|
||||
).get()
|
||||
codes[i] = code
|
||||
}
|
||||
(0 until threadCount).map(::runThread).map { it.await() }
|
||||
assertTrue("not-null code", codes.all { it == 0 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user