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
31package com.google.protobuf;
32
33import com.google.protobuf.ByteString.Output;
34
35import junit.framework.TestCase;
36
37import java.io.ByteArrayInputStream;
38import java.io.IOException;
39import java.io.InputStream;
40import java.io.OutputStream;
41import java.io.UnsupportedEncodingException;
42import java.nio.ByteBuffer;
43import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.Iterator;
46import java.util.List;
47import java.util.NoSuchElementException;
48import 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 */
56public class ByteStringTest extends TestCase {
57
58  private static final String UTF_16 = "UTF-16";
59
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
67  private byte[] getTestBytes(int size) {
68    return getTestBytes(size, 445566L);
69  }
70
71  private byte[] getTestBytes() {
72    return getTestBytes(1000);
73  }
74
75  // Compare the entire left array with a subset of the right array.
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.
85  private boolean isArray(byte[] left, byte[] right) {
86    return left.length == right.length && isArrayRange(left, right, 0, left.length);
87  }
88
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
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
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
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
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
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
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
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
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
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
179  public void testReadFrom_smallStream() throws IOException {
180    assertReadFrom(getTestBytes(10));
181  }
182
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.
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.
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.
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().
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.
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.
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().
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.
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 {
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
321    public ReluctantStream(byte[] data) {
322      this.data = data;
323    }
324
325    @Override public int read() {
326      if (pos == data.length) {
327        return -1;
328      } else {
329        return data[pos++];
330      }
331    }
332
333    @Override public int read(byte[] buf) {
334      return read(buf, 0, buf.length);
335    }
336
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 {
350    public AvailableStream(byte[] data) {
351      super(data);
352    }
353
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
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
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
388    public void write(byte[] buf, int off, int len) {
389      if (capturedArray == null) {
390        capturedArray = buf;
391      }
392    }
393
394    @Override
395    public void write(int ignored) {
396      // Purposefully do nothing.
397    }
398  }
399
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
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
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);
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.
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
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
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
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
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
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
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
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   */
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
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
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