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