• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 package com.google.protobuf;
32 
33 import com.google.protobuf.ByteString.Output;
34 
35 import junit.framework.TestCase;
36 
37 import java.io.ByteArrayInputStream;
38 import java.io.ByteArrayOutputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.OutputStream;
42 import java.lang.reflect.Field;
43 import java.nio.ByteBuffer;
44 import java.nio.charset.Charset;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.NoSuchElementException;
50 import java.util.Random;
51 
52 /**
53  * Test methods with implementations in {@link ByteString}, plus do some top-level "integration"
54  * tests.
55  *
56  * @author carlanton@google.com (Carl Haverl)
57  */
58 public class ByteStringTest extends TestCase {
59 
60   private static final Charset UTF_16 = Charset.forName("UTF-16");
61 
getTestBytes(int size, long seed)62   static byte[] getTestBytes(int size, long seed) {
63     Random random = new Random(seed);
64     byte[] result = new byte[size];
65     random.nextBytes(result);
66     return result;
67   }
68 
getTestBytes(int size)69   private byte[] getTestBytes(int size) {
70     return getTestBytes(size, 445566L);
71   }
72 
getTestBytes()73   private byte[] getTestBytes() {
74     return getTestBytes(1000);
75   }
76 
77   // Compare the entire left array with a subset of the right array.
isArrayRange(byte[] left, byte[] right, int rightOffset, int length)78   private boolean isArrayRange(byte[] left, byte[] right, int rightOffset, int length) {
79     boolean stillEqual = (left.length == length);
80     for (int i = 0; (stillEqual && i < length); ++i) {
81       stillEqual = (left[i] == right[rightOffset + i]);
82     }
83     return stillEqual;
84   }
85 
86   // Returns true only if the given two arrays have identical contents.
isArray(byte[] left, byte[] right)87   private boolean isArray(byte[] left, byte[] right) {
88     return left.length == right.length && isArrayRange(left, right, 0, left.length);
89   }
90 
testSubstring_BeginIndex()91   public void testSubstring_BeginIndex() {
92     byte[] bytes = getTestBytes();
93     ByteString substring = ByteString.copyFrom(bytes).substring(500);
94     assertTrue("substring must contain the tail of the string",
95         isArrayRange(substring.toByteArray(), bytes, 500, bytes.length - 500));
96   }
97 
testCopyFrom_BytesOffsetSize()98   public void testCopyFrom_BytesOffsetSize() {
99     byte[] bytes = getTestBytes();
100     ByteString byteString = ByteString.copyFrom(bytes, 500, 200);
101     assertTrue("copyFrom sub-range must contain the expected bytes",
102         isArrayRange(byteString.toByteArray(), bytes, 500, 200));
103   }
104 
testCopyFrom_Bytes()105   public void testCopyFrom_Bytes() {
106     byte[] bytes = getTestBytes();
107     ByteString byteString = ByteString.copyFrom(bytes);
108     assertTrue("copyFrom must contain the expected bytes",
109         isArray(byteString.toByteArray(), bytes));
110   }
111 
testCopyFrom_ByteBufferSize()112   public void testCopyFrom_ByteBufferSize() {
113     byte[] bytes = getTestBytes();
114     ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
115     byteBuffer.put(bytes);
116     byteBuffer.position(500);
117     ByteString byteString = ByteString.copyFrom(byteBuffer, 200);
118     assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
119         isArrayRange(byteString.toByteArray(), bytes, 500, 200));
120   }
121 
testCopyFrom_ByteBuffer()122   public void testCopyFrom_ByteBuffer() {
123     byte[] bytes = getTestBytes();
124     ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
125     byteBuffer.put(bytes);
126     byteBuffer.position(500);
127     ByteString byteString = ByteString.copyFrom(byteBuffer);
128     assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
129         isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500));
130   }
131 
testCopyFrom_StringEncoding()132   public void testCopyFrom_StringEncoding() {
133     String testString = "I love unicode \u1234\u5678 characters";
134     ByteString byteString = ByteString.copyFrom(testString, UTF_16);
135     byte[] testBytes = testString.getBytes(UTF_16);
136     assertTrue("copyFrom string must respect the charset",
137         isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
138   }
139 
testCopyFrom_Utf8()140   public void testCopyFrom_Utf8() {
141     String testString = "I love unicode \u1234\u5678 characters";
142     ByteString byteString = ByteString.copyFromUtf8(testString);
143     byte[] testBytes = testString.getBytes(Internal.UTF_8);
144     assertTrue("copyFromUtf8 string must respect the charset",
145         isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
146   }
147 
testCopyFrom_Iterable()148   public void testCopyFrom_Iterable() {
149     byte[] testBytes = getTestBytes(77777, 113344L);
150     final List<ByteString> pieces = makeConcretePieces(testBytes);
151     // Call copyFrom() on a Collection
152     ByteString byteString = ByteString.copyFrom(pieces);
153     assertTrue("copyFrom a List must contain the expected bytes",
154         isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
155     // Call copyFrom on an iteration that's not a collection
156     ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() {
157       @Override
158       public Iterator<ByteString> iterator() {
159         return pieces.iterator();
160       }
161     });
162     assertEquals("copyFrom from an Iteration must contain the expected bytes",
163         byteString, byteStringAlt);
164   }
165 
testCopyTo_TargetOffset()166   public void testCopyTo_TargetOffset() {
167     byte[] bytes = getTestBytes();
168     ByteString byteString = ByteString.copyFrom(bytes);
169     byte[] target = new byte[bytes.length + 1000];
170     byteString.copyTo(target, 400);
171     assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
172         isArrayRange(bytes, target, 400, bytes.length));
173   }
174 
testReadFrom_emptyStream()175   public void testReadFrom_emptyStream() throws IOException {
176     ByteString byteString =
177         ByteString.readFrom(new ByteArrayInputStream(new byte[0]));
178     assertSame("reading an empty stream must result in the EMPTY constant "
179         + "byte string", ByteString.EMPTY, byteString);
180   }
181 
testReadFrom_smallStream()182   public void testReadFrom_smallStream() throws IOException {
183     assertReadFrom(getTestBytes(10));
184   }
185 
testReadFrom_mutating()186   public void testReadFrom_mutating() throws IOException {
187     byte[] capturedArray = null;
188     EvilInputStream eis = new EvilInputStream();
189     ByteString byteString = ByteString.readFrom(eis);
190 
191     capturedArray = eis.capturedArray;
192     byte[] originalValue = byteString.toByteArray();
193     for (int x = 0; x < capturedArray.length; ++x) {
194       capturedArray[x] = (byte) 0;
195     }
196 
197     byte[] newValue = byteString.toByteArray();
198     assertTrue("copyFrom byteBuffer must not grant access to underlying array",
199         Arrays.equals(originalValue, newValue));
200   }
201 
202   // Tests sizes that are near the rope copy-out threshold.
testReadFrom_mediumStream()203   public void testReadFrom_mediumStream() throws IOException {
204     assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE - 1));
205     assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE));
206     assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE + 1));
207     assertReadFrom(getTestBytes(200));
208   }
209 
210   // Tests sizes that are over multi-segment rope threshold.
testReadFrom_largeStream()211   public void testReadFrom_largeStream() throws IOException {
212     assertReadFrom(getTestBytes(0x100));
213     assertReadFrom(getTestBytes(0x101));
214     assertReadFrom(getTestBytes(0x110));
215     assertReadFrom(getTestBytes(0x1000));
216     assertReadFrom(getTestBytes(0x1001));
217     assertReadFrom(getTestBytes(0x1010));
218     assertReadFrom(getTestBytes(0x10000));
219     assertReadFrom(getTestBytes(0x10001));
220     assertReadFrom(getTestBytes(0x10010));
221   }
222 
223   // Tests sizes that are near the read buffer size.
testReadFrom_byteBoundaries()224   public void testReadFrom_byteBoundaries() throws IOException {
225     final int min = ByteString.MIN_READ_FROM_CHUNK_SIZE;
226     final int max = ByteString.MAX_READ_FROM_CHUNK_SIZE;
227 
228     assertReadFrom(getTestBytes(min - 1));
229     assertReadFrom(getTestBytes(min));
230     assertReadFrom(getTestBytes(min + 1));
231 
232     assertReadFrom(getTestBytes(min * 2 - 1));
233     assertReadFrom(getTestBytes(min * 2));
234     assertReadFrom(getTestBytes(min * 2 + 1));
235 
236     assertReadFrom(getTestBytes(min * 4 - 1));
237     assertReadFrom(getTestBytes(min * 4));
238     assertReadFrom(getTestBytes(min * 4 + 1));
239 
240     assertReadFrom(getTestBytes(min * 8 - 1));
241     assertReadFrom(getTestBytes(min * 8));
242     assertReadFrom(getTestBytes(min * 8 + 1));
243 
244     assertReadFrom(getTestBytes(max - 1));
245     assertReadFrom(getTestBytes(max));
246     assertReadFrom(getTestBytes(max + 1));
247 
248     assertReadFrom(getTestBytes(max * 2 - 1));
249     assertReadFrom(getTestBytes(max * 2));
250     assertReadFrom(getTestBytes(max * 2 + 1));
251   }
252 
253   // Tests that IOExceptions propagate through ByteString.readFrom().
testReadFrom_IOExceptions()254   public void testReadFrom_IOExceptions() {
255     try {
256       ByteString.readFrom(new FailStream());
257       fail("readFrom must throw the underlying IOException");
258 
259     } catch (IOException e) {
260       assertEquals("readFrom must throw the expected exception",
261                    "synthetic failure", e.getMessage());
262     }
263   }
264 
265   // Tests that ByteString.readFrom works with streams that don't
266   // always fill their buffers.
testReadFrom_reluctantStream()267   public void testReadFrom_reluctantStream() throws IOException {
268     final byte[] data = getTestBytes(0x1000);
269 
270     ByteString byteString = ByteString.readFrom(new ReluctantStream(data));
271     assertTrue("readFrom byte stream must contain the expected bytes",
272         isArray(byteString.toByteArray(), data));
273 
274     // Same test as above, but with some specific chunk sizes.
275     assertReadFromReluctantStream(data, 100);
276     assertReadFromReluctantStream(data, 248);
277     assertReadFromReluctantStream(data, 249);
278     assertReadFromReluctantStream(data, 250);
279     assertReadFromReluctantStream(data, 251);
280     assertReadFromReluctantStream(data, 0x1000);
281     assertReadFromReluctantStream(data, 0x1001);
282   }
283 
284   // Fails unless ByteString.readFrom reads the bytes correctly from a
285   // reluctant stream with the given chunkSize parameter.
assertReadFromReluctantStream(byte[] bytes, int chunkSize)286   private void assertReadFromReluctantStream(byte[] bytes, int chunkSize)
287       throws IOException {
288     ByteString b = ByteString.readFrom(new ReluctantStream(bytes), chunkSize);
289     assertTrue("readFrom byte stream must contain the expected bytes",
290         isArray(b.toByteArray(), bytes));
291   }
292 
293   // Tests that ByteString.readFrom works with streams that implement
294   // available().
testReadFrom_available()295   public void testReadFrom_available() throws IOException {
296     final byte[] data = getTestBytes(0x1001);
297 
298     ByteString byteString = ByteString.readFrom(new AvailableStream(data));
299     assertTrue("readFrom byte stream must contain the expected bytes",
300         isArray(byteString.toByteArray(), data));
301   }
302 
303   // Fails unless ByteString.readFrom reads the bytes correctly.
assertReadFrom(byte[] bytes)304   private void assertReadFrom(byte[] bytes) throws IOException {
305     ByteString byteString =
306         ByteString.readFrom(new ByteArrayInputStream(bytes));
307     assertTrue("readFrom byte stream must contain the expected bytes",
308         isArray(byteString.toByteArray(), bytes));
309   }
310 
311   // A stream that fails when read.
312   private static final class FailStream extends InputStream {
read()313     @Override public int read() throws IOException {
314       throw new IOException("synthetic failure");
315     }
316   }
317 
318   // A stream that simulates blocking by only producing 250 characters
319   // per call to read(byte[]).
320   private static class ReluctantStream extends InputStream {
321     protected final byte[] data;
322     protected int pos = 0;
323 
ReluctantStream(byte[] data)324     public ReluctantStream(byte[] data) {
325       this.data = data;
326     }
327 
read()328     @Override public int read() {
329       if (pos == data.length) {
330         return -1;
331       } else {
332         return data[pos++];
333       }
334     }
335 
read(byte[] buf)336     @Override public int read(byte[] buf) {
337       return read(buf, 0, buf.length);
338     }
339 
read(byte[] buf, int offset, int size)340     @Override public int read(byte[] buf, int offset, int size) {
341       if (pos == data.length) {
342         return -1;
343       }
344       int count = Math.min(Math.min(size, data.length - pos), 250);
345       System.arraycopy(data, pos, buf, offset, count);
346       pos += count;
347       return count;
348     }
349   }
350 
351   // Same as above, but also implements available().
352   private static final class AvailableStream extends ReluctantStream {
AvailableStream(byte[] data)353     public AvailableStream(byte[] data) {
354       super(data);
355     }
356 
available()357     @Override public int available() {
358       return Math.min(250, data.length - pos);
359     }
360   }
361 
362   // A stream which exposes the byte array passed into read(byte[], int, int).
363   private static class EvilInputStream extends InputStream {
364     public byte[] capturedArray = null;
365 
366     @Override
read(byte[] buf, int off, int len)367     public int read(byte[] buf, int off, int len) {
368       if (capturedArray != null) {
369         return -1;
370       } else {
371         capturedArray = buf;
372         for (int x = 0; x < len; ++x) {
373           buf[x] = (byte) x;
374         }
375         return len;
376       }
377     }
378 
379     @Override
read()380     public int read() {
381       // Purposefully do nothing.
382       return -1;
383     }
384   }
385 
386   // A stream which exposes the byte array passed into write(byte[], int, int).
387   private static class EvilOutputStream extends OutputStream {
388     public byte[] capturedArray = null;
389 
390     @Override
write(byte[] buf, int off, int len)391     public void write(byte[] buf, int off, int len) {
392       if (capturedArray == null) {
393         capturedArray = buf;
394       }
395     }
396 
397     @Override
write(int ignored)398     public void write(int ignored) {
399       // Purposefully do nothing.
400     }
401   }
402 
testToStringUtf8()403   public void testToStringUtf8() {
404     String testString = "I love unicode \u1234\u5678 characters";
405     byte[] testBytes = testString.getBytes(Internal.UTF_8);
406     ByteString byteString = ByteString.copyFrom(testBytes);
407     assertEquals("copyToStringUtf8 must respect the charset",
408         testString, byteString.toStringUtf8());
409   }
410 
testNewOutput_InitialCapacity()411   public void testNewOutput_InitialCapacity() throws IOException {
412     byte[] bytes = getTestBytes();
413     ByteString.Output output = ByteString.newOutput(bytes.length + 100);
414     output.write(bytes);
415     ByteString byteString = output.toByteString();
416     assertTrue(
417         "String built from newOutput(int) must contain the expected bytes",
418         isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
419   }
420 
421   // Test newOutput() using a variety of buffer sizes and a variety of (fixed)
422   // write sizes
testNewOutput_ArrayWrite()423   public void testNewOutput_ArrayWrite() {
424     byte[] bytes = getTestBytes();
425     int length = bytes.length;
426     int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1,
427                          2 * length, 3 * length};
428     int[] writeSizes = {1, 4, 5, 7, 23, bytes.length};
429 
430     for (int bufferSize : bufferSizes) {
431       for (int writeSize : writeSizes) {
432         // Test writing the entire output writeSize bytes at a time.
433         ByteString.Output output = ByteString.newOutput(bufferSize);
434         for (int i = 0; i < length; i += writeSize) {
435           output.write(bytes, i, Math.min(writeSize, length - i));
436         }
437         ByteString byteString = output.toByteString();
438         assertTrue("String built from newOutput() must contain the expected bytes",
439             isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
440       }
441     }
442   }
443 
444   // Test newOutput() using a variety of buffer sizes, but writing all the
445   // characters using write(byte);
testNewOutput_WriteChar()446   public void testNewOutput_WriteChar() {
447     byte[] bytes = getTestBytes();
448     int length = bytes.length;
449     int[] bufferSizes = {0, 1, 128, 256, length / 2,
450                          length - 1, length, length + 1,
451                          2 * length, 3 * length};
452     for (int bufferSize : bufferSizes) {
453       ByteString.Output output = ByteString.newOutput(bufferSize);
454       for (byte byteValue : bytes) {
455         output.write(byteValue);
456       }
457       ByteString byteString = output.toByteString();
458       assertTrue("String built from newOutput() must contain the expected bytes",
459           isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
460     }
461   }
462 
463   // Test newOutput() in which we write the bytes using a variety of methods
464   // and sizes, and in which we repeatedly call toByteString() in the middle.
testNewOutput_Mixed()465   public void testNewOutput_Mixed() {
466     Random rng = new Random(1);
467     byte[] bytes = getTestBytes();
468     int length = bytes.length;
469     int[] bufferSizes = {0, 1, 128, 256, length / 2,
470                          length - 1, length, length + 1,
471                          2 * length, 3 * length};
472 
473     for (int bufferSize : bufferSizes) {
474       // Test writing the entire output using a mixture of write sizes and
475       // methods;
476       ByteString.Output output = ByteString.newOutput(bufferSize);
477       int position = 0;
478       while (position < bytes.length) {
479         if (rng.nextBoolean()) {
480           int count = 1 + rng.nextInt(bytes.length - position);
481           output.write(bytes, position, count);
482           position += count;
483         } else {
484           output.write(bytes[position]);
485           position++;
486         }
487         assertEquals("size() returns the right value", position, output.size());
488         assertTrue("newOutput() substring must have correct bytes",
489             isArrayRange(output.toByteString().toByteArray(),
490                 bytes, 0, position));
491       }
492       ByteString byteString = output.toByteString();
493       assertTrue("String built from newOutput() must contain the expected bytes",
494           isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
495     }
496   }
497 
testNewOutputEmpty()498   public void testNewOutputEmpty() {
499     // Make sure newOutput() correctly builds empty byte strings
500     ByteString byteString = ByteString.newOutput().toByteString();
501     assertEquals(ByteString.EMPTY, byteString);
502   }
503 
testNewOutput_Mutating()504   public void testNewOutput_Mutating() throws IOException {
505     Output os = ByteString.newOutput(5);
506     os.write(new byte[] {1, 2, 3, 4, 5});
507     EvilOutputStream eos = new EvilOutputStream();
508     os.writeTo(eos);
509     byte[] capturedArray = eos.capturedArray;
510     ByteString byteString = os.toByteString();
511     byte[] oldValue = byteString.toByteArray();
512     Arrays.fill(capturedArray, (byte) 0);
513     byte[] newValue = byteString.toByteArray();
514     assertTrue("Output must not provide access to the underlying byte array",
515         Arrays.equals(oldValue, newValue));
516   }
517 
testNewCodedBuilder()518   public void testNewCodedBuilder() throws IOException {
519     byte[] bytes = getTestBytes();
520     ByteString.CodedBuilder builder = ByteString.newCodedBuilder(bytes.length);
521     builder.getCodedOutput().writeRawBytes(bytes);
522     ByteString byteString = builder.build();
523     assertTrue("String built from newCodedBuilder() must contain the expected bytes",
524         isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
525   }
526 
testSubstringParity()527   public void testSubstringParity() {
528     byte[] bigBytes = getTestBytes(2048 * 1024, 113344L);
529     int start = 512 * 1024 - 3333;
530     int end   = 512 * 1024 + 7777;
531     ByteString concreteSubstring = ByteString.copyFrom(bigBytes).substring(start, end);
532     boolean ok = true;
533     for (int i = start; ok && i < end; ++i) {
534       ok = (bigBytes[i] == concreteSubstring.byteAt(i - start));
535     }
536     assertTrue("Concrete substring didn't capture the right bytes", ok);
537 
538     ByteString literalString = ByteString.copyFrom(bigBytes, start, end - start);
539     assertTrue("Substring must be equal to literal string",
540         concreteSubstring.equals(literalString));
541     assertEquals("Substring must have same hashcode as literal string",
542         literalString.hashCode(), concreteSubstring.hashCode());
543   }
544 
testCompositeSubstring()545   public void testCompositeSubstring() {
546     byte[] referenceBytes = getTestBytes(77748, 113344L);
547 
548     List<ByteString> pieces = makeConcretePieces(referenceBytes);
549     ByteString listString = ByteString.copyFrom(pieces);
550 
551     int from = 1000;
552     int to = 40000;
553     ByteString compositeSubstring = listString.substring(from, to);
554     byte[] substringBytes = compositeSubstring.toByteArray();
555     boolean stillEqual = true;
556     for (int i = 0; stillEqual && i < to - from; ++i) {
557       stillEqual = referenceBytes[from + i] == substringBytes[i];
558     }
559     assertTrue("Substring must return correct bytes", stillEqual);
560 
561     stillEqual = true;
562     for (int i = 0; stillEqual && i < to - from; ++i) {
563       stillEqual = referenceBytes[from + i] == compositeSubstring.byteAt(i);
564     }
565     assertTrue("Substring must support byteAt() correctly", stillEqual);
566 
567     ByteString literalSubstring = ByteString.copyFrom(referenceBytes, from, to - from);
568     assertTrue("Composite substring must equal a literal substring over the same bytes",
569         compositeSubstring.equals(literalSubstring));
570     assertTrue("Literal substring must equal a composite substring over the same bytes",
571         literalSubstring.equals(compositeSubstring));
572 
573     assertEquals("We must get the same hashcodes for composite and literal substrings",
574         literalSubstring.hashCode(), compositeSubstring.hashCode());
575 
576     assertFalse("We can't be equal to a proper substring",
577         compositeSubstring.equals(literalSubstring.substring(0, literalSubstring.size() - 1)));
578   }
579 
testCopyFromList()580   public void testCopyFromList() {
581     byte[] referenceBytes = getTestBytes(77748, 113344L);
582     ByteString literalString = ByteString.copyFrom(referenceBytes);
583 
584     List<ByteString> pieces = makeConcretePieces(referenceBytes);
585     ByteString listString = ByteString.copyFrom(pieces);
586 
587     assertTrue("Composite string must be equal to literal string",
588         listString.equals(literalString));
589     assertEquals("Composite string must have same hashcode as literal string",
590         literalString.hashCode(), listString.hashCode());
591   }
592 
testConcat()593   public void testConcat() {
594     byte[] referenceBytes = getTestBytes(77748, 113344L);
595     ByteString literalString = ByteString.copyFrom(referenceBytes);
596 
597     List<ByteString> pieces = makeConcretePieces(referenceBytes);
598 
599     Iterator<ByteString> iter = pieces.iterator();
600     ByteString concatenatedString = iter.next();
601     while (iter.hasNext()) {
602       concatenatedString = concatenatedString.concat(iter.next());
603     }
604 
605     assertTrue("Concatenated string must be equal to literal string",
606         concatenatedString.equals(literalString));
607     assertEquals("Concatenated string must have same hashcode as literal string",
608         literalString.hashCode(), concatenatedString.hashCode());
609   }
610 
611   /**
612    * Test the Rope implementation can deal with Empty nodes, even though we
613    * guard against them. See also {@link LiteralByteStringTest#testConcat_empty()}.
614    */
testConcat_empty()615   public void testConcat_empty() {
616     byte[] referenceBytes = getTestBytes(7748, 113344L);
617     ByteString literalString = ByteString.copyFrom(referenceBytes);
618 
619     ByteString duo = RopeByteString.newInstanceForTest(literalString, literalString);
620     ByteString temp = RopeByteString.newInstanceForTest(
621         RopeByteString.newInstanceForTest(literalString, ByteString.EMPTY),
622         RopeByteString.newInstanceForTest(ByteString.EMPTY, literalString));
623     ByteString quintet = RopeByteString.newInstanceForTest(temp, ByteString.EMPTY);
624 
625     assertTrue("String with concatenated nulls must equal simple concatenate",
626         duo.equals(quintet));
627     assertEquals("String with concatenated nulls have same hashcode as simple concatenate",
628         duo.hashCode(), quintet.hashCode());
629 
630     ByteString.ByteIterator duoIter = duo.iterator();
631     ByteString.ByteIterator quintetIter = quintet.iterator();
632     boolean stillEqual = true;
633     while (stillEqual && quintetIter.hasNext()) {
634       stillEqual = (duoIter.nextByte() == quintetIter.nextByte());
635     }
636     assertTrue("We must get the same characters by iterating", stillEqual);
637     assertFalse("Iterator must be exhausted", duoIter.hasNext());
638     try {
639       duoIter.nextByte();
640       fail("Should have thrown an exception.");
641     } catch (NoSuchElementException e) {
642       // This is success
643     }
644     try {
645       quintetIter.nextByte();
646       fail("Should have thrown an exception.");
647     } catch (NoSuchElementException e) {
648       // This is success
649     }
650 
651     // Test that even if we force empty strings in as rope leaves in this
652     // configuration, we always get a (possibly Bounded) LiteralByteString
653     // for a length 1 substring.
654     //
655     // It is possible, using the testing factory method to create deeply nested
656     // trees of empty leaves, to make a string that will fail this test.
657     for (int i = 1; i < duo.size(); ++i) {
658       assertTrue("Substrings of size() < 2 must not be RopeByteStrings",
659           duo.substring(i - 1, i) instanceof ByteString.LeafByteString);
660     }
661     for (int i = 1; i < quintet.size(); ++i) {
662       assertTrue("Substrings of size() < 2 must not be RopeByteStrings",
663           quintet.substring(i - 1, i) instanceof ByteString.LeafByteString);
664     }
665   }
666 
testStartsWith()667   public void testStartsWith() {
668     byte[] bytes = getTestBytes(1000, 1234L);
669     ByteString string = ByteString.copyFrom(bytes);
670     ByteString prefix = ByteString.copyFrom(bytes, 0, 500);
671     ByteString suffix = ByteString.copyFrom(bytes, 400, 600);
672     assertTrue(string.startsWith(ByteString.EMPTY));
673     assertTrue(string.startsWith(string));
674     assertTrue(string.startsWith(prefix));
675     assertFalse(string.startsWith(suffix));
676     assertFalse(prefix.startsWith(suffix));
677     assertFalse(suffix.startsWith(prefix));
678     assertFalse(ByteString.EMPTY.startsWith(prefix));
679     assertTrue(ByteString.EMPTY.startsWith(ByteString.EMPTY));
680   }
681 
testEndsWith()682   public void testEndsWith() {
683     byte[] bytes = getTestBytes(1000, 1234L);
684     ByteString string = ByteString.copyFrom(bytes);
685     ByteString prefix = ByteString.copyFrom(bytes, 0, 500);
686     ByteString suffix = ByteString.copyFrom(bytes, 400, 600);
687     assertTrue(string.endsWith(ByteString.EMPTY));
688     assertTrue(string.endsWith(string));
689     assertTrue(string.endsWith(suffix));
690     assertFalse(string.endsWith(prefix));
691     assertFalse(suffix.endsWith(prefix));
692     assertFalse(prefix.endsWith(suffix));
693     assertFalse(ByteString.EMPTY.endsWith(suffix));
694     assertTrue(ByteString.EMPTY.endsWith(ByteString.EMPTY));
695   }
696 
makeConcretePieces(byte[] referenceBytes)697   static List<ByteString> makeConcretePieces(byte[] referenceBytes) {
698     List<ByteString> pieces = new ArrayList<ByteString>();
699     // Starting length should be small enough that we'll do some concatenating by
700     // copying if we just concatenate all these pieces together.
701     for (int start = 0, length = 16; start < referenceBytes.length; start += length) {
702       length = (length << 1) - 1;
703       if (start + length > referenceBytes.length) {
704         length = referenceBytes.length - start;
705       }
706       pieces.add(ByteString.copyFrom(referenceBytes, start, length));
707     }
708     return pieces;
709   }
710 
substringUsingWriteTo( ByteString data, int offset, int length)711   private byte[] substringUsingWriteTo(
712       ByteString data, int offset, int length) throws IOException {
713     ByteArrayOutputStream output = new ByteArrayOutputStream();
714     data.writeTo(output, offset, length);
715     return output.toByteArray();
716   }
717 
testWriteToOutputStream()718   public void testWriteToOutputStream() throws Exception {
719     // Choose a size large enough so when two ByteStrings are concatenated they
720     // won't be merged into one byte array due to some optimizations.
721     final int dataSize = ByteString.CONCATENATE_BY_COPY_SIZE + 1;
722     byte[] data1 = new byte[dataSize];
723     for (int i = 0; i < data1.length; i++) {
724       data1[i] = (byte) 1;
725     }
726     data1[1] = (byte) 11;
727     // Test LiteralByteString.writeTo(OutputStream,int,int)
728     ByteString left = ByteString.wrap(data1);
729     byte[] result = substringUsingWriteTo(left, 1, 1);
730     assertEquals(1, result.length);
731     assertEquals((byte) 11, result[0]);
732 
733     byte[] data2 = new byte[dataSize];
734     for (int i = 0; i < data1.length; i++) {
735       data2[i] = (byte) 2;
736     }
737     ByteString right = ByteString.wrap(data2);
738     // Concatenate two ByteStrings to create a RopeByteString.
739     ByteString root = left.concat(right);
740     // Make sure we are actually testing a RopeByteString with a simple tree
741     // structure.
742     assertEquals(1, root.getTreeDepth());
743     // Write parts of the left node.
744     result = substringUsingWriteTo(root, 0, dataSize);
745     assertEquals(dataSize, result.length);
746     assertEquals((byte) 1, result[0]);
747     assertEquals((byte) 1, result[dataSize - 1]);
748     // Write parts of the right node.
749     result = substringUsingWriteTo(root, dataSize, dataSize);
750     assertEquals(dataSize, result.length);
751     assertEquals((byte) 2, result[0]);
752     assertEquals((byte) 2, result[dataSize - 1]);
753     // Write a segment of bytes that runs across both nodes.
754     result = substringUsingWriteTo(root, dataSize / 2, dataSize);
755     assertEquals(dataSize, result.length);
756     assertEquals((byte) 1, result[0]);
757     assertEquals((byte) 1, result[dataSize - dataSize / 2 - 1]);
758     assertEquals((byte) 2, result[dataSize - dataSize / 2]);
759     assertEquals((byte) 2, result[dataSize - 1]);
760   }
761 
762   /**
763    * Tests ByteString uses Arrays based byte copier when running under Hotstop VM.
764    */
testByteArrayCopier()765   public void testByteArrayCopier() throws Exception {
766     Field field = ByteString.class.getDeclaredField("byteArrayCopier");
767     field.setAccessible(true);
768     Object byteArrayCopier = field.get(null);
769     assertNotNull(byteArrayCopier);
770     assertTrue(
771         byteArrayCopier.toString(),
772         byteArrayCopier.getClass().getSimpleName().endsWith("ArraysByteArrayCopier"));
773   }
774 }
775