Add and refactor unit tests

This commit is contained in:
Mike Cifelli 2017-03-23 16:14:26 -04:00
parent 23dd1c0654
commit 52762a6152
9 changed files with 545 additions and 401 deletions

View File

@ -63,6 +63,12 @@
<artifactId>lanterna</artifactId> <artifactId>lanterna</artifactId>
<version>3.0.0-rc1</version> <version>3.0.0-rc1</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.16.1</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@ -9,17 +9,18 @@ import java.util.function.Function;
import com.googlecode.lanterna.terminal.*; import com.googlecode.lanterna.terminal.*;
import interpreter.*; import interpreter.*;
import terminal.LispTerminal; import stream.UncheckedIOException;
import terminal.*;
public class LispMain { public class LispMain {
private static final String GREETING = "Transcendental Lisp - Version 1.0.1"; private static final String GREETING = "Transcendental Lisp - Version 1.0.1";
private static final String ANSI_RESET = "\u001B[0m"; public static final String ANSI_RESET = "\u001B[0m";
private static final String ANSI_RED = "\u001B[31m"; public static final String ANSI_RED = "\u001B[31m";
private static final String ANSI_GREEN = "\u001B[32m"; public static final String ANSI_GREEN = "\u001B[32m";
private static final String ANSI_YELLOW = "\u001B[33m"; public static final String ANSI_YELLOW = "\u001B[33m";
private static final String ANSI_PURPLE = "\u001B[35m"; public static final String ANSI_PURPLE = "\u001B[35m";
public static void main(String[] arguments) { public static void main(String[] arguments) {
LispMain lispMain = new LispMain(); LispMain lispMain = new LispMain();
@ -143,35 +144,4 @@ public class LispMain {
return builder.build(); 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;
}
}
} }

View File

@ -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;
}
}

View File

@ -157,7 +157,7 @@ public class LispInterpreterTest {
} }
@Test @Test
public void fileBasedInterpreterWorks() { public void fileBasedInterpreterWorks_PrintsLastValueOnly() {
setCommonFeatures(); setCommonFeatures();
builder.useFile("test/interpreter/test-files/file.lisp"); builder.useFile("test/interpreter/test-files/file.lisp");
builder.build().interpret(); builder.build().interpret();

View File

@ -1 +1,2 @@
'onion
'pickle 'pickle

View File

@ -1,13 +1,92 @@
package main; 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.*;
import org.junit.contrib.java.lang.system.*;
import com.googlecode.lanterna.terminal.virtual.*;
import environment.RuntimeEnvironment;
import interpreter.LispInterpreterBuilderImpl;
import terminal.*;
public class MainTest { 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 @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 @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();
}
}
} }

View File

@ -0,0 +1,2 @@
'pickle
'radish

View File

@ -1,562 +1,437 @@
package terminal; package terminal;
import static com.googlecode.lanterna.input.KeyType.*; import static com.googlecode.lanterna.input.KeyType.*;
import static org.junit.Assert.*;
import static terminal.LispTerminal.END_OF_SEGMENT; import static terminal.LispTerminal.END_OF_SEGMENT;
import java.io.*;
import org.junit.*; import org.junit.*;
import com.googlecode.lanterna.*;
import com.googlecode.lanterna.input.*;
import com.googlecode.lanterna.terminal.virtual.*;
public class LispTerminalTest { public class LispTerminalTest {
private PipedInputStream inputReader; private VirtualTerminalInteractor terminal;
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) {}
}
@Before @Before
public void setUp() throws IOException { public void setUp() {
inputReader = new PipedInputStream(); terminal = new VirtualTerminalInteractor();
inputWriter = new PipedOutputStream(inputReader); terminal.start();
outputReader = new PipedInputStream();
outputWriter = new PipedOutputStream(outputReader);
virtualTerminal = new DefaultVirtualTerminal();
flushListener = new FlushListener();
virtualTerminal.addVirtualTerminalListener(flushListener);
lispTerminal = new LispTerminal(virtualTerminal, inputWriter, outputReader);
lispTerminal.start();
} }
@After @After
public void tearDown() throws IOException { public void tearDown() {
lispTerminal.stop(); terminal.stop();
outputWriter.close();
} }
@Test @Test
public void leftArrowDoesNotMovePastOrigin() { public void leftArrowDoesNotMovePastOrigin() {
pressKey(ArrowLeft); terminal.pressKey(ArrowLeft);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
} }
@Test @Test
public void leftArrowWorksAfterEnteringCharacters() { public void leftArrowWorksAfterEnteringCharacters() {
enterCharacters("abc"); terminal.enterCharacters("abc");
assertCursorPosition(3, 0); terminal.assertCursorPosition(3, 0);
pressKey(ArrowLeft); terminal.pressKey(ArrowLeft);
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
pressKey(ArrowLeft); terminal.pressKey(ArrowLeft);
assertCursorPosition(1, 0); terminal.assertCursorPosition(1, 0);
pressKey(ArrowLeft); terminal.pressKey(ArrowLeft);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
pressKey(ArrowLeft); terminal.pressKey(ArrowLeft);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
} }
@Test @Test
public void leftArrowWorksAcrossRows() { public void leftArrowWorksAcrossRows() {
setColumns(5); terminal.setColumns(5);
enterCharacters("123451"); terminal.enterCharacters("123451");
assertCursorPosition(1, 1); terminal.assertCursorPosition(1, 1);
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
assertCursorPosition(4, 0); terminal.assertCursorPosition(4, 0);
} }
@Test @Test
public void rightArrowDoesNotMovePastEndOfInput() { public void rightArrowDoesNotMovePastEndOfInput() {
pressKey(ArrowRight); terminal.pressKey(ArrowRight);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
} }
@Test @Test
public void rightArrowWorksAfterMovingLeft() { public void rightArrowWorksAfterMovingLeft() {
enterCharacters("12"); terminal.enterCharacters("12");
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
pressKey(ArrowLeft); terminal.pressKey(ArrowLeft);
assertCursorPosition(1, 0); terminal.assertCursorPosition(1, 0);
pressKey(ArrowRight); terminal.pressKey(ArrowRight);
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
pressKey(ArrowRight); terminal.pressKey(ArrowRight);
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
} }
@Test @Test
public void rightArrowWorksAcrossRow() { public void rightArrowWorksAcrossRow() {
setColumns(5); terminal.setColumns(5);
enterCharacters("123451"); terminal.enterCharacters("123451");
assertCursorPosition(1, 1); terminal.assertCursorPosition(1, 1);
pressKeyTimes(ArrowLeft, 3); terminal.pressKeyTimes(ArrowLeft, 3);
assertCursorPosition(3, 0); terminal.assertCursorPosition(3, 0);
pressKeyTimes(ArrowRight, 3); terminal.pressKeyTimes(ArrowRight, 3);
assertCursorPosition(1, 1); terminal.assertCursorPosition(1, 1);
} }
@Test @Test
public void characterKeyIsEchoed() { public void characterKeyIsEchoed() {
enterCharacter('a'); terminal.enterCharacter('a');
assertCursorPosition(1, 0); terminal.assertCursorPosition(1, 0);
assertCharacterAtPosition('a', 0, 0); terminal.assertCharacterAtPosition('a', 0, 0);
} }
@Test @Test
public void characterIsInserted() { public void characterIsInserted() {
enterCharacters("abcd"); terminal.enterCharacters("abcd");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
enterCharacter('x'); terminal.enterCharacter('x');
assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } }); terminal.assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c', 'd' } });
} }
@Test @Test
public void characterIsInserted_PushesInputToNextRow() { public void characterIsInserted_PushesInputToNextRow() {
setColumns(4); terminal.setColumns(4);
enterCharacters("abcd"); terminal.enterCharacters("abcd");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
enterCharacter('x'); terminal.enterCharacter('x');
assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { 'a', 'b', 'x', 'c' }, { 'd', ' ', ' ', ' ' } });
} }
@Test @Test
public void backspaceDoesNothingAtOrigin() { public void backspaceDoesNothingAtOrigin() {
pressKey(Backspace); terminal.pressKey(Backspace);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
} }
@Test @Test
public void backspaceWorksAfterInput() { public void backspaceWorksAfterInput() {
enterCharacters("12345"); terminal.enterCharacters("12345");
pressKeyTimes(Backspace, 2); terminal.pressKeyTimes(Backspace, 2);
assertCursorPosition(3, 0); terminal.assertCursorPosition(3, 0);
assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { '1', '2', '3', ' ', ' ', ' ' } });
} }
@Test @Test
public void backspaceWorksAcrossRow() { public void backspaceWorksAcrossRow() {
setColumns(4); terminal.setColumns(4);
enterCharacters("1234567"); terminal.enterCharacters("1234567");
pressKeyTimes(Backspace, 5); terminal.pressKeyTimes(Backspace, 5);
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { '1', '2', ' ', ' ' }, { ' ', ' ', ' ', ' ' } });
} }
@Test @Test
public void backspaceWorksInMiddleOfInput() { public void backspaceWorksInMiddleOfInput() {
enterCharacters("12345"); terminal.enterCharacters("12345");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
pressKey(Backspace); terminal.pressKey(Backspace);
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } }); terminal.assertCharacterPositions(new char[][] { { '1', '2', '4', '5' } });
} }
@Test @Test
public void deleteDoesNothingAtOrigin() { public void deleteDoesNothingAtOrigin() {
pressKey(Delete); terminal.pressKey(Delete);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
} }
@Test @Test
public void deleteDoesNothingAtEndOfInput() { public void deleteDoesNothingAtEndOfInput() {
enterCharacters("del"); terminal.enterCharacters("del");
pressKey(Delete); terminal.pressKey(Delete);
assertCursorPosition(3, 0); terminal.assertCursorPosition(3, 0);
assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } }); terminal.assertCharacterPositions(new char[][] { { 'd', 'e', 'l' } });
} }
@Test @Test
public void deleteWorksAtStartOfInput() { public void deleteWorksAtStartOfInput() {
enterCharacters("del"); terminal.enterCharacters("del");
pressKeyTimes(ArrowLeft, 3); terminal.pressKeyTimes(ArrowLeft, 3);
pressKeyTimes(Delete, 3); terminal.pressKeyTimes(Delete, 3);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { ' ', ' ', ' ' } });
} }
@Test @Test
public void deleteWorksAcrossRow() { public void deleteWorksAcrossRow() {
setColumns(4); terminal.setColumns(4);
enterCharacters("delete"); terminal.enterCharacters("delete");
pressKeyTimes(ArrowLeft, 5); terminal.pressKeyTimes(ArrowLeft, 5);
pressKey(Delete); terminal.pressKey(Delete);
assertCursorPosition(1, 0); terminal.assertCursorPosition(1, 0);
assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } }); terminal.assertCharacterPositions(new char[][] { { 'd', 'l', 'e', 't' }, { 'e', ' ' } });
} }
@Test @Test
public void enterMovesToNextLine() { public void enterMovesToNextLine() {
pressKey(Enter); terminal.pressKey(Enter);
assertCursorPosition(0, 1); terminal.assertCursorPosition(0, 1);
} }
@Test @Test
public void enterWritesLineToPipedStream() { public void enterWritesLineToPipedStream() {
enterCharacters("enter"); terminal.enterCharacters("enter");
pressKey(Enter); terminal.pressKey(Enter);
assertInputWritten("enter\n"); terminal.assertInputWritten("enter\n");
} }
@Test @Test
public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() { public void enterPressedInMiddleOfInput_WritesEntireLineToPipedStream() {
enterCharacters("enter"); terminal.enterCharacters("enter");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
pressKey(Enter); terminal.pressKey(Enter);
assertInputWritten("enter\n"); terminal.assertInputWritten("enter\n");
} }
@Test @Test
public void enterAfterInsertedText_WritesLineToPipedStream() { public void enterAfterInsertedText_WritesLineToPipedStream() {
enterCharacters("enter"); terminal.enterCharacters("enter");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
enterCharacters("||"); terminal.enterCharacters("||");
pressKey(Enter); terminal.pressKey(Enter);
assertInputWritten("ent||er\n"); terminal.assertInputWritten("ent||er\n");
} }
@Test @Test
public void enterAfterBackspace_WritesLineToPipedStream() { public void enterAfterBackspace_WritesLineToPipedStream() {
enterCharacters("enter"); terminal.enterCharacters("enter");
pressKeyTimes(Backspace, 2); terminal.pressKeyTimes(Backspace, 2);
pressKey(Enter); terminal.pressKey(Enter);
assertInputWritten("ent\n"); terminal.assertInputWritten("ent\n");
} }
@Test @Test
public void enterAfterDelete_WritesLineToPipedStream() { public void enterAfterDelete_WritesLineToPipedStream() {
enterCharacters("enter"); terminal.enterCharacters("enter");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
pressKeyTimes(Delete, 2); terminal.pressKeyTimes(Delete, 2);
pressKey(Enter); terminal.pressKey(Enter);
assertInputWritten("ent\n"); terminal.assertInputWritten("ent\n");
} }
@Test @Test
public void controlDWorks() { public void controlDWorks() {
enterCharacters("control-d"); terminal.enterCharacters("control-d");
enterControlCharacter('d'); terminal.enterControlCharacter('d');
assertInputStreamClosed(); terminal.assertInputStreamClosed();
assertInputWritten("control-d\n"); terminal.assertInputWritten("control-d\n");
} }
@Test @Test
public void controlCWorks() { public void controlCWorks() {
enterCharacters("ctrl-c"); terminal.enterCharacters("ctrl-c");
enterControlCharacter('c'); terminal.enterControlCharacter('c');
produceOutput(""); terminal.produceOutput("");
assertInputStreamClosed(); terminal.assertInputStreamClosed();
assertInputWritten(""); terminal.assertInputWritten("");
assertCharacterPositions(new char[][] { { 'c', 't', 'r', 'l', '-', 'c', ' ', ' ', ' ' }, terminal.assertCharacterPositions(new char[][] { { 'c', 't', 'r', 'l', '-', 'c', ' ', ' ', ' ' },
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } }); { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' } });
} }
@Test @Test
public void controlDWorksInMiddleOfInput() { public void controlDWorksInMiddleOfInput() {
enterCharacters("control-d"); terminal.enterCharacters("control-d");
pressKeyTimes(ArrowLeft, 2); terminal.pressKeyTimes(ArrowLeft, 2);
enterControlCharacter('d'); terminal.enterControlCharacter('d');
assertInputStreamClosed(); terminal.assertInputStreamClosed();
assertInputWritten("control-d\n"); terminal.assertInputWritten("control-d\n");
} }
@Test @Test
public void escapeDoesNothing() { public void escapeDoesNothing() {
pressKey(Escape); terminal.pressKey(Escape);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
assertInputWritten(""); terminal.assertInputWritten("");
} }
@Test @Test
public void controlQDoesNothing() { public void controlQDoesNothing() {
enterControlCharacter('q'); terminal.enterControlCharacter('q');
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
assertInputWritten(""); terminal.assertInputWritten("");
} }
@Test @Test
public void controlEnterDoesNothing() { public void controlEnterDoesNothing() {
pressControlKey(Enter); terminal.pressControlKey(Enter);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
assertInputWritten(""); terminal.assertInputWritten("");
} }
@Test @Test
public void outputIsWritten() { public void outputIsWritten() {
produceOutput("output"); terminal.produceOutput("output");
assertCursorPosition(6, 0); terminal.assertCursorPosition(6, 0);
assertCharacterPositions(new char[][] { { 'o', 'u', 't', 'p', 'u', 't' } }); terminal.assertCharacterPositions(new char[][] { { 'o', 'u', 't', 'p', 'u', 't' } });
} }
@Test @Test
public void endOfSegmentCharacterIsNotPrinted() { public void endOfSegmentCharacterIsNotPrinted() {
produceOutput("> " + END_OF_SEGMENT); terminal.produceOutput("> " + END_OF_SEGMENT);
assertCursorPosition(2, 0); terminal.assertCursorPosition(2, 0);
assertCharacterPositions(new char[][] { { '>', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { '>', ' ', ' ' } });
} }
@Test @Test
public void enterTextPastLastLineOfBuffer() { public void enterTextPastLastLineOfBuffer() {
setColumns(3); terminal.setColumns(3);
setRows(2); terminal.setRows(2);
enterCharacters("01201201"); terminal.enterCharacters("01201201");
assertCursorPosition(2, 1); terminal.assertCursorPosition(2, 1);
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } }); terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', ' ' } });
} }
@Test @Test
public void insertingTextPushesInputPastEndOfBuffer() { public void insertingTextPushesInputPastEndOfBuffer() {
setColumns(3); terminal.setColumns(3);
setRows(4); terminal.setRows(4);
pressKey(Enter); terminal.pressKey(Enter);
enterCharacters("00011122"); terminal.enterCharacters("00011122");
pressKeyTimes(ArrowLeft, 4); terminal.pressKeyTimes(ArrowLeft, 4);
assertCursorPosition(1, 2); terminal.assertCursorPosition(1, 2);
enterCharacters("zz"); terminal.enterCharacters("zz");
assertCursorPosition(0, 2); terminal.assertCursorPosition(0, 2);
assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', 'z', 'z' }, { '1', '1', '2' }, terminal.assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', 'z', 'z' }, { '1', '1', '2' },
{ '2', ' ', ' ' } }); { '2', ' ', ' ' } });
} }
@Test @Test
public void insertingTextDoesNothingWhenBufferFilled() { public void insertingTextDoesNothingWhenBufferFilled() {
setColumns(3); terminal.setColumns(3);
setRows(3); terminal.setRows(3);
enterCharacters("00011122"); terminal.enterCharacters("00011122");
pressKeyTimes(ArrowLeft, 4); terminal.pressKeyTimes(ArrowLeft, 4);
assertCursorPosition(1, 1); terminal.assertCursorPosition(1, 1);
enterCharacters("zz"); terminal.enterCharacters("zz");
assertCursorPosition(1, 1); terminal.assertCursorPosition(1, 1);
assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); terminal.assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } });
} }
@Test @Test
public void appendingTextDoesNothingWhenBufferFilled() { public void appendingTextDoesNothingWhenBufferFilled() {
setColumns(3); terminal.setColumns(3);
setRows(3); terminal.setRows(3);
enterCharacters("000111222333444"); terminal.enterCharacters("000111222333444");
assertCursorPosition(2, 2); terminal.assertCursorPosition(2, 2);
assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } }); terminal.assertCharacterPositions(new char[][] { { '0', '0', '0' }, { '1', '1', '1' }, { '2', '2', ' ' } });
} }
@Test @Test
public void printedOutputToEndOfRow_MovesCursorToNextRow() { public void printedOutputToEndOfRow_MovesCursorToNextRow() {
setColumns(3); terminal.setColumns(3);
produceOutput("out"); terminal.produceOutput("out");
assertCursorPosition(0, 1); terminal.assertCursorPosition(0, 1);
} }
@Test @Test
public void printedOutputToEndOfBuffer_MovesCursorToNewRow() { public void printedOutputToEndOfBuffer_MovesCursorToNewRow() {
setColumns(3); terminal.setColumns(3);
setRows(2); terminal.setRows(2);
produceOutput("output"); terminal.produceOutput("output");
assertCursorPosition(0, 1); terminal.assertCursorPosition(0, 1);
assertCharacterPositions(new char[][] { { 'p', 'u', 't' }, { ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { 'p', 'u', 't' }, { ' ', ' ', ' ' } });
} }
@Test @Test
public void outputDoesNotOverwriteInput_AndRedisplaysInput() { public void outputDoesNotOverwriteInput_AndRedisplaysInput() {
setColumns(3); terminal.setColumns(3);
enterCharacters("0123"); terminal.enterCharacters("0123");
pressKeyTimes(ArrowLeft, 3); terminal.pressKeyTimes(ArrowLeft, 3);
produceOutput("out"); terminal.produceOutput("out");
assertCursorPosition(2, 3); terminal.assertCursorPosition(2, 3);
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', 'o', 'u' }, { 't', '0', '1' }, terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', 'o', 'u' }, { 't', '0', '1' },
{ '2', '3', ' ' }, { ' ', ' ', ' ' } }); { '2', '3', ' ' }, { ' ', ' ', ' ' } });
} }
@Test @Test
public void outputEndsOnSecondToLastColumn_MovesToNewRow() { public void outputEndsOnSecondToLastColumn_MovesToNewRow() {
setColumns(3); terminal.setColumns(3);
enterCharacters("01234"); terminal.enterCharacters("01234");
pressKeyTimes(ArrowLeft, 3); terminal.pressKeyTimes(ArrowLeft, 3);
produceOutput("out"); terminal.produceOutput("out");
assertCursorPosition(2, 4); terminal.assertCursorPosition(2, 4);
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', 'o' }, { 'u', 't', ' ' }, terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', 'o' }, { 'u', 't', ' ' },
{ '0', '1', '2' }, { '3', '4', ' ' } }); { '0', '1', '2' }, { '3', '4', ' ' } });
} }
@Test @Test
public void outputEndsOnLastColumn_MovesToNewRow() { public void outputEndsOnLastColumn_MovesToNewRow() {
setColumns(3); terminal.setColumns(3);
enterCharacters("012345"); terminal.enterCharacters("012345");
pressKeyTimes(ArrowLeft, 3); terminal.pressKeyTimes(ArrowLeft, 3);
produceOutput("out"); terminal.produceOutput("out");
assertCursorPosition(0, 5); terminal.assertCursorPosition(0, 5);
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', '5' }, { 'o', 'u', 't' }, terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '3', '4', '5' }, { 'o', 'u', 't' },
{ '0', '1', '2' }, { '3', '4', '5' }, { ' ', ' ', ' ' } }); { '0', '1', '2' }, { '3', '4', '5' }, { ' ', ' ', ' ' } });
} }
@Test @Test
public void outputRedisplaysInputAtEndOfBuffer() { public void outputRedisplaysInputAtEndOfBuffer() {
setColumns(3); terminal.setColumns(3);
setRows(4); terminal.setRows(4);
enterCharacters("01234"); terminal.enterCharacters("01234");
pressKeyTimes(ArrowLeft, 3); terminal.pressKeyTimes(ArrowLeft, 3);
produceOutput("out"); terminal.produceOutput("out");
assertCursorPosition(2, 3); terminal.assertCursorPosition(2, 3);
assertCharacterPositions(new char[][] { { '3', '4', 'o' }, { 'u', 't', ' ' }, { '0', '1', '2' }, terminal.assertCharacterPositions(new char[][] { { '3', '4', 'o' }, { 'u', 't', ' ' }, { '0', '1', '2' },
{ '3', '4', ' ' } }); { '3', '4', ' ' } });
} }
@Test @Test
public void outputDoesNotOverwriteInput_AfterEnter() { public void outputDoesNotOverwriteInput_AfterEnter() {
setColumns(3); terminal.setColumns(3);
enterCharacters("01201201"); terminal.enterCharacters("01201201");
pressKeyTimes(ArrowLeft, 5); terminal.pressKeyTimes(ArrowLeft, 5);
pressKey(Enter); terminal.pressKey(Enter);
produceOutput("out"); terminal.produceOutput("out");
assertCursorPosition(0, 4); terminal.assertCursorPosition(0, 4);
assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' }, terminal.assertCharacterPositions(new char[][] { { '0', '1', '2' }, { '0', '1', '2' }, { '0', '1', ' ' },
{ 'o', 'u', 't' }, { ' ', ' ', ' ' } }); { 'o', 'u', 't' }, { ' ', ' ', ' ' } });
} }
@Test @Test
public void resizeIsHandledGracefully() { public void resizeIsHandledGracefully() {
enterCharacters("resize"); terminal.enterCharacters("resize");
pressKey(Enter); terminal.pressKey(Enter);
enterCharacters("test"); terminal.enterCharacters("test");
setColumns(3); terminal.setColumns(3);
assertCursorPosition(1, 1); terminal.assertCursorPosition(1, 1);
assertCharacterPositions(new char[][] { { 't', 'e', 's' }, { 't', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { 't', 'e', 's' }, { 't', ' ', ' ' } });
} }
@Test @Test
public void backspaceWorksAfterResize() { public void backspaceWorksAfterResize() {
enterCharacters("resize"); terminal.enterCharacters("resize");
pressKey(Enter); terminal.pressKey(Enter);
enterCharacters("test"); terminal.enterCharacters("test");
setColumns(3); terminal.setColumns(3);
pressKeyTimes(Backspace, 20); terminal.pressKeyTimes(Backspace, 20);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } });
} }
@Test @Test
public void deleteWorksAfterResize() { public void deleteWorksAfterResize() {
enterCharacters("resize"); terminal.enterCharacters("resize");
pressKey(Enter); terminal.pressKey(Enter);
enterCharacters("test"); terminal.enterCharacters("test");
setColumns(3); terminal.setColumns(3);
pressKeyTimes(ArrowLeft, 20); terminal.pressKeyTimes(ArrowLeft, 20);
pressKeyTimes(Delete, 20); terminal.pressKeyTimes(Delete, 20);
pressKeyTimes(ArrowRight, 20); terminal.pressKeyTimes(ArrowRight, 20);
assertCursorPosition(0, 0); terminal.assertCursorPosition(0, 0);
assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } }); terminal.assertCharacterPositions(new char[][] { { ' ', ' ', ' ' }, { ' ', ' ', ' ' } });
} }
@Test @Test
public void controlSequencesAreNotPrinted() { public void controlSequencesAreNotPrinted() {
produceOutput("\u001B[32mcontrol\u001B[0mseq"); terminal.produceOutput("\u001B[32mcontrol\u001B[0mseq");
assertCharacterPositions(new char[][] { { 'c', 'o', 'n', 't', 'r', 'o', 'l', 's', 'e', 'q' } }); terminal.assertCharacterPositions(new char[][] { { 'c', 'o', 'n', 't', 'r', 'o', 'l', 's', 'e', 'q' } });
} }
} }

View File

@ -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) {}
}
}