Home | History | Annotate | Download | only in protobuf
      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 (at) 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
     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 
     72   public void testExpectedType() {
     73     String actualClassName = getActualClassName(stringUnderTest);
     74     assertEquals(classUnderTest + " should match type exactly", classUnderTest, actualClassName);
     75   }
     76 
     77   protected String getActualClassName(Object object) {
     78     return object.getClass().getSimpleName();
     79   }
     80 
     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 
     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 
    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 
    117   public void testSize() {
    118     assertEquals(classUnderTest + " must have the expected size", referenceBytes.length,
    119         stringUnderTest.size());
    120   }
    121 
    122   public void testGetTreeDepth() {
    123     assertEquals(classUnderTest + " must have depth 0", 0, stringUnderTest.getTreeDepth());
    124   }
    125 
    126   public void testIsBalanced() {
    127     assertTrue(classUnderTest + " is technically balanced", stringUnderTest.isBalanced());
    128   }
    129 
    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 
    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 
    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 
    211   public void testMarkSupported() {
    212     InputStream stream = stringUnderTest.newInput();
    213     assertTrue(classUnderTest + ".newInput() must support marking", stream.markSupported());
    214   }
    215 
    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    */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    452   public void testHashCode() {
    453     int hash = stringUnderTest.hashCode();
    454     assertEquals(classUnderTest + " must have expected hashCode", expectedHashCode, hash);
    455   }
    456 
    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 
    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 
    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 
    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 
    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    */
    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 
    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