diff --git a/pom.xml b/pom.xml index 81c10f2..34b9a8a 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,12 @@ lanterna 3.0.0-rc1 + + com.github.stefanbirkner + system-rules + 1.16.1 + test + UTF-8 diff --git a/src/main/LispMain.java b/src/main/LispMain.java index 71d530d..8063b33 100644 --- a/src/main/LispMain.java +++ b/src/main/LispMain.java @@ -9,17 +9,18 @@ import java.util.function.Function; import com.googlecode.lanterna.terminal.*; import interpreter.*; -import terminal.LispTerminal; +import stream.UncheckedIOException; +import terminal.*; 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 final String ANSI_RESET = "\u001B[0m"; + public static final String ANSI_RED = "\u001B[31m"; + public static final String ANSI_GREEN = "\u001B[32m"; + public static final String ANSI_YELLOW = "\u001B[33m"; + public static final String ANSI_PURPLE = "\u001B[35m"; public static void main(String[] arguments) { LispMain lispMain = new LispMain(); @@ -143,35 +144,4 @@ 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/terminal/TerminalConfiguration.java b/src/terminal/TerminalConfiguration.java new file mode 100644 index 0000000..944d074 --- /dev/null +++ b/src/terminal/TerminalConfiguration.java @@ -0,0 +1,55 @@ +package terminal; + +import java.io.*; + +import com.googlecode.lanterna.terminal.IOSafeTerminal; + +public class TerminalConfiguration { + + private PipedOutputStream inputWriter; + private PipedInputStream inputReader; + private PipedOutputStream outputWriter; + private PipedInputStream outputReader; + private IOSafeTerminal terminal; + + public void setInputWriter(PipedOutputStream inputWriter) { + this.inputWriter = inputWriter; + } + + public void setInputReader(PipedInputStream inputReader) { + this.inputReader = inputReader; + } + + public void setOutputWriter(PipedOutputStream outputWriter) { + this.outputWriter = outputWriter; + } + + public void setOutputReader(PipedInputStream outputReader) { + this.outputReader = outputReader; + } + + public void setTerminal(IOSafeTerminal terminal) { + this.terminal = terminal; + } + + public PipedOutputStream getInputWriter() { + return inputWriter; + } + + public PipedInputStream getInputReader() { + return inputReader; + } + + public PipedOutputStream getOutputWriter() { + return outputWriter; + } + + public PipedInputStream getOutputReader() { + return outputReader; + } + + public IOSafeTerminal getTerminal() { + return terminal; + } + +} \ No newline at end of file diff --git a/test/interpreter/LispInterpreterTest.java b/test/interpreter/LispInterpreterTest.java index 60eb3bd..ced41f2 100644 --- a/test/interpreter/LispInterpreterTest.java +++ b/test/interpreter/LispInterpreterTest.java @@ -157,7 +157,7 @@ public class LispInterpreterTest { } @Test - public void fileBasedInterpreterWorks() { + public void fileBasedInterpreterWorks_PrintsLastValueOnly() { setCommonFeatures(); builder.useFile("test/interpreter/test-files/file.lisp"); builder.build().interpret(); diff --git a/test/interpreter/test-files/file.lisp b/test/interpreter/test-files/file.lisp index 9ef47de..9734d52 100644 --- a/test/interpreter/test-files/file.lisp +++ b/test/interpreter/test-files/file.lisp @@ -1 +1,2 @@ +'onion 'pickle \ No newline at end of file diff --git a/test/main/MainTest.java b/test/main/MainTest.java index 1647c34..1c38241 100644 --- a/test/main/MainTest.java +++ b/test/main/MainTest.java @@ -1,13 +1,92 @@ package main; +import static java.text.MessageFormat.format; +import static main.LispMain.*; +import static org.junit.Assert.assertEquals; + +import java.io.*; + import org.junit.*; +import org.junit.contrib.java.lang.system.*; + +import com.googlecode.lanterna.terminal.virtual.*; + +import environment.RuntimeEnvironment; +import interpreter.LispInterpreterBuilderImpl; +import terminal.*; public class MainTest { + LispMain main; + PipedOutputStream inputWriter; + PipedInputStream outputReader; + VirtualTerminal terminal; + RuntimeEnvironment environment; + + public MainTest() { + this.environment = RuntimeEnvironment.getInstance(); + } + + @Rule + public ExpectedSystemExit exit = ExpectedSystemExit.none(); + + @Rule + public SystemErrRule systemErrRule = new SystemErrRule().enableLog().mute(); + + @Rule + public SystemOutRule systemOutRule = new SystemOutRule().enableLog().mute(); + @Before - public void setUp() throws Exception {} + public void setUp() throws Exception { + environment.reset(); + TerminalConfiguration configuration = new TerminalConfiguration(); + inputWriter = new PipedOutputStream(); + outputReader = new PipedInputStream(); + terminal = new DefaultVirtualTerminal(); + configuration.setInputWriter(inputWriter); + configuration.setOutputReader(outputReader); + configuration.setTerminal(terminal); + main = new LispMain(new LispInterpreterBuilderImpl() {}, configuration); + } @After - public void tearDown() throws Exception {} + public void tearDown() throws Exception { + terminal.close(); + environment.reset(); + } + + @Test + public void runWithBadFile() { + String expectedMessage = "[critical] test/main/test-files/bad.lisp (No such file or directory)"; + + exit.expectSystemExitWithStatus(1); + exit.checkAssertionAfterwards(() -> { + assertEquals(format("{0}{1}{2}\n", ANSI_PURPLE, expectedMessage, ANSI_RESET), + systemErrRule.getLogWithNormalizedLineSeparator()); + assertEquals("", systemOutRule.getLogWithNormalizedLineSeparator()); + + }); + + main.runWithFile("test/main/test-files/bad.lisp"); + + } + + @Test + public void runWithFile_PrintsDecoratedLastValueOnly() { + main.runWithFile("test/main/test-files/file.lisp"); + assertEquals("", systemErrRule.getLogWithNormalizedLineSeparator()); + assertEquals(format("{0}{1}{2}\n\n", ANSI_GREEN, "RADISH", ANSI_RESET), + systemOutRule.getLogWithNormalizedLineSeparator()); + } + + @Test + public void runInteractive() { + VirtualTerminalInteractor t = new VirtualTerminalInteractor(); + try { + t.start(); + } finally { + t.stop(); + } + } } diff --git a/test/main/test-files/file.lisp b/test/main/test-files/file.lisp new file mode 100644 index 0000000..2c7d117 --- /dev/null +++ b/test/main/test-files/file.lisp @@ -0,0 +1,2 @@ +'pickle +'radish \ No newline at end of file diff --git a/test/terminal/LispTerminalTest.java b/test/terminal/LispTerminalTest.java index 938888d..29a5077 100644 --- a/test/terminal/LispTerminalTest.java +++ b/test/terminal/LispTerminalTest.java @@ -1,562 +1,437 @@ package terminal; import static com.googlecode.lanterna.input.KeyType.*; -import static org.junit.Assert.*; import static terminal.LispTerminal.END_OF_SEGMENT; -import java.io.*; - import org.junit.*; -import com.googlecode.lanterna.*; -import com.googlecode.lanterna.input.*; -import com.googlecode.lanterna.terminal.virtual.*; - public class LispTerminalTest { - private PipedInputStream inputReader; - private PipedOutputStream inputWriter; - private PipedInputStream outputReader; - private PipedOutputStream outputWriter; - private FlushListener flushListener; - private VirtualTerminal virtualTerminal; - private LispTerminal lispTerminal; - - private void pressKey(KeyType keyType) { - virtualTerminal.addInput(new KeyStroke(keyType)); - waitForFlushes(1); - } - - private void pressKeyTimes(KeyType keyType, int times) { - for (int i = times; i > 0; i--) - virtualTerminal.addInput(new KeyStroke(keyType)); - - waitForFlushes(times); - } - - private void pressControlKey(KeyType keyType) { - virtualTerminal.addInput(new KeyStroke(keyType, true, false)); - waitForFlushes(1); - } - - private void enterCharacter(char character) { - virtualTerminal.addInput(new KeyStroke(character, false, false)); - waitForFlushes(1); - } - - private void enterControlCharacter(char character) { - virtualTerminal.addInput(new KeyStroke(character, true, false)); - waitForFlushes(1); - } - - private void enterCharacters(String characters) { - for (char c : characters.toCharArray()) - virtualTerminal.addInput(new KeyStroke(c, false, false)); - - waitForFlushes(characters.length()); - } - - private void produceOutput(String output) { - try { - for (char c : output.toCharArray()) - outputWriter.write(c); - - outputWriter.write(END_OF_SEGMENT); - outputWriter.flush(); - waitForFlushes(1); - } catch (IOException ignored) {} - } - - private void waitForFlushes(int flushCount) { - try { - synchronized (flushListener) { - while (flushListener.getFlushCount() < flushCount) - flushListener.wait(); - - flushListener.resetFlushCount(); - } - } catch (InterruptedException ignored) {} - } - - private void setColumns(int columns) { - int rows = virtualTerminal.getTerminalSize().getRows(); - virtualTerminal.setTerminalSize(new TerminalSize(columns, rows)); - } - - private void setRows(int rows) { - int columns = virtualTerminal.getTerminalSize().getColumns(); - virtualTerminal.setTerminalSize(new TerminalSize(columns, rows)); - } - - private void assertCursorPosition(int column, int row) { - assertEquals(column, virtualTerminal.getCursorPosition().getColumn()); - assertEquals(row, virtualTerminal.getCursorPosition().getRow()); - } - - private void assertCharacterAtPosition(char character, int column, int row) { - TerminalPosition position = new TerminalPosition(column, row); - String expected = String.valueOf(character); - String actual = String.valueOf(virtualTerminal.getCharacter(position).getCharacter()); - assertEquals(expected, actual); - } - - private void assertCharacterPositions(char[][] positions) { - for (int row = 0; row < positions.length; row++) - for (int column = 0; column < positions[row].length; column++) - assertCharacterAtPosition(positions[row][column], column, row); - } - - private void assertInputWritten(String expected) { - String actual = ""; - - try { - inputWriter.close(); - - for (int c = inputReader.read(); c != -1; c = inputReader.read()) - actual += (char) c; - } catch (IOException ignored) {} - - assertEquals(expected, actual); - } - - private void assertInputStreamClosed() { - try { - inputWriter.write(0); - fail("input stream not closed"); - } catch (IOException ignored) {} - } + private VirtualTerminalInteractor terminal; @Before - public void setUp() throws IOException { - inputReader = new PipedInputStream(); - inputWriter = new PipedOutputStream(inputReader); - outputReader = new PipedInputStream(); - outputWriter = new PipedOutputStream(outputReader); - virtualTerminal = new DefaultVirtualTerminal(); - flushListener = new FlushListener(); - virtualTerminal.addVirtualTerminalListener(flushListener); - lispTerminal = new LispTerminal(virtualTerminal, inputWriter, outputReader); - lispTerminal.start(); + public void setUp() { + terminal = new VirtualTerminalInteractor(); + terminal.start(); } @After - public void tearDown() throws IOException { - lispTerminal.stop(); - outputWriter.close(); + public void tearDown() { + terminal.stop(); } @Test public void leftArrowDoesNotMovePastOrigin() { - pressKey(ArrowLeft); - assertCursorPosition(0, 0); + terminal.pressKey(ArrowLeft); + terminal.assertCursorPosition(0, 0); } @Test public void leftArrowWorksAfterEnteringCharacters() { - enterCharacters("abc"); - assertCursorPosition(3, 0); - pressKey(ArrowLeft); - assertCursorPosition(2, 0); - pressKey(ArrowLeft); - assertCursorPosition(1, 0); - pressKey(ArrowLeft); - assertCursorPosition(0, 0); - pressKey(ArrowLeft); - assertCursorPosition(0, 0); + terminal.enterCharacters("abc"); + terminal.assertCursorPosition(3, 0); + terminal.pressKey(ArrowLeft); + terminal.assertCursorPosition(2, 0); + terminal.pressKey(ArrowLeft); + terminal.assertCursorPosition(1, 0); + terminal.pressKey(ArrowLeft); + terminal.assertCursorPosition(0, 0); + terminal.pressKey(ArrowLeft); + terminal.assertCursorPosition(0, 0); } @Test public void leftArrowWorksAcrossRows() { - setColumns(5); - enterCharacters("123451"); - assertCursorPosition(1, 1); - pressKeyTimes(ArrowLeft, 2); - assertCursorPosition(4, 0); + terminal.setColumns(5); + terminal.enterCharacters("123451"); + terminal.assertCursorPosition(1, 1); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.assertCursorPosition(4, 0); } @Test public void rightArrowDoesNotMovePastEndOfInput() { - pressKey(ArrowRight); - assertCursorPosition(0, 0); + terminal.pressKey(ArrowRight); + terminal.assertCursorPosition(0, 0); } @Test public void rightArrowWorksAfterMovingLeft() { - enterCharacters("12"); - assertCursorPosition(2, 0); - pressKey(ArrowLeft); - assertCursorPosition(1, 0); - pressKey(ArrowRight); - assertCursorPosition(2, 0); - pressKey(ArrowRight); - assertCursorPosition(2, 0); + terminal.enterCharacters("12"); + terminal.assertCursorPosition(2, 0); + terminal.pressKey(ArrowLeft); + terminal.assertCursorPosition(1, 0); + terminal.pressKey(ArrowRight); + terminal.assertCursorPosition(2, 0); + terminal.pressKey(ArrowRight); + terminal.assertCursorPosition(2, 0); } @Test public void rightArrowWorksAcrossRow() { - setColumns(5); - enterCharacters("123451"); - assertCursorPosition(1, 1); - pressKeyTimes(ArrowLeft, 3); - assertCursorPosition(3, 0); - pressKeyTimes(ArrowRight, 3); - assertCursorPosition(1, 1); + terminal.setColumns(5); + terminal.enterCharacters("123451"); + terminal.assertCursorPosition(1, 1); + terminal.pressKeyTimes(ArrowLeft, 3); + terminal.assertCursorPosition(3, 0); + terminal.pressKeyTimes(ArrowRight, 3); + terminal.assertCursorPosition(1, 1); } @Test public void characterKeyIsEchoed() { - enterCharacter('a'); - assertCursorPosition(1, 0); - assertCharacterAtPosition('a', 0, 0); + terminal.enterCharacter('a'); + terminal.assertCursorPosition(1, 0); + terminal.assertCharacterAtPosition('a', 0, 0); } @Test public void characterIsInserted() { - enterCharacters("abcd"); - pressKeyTimes(ArrowLeft, 2); - enterCharacter('x'); - assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } }); + terminal.enterCharacters("abcd"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.enterCharacter('x'); + terminal.assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } }); } @Test public void characterIsInserted_PushesInputToNextRow() { - setColumns(4); - enterCharacters("abcd"); - pressKeyTimes(ArrowLeft, 2); - enterCharacter('x'); - assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } }); + terminal.setColumns(4); + terminal.enterCharacters("abcd"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.enterCharacter('x'); + terminal.assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } }); } @Test public void backspaceDoesNothingAtOrigin() { - pressKey(Backspace); - assertCursorPosition(0, 0); + terminal.pressKey(Backspace); + terminal.assertCursorPosition(0, 0); } @Test public void backspaceWorksAfterInput() { - enterCharacters("12345"); - pressKeyTimes(Backspace, 2); - assertCursorPosition(3, 0); - assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } }); + terminal.enterCharacters("12345"); + terminal.pressKeyTimes(Backspace, 2); + terminal.assertCursorPosition(3, 0); + terminal.assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } }); } @Test public void backspaceWorksAcrossRow() { - setColumns(4); - enterCharacters("1234567"); - pressKeyTimes(Backspace, 5); - assertCursorPosition(2, 0); - assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } }); + terminal.setColumns(4); + terminal.enterCharacters("1234567"); + terminal.pressKeyTimes(Backspace, 5); + terminal.assertCursorPosition(2, 0); + terminal.assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } }); } @Test public void backspaceWorksInMiddleOfInput() { - enterCharacters("12345"); - pressKeyTimes(ArrowLeft, 2); - pressKey(Backspace); - assertCursorPosition(2, 0); - assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } }); + terminal.enterCharacters("12345"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.pressKey(Backspace); + terminal.assertCursorPosition(2, 0); + terminal.assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } }); } @Test public void deleteDoesNothingAtOrigin() { - pressKey(Delete); - assertCursorPosition(0, 0); + terminal.pressKey(Delete); + terminal.assertCursorPosition(0, 0); } @Test public void deleteDoesNothingAtEndOfInput() { - enterCharacters("del"); - pressKey(Delete); - assertCursorPosition(3, 0); - assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } }); + terminal.enterCharacters("del"); + terminal.pressKey(Delete); + terminal.assertCursorPosition(3, 0); + terminal.assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } }); } @Test public void deleteWorksAtStartOfInput() { - enterCharacters("del"); - pressKeyTimes(ArrowLeft, 3); - pressKeyTimes(Delete, 3); - assertCursorPosition(0, 0); - assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } }); + terminal.enterCharacters("del"); + terminal.pressKeyTimes(ArrowLeft, 3); + terminal.pressKeyTimes(Delete, 3); + terminal.assertCursorPosition(0, 0); + terminal.assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } }); } @Test public void deleteWorksAcrossRow() { - setColumns(4); - enterCharacters("delete"); - pressKeyTimes(ArrowLeft, 5); - pressKey(Delete); - assertCursorPosition(1, 0); - assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } }); + terminal.setColumns(4); + terminal.enterCharacters("delete"); + terminal.pressKeyTimes(ArrowLeft, 5); + terminal.pressKey(Delete); + terminal.assertCursorPosition(1, 0); + terminal.assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } }); } @Test public void enterMovesToNextLine() { - pressKey(Enter); - assertCursorPosition(0, 1); + terminal.pressKey(Enter); + terminal.assertCursorPosition(0, 1); } @Test public void enterWritesLineToPipedStream() { - enterCharacters("enter"); - pressKey(Enter); - assertInputWritten("enter\n"); + terminal.enterCharacters("enter"); + terminal.pressKey(Enter); + terminal.assertInputWritten("enter\n"); } @Test public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() { - enterCharacters("enter"); - pressKeyTimes(ArrowLeft, 2); - pressKey(Enter); - assertInputWritten("enter\n"); + terminal.enterCharacters("enter"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.pressKey(Enter); + terminal.assertInputWritten("enter\n"); } @Test public void enterAfterInsertedText_WritesLineToPipedStream() { - enterCharacters("enter"); - pressKeyTimes(ArrowLeft, 2); - enterCharacters("||"); - pressKey(Enter); - assertInputWritten("ent||er\n"); + terminal.enterCharacters("enter"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.enterCharacters("||"); + terminal.pressKey(Enter); + terminal.assertInputWritten("ent||er\n"); } @Test public void enterAfterBackspace_WritesLineToPipedStream() { - enterCharacters("enter"); - pressKeyTimes(Backspace, 2); - pressKey(Enter); - assertInputWritten("ent\n"); + terminal.enterCharacters("enter"); + terminal.pressKeyTimes(Backspace, 2); + terminal.pressKey(Enter); + terminal.assertInputWritten("ent\n"); } @Test public void enterAfterDelete_WritesLineToPipedStream() { - enterCharacters("enter"); - pressKeyTimes(ArrowLeft, 2); - pressKeyTimes(Delete, 2); - pressKey(Enter); - assertInputWritten("ent\n"); + terminal.enterCharacters("enter"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.pressKeyTimes(Delete, 2); + terminal.pressKey(Enter); + terminal.assertInputWritten("ent\n"); } @Test public void controlDWorks() { - enterCharacters("control-d"); - enterControlCharacter('d'); - assertInputStreamClosed(); - assertInputWritten("control-d\n"); + terminal.enterCharacters("control-d"); + terminal.enterControlCharacter('d'); + terminal.assertInputStreamClosed(); + terminal.assertInputWritten("control-d\n"); } @Test public void controlCWorks() { - enterCharacters("ctrl-c"); - enterControlCharacter('c'); - produceOutput(""); - assertInputStreamClosed(); - assertInputWritten(""); - assertCharacterPositions(new char[][] { { 'c', 't', 'r', 'l', '-', 'c', ' ', ' ', ' ' }, - { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } }); + terminal.enterCharacters("ctrl-c"); + terminal.enterControlCharacter('c'); + terminal.produceOutput(""); + terminal.assertInputStreamClosed(); + terminal.assertInputWritten(""); + terminal.assertCharacterPositions(new char[][] { { 'c', 't', 'r', 'l', '-', 'c', ' ', ' ', ' ' }, + { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } }); } @Test public void controlDWorksInMiddleOfInput() { - enterCharacters("control-d"); - pressKeyTimes(ArrowLeft, 2); - enterControlCharacter('d'); - assertInputStreamClosed(); - assertInputWritten("control-d\n"); + terminal.enterCharacters("control-d"); + terminal.pressKeyTimes(ArrowLeft, 2); + terminal.enterControlCharacter('d'); + terminal.assertInputStreamClosed(); + terminal.assertInputWritten("control-d\n"); } @Test public void escapeDoesNothing() { - pressKey(Escape); - assertCursorPosition(0, 0); - assertInputWritten(""); + terminal.pressKey(Escape); + terminal.assertCursorPosition(0, 0); + terminal.assertInputWritten(""); } @Test public void controlQDoesNothing() { - enterControlCharacter('q'); - assertCursorPosition(0, 0); - assertInputWritten(""); + terminal.enterControlCharacter('q'); + terminal.assertCursorPosition(0, 0); + terminal.assertInputWritten(""); } @Test public void controlEnterDoesNothing() { - pressControlKey(Enter); - assertCursorPosition(0, 0); - assertInputWritten(""); + terminal.pressControlKey(Enter); + terminal.assertCursorPosition(0, 0); + terminal.assertInputWritten(""); } @Test public void outputIsWritten() { - produceOutput("output"); - assertCursorPosition(6, 0); - assertCharacterPositions(new char[][] { { 'o', 'u', 't', 'p', 'u', 't' } }); + terminal.produceOutput("output"); + terminal.assertCursorPosition(6, 0); + terminal.assertCharacterPositions(new char[][] { { 'o', 'u', 't', 'p', 'u', 't' } }); } @Test public void endOfSegmentCharacterIsNotPrinted() { - produceOutput("> " + END_OF_SEGMENT); - assertCursorPosition(2, 0); - assertCharacterPositions(new char[][] { { '>', ' ', ' ' } }); + terminal.produceOutput("> " + END_OF_SEGMENT); + terminal.assertCursorPosition(2, 0); + terminal.assertCharacterPositions(new char[][] { { '>', ' ', ' ' } }); } @Test public void enterTextPastLastLineOfBuffer() { - setColumns(3); - setRows(2); - enterCharacters("01201201"); - assertCursorPosition(2, 1); - assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } }); + terminal.setColumns(3); + terminal.setRows(2); + terminal.enterCharacters("01201201"); + terminal.assertCursorPosition(2, 1); + terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } }); } @Test public void insertingTextPushesInputPastEndOfBuffer() { - setColumns(3); - setRows(4); - pressKey(Enter); - enterCharacters("00011122"); - pressKeyTimes(ArrowLeft, 4); - assertCursorPosition(1, 2); - enterCharacters("zz"); - assertCursorPosition(0, 2); - assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', 'z', 'z' }, { '1', '1', '2' }, - { '2', ' ', ' ' } }); + terminal.setColumns(3); + terminal.setRows(4); + terminal.pressKey(Enter); + terminal.enterCharacters("00011122"); + terminal.pressKeyTimes(ArrowLeft, 4); + terminal.assertCursorPosition(1, 2); + terminal.enterCharacters("zz"); + terminal.assertCursorPosition(0, 2); + terminal.assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', 'z', 'z' }, { '1', '1', '2' }, + { '2', ' ', ' ' } }); } @Test public void insertingTextDoesNothingWhenBufferFilled() { - setColumns(3); - setRows(3); - enterCharacters("00011122"); - pressKeyTimes(ArrowLeft, 4); - assertCursorPosition(1, 1); - enterCharacters("zz"); - assertCursorPosition(1, 1); - assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); + terminal.setColumns(3); + terminal.setRows(3); + terminal.enterCharacters("00011122"); + terminal.pressKeyTimes(ArrowLeft, 4); + terminal.assertCursorPosition(1, 1); + terminal.enterCharacters("zz"); + terminal.assertCursorPosition(1, 1); + terminal.assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); } @Test public void appendingTextDoesNothingWhenBufferFilled() { - setColumns(3); - setRows(3); - enterCharacters("000111222333444"); - assertCursorPosition(2, 2); - assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); + terminal.setColumns(3); + terminal.setRows(3); + terminal.enterCharacters("000111222333444"); + terminal.assertCursorPosition(2, 2); + terminal.assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); } @Test public void printedOutputToEndOfRow_MovesCursorToNextRow() { - setColumns(3); - produceOutput("out"); - assertCursorPosition(0, 1); + terminal.setColumns(3); + terminal.produceOutput("out"); + terminal.assertCursorPosition(0, 1); } @Test public void printedOutputToEndOfBuffer_MovesCursorToNewRow() { - setColumns(3); - setRows(2); - produceOutput("output"); - assertCursorPosition(0, 1); - assertCharacterPositions(new char[][] { { 'p', 'u', 't' }, { ' ', ' ', ' ' } }); + terminal.setColumns(3); + terminal.setRows(2); + terminal.produceOutput("output"); + terminal.assertCursorPosition(0, 1); + terminal.assertCharacterPositions(new char[][] { { 'p', 'u', 't' }, { ' ', ' ', ' ' } }); } @Test public void outputDoesNotOverwriteInput_AndRedisplaysInput() { - setColumns(3); - enterCharacters("0123"); - pressKeyTimes(ArrowLeft, 3); - produceOutput("out"); - assertCursorPosition(2, 3); - assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', 'o', 'u' }, { 't', '0', '1' }, - { '2', '3', ' ' }, { ' ', ' ', ' ' } }); + terminal.setColumns(3); + terminal.enterCharacters("0123"); + terminal.pressKeyTimes(ArrowLeft, 3); + terminal.produceOutput("out"); + terminal.assertCursorPosition(2, 3); + terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', 'o', 'u' }, { 't', '0', '1' }, + { '2', '3', ' ' }, { ' ', ' ', ' ' } }); } @Test public void outputEndsOnSecondToLastColumn_MovesToNewRow() { - setColumns(3); - enterCharacters("01234"); - pressKeyTimes(ArrowLeft, 3); - produceOutput("out"); - assertCursorPosition(2, 4); - assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', 'o' }, { 'u', 't', ' ' }, - { '0', '1', '2' }, { '3', '4', ' ' } }); + terminal.setColumns(3); + terminal.enterCharacters("01234"); + terminal.pressKeyTimes(ArrowLeft, 3); + terminal.produceOutput("out"); + terminal.assertCursorPosition(2, 4); + terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', 'o' }, { 'u', 't', ' ' }, + { '0', '1', '2' }, { '3', '4', ' ' } }); } @Test public void outputEndsOnLastColumn_MovesToNewRow() { - setColumns(3); - enterCharacters("012345"); - pressKeyTimes(ArrowLeft, 3); - produceOutput("out"); - assertCursorPosition(0, 5); - assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', '5' }, { 'o', 'u', 't' }, - { '0', '1', '2' }, { '3', '4', '5' }, { ' ', ' ', ' ' } }); + terminal.setColumns(3); + terminal.enterCharacters("012345"); + terminal.pressKeyTimes(ArrowLeft, 3); + terminal.produceOutput("out"); + terminal.assertCursorPosition(0, 5); + terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', '5' }, { 'o', 'u', 't' }, + { '0', '1', '2' }, { '3', '4', '5' }, { ' ', ' ', ' ' } }); } @Test public void outputRedisplaysInputAtEndOfBuffer() { - setColumns(3); - setRows(4); - enterCharacters("01234"); - pressKeyTimes(ArrowLeft, 3); - produceOutput("out"); - assertCursorPosition(2, 3); - assertCharacterPositions(new char[][] { { '3', '4', 'o' }, { 'u', 't', ' ' }, { '0', '1', '2' }, - { '3', '4', ' ' } }); + terminal.setColumns(3); + terminal.setRows(4); + terminal.enterCharacters("01234"); + terminal.pressKeyTimes(ArrowLeft, 3); + terminal.produceOutput("out"); + terminal.assertCursorPosition(2, 3); + terminal.assertCharacterPositions(new char[][] { { '3', '4', 'o' }, { 'u', 't', ' ' }, { '0', '1', '2' }, + { '3', '4', ' ' } }); } @Test public void outputDoesNotOverwriteInput_AfterEnter() { - setColumns(3); - enterCharacters("01201201"); - pressKeyTimes(ArrowLeft, 5); - pressKey(Enter); - produceOutput("out"); - assertCursorPosition(0, 4); - assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' }, - { 'o', 'u', 't' }, { ' ', ' ', ' ' } }); + terminal.setColumns(3); + terminal.enterCharacters("01201201"); + terminal.pressKeyTimes(ArrowLeft, 5); + terminal.pressKey(Enter); + terminal.produceOutput("out"); + terminal.assertCursorPosition(0, 4); + terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' }, + { 'o', 'u', 't' }, { ' ', ' ', ' ' } }); } @Test public void resizeIsHandledGracefully() { - enterCharacters("resize"); - pressKey(Enter); - enterCharacters("test"); - setColumns(3); - assertCursorPosition(1, 1); - assertCharacterPositions(new char[][] { { 't', 'e', 's' }, { 't', ' ', ' ' } }); + terminal.enterCharacters("resize"); + terminal.pressKey(Enter); + terminal.enterCharacters("test"); + terminal.setColumns(3); + terminal.assertCursorPosition(1, 1); + terminal.assertCharacterPositions(new char[][] { { 't', 'e', 's' }, { 't', ' ', ' ' } }); } @Test public void backspaceWorksAfterResize() { - enterCharacters("resize"); - pressKey(Enter); - enterCharacters("test"); - setColumns(3); - pressKeyTimes(Backspace, 20); - assertCursorPosition(0, 0); - assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }); + terminal.enterCharacters("resize"); + terminal.pressKey(Enter); + terminal.enterCharacters("test"); + terminal.setColumns(3); + terminal.pressKeyTimes(Backspace, 20); + terminal.assertCursorPosition(0, 0); + terminal.assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }); } @Test public void deleteWorksAfterResize() { - enterCharacters("resize"); - pressKey(Enter); - enterCharacters("test"); - setColumns(3); - pressKeyTimes(ArrowLeft, 20); - pressKeyTimes(Delete, 20); - pressKeyTimes(ArrowRight, 20); - assertCursorPosition(0, 0); - assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }); + terminal.enterCharacters("resize"); + terminal.pressKey(Enter); + terminal.enterCharacters("test"); + terminal.setColumns(3); + terminal.pressKeyTimes(ArrowLeft, 20); + terminal.pressKeyTimes(Delete, 20); + terminal.pressKeyTimes(ArrowRight, 20); + terminal.assertCursorPosition(0, 0); + terminal.assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }); } @Test public void controlSequencesAreNotPrinted() { - produceOutput("\u001B[32mcontrol\u001B[0mseq"); - assertCharacterPositions(new char[][] { { 'c', 'o', 'n', 't', 'r', 'o', 'l', 's', 'e', 'q' } }); + terminal.produceOutput("\u001B[32mcontrol\u001B[0mseq"); + terminal.assertCharacterPositions(new char[][] { { 'c', 'o', 'n', 't', 'r', 'o', 'l', 's', 'e', 'q' } }); } } diff --git a/test/terminal/VirtualTerminalInteractor.java b/test/terminal/VirtualTerminalInteractor.java new file mode 100644 index 0000000..d20071f --- /dev/null +++ b/test/terminal/VirtualTerminalInteractor.java @@ -0,0 +1,156 @@ +package terminal; + +import static org.junit.Assert.*; +import static terminal.LispTerminal.END_OF_SEGMENT; + +import java.io.*; + +import com.googlecode.lanterna.*; +import com.googlecode.lanterna.input.*; +import com.googlecode.lanterna.terminal.virtual.*; + +import stream.UncheckedIOException; + +public class VirtualTerminalInteractor { + + private VirtualTerminal virtualTerminal; + private FlushListener flushListener; + private PipedInputStream inputReader; + private PipedOutputStream inputWriter; + private PipedInputStream outputReader; + private PipedOutputStream outputWriter; + private LispTerminal lispTerminal; + + public VirtualTerminalInteractor() { + try { + this.inputReader = new PipedInputStream(); + this.inputWriter = new PipedOutputStream(inputReader); + this.outputReader = new PipedInputStream(); + this.outputWriter = new PipedOutputStream(outputReader); + this.virtualTerminal = new DefaultVirtualTerminal(); + this.flushListener = new FlushListener(); + this.virtualTerminal.addVirtualTerminalListener(flushListener); + this.lispTerminal = new LispTerminal(virtualTerminal, inputWriter, outputReader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void start() { + lispTerminal.start(); + } + + public void stop() { + try { + lispTerminal.stop(); + outputWriter.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public void pressKey(KeyType keyType) { + virtualTerminal.addInput(new KeyStroke(keyType)); + waitForFlushes(1); + } + + public void pressKeyTimes(KeyType keyType, int times) { + for (int i = times; i > 0; i--) + virtualTerminal.addInput(new KeyStroke(keyType)); + + waitForFlushes(times); + } + + public void pressControlKey(KeyType keyType) { + virtualTerminal.addInput(new KeyStroke(keyType, true, false)); + waitForFlushes(1); + } + + public void enterCharacter(char character) { + virtualTerminal.addInput(new KeyStroke(character, false, false)); + waitForFlushes(1); + } + + public void enterControlCharacter(char character) { + virtualTerminal.addInput(new KeyStroke(character, true, false)); + waitForFlushes(1); + } + + public void enterCharacters(String characters) { + for (char c : characters.toCharArray()) + virtualTerminal.addInput(new KeyStroke(c, false, false)); + + waitForFlushes(characters.length()); + } + + public void produceOutput(String output) { + try { + for (char c : output.toCharArray()) + outputWriter.write(c); + + outputWriter.write(END_OF_SEGMENT); + outputWriter.flush(); + waitForFlushes(1); + } catch (IOException ignored) {} + } + + public void waitForFlushes(int flushCount) { + try { + synchronized (flushListener) { + while (flushListener.getFlushCount() < flushCount) + flushListener.wait(); + + flushListener.resetFlushCount(); + } + } catch (InterruptedException ignored) {} + } + + public void setColumns(int columns) { + int rows = virtualTerminal.getTerminalSize().getRows(); + virtualTerminal.setTerminalSize(new TerminalSize(columns, rows)); + } + + public void setRows(int rows) { + int columns = virtualTerminal.getTerminalSize().getColumns(); + virtualTerminal.setTerminalSize(new TerminalSize(columns, rows)); + } + + public void assertCursorPosition(int column, int row) { + assertEquals(column, virtualTerminal.getCursorPosition().getColumn()); + assertEquals(row, virtualTerminal.getCursorPosition().getRow()); + } + + public void assertCharacterAtPosition(char character, int column, int row) { + TerminalPosition position = new TerminalPosition(column, row); + String expected = String.valueOf(character); + String actual = String.valueOf(virtualTerminal.getCharacter(position).getCharacter()); + assertEquals(expected, actual); + } + + public void assertCharacterPositions(char[][] positions) { + for (int row = 0; row < positions.length; row++) + for (int column = 0; column < positions[row].length; column++) + assertCharacterAtPosition(positions[row][column], column, row); + } + + public void assertInputWritten(String expected) { + String actual = ""; + + try { + inputWriter.close(); + + for (int c = inputReader.read(); c != -1; c = inputReader.read()) + actual += (char) c; + } catch (IOException ignored) {} + + assertEquals(expected, actual); + } + + public void assertInputStreamClosed() { + try { + inputWriter.write(0); + fail("input stream not closed"); + } catch (IOException ignored) {} + } + +}