/* * Copyright (C) 2015 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 org.junit.Test; import static okio.TestUtil.assertEquivalent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** Tests behavior optimized by sharing segments between buffers and byte strings. */ public final class SegmentSharingTest { private static final String us = TestUtil.repeat('u', Segment.SIZE / 2 - 2); private static final String vs = TestUtil.repeat('v', Segment.SIZE / 2 - 1); private static final String ws = TestUtil.repeat('w', Segment.SIZE / 2); private static final String xs = TestUtil.repeat('x', Segment.SIZE / 2 + 1); private static final String ys = TestUtil.repeat('y', Segment.SIZE / 2 + 2); private static final String zs = TestUtil.repeat('z', Segment.SIZE / 2 + 3); @Test public void snapshotOfEmptyBuffer() throws Exception { ByteString snapshot = new Buffer().snapshot(); assertEquivalent(snapshot, ByteString.EMPTY); } @Test public void snapshotsAreEquivalent() throws Exception { ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot(); assertEquivalent(byteString, concatenateBuffers(xs, ys + zs).snapshot()); assertEquivalent(byteString, concatenateBuffers(xs + ys + zs).snapshot()); assertEquivalent(byteString, ByteString.encodeUtf8(xs + ys + zs)); } @Test public void snapshotGetByte() throws Exception { ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot(); assertEquals('x', byteString.getByte(0)); assertEquals('x', byteString.getByte(xs.length() - 1)); assertEquals('y', byteString.getByte(xs.length())); assertEquals('y', byteString.getByte(xs.length() + ys.length() - 1)); assertEquals('z', byteString.getByte(xs.length() + ys.length())); assertEquals('z', byteString.getByte(xs.length() + ys.length() + zs.length() - 1)); try { byteString.getByte(-1); fail(); } catch (IndexOutOfBoundsException expected) { } try { byteString.getByte(xs.length() + ys.length() + zs.length()); fail(); } catch (IndexOutOfBoundsException expected) { } } @Test public void snapshotWriteToOutputStream() throws Exception { ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot(); Buffer out = new Buffer(); byteString.write(out.outputStream()); assertEquals(xs + ys + zs, out.readUtf8()); } /** * Snapshots share their backing byte arrays with the source buffers. Those byte arrays must not * be recycled, otherwise the new writer could corrupt the segment. */ @Test public void snapshotSegmentsAreNotRecycled() throws Exception { Buffer buffer = concatenateBuffers(xs, ys, zs); ByteString snapshot = buffer.snapshot(); assertEquals(xs + ys + zs, snapshot.utf8()); // While locking the pool, confirm that clearing the buffer doesn't release its segments. synchronized (SegmentPool.class) { SegmentPool.next = null; SegmentPool.byteCount = 0L; buffer.clear(); assertEquals(null, SegmentPool.next); } } /** * Clones share their backing byte arrays with the source buffers. Those byte arrays must not * be recycled, otherwise the new writer could corrupt the segment. */ @Test public void cloneSegmentsAreNotRecycled() throws Exception { Buffer buffer = concatenateBuffers(xs, ys, zs); Buffer clone = buffer.clone(); // While locking the pool, confirm that clearing the buffer doesn't release its segments. synchronized (SegmentPool.class) { SegmentPool.next = null; SegmentPool.byteCount = 0L; buffer.clear(); assertEquals(null, SegmentPool.next); clone.clear(); assertEquals(null, SegmentPool.next); } } @Test public void snapshotJavaSerialization() throws Exception { ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot(); assertEquivalent(byteString, TestUtil.reserialize(byteString)); } @Test public void clonesAreEquivalent() throws Exception { Buffer bufferA = concatenateBuffers(xs, ys, zs); Buffer bufferB = bufferA.clone(); assertEquivalent(bufferA, bufferB); assertEquivalent(bufferA, concatenateBuffers(xs + ys, zs)); } /** Even though some segments are shared, clones can be mutated independently. */ @Test public void mutateAfterClone() throws Exception { Buffer bufferA = new Buffer(); bufferA.writeUtf8("abc"); Buffer bufferB = bufferA.clone(); bufferA.writeUtf8("def"); bufferB.writeUtf8("DEF"); assertEquals("abcdef", bufferA.readUtf8()); assertEquals("abcDEF", bufferB.readUtf8()); } @Test public void concatenateSegmentsCanCombine() throws Exception { Buffer bufferA = new Buffer().writeUtf8(ys).writeUtf8(us); assertEquals(ys, bufferA.readUtf8(ys.length())); Buffer bufferB = new Buffer().writeUtf8(vs).writeUtf8(ws); Buffer bufferC = bufferA.clone(); bufferA.write(bufferB, vs.length()); bufferC.writeUtf8(xs); assertEquals(us + vs, bufferA.readUtf8()); assertEquals(ws, bufferB.readUtf8()); assertEquals(us + xs, bufferC.readUtf8()); } @Test public void shareAndSplit() throws Exception { Buffer bufferA = new Buffer().writeUtf8("xxxx"); ByteString snapshot = bufferA.snapshot(); // Share the segment. Buffer bufferB = new Buffer(); bufferB.write(bufferA, 2); // Split the shared segment in two. bufferB.writeUtf8("yy"); // Append to the first half of the shared segment. assertEquals("xxxx", snapshot.utf8()); } @Test public void appendSnapshotToEmptyBuffer() throws Exception { Buffer bufferA = concatenateBuffers(xs, ys); ByteString snapshot = bufferA.snapshot(); Buffer bufferB = new Buffer(); bufferB.write(snapshot); assertEquivalent(bufferB, bufferA); } @Test public void appendSnapshotToNonEmptyBuffer() throws Exception { Buffer bufferA = concatenateBuffers(xs, ys); ByteString snapshot = bufferA.snapshot(); Buffer bufferB = new Buffer().writeUtf8(us); bufferB.write(snapshot); assertEquivalent(bufferB, new Buffer().writeUtf8(us + xs + ys)); } @Test public void copyToSegmentSharing() throws Exception { Buffer bufferA = concatenateBuffers(ws, xs + "aaaa", ys, "bbbb" + zs); Buffer bufferB = concatenateBuffers(us); bufferA.copyTo(bufferB, ws.length() + xs.length(), 4 + ys.length() + 4); assertEquivalent(bufferB, new Buffer().writeUtf8(us + "aaaa" + ys + "bbbb")); } /** * Returns a new buffer containing the contents of {@code segments}, attempting to isolate each * string to its own segment in the returned buffer. */ public Buffer concatenateBuffers(String... segments) throws Exception { Buffer result = new Buffer(); for (String s : segments) { int offsetInSegment = s.length() < Segment.SIZE ? (Segment.SIZE - s.length()) / 2 : 0; Buffer buffer = new Buffer(); buffer.writeUtf8(TestUtil.repeat('_', offsetInSegment)); buffer.writeUtf8(s); buffer.skip(offsetInSegment); result.write(buffer, buffer.size); } return result; } }