From 228b4b1793697c01bd73cb177f568d6488212e0c Mon Sep 17 00:00:00 2001 From: Mike Cifelli Date: Wed, 22 Mar 2017 14:08:22 -0400 Subject: [PATCH] Refactor stream code and add unit tests --- src/main/LispMain.java | 31 ++++--- .../LispCommentRemovingInputStream.java | 23 ++--- src/scanner/LispInputStream.java | 17 ---- src/stream/SafeInputStream.java | 29 +++++++ src/stream/SafeOutputStream.java | 38 ++++++++ src/stream/UncheckedIOException.java | 21 +++++ src/terminal/ControlSequenceHandler.java | 2 +- src/terminal/LispTerminal.java | 2 +- src/terminal/SafeStream.java | 81 ----------------- .../LispInterpreterBuilderTest.java | 86 ++++++++++++++++--- test/parser/LispParserTest.java | 2 +- .../LispCommentRemovingInputStreamTest.java | 22 ++--- test/terminal/ControlSequenceHandlerTest.java | 2 +- 13 files changed, 193 insertions(+), 163 deletions(-) create mode 100644 src/stream/SafeInputStream.java create mode 100644 src/stream/SafeOutputStream.java create mode 100644 src/stream/UncheckedIOException.java delete mode 100644 src/terminal/SafeStream.java diff --git a/src/main/LispMain.java b/src/main/LispMain.java index b03f644..7f50af1 100644 --- a/src/main/LispMain.java +++ b/src/main/LispMain.java @@ -8,26 +8,26 @@ import java.util.function.Function; import com.googlecode.lanterna.terminal.*; import interpreter.*; +import stream.SafeOutputStream; import terminal.LispTerminal; -import terminal.SafeStream.SafeOutputStream; -import terminal.SafeStream.UncheckedIOException; public class LispMain { private static final String GREETING = "Transcendental Lisp - Version 1.0.1"; + private static final String ANSI_RESET = "\u001B[0m"; private static final String ANSI_RED = "\u001B[31m"; private static final String ANSI_GREEN = "\u001B[32m"; private static final String ANSI_YELLOW = "\u001B[33m"; private static final String ANSI_PURPLE = "\u001B[35m"; - public static void main(String[] args) throws IOException { + public static void main(String[] arguments) { LispMain lispMain = new LispMain(); - if (args.length == 0) + if (arguments.length == 0) lispMain.runInteractive(); else - lispMain.runWithFile(args[0]); + lispMain.runWithFile(arguments[0]); } private PipedInputStream inputReader; @@ -98,6 +98,16 @@ public class LispMain { safeOutputWriter.close(); } + private Function makeColorDecorator(String color) { + return new Function() { + + @Override + public String apply(String s) { + return color + s + ANSI_RESET; + } + }; + } + private void runWithFile(String fileName) { buildFileInterpreter(fileName).interpret(); } @@ -116,15 +126,4 @@ public class LispMain { return builder.build(); } - - private static Function makeColorDecorator(String color) { - return new Function() { - - @Override - public String apply(String s) { - return color + s + ANSI_RESET; - } - }; - } - } diff --git a/src/scanner/LispCommentRemovingInputStream.java b/src/scanner/LispCommentRemovingInputStream.java index 6340e72..1684cfa 100644 --- a/src/scanner/LispCommentRemovingInputStream.java +++ b/src/scanner/LispCommentRemovingInputStream.java @@ -2,21 +2,23 @@ package scanner; import static util.Characters.*; -import java.io.*; +import java.io.InputStream; + +import stream.SafeInputStream; /** * Removes Lisp comments from an input stream. */ public class LispCommentRemovingInputStream implements LispInputStream { - private InputStream underlyingInputStream; + private SafeInputStream underlyingInputStream; private boolean isInQuotedString; private boolean rereadLastCharacter; private int previousCharacter; private int currentCharacter; public LispCommentRemovingInputStream(InputStream underlyingInputStream) { - this.underlyingInputStream = underlyingInputStream; + this.underlyingInputStream = new SafeInputStream(underlyingInputStream); this.isInQuotedString = false; this.rereadLastCharacter = false; this.previousCharacter = 0; @@ -25,14 +27,6 @@ public class LispCommentRemovingInputStream implements LispInputStream { @Override public int read() { - try { - return readWithIOException(); - } catch (IOException ioException) { - throw new UncheckedIOException(ioException); - } - } - - private int readWithIOException() throws IOException { if (!rereadLastCharacter) return readFromUnderlyingInputStream(); @@ -41,17 +35,16 @@ public class LispCommentRemovingInputStream implements LispInputStream { return currentCharacter; } - private int readFromUnderlyingInputStream() throws IOException { + private int readFromUnderlyingInputStream() { readNextCharacter(); if (haveEnteredComment()) consumeAllBytesInComment(); return currentCharacter; - } - private void readNextCharacter() throws IOException { + private void readNextCharacter() { previousCharacter = currentCharacter; currentCharacter = underlyingInputStream.read(); @@ -67,7 +60,7 @@ public class LispCommentRemovingInputStream implements LispInputStream { return (currentCharacter == SEMICOLON) && (!isInQuotedString); } - private void consumeAllBytesInComment() throws IOException { + private void consumeAllBytesInComment() { while (stillInComment()) currentCharacter = underlyingInputStream.read(); } diff --git a/src/scanner/LispInputStream.java b/src/scanner/LispInputStream.java index 90222e9..014afd0 100644 --- a/src/scanner/LispInputStream.java +++ b/src/scanner/LispInputStream.java @@ -1,7 +1,5 @@ package scanner; -import java.io.IOException; - import error.CriticalLispException; public interface LispInputStream { @@ -15,19 +13,4 @@ public interface LispInputStream { private static final long serialVersionUID = 1L; } - public static class UncheckedIOException extends CriticalLispException { - - private static final long serialVersionUID = 1L; - private IOException ioException; - - public UncheckedIOException(IOException ioException) { - this.ioException = ioException; - } - - @Override - public String getMessage() { - return ioException.getMessage(); - } - } - } diff --git a/src/stream/SafeInputStream.java b/src/stream/SafeInputStream.java new file mode 100644 index 0000000..55953c1 --- /dev/null +++ b/src/stream/SafeInputStream.java @@ -0,0 +1,29 @@ +package stream; + +import java.io.*; + +public class SafeInputStream { + + private InputStream underlyingStream; + + public SafeInputStream(InputStream underlyingStream) { + this.underlyingStream = underlyingStream; + } + + public int read() { + try { + return underlyingStream.read(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void close() { + try { + underlyingStream.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} \ No newline at end of file diff --git a/src/stream/SafeOutputStream.java b/src/stream/SafeOutputStream.java new file mode 100644 index 0000000..c40e3c7 --- /dev/null +++ b/src/stream/SafeOutputStream.java @@ -0,0 +1,38 @@ +package stream; + +import java.io.*; + +public class SafeOutputStream { + + private OutputStream underlyingStream; + + public SafeOutputStream(OutputStream underlyingStream) { + this.underlyingStream = underlyingStream; + } + + public void write(byte[] b) { + try { + underlyingStream.write(b); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void flush() { + + try { + underlyingStream.flush(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void close() { + try { + underlyingStream.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} \ No newline at end of file diff --git a/src/stream/UncheckedIOException.java b/src/stream/UncheckedIOException.java new file mode 100644 index 0000000..de93070 --- /dev/null +++ b/src/stream/UncheckedIOException.java @@ -0,0 +1,21 @@ +package stream; + +import java.io.IOException; + +import error.CriticalLispException; + +public class UncheckedIOException extends CriticalLispException { + + private static final long serialVersionUID = 1L; + private IOException exception; + + public UncheckedIOException(IOException exception) { + this.exception = exception; + } + + @Override + public String getMessage() { + return exception.getMessage(); + } + +} \ No newline at end of file diff --git a/src/terminal/ControlSequenceHandler.java b/src/terminal/ControlSequenceHandler.java index 74d2f33..8fd5aea 100644 --- a/src/terminal/ControlSequenceHandler.java +++ b/src/terminal/ControlSequenceHandler.java @@ -4,7 +4,7 @@ import static java.lang.Character.isDigit; import static terminal.ControlSequenceLookup.lookupControlSequence; import static util.Characters.*; -import terminal.SafeStream.SafeInputStream; +import stream.SafeInputStream; class ControlSequenceHandler { diff --git a/src/terminal/LispTerminal.java b/src/terminal/LispTerminal.java index 5b95fa4..ca195ff 100644 --- a/src/terminal/LispTerminal.java +++ b/src/terminal/LispTerminal.java @@ -11,7 +11,7 @@ import com.googlecode.lanterna.*; import com.googlecode.lanterna.input.*; import com.googlecode.lanterna.terminal.IOSafeTerminal; -import terminal.SafeStream.*; +import stream.*; public class LispTerminal { diff --git a/src/terminal/SafeStream.java b/src/terminal/SafeStream.java deleted file mode 100644 index 3fb05b9..0000000 --- a/src/terminal/SafeStream.java +++ /dev/null @@ -1,81 +0,0 @@ -package terminal; - -import java.io.*; - -public interface SafeStream { - - public static class SafeInputStream { - - private InputStream underlyingStream; - - public SafeInputStream(InputStream underlyingStream) { - this.underlyingStream = underlyingStream; - } - - public int read() { - try { - return underlyingStream.read(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void close() { - try { - underlyingStream.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - - public static class SafeOutputStream { - - private OutputStream underlyingStream; - - public SafeOutputStream(OutputStream underlyingStream) { - this.underlyingStream = underlyingStream; - } - - public void write(byte[] b) { - try { - underlyingStream.write(b); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void flush() { - - try { - underlyingStream.flush(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public void close() { - try { - underlyingStream.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - - public static class UncheckedIOException extends RuntimeException { - - private static final long serialVersionUID = 1L; - private IOException exception; - - public UncheckedIOException(IOException exception) { - this.exception = exception; - } - - @Override - public String getMessage() { - return exception.getMessage(); - } - } - -} diff --git a/test/interpreter/LispInterpreterBuilderTest.java b/test/interpreter/LispInterpreterBuilderTest.java index f896cea..f3ea940 100644 --- a/test/interpreter/LispInterpreterBuilderTest.java +++ b/test/interpreter/LispInterpreterBuilderTest.java @@ -4,36 +4,63 @@ import static error.ErrorManager.Severity.CRITICAL; import static org.junit.Assert.*; import java.io.*; +import java.util.*; import org.junit.*; +import environment.RuntimeEnvironment; import interpreter.LispInterpreterBuilderImpl.InterpreterAlreadyBuiltException; public class LispInterpreterBuilderTest { - private LispInterpreterBuilder builder = new LispInterpreterBuilderImpl() { + private static final String TERMINATED = "terminated"; - @Override - public void reset() { - this.isBuilt = false; - } - }; + private Set indicatorSet; + private ByteArrayOutputStream outputStream; + private ByteArrayOutputStream errorOutputStream; + private RuntimeEnvironment environment; + private LispInterpreterBuilder builder; + + public LispInterpreterBuilderTest() { + this.environment = RuntimeEnvironment.getInstance(); + + this.builder = new LispInterpreterBuilderImpl() { + + @Override + public void reset() { + this.isBuilt = false; + } + }; + } private void setCommonFeatures() { - builder.setOutput(new PrintStream(new ByteArrayOutputStream())); - builder.setErrorOutput(new PrintStream(new ByteArrayOutputStream())); + builder.setOutput(new PrintStream(outputStream)); + builder.setErrorOutput(new PrintStream(errorOutputStream)); builder.setTerminationFunction(() -> {}); - builder.setErrorTerminationFunction(() -> {}); + builder.setErrorTerminationFunction(() -> indicatorSet.add(TERMINATED)); + } + + private void assertTerminated() { + assertTrue(indicatorSet.contains(TERMINATED)); + } + + private void assertErrorMessageWritten() { + assertTrue(errorOutputStream.toByteArray().length > 0); } @Before public void setUp() throws Exception { + indicatorSet = new HashSet<>(); + outputStream = new ByteArrayOutputStream(); + errorOutputStream = new ByteArrayOutputStream(); builder.reset(); + environment.reset(); } @After public void tearDown() throws Exception { builder.reset(); + environment.reset(); } @Test @@ -41,7 +68,6 @@ public class LispInterpreterBuilderTest { setCommonFeatures(); builder.setInput(System.in, "stdin"); LispInterpreter interpreter = builder.build(); - assertTrue(interpreter instanceof InteractiveLispInterpreter); } @@ -51,8 +77,8 @@ public class LispInterpreterBuilderTest { builder.setInput(System.in, "stdin"); builder.setNotInteractive(); LispInterpreter interpreter = builder.build(); - assertFalse(interpreter instanceof InteractiveLispInterpreter); + assertFalse(interpreter instanceof FileLispInterpreter); } @Test @@ -60,8 +86,7 @@ public class LispInterpreterBuilderTest { setCommonFeatures(); builder.useFile("test/interpreter/test-files/file.lisp"); LispInterpreter interpreter = builder.build(); - - assertFalse(interpreter instanceof InteractiveLispInterpreter); + assertTrue(interpreter instanceof FileLispInterpreter); } @Test(expected = InterpreterAlreadyBuiltException.class) @@ -73,7 +98,6 @@ public class LispInterpreterBuilderTest { @Test public void interpreterAlreadyBuiltException_HasCorrectAttributes() { InterpreterAlreadyBuiltException e = new InterpreterAlreadyBuiltException(); - assertEquals(CRITICAL, e.getSeverity()); assertNotNull(e.getMessage()); assertTrue(e.getMessage().length() > 0); @@ -88,4 +112,38 @@ public class LispInterpreterBuilderTest { builder.build(); } + @Test + public void attemptToBuildInterpreterOnBadFile() { + setCommonFeatures(); + builder.useFile("test/interpreter/test-files/does-not-exist.lisp"); + builder.build(); + assertErrorMessageWritten(); + assertTerminated(); + } + + @Test + public void makeSureDecoratorsAreInitializedWithDefaults() { + builder.build(); + assertEquals("", environment.decoratePrompt("")); + assertEquals("", environment.decorateValueOutput("")); + assertEquals("", environment.decorateWarningOutput("")); + assertEquals("", environment.decorateErrorOutput("")); + assertEquals("", environment.decorateCriticalOutput("")); + } + + @Test + public void makeSureDecoratorsAreSetCorrectly() { + builder.setPromptDecorator(s -> "#" + s + "#"); + builder.setValueOutputDecorator(s -> "@" + s + "@"); + builder.setWarningOutputDecorator(s -> "%" + s + "%"); + 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")); + assertEquals("*x*", environment.decorateErrorOutput("x")); + assertEquals("$x$", environment.decorateCriticalOutput("x")); + } + } diff --git a/test/parser/LispParserTest.java b/test/parser/LispParserTest.java index 3b93b07..0c53a93 100644 --- a/test/parser/LispParserTest.java +++ b/test/parser/LispParserTest.java @@ -10,8 +10,8 @@ import java.io.InputStream; import org.junit.Test; import error.LispException; -import scanner.LispInputStream.UncheckedIOException; import scanner.LispScanner.UnterminatedStringException; +import stream.UncheckedIOException; import token.Eof.EofEncounteredException; import token.RightParenthesis.StartsWithRightParenthesisException; import token.TokenFactory.BadCharacterException; diff --git a/test/scanner/LispCommentRemovingInputStreamTest.java b/test/scanner/LispCommentRemovingInputStreamTest.java index 249107a..ad3d308 100644 --- a/test/scanner/LispCommentRemovingInputStreamTest.java +++ b/test/scanner/LispCommentRemovingInputStreamTest.java @@ -8,7 +8,8 @@ import java.io.InputStream; import org.junit.Test; -import scanner.LispInputStream.*; +import scanner.LispInputStream.MaximumUnreadsExceededException; +import stream.UncheckedIOException; public class LispCommentRemovingInputStreamTest { @@ -177,7 +178,7 @@ public class LispCommentRemovingInputStreamTest { lispInputStream.unreadLastCharacter(); } - @Test() + @Test public void callUnreadMultipleTimes_ExceptionHasCorrectSeverity() { String input = "abc"; LispInputStream lispInputStream = createLispInputStream(input); @@ -200,20 +201,8 @@ public class LispCommentRemovingInputStreamTest { lispInputStream.read(); } - @Test() - public void underlyingInputStreamThrowsIOException_ExceptionHasCorrectSeverity() { - InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream(); - LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream); - - try { - lispInputStream.read(); - } catch (UncheckedIOException e) { - assertEquals(CRITICAL, e.getSeverity()); - } - } - - @Test() - public void underlyingInputStreamThrowsIOException_ExceptionHasErrorMessage() { + @Test + public void underlyingInputStreamThrowsIOException_ExceptionHasCorrectAttributes() { InputStream ioExceptionThrowingInputStream = createIOExceptionThrowingInputStream(); LispInputStream lispInputStream = new LispCommentRemovingInputStream(ioExceptionThrowingInputStream); @@ -223,6 +212,7 @@ public class LispCommentRemovingInputStreamTest { String message = e.getMessage(); assertNotNull(message); assertTrue(message.length() >= 0); + assertEquals(CRITICAL, e.getSeverity()); } } diff --git a/test/terminal/ControlSequenceHandlerTest.java b/test/terminal/ControlSequenceHandlerTest.java index 7c14f43..b429160 100644 --- a/test/terminal/ControlSequenceHandlerTest.java +++ b/test/terminal/ControlSequenceHandlerTest.java @@ -6,8 +6,8 @@ import static terminal.SelectGraphicRendition.*; import org.junit.*; +import stream.SafeInputStream; import terminal.ControlSequence.NullControlSequence; -import terminal.SafeStream.SafeInputStream; import testutil.TestUtilities; import util.Characters;