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