1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // http://code.google.com/p/protobuf/
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.IOException;
39 import java.io.InputStream;
40 import java.io.OutputStream;
41 import java.io.UnsupportedEncodingException;
42 import java.nio.ByteBuffer;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.NoSuchElementException;
48 import java.util.Random;
49 
50 /**
51  * Test methods with implementations in {@link ByteString}, plus do some top-level "integration"
52  * tests.
53  *
54  * @author carlanton@google.com (Carl Haverl)
55  */
56 public class ByteStringTest extends TestCase {
57 
58   private static final String UTF_16 = "UTF-16";
59 
getTestBytes(int size, long seed)60   static byte[] getTestBytes(int size, long seed) {
61     Random random = new Random(seed);
62     byte[] result = new byte[size];
63     random.nextBytes(result);
64     return result;
65   }
66 
getTestBytes(int size)67   private byte[] getTestBytes(int size) {
68     return getTestBytes(size, 445566L);
69   }
70 
getTestBytes()71   private byte[] getTestBytes() {
72     return getTestBytes(1000);
73   }
74 
75   // Compare the entire left array with a subset of the right array.
isArrayRange(byte[] left, byte[] right, int rightOffset, int length)76   private boolean isArrayRange(byte[] left, byte[] right, int rightOffset, int length) {
77     boolean stillEqual = (left.length == length);
78     for (int i = 0; (stillEqual && i < length); ++i) {
79       stillEqual = (left[i] == right[rightOffset + i]);
80     }
81     return stillEqual;
82   }
83 
84   // Returns true only if the given two arrays have identical contents.
isArray(byte[] left, byte[] right)85   private boolean isArray(byte[] left, byte[] right) {
86     return left.length == right.length && isArrayRange(left, right, 0, left.length);
87   }
88 
testSubstring_BeginIndex()89   public void testSubstring_BeginIndex() {
90     byte[] bytes = getTestBytes();
91     ByteString substring = ByteString.copyFrom(bytes).substring(500);
92     assertTrue("substring must contain the tail of the string",
93         isArrayRange(substring.toByteArray(), bytes, 500, bytes.length - 500));
94   }
95 
testCopyFrom_BytesOffsetSize()96   public void testCopyFrom_BytesOffsetSize() {
97     byte[] bytes = getTestBytes();
98     ByteString byteString = ByteString.copyFrom(bytes, 500, 200);
99     assertTrue("copyFrom sub-range must contain the expected bytes",
100         isArrayRange(byteString.toByteArray(), bytes, 500, 200));
101   }
102 
testCopyFrom_Bytes()103   public void testCopyFrom_Bytes() {
104     byte[] bytes = getTestBytes();
105     ByteString byteString = ByteString.copyFrom(bytes);
106     assertTrue("copyFrom must contain the expected bytes",
107         isArray(byteString.toByteArray(), bytes));
108   }
109 
testCopyFrom_ByteBufferSize()110   public void testCopyFrom_ByteBufferSize() {
111     byte[] bytes = getTestBytes();
112     ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
113     byteBuffer.put(bytes);
114     byteBuffer.position(500);
115     ByteString byteString = ByteString.copyFrom(byteBuffer, 200);
116     assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
117         isArrayRange(byteString.toByteArray(), bytes, 500, 200));
118   }
119 
testCopyFrom_ByteBuffer()120   public void testCopyFrom_ByteBuffer() {
121     byte[] bytes = getTestBytes();
122     ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
123     byteBuffer.put(bytes);
124     byteBuffer.position(500);
125     ByteString byteString = ByteString.copyFrom(byteBuffer);
126     assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
127         isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500));
128   }
129 
testCopyFrom_StringEncoding()130   public void testCopyFrom_StringEncoding() throws UnsupportedEncodingException {
131     String testString = "I love unicode \u1234\u5678 characters";
132     ByteString byteString = ByteString.copyFrom(testString, UTF_16);
133     byte[] testBytes = testString.getBytes(UTF_16);
134     assertTrue("copyFrom string must respect the charset",
135         isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
136   }
137 
testCopyFrom_Utf8()138   public void testCopyFrom_Utf8() throws UnsupportedEncodingException {
139     String testString = "I love unicode \u1234\u5678 characters";
140     ByteString byteString = ByteString.copyFromUtf8(testString);
141     byte[] testBytes = testString.getBytes("UTF-8");
142     assertTrue("copyFromUtf8 string must respect the charset",
143         isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
144   }
145 
testCopyFrom_Iterable()146   public void testCopyFrom_Iterable() {
147     byte[] testBytes = getTestBytes(77777, 113344L);
148     final List<ByteString> pieces = makeConcretePieces(testBytes);
149     // Call copyFrom() on a Collection
150     ByteString byteString = ByteString.copyFrom(pieces);
151     assertTrue("copyFrom a List must contain the expected bytes",
152         isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length));
153     // Call copyFrom on an iteration that's not a collection
154     ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() {
155       public Iterator<ByteString> iterator() {
156         return pieces.iterator();
157       }
158     });
159     assertEquals("copyFrom from an Iteration must contain the expected bytes",
160         byteString, byteStringAlt);
161   }
162 
testCopyTo_TargetOffset()163   public void testCopyTo_TargetOffset() {
164     byte[] bytes = getTestBytes();
165     ByteString byteString = ByteString.copyFrom(bytes);
166     byte[] target = new byte[bytes.length + 1000];
167     byteString.copyTo(target, 400);
168     assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes",
169         isArrayRange(bytes, target, 400, bytes.length));
170   }
171 
testReadFrom_emptyStream()172   public void testReadFrom_emptyStream() throws IOException {
173     ByteString byteString =
174         ByteString.readFrom(new ByteArrayInputStream(new byte[0]));
175     assertSame("reading an empty stream must result in the EMPTY constant "
176         + "byte string", ByteString.EMPTY, byteString);
177   }
178 
testReadFrom_smallStream()179   public void testReadFrom_smallStream() throws IOException {
180     assertReadFrom(getTestBytes(10));
181   }
182 
testReadFrom_mutating()183   public void testReadFrom_mutating() throws IOException {
184     byte[] capturedArray = null;
185     EvilInputStream eis = new EvilInputStream();
186     ByteString byteString = ByteString.readFrom(eis);
187 
188     capturedArray = eis.capturedArray;
189     byte[] originalValue = byteString.toByteArray();
190     for (int x = 0; x < capturedArray.length; ++x) {
191       capturedArray[x] = (byte) 0;
192     }
193 
194     byte[] newValue = byteString.toByteArray();
195     assertTrue("copyFrom byteBuffer must not grant access to underlying array",
196         Arrays.equals(originalValue, newValue));
197   }
198 
199   // Tests sizes that are near the rope copy-out threshold.
testReadFrom_mediumStream()200   public void testReadFrom_mediumStream() throws IOException {
201     assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE - 1));
202     assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE));
203     assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE + 1));
204     assertReadFrom(getTestBytes(200));
205   }
206 
207   // Tests sizes that are over multi-segment rope threshold.
testReadFrom_largeStream()208   public void testReadFrom_largeStream() throws IOException {
209     assertReadFrom(getTestBytes(0x100));
210     assertReadFrom(getTestBytes(0x101));
211     assertReadFrom(getTestBytes(0x110));
212     assertReadFrom(getTestBytes(0x1000));
213     assertReadFrom(getTestBytes(0x1001));
214     assertReadFrom(getTestBytes(0x1010));
215     assertReadFrom(getTestBytes(0x10000));
216     assertReadFrom(getTestBytes(0x10001));
217     assertReadFrom(getTestBytes(0x10010));
218   }
219 
220   // Tests sizes that are near the read buffer size.
testReadFrom_byteBoundaries()221   public void testReadFrom_byteBoundaries() throws IOException {
222     final int min = ByteString.MIN_READ_FROM_CHUNK_SIZE;
223     final int max = ByteString.MAX_READ_FROM_CHUNK_SIZE;
224 
225     assertReadFrom(getTestBytes(min - 1));
226     assertReadFrom(getTestBytes(min));
227     assertReadFrom(getTestBytes(min + 1));
228 
229     assertReadFrom(getTestBytes(min * 2 - 1));
230     assertReadFrom(getTestBytes(min * 2));
231     assertReadFrom(getTestBytes(min * 2 + 1));
232 
233     assertReadFrom(getTestBytes(min * 4 - 1));
234     assertReadFrom(getTestBytes(min * 4));
235     assertReadFrom(getTestBytes(min * 4 + 1));
236 
237     assertReadFrom(getTestBytes(min * 8 - 1));
238     assertReadFrom(getTestBytes(min * 8));
239     assertReadFrom(getTestBytes(min * 8 + 1));
240 
241     assertReadFrom(getTestBytes(max - 1));
242     assertReadFrom(getTestBytes(max));
243     assertReadFrom(getTestBytes(max + 1));
244 
245     assertReadFrom(getTestBytes(max * 2 - 1));
246     assertReadFrom(getTestBytes(max * 2));
247     assertReadFrom(getTestBytes(max * 2 + 1));
248   }
249 
250   // Tests that IOExceptions propagate through ByteString.readFrom().
testReadFrom_IOExceptions()251   public void testReadFrom_IOExceptions() {
252     try {
253       ByteString.readFrom(new FailStream());
254       fail("readFrom must throw the underlying IOException");
255 
256     } catch (IOException e) {
257       assertEquals("readFrom must throw the expected exception",
258                    "synthetic failure", e.getMessage());
259     }
260   }
261 
262   // Tests that ByteString.readFrom works with streams that don't
263   // always fill their buffers.
testReadFrom_reluctantStream()264   public void testReadFrom_reluctantStream() throws IOException {
265     final byte[] data = getTestBytes(0x1000);
266 
267     ByteString byteString = ByteString.readFrom(new ReluctantStream(data));
268     assertTrue("readFrom byte stream must contain the expected bytes",
269         isArray(byteString.toByteArray(), data));
270 
271     // Same test as above, but with some specific chunk sizes.
272     assertReadFromReluctantStream(data, 100);
273     assertReadFromReluctantStream(data, 248);
274     assertReadFromReluctantStream(data, 249);
275     assertReadFromReluctantStream(data, 250);
276     assertReadFromReluctantStream(data, 251);
277     assertReadFromReluctantStream(data, 0x1000);
278     assertReadFromReluctantStream(data, 0x1001);
279   }
280 
281   // Fails unless ByteString.readFrom reads the bytes correctly from a
282   // reluctant stream with the given chunkSize parameter.
assertReadFromReluctantStream(byte[] bytes, int chunkSize)283   private void assertReadFromReluctantStream(byte[] bytes, int chunkSize)
284       throws IOException {
285     ByteString b = ByteString.readFrom(new ReluctantStream(bytes), chunkSize);
286     assertTrue("readFrom byte stream must contain the expected bytes",
287         isArray(b.toByteArray(), bytes));
288   }
289 
290   // Tests that ByteString.readFrom works with streams that implement
291   // available().
testReadFrom_available()292   public void testReadFrom_available() throws IOException {
293     final byte[] data = getTestBytes(0x1001);
294 
295     ByteString byteString = ByteString.readFrom(new AvailableStream(data));
296     assertTrue("readFrom byte stream must contain the expected bytes",
297         isArray(byteString.toByteArray(), data));
298   }
299 
300   // Fails unless ByteString.readFrom reads the bytes correctly.
assertReadFrom(byte[] bytes)301   private void assertReadFrom(byte[] bytes) throws IOException {
302     ByteString byteString =
303         ByteString.readFrom(new ByteArrayInputStream(bytes));
304     assertTrue("readFrom byte stream must contain the expected bytes",
305         isArray(byteString.toByteArray(), bytes));
306   }
307 
308   // A stream that fails when read.
309   private static final class FailStream extends InputStream {
read()310     @Override public int read() throws IOException {
311       throw new IOException("synthetic failure");
312     }
313   }
314 
315   // A stream that simulates blocking by only producing 250 characters
316   // per call to read(byte[]).
317   private static class ReluctantStream extends InputStream {
318     protected final byte[] data;
319     protected int pos = 0;
320 
ReluctantStream(byte[] data)321     public ReluctantStream(byte[] data) {
322       this.data = data;
323     }
324 
read()325     @Override public int read() {
326       if (pos == data.length) {
327         return -1;
328       } else {
329         return data[pos++];
330       }
331     }
332 
read(byte[] buf)333     @Override public int read(byte[] buf) {
334       return read(buf, 0, buf.length);
335     }
336 
read(byte[] buf, int offset, int size)337     @Override public int read(byte[] buf, int offset, int size) {
338       if (pos == data.length) {
339         return -1;
340       }
341       int count = Math.min(Math.min(size, data.length - pos), 250);
342       System.arraycopy(data, pos, buf, offset, count);
343       pos += count;
344       return count;
345     }
346   }
347 
348   // Same as above, but also implements available().
349   private static final class AvailableStream extends ReluctantStream {
AvailableStream(byte[] data)350     public AvailableStream(byte[] data) {
351       super(data);
352     }
353 
available()354     @Override public int available() {
355       return Math.min(250, data.length - pos);
356     }
357   }
358 
359   // A stream which exposes the byte array passed into read(byte[], int, int).
360   private static class EvilInputStream extends InputStream {
361     public byte[] capturedArray = null;
362 
363     @Override
read(byte[] buf, int off, int len)364     public int read(byte[] buf, int off, int len) {
365       if (capturedArray != null) {
366         return -1;
367       } else {
368         capturedArray = buf;
369         for (int x = 0; x < len; ++x) {
370           buf[x] = (byte) x;
371         }
372         return len;
373       }
374     }
375 
376     @Override
read()377     public int read() {
378       // Purposefully do nothing.
379       return -1;
380     }
381   }
382 
383   // A stream which exposes the byte array passed into write(byte[], int, int).
384   private static class EvilOutputStream extends OutputStream {
385     public byte[] capturedArray = null;
386 
387     @Override
write(byte[] buf, int off, int len)388     public void write(byte[] buf, int off, int len) {
389       if (capturedArray == null) {
390         capturedArray = buf;
391       }
392     }
393 
394     @Override
write(int ignored)395     public void write(int ignored) {
396       // Purposefully do nothing.
397     }
398   }
399 
testToStringUtf8()400   public void testToStringUtf8() throws UnsupportedEncodingException {
401     String testString = "I love unicode \u1234\u5678 characters";
402     byte[] testBytes = testString.getBytes("UTF-8");
403     ByteString byteString = ByteString.copyFrom(testBytes);
404     assertEquals("copyToStringUtf8 must respect the charset",
405         testString, byteString.toStringUtf8());
406   }
407 
testNewOutput_InitialCapacity()408   public void testNewOutput_InitialCapacity() throws IOException {
409     byte[] bytes = getTestBytes();
410     ByteString.Output output = ByteString.newOutput(bytes.length + 100);
411     output.write(bytes);
412     ByteString byteString = output.toByteString();
413     assertTrue(
414         "String built from newOutput(int) must contain the expected bytes",
415         isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
416   }
417 
418   // Test newOutput() using a variety of buffer sizes and a variety of (fixed)
419   // write sizes
testNewOutput_ArrayWrite()420   public void testNewOutput_ArrayWrite() throws IOException {
421     byte[] bytes = getTestBytes();
422     int length = bytes.length;
423     int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1,
424                          2 * length, 3 * length};
425     int[] writeSizes = {1, 4, 5, 7, 23, bytes.length};
426 
427     for (int bufferSize : bufferSizes) {
428       for (int writeSize : writeSizes) {
429         // Test writing the entire output writeSize bytes at a time.
430         ByteString.Output output = ByteString.newOutput(bufferSize);
431         for (int i = 0; i < length; i += writeSize) {
432           output.write(bytes, i, Math.min(writeSize, length - i));
433         }
434         ByteString byteString = output.toByteString();
435         assertTrue("String built from newOutput() must contain the expected bytes",
436             isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
437       }
438     }
439   }
440 
441   // Test newOutput() using a variety of buffer sizes, but writing all the
442   // characters using write(byte);
testNewOutput_WriteChar()443   public void testNewOutput_WriteChar() throws IOException {
444     byte[] bytes = getTestBytes();
445     int length = bytes.length;
446     int[] bufferSizes = {0, 1, 128, 256, length / 2,
447                          length - 1, length, length + 1,
448                          2 * length, 3 * length};
449     for (int bufferSize : bufferSizes) {
450       ByteString.Output output = ByteString.newOutput(bufferSize);
451       for (byte byteValue : bytes) {
452         output.write(byteValue);
453       }
454       ByteString byteString = output.toByteString();
455       assertTrue("String built from newOutput() must contain the expected bytes",
456           isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
457     }
458   }
459 
460   // Test newOutput() in which we write the bytes using a variety of methods
461   // and sizes, and in which we repeatedly call toByteString() in the middle.
testNewOutput_Mixed()462   public void testNewOutput_Mixed() throws IOException {
463     Random rng = new Random(1);
464     byte[] bytes = getTestBytes();
465     int length = bytes.length;
466     int[] bufferSizes = {0, 1, 128, 256, length / 2,
467                          length - 1, length, length + 1,
468                          2 * length, 3 * length};
469 
470     for (int bufferSize : bufferSizes) {
471       // Test writing the entire output using a mixture of write sizes and
472       // methods;
473       ByteString.Output output = ByteString.newOutput(bufferSize);
474       int position = 0;
475       while (position < bytes.length) {
476         if (rng.nextBoolean()) {
477           int count = 1 + rng.nextInt(bytes.length - position);
478           output.write(bytes, position, count);
479           position += count;
480         } else {
481           output.write(bytes[position]);
482           position++;
483         }
484         assertEquals("size() returns the right value", position, output.size());
485         assertTrue("newOutput() substring must have correct bytes",
486             isArrayRange(output.toByteString().toByteArray(),
487                 bytes, 0, position));
488       }
489       ByteString byteString = output.toByteString();
490       assertTrue("String built from newOutput() must contain the expected bytes",
491           isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
492     }
493   }
494 
testNewOutputEmpty()495   public void testNewOutputEmpty() throws IOException {
496     // Make sure newOutput() correctly builds empty byte strings
497     ByteString byteString = ByteString.newOutput().toByteString();
498     assertEquals(ByteString.EMPTY, byteString);
499   }
500 
testNewOutput_Mutating()501   public void testNewOutput_Mutating() throws IOException {
502     Output os = ByteString.newOutput(5);
503     os.write(new byte[] {1, 2, 3, 4, 5});
504     EvilOutputStream eos = new EvilOutputStream();
505     os.writeTo(eos);
506     byte[] capturedArray = eos.capturedArray;
507     ByteString byteString = os.toByteString();
508     byte[] oldValue = byteString.toByteArray();
509     Arrays.fill(capturedArray, (byte) 0);
510     byte[] newValue = byteString.toByteArray();
511     assertTrue("Output must not provide access to the underlying byte array",
512         Arrays.equals(oldValue, newValue));
513   }
514 
testNewCodedBuilder()515   public void testNewCodedBuilder() throws IOException {
516     byte[] bytes = getTestBytes();
517     ByteString.CodedBuilder builder = ByteString.newCodedBuilder(bytes.length);
518     builder.getCodedOutput().writeRawBytes(bytes);
519     ByteString byteString = builder.build();
520     assertTrue("String built from newCodedBuilder() must contain the expected bytes",
521         isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length));
522   }
523 
testSubstringParity()524   public void testSubstringParity() {
525     byte[] bigBytes = getTestBytes(2048 * 1024, 113344L);
526     int start = 512 * 1024 - 3333;
527     int end   = 512 * 1024 + 7777;
528     ByteString concreteSubstring = ByteString.copyFrom(bigBytes).substring(start, end);
529     boolean ok = true;
530     for (int i = start; ok && i < end; ++i) {
531       ok = (bigBytes[i] == concreteSubstring.byteAt(i - start));
532     }
533     assertTrue("Concrete substring didn't capture the right bytes", ok);
534 
535     ByteString literalString = ByteString.copyFrom(bigBytes, start, end - start);
536     assertTrue("Substring must be equal to literal string",
537         concreteSubstring.equals(literalString));
538     assertEquals("Substring must have same hashcode as literal string",
539         literalString.hashCode(), concreteSubstring.hashCode());
540   }
541 
testCompositeSubstring()542   public void testCompositeSubstring() {
543     byte[] referenceBytes = getTestBytes(77748, 113344L);
544 
545     List<ByteString> pieces = makeConcretePieces(referenceBytes);
546     ByteString listString = ByteString.copyFrom(pieces);
547 
548     int from = 1000;
549     int to = 40000;
550     ByteString compositeSubstring = listString.substring(from, to);
551     byte[] substringBytes = compositeSubstring.toByteArray();
552     boolean stillEqual = true;
553     for (int i = 0; stillEqual && i < to - from; ++i) {
554       stillEqual = referenceBytes[from + i] == substringBytes[i];
555     }
556     assertTrue("Substring must return correct bytes", stillEqual);
557 
558     stillEqual = true;
559     for (int i = 0; stillEqual && i < to - from; ++i) {
560       stillEqual = referenceBytes[from + i] == compositeSubstring.byteAt(i);
561     }
562     assertTrue("Substring must support byteAt() correctly", stillEqual);
563 
564     ByteString literalSubstring = ByteString.copyFrom(referenceBytes, from, to - from);
565     assertTrue("Composite substring must equal a literal substring over the same bytes",
566         compositeSubstring.equals(literalSubstring));
567     assertTrue("Literal substring must equal a composite substring over the same bytes",
568         literalSubstring.equals(compositeSubstring));
569 
570     assertEquals("We must get the same hashcodes for composite and literal substrings",
571         literalSubstring.hashCode(), compositeSubstring.hashCode());
572 
573     assertFalse("We can't be equal to a proper substring",
574         compositeSubstring.equals(literalSubstring.substring(0, literalSubstring.size() - 1)));
575   }
576 
testCopyFromList()577   public void testCopyFromList() {
578     byte[] referenceBytes = getTestBytes(77748, 113344L);
579     ByteString literalString = ByteString.copyFrom(referenceBytes);
580 
581     List<ByteString> pieces = makeConcretePieces(referenceBytes);
582     ByteString listString = ByteString.copyFrom(pieces);
583 
584     assertTrue("Composite string must be equal to literal string",
585         listString.equals(literalString));
586     assertEquals("Composite string must have same hashcode as literal string",
587         literalString.hashCode(), listString.hashCode());
588   }
589 
testConcat()590   public void testConcat() {
591     byte[] referenceBytes = getTestBytes(77748, 113344L);
592     ByteString literalString = ByteString.copyFrom(referenceBytes);
593 
594     List<ByteString> pieces = makeConcretePieces(referenceBytes);
595 
596     Iterator<ByteString> iter = pieces.iterator();
597     ByteString concatenatedString = iter.next();
598     while (iter.hasNext()) {
599       concatenatedString = concatenatedString.concat(iter.next());
600     }
601 
602     assertTrue("Concatenated string must be equal to literal string",
603         concatenatedString.equals(literalString));
604     assertEquals("Concatenated string must have same hashcode as literal string",
605         literalString.hashCode(), concatenatedString.hashCode());
606   }
607 
608   /**
609    * Test the Rope implementation can deal with Empty nodes, even though we
610    * guard against them. See also {@link LiteralByteStringTest#testConcat_empty()}.
611    */
testConcat_empty()612   public void testConcat_empty() {
613     byte[] referenceBytes = getTestBytes(7748, 113344L);
614     ByteString literalString = ByteString.copyFrom(referenceBytes);
615 
616     ByteString duo = RopeByteString.newInstanceForTest(literalString, literalString);
617     ByteString temp = RopeByteString.newInstanceForTest(
618         RopeByteString.newInstanceForTest(literalString, ByteString.EMPTY),
619         RopeByteString.newInstanceForTest(ByteString.EMPTY, literalString));
620     ByteString quintet = RopeByteString.newInstanceForTest(temp, ByteString.EMPTY);
621 
622     assertTrue("String with concatenated nulls must equal simple concatenate",
623         duo.equals(quintet));
624     assertEquals("String with concatenated nulls have same hashcode as simple concatenate",
625         duo.hashCode(), quintet.hashCode());
626 
627     ByteString.ByteIterator duoIter = duo.iterator();
628     ByteString.ByteIterator quintetIter = quintet.iterator();
629     boolean stillEqual = true;
630     while (stillEqual && quintetIter.hasNext()) {
631       stillEqual = (duoIter.nextByte() == quintetIter.nextByte());
632     }
633     assertTrue("We must get the same characters by iterating", stillEqual);
634     assertFalse("Iterator must be exhausted", duoIter.hasNext());
635     try {
636       duoIter.nextByte();
637       fail("Should have thrown an exception.");
638     } catch (NoSuchElementException e) {
639       // This is success
640     }
641     try {
642       quintetIter.nextByte();
643       fail("Should have thrown an exception.");
644     } catch (NoSuchElementException e) {
645       // This is success
646     }
647 
648     // Test that even if we force empty strings in as rope leaves in this
649     // configuration, we always get a (possibly Bounded) LiteralByteString
650     // for a length 1 substring.
651     //
652     // It is possible, using the testing factory method to create deeply nested
653     // trees of empty leaves, to make a string that will fail this test.
654     for (int i = 1; i < duo.size(); ++i) {
655       assertTrue("Substrings of size() < 2 must not be RopeByteStrings",
656           duo.substring(i - 1, i) instanceof LiteralByteString);
657     }
658     for (int i = 1; i < quintet.size(); ++i) {
659       assertTrue("Substrings of size() < 2 must not be RopeByteStrings",
660           quintet.substring(i - 1, i) instanceof LiteralByteString);
661     }
662   }
663 
testStartsWith()664   public void testStartsWith() {
665     byte[] bytes = getTestBytes(1000, 1234L);
666     ByteString string = ByteString.copyFrom(bytes);
667     ByteString prefix = ByteString.copyFrom(bytes, 0, 500);
668     ByteString suffix = ByteString.copyFrom(bytes, 400, 600);
669     assertTrue(string.startsWith(ByteString.EMPTY));
670     assertTrue(string.startsWith(string));
671     assertTrue(string.startsWith(prefix));
672     assertFalse(string.startsWith(suffix));
673     assertFalse(prefix.startsWith(suffix));
674     assertFalse(suffix.startsWith(prefix));
675     assertFalse(ByteString.EMPTY.startsWith(prefix));
676     assertTrue(ByteString.EMPTY.startsWith(ByteString.EMPTY));
677   }
678 
makeConcretePieces(byte[] referenceBytes)679   static List<ByteString> makeConcretePieces(byte[] referenceBytes) {
680     List<ByteString> pieces = new ArrayList<ByteString>();
681     // Starting length should be small enough that we'll do some concatenating by
682     // copying if we just concatenate all these pieces together.
683     for (int start = 0, length = 16; start < referenceBytes.length; start += length) {
684       length = (length << 1) - 1;
685       if (start + length > referenceBytes.length) {
686         length = referenceBytes.length - start;
687       }
688       pieces.add(ByteString.copyFrom(referenceBytes, start, length));
689     }
690     return pieces;
691   }
692 }
693