aboutsummaryrefslogtreecommitdiff
path: root/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt')
-rw-r--r--okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt1503
1 files changed, 1503 insertions, 0 deletions
diff --git a/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt b/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt
new file mode 100644
index 00000000..b30944b7
--- /dev/null
+++ b/okio/src/jvmTest/kotlin/okio/BufferedSourceTest.kt
@@ -0,0 +1,1503 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package okio
+
+import java.io.EOFException
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+import java.util.Arrays
+import kotlin.text.Charsets.US_ASCII
+import kotlin.text.Charsets.UTF_8
+import okio.ByteString.Companion.decodeHex
+import okio.ByteString.Companion.encodeUtf8
+import okio.Options.Companion.of
+import okio.TestUtil.SEGMENT_SIZE
+import okio.TestUtil.assertByteArrayEquals
+import okio.TestUtil.assertByteArraysEquals
+import okio.TestUtil.randomBytes
+import okio.TestUtil.segmentSizes
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Assume
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+@RunWith(Parameterized::class)
+class BufferedSourceTest(
+ private val factory: Factory,
+) {
+ interface Factory {
+ fun pipe(): Pipe
+ val isOneByteAtATime: Boolean
+
+ companion object {
+ val BUFFER: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(buffer, buffer)
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "Buffer"
+ }
+
+ val REAL_BUFFERED_SOURCE: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = (buffer as Source).buffer(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "RealBufferedSource"
+ }
+
+ /**
+ * A factory deliberately written to create buffers whose internal segments are always 1 byte
+ * long. We like testing with these segments because are likely to trigger bugs!
+ */
+ val ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = object : ForwardingSource(buffer) {
+ override fun read(sink: Buffer, byteCount: Long): Long {
+ // Read one byte into a new buffer, then clone it so that the segment is shared.
+ // Shared segments cannot be compacted so we'll get a long chain of short segments.
+ val box = Buffer()
+ val result = super.read(box, Math.min(byteCount, 1L))
+ if (result > 0L) sink.write(box.clone(), result)
+ return result
+ }
+ }.buffer(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = true
+
+ override fun toString() = "OneByteAtATimeBufferedSource"
+ }
+
+ val ONE_BYTE_AT_A_TIME_BUFFER: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ val sink = object : ForwardingSink(buffer) {
+ override fun write(source: Buffer, byteCount: Long) {
+ // Write each byte into a new buffer, then clone it so that the segments are shared.
+ // Shared segments cannot be compacted so we'll get a long chain of short segments.
+ for (i in 0 until byteCount) {
+ val box = Buffer()
+ box.write(source, 1)
+ super.write(box.clone(), 1)
+ }
+ }
+ }.buffer()
+ return Pipe(
+ sink = sink,
+ source = buffer,
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = true
+
+ override fun toString() = "OneByteAtATimeBuffer"
+ }
+
+ val PEEK_BUFFER: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = buffer.peek(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "PeekBuffer"
+ }
+
+ val PEEK_BUFFERED_SOURCE: Factory = object : Factory {
+ override fun pipe(): Pipe {
+ val buffer = Buffer()
+ return Pipe(
+ sink = buffer,
+ source = (buffer as Source).buffer().peek(),
+ )
+ }
+
+ override val isOneByteAtATime: Boolean get() = false
+
+ override fun toString() = "PeekBufferedSource"
+ }
+ }
+ }
+
+ class Pipe(
+ var sink: BufferedSink,
+ var source: BufferedSource,
+ )
+
+ private val pipe = factory.pipe()
+ private val sink: BufferedSink = pipe.sink
+ private val source: BufferedSource = pipe.source
+
+ @Test
+ fun readBytes() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte()))
+ sink.emit()
+ assertEquals(0xab, (source.readByte().toInt() and 0xff).toLong())
+ assertEquals(0xcd, (source.readByte().toInt() and 0xff).toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readByteTooShortThrows() {
+ try {
+ source.readByte()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readShort() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte()))
+ sink.emit()
+ assertEquals(0xabcd.toShort().toLong(), source.readShort().toLong())
+ assertEquals(0xef01.toShort().toLong(), source.readShort().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readShortLe() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte()))
+ sink.emit()
+ assertEquals(0xcdab.toShort().toLong(), source.readShortLe().toLong())
+ assertEquals(0x10ef.toShort().toLong(), source.readShortLe().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readShortSplitAcrossMultipleSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte()))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 1).toLong())
+ assertEquals(0xabcd.toShort().toLong(), source.readShort().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readShortTooShortThrows() {
+ sink.writeShort(Short.MAX_VALUE.toInt())
+ sink.emit()
+ source.readByte()
+ try {
+ source.readShort()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readShortLeTooShortThrows() {
+ sink.writeShortLe(Short.MAX_VALUE.toInt())
+ sink.emit()
+ source.readByte()
+ try {
+ source.readShortLe()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readInt() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte()))
+ sink.emit()
+ assertEquals(-0x543210ff, source.readInt().toLong())
+ assertEquals(-0x789abcdf, source.readInt().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readIntLe() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte()))
+ sink.emit()
+ assertEquals(0x10efcdab, source.readIntLe().toLong())
+ assertEquals(0x21436587, source.readIntLe().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readIntSplitAcrossMultipleSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 3))
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte()))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 3).toLong())
+ assertEquals(-0x543210ff, source.readInt().toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readIntTooShortThrows() {
+ sink.writeInt(Int.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readInt()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readIntLeTooShortThrows() {
+ sink.writeIntLe(Int.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readIntLe()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readLong() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte(), 0x36.toByte(), 0x47.toByte(), 0x58.toByte(), 0x69.toByte(), 0x12.toByte(), 0x23.toByte(), 0x34.toByte(), 0x45.toByte()))
+ sink.emit()
+ assertEquals(-0x543210ef789abcdfL, source.readLong())
+ assertEquals(0x3647586912233445L, source.readLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readLongLe() {
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x10.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte(), 0x36.toByte(), 0x47.toByte(), 0x58.toByte(), 0x69.toByte(), 0x12.toByte(), 0x23.toByte(), 0x34.toByte(), 0x45.toByte()))
+ sink.emit()
+ assertEquals(0x2143658710efcdabL, source.readLongLe())
+ assertEquals(0x4534231269584736L, source.readLongLe())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readLongSplitAcrossMultipleSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 7))
+ sink.write(byteArrayOf(0xab.toByte(), 0xcd.toByte(), 0xef.toByte(), 0x01.toByte(), 0x87.toByte(), 0x65.toByte(), 0x43.toByte(), 0x21.toByte()))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 7).toLong())
+ assertEquals(-0x543210fe789abcdfL, source.readLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readLongTooShortThrows() {
+ sink.writeLong(Long.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readLong()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readLongLeTooShortThrows() {
+ sink.writeLongLe(Long.MAX_VALUE)
+ sink.emit()
+ source.readByte()
+ try {
+ source.readLongLe()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun readAll() {
+ source.buffer.writeUtf8("abc")
+ sink.writeUtf8("def")
+ sink.emit()
+ val sink = Buffer()
+ assertEquals(6, source.readAll(sink))
+ assertEquals("abcdef", sink.readUtf8())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readAllExhausted() {
+ val mockSink = MockSink()
+ assertEquals(0, source.readAll(mockSink))
+ assertTrue(source.exhausted())
+ mockSink.assertLog()
+ }
+
+ @Test
+ fun readExhaustedSource() {
+ val sink = Buffer()
+ sink.writeUtf8("a".repeat(10))
+ assertEquals(-1, source.read(sink, 10))
+ assertEquals(10, sink.size)
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readZeroBytesFromSource() {
+ val sink = Buffer()
+ sink.writeUtf8("a".repeat(10))
+
+ // Either 0 or -1 is reasonable here. For consistency with Android's
+ // ByteArrayInputStream we return 0.
+ assertEquals(-1, source.read(sink, 0))
+ assertEquals(10, sink.size)
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun readFully() {
+ sink.writeUtf8("a".repeat(10000))
+ sink.emit()
+ val sink = Buffer()
+ source.readFully(sink, 9999)
+ assertEquals("a".repeat(9999), sink.readUtf8())
+ assertEquals("a", source.readUtf8())
+ }
+
+ @Test
+ fun readFullyTooShortThrows() {
+ sink.writeUtf8("Hi")
+ sink.emit()
+ val sink = Buffer()
+ try {
+ source.readFully(sink, 5)
+ fail()
+ } catch (ignored: EOFException) {
+ }
+
+ // Verify we read all that we could from the source.
+ assertEquals("Hi", sink.readUtf8())
+ }
+
+ @Test
+ fun readFullyByteArray() {
+ val data = Buffer()
+ data.writeUtf8("Hello").writeUtf8("e".repeat(SEGMENT_SIZE))
+ val expected = data.clone().readByteArray()
+ sink.write(data, data.size)
+ sink.emit()
+ val sink = ByteArray(SEGMENT_SIZE + 5)
+ source.readFully(sink)
+ assertByteArraysEquals(expected, sink)
+ }
+
+ @Test
+ fun readFullyByteArrayTooShortThrows() {
+ sink.writeUtf8("Hello")
+ sink.emit()
+ val array = ByteArray(6)
+ try {
+ source.readFully(array)
+ fail()
+ } catch (ignored: EOFException) {
+ }
+
+ // Verify we read all that we could from the source.
+ assertByteArraysEquals(byteArrayOf('H'.code.toByte(), 'e'.code.toByte(), 'l'.code.toByte(), 'l'.code.toByte(), 'o'.code.toByte(), 0), array)
+ }
+
+ @Test
+ fun readIntoByteArray() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ val sink = ByteArray(3)
+ val read = source.read(sink)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 0, 0)
+ assertByteArraysEquals(expected, sink)
+ } else {
+ assertEquals(3, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte())
+ assertByteArraysEquals(expected, sink)
+ }
+ }
+
+ @Test
+ fun readIntoByteArrayNotEnough() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ val sink = ByteArray(5)
+ val read = source.read(sink)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 0, 0, 0, 0)
+ assertByteArraysEquals(expected, sink)
+ } else {
+ assertEquals(4, read.toLong())
+ val expected = byteArrayOf('a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte(), 'd'.code.toByte(), 0)
+ assertByteArraysEquals(expected, sink)
+ }
+ }
+
+ @Test
+ fun readIntoByteArrayOffsetAndCount() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ val sink = ByteArray(7)
+ val read = source.read(sink, 2, 3)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 0, 0, 0, 0)
+ assertByteArraysEquals(expected, sink)
+ } else {
+ assertEquals(3, read.toLong())
+ val expected = byteArrayOf(0, 0, 'a'.code.toByte(), 'b'.code.toByte(), 'c'.code.toByte(), 0, 0)
+ assertByteArraysEquals(expected, sink)
+ }
+ }
+
+ @Test
+ fun readByteArray() {
+ val string = "abcd" + "e".repeat(SEGMENT_SIZE)
+ sink.writeUtf8(string)
+ sink.emit()
+ assertByteArraysEquals(string.toByteArray(UTF_8), source.readByteArray())
+ }
+
+ @Test
+ fun readByteArrayPartial() {
+ sink.writeUtf8("abcd")
+ sink.emit()
+ assertEquals("[97, 98, 99]", Arrays.toString(source.readByteArray(3)))
+ assertEquals("d", source.readUtf8(1))
+ }
+
+ @Test
+ fun readByteArrayTooShortThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.readByteArray(4)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun readByteString() {
+ sink.writeUtf8("abcd").writeUtf8("e".repeat(SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("abcd" + "e".repeat(SEGMENT_SIZE), source.readByteString().utf8())
+ }
+
+ @Test
+ fun readByteStringPartial() {
+ sink.writeUtf8("abcd").writeUtf8("e".repeat(SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("abc", source.readByteString(3).utf8())
+ assertEquals("d", source.readUtf8(1))
+ }
+
+ @Test
+ fun readByteStringTooShortThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.readByteString(4)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun readSpecificCharsetPartial() {
+ sink.write(
+ (
+ "0000007600000259000002c80000006c000000e40000007300000259" +
+ "000002cc000000720000006100000070000000740000025900000072"
+ ).decodeHex(),
+ )
+ sink.emit()
+ assertEquals("vəˈläsə", source.readString((7 * 4).toLong(), Charset.forName("utf-32")))
+ }
+
+ @Test
+ fun readSpecificCharset() {
+ sink.write(
+ (
+ "0000007600000259000002c80000006c000000e40000007300000259" +
+ "000002cc000000720000006100000070000000740000025900000072"
+ ).decodeHex(),
+ )
+ sink.emit()
+ assertEquals("vəˈläsəˌraptər", source.readString(Charset.forName("utf-32")))
+ }
+
+ @Test
+ fun readStringTooShortThrows() {
+ sink.writeString("abc", US_ASCII)
+ sink.emit()
+ try {
+ source.readString(4, US_ASCII)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun readUtf8SpansSegments() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 1).toLong())
+ assertEquals("aa", source.readUtf8(2))
+ }
+
+ @Test
+ fun readUtf8Segment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("a".repeat(SEGMENT_SIZE), source.readUtf8(SEGMENT_SIZE.toLong()))
+ }
+
+ @Test
+ fun readUtf8PartialBuffer() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE + 20))
+ sink.emit()
+ assertEquals("a".repeat(SEGMENT_SIZE + 10), source.readUtf8((SEGMENT_SIZE + 10).toLong()))
+ }
+
+ @Test
+ fun readUtf8EntireBuffer() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 2))
+ sink.emit()
+ assertEquals("a".repeat(SEGMENT_SIZE * 2), source.readUtf8())
+ }
+
+ @Test
+ fun readUtf8TooShortThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.readUtf8(4L)
+ fail()
+ } catch (expected: EOFException) {
+ }
+ assertEquals("abc", source.readUtf8()) // The read shouldn't consume any data.
+ }
+
+ @Test
+ fun skip() {
+ sink.writeUtf8("a")
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE))
+ sink.writeUtf8("c")
+ sink.emit()
+ source.skip(1)
+ assertEquals('b'.code.toLong(), (source.readByte().toInt() and 0xff).toLong())
+ source.skip((SEGMENT_SIZE - 2).toLong())
+ assertEquals('b'.code.toLong(), (source.readByte().toInt() and 0xff).toLong())
+ source.skip(1)
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun skipInsufficientData() {
+ sink.writeUtf8("a")
+ sink.emit()
+ try {
+ source.skip(2)
+ fail()
+ } catch (ignored: EOFException) {
+ }
+ }
+
+ @Test
+ fun indexOf() {
+ // The segment is empty.
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+
+ // The segment has one value.
+ sink.writeUtf8("a") // a
+ sink.emit()
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(-1, source.indexOf('b'.code.toByte()))
+
+ // The segment has lots of data.
+ sink.writeUtf8("b".repeat(SEGMENT_SIZE - 2)) // ab...b
+ sink.emit()
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(1, source.indexOf('b'.code.toByte()))
+ assertEquals(-1, source.indexOf('c'.code.toByte()))
+
+ // The segment doesn't start at 0, it starts at 2.
+ source.skip(2) // b...b
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals(-1, source.indexOf('c'.code.toByte()))
+
+ // The segment is full.
+ sink.writeUtf8("c") // b...bc
+ sink.emit()
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals((SEGMENT_SIZE - 3).toLong(), source.indexOf('c'.code.toByte()))
+
+ // The segment doesn't start at 2, it starts at 4.
+ source.skip(2) // b...bc
+ assertEquals(-1, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('b'.code.toByte()))
+ assertEquals((SEGMENT_SIZE - 5).toLong(), source.indexOf('c'.code.toByte()))
+
+ // Two segments.
+ sink.writeUtf8("d") // b...bcd, d is in the 2nd segment.
+ sink.emit()
+ assertEquals((SEGMENT_SIZE - 4).toLong(), source.indexOf('d'.code.toByte()))
+ assertEquals(-1, source.indexOf('e'.code.toByte()))
+ }
+
+ @Test
+ fun indexOfByteWithStartOffset() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertEquals(-1, source.indexOf('a'.code.toByte(), 1))
+ assertEquals(15, source.indexOf('b'.code.toByte(), 15))
+ }
+
+ @Test
+ fun indexOfByteWithBothOffsets() {
+ if (factory.isOneByteAtATime) {
+ // When run on Travis this causes out-of-memory errors.
+ return
+ }
+ val a = 'a'.code.toByte()
+ val c = 'c'.code.toByte()
+ val size: Int = SEGMENT_SIZE * 5
+ val bytes = ByteArray(size)
+ Arrays.fill(bytes, a)
+
+ // These are tricky places where the buffer
+ // starts, ends, or segments come together.
+ val points = intArrayOf(
+ 0, 1, 2,
+ SEGMENT_SIZE - 1, SEGMENT_SIZE, SEGMENT_SIZE + 1,
+ size / 2 - 1, size / 2, size / 2 + 1,
+ size - SEGMENT_SIZE - 1, size - SEGMENT_SIZE, size - SEGMENT_SIZE + 1,
+ size - 3, size - 2, size - 1,
+ )
+
+ // In each iteration, we write c to the known point and then search for it using different
+ // windows. Some of the windows don't overlap with c's position, and therefore a match shouldn't
+ // be found.
+ for (p in points) {
+ bytes[p] = c
+ sink.write(bytes)
+ sink.emit()
+ assertEquals(p.toLong(), source.indexOf(c, 0, size.toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, 0, (p + 1).toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, p.toLong(), size.toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, p.toLong(), (p + 1).toLong()))
+ assertEquals(p.toLong(), source.indexOf(c, (p / 2).toLong(), (p * 2 + 1).toLong()))
+ assertEquals(-1, source.indexOf(c, 0, (p / 2).toLong()))
+ assertEquals(-1, source.indexOf(c, 0, p.toLong()))
+ assertEquals(-1, source.indexOf(c, 0, 0))
+ assertEquals(-1, source.indexOf(c, p.toLong(), p.toLong()))
+
+ // Reset.
+ source.readUtf8()
+ bytes[p] = a
+ }
+ }
+
+ @Test
+ fun indexOfByteInvalidBoundsThrows() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ try {
+ source.indexOf('a'.code.toByte(), -1)
+ fail("Expected failure: fromIndex < 0")
+ } catch (expected: IllegalArgumentException) {
+ }
+ try {
+ source.indexOf('a'.code.toByte(), 10, 0)
+ fail("Expected failure: fromIndex > toIndex")
+ } catch (expected: IllegalArgumentException) {
+ }
+ }
+
+ @Test
+ fun indexOfByteString() {
+ assertEquals(-1, source.indexOf("flop".encodeUtf8()))
+ sink.writeUtf8("flip flop")
+ sink.emit()
+ assertEquals(5, source.indexOf("flop".encodeUtf8()))
+ source.readUtf8() // Clear stream.
+
+ // Make sure we backtrack and resume searching after partial match.
+ sink.writeUtf8("hi hi hi hey")
+ sink.emit()
+ assertEquals(3, source.indexOf("hi hi hey".encodeUtf8()))
+ }
+
+ @Test
+ fun indexOfByteStringAtSegmentBoundary() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.writeUtf8("bcd")
+ sink.emit()
+ assertEquals((SEGMENT_SIZE - 3).toLong(), source.indexOf("aabc".encodeUtf8(), (SEGMENT_SIZE - 4).toLong()))
+ assertEquals((SEGMENT_SIZE - 3).toLong(), source.indexOf("aabc".encodeUtf8(), (SEGMENT_SIZE - 3).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("abcd".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("abc".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("abc".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("ab".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 2).toLong(), source.indexOf("a".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 1).toLong(), source.indexOf("bc".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE - 1).toLong(), source.indexOf("b".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals(SEGMENT_SIZE.toLong(), source.indexOf("c".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals(SEGMENT_SIZE.toLong(), source.indexOf("c".encodeUtf8(), SEGMENT_SIZE.toLong()))
+ assertEquals((SEGMENT_SIZE + 1).toLong(), source.indexOf("d".encodeUtf8(), (SEGMENT_SIZE - 2).toLong()))
+ assertEquals((SEGMENT_SIZE + 1).toLong(), source.indexOf("d".encodeUtf8(), (SEGMENT_SIZE + 1).toLong()))
+ }
+
+ @Test
+ fun indexOfDoesNotWrapAround() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 1))
+ sink.writeUtf8("bcd")
+ sink.emit()
+ assertEquals(-1, source.indexOf("abcda".encodeUtf8(), (SEGMENT_SIZE - 3).toLong()))
+ }
+
+ @Test
+ fun indexOfByteStringWithOffset() {
+ assertEquals(-1, source.indexOf("flop".encodeUtf8(), 1))
+ sink.writeUtf8("flop flip flop")
+ sink.emit()
+ assertEquals(10, source.indexOf("flop".encodeUtf8(), 1))
+ source.readUtf8() // Clear stream
+
+ // Make sure we backtrack and resume searching after partial match.
+ sink.writeUtf8("hi hi hi hi hey")
+ sink.emit()
+ assertEquals(6, source.indexOf("hi hi hey".encodeUtf8(), 1))
+ }
+
+ @Test
+ fun indexOfByteStringInvalidArgumentsThrows() {
+ try {
+ source.indexOf(ByteString.of())
+ fail()
+ } catch (e: IllegalArgumentException) {
+ assertEquals("bytes is empty", e.message)
+ }
+ try {
+ source.indexOf("hi".encodeUtf8(), -1)
+ fail()
+ } catch (e: IllegalArgumentException) {
+ assertEquals("fromIndex < 0: -1", e.message)
+ }
+ }
+
+ /**
+ * With [Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE], this code was extremely slow.
+ * https://github.com/square/okio/issues/171
+ */
+ @Test
+ fun indexOfByteStringAcrossSegmentBoundaries() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 2 - 3))
+ sink.writeUtf8("bcdefg")
+ sink.emit()
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("ab".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abc".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcd".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcde".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcdef".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 4).toLong(), source.indexOf("abcdefg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 3).toLong(), source.indexOf("bcdefg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 2).toLong(), source.indexOf("cdefg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 - 1).toLong(), source.indexOf("defg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2).toLong(), source.indexOf("efg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 + 1).toLong(), source.indexOf("fg".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE * 2 + 2).toLong(), source.indexOf("g".encodeUtf8()))
+ }
+
+ @Test
+ fun indexOfElement() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertEquals(0, source.indexOfElement("DEFGaHIJK".encodeUtf8()))
+ assertEquals(1, source.indexOfElement("DEFGHIJKb".encodeUtf8()))
+ assertEquals((SEGMENT_SIZE + 1).toLong(), source.indexOfElement("cDEFGHIJK".encodeUtf8()))
+ assertEquals(1, source.indexOfElement("DEFbGHIc".encodeUtf8()))
+ assertEquals(-1L, source.indexOfElement("DEFGHIJK".encodeUtf8()))
+ assertEquals(-1L, source.indexOfElement("".encodeUtf8()))
+ }
+
+ @Test
+ fun indexOfElementWithOffset() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertEquals(-1, source.indexOfElement("DEFGaHIJK".encodeUtf8(), 1))
+ assertEquals(15, source.indexOfElement("DEFGHIJKb".encodeUtf8(), 15))
+ }
+
+ @Test
+ fun indexOfByteWithFromIndex() {
+ sink.writeUtf8("aaa")
+ sink.emit()
+ assertEquals(0, source.indexOf('a'.code.toByte()))
+ assertEquals(0, source.indexOf('a'.code.toByte(), 0))
+ assertEquals(1, source.indexOf('a'.code.toByte(), 1))
+ assertEquals(2, source.indexOf('a'.code.toByte(), 2))
+ }
+
+ @Test
+ fun indexOfByteStringWithFromIndex() {
+ sink.writeUtf8("aaa")
+ sink.emit()
+ assertEquals(0, source.indexOf("a".encodeUtf8()))
+ assertEquals(0, source.indexOf("a".encodeUtf8(), 0))
+ assertEquals(1, source.indexOf("a".encodeUtf8(), 1))
+ assertEquals(2, source.indexOf("a".encodeUtf8(), 2))
+ }
+
+ @Test
+ fun indexOfElementWithFromIndex() {
+ sink.writeUtf8("aaa")
+ sink.emit()
+ assertEquals(0, source.indexOfElement("a".encodeUtf8()))
+ assertEquals(0, source.indexOfElement("a".encodeUtf8(), 0))
+ assertEquals(1, source.indexOfElement("a".encodeUtf8(), 1))
+ assertEquals(2, source.indexOfElement("a".encodeUtf8(), 2))
+ }
+
+ @Test
+ fun request() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ assertTrue(source.request((SEGMENT_SIZE + 2).toLong()))
+ assertFalse(source.request((SEGMENT_SIZE + 3).toLong()))
+ }
+
+ @Test
+ fun require() {
+ sink.writeUtf8("a").writeUtf8("b".repeat(SEGMENT_SIZE)).writeUtf8("c")
+ sink.emit()
+ source.require((SEGMENT_SIZE + 2).toLong())
+ try {
+ source.require((SEGMENT_SIZE + 3).toLong())
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun inputStream() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ val `in` = source.inputStream()
+ val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte())
+ var read = `in`.read(bytes)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("azz", bytes)
+ read = `in`.read(bytes)
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("bzz", bytes)
+ read = `in`.read(bytes)
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("czz", bytes)
+ } else {
+ assertEquals(3, read.toLong())
+ assertByteArrayEquals("abc", bytes)
+ }
+ assertEquals(-1, `in`.read().toLong())
+ }
+
+ @Test
+ fun inputStreamOffsetCount() {
+ sink.writeUtf8("abcde")
+ sink.emit()
+ val `in` = source.inputStream()
+ val bytes = byteArrayOf('z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte(), 'z'.code.toByte())
+ val read = `in`.read(bytes, 1, 3)
+ if (factory.isOneByteAtATime) {
+ assertEquals(1, read.toLong())
+ assertByteArrayEquals("zazzz", bytes)
+ } else {
+ assertEquals(3, read.toLong())
+ assertByteArrayEquals("zabcz", bytes)
+ }
+ }
+
+ @Test
+ fun inputStreamSkip() {
+ sink.writeUtf8("abcde")
+ sink.emit()
+ val `in` = source.inputStream()
+ assertEquals(4, `in`.skip(4))
+ assertEquals('e'.code.toLong(), `in`.read().toLong())
+ sink.writeUtf8("abcde")
+ sink.emit()
+ assertEquals(5, `in`.skip(10)) // Try to skip too much.
+ assertEquals(0, `in`.skip(1)) // Try to skip when exhausted.
+ }
+
+ @Test
+ fun inputStreamCharByChar() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ val `in` = source.inputStream()
+ assertEquals('a'.code.toLong(), `in`.read().toLong())
+ assertEquals('b'.code.toLong(), `in`.read().toLong())
+ assertEquals('c'.code.toLong(), `in`.read().toLong())
+ assertEquals(-1, `in`.read().toLong())
+ }
+
+ @Test
+ fun inputStreamBounds() {
+ sink.writeUtf8("a".repeat(100))
+ sink.emit()
+ val `in` = source.inputStream()
+ try {
+ `in`.read(ByteArray(100), 50, 51)
+ fail()
+ } catch (expected: java.lang.ArrayIndexOutOfBoundsException) {
+ }
+ }
+
+ @Test
+ fun longHexString() {
+ assertLongHexString("8000000000000000", -0x7fffffffffffffffL - 1L)
+ assertLongHexString("fffffffffffffffe", -0x2L)
+ assertLongHexString("FFFFFFFFFFFFFFFe", -0x2L)
+ assertLongHexString("ffffffffffffffff", -0x1L)
+ assertLongHexString("FFFFFFFFFFFFFFFF", -0x1L)
+ assertLongHexString("0000000000000000", 0x0)
+ assertLongHexString("0000000000000001", 0x1)
+ assertLongHexString("7999999999999999", 0x7999999999999999L)
+ assertLongHexString("FF", 0xFF)
+ assertLongHexString("0000000000000001", 0x1)
+ }
+
+ @Test
+ fun hexStringWithManyLeadingZeros() {
+ assertLongHexString("00000000000000001", 0x1)
+ assertLongHexString("0000000000000000ffffffffffffffff", -0x1L)
+ assertLongHexString("00000000000000007fffffffffffffff", 0x7fffffffffffffffL)
+ assertLongHexString("0".repeat(SEGMENT_SIZE + 1) + "1", 0x1)
+ }
+
+ private fun assertLongHexString(s: String, expected: Long) {
+ sink.writeUtf8(s)
+ sink.emit()
+ val actual = source.readHexadecimalUnsignedLong()
+ assertEquals("$s --> $expected", expected, actual)
+ }
+
+ @Test
+ fun longHexStringAcrossSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 8)).writeUtf8("FFFFFFFFFFFFFFFF")
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 8).toLong())
+ assertEquals(-1, source.readHexadecimalUnsignedLong())
+ }
+
+ @Test
+ fun longHexStringTooLongThrows() {
+ try {
+ sink.writeUtf8("fffffffffffffffff")
+ sink.emit()
+ source.readHexadecimalUnsignedLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: fffffffffffffffff", e.message)
+ }
+ }
+
+ @Test
+ fun longHexStringTooShortThrows() {
+ try {
+ sink.writeUtf8(" ")
+ sink.emit()
+ source.readHexadecimalUnsignedLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Expected leading [0-9a-fA-F] character but was 0x20", e.message)
+ }
+ }
+
+ @Test
+ fun longHexEmptySourceThrows() {
+ try {
+ sink.writeUtf8("")
+ sink.emit()
+ source.readHexadecimalUnsignedLong()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun longDecimalString() {
+ assertLongDecimalString("-9223372036854775808", -9223372036854775807L - 1L)
+ assertLongDecimalString("-1", -1L)
+ assertLongDecimalString("0", 0L)
+ assertLongDecimalString("1", 1L)
+ assertLongDecimalString("9223372036854775807", 9223372036854775807L)
+ assertLongDecimalString("00000001", 1L)
+ assertLongDecimalString("-000001", -1L)
+ }
+
+ private fun assertLongDecimalString(s: String, expected: Long) {
+ sink.writeUtf8(s)
+ sink.writeUtf8("zzz")
+ sink.emit()
+ val actual = source.readDecimalLong()
+ assertEquals("$s --> $expected", expected, actual)
+ assertEquals("zzz", source.readUtf8())
+ }
+
+ @Test
+ fun longDecimalStringAcrossSegment() {
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE - 8)).writeUtf8("1234567890123456")
+ sink.writeUtf8("zzz")
+ sink.emit()
+ source.skip((SEGMENT_SIZE - 8).toLong())
+ assertEquals(1234567890123456L, source.readDecimalLong())
+ assertEquals("zzz", source.readUtf8())
+ }
+
+ @Test
+ fun longDecimalStringTooLongThrows() {
+ try {
+ sink.writeUtf8("12345678901234567890") // Too many digits.
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: 12345678901234567890", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalStringTooHighThrows() {
+ try {
+ sink.writeUtf8("9223372036854775808") // Right size but cannot fit.
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: 9223372036854775808", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalStringTooLowThrows() {
+ try {
+ sink.writeUtf8("-9223372036854775809") // Right size but cannot fit.
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Number too large: -9223372036854775809", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalStringTooShortThrows() {
+ try {
+ sink.writeUtf8(" ")
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (e: NumberFormatException) {
+ assertEquals("Expected a digit or '-' but was 0x20", e.message)
+ }
+ }
+
+ @Test
+ fun longDecimalEmptyThrows() {
+ try {
+ sink.writeUtf8("")
+ sink.emit()
+ source.readDecimalLong()
+ fail()
+ } catch (expected: EOFException) {
+ }
+ }
+
+ @Test
+ fun codePoints() {
+ sink.write("7f".decodeHex())
+ sink.emit()
+ assertEquals(0x7f, source.readUtf8CodePoint().toLong())
+ sink.write("dfbf".decodeHex())
+ sink.emit()
+ assertEquals(0x07ff, source.readUtf8CodePoint().toLong())
+ sink.write("efbfbf".decodeHex())
+ sink.emit()
+ assertEquals(0xffff, source.readUtf8CodePoint().toLong())
+ sink.write("f48fbfbf".decodeHex())
+ sink.emit()
+ assertEquals(0x10ffff, source.readUtf8CodePoint().toLong())
+ }
+
+ @Test
+ fun decimalStringWithManyLeadingZeros() {
+ assertLongDecimalString("00000000000000001", 1)
+ assertLongDecimalString("00000000000000009223372036854775807", 9223372036854775807L)
+ assertLongDecimalString("-00000000000000009223372036854775808", -9223372036854775807L - 1L)
+ assertLongDecimalString("0".repeat(SEGMENT_SIZE + 1) + "1", 1)
+ }
+
+ @Test
+ fun select() {
+ val options = of(
+ "ROCK".encodeUtf8(),
+ "SCISSORS".encodeUtf8(),
+ "PAPER".encodeUtf8(),
+ )
+ sink.writeUtf8("PAPER,SCISSORS,ROCK")
+ sink.emit()
+ assertEquals(2, source.select(options).toLong())
+ assertEquals(','.code.toLong(), source.readByte().toLong())
+ assertEquals(1, source.select(options).toLong())
+ assertEquals(','.code.toLong(), source.readByte().toLong())
+ assertEquals(0, source.select(options).toLong())
+ assertTrue(source.exhausted())
+ }
+
+ /** Note that this test crashes the VM on Android. */
+ @Test
+ fun selectSpanningMultipleSegments() {
+ val commonPrefix = randomBytes(SEGMENT_SIZE + 10)
+ val a = Buffer().write(commonPrefix).writeUtf8("a").readByteString()
+ val bc = Buffer().write(commonPrefix).writeUtf8("bc").readByteString()
+ val bd = Buffer().write(commonPrefix).writeUtf8("bd").readByteString()
+ val options = of(a, bc, bd)
+ sink.write(bd)
+ sink.write(a)
+ sink.write(bc)
+ sink.emit()
+ assertEquals(2, source.select(options).toLong())
+ assertEquals(0, source.select(options).toLong())
+ assertEquals(1, source.select(options).toLong())
+ assertTrue(source.exhausted())
+ }
+
+ @Test
+ fun selectNotFound() {
+ val options = of(
+ "ROCK".encodeUtf8(),
+ "SCISSORS".encodeUtf8(),
+ "PAPER".encodeUtf8(),
+ )
+ sink.writeUtf8("SPOCK")
+ sink.emit()
+ assertEquals(-1, source.select(options).toLong())
+ assertEquals("SPOCK", source.readUtf8())
+ }
+
+ @Test
+ fun selectValuesHaveCommonPrefix() {
+ val options = of(
+ "abcd".encodeUtf8(),
+ "abce".encodeUtf8(),
+ "abcc".encodeUtf8(),
+ )
+ sink.writeUtf8("abcc").writeUtf8("abcd").writeUtf8("abce")
+ sink.emit()
+ assertEquals(2, source.select(options).toLong())
+ assertEquals(0, source.select(options).toLong())
+ assertEquals(1, source.select(options).toLong())
+ }
+
+ @Test
+ fun selectLongerThanSource() {
+ val options = of(
+ "abcd".encodeUtf8(),
+ "abce".encodeUtf8(),
+ "abcc".encodeUtf8(),
+ )
+ sink.writeUtf8("abc")
+ sink.emit()
+ assertEquals(-1, source.select(options).toLong())
+ assertEquals("abc", source.readUtf8())
+ }
+
+ @Test
+ fun selectReturnsFirstByteStringThatMatches() {
+ val options = of(
+ "abcd".encodeUtf8(),
+ "abc".encodeUtf8(),
+ "abcde".encodeUtf8(),
+ )
+ sink.writeUtf8("abcdef")
+ sink.emit()
+ assertEquals(0, source.select(options).toLong())
+ assertEquals("ef", source.readUtf8())
+ }
+
+ @Test
+ fun selectFromEmptySource() {
+ val options = of(
+ "abc".encodeUtf8(),
+ "def".encodeUtf8(),
+ )
+ assertEquals(-1, source.select(options).toLong())
+ }
+
+ @Test
+ fun selectNoByteStringsFromEmptySource() {
+ val options = Options.of()
+ assertEquals(-1, source.select(options).toLong())
+ }
+
+ @Test
+ fun peek() {
+ sink.writeUtf8("abcdefghi")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek = source.peek()
+ assertEquals("def", peek.readUtf8(3))
+ assertEquals("ghi", peek.readUtf8(3))
+ assertFalse(peek.request(1))
+ assertEquals("def", source.readUtf8(3))
+ }
+
+ @Test
+ fun peekMultiple() {
+ sink.writeUtf8("abcdefghi")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek1 = source.peek()
+ val peek2 = source.peek()
+ assertEquals("def", peek1.readUtf8(3))
+ assertEquals("def", peek2.readUtf8(3))
+ assertEquals("ghi", peek2.readUtf8(3))
+ assertFalse(peek2.request(1))
+ assertEquals("ghi", peek1.readUtf8(3))
+ assertFalse(peek1.request(1))
+ assertEquals("def", source.readUtf8(3))
+ }
+
+ @Test
+ fun peekLarge() {
+ sink.writeUtf8("abcdef")
+ sink.writeUtf8("g".repeat(2 * SEGMENT_SIZE))
+ sink.writeUtf8("hij")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek = source.peek()
+ assertEquals("def", peek.readUtf8(3))
+ peek.skip((2 * SEGMENT_SIZE).toLong())
+ assertEquals("hij", peek.readUtf8(3))
+ assertFalse(peek.request(1))
+ assertEquals("def", source.readUtf8(3))
+ source.skip((2 * SEGMENT_SIZE).toLong())
+ assertEquals("hij", source.readUtf8(3))
+ }
+
+ @Test
+ fun peekInvalid() {
+ sink.writeUtf8("abcdefghi")
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+ val peek = source.peek()
+ assertEquals("def", peek.readUtf8(3))
+ assertEquals("ghi", peek.readUtf8(3))
+ assertFalse(peek.request(1))
+ assertEquals("def", source.readUtf8(3))
+ try {
+ peek.readUtf8()
+ fail()
+ } catch (e: IllegalStateException) {
+ assertEquals("Peek source is invalid because upstream source was used", e.message)
+ }
+ }
+
+ @Test
+ fun peekSegmentThenInvalid() {
+ sink.writeUtf8("abc")
+ sink.writeUtf8("d".repeat(2 * SEGMENT_SIZE))
+ sink.emit()
+ assertEquals("abc", source.readUtf8(3))
+
+ // Peek a little data and skip the rest of the upstream source
+ val peek = source.peek()
+ assertEquals("ddd", peek.readUtf8(3))
+ source.readAll(blackholeSink())
+
+ // Skip the rest of the buffered data
+ peek.skip(peek.buffer.size)
+ try {
+ peek.readByte()
+ fail()
+ } catch (e: IllegalStateException) {
+ assertEquals("Peek source is invalid because upstream source was used", e.message)
+ }
+ }
+
+ @Test
+ fun peekDoesntReadTooMuch() {
+ // 6 bytes in source's buffer plus 3 bytes upstream.
+ sink.writeUtf8("abcdef")
+ sink.emit()
+ source.require(6L)
+ sink.writeUtf8("ghi")
+ sink.emit()
+ val peek = source.peek()
+
+ // Read 3 bytes. This reads some of the buffered data.
+ assertTrue(peek.request(3))
+ if (source !is Buffer) {
+ assertEquals(6, source.buffer.size)
+ assertEquals(6, peek.buffer.size)
+ }
+ assertEquals("abc", peek.readUtf8(3L))
+
+ // Read 3 more bytes. This exhausts the buffered data.
+ assertTrue(peek.request(3))
+ if (source !is Buffer) {
+ assertEquals(6, source.buffer.size)
+ assertEquals(3, peek.buffer.size)
+ }
+ assertEquals("def", peek.readUtf8(3L))
+
+ // Read 3 more bytes. This draws new bytes.
+ assertTrue(peek.request(3))
+ assertEquals(9, source.buffer.size)
+ assertEquals(3, peek.buffer.size)
+ assertEquals("ghi", peek.readUtf8(3L))
+ }
+
+ @Test
+ fun rangeEquals() {
+ sink.writeUtf8("A man, a plan, a canal. Panama.")
+ sink.emit()
+ assertTrue(source.rangeEquals(7, "a plan".encodeUtf8()))
+ assertTrue(source.rangeEquals(0, "A man".encodeUtf8()))
+ assertTrue(source.rangeEquals(24, "Panama".encodeUtf8()))
+ assertFalse(source.rangeEquals(24, "Panama. Panama. Panama.".encodeUtf8()))
+ }
+
+ @Test
+ fun rangeEqualsWithOffsetAndCount() {
+ sink.writeUtf8("A man, a plan, a canal. Panama.")
+ sink.emit()
+ assertTrue(source.rangeEquals(7, "aaa plannn".encodeUtf8(), 2, 6))
+ assertTrue(source.rangeEquals(0, "AAA mannn".encodeUtf8(), 2, 5))
+ assertTrue(source.rangeEquals(24, "PPPanamaaa".encodeUtf8(), 2, 6))
+ }
+
+ @Test
+ fun rangeEqualsOnlyReadsUntilMismatch() {
+ Assume.assumeTrue(factory === Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE) // Other sources read in chunks anyway.
+ sink.writeUtf8("A man, a plan, a canal. Panama.")
+ sink.emit()
+ assertFalse(source.rangeEquals(0, "A man.".encodeUtf8()))
+ assertEquals("A man,", source.buffer.readUtf8())
+ }
+
+ @Test
+ fun rangeEqualsArgumentValidation() {
+ // Negative source offset.
+ assertFalse(source.rangeEquals(-1, "A".encodeUtf8()))
+ // Negative bytes offset.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), -1, 1))
+ // Bytes offset longer than bytes length.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 2, 1))
+ // Negative byte count.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 0, -1))
+ // Byte count longer than bytes length.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 0, 2))
+ // Bytes offset plus byte count longer than bytes length.
+ assertFalse(source.rangeEquals(0, "A".encodeUtf8(), 1, 1))
+ }
+
+ @Test
+ fun readNioBuffer() {
+ val expected = if (factory.isOneByteAtATime) "a" else "abcdefg"
+ sink.writeUtf8("abcdefg")
+ sink.emit()
+ val nioByteBuffer = ByteBuffer.allocate(1024)
+ val byteCount = source.read(nioByteBuffer)
+ assertEquals(expected.length.toLong(), byteCount.toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.position().toLong())
+ assertEquals(nioByteBuffer.capacity().toLong(), nioByteBuffer.limit().toLong())
+ (nioByteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ val data = ByteArray(expected.length)
+ nioByteBuffer[data]
+ assertEquals(expected, data.decodeToString())
+ }
+
+ /** Note that this test crashes the VM on Android. */
+ @Test
+ fun readLargeNioBufferOnlyReadsOneSegment() {
+ val expected = if (factory.isOneByteAtATime) "a" else "a".repeat(SEGMENT_SIZE)
+ sink.writeUtf8("a".repeat(SEGMENT_SIZE * 4))
+ sink.emit()
+ val nioByteBuffer = ByteBuffer.allocate(SEGMENT_SIZE * 3)
+ val byteCount = source.read(nioByteBuffer)
+ assertEquals(expected.length.toLong(), byteCount.toLong())
+ assertEquals(expected.length.toLong(), nioByteBuffer.position().toLong())
+ assertEquals(nioByteBuffer.capacity().toLong(), nioByteBuffer.limit().toLong())
+ (nioByteBuffer as java.nio.Buffer).flip() // Cast necessary for Java 8.
+ val data = ByteArray(expected.length)
+ nioByteBuffer[data]
+ assertEquals(expected, data.decodeToString())
+ }
+
+ @Test
+ fun factorySegmentSizes() {
+ sink.writeUtf8("abc")
+ sink.emit()
+ source.require(3)
+ if (factory.isOneByteAtATime) {
+ assertEquals(mutableListOf(1, 1, 1), segmentSizes(source.buffer))
+ } else {
+ assertEquals(listOf(3), segmentSizes(source.buffer))
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun parameters(): List<Array<Any>> {
+ return listOf(
+ arrayOf(Factory.BUFFER),
+ arrayOf(Factory.REAL_BUFFERED_SOURCE),
+ arrayOf(Factory.ONE_BYTE_AT_A_TIME_BUFFERED_SOURCE),
+ arrayOf(Factory.ONE_BYTE_AT_A_TIME_BUFFER),
+ arrayOf(Factory.PEEK_BUFFER),
+ arrayOf(Factory.PEEK_BUFFERED_SOURCE),
+ )
+ }
+ }
+}