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