diff --git a/base-io/src/main/java/build/base/io/LookaheadReader.java b/base-io/src/main/java/build/base/io/LookaheadReader.java index 53c4012..6a14f01 100644 --- a/base-io/src/main/java/build/base/io/LookaheadReader.java +++ b/base-io/src/main/java/build/base/io/LookaheadReader.java @@ -171,7 +171,7 @@ private boolean prepare(final int desired) { * available */ private boolean prepare() { - return prepare(DEFAULT_LOOKAHEAD); + return prepare(1); } /** diff --git a/base-io/src/test/java/build/base/io/LookaheadReaderTests.java b/base-io/src/test/java/build/base/io/LookaheadReaderTests.java index 0b19bce..2609338 100644 --- a/base-io/src/test/java/build/base/io/LookaheadReaderTests.java +++ b/base-io/src/test/java/build/base/io/LookaheadReaderTests.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.StringReader; +import java.util.concurrent.atomic.AtomicInteger; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -255,4 +256,34 @@ void shouldLookaheadConsumeNSelfAdjustsBufferSize() { assertThat(reader.consume(initialBufferSize * 2)) .isEqualTo(Strings.repeat("a", initialBufferSize * 2)); } + + // prepare() previously passed DEFAULT_LOOKAHEAD as the desired size even for single-character + // operations, so "remaining < desired" became true again after consuming just one character + // from a full buffer, forcing a fresh underlying read() on every single consume(). Asserting + // on the raw read() call count (rather than just consumed output) is what catches that. + @Test + void shouldNotRereadUnderlyingStreamForEverySingleCharacterConsumed() { + final var content = Strings.repeat("a", 10_000); + final var readCalls = new AtomicInteger(); + + final var reader = new LookaheadReader(new StringReader(content) { + @Override + public int read(char [] cbuf, int off, int len) throws IOException { + readCalls.incrementAndGet(); + return super.read(cbuf, off, len); + } + }); + + int consumed = 0; + while (reader.available()) { + reader.consume(); + consumed++; + } + + assertThat(consumed) + .isEqualTo(content.length()); + + assertThat(readCalls.get()) + .isLessThan(content.length() / 10); + } }