Scripting: fix CLI REPL error reporting behavior
discrepancy after switching to the new REPL internals was discovered also on IJ plugin tests, so added a test emulating it. also some test infra refactoring
This commit is contained in:
+3
@@ -2,6 +2,9 @@
|
|||||||
error: expecting an element
|
error: expecting an element
|
||||||
)(
|
)(
|
||||||
^
|
^
|
||||||
|
error: expecting an expression
|
||||||
|
)(
|
||||||
|
^
|
||||||
>>> fun foo() = 98
|
>>> fun foo() = 98
|
||||||
>>> foo()
|
>>> foo()
|
||||||
98
|
98
|
||||||
|
|||||||
@@ -49,12 +49,12 @@ class LauncherReplTest : TestCaseWithTmpdir() {
|
|||||||
val out = ArrayList<String>()
|
val out = ArrayList<String>()
|
||||||
val exceptionContainer = ExceptionContainer()
|
val exceptionContainer = ExceptionContainer()
|
||||||
val thread = thread {
|
val thread = thread {
|
||||||
val promptRegex = Regex("(?:\\u001B\\p{Graph}+)*(?:>>>|\\.\\.\\.)")
|
val promptRegex = Regex("(?:\\u001B\\p{Graph}+)*(?:>>> ?|\\.\\.\\.)")
|
||||||
try {
|
try {
|
||||||
reader().forEachLine { rawLine ->
|
reader().forEachLine { rawLine ->
|
||||||
promptRegex.split(rawLine).forEach { line ->
|
promptRegex.split(rawLine).forEach { line ->
|
||||||
if (line.isNotEmpty()) {
|
if (line.isNotEmpty()) {
|
||||||
out.add(line.trim())
|
out.add(line.trimEnd())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,13 +137,24 @@ class LauncherReplTest : TestCaseWithTmpdir() {
|
|||||||
Assert.fail("missing output for expected patterns:\n${inputsToExpectedOutputsIter.asSequence().joinToString("\n") { it.second } }")
|
Assert.fail("missing output for expected patterns:\n${inputsToExpectedOutputsIter.asSequence().joinToString("\n") { it.second } }")
|
||||||
}
|
}
|
||||||
if (!inputsToExpectedOutputsIter.hasNext() || !actualIter.hasNext()) break
|
if (!inputsToExpectedOutputsIter.hasNext() || !actualIter.hasNext()) break
|
||||||
val (input, expectedPattern) = inputsToExpectedOutputsIter.next()
|
var (input, expectedPattern) = inputsToExpectedOutputsIter.next()
|
||||||
val actualLine = actualIter.next()
|
var actualLine = actualIter.next()
|
||||||
val strippedActualLine = if (input != null) actualLine.removePrefix(input) else actualLine
|
while (input != null) {
|
||||||
assertTrue(
|
if (actualLine.startsWith(input)) {
|
||||||
"line \"$strippedActualLine\" do not match with expected pattern \"$expectedPattern\"",
|
actualLine = actualLine.substring(input.length)
|
||||||
Regex(expectedPattern).matches(strippedActualLine)
|
} else if (expectedPattern.isEmpty() && actualLine.isNotEmpty() && inputsToExpectedOutputsIter.hasNext()) {
|
||||||
)
|
// assuming that on some configs input is not repeated if producing empty output
|
||||||
|
// in this case trying to check the next expected output
|
||||||
|
val nextInputToOutput = inputsToExpectedOutputsIter.next()
|
||||||
|
expectedPattern = nextInputToOutput.second
|
||||||
|
input = nextInputToOutput.first
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!Regex(expectedPattern).matches(actualLine)) {
|
||||||
|
fail("line \"$actualLine\" do not match with expected pattern \"$expectedPattern\"")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +170,37 @@ class LauncherReplTest : TestCaseWithTmpdir() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testSReplWithMultipleErrors() {
|
||||||
|
runInteractive(
|
||||||
|
*replOutHeader,
|
||||||
|
"\$;\$;" to ".*expecting an element",
|
||||||
|
null to "\\\$;\\\$;",
|
||||||
|
null to "\\^",
|
||||||
|
null to ".*expecting an element",
|
||||||
|
null to "\\\$;\\\$;",
|
||||||
|
null to " \\^",
|
||||||
|
"println(\$);println(\$);" to ".*expecting an expression",
|
||||||
|
null to "println\\(\\\$\\);println\\(\\\$\\);",
|
||||||
|
null to " \\^",
|
||||||
|
null to ".*expecting '\\)'",
|
||||||
|
null to "println\\(\\\$\\);println\\(\\\$\\);",
|
||||||
|
null to " \\^",
|
||||||
|
null to ".*expecting an element",
|
||||||
|
null to "println\\(\\\$\\);println\\(\\\$\\);",
|
||||||
|
null to " \\^",
|
||||||
|
null to ".*expecting an expression",
|
||||||
|
null to "println\\(\\\$\\);println\\(\\\$\\);",
|
||||||
|
null to " \\^",
|
||||||
|
null to ".*expecting '\\)'",
|
||||||
|
null to "println\\(\\\$\\);println\\(\\\$\\);",
|
||||||
|
null to " \\^",
|
||||||
|
null to ".*expecting an element",
|
||||||
|
null to "println\\(\\\$\\);println\\(\\\$\\);",
|
||||||
|
null to " \\^",
|
||||||
|
"println(42)" to "42",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun testReplResultFormatting() {
|
fun testReplResultFormatting() {
|
||||||
runInteractive(
|
runInteractive(
|
||||||
*replOutHeader,
|
*replOutHeader,
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import org.jetbrains.kotlin.test.KotlinTestUtils
|
|||||||
import org.jetbrains.kotlin.test.TestJdkKind
|
import org.jetbrains.kotlin.test.TestJdkKind
|
||||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.PrintStream
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
@@ -86,6 +88,24 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun <T> captureOutErrRet(body: () -> T): Triple<String, String, T> {
|
||||||
|
val outStream = ByteArrayOutputStream()
|
||||||
|
val errStream = ByteArrayOutputStream()
|
||||||
|
val prevOut = System.out
|
||||||
|
val prevErr = System.err
|
||||||
|
System.setOut(PrintStream(outStream))
|
||||||
|
System.setErr(PrintStream(errStream))
|
||||||
|
val ret = try {
|
||||||
|
body()
|
||||||
|
} finally {
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
|
System.setOut(prevOut)
|
||||||
|
System.setErr(prevErr)
|
||||||
|
}
|
||||||
|
return Triple(outStream.toString().trim(), errStream.toString().trim(), ret)
|
||||||
|
}
|
||||||
|
|
||||||
protected fun doTest(path: String) {
|
protected fun doTest(path: String) {
|
||||||
val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK)
|
val configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.ALL, TestJdkKind.MOCK_JDK)
|
||||||
loadScriptingPlugin(configuration)
|
loadScriptingPlugin(configuration)
|
||||||
@@ -101,7 +121,7 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
for ((code, expected) in loadLines(File(path))) {
|
for ((code, expected) in loadLines(File(path))) {
|
||||||
val lineResult = repl.eval(code)
|
val (output, _, lineResult) = captureOutErrRet { repl.eval(code) }
|
||||||
|
|
||||||
if (DUMP_BYTECODE) {
|
if (DUMP_BYTECODE) {
|
||||||
repl.dumpClasses(PrintWriter(System.out))
|
repl.dumpClasses(PrintWriter(System.out))
|
||||||
@@ -109,7 +129,7 @@ abstract class AbstractReplInterpreterTest : KtUsefulTestCase() {
|
|||||||
|
|
||||||
val actual = when (lineResult) {
|
val actual = when (lineResult) {
|
||||||
is ReplEvalResult.ValueResult -> lineResult.value.toString()
|
is ReplEvalResult.ValueResult -> lineResult.value.toString()
|
||||||
is ReplEvalResult.Error.CompileTime -> MessageRenderer.WITHOUT_PATHS.render(CompilerMessageSeverity.ERROR, lineResult.message, lineResult.location)
|
is ReplEvalResult.Error.CompileTime -> output
|
||||||
is ReplEvalResult.Error -> lineResult.message
|
is ReplEvalResult.Error -> lineResult.message
|
||||||
is ReplEvalResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
is ReplEvalResult.Incomplete -> INCOMPLETE_LINE_MESSAGE
|
||||||
is ReplEvalResult.UnitResult -> ""
|
is ReplEvalResult.UnitResult -> ""
|
||||||
|
|||||||
+2
-2
@@ -139,8 +139,8 @@ class ReplFromTerminal(
|
|||||||
writer.outputCommandResult(tryInterpretResultAsValueClass(evalResult) ?: evalResult.toString())
|
writer.outputCommandResult(tryInterpretResultAsValueClass(evalResult) ?: evalResult.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ReplEvalResult.Error.Runtime -> writer.outputRuntimeError(evalResult.message)
|
is ReplEvalResult.Error.Runtime -> if (evalResult.message.isNotEmpty()) writer.outputRuntimeError(evalResult.message)
|
||||||
is ReplEvalResult.Error.CompileTime -> writer.outputCompileError(evalResult.message)
|
is ReplEvalResult.Error.CompileTime -> if (evalResult.message.isNotEmpty()) writer.outputCompileError(evalResult.message)
|
||||||
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
|
is ReplEvalResult.Incomplete -> writer.notifyIncomplete()
|
||||||
is ReplEvalResult.HistoryMismatch -> {} // assuming handled elsewhere
|
is ReplEvalResult.HistoryMismatch -> {} // assuming handled elsewhere
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -174,12 +174,11 @@ class ReplInterpreter(
|
|||||||
when (val compileResult = compiler.compile(listOf(snippet), compilationConfiguration)) {
|
when (val compileResult = compiler.compile(listOf(snippet), compilationConfiguration)) {
|
||||||
is ResultWithDiagnostics.Failure -> {
|
is ResultWithDiagnostics.Failure -> {
|
||||||
val incompleteReport = compileResult.reports.find { it.code == ScriptDiagnostic.incompleteCode }
|
val incompleteReport = compileResult.reports.find { it.code == ScriptDiagnostic.incompleteCode }
|
||||||
val firstError = compileResult.reports.find { it.isError() }
|
|
||||||
if (incompleteReport != null)
|
if (incompleteReport != null)
|
||||||
ReplEvalResult.Incomplete(incompleteReport.message)
|
ReplEvalResult.Incomplete(incompleteReport.message)
|
||||||
else {
|
else {
|
||||||
compileResult.reportToMessageCollector()
|
compileResult.reportToMessageCollector()
|
||||||
ReplEvalResult.Error.CompileTime(firstError?.message ?: "", firstError?.location?.toCompilerMessageLocation())
|
ReplEvalResult.Error.CompileTime("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ResultWithDiagnostics.Success -> {
|
is ResultWithDiagnostics.Success -> {
|
||||||
@@ -195,9 +194,8 @@ class ReplInterpreter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val firstError = evalResult.reports.find { it.isError() }
|
|
||||||
evalResult.reportToMessageCollector()
|
evalResult.reportToMessageCollector()
|
||||||
ReplEvalResult.Error.Runtime(firstError?.message ?: "", firstError?.exception)
|
ReplEvalResult.Error.Runtime("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user