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 static com.google.common.truth.Truth.assertThat; 34 35 import java.io.ByteArrayInputStream; 36 import java.io.ByteArrayOutputStream; 37 import java.io.EOFException; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.io.ObjectInputStream; 41 import java.io.ObjectOutputStream; 42 import java.io.OutputStream; 43 import java.io.UnsupportedEncodingException; 44 import java.nio.ByteBuffer; 45 import java.util.Arrays; 46 import java.util.List; 47 import java.util.NoSuchElementException; 48 import junit.framework.TestCase; 49 50 /** 51 * Test {@code LiteralByteString} by setting up a reference string in {@link #setUp()}. This class 52 * is designed to be extended for testing extensions of {@code LiteralByteString} such as {@code 53 * BoundedByteString}, see {@link BoundedByteStringTest}. 54 * 55 * @author carlanton@google.com (Carl Haverl) 56 */ 57 public class LiteralByteStringTest extends TestCase { 58 protected static final String UTF_8 = "UTF-8"; 59 60 protected String classUnderTest; 61 protected byte[] referenceBytes; 62 protected ByteString stringUnderTest; 63 protected int expectedHashCode; 64 65 @Override setUp()66 protected void setUp() throws Exception { 67 classUnderTest = "LiteralByteString"; 68 referenceBytes = ByteStringTest.getTestBytes(1234, 11337766L); 69 stringUnderTest = ByteString.copyFrom(referenceBytes); 70 expectedHashCode = 331161852; 71 } 72 testExpectedType()73 public void testExpectedType() { 74 String actualClassName = getActualClassName(stringUnderTest); 75 assertEquals(classUnderTest + " should match type exactly", classUnderTest, actualClassName); 76 } 77 getActualClassName(Object object)78 protected String getActualClassName(Object object) { 79 return object.getClass().getSimpleName(); 80 } 81 testByteAt()82 public void testByteAt() { 83 boolean stillEqual = true; 84 for (int i = 0; stillEqual && i < referenceBytes.length; ++i) { 85 stillEqual = (referenceBytes[i] == stringUnderTest.byteAt(i)); 86 } 87 assertTrue(classUnderTest + " must capture the right bytes", stillEqual); 88 } 89 testByteIterator()90 public void testByteIterator() { 91 boolean stillEqual = true; 92 ByteString.ByteIterator iter = stringUnderTest.iterator(); 93 for (int i = 0; stillEqual && i < referenceBytes.length; ++i) { 94 stillEqual = (iter.hasNext() && referenceBytes[i] == iter.nextByte()); 95 } 96 assertTrue(classUnderTest + " must capture the right bytes", stillEqual); 97 assertFalse(classUnderTest + " must have exhausted the iterator", iter.hasNext()); 98 99 try { 100 iter.nextByte(); 101 fail("Should have thrown an exception."); 102 } catch (NoSuchElementException e) { 103 // This is success 104 } 105 } 106 testByteIterable()107 public void testByteIterable() { 108 boolean stillEqual = true; 109 int j = 0; 110 for (byte quantum : stringUnderTest) { 111 stillEqual = (referenceBytes[j] == quantum); 112 ++j; 113 } 114 assertTrue(classUnderTest + " must capture the right bytes as Bytes", stillEqual); 115 assertEquals(classUnderTest + " iterable character count", referenceBytes.length, j); 116 } 117 testSize()118 public void testSize() { 119 assertEquals( 120 classUnderTest + " must have the expected size", 121 referenceBytes.length, 122 stringUnderTest.size()); 123 } 124 testGetTreeDepth()125 public void testGetTreeDepth() { 126 assertEquals(classUnderTest + " must have depth 0", 0, stringUnderTest.getTreeDepth()); 127 } 128 testIsBalanced()129 public void testIsBalanced() { 130 assertTrue(classUnderTest + " is technically balanced", stringUnderTest.isBalanced()); 131 } 132 testCopyTo_ByteArrayOffsetLength()133 public void testCopyTo_ByteArrayOffsetLength() { 134 int destinationOffset = 50; 135 int length = 100; 136 byte[] destination = new byte[destinationOffset + length]; 137 int sourceOffset = 213; 138 stringUnderTest.copyTo(destination, sourceOffset, destinationOffset, length); 139 boolean stillEqual = true; 140 for (int i = 0; stillEqual && i < length; ++i) { 141 stillEqual = referenceBytes[i + sourceOffset] == destination[i + destinationOffset]; 142 } 143 assertTrue(classUnderTest + ".copyTo(4 arg) must give the expected bytes", stillEqual); 144 } 145 testCopyTo_ByteArrayOffsetLengthErrors()146 public void testCopyTo_ByteArrayOffsetLengthErrors() { 147 int destinationOffset = 50; 148 int length = 100; 149 byte[] destination = new byte[destinationOffset + length]; 150 151 try { 152 // Copy one too many bytes 153 stringUnderTest.copyTo( 154 destination, stringUnderTest.size() + 1 - length, destinationOffset, length); 155 fail("Should have thrown an exception when copying too many bytes of a " + classUnderTest); 156 } catch (IndexOutOfBoundsException expected) { 157 // This is success 158 } 159 160 try { 161 // Copy with illegal negative sourceOffset 162 stringUnderTest.copyTo(destination, -1, destinationOffset, length); 163 fail( 164 "Should have thrown an exception when given a negative sourceOffset in " 165 + classUnderTest); 166 } catch (IndexOutOfBoundsException expected) { 167 // This is success 168 } 169 170 try { 171 // Copy with illegal negative destinationOffset 172 stringUnderTest.copyTo(destination, 0, -1, length); 173 fail( 174 "Should have thrown an exception when given a negative destinationOffset in " 175 + classUnderTest); 176 } catch (IndexOutOfBoundsException expected) { 177 // This is success 178 } 179 180 try { 181 // Copy with illegal negative size 182 stringUnderTest.copyTo(destination, 0, 0, -1); 183 fail("Should have thrown an exception when given a negative size in " + classUnderTest); 184 } catch (IndexOutOfBoundsException expected) { 185 // This is success 186 } 187 188 try { 189 // Copy with illegal too-large sourceOffset 190 stringUnderTest.copyTo(destination, 2 * stringUnderTest.size(), 0, length); 191 fail( 192 "Should have thrown an exception when the destinationOffset is too large in " 193 + classUnderTest); 194 } catch (IndexOutOfBoundsException expected) { 195 // This is success 196 } 197 198 try { 199 // Copy with illegal too-large destinationOffset 200 stringUnderTest.copyTo(destination, 0, 2 * destination.length, length); 201 fail( 202 "Should have thrown an exception when the destinationOffset is too large in " 203 + classUnderTest); 204 } catch (IndexOutOfBoundsException expected) { 205 // This is success 206 } 207 } 208 testCopyTo_ByteBuffer()209 public void testCopyTo_ByteBuffer() { 210 ByteBuffer myBuffer = ByteBuffer.allocate(referenceBytes.length); 211 stringUnderTest.copyTo(myBuffer); 212 assertTrue( 213 classUnderTest + ".copyTo(ByteBuffer) must give back the same bytes", 214 Arrays.equals(referenceBytes, myBuffer.array())); 215 } 216 testMarkSupported()217 public void testMarkSupported() { 218 InputStream stream = stringUnderTest.newInput(); 219 assertTrue(classUnderTest + ".newInput() must support marking", stream.markSupported()); 220 } 221 testMarkAndReset()222 public void testMarkAndReset() throws IOException { 223 int fraction = stringUnderTest.size() / 3; 224 225 InputStream stream = stringUnderTest.newInput(); 226 stream.mark(stringUnderTest.size()); // First, mark() the end. 227 228 skipFully(stream, fraction); // Skip a large fraction, but not all. 229 int available = stream.available(); 230 assertTrue( 231 classUnderTest + ": after skipping to the 'middle', half the bytes are available", 232 (stringUnderTest.size() - fraction) == available); 233 stream.reset(); 234 235 skipFully(stream, stringUnderTest.size()); // Skip to the end. 236 available = stream.available(); 237 assertTrue( 238 classUnderTest + ": after skipping to the end, no more bytes are available", 239 0 == available); 240 } 241 242 /** 243 * Discards {@code n} bytes of data from the input stream. This method will block until the full 244 * amount has been skipped. Does not close the stream. 245 * 246 * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency. 247 * 248 * @param in the input stream to read from 249 * @param n the number of bytes to skip 250 * @throws EOFException if this stream reaches the end before skipping all the bytes 251 * @throws IOException if an I/O error occurs, or the stream does not support skipping 252 */ skipFully(InputStream in, long n)253 static void skipFully(InputStream in, long n) throws IOException { 254 long toSkip = n; 255 while (n > 0) { 256 long amt = in.skip(n); 257 if (amt == 0) { 258 // Force a blocking read to avoid infinite loop 259 if (in.read() == -1) { 260 long skipped = toSkip - n; 261 throw new EOFException( 262 "reached end of stream after skipping " 263 + skipped 264 + " bytes; " 265 + toSkip 266 + " bytes expected"); 267 } 268 n--; 269 } else { 270 n -= amt; 271 } 272 } 273 } 274 testAsReadOnlyByteBuffer()275 public void testAsReadOnlyByteBuffer() { 276 ByteBuffer byteBuffer = stringUnderTest.asReadOnlyByteBuffer(); 277 byte[] roundTripBytes = new byte[referenceBytes.length]; 278 assertTrue(byteBuffer.remaining() == referenceBytes.length); 279 assertTrue(byteBuffer.isReadOnly()); 280 byteBuffer.get(roundTripBytes); 281 assertTrue( 282 classUnderTest + ".asReadOnlyByteBuffer() must give back the same bytes", 283 Arrays.equals(referenceBytes, roundTripBytes)); 284 } 285 testAsReadOnlyByteBufferList()286 public void testAsReadOnlyByteBufferList() { 287 List<ByteBuffer> byteBuffers = stringUnderTest.asReadOnlyByteBufferList(); 288 int bytesSeen = 0; 289 byte[] roundTripBytes = new byte[referenceBytes.length]; 290 for (ByteBuffer byteBuffer : byteBuffers) { 291 int thisLength = byteBuffer.remaining(); 292 assertTrue(byteBuffer.isReadOnly()); 293 assertTrue(bytesSeen + thisLength <= referenceBytes.length); 294 byteBuffer.get(roundTripBytes, bytesSeen, thisLength); 295 bytesSeen += thisLength; 296 } 297 assertTrue(bytesSeen == referenceBytes.length); 298 assertTrue( 299 classUnderTest + ".asReadOnlyByteBufferTest() must give back the same bytes", 300 Arrays.equals(referenceBytes, roundTripBytes)); 301 } 302 testToByteArray()303 public void testToByteArray() { 304 byte[] roundTripBytes = stringUnderTest.toByteArray(); 305 assertTrue( 306 classUnderTest + ".toByteArray() must give back the same bytes", 307 Arrays.equals(referenceBytes, roundTripBytes)); 308 } 309 testWriteTo()310 public void testWriteTo() throws IOException { 311 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 312 stringUnderTest.writeTo(bos); 313 byte[] roundTripBytes = bos.toByteArray(); 314 assertTrue( 315 classUnderTest + ".writeTo() must give back the same bytes", 316 Arrays.equals(referenceBytes, roundTripBytes)); 317 } 318 testWriteToShouldNotExposeInternalBufferToOutputStream()319 public void testWriteToShouldNotExposeInternalBufferToOutputStream() throws IOException { 320 OutputStream os = 321 new OutputStream() { 322 @Override 323 public void write(byte[] b, int off, int len) { 324 Arrays.fill(b, off, off + len, (byte) 0); 325 } 326 327 @Override 328 public void write(int b) { 329 throw new UnsupportedOperationException(); 330 } 331 }; 332 333 stringUnderTest.writeTo(os); 334 assertTrue( 335 classUnderTest + ".writeTo() must not grant access to underlying array", 336 Arrays.equals(referenceBytes, stringUnderTest.toByteArray())); 337 } 338 testWriteToInternalShouldExposeInternalBufferToOutputStream()339 public void testWriteToInternalShouldExposeInternalBufferToOutputStream() throws IOException { 340 OutputStream os = 341 new OutputStream() { 342 @Override 343 public void write(byte[] b, int off, int len) { 344 Arrays.fill(b, off, off + len, (byte) 0); 345 } 346 347 @Override 348 public void write(int b) { 349 throw new UnsupportedOperationException(); 350 } 351 }; 352 353 stringUnderTest.writeToInternal(os, 0, stringUnderTest.size()); 354 byte[] allZeros = new byte[stringUnderTest.size()]; 355 assertTrue( 356 classUnderTest + ".writeToInternal() must grant access to underlying array", 357 Arrays.equals(allZeros, stringUnderTest.toByteArray())); 358 } 359 testWriteToShouldExposeInternalBufferToByteOutput()360 public void testWriteToShouldExposeInternalBufferToByteOutput() throws IOException { 361 ByteOutput out = 362 new ByteOutput() { 363 @Override 364 public void write(byte value) throws IOException { 365 throw new UnsupportedOperationException(); 366 } 367 368 @Override 369 public void write(byte[] value, int offset, int length) throws IOException { 370 throw new UnsupportedOperationException(); 371 } 372 373 @Override 374 public void write(ByteBuffer value) throws IOException { 375 throw new UnsupportedOperationException(); 376 } 377 378 @Override 379 public void writeLazy(byte[] value, int offset, int length) throws IOException { 380 Arrays.fill(value, offset, offset + length, (byte) 0); 381 } 382 383 @Override 384 public void writeLazy(ByteBuffer value) throws IOException { 385 throw new UnsupportedOperationException(); 386 } 387 }; 388 389 stringUnderTest.writeTo(out); 390 byte[] allZeros = new byte[stringUnderTest.size()]; 391 assertTrue( 392 classUnderTest + ".writeToInternal() must grant access to underlying array", 393 Arrays.equals(allZeros, stringUnderTest.toByteArray())); 394 } 395 testNewOutput()396 public void testNewOutput() throws IOException { 397 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 398 ByteString.Output output = ByteString.newOutput(); 399 stringUnderTest.writeTo(output); 400 assertEquals("Output Size returns correct result", output.size(), stringUnderTest.size()); 401 output.writeTo(bos); 402 assertTrue( 403 "Output.writeTo() must give back the same bytes", 404 Arrays.equals(referenceBytes, bos.toByteArray())); 405 406 // write the output stream to itself! This should cause it to double 407 output.writeTo(output); 408 assertEquals( 409 "Writing an output stream to itself is successful", 410 stringUnderTest.concat(stringUnderTest), 411 output.toByteString()); 412 413 output.reset(); 414 assertEquals("Output.reset() resets the output", 0, output.size()); 415 assertEquals("Output.reset() resets the output", ByteString.EMPTY, output.toByteString()); 416 } 417 testToString()418 public void testToString() throws UnsupportedEncodingException { 419 String testString = "I love unicode \u1234\u5678 characters"; 420 ByteString unicode = ByteString.wrap(testString.getBytes(Internal.UTF_8)); 421 String roundTripString = unicode.toString(UTF_8); 422 assertEquals(classUnderTest + " unicode must match", testString, roundTripString); 423 } 424 testCharsetToString()425 public void testCharsetToString() { 426 String testString = "I love unicode \u1234\u5678 characters"; 427 ByteString unicode = ByteString.wrap(testString.getBytes(Internal.UTF_8)); 428 String roundTripString = unicode.toString(Internal.UTF_8); 429 assertEquals(classUnderTest + " unicode must match", testString, roundTripString); 430 } 431 testToString_returnsCanonicalEmptyString()432 public void testToString_returnsCanonicalEmptyString() { 433 assertSame( 434 classUnderTest + " must be the same string references", 435 ByteString.EMPTY.toString(Internal.UTF_8), 436 ByteString.wrap(new byte[] {}).toString(Internal.UTF_8)); 437 } 438 testToString_raisesException()439 public void testToString_raisesException() { 440 try { 441 ByteString.EMPTY.toString("invalid"); 442 fail("Should have thrown an exception."); 443 } catch (UnsupportedEncodingException expected) { 444 // This is success 445 } 446 447 try { 448 ByteString.wrap(referenceBytes).toString("invalid"); 449 fail("Should have thrown an exception."); 450 } catch (UnsupportedEncodingException expected) { 451 // This is success 452 } 453 } 454 testEquals()455 public void testEquals() { 456 assertEquals(classUnderTest + " must not equal null", false, stringUnderTest.equals(null)); 457 assertEquals(classUnderTest + " must equal self", stringUnderTest, stringUnderTest); 458 assertFalse( 459 classUnderTest + " must not equal the empty string", 460 stringUnderTest.equals(ByteString.EMPTY)); 461 assertEquals( 462 classUnderTest + " empty strings must be equal", 463 ByteString.wrap(new byte[] {}), 464 stringUnderTest.substring(55, 55)); 465 assertEquals( 466 classUnderTest + " must equal another string with the same value", 467 stringUnderTest, 468 ByteString.wrap(referenceBytes)); 469 470 byte[] mungedBytes = new byte[referenceBytes.length]; 471 System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length); 472 mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF); 473 assertFalse( 474 classUnderTest + " must not equal every string with the same length", 475 stringUnderTest.equals(ByteString.wrap(mungedBytes))); 476 } 477 testHashCode()478 public void testHashCode() { 479 int hash = stringUnderTest.hashCode(); 480 assertEquals(classUnderTest + " must have expected hashCode", expectedHashCode, hash); 481 } 482 testPeekCachedHashCode()483 public void testPeekCachedHashCode() { 484 assertEquals( 485 classUnderTest + ".peekCachedHashCode() should return zero at first", 486 0, 487 stringUnderTest.peekCachedHashCode()); 488 stringUnderTest.hashCode(); 489 assertEquals( 490 classUnderTest + ".peekCachedHashCode should return zero at first", 491 expectedHashCode, 492 stringUnderTest.peekCachedHashCode()); 493 } 494 testPartialHash()495 public void testPartialHash() { 496 // partialHash() is more strenuously tested elsewhere by testing hashes of substrings. 497 // This test would fail if the expected hash were 1. It's not. 498 int hash = stringUnderTest.partialHash(stringUnderTest.size(), 0, stringUnderTest.size()); 499 assertEquals( 500 classUnderTest + ".partialHash() must yield expected hashCode", expectedHashCode, hash); 501 } 502 testNewInput()503 public void testNewInput() throws IOException { 504 InputStream input = stringUnderTest.newInput(); 505 assertEquals( 506 "InputStream.available() returns correct value", stringUnderTest.size(), input.available()); 507 boolean stillEqual = true; 508 for (byte referenceByte : referenceBytes) { 509 int expectedInt = (referenceByte & 0xFF); 510 stillEqual = (expectedInt == input.read()); 511 } 512 assertEquals("InputStream.available() returns correct value", 0, input.available()); 513 assertTrue(classUnderTest + " must give the same bytes from the InputStream", stillEqual); 514 assertEquals(classUnderTest + " InputStream must now be exhausted", -1, input.read()); 515 } 516 testNewInput_skip()517 public void testNewInput_skip() throws IOException { 518 InputStream input = stringUnderTest.newInput(); 519 int stringSize = stringUnderTest.size(); 520 int nearEndIndex = stringSize * 2 / 3; 521 522 long skipped1 = input.skip(nearEndIndex); 523 assertEquals("InputStream.skip()", skipped1, nearEndIndex); 524 assertEquals("InputStream.available()", stringSize - skipped1, input.available()); 525 assertTrue("InputStream.mark() is available", input.markSupported()); 526 input.mark(0); 527 assertEquals( 528 "InputStream.skip(), read()", stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read()); 529 assertEquals("InputStream.available()", stringSize - skipped1 - 1, input.available()); 530 531 long skipped2 = input.skip(stringSize); 532 assertEquals("InputStream.skip() incomplete", skipped2, stringSize - skipped1 - 1); 533 assertEquals("InputStream.skip(), no more input", 0, input.available()); 534 assertEquals("InputStream.skip(), no more input", -1, input.read()); 535 assertThat(input.skip(1)).isEqualTo(0); 536 assertThat(input.read(new byte[1], /* off= */ 0, /*len=*/ 0)).isEqualTo(-1); 537 538 input.reset(); 539 assertEquals("InputStream.reset() succeeded", stringSize - skipped1, input.available()); 540 assertEquals( 541 "InputStream.reset(), read()", stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read()); 542 } 543 testNewCodedInput()544 public void testNewCodedInput() throws IOException { 545 CodedInputStream cis = stringUnderTest.newCodedInput(); 546 byte[] roundTripBytes = cis.readRawBytes(referenceBytes.length); 547 assertTrue( 548 classUnderTest + " must give the same bytes back from the CodedInputStream", 549 Arrays.equals(referenceBytes, roundTripBytes)); 550 assertTrue(classUnderTest + " CodedInputStream must now be exhausted", cis.isAtEnd()); 551 } 552 553 /** 554 * Make sure we keep things simple when concatenating with empty. See also {@link 555 * ByteStringTest#testConcat_empty()}. 556 */ testConcat_empty()557 public void testConcat_empty() { 558 assertSame( 559 classUnderTest + " concatenated with empty must give " + classUnderTest, 560 stringUnderTest.concat(ByteString.EMPTY), 561 stringUnderTest); 562 assertSame( 563 "empty concatenated with " + classUnderTest + " must give " + classUnderTest, 564 ByteString.EMPTY.concat(stringUnderTest), 565 stringUnderTest); 566 } 567 testJavaSerialization()568 public void testJavaSerialization() throws Exception { 569 ByteArrayOutputStream out = new ByteArrayOutputStream(); 570 ObjectOutputStream oos = new ObjectOutputStream(out); 571 oos.writeObject(stringUnderTest); 572 oos.close(); 573 byte[] pickled = out.toByteArray(); 574 InputStream in = new ByteArrayInputStream(pickled); 575 ObjectInputStream ois = new ObjectInputStream(in); 576 Object o = ois.readObject(); 577 assertTrue("Didn't get a ByteString back", o instanceof ByteString); 578 assertEquals("Should get an equal ByteString back", stringUnderTest, o); 579 } 580 } 581