Merge pull request #6896 from thundernest/convert_to_kotlin

Convert `FixedLengthInputStream[Test]` to Kotlin
This commit is contained in:
cketti 2023-05-16 16:26:29 +02:00 committed by GitHub
commit 7c41c66f08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 357 additions and 351 deletions

View file

@ -177,6 +177,7 @@ shared-jvm-androidtest-compose = [
"androidx-compose-ui-test-junit4",
]
shared-jvm-test = [
"kotlin-test",
"junit",
"assertk",
"mockito-core",

View file

@ -1,78 +0,0 @@
package com.fsck.k9.mail.filter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
/**
* A filtering InputStream that stops allowing reads after the given length has been read. This
* is used to allow a client to read directly from an underlying protocol stream without reading
* past where the protocol handler intended the client to read.
*/
public class FixedLengthInputStream extends InputStream {
private final InputStream mIn;
private final int mLength;
private int mCount = 0;
public FixedLengthInputStream(InputStream in, int length) {
this.mIn = in;
this.mLength = length;
}
@Override
public int available() throws IOException {
return mLength - mCount;
}
@Override
public int read() throws IOException {
if (mCount >= mLength) {
return -1;
}
int d = mIn.read();
if (d != -1) {
mCount++;
}
return d;
}
@Override
public int read(byte[] b, int offset, int length) throws IOException {
if (mCount >= mLength) {
return -1;
}
int d = mIn.read(b, offset, Math.min(mLength - mCount, length));
if (d != -1) {
mCount += d;
}
return d;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public long skip(long n) throws IOException {
long d = mIn.skip(Math.min(n, available()));
if (d > 0) {
mCount += d;
}
return d;
}
@Override
public String toString() {
return String.format(Locale.US, "FixedLengthInputStream(in=%s, length=%d)", mIn.toString(), mLength);
}
public void skipRemaining() throws IOException {
while (available() > 0) {
skip(available());
}
}
}

View file

@ -0,0 +1,81 @@
package com.fsck.k9.mail.filter
import java.io.IOException
import java.io.InputStream
import java.util.Locale
/**
* A filtering InputStream that stops allowing reads after the given length has been read. This
* is used to allow a client to read directly from an underlying protocol stream without reading
* past where the protocol handler intended the client to read.
*/
class FixedLengthInputStream(
private val inputStream: InputStream,
private val length: Int,
) : InputStream() {
private var numberOfBytesRead = 0
// TODO: Call available() on underlying InputStream if remainingBytes() > 0
@Throws(IOException::class)
override fun available(): Int {
return remainingBytes()
}
@Throws(IOException::class)
override fun read(): Int {
if (remainingBytes() == 0) {
return -1
}
val byte = inputStream.read()
if (byte != -1) {
numberOfBytesRead++
}
return byte
}
@Throws(IOException::class)
override fun read(b: ByteArray, offset: Int, length: Int): Int {
if (remainingBytes() == 0) {
return -1
}
val byte = inputStream.read(b, offset, length.coerceAtMost(remainingBytes()))
if (byte != -1) {
numberOfBytesRead += byte
}
return byte
}
@Throws(IOException::class)
override fun read(b: ByteArray): Int {
return read(b, 0, b.size)
}
@Throws(IOException::class)
override fun skip(n: Long): Long {
val numberOfSkippedBytes = inputStream.skip(n.coerceAtMost(remainingBytes().toLong()))
if (numberOfSkippedBytes > 0) {
numberOfBytesRead += numberOfSkippedBytes.toInt()
}
return numberOfSkippedBytes
}
@Throws(IOException::class)
fun skipRemaining() {
while (remainingBytes() > 0) {
skip(remainingBytes().toLong())
}
}
private fun remainingBytes(): Int {
return length - numberOfBytesRead
}
override fun toString(): String {
return String.format(Locale.ROOT, "FixedLengthInputStream(in=%s, length=%d)", inputStream.toString(), length)
}
}

View file

@ -1,273 +0,0 @@
package com.fsck.k9.mail.filter;
import java.io.IOException;
import java.io.InputStream;
import okio.Buffer;
import okio.ByteString;
import okio.Okio;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class FixedLengthInputStreamTest {
@Test
public void readingStream_shouldReturnDataUpToLimit() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello world"), 5);
String readString = readStreamAsUtf8String(fixedLengthInputStream);
assertEquals("Hello", readString);
}
@Test
public void readingStream_shouldNotConsumeMoreThanLimitFromUnderlyingInputStream() throws Exception {
InputStream inputStream = inputStream("Hello world");
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream, 5);
exhaustStream(fixedLengthInputStream);
assertRemainingInputStreamEquals(" world", inputStream);
}
@Test
//TODO: Maybe this should throw. The underlying stream delivering less bytes than expected is most likely an error.
public void readingStream_withLimitGreaterThanNumberOfBytesInUnderlyingInputStream() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 100);
String readString = readStreamAsUtf8String(fixedLengthInputStream);
assertEquals("Hello World", readString);
}
@Test
public void read_withOverSizedByteArray_shouldReturnDataUpToLimit() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 6);
byte[] data = new byte[100];
int numberOfBytesRead = fixedLengthInputStream.read(data);
assertEquals(6, numberOfBytesRead);
assertEquals("Hello ", ByteString.of(data, 0, numberOfBytesRead).utf8());
}
@Test
public void read_withOverSizedByteArray_shouldNotConsumeMoreThanLimitFromUnderlyingStream() throws Exception {
InputStream inputStream = inputStream("Hello World");
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream, 6);
//noinspection ResultOfMethodCallIgnored
fixedLengthInputStream.read(new byte[100]);
assertRemainingInputStreamEquals("World", inputStream);
}
@Test
public void read_withByteArraySmallerThanLimit_shouldConsumeSizeOfByteArray() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 6);
byte[] data = new byte[5];
int numberOfBytesRead = fixedLengthInputStream.read(data);
assertEquals(5, numberOfBytesRead);
assertEquals("Hello", ByteString.of(data).utf8());
}
@Test
public void read_withOverSizedByteArrayInMiddleOfStream_shouldReturnDataUpToLimit() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 6);
consumeBytes(fixedLengthInputStream, 5);
byte[] data = new byte[10];
int numberOfBytesRead = fixedLengthInputStream.read(data);
assertEquals(1, numberOfBytesRead);
assertEquals(" ", ByteString.of(data, 0, numberOfBytesRead).utf8());
}
@Test
public void read_withOverSizedByteArrayInMiddleOfStream_shouldNotConsumeMoreThanLimitFromUnderlyingStream()
throws Exception {
InputStream inputStream = inputStream("Hello World");
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream, 6);
consumeBytes(fixedLengthInputStream, 5);
//noinspection ResultOfMethodCallIgnored
fixedLengthInputStream.read(new byte[10]);
assertRemainingInputStreamEquals("World", inputStream);
}
@Test
public void read_atStartOfStream() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Word"), 2);
int readByte = fixedLengthInputStream.read();
assertEquals('W', (char) readByte);
}
@Test
public void read_inMiddleOfStream() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Word"), 2);
consumeBytes(fixedLengthInputStream, 1);
int readByte = fixedLengthInputStream.read();
assertEquals('o', (char) readByte);
}
@Test
public void read_atEndOfStream_shouldReturnMinusOne() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello world"), 5);
exhaustStream(fixedLengthInputStream);
int readByte = fixedLengthInputStream.read();
assertEquals(-1, readByte);
}
@Test
public void readArray_atEndOfStream_shouldReturnMinusOne() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello world"), 5);
exhaustStream(fixedLengthInputStream);
int numberOfBytesRead = fixedLengthInputStream.read(new byte[2]);
assertEquals(-1, numberOfBytesRead);
}
@Test
public void readArrayWithOffset_atEndOfStream_shouldReturnMinusOne() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello world"), 5);
exhaustStream(fixedLengthInputStream);
int numberOfBytesRead = fixedLengthInputStream.read(new byte[2], 0, 2);
assertEquals(-1, numberOfBytesRead);
}
@Test
public void available_atStartOfStream() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
int available = fixedLengthInputStream.available();
assertEquals(5, available);
}
@Test
public void available_afterPartialRead() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
//noinspection ResultOfMethodCallIgnored
fixedLengthInputStream.read();
int available = fixedLengthInputStream.available();
assertEquals(4, available);
}
@Test
public void available_afterPartialReadArray() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
consumeBytes(fixedLengthInputStream, 2);
int available = fixedLengthInputStream.available();
assertEquals(3, available);
}
@Test
public void available_afterStreamHasBeenExhausted() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
exhaustStream(fixedLengthInputStream);
int available = fixedLengthInputStream.available();
assertEquals(0, available);
}
@Test
public void available_afterSkip() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
guaranteedSkip(fixedLengthInputStream, 2);
int available = fixedLengthInputStream.available();
assertEquals(3, available);
}
@Test
public void available_afterSkipRemaining() throws Exception {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
fixedLengthInputStream.skipRemaining();
int available = fixedLengthInputStream.available();
assertEquals(0, available);
}
@Test
public void skip_shouldConsumeBytes() throws IOException {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
guaranteedSkip(fixedLengthInputStream, 2);
assertRemainingInputStreamEquals("llo", fixedLengthInputStream);
}
@Test
public void skipRemaining_shouldExhaustStream() throws IOException {
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream("Hello World"), 5);
fixedLengthInputStream.skipRemaining();
assertInputStreamExhausted(fixedLengthInputStream);
}
@Test
public void skipRemaining_shouldNotConsumeMoreThanLimitFromUnderlyingInputStream() throws IOException {
InputStream inputStream = inputStream("Hello World");
FixedLengthInputStream fixedLengthInputStream = new FixedLengthInputStream(inputStream, 6);
fixedLengthInputStream.skipRemaining();
assertRemainingInputStreamEquals("World", inputStream);
}
private String readStreamAsUtf8String(InputStream inputStream) throws IOException {
return Okio.buffer(Okio.source(inputStream)).readUtf8();
}
private void exhaustStream(InputStream inputStream) throws IOException {
Okio.buffer(Okio.source(inputStream)).readAll(Okio.blackhole());
}
private void consumeBytes(InputStream inputStream, int numberOfBytes) throws IOException {
int read = inputStream.read(new byte[numberOfBytes]);
assertEquals(numberOfBytes, read);
}
private void guaranteedSkip(InputStream inputStream, int numberOfBytesToSkip) throws IOException {
int remaining = numberOfBytesToSkip;
while (remaining > 0) {
remaining -= inputStream.skip(remaining);
}
assertEquals(0, remaining);
}
private void assertRemainingInputStreamEquals(String expected, InputStream inputStream) throws IOException {
assertEquals(expected, readStreamAsUtf8String(inputStream));
}
private void assertInputStreamExhausted(InputStream inputStream) throws IOException {
assertEquals(-1, inputStream.read());
}
private InputStream inputStream(String data) {
return new Buffer().writeUtf8(data).inputStream();
}
}

View file

@ -0,0 +1,275 @@
package com.fsck.k9.mail.filter
import assertk.Assert
import assertk.all
import assertk.assertThat
import assertk.assertions.isEqualTo
import java.io.InputStream
import kotlin.test.Test
import okio.blackholeSink
import okio.buffer
import okio.source
class FixedLengthInputStreamTest {
@Test
fun `reading stream should return data up to limit`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
val result = fixedLengthInputStream.readToString()
assertThat(result).isEqualTo("Hello")
}
@Test
fun `reading stream should not consume more than limit from underlying InputStream`() {
val inputStream = "Hello world".byteInputStream()
val fixedLengthInputStream = FixedLengthInputStream(inputStream, 5)
fixedLengthInputStream.exhaust()
assertThat(inputStream).readToString().isEqualTo(" world")
}
// TODO: Maybe this should throw. The underlying stream delivering less bytes than expected is most likely an error.
@Test
fun `reading stream with limit greater than number of bytes in underlying InputStream`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 100)
val result = fixedLengthInputStream.readToString()
assertThat(result).isEqualTo("Hello world")
}
@Test
fun `read() with oversized ByteArray should return data up to limit`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello World", length = 6)
val data = ByteArray(100)
val result = fixedLengthInputStream.read(data)
assertThat(result).isEqualTo(6)
assertThat(data).all {
slice(0 until 6).asString().isEqualTo("Hello ")
slice(6 until 100).isEqualTo(ByteArray(100 - 6))
}
}
@Test
fun `read() with oversized ByteArray should not consume more than limit from underlying InputStream`() {
val inputStream = "Hello World".byteInputStream()
val fixedLengthInputStream = FixedLengthInputStream(inputStream, 6)
fixedLengthInputStream.read(ByteArray(100))
assertThat(inputStream).readToString().isEqualTo("World")
}
@Test
fun `read() with ByteArray smaller than limit should consume size of ByteArray`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello World", length = 6)
val data = ByteArray(5)
val result = fixedLengthInputStream.read(data)
assertThat(result).isEqualTo(5)
assertThat(data).asString().isEqualTo("Hello")
}
@Test
fun `read() with oversized ByteArray in middle of stream should return data up to limit`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello World", length = 6)
fixedLengthInputStream.consumeBytes(5)
val data = ByteArray(10)
val result = fixedLengthInputStream.read(data)
assertThat(result).isEqualTo(1)
assertThat(data).all {
slice(0 until 1).asString().isEqualTo(" ")
slice(1 until 10).isEqualTo(ByteArray(10 - 1))
}
}
@Test
fun `read() with oversized ByteArray in middle of stream should not read more than limit from underlying stream`() {
val inputStream = "Hello World".byteInputStream()
val fixedLengthInputStream = FixedLengthInputStream(inputStream, 6)
fixedLengthInputStream.consumeBytes(5)
fixedLengthInputStream.read(ByteArray(10))
assertThat(inputStream).readToString().isEqualTo("World")
}
@Test
fun `read() at start of stream`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Word", length = 2)
val result = fixedLengthInputStream.read()
assertThat(result).isEqualTo('W'.code)
}
@Test
fun `read() in middle of stream`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Word", length = 2)
fixedLengthInputStream.consumeBytes(1)
val result = fixedLengthInputStream.read()
assertThat(result).isEqualTo('o'.code)
}
@Test
fun `read() at end of stream should return -1`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.exhaust()
val result = fixedLengthInputStream.read()
assertThat(result).isEqualTo(-1)
}
@Test
fun `read(ByteArray) at end of stream should return -1`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.exhaust()
val result = fixedLengthInputStream.read(ByteArray(2))
assertThat(result).isEqualTo(-1)
}
@Test
fun `read(ByteArray) with offset at end of stream should return -1`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.exhaust()
val result = fixedLengthInputStream.read(ByteArray(2), 0, 2)
assertThat(result).isEqualTo(-1)
}
@Test
fun `available() at start of stream`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
val result = fixedLengthInputStream.available()
assertThat(result).isEqualTo(5)
}
@Test
fun `available() after partial read`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.read()
val result = fixedLengthInputStream.available()
assertThat(result).isEqualTo(4)
}
@Test
fun `available() after partial multi-byte read`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.consumeBytes(2)
val result = fixedLengthInputStream.available()
assertThat(result).isEqualTo(3)
}
@Test
fun `available() after stream has been exhausted`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.exhaust()
val result = fixedLengthInputStream.available()
assertThat(result).isEqualTo(0)
}
@Test
fun `available() after skip()`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.guaranteedSkip(2)
val result = fixedLengthInputStream.available()
assertThat(result).isEqualTo(3)
}
@Test
fun `available() after skip remaining`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.skipRemaining()
val result = fixedLengthInputStream.available()
assertThat(result).isEqualTo(0)
}
@Test
fun `skip() should consume bytes`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.guaranteedSkip(2)
assertThat(fixedLengthInputStream).readToString().isEqualTo("llo")
}
@Test
fun `skipRemaining() should exhaust stream`() {
val fixedLengthInputStream = createFixedLengthInputStream(data = "Hello world", length = 5)
fixedLengthInputStream.skipRemaining()
assertThat(fixedLengthInputStream).isExhausted()
}
@Test
fun `skipRemaining() should not consume more than limit from underlying InputStream`() {
val inputStream = "Hello World".byteInputStream()
val fixedLengthInputStream = FixedLengthInputStream(inputStream, 6)
fixedLengthInputStream.skipRemaining()
assertThat(inputStream).readToString().isEqualTo("World")
}
private fun createFixedLengthInputStream(data: String, length: Int): FixedLengthInputStream {
return FixedLengthInputStream(data.byteInputStream(), length)
}
private fun InputStream.guaranteedSkip(numberOfBytesToSkip: Int) {
var remaining = numberOfBytesToSkip.toLong()
while (remaining > 0) {
remaining -= skip(remaining)
}
assertThat(remaining).isEqualTo(0L)
}
private fun InputStream.readToString(): String = source().buffer().readUtf8()
private fun InputStream.exhaust() {
source().buffer().readAll(blackholeSink())
}
private fun InputStream.consumeBytes(numberOfBytes: Int) {
val numberOfBytesRead = read(ByteArray(numberOfBytes))
assertThat(numberOfBytesRead).isEqualTo(numberOfBytes)
}
private fun Assert<InputStream>.isExhausted() = given { actual ->
assertThat(actual.read()).isEqualTo(-1)
assertThat(actual.available()).isEqualTo(0)
}
private fun Assert<InputStream>.readToString() = transform { it.readToString() }
private fun Assert<ByteArray>.slice(range: IntRange) = transform { it.sliceArray(range) }
private fun Assert<ByteArray>.asString() = transform { String(it) }
}