Improve script and REPL result handling:

- implement error result
- refactor other result classes
- implement handling in the script evaluation extension - also restores
  previous script error reporting functionality
- add possibility to customize result fileds in script and REPL
- refactor result calculation in the backend: cleanup, rename (since
  it is not only about REPL now)
This commit is contained in:
Ilya Chernikov
2019-07-04 13:33:15 +02:00
parent dc4370ff08
commit 9ae0ff03fa
23 changed files with 421 additions and 127 deletions
@@ -27,7 +27,7 @@ class ReplTest : TestCase() {
@Test
fun testCompileAndEval() {
val out = captureOut {
chechEvaluateInReplNoErrors(
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
sequenceOf(
@@ -41,6 +41,32 @@ class ReplTest : TestCase() {
Assert.assertEquals("x = 3", out)
}
@Test
fun testEvalWithResult() {
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
sequenceOf(
"val x = 5",
"x + 6",
"res1 * 2"
),
sequenceOf(null, 11, 22)
)
}
@Test
fun testEvalWithError() {
chechEvaluateInRepl(
simpleScriptompilationConfiguration,
simpleScriptEvaluationConfiguration,
sequenceOf(
"throw RuntimeException(\"abc\")"
),
sequenceOf(RuntimeException("abc"))
)
}
fun evaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
@@ -62,15 +88,12 @@ class ReplTest : TestCase() {
}
}
.onSuccess {
val snippetInstance = when (val retVal = it.returnValue) {
is ResultValue.Value -> retVal.scriptInstance
is ResultValue.UnitValue -> retVal.scriptInstance
else -> throw IllegalStateException("Expecting value with script instance, got $it")
}
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
previousSnippets.append(snippetInstance)
jvm {
baseClassLoader(snippetInstance::class.java.classLoader)
it.returnValue.scriptInstance?.let { snippetInstance ->
currentEvalConfig = ScriptEvaluationConfiguration(currentEvalConfig) {
previousSnippets.append(snippetInstance)
jvm {
baseClassLoader(snippetInstance::class.java.classLoader)
}
}
}
it.asSuccess()
@@ -78,7 +101,7 @@ class ReplTest : TestCase() {
}
}
fun chechEvaluateInReplNoErrors(
fun chechEvaluateInRepl(
compilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
snippets: Sequence<String>,
@@ -90,11 +113,19 @@ class ReplTest : TestCase() {
is ResultWithDiagnostics.Failure -> Assert.fail("#$index: Expected result, got $res")
is ResultWithDiagnostics.Success -> {
val expectedVal = expectedIter.next()
val resVal = res.value.returnValue
if (resVal is ResultValue.Value && resVal.type.isNotBlank()) // TODO: the latter check is temporary while the result is used to return the instance too
Assert.assertEquals("#$index: Expected $expectedVal, got $resVal", expectedVal, resVal.value)
else
Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
when (val resVal = res.value.returnValue) {
is ResultValue.Value -> Assert.assertEquals(
"#$index: Expected $expectedVal, got $resVal",
expectedVal,
resVal.value
)
is ResultValue.Unit -> Assert.assertTrue("#$index: Expected $expectedVal, got Unit", expectedVal == null)
is ResultValue.Error -> Assert.assertTrue(
"#$index: Expected $expectedVal, got Error: ${resVal.error}",
expectedVal is Throwable && expectedVal.message == resVal.error.message
)
else -> Assert.assertTrue("#$index: Expected $expectedVal, got unknown result $resVal", expectedVal == null)
}
}
}
}
@@ -15,6 +15,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes
import org.junit.Assert
import org.junit.Test
import java.io.*
import java.lang.RuntimeException
import java.net.URLClassLoader
import java.nio.file.Files
import java.security.MessageDigest
@@ -51,6 +52,41 @@ class ScriptingHostTest : TestCase() {
Assert.assertEquals(greeting, output2)
}
@Test
fun testValueResult() {
val resVal = evalScriptWithResult("42") as ResultValue.Value
Assert.assertEquals(42, resVal.value)
Assert.assertEquals("\$\$result", resVal.name)
Assert.assertEquals("kotlin.Int", resVal.type)
val resField = resVal.scriptInstance!!::class.java.getDeclaredField("\$\$result")
Assert.assertEquals(42, resField.get(resVal.scriptInstance!!))
}
@Test
fun testUnitResult() {
val resVal = evalScriptWithResult("val x = 42")
Assert.assertTrue(resVal is ResultValue.Unit)
}
@Test
fun testErrorResult() {
val resVal = evalScriptWithResult("throw RuntimeException(\"abc\")")
Assert.assertTrue(resVal is ResultValue.Error)
val resValError = (resVal as ResultValue.Error).error
Assert.assertTrue(resValError is RuntimeException)
Assert.assertEquals("abc", resValError.message)
}
@Test
fun testCustomResultField() {
val resVal = evalScriptWithResult("42") {
resultField("outcome")
} as ResultValue.Value
Assert.assertEquals("outcome", resVal.name)
val resField = resVal.scriptInstance!!::class.java.getDeclaredField("outcome")
Assert.assertEquals(42, resField.get(resVal.scriptInstance!!))
}
@Test
fun testSaveToClasses() {
val greeting = "Hello from script classes!"
@@ -366,7 +402,8 @@ class ScriptingHostTest : TestCase() {
val evaluator = BasicJvmScriptEvaluator()
val host = BasicJvmScriptingHost(compiler = compiler, evaluator = evaluator)
val scriptCompilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>(body = configurationBuilder)
val scriptCompilationConfiguration =
createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>(body = configurationBuilder)
Assert.assertEquals(0, cache.storedScripts)
var compiledScript: CompiledScript<*>? = null
@@ -453,6 +490,13 @@ fun <T> ResultWithDiagnostics<T>.throwOnFailure(): ResultWithDiagnostics<T> = ap
private fun evalScript(script: String, host: BasicScriptingHost = BasicJvmScriptingHost()): ResultWithDiagnostics<*> =
evalScriptWithConfiguration(script, host)
private fun evalScriptWithResult(
script: String,
host: BasicScriptingHost = BasicJvmScriptingHost(),
body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ResultValue =
evalScriptWithConfiguration(script, host, body).throwOnFailure().valueOrNull()!!.returnValue
private fun evalScriptWithConfiguration(
script: String,
host: BasicScriptingHost = BasicJvmScriptingHost(),