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