From 23dd1c0654a676e93a19d9800413c419c95023f0 Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Thu, 23 Mar 2017 12:14:44 -0400 Subject: [PATCH] Increase test coverage and refactor code --- src/error/ErrorManager.java | 4 +- src/error/LineColumnException.java | 6 +- src/function/ArgumentValidator.java | 12 +- src/function/UserDefinedFunction.java | 6 +- src/function/builtin/EVAL.java | 7 +- src/function/builtin/LOAD.java | 4 +- src/function/builtin/SYMBOL_FUNCTION.java | 7 +- src/function/builtin/special/Define.java | 5 +- .../InteractiveLispInterpreter.java | 2 +- src/main/LispMain.java | 122 ++++++++++++------ src/sexpression/LispNumber.java | 5 +- src/table/FunctionTable.java | 5 +- src/terminal/ControlSequenceHandler.java | 8 +- src/terminal/ControlSequenceLookup.java | 12 +- src/terminal/LispTerminal.java | 25 ++-- src/token/TokenFactory.java | 4 +- test/error/ErrorManagerTest.java | 7 +- test/function/builtin/PRINTTest.java | 10 +- ...lderTest.java => LispInterpreterTest.java} | 45 ++++++- test/interpreter/test-files/file.lisp | 1 + test/stream/SafeInputStreamTest.java | 43 ++++++ test/stream/SafeOutputStreamTest.java | 54 ++++++++ test/terminal/ControlSequenceTest.java | 14 +- test/terminal/LispTerminalTest.java | 11 ++ test/testutil/TestUtilities.java | 32 ++++- 25 files changed, 350 insertions(+), 101 deletions(-) rename test/interpreter/{LispInterpreterBuilderTest.java => LispInterpreterTest.java} (78%) create mode 100644 test/stream/SafeInputStreamTest.java create mode 100644 test/stream/SafeOutputStreamTest.java diff --git a/src/error/ErrorManager.java b/src/error/ErrorManager.java index d336fa1..d609e51 100644 --- a/src/error/ErrorManager.java +++ b/src/error/ErrorManager.java @@ -1,9 +1,9 @@ package error; import static error.ErrorManager.Severity.*; +import static java.text.MessageFormat.format; import java.io.PrintStream; -import java.text.MessageFormat; import environment.RuntimeEnvironment; @@ -40,7 +40,7 @@ public class ErrorManager { private String formatMessage(LispException lispException) { Severity severity = lispException.getSeverity(); String prefix = severity.toDisplayString(); - String message = MessageFormat.format("[{0}] {1}", prefix, lispException.getMessage()); + String message = format("[{0}] {1}", prefix, lispException.getMessage()); return severity.decorate(message, environment); } diff --git a/src/error/LineColumnException.java b/src/error/LineColumnException.java index 2490732..18aa592 100644 --- a/src/error/LineColumnException.java +++ b/src/error/LineColumnException.java @@ -1,6 +1,6 @@ package error; -import java.text.MessageFormat; +import static java.text.MessageFormat.format; import file.FilePosition; @@ -15,8 +15,8 @@ public abstract class LineColumnException extends LispException { @Override public String getMessage() { - return MessageFormat.format("{0} - line {1}, column {2}", getMessagePrefix(), position.getLineNumber(), - position.getColumnNumber()); + return format("{0} - line {1}, column {2}", getMessagePrefix(), position.getLineNumber(), + position.getColumnNumber()); } public abstract String getMessagePrefix(); diff --git a/src/function/ArgumentValidator.java b/src/function/ArgumentValidator.java index 79628dd..0d9df0b 100644 --- a/src/function/ArgumentValidator.java +++ b/src/function/ArgumentValidator.java @@ -1,9 +1,9 @@ package function; import static function.builtin.cons.LENGTH.getLength; +import static java.text.MessageFormat.format; import java.math.BigInteger; -import java.text.MessageFormat; import error.LispException; import sexpression.*; @@ -163,7 +163,7 @@ public class ArgumentValidator { @Override public String getMessage() { - return MessageFormat.format("too few arguments given to {0}: {1}", functionName, argumentList); + return format("too few arguments given to {0}: {1}", functionName, argumentList); } } @@ -180,7 +180,7 @@ public class ArgumentValidator { @Override public String getMessage() { - return MessageFormat.format("too many arguments given to {0}: {1}", functionName, argumentList); + return format("too many arguments given to {0}: {1}", functionName, argumentList); } } @@ -197,7 +197,7 @@ public class ArgumentValidator { @Override public String getMessage() { - return MessageFormat.format("dotted argument list given to {0}: {1}", functionName, argumentList); + return format("dotted argument list given to {0}: {1}", functionName, argumentList); } } @@ -217,8 +217,8 @@ public class ArgumentValidator { @Override public String getMessage() { - return MessageFormat.format("{0}: {1} is not the expected type of ''{2}''", functionName, argument, - getExpectedTypeName()); + return format("{0}: {1} is not the expected type of ''{2}''", functionName, argument, + getExpectedTypeName()); } private String getExpectedTypeName() { diff --git a/src/function/UserDefinedFunction.java b/src/function/UserDefinedFunction.java index 7882760..546aa3e 100644 --- a/src/function/UserDefinedFunction.java +++ b/src/function/UserDefinedFunction.java @@ -1,9 +1,9 @@ package function; import static function.builtin.EVAL.eval; +import static java.text.MessageFormat.format; import static sexpression.Nil.NIL; -import java.text.MessageFormat; import java.util.ArrayList; import error.LispException; @@ -140,8 +140,8 @@ public class UserDefinedFunction extends LispFunction { @Override public String getMessage() { - return MessageFormat.format("unexpected parameters following ''&rest'' in definition of {0}: {1}", - functionName, parameters); + return format("unexpected parameters following ''&rest'' in definition of {0}: {1}", functionName, + parameters); } } diff --git a/src/function/builtin/EVAL.java b/src/function/builtin/EVAL.java index 997964b..ab1d1aa 100644 --- a/src/function/builtin/EVAL.java +++ b/src/function/builtin/EVAL.java @@ -2,12 +2,11 @@ package function.builtin; import static function.builtin.cons.LIST.makeList; import static function.builtin.special.LAMBDA.*; +import static java.text.MessageFormat.format; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; import static table.FunctionTable.lookupFunction; -import java.text.MessageFormat; - import error.LispException; import function.*; import sexpression.*; @@ -150,7 +149,7 @@ public class EVAL extends LispFunction { @Override public String getMessage() { - return MessageFormat.format("undefined function: {0}", function); + return format("undefined function: {0}", function); } } @@ -165,7 +164,7 @@ public class EVAL extends LispFunction { @Override public String getMessage() { - return MessageFormat.format("symbol {0} has no value", symbol); + return format("symbol {0} has no value", symbol); } } diff --git a/src/function/builtin/LOAD.java b/src/function/builtin/LOAD.java index 00b648a..a3f5352 100644 --- a/src/function/builtin/LOAD.java +++ b/src/function/builtin/LOAD.java @@ -1,12 +1,12 @@ package function.builtin; import static function.builtin.EVAL.eval; +import static java.text.MessageFormat.format; import static sexpression.Nil.NIL; import static sexpression.Symbol.T; import static util.Path.getPathPrefix; import java.io.*; -import java.text.MessageFormat; import java.util.Stack; import environment.RuntimeEnvironment; @@ -105,7 +105,7 @@ public class LOAD extends LispFunction { @Override public String getMessage() { - return MessageFormat.format("could not load ''{0}''", fileName); + return format("could not load ''{0}''", fileName); } } diff --git a/src/function/builtin/SYMBOL_FUNCTION.java b/src/function/builtin/SYMBOL_FUNCTION.java index ba65670..a106955 100644 --- a/src/function/builtin/SYMBOL_FUNCTION.java +++ b/src/function/builtin/SYMBOL_FUNCTION.java @@ -1,9 +1,8 @@ package function.builtin; +import static java.text.MessageFormat.format; import static table.FunctionTable.lookupFunction; -import java.text.MessageFormat; - import error.LispException; import function.*; import sexpression.*; @@ -38,7 +37,7 @@ public class SYMBOL_FUNCTION extends LispFunction { String typeIndicator = function instanceof LispSpecialFunction ? "SPECIAL-FUNCTION" : "FUNCTION"; - return new Symbol(MessageFormat.format("#<{0} {1}>", typeIndicator, symbol)); + return new Symbol(format("#<{0} {1}>", typeIndicator, symbol)); } public static class UndefinedSymbolFunctionException extends LispException { @@ -52,7 +51,7 @@ public class SYMBOL_FUNCTION extends LispFunction { @Override public String getMessage() { - return MessageFormat.format("SYMBOL-FUNCTION: undefined function: {0}", function); + return format("SYMBOL-FUNCTION: undefined function: {0}", function); } } diff --git a/src/function/builtin/special/Define.java b/src/function/builtin/special/Define.java index 1eb437d..211a822 100644 --- a/src/function/builtin/special/Define.java +++ b/src/function/builtin/special/Define.java @@ -1,10 +1,9 @@ package function.builtin.special; import static function.builtin.cons.LIST.makeList; +import static java.text.MessageFormat.format; import static table.FunctionTable.*; -import java.text.MessageFormat; - import environment.RuntimeEnvironment; import error.LispWarning; import function.*; @@ -67,7 +66,7 @@ public abstract class Define extends LispSpecialFunction { @Override public String getMessage() { - return MessageFormat.format("redefining function {0}", functionName); + return format("redefining function {0}", functionName); } } diff --git a/src/interpreter/InteractiveLispInterpreter.java b/src/interpreter/InteractiveLispInterpreter.java index 78bca67..6ec99d2 100644 --- a/src/interpreter/InteractiveLispInterpreter.java +++ b/src/interpreter/InteractiveLispInterpreter.java @@ -2,7 +2,7 @@ package interpreter; public class InteractiveLispInterpreter extends LispInterpreter { - private static final String PROMPT = "~ "; + public static final String PROMPT = "~ "; @Override protected void prompt() { diff --git a/src/main/LispMain.java b/src/main/LispMain.java index 7f50af1..71d530d 100644 --- a/src/main/LispMain.java +++ b/src/main/LispMain.java @@ -1,5 +1,6 @@ package main; +import static com.googlecode.lanterna.terminal.IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter; import static terminal.LispTerminal.END_OF_SEGMENT; import java.io.*; @@ -8,7 +9,6 @@ import java.util.function.Function; import com.googlecode.lanterna.terminal.*; import interpreter.*; -import stream.SafeOutputStream; import terminal.LispTerminal; public class LispMain { @@ -30,58 +30,76 @@ public class LispMain { lispMain.runWithFile(arguments[0]); } + private LispInterpreterBuilder builder; private PipedInputStream inputReader; - private PipedOutputStream inputWriter; - private PipedInputStream outputReader; private PipedOutputStream outputWriter; - private PrintStream outputStream; - private SafeOutputStream safeOutputWriter; + private PrintStream output; private LispTerminal lispTerminal; - private void runInteractive() { - initializeTerminalAndStreams(); - printGreeting(); - lispTerminal.start(); - buildInteractiveInterpreter().interpret(); - outputStream.close(); + public LispMain() { + this.builder = LispInterpreterBuilderImpl.getInstance(); + + TerminalConfiguration terminalConfiguration = new TerminalConfiguration(); + terminalConfiguration.setInputWriter(new PipedOutputStream()); + terminalConfiguration.setOutputReader(new PipedInputStream()); + terminalConfiguration.setTerminal(createIOSafeTerminal()); + + initializeTerminal(terminalConfiguration); } - private void initializeTerminalAndStreams() { + private IOSafeTerminal createIOSafeTerminal() { + return createRuntimeExceptionConvertingAdapter(createDefaultTerminal()); + } + + private Terminal createDefaultTerminal() { try { - initalizeTerminalAndStreamsWithException(); + return new DefaultTerminalFactory().createTerminal(); } catch (IOException e) { throw new UncheckedIOException(e); } } - private void initalizeTerminalAndStreamsWithException() throws IOException { - inputReader = new PipedInputStream(); - inputWriter = new PipedOutputStream(inputReader); - outputReader = new PipedInputStream(); - outputWriter = new PipedOutputStream(outputReader); - outputStream = new PrintStream(outputWriter); - safeOutputWriter = new SafeOutputStream(outputWriter); - lispTerminal = new LispTerminal(createIOSafeTerminal(), inputWriter, outputReader); + public LispMain(LispInterpreterBuilder builder, TerminalConfiguration configuration) { + this.builder = builder; + initializeTerminal(configuration); } - private IOSafeTerminal createIOSafeTerminal() throws IOException { - Terminal defaultTerminal = new DefaultTerminalFactory().createTerminal(); + private void initializeTerminal(TerminalConfiguration configuration) { + try { + initalizeTerminalWithException(configuration); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } - return IOSafeTerminalAdapter.createRuntimeExceptionConvertingAdapter(defaultTerminal); + private void initalizeTerminalWithException(TerminalConfiguration configuration) throws IOException { + PipedOutputStream inputWriter = configuration.getInputWriter(); + PipedInputStream outputReader = configuration.getOutputReader(); + + inputReader = new PipedInputStream(inputWriter); + outputWriter = new PipedOutputStream(outputReader); + output = new PrintStream(outputWriter); + lispTerminal = new LispTerminal(configuration.getTerminal(), inputWriter, outputReader); + } + + public void runInteractive() { + printGreeting(); + lispTerminal.start(); + buildInteractiveInterpreter().interpret(); + shutdownTerminal(); } private void printGreeting() { - outputStream.println(GREETING); - outputStream.println(); + output.println(GREETING); + output.println(); } private LispInterpreter buildInteractiveInterpreter() { - LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance(); builder.setInput(inputReader, "terminal"); - builder.setOutput(outputStream); - builder.setErrorOutput(outputStream); - builder.setTerminationFunction(this::shutdown); - builder.setErrorTerminationFunction(this::shutdown); + builder.setOutput(output); + builder.setErrorOutput(output); + builder.setTerminationFunction(this::shutdownTerminal); + builder.setErrorTerminationFunction(this::shutdownTerminal); builder.setPromptDecorator(s -> s + END_OF_SEGMENT); builder.setValueOutputDecorator(makeColorDecorator(ANSI_GREEN)); builder.setWarningOutputDecorator(makeColorDecorator(ANSI_YELLOW)); @@ -91,11 +109,10 @@ public class LispMain { return builder.build(); } - private void shutdown() { - outputStream.println(); - outputStream.println(END_OF_SEGMENT); + private void shutdownTerminal() { + output.print(END_OF_SEGMENT); lispTerminal.stop(); - safeOutputWriter.close(); + output.close(); } private Function makeColorDecorator(String color) { @@ -108,12 +125,11 @@ public class LispMain { }; } - private void runWithFile(String fileName) { + public void runWithFile(String fileName) { buildFileInterpreter(fileName).interpret(); } private LispInterpreter buildFileInterpreter(String fileName) { - LispInterpreterBuilder builder = LispInterpreterBuilderImpl.getInstance(); builder.useFile(fileName); builder.setOutput(System.out); builder.setErrorOutput(System.err); @@ -126,4 +142,36 @@ public class LispMain { return builder.build(); } + + public class TerminalConfiguration { + + private PipedOutputStream inputWriter; + private PipedInputStream outputReader; + private IOSafeTerminal terminal; + + public PipedOutputStream getInputWriter() { + return inputWriter; + } + + public void setInputWriter(PipedOutputStream inputWriter) { + this.inputWriter = inputWriter; + } + + public PipedInputStream getOutputReader() { + return outputReader; + } + + public void setOutputReader(PipedInputStream outputReader) { + this.outputReader = outputReader; + } + + public IOSafeTerminal getTerminal() { + return terminal; + } + + public void setTerminal(IOSafeTerminal terminal) { + this.terminal = terminal; + } + } + } diff --git a/src/sexpression/LispNumber.java b/src/sexpression/LispNumber.java index fd511f4..bb00a96 100644 --- a/src/sexpression/LispNumber.java +++ b/src/sexpression/LispNumber.java @@ -1,7 +1,8 @@ package sexpression; +import static java.text.MessageFormat.format; + import java.math.BigInteger; -import java.text.MessageFormat; import error.LispException; @@ -49,7 +50,7 @@ public class LispNumber extends Atom { @Override public String getMessage() { - return MessageFormat.format("{0} is not a valid integer", text); + return format("{0} is not a valid integer", text); } } diff --git a/src/table/FunctionTable.java b/src/table/FunctionTable.java index cedbe62..efabaad 100644 --- a/src/table/FunctionTable.java +++ b/src/table/FunctionTable.java @@ -1,6 +1,7 @@ package table; -import java.text.MessageFormat; +import static java.text.MessageFormat.format; + import java.util.*; import error.CriticalLispException; @@ -141,7 +142,7 @@ public class FunctionTable { @Override public String getMessage() { - return MessageFormat.format("Could not create an instance of ''{0}''", functionName); + return format("Could not create an instance of ''{0}''", functionName); } } diff --git a/src/terminal/ControlSequenceHandler.java b/src/terminal/ControlSequenceHandler.java index b5ff655..06786d4 100644 --- a/src/terminal/ControlSequenceHandler.java +++ b/src/terminal/ControlSequenceHandler.java @@ -1,7 +1,6 @@ package terminal; import static java.lang.Character.isDigit; -import static terminal.ControlSequenceLookup.lookupControlSequence; import static util.Characters.*; import stream.SafeInputStream; @@ -12,14 +11,13 @@ class ControlSequenceHandler { return c == UNICODE_ESCAPE; } + private ControlSequenceLookup controlSequenceLookup; private SafeInputStream input; private String code; private int currentCharacter; public ControlSequenceHandler() { - this.input = null; - this.code = ""; - this.currentCharacter = 0; + this.controlSequenceLookup = new ControlSequenceLookup(); } public ControlSequence parse(SafeInputStream inputStream) { @@ -30,7 +28,7 @@ class ControlSequenceHandler { if (isExpectedFirstCharacter()) readCode(); - return lookupControlSequence((char) currentCharacter, code); + return controlSequenceLookup.get((char) currentCharacter, code); } private void readCharacter() { diff --git a/src/terminal/ControlSequenceLookup.java b/src/terminal/ControlSequenceLookup.java index febb75a..35399bf 100644 --- a/src/terminal/ControlSequenceLookup.java +++ b/src/terminal/ControlSequenceLookup.java @@ -8,9 +8,15 @@ import terminal.ControlSequence.NullControlSequence; public class ControlSequenceLookup { - private static Map> commands = new HashMap<>(); + private Map> commands; - static { + public ControlSequenceLookup() { + this.commands = new HashMap<>(); + + initializeCommands(); + } + + private void initializeCommands() { Map sgrCodes = new HashMap<>(); for (SelectGraphicRendition sgr : SelectGraphicRendition.values()) @@ -19,7 +25,7 @@ public class ControlSequenceLookup { commands.put(SGR_COMMAND, sgrCodes); } - public static ControlSequence lookupControlSequence(char command, String code) { + public ControlSequence get(char command, String code) { Map codes = commands.getOrDefault(command, new HashMap<>()); return codes.getOrDefault(code, new NullControlSequence()); diff --git a/src/terminal/LispTerminal.java b/src/terminal/LispTerminal.java index 94ac42a..1661b50 100644 --- a/src/terminal/LispTerminal.java +++ b/src/terminal/LispTerminal.java @@ -115,13 +115,6 @@ public class LispTerminal { return keyStroke; } - private void doControlC() { - moveCursorToEndOfInput(); - terminal.putCharacter('\n'); - setOriginToCurrentPosition(); - stop(); - } - private synchronized void handleKey(KeyStroke keyStroke) { doKey(keyStroke); terminal.flush(); @@ -142,10 +135,20 @@ public class LispTerminal { } private synchronized void doControlCharacter(KeyStroke keyStroke) { - if (keyStroke.getCharacter() == 'd') + if (keyStroke.getCharacter() == 'c') + doControlC(); + else if (keyStroke.getCharacter() == 'd') doControlD(); } + private void doControlC() { + moveCursorToEndOfInput(); + terminal.putCharacter('\n'); + inputLine = ""; + setOriginToCurrentPosition(); + stop(); + } + private synchronized void doControlD() { doEnter(); stop(); @@ -374,7 +377,7 @@ public class LispTerminal { } private synchronized void putOutputToTerminal() { - SafeInputStream input = new SafeInputStream(new ByteArrayInputStream(outputSegment.getBytes())); + SafeInputStream input = convertOutputToStream(); for (int c = input.read(); c != EOF; c = input.read()) if (isEscape((char) c)) @@ -383,6 +386,10 @@ public class LispTerminal { terminal.putCharacter((char) c); } + private synchronized SafeInputStream convertOutputToStream() { + return new SafeInputStream(new ByteArrayInputStream(outputSegment.getBytes())); + } + private void applyControlSequence(SafeInputStream input) { ControlSequence controlSequence = controlSequenceHandler.parse(input); controlSequence.applyTo(terminal); diff --git a/src/token/TokenFactory.java b/src/token/TokenFactory.java index 45003b2..1347f24 100644 --- a/src/token/TokenFactory.java +++ b/src/token/TokenFactory.java @@ -1,6 +1,6 @@ package token; -import java.text.MessageFormat; +import static java.text.MessageFormat.format; import error.*; import file.FilePosition; @@ -37,7 +37,7 @@ public interface TokenFactory { @Override public String getMessagePrefix() { - return MessageFormat.format("illegal character >>{0}<<", text); + return format("illegal character >>{0}<<", text); } } diff --git a/test/error/ErrorManagerTest.java b/test/error/ErrorManagerTest.java index 10f70c4..c0bf58c 100644 --- a/test/error/ErrorManagerTest.java +++ b/test/error/ErrorManagerTest.java @@ -152,10 +152,9 @@ public class ErrorManagerTest { } @Test - public void severityCoverage() { - Severity.valueOf(WARNING.toString()); - Severity.valueOf(ERROR.toString()); - Severity.valueOf(CRITICAL.toString()); + public void severityEnumCoverage() { + for (Severity severity : Severity.values()) + Severity.valueOf(severity.toString()); } } diff --git a/test/function/builtin/PRINTTest.java b/test/function/builtin/PRINTTest.java index b4df62b..edf2564 100644 --- a/test/function/builtin/PRINTTest.java +++ b/test/function/builtin/PRINTTest.java @@ -1,10 +1,10 @@ package function.builtin; +import static java.text.MessageFormat.format; import static org.junit.Assert.assertEquals; import static testutil.TestUtilities.evaluateString; import java.io.*; -import java.text.MessageFormat; import org.junit.*; @@ -40,16 +40,16 @@ public class PRINTTest { public void printStringWorks() { String output = "\"Hello, world!\""; - evaluateString(MessageFormat.format("(print {0})", output)); - assertPrinted(MessageFormat.format("{0}\n", output)); + evaluateString(format("(print {0})", output)); + assertPrinted(format("{0}\n", output)); } @Test public void printSymbolWorks() { String output = "A"; - evaluateString(MessageFormat.format("(print ''{0})", output)); - assertPrinted(MessageFormat.format("{0}\n", output)); + evaluateString(format("(print ''{0})", output)); + assertPrinted(format("{0}\n", output)); } @Test(expected = TooManyArgumentsException.class) diff --git a/test/interpreter/LispInterpreterBuilderTest.java b/test/interpreter/LispInterpreterTest.java similarity index 78% rename from test/interpreter/LispInterpreterBuilderTest.java rename to test/interpreter/LispInterpreterTest.java index f3ea940..60eb3bd 100644 --- a/test/interpreter/LispInterpreterBuilderTest.java +++ b/test/interpreter/LispInterpreterTest.java @@ -1,7 +1,10 @@ package interpreter; import static error.ErrorManager.Severity.CRITICAL; +import static interpreter.InteractiveLispInterpreter.PROMPT; +import static java.text.MessageFormat.format; import static org.junit.Assert.*; +import static testutil.TestUtilities.createInputStreamFromString; import java.io.*; import java.util.*; @@ -11,7 +14,7 @@ import org.junit.*; import environment.RuntimeEnvironment; import interpreter.LispInterpreterBuilderImpl.InterpreterAlreadyBuiltException; -public class LispInterpreterBuilderTest { +public class LispInterpreterTest { private static final String TERMINATED = "terminated"; @@ -21,7 +24,7 @@ public class LispInterpreterBuilderTest { private RuntimeEnvironment environment; private LispInterpreterBuilder builder; - public LispInterpreterBuilderTest() { + public LispInterpreterTest() { this.environment = RuntimeEnvironment.getInstance(); this.builder = new LispInterpreterBuilderImpl() { @@ -68,6 +71,7 @@ public class LispInterpreterBuilderTest { setCommonFeatures(); builder.setInput(System.in, "stdin"); LispInterpreter interpreter = builder.build(); + assertTrue(interpreter instanceof InteractiveLispInterpreter); } @@ -77,6 +81,7 @@ public class LispInterpreterBuilderTest { builder.setInput(System.in, "stdin"); builder.setNotInteractive(); LispInterpreter interpreter = builder.build(); + assertFalse(interpreter instanceof InteractiveLispInterpreter); assertFalse(interpreter instanceof FileLispInterpreter); } @@ -86,6 +91,7 @@ public class LispInterpreterBuilderTest { setCommonFeatures(); builder.useFile("test/interpreter/test-files/file.lisp"); LispInterpreter interpreter = builder.build(); + assertTrue(interpreter instanceof FileLispInterpreter); } @@ -98,6 +104,7 @@ public class LispInterpreterBuilderTest { @Test public void interpreterAlreadyBuiltException_HasCorrectAttributes() { InterpreterAlreadyBuiltException e = new InterpreterAlreadyBuiltException(); + assertEquals(CRITICAL, e.getSeverity()); assertNotNull(e.getMessage()); assertTrue(e.getMessage().length() > 0); @@ -117,6 +124,7 @@ public class LispInterpreterBuilderTest { setCommonFeatures(); builder.useFile("test/interpreter/test-files/does-not-exist.lisp"); builder.build(); + assertErrorMessageWritten(); assertTerminated(); } @@ -124,6 +132,7 @@ public class LispInterpreterBuilderTest { @Test public void makeSureDecoratorsAreInitializedWithDefaults() { builder.build(); + assertEquals("", environment.decoratePrompt("")); assertEquals("", environment.decorateValueOutput("")); assertEquals("", environment.decorateWarningOutput("")); @@ -139,6 +148,7 @@ public class LispInterpreterBuilderTest { builder.setErrorOutputDecorator(s -> "*" + s + "*"); builder.setCriticalOutputDecorator(s -> "$" + s + "$"); builder.build(); + assertEquals("#x#", environment.decoratePrompt("x")); assertEquals("@x@", environment.decorateValueOutput("x")); assertEquals("%x%", environment.decorateWarningOutput("x")); @@ -146,4 +156,35 @@ public class LispInterpreterBuilderTest { assertEquals("$x$", environment.decorateCriticalOutput("x")); } + @Test + public void fileBasedInterpreterWorks() { + setCommonFeatures(); + builder.useFile("test/interpreter/test-files/file.lisp"); + builder.build().interpret(); + + assertEquals("PICKLE\n\n", outputStream.toString()); + assertEquals("", errorOutputStream.toString()); + } + + @Test + public void interactiveInterpreterWorks() { + setCommonFeatures(); + builder.setInput(createInputStreamFromString("'pickle"), "input"); + builder.build().interpret(); + + assertEquals(format("{0}\n{1}\n{0}\n\n", PROMPT, "PICKLE"), outputStream.toString()); + assertEquals("", errorOutputStream.toString()); + } + + @Test + public void interpreterHandlesError() { + setCommonFeatures(); + builder.setNotInteractive(); + builder.setInput(createInputStreamFromString("pickle"), "input"); + builder.build().interpret(); + + assertEquals("\n", outputStream.toString()); + assertEquals("[error] symbol PICKLE has no value\n", errorOutputStream.toString()); + } + } diff --git a/test/interpreter/test-files/file.lisp b/test/interpreter/test-files/file.lisp index e69de29..9ef47de 100644 --- a/test/interpreter/test-files/file.lisp +++ b/test/interpreter/test-files/file.lisp @@ -0,0 +1 @@ +'pickle \ No newline at end of file diff --git a/test/stream/SafeInputStreamTest.java b/test/stream/SafeInputStreamTest.java new file mode 100644 index 0000000..ce775e1 --- /dev/null +++ b/test/stream/SafeInputStreamTest.java @@ -0,0 +1,43 @@ +package stream; + +import static org.junit.Assert.assertEquals; +import static testutil.TestUtilities.*; + +import org.junit.*; + +public class SafeInputStreamTest { + + SafeInputStream safe; + SafeInputStream safeWithException; + + @Before + public void setUp() throws Exception { + safe = new SafeInputStream(createInputStreamFromString("a")); + safeWithException = new SafeInputStream(createIOExceptionThrowingInputStream()); + } + + @After + public void tearDown() throws Exception {} + + @Test + public void readWorks() { + assertEquals('a', (char) safe.read()); + assertEquals(-1, safe.read()); + } + + @Test + public void closeWorks() { + safe.close(); + } + + @Test(expected = UncheckedIOException.class) + public void readThrowsUncheckedException() { + safeWithException.read(); + } + + @Test(expected = UncheckedIOException.class) + public void closeThrowsUncheckedException() { + safeWithException.close(); + } + +} diff --git a/test/stream/SafeOutputStreamTest.java b/test/stream/SafeOutputStreamTest.java new file mode 100644 index 0000000..dcec94f --- /dev/null +++ b/test/stream/SafeOutputStreamTest.java @@ -0,0 +1,54 @@ +package stream; + +import static org.junit.Assert.assertEquals; +import static testutil.TestUtilities.createIOExceptionThrowingOutputStream; + +import java.io.ByteArrayOutputStream; + +import org.junit.*; + +public class SafeOutputStreamTest { + + SafeOutputStream safe; + SafeOutputStream safeWithException; + ByteArrayOutputStream output; + + @Before + public void setUp() throws Exception { + output = new ByteArrayOutputStream(); + safe = new SafeOutputStream(output); + safeWithException = new SafeOutputStream(createIOExceptionThrowingOutputStream()); + } + + @Test + public void writeWorks() { + safe.write("write".getBytes()); + assertEquals("write", output.toString()); + } + + @Test + public void flushWorks() { + safe.flush(); + } + + @Test + public void closeWorks() { + safe.close(); + } + + @Test(expected = UncheckedIOException.class) + public void writeThrowsUncheckedException() { + safeWithException.write("write".getBytes()); + } + + @Test(expected = UncheckedIOException.class) + public void flushThrowsUncheckedException() { + safeWithException.flush(); + } + + @Test(expected = UncheckedIOException.class) + public void closeThrowsUncheckedException() { + safeWithException.close(); + } + +} diff --git a/test/terminal/ControlSequenceTest.java b/test/terminal/ControlSequenceTest.java index a6dbe2d..608e6ea 100644 --- a/test/terminal/ControlSequenceTest.java +++ b/test/terminal/ControlSequenceTest.java @@ -1,6 +1,6 @@ package terminal; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static terminal.SelectGraphicRendition.*; import java.util.*; @@ -79,4 +79,16 @@ public class ControlSequenceTest { assertTrue(indicatorSet.contains("MAGENTA")); } + @Test + public void nullControlSequenceHasCorrectCode() { + ControlSequence nullControlSequence = new NullControlSequence(); + assertEquals("", nullControlSequence.getCode()); + } + + @Test + public void SelectGraphicRenditionEnumCoverage() { + for (SelectGraphicRendition sgr : SelectGraphicRendition.values()) + SelectGraphicRendition.valueOf(sgr.toString()); + } + } diff --git a/test/terminal/LispTerminalTest.java b/test/terminal/LispTerminalTest.java index 1cb05bc..938888d 100644 --- a/test/terminal/LispTerminalTest.java +++ b/test/terminal/LispTerminalTest.java @@ -347,6 +347,17 @@ public class LispTerminalTest { assertInputWritten("control-d\n"); } + @Test + public void controlCWorks() { + enterCharacters("ctrl-c"); + enterControlCharacter('c'); + produceOutput(""); + assertInputStreamClosed(); + assertInputWritten(""); + assertCharacterPositions(new char[][] { { 'c', 't', 'r', 'l', '-', 'c', ' ', ' ', ' ' }, + { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } }); + } + @Test public void controlDWorksInMiddleOfInput() { enterCharacters("control-d"); diff --git a/test/testutil/TestUtilities.java b/test/testutil/TestUtilities.java index b1e22fc..65b9971 100644 --- a/test/testutil/TestUtilities.java +++ b/test/testutil/TestUtilities.java @@ -23,7 +23,37 @@ public final class TestUtilities { @Override public int read() throws IOException { - throw new IOException("test IOException"); + throw new IOException("read()"); + } + + @Override + public void close() throws IOException { + throw new IOException("close()"); + } + }; + } + + public static OutputStream createIOExceptionThrowingOutputStream() { + return new OutputStream() { + + @Override + public void write(byte[] b) throws IOException { + throw new IOException("write(byte[])"); + } + + @Override + public void flush() throws IOException { + throw new IOException("flush()"); + } + + @Override + public void close() throws IOException { + throw new IOException("close()"); + } + + @Override + public void write(int arg0) throws IOException { + throw new IOException("write(int)"); } }; }