Home | History | Annotate | Download | only in protobuf
      1 // Protocol Buffers - Google's data interchange format
      2 // Copyright 2008 Google Inc.  All rights reserved.
      3 // http://code.google.com/p/protobuf/
      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.ByteArrayOutputStream;
     36 import java.io.IOException;
     37 import java.io.InputStream;
     38 import java.io.OutputStream;
     39 import java.io.UnsupportedEncodingException;
     40 import java.nio.ByteBuffer;
     41 import java.util.Arrays;
     42 import java.util.List;
     43 import java.util.NoSuchElementException;
     44 
     45 /**
     46  * Test {@link LiteralByteString} by setting up a reference string in {@link #setUp()}.
     47  * This class is designed to be extended for testing extensions of {@link LiteralByteString}
     48  * such as {@link BoundedByteString}, see {@link BoundedByteStringTest}.
     49  *
     50  * @author carlanton (at) google.com (Carl Haverl)
     51  */
     52 public class LiteralByteStringTest extends TestCase {
     53   protected static final String UTF_8 = "UTF-8";
     54 
     55   protected String classUnderTest;
     56   protected byte[] referenceBytes;
     57   protected ByteString stringUnderTest;
     58   protected int expectedHashCode;
     59 
     60   @Override
     61   protected void setUp() throws Exception {
     62     classUnderTest = "LiteralByteString";
     63     referenceBytes = ByteStringTest.getTestBytes(1234, 11337766L);
     64     stringUnderTest = ByteString.copyFrom(referenceBytes);
     65     expectedHashCode = 331161852;
     66   }
     67 
     68   public void testExpectedType() {
     69     String actualClassName = getActualClassName(stringUnderTest);
     70     assertEquals(classUnderTest + " should match type exactly", classUnderTest, actualClassName);
     71   }
     72 
     73   protected String getActualClassName(Object object) {
     74     String actualClassName = object.getClass().getName();
     75     actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1);
     76     return actualClassName;
     77   }
     78 
     79   public void testByteAt() {
     80     boolean stillEqual = true;
     81     for (int i = 0; stillEqual && i < referenceBytes.length; ++i) {
     82       stillEqual = (referenceBytes[i] == stringUnderTest.byteAt(i));
     83     }
     84     assertTrue(classUnderTest + " must capture the right bytes", stillEqual);
     85   }
     86 
     87   public void testByteIterator() {
     88     boolean stillEqual = true;
     89     ByteString.ByteIterator iter = stringUnderTest.iterator();
     90     for (int i = 0; stillEqual && i < referenceBytes.length; ++i) {
     91       stillEqual = (iter.hasNext() && referenceBytes[i] == iter.nextByte());
     92     }
     93     assertTrue(classUnderTest + " must capture the right bytes", stillEqual);
     94     assertFalse(classUnderTest + " must have exhausted the itertor", iter.hasNext());
     95 
     96     try {
     97       iter.nextByte();
     98       fail("Should have thrown an exception.");
     99     } catch (NoSuchElementException e) {
    100       // This is success
    101     }
    102   }
    103 
    104   public void testByteIterable() {
    105     boolean stillEqual = true;
    106     int j = 0;
    107     for (byte quantum : stringUnderTest) {
    108       stillEqual = (referenceBytes[j] == quantum);
    109       ++j;
    110     }
    111     assertTrue(classUnderTest + " must capture the right bytes as Bytes", stillEqual);
    112     assertEquals(classUnderTest + " iterable character count", referenceBytes.length, j);
    113   }
    114 
    115   public void testSize() {
    116     assertEquals(classUnderTest + " must have the expected size", referenceBytes.length,
    117         stringUnderTest.size());
    118   }
    119 
    120   public void testGetTreeDepth() {
    121     assertEquals(classUnderTest + " must have depth 0", 0, stringUnderTest.getTreeDepth());
    122   }
    123 
    124   public void testIsBalanced() {
    125     assertTrue(classUnderTest + " is technically balanced", stringUnderTest.isBalanced());
    126   }
    127 
    128   public void testCopyTo_ByteArrayOffsetLength() {
    129     int destinationOffset = 50;
    130     int length = 100;
    131     byte[] destination = new byte[destinationOffset + length];
    132     int sourceOffset = 213;
    133     stringUnderTest.copyTo(destination, sourceOffset, destinationOffset, length);
    134     boolean stillEqual = true;
    135     for (int i = 0; stillEqual && i < length; ++i) {
    136       stillEqual = referenceBytes[i + sourceOffset] == destination[i + destinationOffset];
    137     }
    138     assertTrue(classUnderTest + ".copyTo(4 arg) must give the expected bytes", stillEqual);
    139   }
    140 
    141   public void testCopyTo_ByteArrayOffsetLengthErrors() {
    142     int destinationOffset = 50;
    143     int length = 100;
    144     byte[] destination = new byte[destinationOffset + length];
    145 
    146     try {
    147       // Copy one too many bytes
    148       stringUnderTest.copyTo(destination, stringUnderTest.size() + 1 - length,
    149           destinationOffset, length);
    150       fail("Should have thrown an exception when copying too many bytes of a "
    151           + classUnderTest);
    152     } catch (IndexOutOfBoundsException expected) {
    153       // This is success
    154     }
    155 
    156     try {
    157       // Copy with illegal negative sourceOffset
    158       stringUnderTest.copyTo(destination, -1, destinationOffset, length);
    159       fail("Should have thrown an exception when given a negative sourceOffset in "
    160           + classUnderTest);
    161     } catch (IndexOutOfBoundsException expected) {
    162       // This is success
    163     }
    164 
    165     try {
    166       // Copy with illegal negative destinationOffset
    167       stringUnderTest.copyTo(destination, 0, -1, length);
    168       fail("Should have thrown an exception when given a negative destinationOffset in "
    169           + classUnderTest);
    170     } catch (IndexOutOfBoundsException expected) {
    171       // This is success
    172     }
    173 
    174     try {
    175       // Copy with illegal negative size
    176       stringUnderTest.copyTo(destination, 0, 0, -1);
    177       fail("Should have thrown an exception when given a negative size in "
    178           + classUnderTest);
    179     } catch (IndexOutOfBoundsException expected) {
    180       // This is success
    181     }
    182 
    183     try {
    184       // Copy with illegal too-large sourceOffset
    185       stringUnderTest.copyTo(destination, 2 * stringUnderTest.size(), 0, length);
    186       fail("Should have thrown an exception when the destinationOffset is too large in "
    187           + classUnderTest);
    188     } catch (IndexOutOfBoundsException expected) {
    189       // This is success
    190     }
    191 
    192     try {
    193       // Copy with illegal too-large destinationOffset
    194       stringUnderTest.copyTo(destination, 0, 2 * destination.length, length);
    195       fail("Should have thrown an exception when the destinationOffset is too large in "
    196           + classUnderTest);
    197     } catch (IndexOutOfBoundsException expected) {
    198       // This is success
    199     }
    200   }
    201 
    202   public void testCopyTo_ByteBuffer() {
    203     ByteBuffer myBuffer = ByteBuffer.allocate(referenceBytes.length);
    204     stringUnderTest.copyTo(myBuffer);
    205     assertTrue(classUnderTest + ".copyTo(ByteBuffer) must give back the same bytes",
    206         Arrays.equals(referenceBytes, myBuffer.array()));
    207   }
    208 
    209   public void testAsReadOnlyByteBuffer() {
    210     ByteBuffer byteBuffer = stringUnderTest.asReadOnlyByteBuffer();
    211     byte[] roundTripBytes = new byte[referenceBytes.length];
    212     assertTrue(byteBuffer.remaining() == referenceBytes.length);
    213     assertTrue(byteBuffer.isReadOnly());
    214     byteBuffer.get(roundTripBytes);
    215     assertTrue(classUnderTest + ".asReadOnlyByteBuffer() must give back the same bytes",
    216         Arrays.equals(referenceBytes, roundTripBytes));
    217   }
    218 
    219   public void testAsReadOnlyByteBufferList() {
    220     List<ByteBuffer> byteBuffers = stringUnderTest.asReadOnlyByteBufferList();
    221     int bytesSeen = 0;
    222     byte[] roundTripBytes = new byte[referenceBytes.length];
    223     for (ByteBuffer byteBuffer : byteBuffers) {
    224       int thisLength = byteBuffer.remaining();
    225       assertTrue(byteBuffer.isReadOnly());
    226       assertTrue(bytesSeen + thisLength <= referenceBytes.length);
    227       byteBuffer.get(roundTripBytes, bytesSeen, thisLength);
    228       bytesSeen += thisLength;
    229     }
    230     assertTrue(bytesSeen == referenceBytes.length);
    231     assertTrue(classUnderTest + ".asReadOnlyByteBufferTest() must give back the same bytes",
    232         Arrays.equals(referenceBytes, roundTripBytes));
    233   }
    234 
    235   public void testToByteArray() {
    236     byte[] roundTripBytes = stringUnderTest.toByteArray();
    237     assertTrue(classUnderTest + ".toByteArray() must give back the same bytes",
    238         Arrays.equals(referenceBytes, roundTripBytes));
    239   }
    240 
    241   public void testWriteTo() throws IOException {
    242     ByteArrayOutputStream bos = new ByteArrayOutputStream();
    243     stringUnderTest.writeTo(bos);
    244     byte[] roundTripBytes = bos.toByteArray();
    245     assertTrue(classUnderTest + ".writeTo() must give back the same bytes",
    246         Arrays.equals(referenceBytes, roundTripBytes));
    247   }
    248 
    249   public void testWriteTo_mutating() throws IOException {
    250     OutputStream os = new OutputStream() {
    251       @Override
    252       public void write(byte[] b, int off, int len) {
    253         for (int x = 0; x < len; ++x) {
    254           b[off + x] = (byte) 0;
    255         }
    256       }
    257 
    258       @Override
    259       public void write(int b) {
    260         // Purposefully left blank.
    261       }
    262     };
    263 
    264     stringUnderTest.writeTo(os);
    265     byte[] newBytes = stringUnderTest.toByteArray();
    266     assertTrue(classUnderTest + ".writeTo() must not grant access to underlying array",
    267         Arrays.equals(referenceBytes, newBytes));
    268   }
    269 
    270   public void testNewOutput() throws IOException {
    271     ByteArrayOutputStream bos = new ByteArrayOutputStream();
    272     ByteString.Output output = ByteString.newOutput();
    273     stringUnderTest.writeTo(output);
    274     assertEquals("Output Size returns correct result",
    275         output.size(), stringUnderTest.size());
    276     output.writeTo(bos);
    277     assertTrue("Output.writeTo() must give back the same bytes",
    278         Arrays.equals(referenceBytes, bos.toByteArray()));
    279 
    280     // write the output stream to itself! This should cause it to double
    281     output.writeTo(output);
    282     assertEquals("Writing an output stream to itself is successful",
    283         stringUnderTest.concat(stringUnderTest), output.toByteString());
    284 
    285     output.reset();
    286     assertEquals("Output.reset() resets the output", 0, output.size());
    287     assertEquals("Output.reset() resets the output",
    288         ByteString.EMPTY, output.toByteString());
    289 
    290   }
    291 
    292   public void testToString() throws UnsupportedEncodingException {
    293     String testString = "I love unicode \u1234\u5678 characters";
    294     LiteralByteString unicode = new LiteralByteString(testString.getBytes(UTF_8));
    295     String roundTripString = unicode.toString(UTF_8);
    296     assertEquals(classUnderTest + " unicode must match", testString, roundTripString);
    297   }
    298 
    299   public void testEquals() {
    300     assertEquals(classUnderTest + " must not equal null", false, stringUnderTest.equals(null));
    301     assertEquals(classUnderTest + " must equal self", stringUnderTest, stringUnderTest);
    302     assertFalse(classUnderTest + " must not equal the empty string",
    303         stringUnderTest.equals(ByteString.EMPTY));
    304     assertEquals(classUnderTest + " empty strings must be equal",
    305         new LiteralByteString(new byte[]{}), stringUnderTest.substring(55, 55));
    306     assertEquals(classUnderTest + " must equal another string with the same value",
    307         stringUnderTest, new LiteralByteString(referenceBytes));
    308 
    309     byte[] mungedBytes = new byte[referenceBytes.length];
    310     System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length);
    311     mungedBytes[mungedBytes.length - 5] ^= 0xFF;
    312     assertFalse(classUnderTest + " must not equal every string with the same length",
    313         stringUnderTest.equals(new LiteralByteString(mungedBytes)));
    314   }
    315 
    316   public void testHashCode() {
    317     int hash = stringUnderTest.hashCode();
    318     assertEquals(classUnderTest + " must have expected hashCode", expectedHashCode, hash);
    319   }
    320 
    321   public void testPeekCachedHashCode() {
    322     assertEquals(classUnderTest + ".peekCachedHashCode() should return zero at first", 0,
    323         stringUnderTest.peekCachedHashCode());
    324     stringUnderTest.hashCode();
    325     assertEquals(classUnderTest + ".peekCachedHashCode should return zero at first",
    326         expectedHashCode, stringUnderTest.peekCachedHashCode());
    327   }
    328 
    329   public void testPartialHash() {
    330     // partialHash() is more strenuously tested elsewhere by testing hashes of substrings.
    331     // This test would fail if the expected hash were 1.  It's not.
    332     int hash = stringUnderTest.partialHash(stringUnderTest.size(), 0, stringUnderTest.size());
    333     assertEquals(classUnderTest + ".partialHash() must yield expected hashCode",
    334         expectedHashCode, hash);
    335   }
    336 
    337   public void testNewInput() throws IOException {
    338     InputStream input = stringUnderTest.newInput();
    339     assertEquals("InputStream.available() returns correct value",
    340         stringUnderTest.size(), input.available());
    341     boolean stillEqual = true;
    342     for (byte referenceByte : referenceBytes) {
    343       int expectedInt = (referenceByte & 0xFF);
    344       stillEqual = (expectedInt == input.read());
    345     }
    346     assertEquals("InputStream.available() returns correct value",
    347         0, input.available());
    348     assertTrue(classUnderTest + " must give the same bytes from the InputStream", stillEqual);
    349     assertEquals(classUnderTest + " InputStream must now be exhausted", -1, input.read());
    350   }
    351 
    352   public void testNewInput_skip() throws IOException {
    353     InputStream input = stringUnderTest.newInput();
    354     int stringSize = stringUnderTest.size();
    355     int nearEndIndex = stringSize * 2 / 3;
    356     long skipped1 = input.skip(nearEndIndex);
    357     assertEquals("InputStream.skip()", skipped1, nearEndIndex);
    358     assertEquals("InputStream.available()",
    359         stringSize - skipped1, input.available());
    360     assertTrue("InputStream.mark() is available", input.markSupported());
    361     input.mark(0);
    362     assertEquals("InputStream.skip(), read()",
    363         stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read());
    364     assertEquals("InputStream.available()",
    365                  stringSize - skipped1 - 1, input.available());
    366     long skipped2 = input.skip(stringSize);
    367     assertEquals("InputStream.skip() incomplete",
    368         skipped2, stringSize - skipped1 - 1);
    369     assertEquals("InputStream.skip(), no more input", 0, input.available());
    370     assertEquals("InputStream.skip(), no more input", -1, input.read());
    371     input.reset();
    372     assertEquals("InputStream.reset() succeded",
    373                  stringSize - skipped1, input.available());
    374     assertEquals("InputStream.reset(), read()",
    375         stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read());
    376   }
    377 
    378   public void testNewCodedInput() throws IOException {
    379     CodedInputStream cis = stringUnderTest.newCodedInput();
    380     byte[] roundTripBytes = cis.readRawBytes(referenceBytes.length);
    381     assertTrue(classUnderTest + " must give the same bytes back from the CodedInputStream",
    382         Arrays.equals(referenceBytes, roundTripBytes));
    383     assertTrue(classUnderTest + " CodedInputStream must now be exhausted", cis.isAtEnd());
    384   }
    385 
    386   /**
    387    * Make sure we keep things simple when concatenating with empty. See also
    388    * {@link ByteStringTest#testConcat_empty()}.
    389    */
    390   public void testConcat_empty() {
    391     assertSame(classUnderTest + " concatenated with empty must give " + classUnderTest,
    392         stringUnderTest.concat(ByteString.EMPTY), stringUnderTest);
    393     assertSame("empty concatenated with " + classUnderTest + " must give " + classUnderTest,
    394         ByteString.EMPTY.concat(stringUnderTest), stringUnderTest);
    395   }
    396 }
    397