Implement proper script runtime exception rendering with tests
#KT-42335 fixed
This commit is contained in:
+10
-19
@@ -19,10 +19,11 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.util.*
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.StringScriptSource
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.util.renderError
|
||||
|
||||
abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
|
||||
|
||||
@@ -70,13 +71,19 @@ abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
|
||||
val extensionHint =
|
||||
if (configuration.get(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)?.let { it.size == 1 && it.first().isDefault } == true) " (.kts)"
|
||||
else ""
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "$error; Specify path to the script file$extensionHint as the first argument")
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR,
|
||||
"$error; Specify path to the script file$extensionHint as the first argument"
|
||||
)
|
||||
return ExitCode.COMPILATION_ERROR
|
||||
}
|
||||
script
|
||||
}
|
||||
else -> {
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "Illegal set of arguments: either -script or -expression arguments expected at this point")
|
||||
messageCollector.report(
|
||||
CompilerMessageSeverity.ERROR,
|
||||
"Illegal set of arguments: either -script or -expression arguments expected at this point"
|
||||
)
|
||||
return ExitCode.COMPILATION_ERROR
|
||||
}
|
||||
}
|
||||
@@ -150,19 +157,3 @@ fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessageSeveri
|
||||
ScriptDiagnostic.Severity.DEBUG -> CompilerMessageSeverity.LOGGING
|
||||
}
|
||||
|
||||
private fun ResultValue.Error.renderError(stream: PrintStream) {
|
||||
val fullTrace = error.stackTrace
|
||||
if (wrappingException == null || fullTrace.size < wrappingException!!.stackTrace.size) {
|
||||
error.printStackTrace(stream)
|
||||
} else {
|
||||
// subtracting wrapping message stacktrace from error stacktrace to show only user-specific part of it
|
||||
// TODO: consider more reliable logic, e.g. comparing traces, fallback to full error printing in case of mismatch
|
||||
// TODO: write tests
|
||||
stream.println(error)
|
||||
val scriptTraceSize = fullTrace.size - wrappingException!!.stackTrace.size
|
||||
for (i in 0 until scriptTraceSize) {
|
||||
stream.println("\tat " + fullTrace[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
try {
|
||||
try {
|
||||
throw Exception("Error!")
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Oh no", e)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Top", e)
|
||||
}
|
||||
+28
@@ -5,7 +5,10 @@
|
||||
|
||||
package org.jetbrains.kotlin.scripting.compiler.plugin
|
||||
|
||||
import org.jetbrains.kotlin.cli.common.CLITool
|
||||
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.jetbrains.kotlin.scripting.compiler.test.linesSplitTrim
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
@@ -84,6 +87,31 @@ class ScriptingWithCliCompilerTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExceptionWithCause() {
|
||||
val (_, err, _) = captureOutErrRet {
|
||||
CLITool.doMainNoExit(
|
||||
K2JVMCompiler(),
|
||||
arrayOf(
|
||||
"-script",
|
||||
"$TEST_DATA_DIR/integration/exceptionWithCause.kts"
|
||||
)
|
||||
)
|
||||
}
|
||||
val filteredErr = err.linesSplitTrim().filterNot { it.startsWith("WARN: ") }
|
||||
Assert.assertEquals(
|
||||
"""
|
||||
java.lang.Exception: Top
|
||||
at ExceptionWithCause.<init>(exceptionWithCause.kts:8)
|
||||
Caused by: java.lang.Exception: Oh no
|
||||
at ExceptionWithCause.<init>(exceptionWithCause.kts:5)
|
||||
Caused by: java.lang.Exception: Error!
|
||||
at ExceptionWithCause.<init>(exceptionWithCause.kts:3)
|
||||
""".trimIndent().linesSplitTrim(),
|
||||
filteredErr
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMainKtsClassPath(): List<File> {
|
||||
return listOf(
|
||||
File("dist/kotlinc/lib/kotlin-main-kts.jar").also {
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import junit.framework.TestCase
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptJvmCompilerIsolated
|
||||
import org.jetbrains.kotlin.scripting.compiler.test.assertEqualsTrimmed
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
|
||||
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
|
||||
import kotlin.script.experimental.jvm.util.renderError
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
class ScriptEvaluationTest : TestCase() {
|
||||
|
||||
fun testExceptionWithCause() {
|
||||
checkEvaluate(
|
||||
"""
|
||||
try {
|
||||
throw Exception("Error!")
|
||||
} catch (e: Exception) {
|
||||
throw Exception("Oh no", e)
|
||||
}
|
||||
""".trimIndent().toScriptSource("exceptionWithCause.kts"),
|
||||
"""
|
||||
java.lang.Exception: Oh no
|
||||
at ExceptionWithCause.<init>(exceptionWithCause.kts:4)
|
||||
Caused by: java.lang.Exception: Error!
|
||||
at ExceptionWithCause.<init>(exceptionWithCause.kts:2)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
private fun checkEvaluate(script: SourceCode, expectedOutput: String) {
|
||||
val compilationConfiguration = ScriptCompilationConfiguration()
|
||||
val compiler = ScriptJvmCompilerIsolated(defaultJvmScriptingHostConfiguration)
|
||||
val compiled = compiler.compile(script, compilationConfiguration).valueOrThrow()
|
||||
val evaluationConfiguration = ScriptEvaluationConfiguration()
|
||||
val evaluator = BasicJvmScriptEvaluator()
|
||||
val res = runBlocking {
|
||||
evaluator.invoke(compiled, evaluationConfiguration).valueOrThrow()
|
||||
}
|
||||
assert(res.returnValue is ResultValue.Error)
|
||||
ByteArrayOutputStream().use { os ->
|
||||
val ps = PrintStream(os)
|
||||
(res.returnValue as ResultValue.Error).renderError(ps)
|
||||
ps.flush()
|
||||
assertEqualsTrimmed(expectedOutput, os.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -53,7 +53,7 @@ internal fun captureOut(body: () -> Unit): String {
|
||||
return outStream.toString()
|
||||
}
|
||||
|
||||
private fun String.linesSplitTrim() =
|
||||
internal fun String.linesSplitTrim() =
|
||||
split('\n', '\r').map(String::trim).filter(String::isNotBlank)
|
||||
|
||||
internal fun assertEqualsTrimmed(expected: String, actual: String) =
|
||||
|
||||
Reference in New Issue
Block a user