1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 // https://developers.google.com/protocol-buffers/
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
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
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.
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.
31 package com.google.protobuf;
33 import static com.google.protobuf.Internal.UTF_8;
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.BufferOverflowException;
45 import java.nio.ByteBuffer;
46 import java.util.Arrays;
47 import java.util.List;
48 import java.util.NoSuchElementException;
49 import junit.framework.TestCase;
51 /** Tests for {@link NioByteString}. */
52 public class NioByteStringTest extends TestCase {
53 private static final ByteString EMPTY = new NioByteString(ByteBuffer.wrap(new byte[0]));
54 private static final String CLASSNAME = NioByteString.class.getSimpleName();
55 private static final byte[] BYTES = ByteStringTest.getTestBytes(1234, 11337766L);
56 private static final int EXPECTED_HASH = ByteString.wrap(BYTES).hashCode();
58 private final ByteBuffer backingBuffer = ByteBuffer.wrap(BYTES.clone());
59 private final ByteString testString = new NioByteString(backingBuffer);
61 public void testExpectedType() {
62 String actualClassName = getActualClassName(testString);
63 assertEquals(CLASSNAME + " should match type exactly", CLASSNAME, actualClassName);
66 protected String getActualClassName(Object object) {
67 String actualClassName = object.getClass().getName();
68 actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1);
69 return actualClassName;
72 public void testByteAt() {
73 boolean stillEqual = true;
74 for (int i = 0; stillEqual && i < BYTES.length; ++i) {
75 stillEqual = (BYTES[i] == testString.byteAt(i));
77 assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
80 public void testByteIterator() {
81 boolean stillEqual = true;
82 ByteString.ByteIterator iter = testString.iterator();
83 for (int i = 0; stillEqual && i < BYTES.length; ++i) {
84 stillEqual = (iter.hasNext() && BYTES[i] == iter.nextByte());
86 assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
87 assertFalse(CLASSNAME + " must have exhausted the itertor", iter.hasNext());
91 fail("Should have thrown an exception.");
92 } catch (NoSuchElementException e) {
97 public void testByteIterable() {
98 boolean stillEqual = true;
100 for (byte quantum : testString) {
101 stillEqual = (BYTES[j] == quantum);
104 assertTrue(CLASSNAME + " must capture the right bytes as Bytes", stillEqual);
105 assertEquals(CLASSNAME + " iterable character count", BYTES.length, j);
108 public void testSize() {
109 assertEquals(CLASSNAME + " must have the expected size", BYTES.length, testString.size());
112 public void testGetTreeDepth() {
113 assertEquals(CLASSNAME + " must have depth 0", 0, testString.getTreeDepth());
116 public void testIsBalanced() {
117 assertTrue(CLASSNAME + " is technically balanced", testString.isBalanced());
120 public void testCopyTo_ByteArrayOffsetLength() {
121 int destinationOffset = 50;
123 byte[] destination = new byte[destinationOffset + length];
124 int sourceOffset = 213;
125 testString.copyTo(destination, sourceOffset, destinationOffset, length);
126 boolean stillEqual = true;
127 for (int i = 0; stillEqual && i < length; ++i) {
128 stillEqual = BYTES[i + sourceOffset] == destination[i + destinationOffset];
130 assertTrue(CLASSNAME + ".copyTo(4 arg) must give the expected bytes", stillEqual);
133 public void testCopyTo_ByteArrayOffsetLengthErrors() {
134 int destinationOffset = 50;
136 byte[] destination = new byte[destinationOffset + length];
139 // Copy one too many bytes
140 testString.copyTo(destination, testString.size() + 1 - length, destinationOffset, length);
141 fail("Should have thrown an exception when copying too many bytes of a " + CLASSNAME);
142 } catch (IndexOutOfBoundsException expected) {
147 // Copy with illegal negative sourceOffset
148 testString.copyTo(destination, -1, destinationOffset, length);
149 fail("Should have thrown an exception when given a negative sourceOffset in " + CLASSNAME);
150 } catch (IndexOutOfBoundsException expected) {
155 // Copy with illegal negative destinationOffset
156 testString.copyTo(destination, 0, -1, length);
158 "Should have thrown an exception when given a negative destinationOffset in "
160 } catch (IndexOutOfBoundsException expected) {
165 // Copy with illegal negative size
166 testString.copyTo(destination, 0, 0, -1);
167 fail("Should have thrown an exception when given a negative size in " + CLASSNAME);
168 } catch (IndexOutOfBoundsException expected) {
173 // Copy with illegal too-large sourceOffset
174 testString.copyTo(destination, 2 * testString.size(), 0, length);
176 "Should have thrown an exception when the destinationOffset is too large in "
178 } catch (IndexOutOfBoundsException expected) {
183 // Copy with illegal too-large destinationOffset
184 testString.copyTo(destination, 0, 2 * destination.length, length);
186 "Should have thrown an exception when the destinationOffset is too large in "
188 } catch (IndexOutOfBoundsException expected) {
193 public void testCopyTo_ByteBuffer() {
195 ByteBuffer myBuffer = ByteBuffer.allocate(BYTES.length);
196 testString.copyTo(myBuffer);
199 CLASSNAME + ".copyTo(ByteBuffer) must give back the same bytes", backingBuffer, myBuffer);
201 // Target buffer bigger than required.
202 myBuffer = ByteBuffer.allocate(testString.size() + 1);
203 testString.copyTo(myBuffer);
205 assertEquals(backingBuffer, myBuffer);
207 // Target buffer has no space.
208 myBuffer = ByteBuffer.allocate(0);
210 testString.copyTo(myBuffer);
211 fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
212 } catch (BufferOverflowException e) {
216 // Target buffer too small.
217 myBuffer = ByteBuffer.allocate(1);
219 testString.copyTo(myBuffer);
220 fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
221 } catch (BufferOverflowException e) {
226 public void testMarkSupported() {
227 InputStream stream = testString.newInput();
228 assertTrue(CLASSNAME + ".newInput() must support marking", stream.markSupported());
231 public void testMarkAndReset() throws IOException {
232 int fraction = testString.size() / 3;
234 InputStream stream = testString.newInput();
235 stream.mark(testString.size()); // First, mark() the end.
237 skipFully(stream, fraction); // Skip a large fraction, but not all.
239 CLASSNAME + ": after skipping to the 'middle', half the bytes are available",
240 (testString.size() - fraction),
244 CLASSNAME + ": after resetting, all bytes are available",
248 skipFully(stream, testString.size()); // Skip to the end.
250 CLASSNAME + ": after skipping to the end, no more bytes are available",
256 * Discards {@code n} bytes of data from the input stream. This method will block until the full
257 * amount has been skipped. Does not close the stream.
259 * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency.
261 * @param in the input stream to read from
262 * @param n the number of bytes to skip
263 * @throws EOFException if this stream reaches the end before skipping all the bytes
264 * @throws IOException if an I/O error occurs, or the stream does not support skipping
266 static void skipFully(InputStream in, long n) throws IOException {
269 long amt = in.skip(n);
271 // Force a blocking read to avoid infinite loop
272 if (in.read() == -1) {
273 long skipped = toSkip - n;
274 throw new EOFException(
275 "reached end of stream after skipping "
279 + " bytes expected");
288 public void testAsReadOnlyByteBuffer() {
289 ByteBuffer byteBuffer = testString.asReadOnlyByteBuffer();
290 byte[] roundTripBytes = new byte[BYTES.length];
291 assertTrue(byteBuffer.remaining() == BYTES.length);
292 assertTrue(byteBuffer.isReadOnly());
293 byteBuffer.get(roundTripBytes);
295 CLASSNAME + ".asReadOnlyByteBuffer() must give back the same bytes",
296 Arrays.equals(BYTES, roundTripBytes));
299 public void testAsReadOnlyByteBufferList() {
300 List<ByteBuffer> byteBuffers = testString.asReadOnlyByteBufferList();
302 byte[] roundTripBytes = new byte[BYTES.length];
303 for (ByteBuffer byteBuffer : byteBuffers) {
304 int thisLength = byteBuffer.remaining();
305 assertTrue(byteBuffer.isReadOnly());
306 assertTrue(bytesSeen + thisLength <= BYTES.length);
307 byteBuffer.get(roundTripBytes, bytesSeen, thisLength);
308 bytesSeen += thisLength;
310 assertTrue(bytesSeen == BYTES.length);
312 CLASSNAME + ".asReadOnlyByteBufferTest() must give back the same bytes",
313 Arrays.equals(BYTES, roundTripBytes));
316 public void testToByteArray() {
317 byte[] roundTripBytes = testString.toByteArray();
319 CLASSNAME + ".toByteArray() must give back the same bytes",
320 Arrays.equals(BYTES, roundTripBytes));
323 public void testWriteTo() throws IOException {
324 ByteArrayOutputStream bos = new ByteArrayOutputStream();
325 testString.writeTo(bos);
326 byte[] roundTripBytes = bos.toByteArray();
328 CLASSNAME + ".writeTo() must give back the same bytes",
329 Arrays.equals(BYTES, roundTripBytes));
332 public void testWriteToShouldNotExposeInternalBufferToOutputStream() throws IOException {
336 public void write(byte[] b, int off, int len) {
337 Arrays.fill(b, off, off + len, (byte) 0);
341 public void write(int b) {
342 throw new UnsupportedOperationException();
346 byte[] original = Arrays.copyOf(BYTES, BYTES.length);
347 testString.writeTo(os);
349 CLASSNAME + ".writeTo() must NOT grant access to underlying buffer",
350 Arrays.equals(original, BYTES));
353 public void testWriteToInternalShouldExposeInternalBufferToOutputStream() throws IOException {
357 public void write(byte[] b, int off, int len) {
358 Arrays.fill(b, off, off + len, (byte) 0);
362 public void write(int b) {
363 throw new UnsupportedOperationException();
367 testString.writeToInternal(os, 0, testString.size());
368 byte[] allZeros = new byte[testString.size()];
370 CLASSNAME + ".writeToInternal() must grant access to underlying buffer",
371 Arrays.equals(allZeros, backingBuffer.array()));
374 public void testWriteToShouldExposeInternalBufferToByteOutput() throws IOException {
378 public void write(byte value) throws IOException {
379 throw new UnsupportedOperationException();
383 public void write(byte[] value, int offset, int length) throws IOException {
384 throw new UnsupportedOperationException();
388 public void write(ByteBuffer value) throws IOException {
389 throw new UnsupportedOperationException();
393 public void writeLazy(byte[] value, int offset, int length) throws IOException {
394 throw new UnsupportedOperationException();
398 public void writeLazy(ByteBuffer value) throws IOException {
400 value.array(), value.arrayOffset(), value.arrayOffset() + value.limit(), (byte) 0);
404 testString.writeTo(out);
405 byte[] allZeros = new byte[testString.size()];
407 CLASSNAME + ".writeTo() must grant access to underlying buffer",
408 Arrays.equals(allZeros, backingBuffer.array()));
411 public void testNewOutput() throws IOException {
412 ByteArrayOutputStream bos = new ByteArrayOutputStream();
413 ByteString.Output output = ByteString.newOutput();
414 testString.writeTo(output);
415 assertEquals("Output Size returns correct result", output.size(), testString.size());
418 "Output.writeTo() must give back the same bytes", Arrays.equals(BYTES, bos.toByteArray()));
420 // write the output stream to itself! This should cause it to double
421 output.writeTo(output);
423 "Writing an output stream to itself is successful",
424 testString.concat(testString),
425 output.toByteString());
428 assertEquals("Output.reset() resets the output", 0, output.size());
429 assertEquals("Output.reset() resets the output", EMPTY, output.toByteString());
432 public void testToString() {
433 String testString = "I love unicode \u1234\u5678 characters";
434 ByteString unicode = forString(testString);
435 String roundTripString = unicode.toString(UTF_8);
436 assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
439 public void testCharsetToString() {
440 String testString = "I love unicode \u1234\u5678 characters";
441 ByteString unicode = forString(testString);
442 String roundTripString = unicode.toString(UTF_8);
443 assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
446 public void testToString_returnsCanonicalEmptyString() {
448 CLASSNAME + " must be the same string references",
449 EMPTY.toString(UTF_8),
450 new NioByteString(ByteBuffer.wrap(new byte[0])).toString(UTF_8));
453 public void testToString_raisesException() {
455 EMPTY.toString("invalid");
456 fail("Should have thrown an exception.");
457 } catch (UnsupportedEncodingException expected) {
462 testString.toString("invalid");
463 fail("Should have thrown an exception.");
464 } catch (UnsupportedEncodingException expected) {
469 public void testEquals() {
470 assertEquals(CLASSNAME + " must not equal null", false, testString.equals(null));
471 assertEquals(CLASSNAME + " must equal self", testString, testString);
472 assertFalse(CLASSNAME + " must not equal the empty string", testString.equals(EMPTY));
473 assertEquals(CLASSNAME + " empty strings must be equal", EMPTY, testString.substring(55, 55));
475 CLASSNAME + " must equal another string with the same value",
477 new NioByteString(backingBuffer));
479 byte[] mungedBytes = mungedBytes();
481 CLASSNAME + " must not equal every string with the same length",
482 testString.equals(new NioByteString(ByteBuffer.wrap(mungedBytes))));
485 public void testEqualsLiteralByteString() {
486 ByteString literal = ByteString.copyFrom(BYTES);
487 assertEquals(CLASSNAME + " must equal LiteralByteString with same value", literal, testString);
488 assertEquals(CLASSNAME + " must equal LiteralByteString with same value", testString, literal);
490 CLASSNAME + " must not equal the empty string", testString.equals(ByteString.EMPTY));
492 CLASSNAME + " empty strings must be equal", ByteString.EMPTY, testString.substring(55, 55));
494 literal = ByteString.copyFrom(mungedBytes());
496 CLASSNAME + " must not equal every LiteralByteString with the same length",
497 testString.equals(literal));
499 CLASSNAME + " must not equal every LiteralByteString with the same length",
500 literal.equals(testString));
503 public void testEqualsRopeByteString() {
504 ByteString p1 = ByteString.copyFrom(BYTES, 0, 5);
505 ByteString p2 = ByteString.copyFrom(BYTES, 5, BYTES.length - 5);
506 ByteString rope = p1.concat(p2);
508 assertEquals(CLASSNAME + " must equal RopeByteString with same value", rope, testString);
509 assertEquals(CLASSNAME + " must equal RopeByteString with same value", testString, rope);
511 CLASSNAME + " must not equal the empty string",
512 testString.equals(ByteString.EMPTY.concat(ByteString.EMPTY)));
514 CLASSNAME + " empty strings must be equal",
515 ByteString.EMPTY.concat(ByteString.EMPTY),
516 testString.substring(55, 55));
518 byte[] mungedBytes = mungedBytes();
519 p1 = ByteString.copyFrom(mungedBytes, 0, 5);
520 p2 = ByteString.copyFrom(mungedBytes, 5, mungedBytes.length - 5);
521 rope = p1.concat(p2);
523 CLASSNAME + " must not equal every RopeByteString with the same length",
524 testString.equals(rope));
526 CLASSNAME + " must not equal every RopeByteString with the same length",
527 rope.equals(testString));
530 private byte[] mungedBytes() {
531 byte[] mungedBytes = new byte[BYTES.length];
532 System.arraycopy(BYTES, 0, mungedBytes, 0, BYTES.length);
533 mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF);
537 public void testHashCode() {
538 int hash = testString.hashCode();
539 assertEquals(CLASSNAME + " must have expected hashCode", EXPECTED_HASH, hash);
542 public void testPeekCachedHashCode() {
543 ByteString newString = new NioByteString(backingBuffer);
545 CLASSNAME + ".peekCachedHashCode() should return zero at first",
547 newString.peekCachedHashCode());
548 newString.hashCode();
550 CLASSNAME + ".peekCachedHashCode should return zero at first",
552 newString.peekCachedHashCode());
555 public void testPartialHash() {
556 // partialHash() is more strenuously tested elsewhere by testing hashes of substrings.
557 // This test would fail if the expected hash were 1. It's not.
558 int hash = testString.partialHash(testString.size(), 0, testString.size());
559 assertEquals(CLASSNAME + ".partialHash() must yield expected hashCode", EXPECTED_HASH, hash);
562 public void testNewInput() throws IOException {
563 InputStream input = testString.newInput();
565 "InputStream.available() returns correct value", testString.size(), input.available());
566 boolean stillEqual = true;
567 for (byte referenceByte : BYTES) {
568 int expectedInt = (referenceByte & 0xFF);
569 stillEqual = (expectedInt == input.read());
571 assertEquals("InputStream.available() returns correct value", 0, input.available());
572 assertTrue(CLASSNAME + " must give the same bytes from the InputStream", stillEqual);
573 assertEquals(CLASSNAME + " InputStream must now be exhausted", -1, input.read());
576 public void testNewInput_skip() throws IOException {
577 InputStream input = testString.newInput();
578 int stringSize = testString.size();
579 int nearEndIndex = stringSize * 2 / 3;
580 long skipped1 = input.skip(nearEndIndex);
581 assertEquals("InputStream.skip()", skipped1, nearEndIndex);
582 assertEquals("InputStream.available()", stringSize - skipped1, input.available());
583 assertTrue("InputStream.mark() is available", input.markSupported());
586 "InputStream.skip(), read()", testString.byteAt(nearEndIndex) & 0xFF, input.read());
587 assertEquals("InputStream.available()", stringSize - skipped1 - 1, input.available());
588 long skipped2 = input.skip(stringSize);
589 assertEquals("InputStream.skip() incomplete", skipped2, stringSize - skipped1 - 1);
590 assertEquals("InputStream.skip(), no more input", 0, input.available());
591 assertEquals("InputStream.skip(), no more input", -1, input.read());
593 assertEquals("InputStream.reset() succeded", stringSize - skipped1, input.available());
595 "InputStream.reset(), read()", testString.byteAt(nearEndIndex) & 0xFF, input.read());
598 public void testNewCodedInput() throws IOException {
599 CodedInputStream cis = testString.newCodedInput();
600 byte[] roundTripBytes = cis.readRawBytes(BYTES.length);
602 CLASSNAME + " must give the same bytes back from the CodedInputStream",
603 Arrays.equals(BYTES, roundTripBytes));
604 assertTrue(CLASSNAME + " CodedInputStream must now be exhausted", cis.isAtEnd());
608 * Make sure we keep things simple when concatenating with empty. See also {@link
609 * ByteStringTest#testConcat_empty()}.
611 public void testConcat_empty() {
613 CLASSNAME + " concatenated with empty must give " + CLASSNAME,
614 testString.concat(EMPTY),
617 "empty concatenated with " + CLASSNAME + " must give " + CLASSNAME,
618 EMPTY.concat(testString),
622 public void testJavaSerialization() throws Exception {
623 ByteArrayOutputStream out = new ByteArrayOutputStream();
624 ObjectOutputStream oos = new ObjectOutputStream(out);
625 oos.writeObject(testString);
627 byte[] pickled = out.toByteArray();
628 InputStream in = new ByteArrayInputStream(pickled);
629 ObjectInputStream ois = new ObjectInputStream(in);
630 Object o = ois.readObject();
631 assertTrue("Didn't get a ByteString back", o instanceof ByteString);
632 assertEquals("Should get an equal ByteString back", testString, o);
635 private static ByteString forString(String str) {
636 return new NioByteString(ByteBuffer.wrap(str.getBytes(UTF_8)));