From 6f21015d5e335c21c3be77288401505a0850fddc Mon Sep 17 00:00:00 2001 From: Stefan Birkner Date: Fri, 4 Dec 2015 18:15:41 +0100 Subject: [PATCH] fix #35 --- .../system/TextFromStandardInputStream.java | 91 +++++++++----- .../TextFromStandardInputStreamTest.java | 115 +++++++++++++++++- 2 files changed, 175 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/junit/contrib/java/lang/system/TextFromStandardInputStream.java b/src/main/java/org/junit/contrib/java/lang/system/TextFromStandardInputStream.java index 7b7ef2c1..a6c008b6 100644 --- a/src/main/java/org/junit/contrib/java/lang/system/TextFromStandardInputStream.java +++ b/src/main/java/org/junit/contrib/java/lang/system/TextFromStandardInputStream.java @@ -3,13 +3,12 @@ import static java.lang.System.getProperty; import static java.lang.System.in; import static java.lang.System.setIn; -import static java.util.Arrays.asList; +import static java.nio.charset.Charset.defaultCharset; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; -import java.util.Iterator; -import java.util.List; +import java.nio.charset.Charset; import org.junit.rules.ExternalResource; @@ -95,27 +94,26 @@ public TextFromStandardInputStream(String text) { /** * Set the text that is returned by {@code System.in}. You can * provide multiple texts. In that case {@code System.in.read()} - * returns -1 once when the end of a single text is reached and - * continues with the next text afterwards. + * continues with the next text after a text has been completely + * read. * * @param texts a list of texts. + * @deprecated please use {@link #provideLines(String[])} */ + @Deprecated public void provideText(String... texts) { - systemInMock.provideText(asList(texts)); + systemInMock.provideText(join(texts)); } /** * Set the lines that are returned by {@code System.in}. * {@code System.getProperty("line.separator")} is used for the end - * of line. {@code System.in.read()} returns -1 once when the end - * of a single line is reached and continues with the next line - * afterwards. + * of line. * * @param lines a list of lines. */ public void provideLines(String... lines) { - String[] texts = appendEndOfLineToLines(lines); - provideText(texts); + systemInMock.provideText(joinLines(lines)); } /** @@ -148,11 +146,18 @@ public void throwExceptionOnInputEnd(RuntimeException exception) { systemInMock.throwExceptionOnInputEnd(exception); } - private String[] appendEndOfLineToLines(String[] lines) { - String[] texts = new String[lines.length]; - for (int index = 0; index < lines.length; ++index) - texts[index] = lines[index] + getProperty("line.separator"); - return texts; + private String join(String[] texts) { + StringBuilder sb = new StringBuilder(); + for (String text: texts) + sb.append(text); + return sb.toString(); + } + + private String joinLines(String[] lines) { + StringBuilder sb = new StringBuilder(); + for (String line: lines) + sb.append(line).append(getProperty("line.separator")); + return sb.toString(); } @Override @@ -167,14 +172,12 @@ protected void after() { } private static class SystemInMock extends InputStream { - private Iterator texts; private StringReader currentReader; private IOException ioException; private RuntimeException runtimeException; - void provideText(List texts) { - this.texts = texts.iterator(); - updateReader(); + void provideText(String text) { + currentReader = new StringReader(text); } void throwExceptionOnInputEnd(IOException exception) { @@ -203,18 +206,50 @@ public int read() throws IOException { return character; } + @Override + public int read(byte[] b, int offset, int len) throws IOException { + if (b == null) + throw new NullPointerException(); + else if (offset < 0 || len < 0 || len > b.length - offset) + throw new IndexOutOfBoundsException(); + else if (len == 0) + return 0; + else + return readUntilNextLineBreak(b, offset, len); + } + + private int readUntilNextLineBreak(byte[] b, int offset, int len) throws IOException { + int c = read(); + if (c == -1) { + return -1; + } + b[offset] = (byte) c; + + int i = 1; + for (; (i < len) && !lineEnds(b, i); ++i) { + byte read = (byte) read(); + if (read == -1) + break; + else + b[offset + i] = read; + } + return i; + } + + private boolean lineEnds(byte[] buffer, int pos) { + byte[] separator = getProperty("line.separator") + .getBytes(defaultCharset()); + for (int i = 0; i < separator.length; ++i) + if (buffer[pos - separator.length + i] != separator[i]) + return false; + return true; + } + private void handleEmptyReader() throws IOException { - if (texts.hasNext()) - updateReader(); - else if (ioException != null) + if (ioException != null) throw ioException; else if (runtimeException != null) throw runtimeException; } - - private void updateReader() { - if (texts.hasNext()) - currentReader = new StringReader(texts.next()); - } } } diff --git a/src/test/java/org/junit/contrib/java/lang/system/TextFromStandardInputStreamTest.java b/src/test/java/org/junit/contrib/java/lang/system/TextFromStandardInputStreamTest.java index 942df035..15a9f056 100644 --- a/src/test/java/org/junit/contrib/java/lang/system/TextFromStandardInputStreamTest.java +++ b/src/test/java/org/junit/contrib/java/lang/system/TextFromStandardInputStreamTest.java @@ -2,6 +2,7 @@ import static com.github.stefanbirkner.fishbowl.Fishbowl.exceptionThrownBy; import static java.lang.System.in; +import static org.assertj.core.api.Assertions.allOf; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.contrib.java.lang.system.Executor.exceptionThrownWhenTestIsExecutedWithRule; import static org.junit.contrib.java.lang.system.Executor.executeTestWithRule; @@ -12,12 +13,16 @@ import java.io.InputStream; import java.util.Scanner; +import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import org.junit.runners.model.Statement; public class TextFromStandardInputStreamTest { + private static final byte[] DUMMY_ARRAY = new byte[1024]; + private static final int VALID_OFFSET = 2; + private static final int VALID_READ_LENGTH = 100; private static final IOException DUMMY_IO_EXCEPTION = new IOException(); private static final RuntimeException DUMMY_RUNTIME_EXCEPTION = new RuntimeException(); private static final com.github.stefanbirkner.fishbowl.Statement READ_NEXT_BYTE @@ -27,11 +32,14 @@ public void evaluate() throws Throwable { } }; - @Rule - public final Timeout timeout = new Timeout(1000); - private final TextFromStandardInputStream systemInMock = emptyStandardInputStream(); + @BeforeClass + public static void checkArrayConstants() { + assertThat(VALID_OFFSET).isBetween(0, DUMMY_ARRAY.length); + assertThat(VALID_READ_LENGTH).isBetween(0, DUMMY_ARRAY.length - VALID_OFFSET); + } + @Test public void provided_text_is_available_from_system_in() { executeTestWithRule(new Statement() { @@ -193,6 +201,107 @@ public void after_the_test_system_in_is_same_as_before() { assertThat(in).isSameAs(originalSystemIn); } + @Test + public void system_in_throws_NullPointerException_when_read_is_called_with_null_array() { + //this is default behaviour of an InputStream according to its JavaDoc + Throwable exception = exceptionThrownWhenTestIsExecutedWithRule( + new Statement() { + @Override + public void evaluate() throws Throwable { + System.in.read(null); + } + }, systemInMock); + assertThat(exception).isInstanceOf(NullPointerException.class); + } + + @Test + public void system_in_throws_IndexOutOfBoundsException_when_read_is_called_with_negative_offset() { + //this is default behaviour of an InputStream according to its JavaDoc + Throwable exception = exceptionThrownWhenTestIsExecutedWithRule( + new Statement() { + @Override + public void evaluate() throws Throwable { + System.in.read(DUMMY_ARRAY, -1, VALID_READ_LENGTH); + } + }, systemInMock); + assertThat(exception).isInstanceOf(IndexOutOfBoundsException.class); + } + + @Test + public void system_in_throws_IndexOutOfBoundsException_when_read_is_called_with_negative_length() { + //this is default behaviour of an InputStream according to its JavaDoc + Throwable exception = exceptionThrownWhenTestIsExecutedWithRule( + new Statement() { + @Override + public void evaluate() throws Throwable { + System.in.read(DUMMY_ARRAY, VALID_OFFSET, -1); + } + }, systemInMock); + assertThat(exception).isInstanceOf(IndexOutOfBoundsException.class); + } + + @Test + public void system_in_throws_IndexOutOfBoundsException_when_read_is_called_with_oversized_length() { + //this is default behaviour of an InputStream according to its JavaDoc + Throwable exception = exceptionThrownWhenTestIsExecutedWithRule( + new Statement() { + @Override + public void evaluate() throws Throwable { + int oversizedLength = DUMMY_ARRAY.length - VALID_OFFSET + 1; + System.in.read(DUMMY_ARRAY, VALID_OFFSET, oversizedLength); + } + }, systemInMock); + assertThat(exception).isInstanceOf(IndexOutOfBoundsException.class); + } + + @Test + public void system_in_reads_zero_bytes_even_mock_should_throw_IOException_on_input_end() { + executeTestWithRule(new Statement() { + @Override + public void evaluate() throws Throwable { + systemInMock.throwExceptionOnInputEnd(DUMMY_IO_EXCEPTION); + int numBytesRead = System.in.read(DUMMY_ARRAY, VALID_OFFSET, 0); + assertThat(numBytesRead).isZero(); + } + }, systemInMock); + } + + @Test + public void system_in_reads_zero_bytes_even_mock_should_throw_RuntimeException_on_input_end() { + executeTestWithRule(new Statement() { + @Override + public void evaluate() throws Throwable { + systemInMock.throwExceptionOnInputEnd(DUMMY_RUNTIME_EXCEPTION); + int numBytesRead = System.in.read(DUMMY_ARRAY, VALID_OFFSET, 0); + assertThat(numBytesRead).isZero(); + } + }, systemInMock); + } + + @Test + public void system_in_read_bytes_throws_specified_IOException_on_input_end() { + Throwable exception = exceptionThrownWhenTestIsExecutedWithRule(new Statement() { + @Override + public void evaluate() throws Throwable { + systemInMock.throwExceptionOnInputEnd(DUMMY_IO_EXCEPTION); + System.in.read(DUMMY_ARRAY, VALID_OFFSET, VALID_READ_LENGTH); + } + }, systemInMock); + assertThat(exception).isSameAs(DUMMY_IO_EXCEPTION); + } + + @Test + public void system_in_read_bytes_throws_specified_RuntimeException_on_input_end() { + Throwable exception = exceptionThrownWhenTestIsExecutedWithRule(new Statement() { + @Override + public void evaluate() throws Throwable { + systemInMock.throwExceptionOnInputEnd(DUMMY_RUNTIME_EXCEPTION); + System.in.read(DUMMY_ARRAY, VALID_OFFSET, VALID_READ_LENGTH); + } + }, systemInMock); + assertThat(exception).isSameAs(DUMMY_RUNTIME_EXCEPTION); + } + private void assertSystemInProvidesText(String text) throws IOException { for (char c : text.toCharArray()) assertThat((char) System.in.read()).isSameAs(c);