diff --git a/libraries/scripting/js-test/build.gradle.kts b/libraries/scripting/js-test/build.gradle.kts new file mode 100644 index 00000000000..1f536ed2b9c --- /dev/null +++ b/libraries/scripting/js-test/build.gradle.kts @@ -0,0 +1,32 @@ + +plugins { + kotlin("jvm") +} + +jvmTarget = "1.6" + +val embeddableTestRuntime by configurations.creating + +dependencies { + testCompile(commonDep("junit")) + + testCompile(project(":kotlin-scripting-js")) + testCompile(project(":compiler:plugin-api")) + testCompile(project(":kotlin-scripting-compiler")) + testCompile(project(":compiler:cli")) + testCompile(project(":compiler:backend.js")) + testCompile(project(":js:js.engines")) + testCompile(intellijCoreDep()) { includeJars("intellij-core") } + testRuntimeOnly(intellijCoreDep()) { includeJars("intellij-core") } + testRuntimeOnly(intellijDep()) { includeJars("openapi", "idea", "idea_rt", "log4j", "picocontainer-1.2", "guava-25.1-jre", "jdom") } + testRuntimeOnly(commonDep("org.jetbrains.intellij.deps", "trove4j")) +} + +sourceSets { + "main" {} + "test" { projectDefault() } +} + +projectTest(parallel = true) { + workingDir = rootDir +} diff --git a/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt new file mode 100644 index 00000000000..bced0deb78e --- /dev/null +++ b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/AbstractJsReplTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2019 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.scripting.repl.js.test + +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine +import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult +import org.jetbrains.kotlin.cli.common.repl.ReplCompiler +import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar +import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys +import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition +import org.jetbrains.kotlin.scripting.definitions.platform +import org.jetbrains.kotlin.scripting.repl.js.JsReplEvaluator +import org.jetbrains.kotlin.scripting.repl.js.ReplMessageCollector +import java.io.Closeable +import kotlin.script.experimental.api.ScriptCompilationConfiguration +import kotlin.script.experimental.api.baseClass +import kotlin.script.experimental.api.dependencies +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.jvm.JsDependency + +abstract class AbstractJsReplTest : Closeable { + abstract fun createCompiler(): ReplCompiler + abstract fun preprocessEvaluation() + + fun compile(codeLine: ReplCodeLine): ReplCompileResult { + return compiler.compile(compiler.createState(), codeLine) + } + + fun evaluate(compileResult: ReplCompileResult.CompiledClasses): ReplEvalResult { + return jsEvaluator.eval(jsEvaluator.createState(), compileResult) + } + + fun reset() { + collector.clear() + compiler = createCompiler() + jsEvaluator = JsReplEvaluator() + preprocessEvaluation() + } + + private val collector: MessageCollector = ReplMessageCollector() + protected val disposable = Disposer.newDisposable() + protected val environment = KotlinCoreEnvironment.createForProduction( + disposable, loadConfiguration(), EnvironmentConfigFiles.JS_CONFIG_FILES + ) + + lateinit var compiler: ReplCompiler + lateinit var jsEvaluator: JsReplEvaluator + + private var snippetId: Int = 1 //index 0 for klib + fun newSnippetId(): Int = snippetId++ + + private fun loadConfiguration(): CompilerConfiguration { + val configuration = CompilerConfiguration() + configuration.add(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, ScriptingCompilerConfigurationComponentRegistrar()) + configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, collector) + configuration.put(CommonConfigurationKeys.MODULE_NAME, "repl.kts") + val scriptConfiguration = ScriptCompilationConfiguration { + baseClass("kotlin.Any") + dependencies.append(JsDependency("compiler/ir/serialization.js/build/fullRuntime/klib")) + platform.put("JS") + } + configuration.add( + ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, + ScriptDefinition.FromConfigurations(ScriptingHostConfiguration(), scriptConfiguration, null) + ) + return configuration + } +} diff --git a/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/JsReplTestAgainstBinaries.kt b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/JsReplTestAgainstBinaries.kt new file mode 100644 index 00000000000..ed562e45de5 --- /dev/null +++ b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/JsReplTestAgainstBinaries.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2019 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.scripting.repl.js.test + +import org.jetbrains.kotlin.cli.common.repl.ReplCompiler +import org.jetbrains.kotlin.ir.backend.js.utils.NameTables +import org.jetbrains.kotlin.scripting.repl.js.* + +class JsReplTestAgainstBinaries : AbstractJsReplTest() { + private val dependencyLoader = DependencyLoader() + + private val runtimeBinary: String + private val nameTable: NameTables + + init { + val dependencies = readLibrariesFromConfiguration(environment.configuration) + val compiler = ScriptDependencyCompiler(environment) + + val result = compiler.compile(dependencies) + runtimeBinary = result.first + nameTable = result.second + + dependencyLoader.saveScriptDependencyBinary(runtimeBinary) + dependencyLoader.saveNames(nameTable) + } + + override fun createCompiler(): ReplCompiler { + return JsDebuggerCompiler(environment, dependencyLoader.loadNames()) + } + + override fun preprocessEvaluation() { + jsEvaluator.eval( + jsEvaluator.createState(), + createCompileResult(dependencyLoader.loadScriptDependencyBinary()) + ) + } + + override fun close() { + //do nothing + } +} diff --git a/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/JsReplTestAgainstKlib.kt b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/JsReplTestAgainstKlib.kt new file mode 100644 index 00000000000..e2f51b0d68e --- /dev/null +++ b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/JsReplTestAgainstKlib.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2019 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.scripting.repl.js.test + +import com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.cli.common.repl.ReplCompiler +import org.jetbrains.kotlin.scripting.repl.js.* + +class JsReplTestAgainstKlib : AbstractJsReplTest() { + override fun createCompiler(): ReplCompiler = JsReplCompiler(environment) + + override fun preprocessEvaluation() { + val scriptDependencyBinary = (compiler as JsReplCompiler).scriptDependencyBinary + + jsEvaluator.eval( + jsEvaluator.createState(), + createCompileResult(scriptDependencyBinary) + ) + } + + override fun close() { + Disposer.dispose(disposable) + } +} diff --git a/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/ReplTest.kt b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/ReplTest.kt new file mode 100644 index 00000000000..d523de80c17 --- /dev/null +++ b/libraries/scripting/js-test/test/org/jetbrains/kotlin/scripting/repl/js/test/ReplTest.kt @@ -0,0 +1,243 @@ +/* + * Copyright 2010-2019 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.scripting.repl.js.test + +import junit.framework.TestCase +import org.jetbrains.kotlin.cli.common.repl.ReplCompileResult +import org.junit.Assert +import org.junit.Test +import org.jetbrains.kotlin.cli.common.repl.ReplEvalResult +import org.jetbrains.kotlin.scripting.repl.js.makeReplCodeLine + +abstract class AbstractReplTestRunner : TestCase() { + abstract fun getTester(): AbstractJsReplTest + + @Test + fun testIndependentLines() { + val lines = listOf( + "var x = 38", + "var y = 99", + "4 + 1" + ) + Assert.assertEquals(5, compileAndEval(lines)) + } + + @Test + fun testDependentLines() { + val lines = listOf( + "var x = 7", + "var y = 32", + "x + y" + ) + Assert.assertEquals(39, compileAndEval(lines)) + } + + @Test + fun testFunctionCall() { + val lines = listOf( + "var x = 2", + "var y = 3", + "fun foo(x: Int, unused: Int) = x + y", + "foo(x, x) * foo(y, y)" + ) + Assert.assertEquals(30, compileAndEval(lines)) + } + + @Test + fun testList() { + val lines = listOf( + "var a = 4", + "var b = 6", + "listOf(a, 5, b).last()" + ) + Assert.assertEquals(6, compileAndEval(lines)) + } + + @Test + fun testMatchingNames() { + val lines = listOf( + "fun foo(i: Int) = i + 2", + "fun foo(s: String) = s", + "class C {fun foo(s: String) = s + s}", + "foo(\"x\") + foo(2) + C().foo(\"class\")" + ) + Assert.assertEquals("x4classclass", compileAndEval(lines)) + } + + @Test + fun testInline() { + val lines = listOf( + "inline fun foo(i : Int) = if (i % 2 == 0) {} else i", + """ + fun box(): String { + val a = foo(1) + if (a != 1) return "fail1: ${'$'}a" + + val b = foo(2) + if (b != Unit) return "fail2: ${'$'}b" + + return "OK" + }, + box() + """ + ) + Assert.assertEquals("OK", compileAndEval(lines)) + } + + @Test + fun testAnonymous() { + val lines = listOf( + """ + inline fun foo(f: () -> String): () -> String { + val result = f() + return { result } + } + """, + "fun bar(f: () -> String) = foo(f)()", + "fun box(): String = bar { \"OK\" }", + "box()" + ) + Assert.assertEquals("OK", compileAndEval(lines)) + } + + @Test + fun testNoneLocalReturn() { + val lines = listOf( + """ + inline fun f(ignored: () -> Any): Any { + return ignored() + } + """, + """ + fun test(): String { + f { return "OK" }; + return "error" + } + """, + "test()" + ) + Assert.assertEquals("OK", compileAndEval(lines)) + } + + @Test + fun testInstanceOf() { + val lines = listOf( + """ + val list = listOf(1, 2, 3) + val f: Boolean = list is List + """, + "val s = list is List", + "f.toString() + s.toString()" + ) + Assert.assertEquals("truetrue", compileAndEval(lines)) + } + + @Test + fun testScopes() { + val lines = listOf( + """ + fun foo(): Int { + var t = 2 * 2 + class A(val value: Int = 5) { + fun bar(): Int { + var q = 4 + var w = 1 + return q + w + } + } + + return A().bar() * 2 + } + foo() + """ + ) + Assert.assertEquals(10, compileAndEval(lines)) + } + + @Test + fun testEvaluateFunctionName() { + val lines = listOf( + "fun evaluateScript() = 5", + "fun foo(i: Int) = i + evaluateScript()", + "foo(5)" + ) + Assert.assertEquals(10, compileAndEval(lines)) + } + + @Test + fun testMemberDeclarations() { + val lines = listOf( + """ + val a = listOf(1, 2, 3, 4, 5) + val str = "" + kotlin.math.PI + str + a.subList(2, 3).toString() + a.lastIndexOf(4) + """ + ) + Assert.assertEquals("3.141592653589793[3]3", compileAndEval(lines)) + } + + @Test + fun testInitializeScriptFunction() { + val lines = listOf( + """ + var result = "" + + class Class(val x: Int = 10) + result += Class().x + + fun function() = "#$@" + result += function() + + val field = 123456 + result += field + + val sq: (x: Int) -> Int = { x -> x * x } + result += "_" + sq(9) + + result += if (sq(5) % 2 == 0) { + class I(val x: Int = 100) + I().x + } else { + class I(val x: String = "goo") + I().x + } + + result + """ + ) + Assert.assertEquals("10#$@123456_81goo", compileAndEval(lines)) + } + + private fun compileAndEval(lines: List): Any? { + var result: Any? = null + getTester().use { tester -> + tester.reset() + + lines.forEach { line -> + val compileResult = tester.compile(makeReplCodeLine(tester.newSnippetId(), line)) + if (compileResult !is ReplCompileResult.CompiledClasses) return compileResult.toString() + + val evalResult = tester.evaluate(compileResult) + if (evalResult !is ReplEvalResult.ValueResult) return evalResult.toString() + + result = evalResult.value + } + } + return result + } +} + +class ReplTestRunnerAgainstKLib : AbstractReplTestRunner() { + override fun getTester(): AbstractJsReplTest = JsReplTestAgainstKlib() +} + +class ReplTestRunnerAgainstBinaries : AbstractReplTestRunner() { + override fun getTester(): AbstractJsReplTest = tester + + companion object { + private val tester = JsReplTestAgainstBinaries() + } +} diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt index 6ba3a7651a8..6cc6f29ee39 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt @@ -149,6 +149,9 @@ abstract class ScriptDefinition : UserDataHolderBase() { ?: hostConfiguration[ScriptingHostConfiguration.jvm.baseClassLoader] } + override val platform: String + get() = compilationConfiguration[ScriptCompilationConfiguration.platform] ?: super.platform + override val baseClassType: KotlinType get() = compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!