Add new REPL API JVM implementation
This commit is contained in:
committed by
Ilya Chernikov
parent
4c2c44b106
commit
d2fec96f38
@@ -0,0 +1,50 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
jvmTarget = "1.8"
|
||||
|
||||
val allTestsRuntime by configurations.creating
|
||||
val testCompile by configurations
|
||||
testCompile.extendsFrom(allTestsRuntime)
|
||||
val embeddableTestRuntime by configurations.creating {
|
||||
extendsFrom(allTestsRuntime)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
allTestsRuntime(commonDep("junit"))
|
||||
testCompile(project(":kotlin-scripting-ide-services"))
|
||||
testCompile(project(":kotlin-scripting-compiler"))
|
||||
testCompile(project(":compiler:cli-common"))
|
||||
|
||||
testRuntimeOnly(project(":kotlin-compiler"))
|
||||
testRuntimeOnly(commonDep("org.jetbrains.intellij.deps", "trove4j"))
|
||||
testRuntimeOnly(project(":idea:ide-common")) { isTransitive = false }
|
||||
|
||||
embeddableTestRuntime(project(":kotlin-scripting-ide-services-embeddable"))
|
||||
embeddableTestRuntime(project(":kotlin-compiler-embeddable"))
|
||||
embeddableTestRuntime(testSourceSet.output)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" {}
|
||||
"test" { projectDefault() }
|
||||
}
|
||||
|
||||
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>> {
|
||||
kotlinOptions.freeCompilerArgs += "-Xallow-kotlin-package"
|
||||
}
|
||||
|
||||
projectTest(parallel = true) {
|
||||
dependsOn(":dist")
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
// This doesn;t work now due to conflicts between embeddable compiler contents and intellij sdk modules
|
||||
// To make it work, the dependencies to the intellij sdk should be eliminated
|
||||
projectTest(taskName = "embeddableTest", parallel = true) {
|
||||
workingDir = rootDir
|
||||
dependsOn(embeddableTestRuntime)
|
||||
classpath = embeddableTestRuntime
|
||||
}
|
||||
+426
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.ide_services
|
||||
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
|
||||
import org.jetbrains.kotlin.scripting.ide_services.test_util.JvmTestRepl
|
||||
import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
|
||||
import kotlin.script.experimental.util.LinkedSnippet
|
||||
import kotlin.script.experimental.util.get
|
||||
import kotlin.script.experimental.jvm.util.isError
|
||||
import kotlin.script.experimental.jvm.util.isIncomplete
|
||||
|
||||
// Adapted form GenericReplTest
|
||||
|
||||
// Artificial split into several testsuites, to speed up parallel testing
|
||||
class JvmIdeServicesTest : TestCase() {
|
||||
fun testReplBasics() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
val res1 = repl.compile(
|
||||
SourceCodeTestImpl(
|
||||
0,
|
||||
"val x ="
|
||||
)
|
||||
)
|
||||
assertTrue("Unexpected check results: $res1", res1.isIncomplete())
|
||||
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"val l1 = listOf(1 + 2)\nl1.first()",
|
||||
3
|
||||
)
|
||||
|
||||
assertEvalUnit(
|
||||
repl,
|
||||
"val x = 5"
|
||||
)
|
||||
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"x + 2",
|
||||
7
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplErrors() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
repl.compileAndEval(repl.nextCodeLine("val x = 10"))
|
||||
|
||||
val res = repl.compileAndEval(repl.nextCodeLine("java.util.fish"))
|
||||
assertTrue("Expected compile error", res.first.isError())
|
||||
|
||||
val result = repl.compileAndEval(repl.nextCodeLine("x"))
|
||||
assertEquals(res.second.toString(), 10, (result.second?.result as ResultValue.Value?)?.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplErrorsWithLocations() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
val (compileResult, evalResult) = repl.compileAndEval(
|
||||
repl.nextCodeLine(
|
||||
"""
|
||||
val foobar = 78
|
||||
val foobaz = "dsdsda"
|
||||
val ddd = ppp
|
||||
val ooo = foobar
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
if (compileResult.isError() && evalResult == null) {
|
||||
val errors = compileResult.getErrors()
|
||||
val loc = errors.location
|
||||
if (loc == null) {
|
||||
fail("Location shouldn't be null")
|
||||
} else {
|
||||
assertEquals(3, loc.line)
|
||||
assertEquals(11, loc.column)
|
||||
assertEquals(3, loc.lineEnd)
|
||||
assertEquals(14, loc.columnEnd)
|
||||
}
|
||||
} else {
|
||||
fail("Result should be an error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplErrorsAndWarningsWithLocations() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
val (compileResult, evalResult) = repl.compileAndEval(
|
||||
repl.nextCodeLine(
|
||||
"""
|
||||
fun f() {
|
||||
val x = 3
|
||||
val y = ooo
|
||||
return y
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
if (compileResult.isError() && evalResult == null) {
|
||||
val errors = compileResult.getErrors()
|
||||
val loc = errors.location
|
||||
if (loc == null) {
|
||||
fail("Location shouldn't be null")
|
||||
} else {
|
||||
assertEquals(3, loc.line)
|
||||
assertEquals(13, loc.column)
|
||||
assertEquals(3, loc.lineEnd)
|
||||
assertEquals(16, loc.columnEnd)
|
||||
}
|
||||
} else {
|
||||
fail("Result should be an error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplSyntaxErrorsChecked() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
val res = repl.compileAndEval(repl.nextCodeLine("data class Q(val x: Int, val: String)"))
|
||||
assertTrue("Expected compile error", res.first.isError())
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkContains(actual: Sequence<SourceCodeCompletionVariant>, expected: Set<String>) {
|
||||
val variants = actual.map { it.displayText }.toHashSet()
|
||||
for (displayText in expected) {
|
||||
if (!variants.contains(displayText)) {
|
||||
fail("Element $displayText should be in $variants")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkDoesntContain(actual: Sequence<SourceCodeCompletionVariant>, expected: Set<String>) {
|
||||
val variants = actual.map { it.displayText }.toHashSet()
|
||||
for (displayText in expected) {
|
||||
if (variants.contains(displayText)) {
|
||||
fail("Element $displayText should NOT be in $variants")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testCompletion() = JvmTestRepl().use { repl ->
|
||||
repl.compileAndEval(
|
||||
repl.nextCodeLine(
|
||||
"""
|
||||
class AClass(val prop_x: Int) {
|
||||
fun filter(xxx: (AClass).(AClass) -> Boolean): AClass {
|
||||
return if(this.xxx(this))
|
||||
this
|
||||
else
|
||||
this
|
||||
}
|
||||
}
|
||||
val AClass.prop_y: Int
|
||||
get() = prop_x * prop_x
|
||||
|
||||
val df = AClass(10)
|
||||
val pro = "some string"
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
val codeLine1 = repl.nextCodeLine(
|
||||
"""
|
||||
df.filter{pr}
|
||||
""".trimIndent()
|
||||
)
|
||||
val completionList1 = repl.complete(codeLine1, 12)
|
||||
checkContains(completionList1.valueOrThrow(), setOf("prop_x", "prop_y", "pro", "println(Double)"))
|
||||
}
|
||||
|
||||
fun testPackageCompletion() = JvmTestRepl().use { repl ->
|
||||
val codeLine1 = repl.nextCodeLine(
|
||||
"""
|
||||
import java.
|
||||
val xval = 3
|
||||
""".trimIndent()
|
||||
)
|
||||
val completionList1 = repl.complete(codeLine1, 12)
|
||||
checkContains(completionList1.valueOrThrow(), setOf("lang", "math"))
|
||||
checkDoesntContain(completionList1.valueOrThrow(), setOf("xval"))
|
||||
}
|
||||
|
||||
fun testFileCompletion() = JvmTestRepl().use { repl ->
|
||||
val codeLine1 = repl.nextCodeLine(
|
||||
"""
|
||||
val fname = "
|
||||
""".trimIndent()
|
||||
)
|
||||
val completionList1 = repl.complete(codeLine1, 13)
|
||||
val files = File(".").listFiles()?.map { it.name }
|
||||
assertFalse("There should be files in current dir", files.isNullOrEmpty())
|
||||
checkContains(completionList1.valueOrThrow(), files!!.toSet())
|
||||
}
|
||||
|
||||
fun testReplCodeFormat() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
val codeLine0 =
|
||||
SourceCodeTestImpl(0, "val l1 = 1\r\nl1\r\n")
|
||||
val res = repl.compile(codeLine0)
|
||||
|
||||
assertTrue("Unexpected compile result: $res", res is ResultWithDiagnostics.Success<*>)
|
||||
}
|
||||
}
|
||||
|
||||
fun testRepPackage() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"package mypackage\n\nval x = 1\nx+2",
|
||||
3
|
||||
)
|
||||
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"x+4",
|
||||
5
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplResultFieldWithFunction() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
assertEvalResultIs<Function0<Int>>(
|
||||
repl,
|
||||
"{ 1 + 2 }"
|
||||
)
|
||||
assertEvalResultIs<Function0<Int>>(
|
||||
repl,
|
||||
"res0"
|
||||
)
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"res0()",
|
||||
3
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun testReplResultField() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"5 * 4",
|
||||
20
|
||||
)
|
||||
assertEvalResult(
|
||||
repl,
|
||||
"res0 + 3",
|
||||
23
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Artificial split into several testsuites, to speed up parallel testing
|
||||
class LegacyReplTestLong1 : TestCase() {
|
||||
|
||||
fun test256Evals() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
repl.compileAndEval(
|
||||
SourceCodeTestImpl(
|
||||
0,
|
||||
"val x0 = 0"
|
||||
)
|
||||
)
|
||||
|
||||
val evals = 256
|
||||
for (i in 1..evals) {
|
||||
repl.compileAndEval(
|
||||
SourceCodeTestImpl(
|
||||
i,
|
||||
"val x$i = x${i - 1} + 1"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val (_, evaluated) = repl.compileAndEval(
|
||||
SourceCodeTestImpl(
|
||||
evals + 1,
|
||||
"x$evals"
|
||||
)
|
||||
)
|
||||
assertEquals(evaluated.toString(), evals, (evaluated?.result as ResultValue.Value?)?.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Artificial split into several testsuites, to speed up parallel testing
|
||||
class LegacyReplTestLong2 : TestCase() {
|
||||
|
||||
fun testReplSlowdownKt22740() {
|
||||
JvmTestRepl()
|
||||
.use { repl ->
|
||||
repl.compileAndEval(
|
||||
SourceCodeTestImpl(
|
||||
0,
|
||||
"class Test<T>(val x: T) { fun <R> map(f: (T) -> R): R = f(x) }".trimIndent()
|
||||
)
|
||||
)
|
||||
|
||||
// We expect that analysis time is not exponential
|
||||
for (i in 1..60) {
|
||||
repl.compileAndEval(
|
||||
SourceCodeTestImpl(
|
||||
i,
|
||||
"fun <T> Test<T>.map(f: (T) -> Double): List<Double> = listOf(f(this.x))"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JvmTestRepl.compileAndEval(codeLine: SourceCode): Pair<ResultWithDiagnostics<LinkedSnippet<out CompiledSnippet>>, EvaluatedSnippet?> {
|
||||
|
||||
val compRes = compile(codeLine)
|
||||
|
||||
val evalRes = compRes.valueOrNull()?.let {
|
||||
eval(it)
|
||||
}
|
||||
return compRes to evalRes?.valueOrNull().get()
|
||||
}
|
||||
|
||||
private fun assertEvalUnit(
|
||||
repl: JvmTestRepl,
|
||||
@Suppress("SameParameterValue")
|
||||
line: String
|
||||
) {
|
||||
val compiledSnippet =
|
||||
checkCompile(repl, line)
|
||||
|
||||
val evalResult = repl.eval(compiledSnippet!!)
|
||||
val valueResult = evalResult.valueOrNull().get()
|
||||
|
||||
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
|
||||
TestCase.assertTrue(valueResult!!.result is ResultValue.Unit)
|
||||
}
|
||||
|
||||
private fun <R> assertEvalResult(repl: JvmTestRepl, line: String, expectedResult: R) {
|
||||
val compiledSnippet =
|
||||
checkCompile(repl, line)
|
||||
|
||||
val evalResult = repl.eval(compiledSnippet!!)
|
||||
val valueResult = evalResult.valueOrNull().get()
|
||||
|
||||
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
|
||||
TestCase.assertTrue(valueResult!!.result is ResultValue.Value)
|
||||
TestCase.assertEquals(expectedResult, (valueResult.result as ResultValue.Value).value)
|
||||
}
|
||||
|
||||
private inline fun <reified R> assertEvalResultIs(repl: JvmTestRepl, line: String) {
|
||||
val compiledSnippet =
|
||||
checkCompile(repl, line)
|
||||
|
||||
val evalResult = repl.eval(compiledSnippet!!)
|
||||
val valueResult = evalResult.valueOrNull().get()
|
||||
|
||||
TestCase.assertNotNull("Unexpected eval result: $evalResult", valueResult)
|
||||
TestCase.assertTrue(valueResult!!.result is ResultValue.Value)
|
||||
TestCase.assertTrue((valueResult.result as ResultValue.Value).value is R)
|
||||
}
|
||||
|
||||
private fun checkCompile(repl: JvmTestRepl, line: String): LinkedSnippet<KJvmCompiledScript>? {
|
||||
val codeLine = repl.nextCodeLine(line)
|
||||
val compileResult = repl.compile(codeLine)
|
||||
return compileResult.valueOrNull()
|
||||
}
|
||||
|
||||
private data class CompilationErrors(
|
||||
val message: String,
|
||||
val location: CompilerMessageLocation?
|
||||
)
|
||||
|
||||
private fun <T> ResultWithDiagnostics<T>.getErrors(): CompilationErrors =
|
||||
CompilationErrors(
|
||||
reports.joinToString("\n") { report ->
|
||||
report.location?.let { loc ->
|
||||
CompilerMessageLocation.create(
|
||||
report.sourcePath,
|
||||
loc.start.line,
|
||||
loc.start.col,
|
||||
loc.end?.line,
|
||||
loc.end?.col,
|
||||
null
|
||||
)?.toString()?.let {
|
||||
"$it "
|
||||
}
|
||||
}.orEmpty() + report.message
|
||||
},
|
||||
reports.firstOrNull {
|
||||
when (it.severity) {
|
||||
ScriptDiagnostic.Severity.ERROR -> true
|
||||
ScriptDiagnostic.Severity.FATAL -> true
|
||||
else -> false
|
||||
}
|
||||
}?.let {
|
||||
val loc = it.location ?: return@let null
|
||||
CompilerMessageLocation.create(
|
||||
it.sourcePath,
|
||||
loc.start.line,
|
||||
loc.start.col,
|
||||
loc.end?.line,
|
||||
loc.end?.col,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
+379
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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.ide_services
|
||||
|
||||
import junit.framework.TestCase
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
|
||||
import org.jetbrains.kotlin.scripting.ide_services.test_util.SourceCodeTestImpl
|
||||
import org.jetbrains.kotlin.scripting.ide_services.test_util.simpleScriptCompilationConfiguration
|
||||
import org.jetbrains.kotlin.scripting.ide_services.test_util.toList
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.script.experimental.api.*
|
||||
|
||||
class ReplCompletionAndErrorsAnalysisTest : TestCase() {
|
||||
@Test
|
||||
fun testTrivial() = test {
|
||||
run {
|
||||
doCompile
|
||||
code = """
|
||||
data class AClass(val memx: Int, val memy: String)
|
||||
data class BClass(val memz: String, val mema: AClass)
|
||||
val foobar = 42
|
||||
var foobaz = "string"
|
||||
val v = BClass("KKK", AClass(5, "25"))
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
run {
|
||||
code = "foo"
|
||||
cursor = 3
|
||||
expect {
|
||||
addCompletion("foobar", "foobar", "Int", "property")
|
||||
addCompletion("foobaz", "foobaz", "String", "property")
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
code = "v.mema."
|
||||
cursor = 7
|
||||
expect {
|
||||
completionsMode(ComparisonType.INCLUDES)
|
||||
addCompletion("memx", "memx", "Int", "property")
|
||||
addCompletion("memy", "memy", "String", "property")
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
code = "listO"
|
||||
cursor = 5
|
||||
expect {
|
||||
addCompletion("listOf(", "listOf(T)", "List<T>", "method")
|
||||
addCompletion("listOf()", "listOf()", "List<T>", "method")
|
||||
addCompletion("listOf(", "listOf(vararg T)", "List<T>", "method")
|
||||
addCompletion("listOfNotNull(", "listOfNotNull(T?)", "List<T>", "method")
|
||||
addCompletion("listOfNotNull(", "listOfNotNull(vararg T?)", "List<T>", "method")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testPackagesImport() = test {
|
||||
run {
|
||||
cursor = 17
|
||||
code = "import java.lang."
|
||||
expect {
|
||||
completionsMode(ComparisonType.INCLUDES)
|
||||
addCompletion("Process", "Process", " (java.lang)", "class")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExtensionMethods() = test {
|
||||
run {
|
||||
doCompile
|
||||
code = """
|
||||
class AClass(val c_prop_x: Int) {
|
||||
fun filter(xxx: (AClass).() -> Boolean): AClass {
|
||||
return this
|
||||
}
|
||||
}
|
||||
val AClass.c_prop_y: Int
|
||||
get() = c_prop_x * c_prop_x
|
||||
|
||||
fun AClass.c_meth_z(v: Int) = v * c_prop_y
|
||||
val df = AClass(10)
|
||||
val c_zzz = "some string"
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
run {
|
||||
code = "df.filter{ c_ }"
|
||||
cursor = 13
|
||||
expect {
|
||||
addCompletion("c_prop_x", "c_prop_x", "Int", "property")
|
||||
addCompletion("c_zzz", "c_zzz", "String", "property")
|
||||
addCompletion("c_prop_y", "c_prop_y", "Int", "property")
|
||||
addCompletion("c_meth_z(", "c_meth_z(Int)", "Int", "method")
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
code = "df.fil"
|
||||
cursor = 6
|
||||
expect {
|
||||
addCompletion("filter { ", "filter(Line_1_simplescript.AClass.() -> ...", "Line_1_simplescript.AClass", "method")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBacktickedFields() = test {
|
||||
run {
|
||||
doCompile
|
||||
code = """
|
||||
class AClass(val `c_prop x y z`: Int)
|
||||
val df = AClass(33)
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
run {
|
||||
code = "df.c_"
|
||||
cursor = 5
|
||||
expect {
|
||||
addCompletion("`c_prop x y z`", "`c_prop x y z`", "Int", "property")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListErrors() = test {
|
||||
run {
|
||||
doCompile
|
||||
code = """
|
||||
data class AClass(val memx: Int, val memy: String)
|
||||
data class BClass(val memz: String, val mema: AClass)
|
||||
val foobar = 42
|
||||
var foobaz = "string"
|
||||
val v = BClass("KKK", AClass(5, "25"))
|
||||
""".trimIndent()
|
||||
expect {
|
||||
errorsMode(ComparisonType.EQUALS)
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
code = """
|
||||
val a = AClass("42", 3.14)
|
||||
val b: Int = "str"
|
||||
val c = foob
|
||||
""".trimIndent()
|
||||
expect {
|
||||
addError(1, 16, 1, 20, "Type mismatch: inferred type is String but Int was expected", "ERROR")
|
||||
addError(1, 22, 1, 26, "The floating-point literal does not conform to the expected type String", "ERROR")
|
||||
addError(2, 14, 2, 19, "Type mismatch: inferred type is String but Int was expected", "ERROR")
|
||||
addError(3, 9, 3, 13, "Unresolved reference: foob", "ERROR")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCompletionDuplication() = test {
|
||||
for (i in 1..6) {
|
||||
run {
|
||||
if (i == 5) doCompile
|
||||
if (i % 2 == 1) doErrorCheck
|
||||
|
||||
val value = "a".repeat(i)
|
||||
code = "val ddddd = \"$value\""
|
||||
cursor = 13 + i
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
code = "dd"
|
||||
cursor = 2
|
||||
expect {
|
||||
addCompletion("ddddd", "ddddd", "String", "property")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestConf {
|
||||
private val runs = mutableListOf<Run>()
|
||||
|
||||
fun run(setup: (Run).() -> Unit) {
|
||||
val r = Run()
|
||||
r.setup()
|
||||
runs.add(r)
|
||||
}
|
||||
|
||||
fun collect() = runs.map { it.collect() }
|
||||
|
||||
class Run {
|
||||
private var _doCompile = false
|
||||
val doCompile: Unit
|
||||
get() {
|
||||
_doCompile = true
|
||||
}
|
||||
|
||||
private var _doComplete = false
|
||||
val doComplete: Unit
|
||||
get() {
|
||||
_doComplete = true
|
||||
}
|
||||
|
||||
private var _doErrorCheck = false
|
||||
val doErrorCheck: Unit
|
||||
get() {
|
||||
_doErrorCheck = true
|
||||
}
|
||||
|
||||
var cursor: Int? = null
|
||||
var code: String = ""
|
||||
private var _expected: Expected = Expected(this)
|
||||
|
||||
fun expect(setup: (Expected).() -> Unit) {
|
||||
_expected = Expected(this)
|
||||
_expected.setup()
|
||||
}
|
||||
|
||||
fun collect(): Pair<RunRequest, ExpectedResult> {
|
||||
return RunRequest(cursor, code, _doCompile, _doComplete, _doErrorCheck) to _expected.collect()
|
||||
}
|
||||
|
||||
class Expected(private val run: Run) {
|
||||
private val variants = mutableListOf<SourceCodeCompletionVariant>()
|
||||
private var _completionsMode: ComparisonType = ComparisonType.DONT_CHECK
|
||||
fun completionsMode(mode: ComparisonType) {
|
||||
_completionsMode = mode
|
||||
}
|
||||
|
||||
fun addCompletion(text: String, displayText: String, tail: String, icon: String) {
|
||||
if (_completionsMode == ComparisonType.DONT_CHECK)
|
||||
_completionsMode = ComparisonType.EQUALS
|
||||
run.doComplete
|
||||
variants.add(SourceCodeCompletionVariant(text, displayText, tail, icon))
|
||||
}
|
||||
|
||||
private val errors = mutableListOf<ScriptDiagnostic>()
|
||||
private var _errorsMode: ComparisonType = ComparisonType.DONT_CHECK
|
||||
fun errorsMode(mode: ComparisonType) {
|
||||
_errorsMode = mode
|
||||
}
|
||||
|
||||
fun addError(startLine: Int, startCol: Int, endLine: Int, endCol: Int, message: String, severity: String) {
|
||||
if (_errorsMode == ComparisonType.DONT_CHECK)
|
||||
_errorsMode = ComparisonType.EQUALS
|
||||
run.doErrorCheck
|
||||
errors.add(
|
||||
ScriptDiagnostic(
|
||||
ScriptDiagnostic.unspecifiedError,
|
||||
message,
|
||||
ScriptDiagnostic.Severity.valueOf(severity),
|
||||
location = SourceCode.Location(
|
||||
SourceCode.Position(startLine, startCol),
|
||||
SourceCode.Position(endLine, endCol)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun collect(): ExpectedResult {
|
||||
return ExpectedResult(variants, _completionsMode, errors, _errorsMode)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun test(setup: (TestConf).() -> Unit) {
|
||||
val test = TestConf()
|
||||
test.setup()
|
||||
runBlocking { checkEvaluateInRepl(simpleScriptCompilationConfiguration, test.collect()) }
|
||||
}
|
||||
|
||||
enum class ComparisonType {
|
||||
INCLUDES, EQUALS, DONT_CHECK
|
||||
}
|
||||
|
||||
data class RunRequest(
|
||||
val cursor: Int?,
|
||||
val code: String,
|
||||
val doCompile: Boolean,
|
||||
val doComplete: Boolean,
|
||||
val doErrorCheck: Boolean
|
||||
)
|
||||
|
||||
data class ExpectedResult(
|
||||
val completions: List<SourceCodeCompletionVariant>, val completionsCompType: ComparisonType,
|
||||
val errors: List<ScriptDiagnostic>, val errorsCompType: ComparisonType
|
||||
)
|
||||
|
||||
private val currentLineCounter = AtomicInteger()
|
||||
|
||||
private fun nextCodeLine(code: String): SourceCode =
|
||||
SourceCodeTestImpl(
|
||||
currentLineCounter.getAndIncrement(),
|
||||
code
|
||||
)
|
||||
|
||||
private suspend fun evaluateInRepl(
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
snippets: List<RunRequest>
|
||||
): List<ResultWithDiagnostics<Pair<List<SourceCodeCompletionVariant>, List<ScriptDiagnostic>>>> {
|
||||
val compiler = KJvmReplCompilerWithIdeServices()
|
||||
return snippets.map { (cursor, snippetText, doComplile, doComplete, doErrorCheck) ->
|
||||
val pos = SourceCode.Position(0, 0, cursor)
|
||||
val codeLine = nextCodeLine(snippetText)
|
||||
val completionRes = if (doComplete && cursor != null) {
|
||||
val res = compiler.complete(codeLine, pos, compilationConfiguration)
|
||||
res.toList().filter { it.tail != "keyword" }
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val errorsRes = if (doErrorCheck) {
|
||||
val codeLineForErrorCheck = nextCodeLine(snippetText)
|
||||
compiler.analyze(codeLineForErrorCheck, SourceCode.Position(0, 0), compilationConfiguration).toList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
if (doComplile) {
|
||||
val codeLineForCompilation = nextCodeLine(snippetText)
|
||||
compiler.compile(codeLineForCompilation, compilationConfiguration)
|
||||
}
|
||||
|
||||
Pair(completionRes, errorsRes).asSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> checkLists(index: Int, checkName: String, expected: List<T>, actual: List<T>, compType: ComparisonType) {
|
||||
when (compType) {
|
||||
ComparisonType.EQUALS -> Assert.assertEquals(
|
||||
"#$index ($checkName): Expected $expected, got $actual",
|
||||
expected,
|
||||
actual
|
||||
)
|
||||
ComparisonType.INCLUDES -> Assert.assertTrue(
|
||||
"#$index ($checkName): Expected $actual to include $expected",
|
||||
actual.containsAll(expected)
|
||||
)
|
||||
ComparisonType.DONT_CHECK -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkEvaluateInRepl(
|
||||
compilationConfiguration: ScriptCompilationConfiguration,
|
||||
testData: List<Pair<RunRequest, ExpectedResult>>
|
||||
) {
|
||||
val (snippets, expected) = testData.unzip()
|
||||
val expectedIter = expected.iterator()
|
||||
evaluateInRepl(compilationConfiguration, snippets).forEachIndexed { index, res ->
|
||||
when (res) {
|
||||
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
|
||||
is ResultWithDiagnostics.Success -> {
|
||||
val (expectedCompletions, completionsCompType, expectedErrors, errorsCompType) = expectedIter.next()
|
||||
val (completionsRes, errorsRes) = res.value
|
||||
|
||||
checkLists(index, "completions", expectedCompletions, completionsRes, completionsCompType)
|
||||
val expectedErrorsWithPath = expectedErrors.map {
|
||||
it.copy(sourcePath = errorsRes.firstOrNull()?.sourcePath)
|
||||
}
|
||||
checkLists(index, "errors", expectedErrorsWithPath, errorsRes, errorsCompType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.ide_services.test_util
|
||||
|
||||
import kotlin.script.experimental.annotations.KotlinScript
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.experimental.jvm.updateClasspath
|
||||
import kotlin.script.experimental.jvm.util.classpathFromClass
|
||||
|
||||
@KotlinScript(fileExtension = "simplescript.kts")
|
||||
abstract class SimpleScript
|
||||
|
||||
val simpleScriptCompilationConfiguration =
|
||||
createJvmCompilationConfigurationFromTemplate<SimpleScript> {
|
||||
updateClasspath(classpathFromClass<SimpleScript>())
|
||||
}
|
||||
|
||||
val simpleScriptEvaluationConfiguration = ScriptEvaluationConfiguration()
|
||||
|
||||
@KotlinScript(fileExtension = "withboth.kts", compilationConfiguration = ReceiverAndPropertiesConfiguration::class)
|
||||
abstract class ScriptWithBoth
|
||||
|
||||
@KotlinScript(fileExtension = "withproperties.kts", compilationConfiguration = ProvidedPropertiesConfiguration::class)
|
||||
abstract class ScriptWithProvidedProperties
|
||||
|
||||
@KotlinScript(fileExtension = "withreceiver.kts", compilationConfiguration = ImplicitReceiverConfiguration::class)
|
||||
abstract class ScriptWithImplicitReceiver
|
||||
|
||||
object ReceiverAndPropertiesConfiguration : ScriptCompilationConfiguration(
|
||||
{
|
||||
updateClasspath(classpathFromClass<ScriptWithBoth>())
|
||||
|
||||
providedProperties("providedString" to String::class)
|
||||
|
||||
implicitReceivers(ImplicitReceiverClass::class)
|
||||
}
|
||||
)
|
||||
|
||||
object ProvidedPropertiesConfiguration : ScriptCompilationConfiguration(
|
||||
{
|
||||
updateClasspath(classpathFromClass<ScriptWithProvidedProperties>())
|
||||
|
||||
providedProperties("providedString" to String::class)
|
||||
}
|
||||
)
|
||||
|
||||
object ImplicitReceiverConfiguration : ScriptCompilationConfiguration(
|
||||
{
|
||||
updateClasspath(classpathFromClass<ScriptWithImplicitReceiver>())
|
||||
|
||||
implicitReceivers(ImplicitReceiverClass::class)
|
||||
}
|
||||
)
|
||||
|
||||
class ImplicitReceiverClass(
|
||||
@Suppress("unused")
|
||||
val receiverString: String
|
||||
)
|
||||
|
||||
|
||||
inline fun <reified T : Any> createJvmCompilationConfigurationFromTemplate(
|
||||
hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration,
|
||||
noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
|
||||
): ScriptCompilationConfiguration =
|
||||
createCompilationConfigurationFromTemplate(
|
||||
KotlinType(T::class),
|
||||
hostConfiguration,
|
||||
ScriptCompilationConfiguration::class,
|
||||
body
|
||||
)
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2010-2020 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.ide_services.test_util
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.scripting.ide_services.compiler.KJvmReplCompilerWithIdeServices
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.script.experimental.api.CompiledSnippet
|
||||
import kotlin.script.experimental.api.ResultWithDiagnostics
|
||||
import kotlin.script.experimental.api.SourceCode
|
||||
import kotlin.script.experimental.api.valueOrNull
|
||||
import kotlin.script.experimental.jvm.BasicJvmReplEvaluator
|
||||
import kotlin.script.experimental.util.LinkedSnippet
|
||||
import kotlin.script.experimental.jvm.util.toSourceCodePosition
|
||||
|
||||
internal class JvmTestRepl : Closeable {
|
||||
private val currentLineCounter = AtomicInteger(0)
|
||||
|
||||
private val compileConfiguration = simpleScriptCompilationConfiguration
|
||||
private val evalConfiguration = simpleScriptEvaluationConfiguration
|
||||
|
||||
fun nextCodeLine(code: String): SourceCode =
|
||||
SourceCodeTestImpl(
|
||||
currentLineCounter.getAndIncrement(),
|
||||
code
|
||||
)
|
||||
|
||||
private val replCompiler: KJvmReplCompilerWithIdeServices by lazy {
|
||||
KJvmReplCompilerWithIdeServices()
|
||||
}
|
||||
|
||||
private val compiledEvaluator: BasicJvmReplEvaluator by lazy {
|
||||
BasicJvmReplEvaluator()
|
||||
}
|
||||
|
||||
fun compile(code: SourceCode) = runBlocking { replCompiler.compile(code, compileConfiguration) }
|
||||
fun complete(code: SourceCode, cursor: Int) = runBlocking { replCompiler.complete(code, cursor.toSourceCodePosition(code), compileConfiguration) }
|
||||
|
||||
fun eval(snippet: LinkedSnippet<out CompiledSnippet>) = runBlocking { compiledEvaluator.eval(snippet, evalConfiguration) }
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class SourceCodeTestImpl(number: Int, override val text: String) : SourceCode {
|
||||
override val name: String? = "Line_$number"
|
||||
override val locationId: String? = "location_$number"
|
||||
}
|
||||
|
||||
@JvmName("iterableToList")
|
||||
fun <T> ResultWithDiagnostics<Iterable<T>>.toList() = this.valueOrNull()?.toList().orEmpty()
|
||||
|
||||
@JvmName("sequenceToList")
|
||||
fun <T> ResultWithDiagnostics<Sequence<T>>.toList() = this.valueOrNull()?.toList().orEmpty()
|
||||
Reference in New Issue
Block a user