1a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller/*
2a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * Copyright (C) 2015 Square, Inc.
3a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *
4a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * Licensed under the Apache License, Version 2.0 (the "License");
5a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * you may not use this file except in compliance with the License.
6a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * You may obtain a copy of the License at
7a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *
8a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *      http://www.apache.org/licenses/LICENSE-2.0
9a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *
10a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * Unless required by applicable law or agreed to in writing, software
11a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * distributed under the License is distributed on an "AS IS" BASIS,
12a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * See the License for the specific language governing permissions and
14a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * limitations under the License.
15a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller */
16a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerpackage okio;
17a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
18a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport org.junit.Test;
19a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
20a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport static okio.TestUtil.assertEquivalent;
21a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport static org.junit.Assert.assertEquals;
22a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport static org.junit.Assert.fail;
23a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
24a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller/** Tests behavior optimized by sharing segments between buffers and byte strings. */
25a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerpublic final class SegmentSharingTest {
26a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final String us = TestUtil.repeat('u', Segment.SIZE / 2 - 2);
27a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final String vs = TestUtil.repeat('v', Segment.SIZE / 2 - 1);
28a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final String ws = TestUtil.repeat('w', Segment.SIZE / 2);
29a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final String xs = TestUtil.repeat('x', Segment.SIZE / 2 + 1);
30a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final String ys = TestUtil.repeat('y', Segment.SIZE / 2 + 2);
31a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private static final String zs = TestUtil.repeat('z', Segment.SIZE / 2 + 3);
32a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
33a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void snapshotOfEmptyBuffer() throws Exception {
34a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString snapshot = new Buffer().snapshot();
35a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(snapshot, ByteString.EMPTY);
36a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
37a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
38a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void snapshotsAreEquivalent() throws Exception {
39a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot();
40a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(byteString, concatenateBuffers(xs, ys + zs).snapshot());
41a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(byteString, concatenateBuffers(xs + ys + zs).snapshot());
42a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(byteString, ByteString.encodeUtf8(xs + ys + zs));
43a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
44a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
45a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void snapshotGetByte() throws Exception {
46a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot();
47a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals('x', byteString.getByte(0));
48a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals('x', byteString.getByte(xs.length() - 1));
49a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals('y', byteString.getByte(xs.length()));
50a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals('y', byteString.getByte(xs.length() + ys.length() - 1));
51a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals('z', byteString.getByte(xs.length() + ys.length()));
52a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals('z', byteString.getByte(xs.length() + ys.length() + zs.length() - 1));
53a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    try {
54a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      byteString.getByte(-1);
55a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      fail();
56a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    } catch (IndexOutOfBoundsException expected) {
57a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
58a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    try {
59a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      byteString.getByte(xs.length() + ys.length() + zs.length());
60a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      fail();
61a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    } catch (IndexOutOfBoundsException expected) {
62a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
63a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
64a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
65a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void snapshotWriteToOutputStream() throws Exception {
66a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot();
67a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer out = new Buffer();
68a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    byteString.write(out.outputStream());
69a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals(xs + ys + zs, out.readUtf8());
70a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
71a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
72a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  /**
73a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   * Snapshots share their backing byte arrays with the source buffers. Those byte arrays must not
74a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   * be recycled, otherwise the new writer could corrupt the segment.
75a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   */
76a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void snapshotSegmentsAreNotRecycled() throws Exception {
77a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer buffer = concatenateBuffers(xs, ys, zs);
78a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString snapshot = buffer.snapshot();
79a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals(xs + ys + zs, snapshot.utf8());
80a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
81a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    // While locking the pool, confirm that clearing the buffer doesn't release its segments.
82a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    synchronized (SegmentPool.class) {
83a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      SegmentPool.next = null;
84a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      SegmentPool.byteCount = 0L;
85a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      buffer.clear();
86a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      assertEquals(null, SegmentPool.next);
87a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
88a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
89a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
90a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  /**
91a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   * Clones share their backing byte arrays with the source buffers. Those byte arrays must not
92a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   * be recycled, otherwise the new writer could corrupt the segment.
93a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   */
94a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void cloneSegmentsAreNotRecycled() throws Exception {
95a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer buffer = concatenateBuffers(xs, ys, zs);
96a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer clone = buffer.clone();
97a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
98a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    // While locking the pool, confirm that clearing the buffer doesn't release its segments.
99a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    synchronized (SegmentPool.class) {
100a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      SegmentPool.next = null;
101a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      SegmentPool.byteCount = 0L;
102a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      buffer.clear();
103a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      assertEquals(null, SegmentPool.next);
104a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      clone.clear();
105a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      assertEquals(null, SegmentPool.next);
106a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
107a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
108a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
109a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void snapshotJavaSerialization() throws Exception {
110a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString byteString = concatenateBuffers(xs, ys, zs).snapshot();
111a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(byteString, TestUtil.reserialize(byteString));
112a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
113a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
114a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void clonesAreEquivalent() throws Exception {
115a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = concatenateBuffers(xs, ys, zs);
116a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = bufferA.clone();
117a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(bufferA, bufferB);
118a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(bufferA, concatenateBuffers(xs + ys, zs));
119a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
120a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
121a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  /** Even though some segments are shared, clones can be mutated independently. */
122a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void mutateAfterClone() throws Exception {
123a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = new Buffer();
124a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferA.writeUtf8("abc");
125a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = bufferA.clone();
126a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferA.writeUtf8("def");
127a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferB.writeUtf8("DEF");
128a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals("abcdef", bufferA.readUtf8());
129a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals("abcDEF", bufferB.readUtf8());
130a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
131a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
132a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void concatenateSegmentsCanCombine() throws Exception {
133a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = new Buffer().writeUtf8(ys).writeUtf8(us);
134a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals(ys, bufferA.readUtf8(ys.length()));
135a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = new Buffer().writeUtf8(vs).writeUtf8(ws);
136a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferC = bufferA.clone();
137a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferA.write(bufferB, vs.length());
138a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferC.writeUtf8(xs);
139a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
140a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals(us + vs, bufferA.readUtf8());
141a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals(ws, bufferB.readUtf8());
142a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals(us + xs, bufferC.readUtf8());
143a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
144a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
145a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void shareAndSplit() throws Exception {
146a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = new Buffer().writeUtf8("xxxx");
147a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString snapshot = bufferA.snapshot(); // Share the segment.
148a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = new Buffer();
149a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferB.write(bufferA, 2); // Split the shared segment in two.
150a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferB.writeUtf8("yy"); // Append to the first half of the shared segment.
151a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquals("xxxx", snapshot.utf8());
152a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
153a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
154a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void appendSnapshotToEmptyBuffer() throws Exception {
155a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = concatenateBuffers(xs, ys);
156a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString snapshot = bufferA.snapshot();
157a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = new Buffer();
158a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferB.write(snapshot);
159a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(bufferB, bufferA);
160a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
161a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
162a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void appendSnapshotToNonEmptyBuffer() throws Exception {
163a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = concatenateBuffers(xs, ys);
164a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    ByteString snapshot = bufferA.snapshot();
165a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = new Buffer().writeUtf8(us);
166a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferB.write(snapshot);
167a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(bufferB, new Buffer().writeUtf8(us + xs + ys));
168a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
169a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
170a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Test public void copyToSegmentSharing() throws Exception {
171a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferA = concatenateBuffers(ws, xs + "aaaa", ys, "bbbb" + zs);
172a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer bufferB = concatenateBuffers(us);
173a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    bufferA.copyTo(bufferB, ws.length() + xs.length(), 4 + ys.length() + 4);
174a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    assertEquivalent(bufferB, new Buffer().writeUtf8(us + "aaaa" + ys + "bbbb"));
175a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
176a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
177a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  /**
178a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   * Returns a new buffer containing the contents of {@code segments}, attempting to isolate each
179a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   * string to its own segment in the returned buffer.
180a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller   */
181a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  public Buffer concatenateBuffers(String... segments) throws Exception {
182a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer result = new Buffer();
183a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    for (String s : segments) {
184a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      int offsetInSegment = s.length() < Segment.SIZE ? (Segment.SIZE - s.length()) / 2 : 0;
185a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      Buffer buffer = new Buffer();
186a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      buffer.writeUtf8(TestUtil.repeat('_', offsetInSegment));
187a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      buffer.writeUtf8(s);
188a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      buffer.skip(offsetInSegment);
189a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      result.write(buffer, buffer.size);
190a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
191a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    return result;
192a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
193a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller}
194