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