transcendental-lisp/src/test/kotlin/interpreter/LispInterpreterTest.kt

162 lines
5.8 KiB
Kotlin

package interpreter
import environment.RuntimeEnvironment
import interpreter.InteractiveLispInterpreter.Companion.PROMPT
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import testutil.TestUtilities.createInputStreamFromString
import java.io.ByteArrayOutputStream
import java.io.PrintStream
class LispInterpreterTest {
companion object {
private const val TERMINATED = "terminated"
private val FILE = LispInterpreterTest::class.java.getResource("file.lisp").file
}
private val indicatorSet = mutableSetOf<String>()
private val outputStream = ByteArrayOutputStream()
private val errorOutputStream = ByteArrayOutputStream()
private fun setCommonFeatures() {
LispInterpreterBuilder.setOutput(PrintStream(outputStream))
LispInterpreterBuilder.setErrorOutput(PrintStream(errorOutputStream))
LispInterpreterBuilder.setTerminationFunction { }
LispInterpreterBuilder.setErrorTerminationFunction { indicatorSet.add(TERMINATED) }
}
private fun assertTerminated() {
assertThat(indicatorSet).contains(TERMINATED)
}
private fun assertErrorMessageWritten() {
assertThat(errorOutputStream.toByteArray()).isNotEmpty()
}
@BeforeEach()
fun setUp() {
indicatorSet.clear()
outputStream.reset()
errorOutputStream.reset()
RuntimeEnvironment.reset()
LispInterpreterBuilder.reset()
}
@AfterEach()
fun tearDown() {
RuntimeEnvironment.reset()
LispInterpreterBuilder.reset()
}
@Test
fun `build an interactive interpreter`() {
setCommonFeatures()
LispInterpreterBuilder.setInput(System.`in`, "stdin")
val interpreter = LispInterpreterBuilder.build()
assertThat(interpreter is InteractiveLispInterpreter).isTrue()
}
@Test
fun `build a non interactive interpreter`() {
setCommonFeatures()
LispInterpreterBuilder.setInput(System.`in`, "stdin")
LispInterpreterBuilder.setNotInteractive()
val interpreter = LispInterpreterBuilder.build()
assertThat(interpreter is InteractiveLispInterpreter).isFalse()
assertThat(interpreter is FileLispInterpreter).isFalse()
}
@Test
fun `build a file based interpreter`() {
setCommonFeatures()
LispInterpreterBuilder.useFile(FILE)
val interpreter = LispInterpreterBuilder.build()
assertThat(interpreter is FileLispInterpreter).isTrue()
}
@Test
fun `building an interpreter on a bad file terminates correctly`() {
setCommonFeatures()
LispInterpreterBuilder.useFile("does-not-exist.lisp")
LispInterpreterBuilder.build()
assertErrorMessageWritten()
assertTerminated()
}
@Test
fun `the decorators are initialized with the correct defaults`() {
LispInterpreterBuilder.build()
assertThat(RuntimeEnvironment.decoratePrompt("")).isEmpty()
assertThat(RuntimeEnvironment.decorateValueOutput("")).isEmpty()
assertThat(RuntimeEnvironment.decorateWarningOutput("")).isEmpty()
assertThat(RuntimeEnvironment.decorateErrorOutput("")).isEmpty()
assertThat(RuntimeEnvironment.decorateCriticalOutput("")).isEmpty()
}
@Test
fun `the decorators are set correctly`() {
LispInterpreterBuilder.setPromptDecorator { s -> "#$s#" }
LispInterpreterBuilder.setValueOutputDecorator { s -> "@$s@" }
LispInterpreterBuilder.setWarningOutputDecorator { s -> "%$s%" }
LispInterpreterBuilder.setErrorOutputDecorator { s -> "*$s*" }
LispInterpreterBuilder.setCriticalOutputDecorator { s -> "$$s$" }
LispInterpreterBuilder.build()
assertThat(RuntimeEnvironment.decoratePrompt("x")).isEqualTo("#x#")
assertThat(RuntimeEnvironment.decorateValueOutput("x")).isEqualTo("@x@")
assertThat(RuntimeEnvironment.decorateWarningOutput("x")).isEqualTo("%x%")
assertThat(RuntimeEnvironment.decorateErrorOutput("x")).isEqualTo("*x*")
assertThat(RuntimeEnvironment.decorateCriticalOutput("x")).isEqualTo("\$x$")
}
@Test
fun `the file based interpreter works and prints the last value only`() {
setCommonFeatures()
LispInterpreterBuilder.useFile(FILE)
LispInterpreterBuilder.build().interpret()
assertThat(outputStream.toString()).isEqualTo("PICKLE\n\n")
assertThat(errorOutputStream.toString()).isEmpty()
}
@Test
fun `the interactive interpreter works`() {
setCommonFeatures()
LispInterpreterBuilder.setInput(createInputStreamFromString("'pickle"), "input")
LispInterpreterBuilder.build().interpret()
assertThat(outputStream.toString()).isEqualTo("$PROMPT\nPICKLE\n$PROMPT\n")
assertThat(errorOutputStream.toString()).isEmpty()
}
@Test
fun `the interpreter handles bad input`() {
setCommonFeatures()
LispInterpreterBuilder.setNotInteractive()
LispInterpreterBuilder.setInput(createInputStreamFromString("['pickle"), "input")
LispInterpreterBuilder.build().interpret()
assertThat(outputStream.toString()).isEqualTo("PICKLE\n\n")
assertThat(errorOutputStream.toString()).isEqualTo("[error] illegal character >>[<< - line 1, column 1\n")
}
@Test
fun `the interpreter handles an evaluation error`() {
setCommonFeatures()
LispInterpreterBuilder.setNotInteractive()
LispInterpreterBuilder.setInput(createInputStreamFromString("pickle"), "input")
LispInterpreterBuilder.build().interpret()
assertThat(outputStream.toString()).isEqualTo("\n")
assertThat(errorOutputStream.toString()).isEqualTo("[error] symbol PICKLE has no value\n")
}
}