| /* |
| * 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 okio.ByteString.Companion.decodeHex |
| import kotlin.random.Random |
| import kotlin.test.Test |
| import kotlin.test.assertEquals |
| import kotlin.test.assertFailsWith |
| import kotlin.test.assertFalse |
| import kotlin.test.assertTrue |
| |
| /** |
| * Tests solely for the behavior of Buffer's implementation. For generic BufferedSink or |
| * BufferedSource behavior use BufferedSinkTest or BufferedSourceTest, respectively. |
| */ |
| class CommonBufferTest { |
| @Test fun readAndWriteUtf8() { |
| val buffer = Buffer() |
| buffer.writeUtf8("ab") |
| assertEquals(2, buffer.size) |
| buffer.writeUtf8("cdef") |
| assertEquals(6, buffer.size) |
| assertEquals("abcd", buffer.readUtf8(4)) |
| assertEquals(2, buffer.size) |
| assertEquals("ef", buffer.readUtf8(2)) |
| assertEquals(0, buffer.size) |
| assertFailsWith<EOFException> { |
| buffer.readUtf8(1) |
| } |
| } |
| |
| /** Buffer's toString is the same as ByteString's. */ |
| @Test fun bufferToString() { |
| assertEquals("[size=0]", Buffer().toString()) |
| assertEquals( |
| "[text=a\\r\\nb\\nc\\rd\\\\e]", |
| Buffer().writeUtf8("a\r\nb\nc\rd\\e").toString() |
| ) |
| assertEquals( |
| "[text=Tyrannosaur]", |
| Buffer().writeUtf8("Tyrannosaur").toString() |
| ) |
| assertEquals( |
| "[text=təˈranəˌsôr]", |
| Buffer() |
| .write("74c999cb8872616ec999cb8c73c3b472".decodeHex()) |
| .toString() |
| ) |
| assertEquals( |
| "[hex=0000000000000000000000000000000000000000000000000000000000000000000000000000" + |
| "0000000000000000000000000000000000000000000000000000]", |
| Buffer().write(ByteArray(64)).toString() |
| ) |
| } |
| |
| @Test fun multipleSegmentBuffers() { |
| val buffer = Buffer() |
| buffer.writeUtf8('a'.repeat(1000)) |
| buffer.writeUtf8('b'.repeat(2500)) |
| buffer.writeUtf8('c'.repeat(5000)) |
| buffer.writeUtf8('d'.repeat(10000)) |
| buffer.writeUtf8('e'.repeat(25000)) |
| buffer.writeUtf8('f'.repeat(50000)) |
| |
| assertEquals('a'.repeat(999), buffer.readUtf8(999)) // a...a |
| assertEquals("a" + 'b'.repeat(2500) + "c", buffer.readUtf8(2502)) // ab...bc |
| assertEquals('c'.repeat(4998), buffer.readUtf8(4998)) // c...c |
| assertEquals("c" + 'd'.repeat(10000) + "e", buffer.readUtf8(10002)) // cd...de |
| assertEquals('e'.repeat(24998), buffer.readUtf8(24998)) // e...e |
| assertEquals("e" + 'f'.repeat(50000), buffer.readUtf8(50001)) // ef...f |
| assertEquals(0, buffer.size) |
| } |
| |
| @Test fun fillAndDrainPool() { |
| val buffer = Buffer() |
| |
| // Take 2 * MAX_SIZE segments. This will drain the pool, even if other tests filled it. |
| buffer.write(ByteArray(SegmentPool.MAX_SIZE)) |
| buffer.write(ByteArray(SegmentPool.MAX_SIZE)) |
| assertEquals(0, SegmentPool.byteCount) |
| |
| // Recycle MAX_SIZE segments. They're all in the pool. |
| buffer.skip(SegmentPool.MAX_SIZE.toLong()) |
| assertEquals(SegmentPool.MAX_SIZE, SegmentPool.byteCount) |
| |
| // Recycle MAX_SIZE more segments. The pool is full so they get garbage collected. |
| buffer.skip(SegmentPool.MAX_SIZE.toLong()) |
| assertEquals(SegmentPool.MAX_SIZE, SegmentPool.byteCount) |
| |
| // Take MAX_SIZE segments to drain the pool. |
| buffer.write(ByteArray(SegmentPool.MAX_SIZE)) |
| assertEquals(0, SegmentPool.byteCount) |
| |
| // Take MAX_SIZE more segments. The pool is drained so these will need to be allocated. |
| buffer.write(ByteArray(SegmentPool.MAX_SIZE)) |
| assertEquals(0, SegmentPool.byteCount) |
| } |
| |
| @Test fun moveBytesBetweenBuffersShareSegment() { |
| val size = Segment.SIZE / 2 - 1 |
| val segmentSizes = moveBytesBetweenBuffers('a'.repeat(size), 'b'.repeat(size)) |
| assertEquals(listOf(size * 2), segmentSizes) |
| } |
| |
| @Test fun moveBytesBetweenBuffersReassignSegment() { |
| val size = Segment.SIZE / 2 + 1 |
| val segmentSizes = moveBytesBetweenBuffers('a'.repeat(size), 'b'.repeat(size)) |
| assertEquals(listOf(size, size), segmentSizes) |
| } |
| |
| @Test fun moveBytesBetweenBuffersMultipleSegments() { |
| val size = 3 * Segment.SIZE + 1 |
| val segmentSizes = moveBytesBetweenBuffers('a'.repeat(size), 'b'.repeat(size)) |
| assertEquals( |
| listOf( |
| Segment.SIZE, Segment.SIZE, Segment.SIZE, 1, |
| Segment.SIZE, Segment.SIZE, Segment.SIZE, 1 |
| ), |
| segmentSizes |
| ) |
| } |
| |
| private fun moveBytesBetweenBuffers(vararg contents: String): List<Int> { |
| val expected = StringBuilder() |
| val buffer = Buffer() |
| for (s in contents) { |
| val source = Buffer() |
| source.writeUtf8(s) |
| buffer.writeAll(source) |
| expected.append(s) |
| } |
| val segmentSizes = segmentSizes(buffer) |
| assertEquals(expected.toString(), buffer.readUtf8(expected.length.toLong())) |
| return segmentSizes |
| } |
| |
| /** The big part of source's first segment is being moved. */ |
| @Test fun writeSplitSourceBufferLeft() { |
| val writeSize = Segment.SIZE / 2 + 1 |
| |
| val sink = Buffer() |
| sink.writeUtf8('b'.repeat(Segment.SIZE - 10)) |
| |
| val source = Buffer() |
| source.writeUtf8('a'.repeat(Segment.SIZE * 2)) |
| sink.write(source, writeSize.toLong()) |
| |
| assertEquals(listOf(Segment.SIZE - 10, writeSize), segmentSizes(sink)) |
| assertEquals(listOf(Segment.SIZE - writeSize, Segment.SIZE), segmentSizes(source)) |
| } |
| |
| /** The big part of source's first segment is staying put. */ |
| @Test fun writeSplitSourceBufferRight() { |
| val writeSize = Segment.SIZE / 2 - 1 |
| |
| val sink = Buffer() |
| sink.writeUtf8('b'.repeat(Segment.SIZE - 10)) |
| |
| val source = Buffer() |
| source.writeUtf8('a'.repeat(Segment.SIZE * 2)) |
| sink.write(source, writeSize.toLong()) |
| |
| assertEquals(listOf(Segment.SIZE - 10, writeSize), segmentSizes(sink)) |
| assertEquals(listOf(Segment.SIZE - writeSize, Segment.SIZE), segmentSizes(source)) |
| } |
| |
| @Test fun writePrefixDoesntSplit() { |
| val sink = Buffer() |
| sink.writeUtf8('b'.repeat(10)) |
| |
| val source = Buffer() |
| source.writeUtf8('a'.repeat(Segment.SIZE * 2)) |
| sink.write(source, 20) |
| |
| assertEquals(listOf(30), segmentSizes(sink)) |
| assertEquals(listOf(Segment.SIZE - 20, Segment.SIZE), segmentSizes(source)) |
| assertEquals(30, sink.size) |
| assertEquals((Segment.SIZE * 2 - 20).toLong(), source.size) |
| } |
| |
| @Test fun writePrefixDoesntSplitButRequiresCompact() { |
| val sink = Buffer() |
| sink.writeUtf8('b'.repeat(Segment.SIZE - 10)) // limit = size - 10 |
| sink.readUtf8((Segment.SIZE - 20).toLong()) // pos = size = 20 |
| |
| val source = Buffer() |
| source.writeUtf8('a'.repeat(Segment.SIZE * 2)) |
| sink.write(source, 20) |
| |
| assertEquals(listOf(30), segmentSizes(sink)) |
| assertEquals(listOf(Segment.SIZE - 20, Segment.SIZE), segmentSizes(source)) |
| assertEquals(30, sink.size) |
| assertEquals((Segment.SIZE * 2 - 20).toLong(), source.size) |
| } |
| |
| @Test fun moveAllRequestedBytesWithRead() { |
| val sink = Buffer() |
| sink.writeUtf8('a'.repeat(10)) |
| |
| val source = Buffer() |
| source.writeUtf8('b'.repeat(15)) |
| |
| assertEquals(10, source.read(sink, 10)) |
| assertEquals(20, sink.size) |
| assertEquals(5, source.size) |
| assertEquals('a'.repeat(10) + 'b'.repeat(10), sink.readUtf8(20)) |
| } |
| |
| @Test fun moveFewerThanRequestedBytesWithRead() { |
| val sink = Buffer() |
| sink.writeUtf8('a'.repeat(10)) |
| |
| val source = Buffer() |
| source.writeUtf8('b'.repeat(20)) |
| |
| assertEquals(20, source.read(sink, 25)) |
| assertEquals(30, sink.size) |
| assertEquals(0, source.size) |
| assertEquals('a'.repeat(10) + 'b'.repeat(20), sink.readUtf8(30)) |
| } |
| |
| @Test fun indexOfWithOffset() { |
| val buffer = Buffer() |
| val halfSegment = Segment.SIZE / 2 |
| buffer.writeUtf8('a'.repeat(halfSegment)) |
| buffer.writeUtf8('b'.repeat(halfSegment)) |
| buffer.writeUtf8('c'.repeat(halfSegment)) |
| buffer.writeUtf8('d'.repeat(halfSegment)) |
| assertEquals(0, buffer.indexOf('a'.toByte(), 0)) |
| assertEquals((halfSegment - 1).toLong(), buffer.indexOf('a'.toByte(), (halfSegment - 1).toLong())) |
| assertEquals(halfSegment.toLong(), buffer.indexOf('b'.toByte(), (halfSegment - 1).toLong())) |
| assertEquals((halfSegment * 2).toLong(), buffer.indexOf('c'.toByte(), (halfSegment - 1).toLong())) |
| assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.toByte(), (halfSegment - 1).toLong())) |
| assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.toByte(), (halfSegment * 2).toLong())) |
| assertEquals((halfSegment * 3).toLong(), buffer.indexOf('d'.toByte(), (halfSegment * 3).toLong())) |
| assertEquals((halfSegment * 4 - 1).toLong(), buffer.indexOf('d'.toByte(), (halfSegment * 4 - 1).toLong())) |
| } |
| |
| @Test fun byteAt() { |
| val buffer = Buffer() |
| buffer.writeUtf8("a") |
| buffer.writeUtf8('b'.repeat(Segment.SIZE)) |
| buffer.writeUtf8("c") |
| assertEquals('a'.toLong(), buffer[0].toLong()) |
| assertEquals('a'.toLong(), buffer[0].toLong()) // getByte doesn't mutate! |
| assertEquals('c'.toLong(), buffer[buffer.size - 1].toLong()) |
| assertEquals('b'.toLong(), buffer[buffer.size - 2].toLong()) |
| assertEquals('b'.toLong(), buffer[buffer.size - 3].toLong()) |
| } |
| |
| @Test fun getByteOfEmptyBuffer() { |
| val buffer = Buffer() |
| assertFailsWith<IndexOutOfBoundsException> { |
| buffer[0] |
| } |
| } |
| |
| @Test |
| fun writePrefixToEmptyBuffer() { |
| val sink = Buffer() |
| val source = Buffer() |
| source.writeUtf8("abcd") |
| sink.write(source, 2) |
| assertEquals("ab", sink.readUtf8(2)) |
| } |
| |
| @Suppress("ReplaceAssertBooleanWithAssertEquality") |
| @Test fun equalsAndHashCodeEmpty() { |
| val a = Buffer() |
| val b = Buffer() |
| assertTrue(a == b) |
| assertTrue(a.hashCode() == b.hashCode()) |
| } |
| |
| @Suppress("ReplaceAssertBooleanWithAssertEquality") |
| @Test fun equalsAndHashCode() { |
| val a = Buffer().writeUtf8("dog") |
| val b = Buffer().writeUtf8("hotdog") |
| assertFalse(a == b) |
| assertFalse(a.hashCode() == b.hashCode()) |
| |
| b.readUtf8(3) // Leaves b containing 'dog'. |
| assertTrue(a == b) |
| assertTrue(a.hashCode() == b.hashCode()) |
| } |
| |
| @Suppress("ReplaceAssertBooleanWithAssertEquality") |
| @Test fun equalsAndHashCodeSpanningSegments() { |
| val data = ByteArray(1024 * 1024) |
| val dice = Random(0) |
| dice.nextBytes(data) |
| |
| val a = bufferWithRandomSegmentLayout(dice, data) |
| val b = bufferWithRandomSegmentLayout(dice, data) |
| assertTrue(a == b) |
| assertTrue(a.hashCode() == b.hashCode()) |
| |
| data[data.size / 2]++ // Change a single byte. |
| val c = bufferWithRandomSegmentLayout(dice, data) |
| assertFalse(a == c) |
| assertFalse(a.hashCode() == c.hashCode()) |
| } |
| |
| /** |
| * When writing data that's already buffered, there's no reason to page the |
| * data by segment. |
| */ |
| @Test fun readAllWritesAllSegmentsAtOnce() { |
| val write1 = Buffer().writeUtf8( |
| 'a'.repeat(Segment.SIZE) + |
| 'b'.repeat(Segment.SIZE) + |
| 'c'.repeat(Segment.SIZE) |
| ) |
| |
| val source = Buffer().writeUtf8( |
| 'a'.repeat(Segment.SIZE) + |
| 'b'.repeat(Segment.SIZE) + |
| 'c'.repeat(Segment.SIZE) |
| ) |
| |
| val mockSink = MockSink() |
| |
| assertEquals((Segment.SIZE * 3).toLong(), source.readAll(mockSink)) |
| assertEquals(0, source.size) |
| mockSink.assertLog("write($write1, ${write1.size})") |
| } |
| |
| @Test fun writeAllMultipleSegments() { |
| val source = Buffer().writeUtf8('a'.repeat(Segment.SIZE * 3)) |
| val sink = Buffer() |
| |
| assertEquals((Segment.SIZE * 3).toLong(), sink.writeAll(source)) |
| assertEquals(0, source.size) |
| assertEquals('a'.repeat(Segment.SIZE * 3), sink.readUtf8()) |
| } |
| |
| @Test fun copyTo() { |
| val source = Buffer() |
| source.writeUtf8("party") |
| |
| val target = Buffer() |
| source.copyTo(target, 1, 3) |
| |
| assertEquals("art", target.readUtf8()) |
| assertEquals("party", source.readUtf8()) |
| } |
| |
| @Test fun copyToOnSegmentBoundary() { |
| val `as` = 'a'.repeat(Segment.SIZE) |
| val bs = 'b'.repeat(Segment.SIZE) |
| val cs = 'c'.repeat(Segment.SIZE) |
| val ds = 'd'.repeat(Segment.SIZE) |
| |
| val source = Buffer() |
| source.writeUtf8(`as`) |
| source.writeUtf8(bs) |
| source.writeUtf8(cs) |
| |
| val target = Buffer() |
| target.writeUtf8(ds) |
| |
| source.copyTo(target, `as`.length.toLong(), (bs.length + cs.length).toLong()) |
| assertEquals(ds + bs + cs, target.readUtf8()) |
| } |
| |
| @Test fun copyToOffSegmentBoundary() { |
| val `as` = 'a'.repeat(Segment.SIZE - 1) |
| val bs = 'b'.repeat(Segment.SIZE + 2) |
| val cs = 'c'.repeat(Segment.SIZE - 4) |
| val ds = 'd'.repeat(Segment.SIZE + 8) |
| |
| val source = Buffer() |
| source.writeUtf8(`as`) |
| source.writeUtf8(bs) |
| source.writeUtf8(cs) |
| |
| val target = Buffer() |
| target.writeUtf8(ds) |
| |
| source.copyTo(target, `as`.length.toLong(), (bs.length + cs.length).toLong()) |
| assertEquals(ds + bs + cs, target.readUtf8()) |
| } |
| |
| @Test fun copyToSourceAndTargetCanBeTheSame() { |
| val `as` = 'a'.repeat(Segment.SIZE) |
| val bs = 'b'.repeat(Segment.SIZE) |
| |
| val source = Buffer() |
| source.writeUtf8(`as`) |
| source.writeUtf8(bs) |
| |
| source.copyTo(source, 0, source.size) |
| assertEquals(`as` + bs + `as` + bs, source.readUtf8()) |
| } |
| |
| @Test fun copyToEmptySource() { |
| val source = Buffer() |
| val target = Buffer().writeUtf8("aaa") |
| source.copyTo(target, 0L, 0L) |
| assertEquals("", source.readUtf8()) |
| assertEquals("aaa", target.readUtf8()) |
| } |
| |
| @Test fun copyToEmptyTarget() { |
| val source = Buffer().writeUtf8("aaa") |
| val target = Buffer() |
| source.copyTo(target, 0L, 3L) |
| assertEquals("aaa", source.readUtf8()) |
| assertEquals("aaa", target.readUtf8()) |
| } |
| |
| @Test fun snapshotReportsAccurateSize() { |
| val buf = Buffer().write(byteArrayOf(0, 1, 2, 3)) |
| assertEquals(1, buf.snapshot(1).size) |
| } |
| } |