/* Copyright (c) 2011 Danish Maritime Authority. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // http://code.google.com/p/protobuf/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package net.maritimecloud.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; /** * Test {@link LiteralBinary} by setting up a reference string in {@link #setUp()}. This class is designed to be * extended for testing extensions of {@link LiteralBinary} such as {@link BoundedBinary}, see * {@link BoundedByteStringTest}. * * @author carlanton@google.com (Carl Haverl) */ public class LiteralByteStringTest extends TestCase { protected static final String UTF_8 = "UTF-8"; protected String classUnderTest; protected byte[] referenceBytes; protected Binary stringUnderTest; protected int expectedHashCode; @Override protected void setUp() throws Exception { classUnderTest = "LiteralBinary"; referenceBytes = ByteStringTest.getTestBytes(1234, 11337766L); stringUnderTest = Binary.copyFrom(referenceBytes); expectedHashCode = 331161852; } public void testExpectedType() { String actualClassName = getActualClassName(stringUnderTest); assertEquals(classUnderTest + " should match type exactly", classUnderTest, actualClassName); } protected String getActualClassName(Object object) { String actualClassName = object.getClass().getName(); actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1); return actualClassName; } public void testByteAt() { boolean stillEqual = true; for (int i = 0; stillEqual && i < referenceBytes.length; ++i) { stillEqual = referenceBytes[i] == stringUnderTest.byteAt(i); } assertTrue(classUnderTest + " must capture the right bytes", stillEqual); } public void testByteIterator() { boolean stillEqual = true; Binary.ByteIterator iter = stringUnderTest.iterator(); for (int i = 0; stillEqual && i < referenceBytes.length; ++i) { stillEqual = iter.hasNext() && referenceBytes[i] == iter.nextByte(); } assertTrue(classUnderTest + " must capture the right bytes", stillEqual); assertFalse(classUnderTest + " must have exhausted the itertor", iter.hasNext()); try { iter.nextByte(); fail("Should have thrown an exception."); } catch (NoSuchElementException e) { // This is success } } public void testByteIterable() { boolean stillEqual = true; int j = 0; for (byte quantum : stringUnderTest) { stillEqual = referenceBytes[j] == quantum; ++j; } assertTrue(classUnderTest + " must capture the right bytes as Bytes", stillEqual); assertEquals(classUnderTest + " iterable character count", referenceBytes.length, j); } public void testSize() { assertEquals(classUnderTest + " must have the expected size", referenceBytes.length, stringUnderTest.size()); } public void testGetTreeDepth() { assertEquals(classUnderTest + " must have depth 0", 0, stringUnderTest.getTreeDepth()); } public void testIsBalanced() { assertTrue(classUnderTest + " is technically balanced", stringUnderTest.isBalanced()); } public void testCopyTo_ByteArrayOffsetLength() { int destinationOffset = 50; int length = 100; byte[] destination = new byte[destinationOffset + length]; int sourceOffset = 213; stringUnderTest.copyTo(destination, sourceOffset, destinationOffset, length); boolean stillEqual = true; for (int i = 0; stillEqual && i < length; ++i) { stillEqual = referenceBytes[i + sourceOffset] == destination[i + destinationOffset]; } assertTrue(classUnderTest + ".copyTo(4 arg) must give the expected bytes", stillEqual); } public void testCopyTo_ByteArrayOffsetLengthErrors() { int destinationOffset = 50; int length = 100; byte[] destination = new byte[destinationOffset + length]; try { // Copy one too many bytes stringUnderTest.copyTo(destination, stringUnderTest.size() + 1 - length, destinationOffset, length); fail("Should have thrown an exception when copying too many bytes of a " + classUnderTest); } catch (IndexOutOfBoundsException expected) { // This is success } try { // Copy with illegal negative sourceOffset stringUnderTest.copyTo(destination, -1, destinationOffset, length); fail("Should have thrown an exception when given a negative sourceOffset in " + classUnderTest); } catch (IndexOutOfBoundsException expected) { // This is success } try { // Copy with illegal negative destinationOffset stringUnderTest.copyTo(destination, 0, -1, length); fail("Should have thrown an exception when given a negative destinationOffset in " + classUnderTest); } catch (IndexOutOfBoundsException expected) { // This is success } try { // Copy with illegal negative size stringUnderTest.copyTo(destination, 0, 0, -1); fail("Should have thrown an exception when given a negative size in " + classUnderTest); } catch (IndexOutOfBoundsException expected) { // This is success } try { // Copy with illegal too-large sourceOffset stringUnderTest.copyTo(destination, 2 * stringUnderTest.size(), 0, length); fail("Should have thrown an exception when the destinationOffset is too large in " + classUnderTest); } catch (IndexOutOfBoundsException expected) { // This is success } try { // Copy with illegal too-large destinationOffset stringUnderTest.copyTo(destination, 0, 2 * destination.length, length); fail("Should have thrown an exception when the destinationOffset is too large in " + classUnderTest); } catch (IndexOutOfBoundsException expected) { // This is success } } public void testCopyTo_ByteBuffer() { ByteBuffer myBuffer = ByteBuffer.allocate(referenceBytes.length); stringUnderTest.copyTo(myBuffer); assertTrue(classUnderTest + ".copyTo(ByteBuffer) must give back the same bytes", Arrays.equals(referenceBytes, myBuffer.array())); } public void testAsReadOnlyByteBuffer() { ByteBuffer byteBuffer = stringUnderTest.asReadOnlyByteBuffer(); byte[] roundTripBytes = new byte[referenceBytes.length]; assertTrue(byteBuffer.remaining() == referenceBytes.length); assertTrue(byteBuffer.isReadOnly()); byteBuffer.get(roundTripBytes); assertTrue(classUnderTest + ".asReadOnlyByteBuffer() must give back the same bytes", Arrays.equals(referenceBytes, roundTripBytes)); } public void testAsReadOnlyByteBufferList() { List<ByteBuffer> byteBuffers = stringUnderTest.asReadOnlyByteBufferList(); int bytesSeen = 0; byte[] roundTripBytes = new byte[referenceBytes.length]; for (ByteBuffer byteBuffer : byteBuffers) { int thisLength = byteBuffer.remaining(); assertTrue(byteBuffer.isReadOnly()); assertTrue(bytesSeen + thisLength <= referenceBytes.length); byteBuffer.get(roundTripBytes, bytesSeen, thisLength); bytesSeen += thisLength; } assertTrue(bytesSeen == referenceBytes.length); assertTrue(classUnderTest + ".asReadOnlyByteBufferTest() must give back the same bytes", Arrays.equals(referenceBytes, roundTripBytes)); } public void testToByteArray() { byte[] roundTripBytes = stringUnderTest.toByteArray(); assertTrue(classUnderTest + ".toByteArray() must give back the same bytes", Arrays.equals(referenceBytes, roundTripBytes)); } public void testWriteTo() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); stringUnderTest.writeTo(bos); byte[] roundTripBytes = bos.toByteArray(); assertTrue(classUnderTest + ".writeTo() must give back the same bytes", Arrays.equals(referenceBytes, roundTripBytes)); } public void testWriteTo_mutating() throws IOException { OutputStream os = new OutputStream() { @Override public void write(byte[] b, int off, int len) { for (int x = 0; x < len; ++x) { b[off + x] = (byte) 0; } } @Override public void write(int b) { // Purposefully left blank. } }; stringUnderTest.writeTo(os); byte[] newBytes = stringUnderTest.toByteArray(); assertTrue(classUnderTest + ".writeTo() must not grant access to underlying array", Arrays.equals(referenceBytes, newBytes)); } public void testNewOutput() throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); Binary.Output output = Binary.newOutput(); stringUnderTest.writeTo(output); assertEquals("Output Size returns correct result", output.size(), stringUnderTest.size()); output.writeTo(bos); assertTrue("Output.writeTo() must give back the same bytes", Arrays.equals(referenceBytes, bos.toByteArray())); // write the output stream to itself! This should cause it to double output.writeTo(output); assertEquals("Writing an output stream to itself is successful", stringUnderTest.concat(stringUnderTest), output.toBinary()); output.reset(); assertEquals("Output.reset() resets the output", 0, output.size()); assertEquals("Output.reset() resets the output", Binary.EMPTY, output.toBinary()); } public void testToString() throws UnsupportedEncodingException { String testString = "I love unicode \u1234\u5678 characters"; LiteralBinary unicode = new LiteralBinary(testString.getBytes(UTF_8)); String roundTripString = unicode.toString(UTF_8); assertEquals(classUnderTest + " unicode must match", testString, roundTripString); } public void testEquals() { assertEquals(classUnderTest + " must not equal null", false, stringUnderTest.equals(null)); assertEquals(classUnderTest + " must equal self", stringUnderTest, stringUnderTest); assertFalse(classUnderTest + " must not equal the empty string", stringUnderTest.equals(Binary.EMPTY)); assertEquals(classUnderTest + " empty strings must be equal", new LiteralBinary(new byte[] {}), stringUnderTest.substring(55, 55)); assertEquals(classUnderTest + " must equal another string with the same value", stringUnderTest, new LiteralBinary(referenceBytes)); byte[] mungedBytes = new byte[referenceBytes.length]; System.arraycopy(referenceBytes, 0, mungedBytes, 0, referenceBytes.length); mungedBytes[mungedBytes.length - 5] ^= 0xFF; assertFalse(classUnderTest + " must not equal every string with the same length", stringUnderTest.equals(new LiteralBinary(mungedBytes))); } public void testHashCode() { int hash = stringUnderTest.hashCode(); assertEquals(classUnderTest + " must have expected hashCode", expectedHashCode, hash); } public void testPeekCachedHashCode() { assertEquals(classUnderTest + ".peekCachedHashCode() should return zero at first", 0, stringUnderTest.peekCachedHashCode()); stringUnderTest.hashCode(); assertEquals(classUnderTest + ".peekCachedHashCode should return zero at first", expectedHashCode, stringUnderTest.peekCachedHashCode()); } public void testPartialHash() { // partialHash() is more strenuously tested elsewhere by testing hashes of substrings. // This test would fail if the expected hash were 1. It's not. int hash = stringUnderTest.partialHash(stringUnderTest.size(), 0, stringUnderTest.size()); assertEquals(classUnderTest + ".partialHash() must yield expected hashCode", expectedHashCode, hash); } public void testNewInput() throws IOException { InputStream input = stringUnderTest.newInput(); assertEquals("InputStream.available() returns correct value", stringUnderTest.size(), input.available()); boolean stillEqual = true; for (byte referenceByte : referenceBytes) { int expectedInt = referenceByte & 0xFF; stillEqual = expectedInt == input.read(); } assertEquals("InputStream.available() returns correct value", 0, input.available()); assertTrue(classUnderTest + " must give the same bytes from the InputStream", stillEqual); assertEquals(classUnderTest + " InputStream must now be exhausted", -1, input.read()); } public void testNewInput_skip() throws IOException { InputStream input = stringUnderTest.newInput(); int stringSize = stringUnderTest.size(); int nearEndIndex = stringSize * 2 / 3; long skipped1 = input.skip(nearEndIndex); assertEquals("InputStream.skip()", skipped1, nearEndIndex); assertEquals("InputStream.available()", stringSize - skipped1, input.available()); assertTrue("InputStream.mark() is available", input.markSupported()); input.mark(0); assertEquals("InputStream.skip(), read()", stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read()); assertEquals("InputStream.available()", stringSize - skipped1 - 1, input.available()); long skipped2 = input.skip(stringSize); assertEquals("InputStream.skip() incomplete", skipped2, stringSize - skipped1 - 1); assertEquals("InputStream.skip(), no more input", 0, input.available()); assertEquals("InputStream.skip(), no more input", -1, input.read()); input.reset(); assertEquals("InputStream.reset() succeded", stringSize - skipped1, input.available()); assertEquals("InputStream.reset(), read()", stringUnderTest.byteAt(nearEndIndex) & 0xFF, input.read()); } // // public void testNewCodedInput() throws IOException { // CodedInputStream cis = stringUnderTest.newCodedInput(); // byte[] roundTripBytes = cis.readRawBytes(referenceBytes.length); // assertTrue(classUnderTest + " must give the same bytes back from the CodedInputStream", // Arrays.equals(referenceBytes, roundTripBytes)); // assertTrue(classUnderTest + " CodedInputStream must now be exhausted", cis.isAtEnd()); // } /** * Make sure we keep things simple when concatenating with empty. See also {@link ByteStringTest#testConcat_empty()} * . */ public void testConcat_empty() { assertSame(classUnderTest + " concatenated with empty must give " + classUnderTest, stringUnderTest.concat(Binary.EMPTY), stringUnderTest); assertSame("empty concatenated with " + classUnderTest + " must give " + classUnderTest, Binary.EMPTY.concat(stringUnderTest), stringUnderTest); } }