• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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