package terminal; 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) { virtualTerminal.setTerminalSize(new TerminalSize(columns, virtualTerminal.getTerminalSize().getRows())); } private void setRows(int rows) { virtualTerminal.setTerminalSize(new TerminalSize(virtualTerminal.getTerminalSize().getColumns(), 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); assertEquals(String.valueOf(character), String.valueOf(virtualTerminal.getCharacter(position).getCharacter())); } 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 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); } @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.run(); } @After public void tearDown() throws IOException { lispTerminal.finish(); outputWriter.close(); } @Test public void leftArrowDoesNotMovePastOrigin() { pressKey(KeyType.ArrowLeft); assertCursorPosition(0, 0); } @Test public void leftArrowWorksAfterEnteringCharacters() { enterCharacters("abc"); assertCursorPosition(3, 0); pressKey(KeyType.ArrowLeft); assertCursorPosition(2, 0); pressKey(KeyType.ArrowLeft); assertCursorPosition(1, 0); pressKey(KeyType.ArrowLeft); assertCursorPosition(0, 0); pressKey(KeyType.ArrowLeft); assertCursorPosition(0, 0); } @Test public void leftArrowWorksAcrossRows() { setColumns(5); enterCharacters("123451"); assertCursorPosition(1, 1); pressKeyTimes(KeyType.ArrowLeft, 2); assertCursorPosition(4, 0); } @Test public void rightArrowDoesNotMovePastEndOfInput() { pressKey(KeyType.ArrowRight); assertCursorPosition(0, 0); } @Test public void rightArrowWorksAfterMovingLeft() { enterCharacters("12"); assertCursorPosition(2, 0); pressKey(KeyType.ArrowLeft); assertCursorPosition(1, 0); pressKey(KeyType.ArrowRight); assertCursorPosition(2, 0); pressKey(KeyType.ArrowRight); assertCursorPosition(2, 0); } @Test public void rightArrowWorksAcrossRow() { setColumns(5); enterCharacters("123451"); assertCursorPosition(1, 1); pressKeyTimes(KeyType.ArrowLeft, 3); assertCursorPosition(3, 0); pressKeyTimes(KeyType.ArrowRight, 3); assertCursorPosition(1, 1); } @Test public void characterKeyIsEchoed() { enterCharacter('a'); assertCursorPosition(1, 0); assertCharacterAtPosition('a', 0, 0); } @Test public void characterIsInserted() { enterCharacters("abcd"); pressKeyTimes(KeyType.ArrowLeft, 2); enterCharacter('x'); assertCharacterAtPosition('a', 0, 0); assertCharacterAtPosition('b', 1, 0); assertCharacterAtPosition('x', 2, 0); assertCharacterAtPosition('c', 3, 0); assertCharacterAtPosition('d', 4, 0); } @Test public void characterIsInserted_PushesInputToNextRow() { setColumns(4); enterCharacters("abcd"); pressKeyTimes(KeyType.ArrowLeft, 2); enterCharacter('x'); assertCharacterAtPosition('a', 0, 0); assertCharacterAtPosition('b', 1, 0); assertCharacterAtPosition('x', 2, 0); assertCharacterAtPosition('c', 3, 0); assertCharacterAtPosition('d', 0, 1); } @Test public void backspaceDoesNothingAtOrigin() { pressKey(KeyType.Backspace); assertCursorPosition(0, 0); } @Test public void backspaceWorksAfterInput() { enterCharacters("12345"); pressKeyTimes(KeyType.Backspace, 2); assertCursorPosition(3, 0); assertCharacterAtPosition('1', 0, 0); assertCharacterAtPosition('2', 1, 0); assertCharacterAtPosition('3', 2, 0); assertCharacterAtPosition(' ', 3, 0); assertCharacterAtPosition(' ', 4, 0); assertCharacterAtPosition(' ', 5, 0); } @Test public void backspaceWorksAcrossRow() { setColumns(4); enterCharacters("1234567"); pressKeyTimes(KeyType.Backspace, 5); assertCursorPosition(2, 0); assertCharacterAtPosition('1', 0, 0); assertCharacterAtPosition('2', 1, 0); assertCharacterAtPosition(' ', 2, 0); assertCharacterAtPosition(' ', 3, 0); assertCharacterAtPosition(' ', 0, 1); assertCharacterAtPosition(' ', 1, 1); assertCharacterAtPosition(' ', 2, 1); } @Test public void backspaceWorksInMiddleOfInput() { enterCharacters("12345"); pressKeyTimes(KeyType.ArrowLeft, 2); pressKey(KeyType.Backspace); assertCursorPosition(2, 0); assertCharacterAtPosition('1', 0, 0); assertCharacterAtPosition('2', 1, 0); assertCharacterAtPosition('4', 2, 0); assertCharacterAtPosition('5', 3, 0); } @Test public void deleteDoesNothingAtOrigin() { pressKey(KeyType.Delete); assertCursorPosition(0, 0); } @Test public void deleteDoesNothingAtEndOfInput() { enterCharacters("del"); pressKey(KeyType.Delete); assertCursorPosition(3, 0); assertCharacterAtPosition('d', 0, 0); assertCharacterAtPosition('e', 1, 0); assertCharacterAtPosition('l', 2, 0); } @Test public void deleteWorksAtStartOfInput() { enterCharacters("del"); pressKeyTimes(KeyType.ArrowLeft, 3); pressKeyTimes(KeyType.Delete, 3); assertCursorPosition(0, 0); assertCharacterAtPosition(' ', 0, 0); assertCharacterAtPosition(' ', 1, 0); assertCharacterAtPosition(' ', 2, 0); } @Test public void deleteWorksAcrossRow() { setColumns(4); enterCharacters("delete"); pressKeyTimes(KeyType.ArrowLeft, 5); pressKey(KeyType.Delete); assertCursorPosition(1, 0); assertCharacterAtPosition('d', 0, 0); assertCharacterAtPosition('l', 1, 0); assertCharacterAtPosition('e', 2, 0); assertCharacterAtPosition('t', 3, 0); assertCharacterAtPosition('e', 0, 1); assertCharacterAtPosition(' ', 1, 1); } @Test public void enterMovesToNextLine() { pressKey(KeyType.Enter); assertCursorPosition(0, 1); } @Test public void enterWritesLineToPipedStream() { enterCharacters("enter"); pressKey(KeyType.Enter); assertInputWritten("enter\n"); } @Test public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() { enterCharacters("enter"); pressKeyTimes(KeyType.ArrowLeft, 2); pressKey(KeyType.Enter); assertInputWritten("enter\n"); } @Test public void controlDWorks() { enterCharacters("control-d"); enterControlCharacter('d'); assertInputStreamClosed(); assertInputWritten("control-d\n"); } @Test public void controlDWorksInMiddleOfInput() { enterCharacters("control-d"); pressKeyTimes(KeyType.ArrowLeft, 2); enterControlCharacter('d'); assertInputStreamClosed(); assertInputWritten("control-d\n"); } @Test public void escapeDoesNothing() { pressKey(KeyType.Escape); assertCursorPosition(0, 0); assertInputWritten(""); } @Test public void controlQDoesNothing() { enterControlCharacter('q'); assertCursorPosition(0, 0); assertInputWritten(""); } @Test public void controlEnterDoesNothing() { pressControlKey(KeyType.Enter); assertCursorPosition(0, 0); assertInputWritten(""); } @Test public void outputIsWritten() { produceOutput("output"); assertCursorPosition(6, 0); assertCharacterPositions(new char[][] { { 'o', 'u', 't', 'p', 'u', 't' } }); } @Test public void endOfSegmentCharacterIsNotPrinted() { produceOutput("> " + END_OF_SEGMENT); assertCursorPosition(2, 0); assertCharacterPositions(new char[][] { { '>', ' ', ' ' } }); } @Test public void enterTextPastLastLineOfBuffer() { setColumns(3); setRows(2); enterCharacters("01201201"); assertCursorPosition(2, 1); assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } }); } // @Test // public void insertingTextPushesInputPastEndOfBuffer() { // setColumns(3); // setRows(3); // enterCharacters("00011122"); // pressKeyTimes(KeyType.ArrowLeft, 4); // enterCharacters("zz"); // assertCursorPosition(0, 1); // assertCharacterPositions(new char[][] { { '1', 'z', 'z' }, { '1', '1', '2' }, { '2', ' ', ' ' } }); // } @Test public void printedOutputToEndOfRow_MovesCursorToNextRow() { setColumns(3); produceOutput("out"); assertCursorPosition(0, 1); } @Test public void printedOutputToEndOfBuffer_MovesCursorToNewRow() { setColumns(3); setRows(2); produceOutput("output"); assertCursorPosition(0, 1); assertCharacterPositions(new char[][] { { 'p', 'u', 't' }, { ' ', ' ', ' ' } }); } @Test public void printedOutputDoesNotOverwriteInput() { setColumns(3); enterCharacters("01201201"); pressKeyTimes(KeyType.ArrowLeft, 5); produceOutput("out"); assertCursorPosition(0, 4); assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', 'o' }, { 'u', 't', ' ' } }); } @Test public void printedOutputDoesNotOverwriteInput_AfterEnter() { setColumns(3); enterCharacters("01201201"); pressKeyTimes(KeyType.ArrowLeft, 5); pressKey(KeyType.Enter); produceOutput("out"); assertCursorPosition(0, 4); assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' }, { 'o', 'u', 't' } }); } }