diff --git a/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt b/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt new file mode 100644 index 00000000000..2013e6976cf --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/cli/jvm/repl/GenericReplTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.cli.jvm.repl + +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer +import junit.framework.TestCase +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer +import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector +import org.jetbrains.kotlin.cli.common.repl.* +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase +import org.jetbrains.kotlin.script.KotlinScriptDefinition +import org.jetbrains.kotlin.script.KotlinScriptDefinitionFromAnnotatedTemplate +import org.jetbrains.kotlin.utils.PathUtil +import org.junit.Test +import java.io.File +import java.net.URLClassLoader + +class GenericReplTest : TestCase() { + + @Test + fun testReplBasics() { + + val disposable = Disposer.newDisposable() + + val repl = TestRepl(disposable, + listOf(File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-runtime.jar")), + "kotlin.script.StandardScriptTemplate") + + val res1 = repl.replCompiler?.check(ReplCodeLine(0, "val x ="), emptyList()) + TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete) + + val codeLine0 = ReplCodeLine(0, "1 + 2") + val res2 = repl.replCompiler?.compile(codeLine0, emptyList()) + val res2c = res2 as? ReplCompileResult.CompiledClasses + TestCase.assertNotNull("Unexpected compile result: $res2", res2c) + + val res21 = repl.compiledEvaluator.eval(codeLine0, emptyList(), res2c!!.classes, res2c.hasResult, res2c.newClasspath) + val res21e = res21 as? ReplEvalResult.ValueResult + TestCase.assertNotNull("Unexpected eval result: $res21", res21e) + TestCase.assertEquals(3, res21e!!.value) + + val codeLine1 = ReplCodeLine(1, "val x = 5") + val res3 = repl.replCompiler?.compile(codeLine1, listOf(codeLine0)) + val res3c = res3 as? ReplCompileResult.CompiledClasses + TestCase.assertNotNull("Unexpected compile result: $res3", res3c) + + val res31 = repl.compiledEvaluator.eval(codeLine1, listOf(codeLine0), res3c!!.classes, res3c.hasResult, res3c.newClasspath) + val res31e = res31 as? ReplEvalResult.UnitResult + TestCase.assertNotNull("Unexpected eval result: $res31", res31e) + + val codeLine2 = ReplCodeLine(2, "x + 2") + val res4x = repl.replCompiler?.compile(codeLine2, listOf(codeLine1)) + TestCase.assertNotNull("Unexpected compile result: $res4x", res4x as? ReplCompileResult.HistoryMismatch) + + val res4 = repl.replCompiler?.compile(codeLine2, listOf(codeLine0, codeLine1)) + val res4c = res4 as? ReplCompileResult.CompiledClasses + TestCase.assertNotNull("Unexpected compile result: $res4", res4c) + + val res41 = repl.compiledEvaluator.eval(codeLine2, emptyList(), res4c!!.classes, res4c.hasResult, res4c.newClasspath) + val res41e = res41 as? ReplEvalResult.ValueResult + TestCase.assertNotNull("Unexpected eval result: $res41", res41e) + TestCase.assertEquals(7, res41e!!.value) + + Disposer.dispose(disposable) + } +} + +internal class TestRepl( + disposable: Disposable, + templateClasspath: List, + templateClassName: String +) { + private val configuration = CompilerConfiguration().apply { + addJvmClasspathRoots(PathUtil.getJdkClassesRoots()) + addJvmClasspathRoots(PathUtil.getKotlinPathsForCompiler().let { listOf(it.runtimePath, it.reflectPath) }) + addJvmClasspathRoots(templateClasspath) + put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") + } + private fun makeScriptDefinition(templateClasspath: List, templateClassName: String): KotlinScriptDefinition { + val classloader = URLClassLoader(templateClasspath.map { it.toURI().toURL() }.toTypedArray(), this.javaClass.classLoader) + val cls = classloader.loadClass(templateClassName) + return KotlinScriptDefinitionFromAnnotatedTemplate(cls.kotlin, null, null, emptyMap()) + } + + private val scriptDef = makeScriptDefinition(templateClasspath, templateClassName) + + val replCompiler : GenericReplCompiler? by lazy { + GenericReplCompiler(disposable, scriptDef, configuration, PrintingMessageCollector(System.out, MessageRenderer.WITHOUT_PATHS, false)) + } + + val compiledEvaluator : GenericReplCompiledEvaluator by lazy { + GenericReplCompiledEvaluator(configuration.jvmClasspathRoots, null, arrayOf(emptyArray())) + } +} + diff --git a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt index 00551e7bda1..f111a652315 100644 --- a/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt +++ b/compiler/tests/org/jetbrains/kotlin/daemon/CompilerDaemonTest.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.daemon +import com.intellij.openapi.Disposable import com.intellij.openapi.util.Disposer import junit.framework.TestCase import org.jetbrains.kotlin.cli.AbstractCliTest @@ -26,7 +27,7 @@ import org.jetbrains.kotlin.integration.KotlinIntegrationTestBase import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.script.* import org.jetbrains.kotlin.test.KotlinTestUtils -import org.jetbrains.kotlin.utils.PathUtil +import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.net.URL @@ -100,9 +101,9 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { logFile.reader().useLines { it.ifNotContainsSequence(LinePattern("Kotlin compiler daemon version"), LinePattern("Starting compilation with args: "), - LinePattern("Compile on daemon: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime1 = it }; true }), + LinePattern("Compile on daemon: (\\d+) ms", { it.groups[1]?.value?.toLong()?.let { compileTime1 = it }; true }), LinePattern("Starting compilation with args: "), - LinePattern("Compile on daemon: (\\d+) ms", { it.groups.get(1)?.value?.toLong()?.let { compileTime2 = it }; true }), + LinePattern("Compile on daemon: (\\d+) ms", { it.groups[1]?.value?.toLong()?.let { compileTime2 = it }; true }), LinePattern("Shutdown complete")) { unmatchedPattern, lineNo -> fail("pattern not found in the input: " + unmatchedPattern.regex + @@ -237,7 +238,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { val jar = tmpdir.absolutePath + File.separator + "hello1.jar" val strm = ByteArrayOutputStream() val code = KotlinCompilerClient.compile(daemon!!, CompileService.NO_SESSION, CompileService.TargetPlatform.JVM, arrayOf("-include-runtime", File(getHelloAppBaseDir(), "hello.kt").absolutePath, "-d", jar), strm) - assertEquals("compilation failed:\n${strm.toString()}", 0, code) + assertEquals("compilation failed:\n$strm", 0, code) logFile.assertLogContainsSequence("Starting compilation with args: ") @@ -358,7 +359,7 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { withFlagFile(getTestName(true), ".alive") { flagFile -> val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) - val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) + val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = true, inheritAdditionalProperties = false) val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) assertNotNull("failed to connect daemon", daemon) @@ -463,74 +464,120 @@ class CompilerDaemonTest : KotlinIntegrationTestBase() { RemoteOutputStreamServer(strm, SOCKET_ANY_FREE_PORT), null).get() + TestCase.assertEquals(0, code) + val compilerOutput = strm.toString() - assertTrue("Expecting cancelation message in:\n$compilerOutput", compilerOutput.contains("Compilation was canceled")) + assertTrue("Expecting cancellation message in:\n$compilerOutput", compilerOutput.contains("Compilation was canceled")) logFile.assertLogContainsSequence("error communicating with host, assuming compilation canceled") logFile.delete() } } - fun testDaemonReplLocalEval() { - withFlagFile(getTestName(true), ".alive") { flagFile -> - val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) - val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) - val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) - assertNotNull("failed to connect daemon", daemon) + fun testDaemonReplLocalEvalNoParams() { + withDaemon { daemon -> + withDisposable { disposable -> + val repl = KotlinRemoteReplCompiler(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, + classpathFromClassloader(), + ScriptWithNoParam::class.qualifiedName!!, + System.err) - val disposable = Disposer.newDisposable() + val localEvaluator = GenericReplCompiledEvaluator(emptyList(), Thread.currentThread().contextClassLoader) - val repl = KotlinRemoteReplCompiler(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, - classpathFromClassloader(), - ScriptWithNoParam::class.qualifiedName!!, System.err) - - val res1 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) - TestCase.assertTrue("Unexpected check results: $res1", res1 is ReplCheckResult.Incomplete) - - val codeLine = ReplCodeLine(0, "1 + 2") - val res2 = repl.compile(codeLine, emptyList()) - - val compileRes = res2 as? ReplCompileResult.CompiledClasses - TestCase.assertNotNull("Unexpected compile result: $res2", compileRes) - - val localEvaluator = GenericReplCompiledEvaluator(emptyList(), Thread.currentThread().contextClassLoader) - - val res3 = localEvaluator.eval(codeLine, emptyList(), compileRes!!.classes, compileRes.hasResult, compileRes.newClasspath) - val evalRes = res3 as? ReplEvalResult.ValueResult - TestCase.assertNotNull("Unexpected eval result: $res3", evalRes) - TestCase.assertEquals(3, evalRes!!.value) - - Disposer.dispose(disposable) + doReplTestWithLocalEval(repl, localEvaluator) + } } } -// fun testDaemonReplRemoteEval() { -// withFlagFile(getTestName(true), ".alive") { flagFile -> -// val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) -// val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) -// val daemon = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) -// assertNotNull("failed to connect daemon", daemon) -// -// val evalOut = ByteArrayOutputStream() -// val evalErr = ByteArrayOutputStream() -// val evalIn = ByteArrayInputStream("42\n".toByteArray()) -// -// val disposable = Disposer.newDisposable() -// -// val repl = KotlinRemoteReplEvaluator(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, emptyList(), -// "", System.err, evalOut, evalErr, evalIn) -// -// val res1 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) -// TestCase.assertEquals(ReplCheckResult.Incomplete, res1) -// -// val res2 = repl.eval(ReplCodeLine(0, "1 + 2"), emptyList()) -// TestCase.assertEquals(ReplEvalResult.ValueResult(3), res2) -// -// Disposer.dispose(disposable) -// } -// } -} + fun testDaemonReplLocalEvalStandardTemplate() { + withDaemon { daemon -> + withDisposable { disposable -> + val repl = KotlinRemoteReplCompiler(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, + classpathFromClassloader(), + "kotlin.script.StandardScriptTemplate", + System.err) + val localEvaluator = GenericReplCompiledEvaluator(emptyList(), + Thread.currentThread().contextClassLoader, + arrayOf(emptyArray())) + + doReplTestWithLocalEval(repl, localEvaluator) + } + } + } + + private fun doReplTestWithLocalEval(repl: KotlinRemoteReplCompiler, localEvaluator: GenericReplCompiledEvaluator) { + val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) + TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete) + + val codeLine1 = ReplCodeLine(1, "val x = 5") + val res1 = repl.compile(codeLine1, emptyList()) + val res1c = res1 as? ReplCompileResult.CompiledClasses + TestCase.assertNotNull("Unexpected compile result: $res1", res1c) + + val res11 = localEvaluator.eval(codeLine1, emptyList(), res1c!!.classes, res1c.hasResult, res1c.newClasspath) + val res11e = res11 as? ReplEvalResult.UnitResult + TestCase.assertNotNull("Unexpected eval result: $res11", res11e) + + val codeLine2 = ReplCodeLine(2, "x + 2") + val res2x = repl.compile(codeLine2, listOf(codeLine2)) + TestCase.assertNotNull("Unexpected compile result: $res2x", res2x as? ReplCompileResult.HistoryMismatch) + + val res2 = repl.compile(codeLine2, listOf(codeLine1)) + val res2c = res2 as? ReplCompileResult.CompiledClasses + TestCase.assertNotNull("Unexpected compile result: $res2", res2c) + + val res21 = localEvaluator.eval(codeLine2, listOf(codeLine1), res2c!!.classes, res2c.hasResult, res2c.newClasspath) + val res21e = res21 as? ReplEvalResult.ValueResult + TestCase.assertNotNull("Unexpected eval result: $res21", res21e) + TestCase.assertEquals(7, res21e!!.value) + } + + fun testDaemonReplRemoteEval() { + withDaemon { daemon -> + withDisposable { disposable -> + + val evalOut = ByteArrayOutputStream() + val evalErr = ByteArrayOutputStream() + val evalIn = ByteArrayInputStream("42\n".toByteArray()) + + val repl = KotlinRemoteReplEvaluator(disposable, daemon!!, null, CompileService.TargetPlatform.JVM, + listOf(File(KotlinIntegrationTestBase.getCompilerLib(), "kotlin-runtime.jar")), + "kotlin.script.StandardScriptTemplate", + arrayOf(emptyArray()), null, + System.err, evalOut, evalErr, evalIn) + + val res0 = repl.check(ReplCodeLine(0, "val x ="), emptyList()) + TestCase.assertTrue("Unexpected check results: $res0", res0 is ReplCheckResult.Incomplete) + + val codeLine1 = ReplCodeLine(1, "val x = 5") + val res1 = repl.eval(codeLine1, emptyList()) + val res1e = res1 as? ReplEvalResult.UnitResult + TestCase.assertNotNull("Unexpected eval result: $res1", res1e) + + val codeLine2 = ReplCodeLine(2, "x + 2") + val res2x = repl.eval(codeLine2, listOf(codeLine2)) + TestCase.assertNotNull("Unexpected compile result: $res2x", res2x as? ReplEvalResult.HistoryMismatch) + + val res2 = repl.eval(codeLine2, listOf(codeLine1)) + val res2e = res2 as? ReplEvalResult.ValueResult + TestCase.assertNotNull("Unexpected eval result: $res2", res2e) + TestCase.assertEquals(7, res2e!!.value) + } + } + } + + internal fun withDaemon(body: (CompileService) -> Unit) { + withFlagFile(getTestName(true), ".alive") { flagFile -> + val daemonOptions = DaemonOptions(runFilesPath = File(tmpdir, getTestName(true)).absolutePath) + val daemonJVMOptions = configureDaemonJVMOptions(inheritMemoryLimits = false, inheritAdditionalProperties = false) + val daemon: CompileService? = KotlinCompilerClient.connectToCompileService(compilerId, flagFile, daemonJVMOptions, daemonOptions, DaemonReportingTargets(out = System.err), autostart = true) + assertNotNull("failed to connect daemon", daemon) + + body(daemon!!) + } + } +} // stolen from CompilerFileLimitTest @@ -595,6 +642,16 @@ internal inline fun withFlagFile(prefix: String, suffix: String? = null, body: ( } } +internal inline fun withDisposable(body: (Disposable) -> Unit) { + val disposable = Disposer.newDisposable() + try { + body(disposable) + } + finally { + Disposer.dispose(disposable) + } +} + // java.util.Logger used in the daemon silently forgets to log into a file specified in the config on Windows, // if file path is given in windows form (using backslash as a separator); the reason is unknown // this function makes a path with forward slashed, that works on windows too @@ -605,8 +662,6 @@ private val File.loggerCompatiblePath: String open class TestKotlinScriptDummyDependenciesResolver : ScriptDependenciesResolver { - private val kotlinPaths by lazy { PathUtil.getKotlinPathsForCompiler() } - override fun resolve(script: ScriptContents, environment: Map?, report: (ScriptDependenciesResolver.ReportSeverity, String, ScriptContents.Position?) -> Unit, @@ -625,7 +680,7 @@ abstract class ScriptWithNoParam() internal fun classpathFromClassloader(): List = (TestKotlinScriptDummyDependenciesResolver::class.java.classLoader as? URLClassLoader)?.urLs - ?.mapNotNull { it.toFile() } + ?.mapNotNull(URL::toFile) ?.filter { it.path.contains("out") && it.path.contains("test") } ?: emptyList() @@ -637,3 +692,4 @@ internal fun URL.toFile() = if (protocol != "file") null else File(file) } +