// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // 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 com.google.protobuf; import com.google.protobuf.ByteString.Output; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Random; import junit.framework.TestCase; /** * Test methods with implementations in {@link ByteString}, plus do some top-level "integration" * tests. * * @author carlanton@google.com (Carl Haverl) */ public class ByteStringTest extends TestCase { private static final Charset UTF_16 = Charset.forName("UTF-16"); static byte[] getTestBytes(int size, long seed) { Random random = new Random(seed); byte[] result = new byte[size]; random.nextBytes(result); return result; } private byte[] getTestBytes(int size) { return getTestBytes(size, 445566L); } private byte[] getTestBytes() { return getTestBytes(1000); } // Compare the entire left array with a subset of the right array. private boolean isArrayRange(byte[] left, byte[] right, int rightOffset, int length) { boolean stillEqual = (left.length == length); for (int i = 0; (stillEqual && i < length); ++i) { stillEqual = (left[i] == right[rightOffset + i]); } return stillEqual; } // Returns true only if the given two arrays have identical contents. private boolean isArray(byte[] left, byte[] right) { return left.length == right.length && isArrayRange(left, right, 0, left.length); } public void testSubstring_BeginIndex() { byte[] bytes = getTestBytes(); ByteString substring = ByteString.copyFrom(bytes).substring(500); assertTrue("substring must contain the tail of the string", isArrayRange(substring.toByteArray(), bytes, 500, bytes.length - 500)); } public void testCopyFrom_BytesOffsetSize() { byte[] bytes = getTestBytes(); ByteString byteString = ByteString.copyFrom(bytes, 500, 200); assertTrue("copyFrom sub-range must contain the expected bytes", isArrayRange(byteString.toByteArray(), bytes, 500, 200)); } public void testCopyFrom_Bytes() { byte[] bytes = getTestBytes(); ByteString byteString = ByteString.copyFrom(bytes); assertTrue("copyFrom must contain the expected bytes", isArray(byteString.toByteArray(), bytes)); } public void testCopyFrom_ByteBufferSize() { byte[] bytes = getTestBytes(); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes); byteBuffer.position(500); ByteString byteString = ByteString.copyFrom(byteBuffer, 200); assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes", isArrayRange(byteString.toByteArray(), bytes, 500, 200)); } public void testCopyFrom_ByteBuffer() { byte[] bytes = getTestBytes(); ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length); byteBuffer.put(bytes); byteBuffer.position(500); ByteString byteString = ByteString.copyFrom(byteBuffer); assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes", isArrayRange(byteString.toByteArray(), bytes, 500, bytes.length - 500)); } public void testCopyFrom_StringEncoding() { String testString = "I love unicode \u1234\u5678 characters"; ByteString byteString = ByteString.copyFrom(testString, UTF_16); byte[] testBytes = testString.getBytes(UTF_16); assertTrue("copyFrom string must respect the charset", isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); } public void testCopyFrom_Utf8() { String testString = "I love unicode \u1234\u5678 characters"; ByteString byteString = ByteString.copyFromUtf8(testString); byte[] testBytes = testString.getBytes(Internal.UTF_8); assertTrue("copyFromUtf8 string must respect the charset", isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); } public void testCopyFrom_Iterable() { byte[] testBytes = getTestBytes(77777, 113344L); final List<ByteString> pieces = makeConcretePieces(testBytes); // Call copyFrom() on a Collection ByteString byteString = ByteString.copyFrom(pieces); assertTrue("copyFrom a List must contain the expected bytes", isArrayRange(byteString.toByteArray(), testBytes, 0, testBytes.length)); // Call copyFrom on an iteration that's not a collection ByteString byteStringAlt = ByteString.copyFrom(new Iterable<ByteString>() { @Override public Iterator<ByteString> iterator() { return pieces.iterator(); } }); assertEquals("copyFrom from an Iteration must contain the expected bytes", byteString, byteStringAlt); } public void testCopyTo_TargetOffset() { byte[] bytes = getTestBytes(); ByteString byteString = ByteString.copyFrom(bytes); byte[] target = new byte[bytes.length + 1000]; byteString.copyTo(target, 400); assertTrue("copyFrom byteBuffer sub-range must contain the expected bytes", isArrayRange(bytes, target, 400, bytes.length)); } public void testReadFrom_emptyStream() throws IOException { ByteString byteString = ByteString.readFrom(new ByteArrayInputStream(new byte[0])); assertSame("reading an empty stream must result in the EMPTY constant " + "byte string", ByteString.EMPTY, byteString); } public void testReadFrom_smallStream() throws IOException { assertReadFrom(getTestBytes(10)); } public void testReadFrom_mutating() throws IOException { byte[] capturedArray = null; EvilInputStream eis = new EvilInputStream(); ByteString byteString = ByteString.readFrom(eis); capturedArray = eis.capturedArray; byte[] originalValue = byteString.toByteArray(); for (int x = 0; x < capturedArray.length; ++x) { capturedArray[x] = (byte) 0; } byte[] newValue = byteString.toByteArray(); assertTrue("copyFrom byteBuffer must not grant access to underlying array", Arrays.equals(originalValue, newValue)); } // Tests sizes that are near the rope copy-out threshold. public void testReadFrom_mediumStream() throws IOException { assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE - 1)); assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE)); assertReadFrom(getTestBytes(ByteString.CONCATENATE_BY_COPY_SIZE + 1)); assertReadFrom(getTestBytes(200)); } // Tests sizes that are over multi-segment rope threshold. public void testReadFrom_largeStream() throws IOException { assertReadFrom(getTestBytes(0x100)); assertReadFrom(getTestBytes(0x101)); assertReadFrom(getTestBytes(0x110)); assertReadFrom(getTestBytes(0x1000)); assertReadFrom(getTestBytes(0x1001)); assertReadFrom(getTestBytes(0x1010)); assertReadFrom(getTestBytes(0x10000)); assertReadFrom(getTestBytes(0x10001)); assertReadFrom(getTestBytes(0x10010)); } // Tests sizes that are near the read buffer size. public void testReadFrom_byteBoundaries() throws IOException { final int min = ByteString.MIN_READ_FROM_CHUNK_SIZE; final int max = ByteString.MAX_READ_FROM_CHUNK_SIZE; assertReadFrom(getTestBytes(min - 1)); assertReadFrom(getTestBytes(min)); assertReadFrom(getTestBytes(min + 1)); assertReadFrom(getTestBytes(min * 2 - 1)); assertReadFrom(getTestBytes(min * 2)); assertReadFrom(getTestBytes(min * 2 + 1)); assertReadFrom(getTestBytes(min * 4 - 1)); assertReadFrom(getTestBytes(min * 4)); assertReadFrom(getTestBytes(min * 4 + 1)); assertReadFrom(getTestBytes(min * 8 - 1)); assertReadFrom(getTestBytes(min * 8)); assertReadFrom(getTestBytes(min * 8 + 1)); assertReadFrom(getTestBytes(max - 1)); assertReadFrom(getTestBytes(max)); assertReadFrom(getTestBytes(max + 1)); assertReadFrom(getTestBytes(max * 2 - 1)); assertReadFrom(getTestBytes(max * 2)); assertReadFrom(getTestBytes(max * 2 + 1)); } // Tests that IOExceptions propagate through ByteString.readFrom(). public void testReadFrom_IOExceptions() { try { ByteString.readFrom(new FailStream()); fail("readFrom must throw the underlying IOException"); } catch (IOException e) { assertEquals("readFrom must throw the expected exception", "synthetic failure", e.getMessage()); } } // Tests that ByteString.readFrom works with streams that don't // always fill their buffers. public void testReadFrom_reluctantStream() throws IOException { final byte[] data = getTestBytes(0x1000); ByteString byteString = ByteString.readFrom(new ReluctantStream(data)); assertTrue("readFrom byte stream must contain the expected bytes", isArray(byteString.toByteArray(), data)); // Same test as above, but with some specific chunk sizes. assertReadFromReluctantStream(data, 100); assertReadFromReluctantStream(data, 248); assertReadFromReluctantStream(data, 249); assertReadFromReluctantStream(data, 250); assertReadFromReluctantStream(data, 251); assertReadFromReluctantStream(data, 0x1000); assertReadFromReluctantStream(data, 0x1001); } // Fails unless ByteString.readFrom reads the bytes correctly from a // reluctant stream with the given chunkSize parameter. private void assertReadFromReluctantStream(byte[] bytes, int chunkSize) throws IOException { ByteString b = ByteString.readFrom(new ReluctantStream(bytes), chunkSize); assertTrue("readFrom byte stream must contain the expected bytes", isArray(b.toByteArray(), bytes)); } // Tests that ByteString.readFrom works with streams that implement // available(). public void testReadFrom_available() throws IOException { final byte[] data = getTestBytes(0x1001); ByteString byteString = ByteString.readFrom(new AvailableStream(data)); assertTrue("readFrom byte stream must contain the expected bytes", isArray(byteString.toByteArray(), data)); } // Fails unless ByteString.readFrom reads the bytes correctly. private void assertReadFrom(byte[] bytes) throws IOException { ByteString byteString = ByteString.readFrom(new ByteArrayInputStream(bytes)); assertTrue("readFrom byte stream must contain the expected bytes", isArray(byteString.toByteArray(), bytes)); } // A stream that fails when read. private static final class FailStream extends InputStream { @Override public int read() throws IOException { throw new IOException("synthetic failure"); } } // A stream that simulates blocking by only producing 250 characters // per call to read(byte[]). private static class ReluctantStream extends InputStream { protected final byte[] data; protected int pos = 0; public ReluctantStream(byte[] data) { this.data = data; } @Override public int read() { if (pos == data.length) { return -1; } else { return data[pos++]; } } @Override public int read(byte[] buf) { return read(buf, 0, buf.length); } @Override public int read(byte[] buf, int offset, int size) { if (pos == data.length) { return -1; } int count = Math.min(Math.min(size, data.length - pos), 250); System.arraycopy(data, pos, buf, offset, count); pos += count; return count; } } // Same as above, but also implements available(). private static final class AvailableStream extends ReluctantStream { public AvailableStream(byte[] data) { super(data); } @Override public int available() { return Math.min(250, data.length - pos); } } // A stream which exposes the byte array passed into read(byte[], int, int). private static class EvilInputStream extends InputStream { public byte[] capturedArray = null; @Override public int read(byte[] buf, int off, int len) { if (capturedArray != null) { return -1; } else { capturedArray = buf; for (int x = 0; x < len; ++x) { buf[x] = (byte) x; } return len; } } @Override public int read() { // Purposefully do nothing. return -1; } } // A stream which exposes the byte array passed into write(byte[], int, int). private static class EvilOutputStream extends OutputStream { public byte[] capturedArray = null; @Override public void write(byte[] buf, int off, int len) { if (capturedArray == null) { capturedArray = buf; } } @Override public void write(int ignored) { // Purposefully do nothing. } } public void testToStringUtf8() { String testString = "I love unicode \u1234\u5678 characters"; byte[] testBytes = testString.getBytes(Internal.UTF_8); ByteString byteString = ByteString.copyFrom(testBytes); assertEquals("copyToStringUtf8 must respect the charset", testString, byteString.toStringUtf8()); } public void testNewOutput_InitialCapacity() throws IOException { byte[] bytes = getTestBytes(); ByteString.Output output = ByteString.newOutput(bytes.length + 100); output.write(bytes); ByteString byteString = output.toByteString(); assertTrue( "String built from newOutput(int) must contain the expected bytes", isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); } // Test newOutput() using a variety of buffer sizes and a variety of (fixed) // write sizes public void testNewOutput_ArrayWrite() { byte[] bytes = getTestBytes(); int length = bytes.length; int[] bufferSizes = {128, 256, length / 2, length - 1, length, length + 1, 2 * length, 3 * length}; int[] writeSizes = {1, 4, 5, 7, 23, bytes.length}; for (int bufferSize : bufferSizes) { for (int writeSize : writeSizes) { // Test writing the entire output writeSize bytes at a time. ByteString.Output output = ByteString.newOutput(bufferSize); for (int i = 0; i < length; i += writeSize) { output.write(bytes, i, Math.min(writeSize, length - i)); } ByteString byteString = output.toByteString(); assertTrue("String built from newOutput() must contain the expected bytes", isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); } } } // Test newOutput() using a variety of buffer sizes, but writing all the // characters using write(byte); public void testNewOutput_WriteChar() { byte[] bytes = getTestBytes(); int length = bytes.length; int[] bufferSizes = {0, 1, 128, 256, length / 2, length - 1, length, length + 1, 2 * length, 3 * length}; for (int bufferSize : bufferSizes) { ByteString.Output output = ByteString.newOutput(bufferSize); for (byte byteValue : bytes) { output.write(byteValue); } ByteString byteString = output.toByteString(); assertTrue("String built from newOutput() must contain the expected bytes", isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); } } // Test newOutput() in which we write the bytes using a variety of methods // and sizes, and in which we repeatedly call toByteString() in the middle. public void testNewOutput_Mixed() { Random rng = new Random(1); byte[] bytes = getTestBytes(); int length = bytes.length; int[] bufferSizes = {0, 1, 128, 256, length / 2, length - 1, length, length + 1, 2 * length, 3 * length}; for (int bufferSize : bufferSizes) { // Test writing the entire output using a mixture of write sizes and // methods; ByteString.Output output = ByteString.newOutput(bufferSize); int position = 0; while (position < bytes.length) { if (rng.nextBoolean()) { int count = 1 + rng.nextInt(bytes.length - position); output.write(bytes, position, count); position += count; } else { output.write(bytes[position]); position++; } assertEquals("size() returns the right value", position, output.size()); assertTrue("newOutput() substring must have correct bytes", isArrayRange(output.toByteString().toByteArray(), bytes, 0, position)); } ByteString byteString = output.toByteString(); assertTrue("String built from newOutput() must contain the expected bytes", isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); } } public void testNewOutputEmpty() { // Make sure newOutput() correctly builds empty byte strings ByteString byteString = ByteString.newOutput().toByteString(); assertEquals(ByteString.EMPTY, byteString); } public void testNewOutput_Mutating() throws IOException { Output os = ByteString.newOutput(5); os.write(new byte[] {1, 2, 3, 4, 5}); EvilOutputStream eos = new EvilOutputStream(); os.writeTo(eos); byte[] capturedArray = eos.capturedArray; ByteString byteString = os.toByteString(); byte[] oldValue = byteString.toByteArray(); Arrays.fill(capturedArray, (byte) 0); byte[] newValue = byteString.toByteArray(); assertTrue("Output must not provide access to the underlying byte array", Arrays.equals(oldValue, newValue)); } public void testNewCodedBuilder() throws IOException { byte[] bytes = getTestBytes(); ByteString.CodedBuilder builder = ByteString.newCodedBuilder(bytes.length); builder.getCodedOutput().writeRawBytes(bytes); ByteString byteString = builder.build(); assertTrue("String built from newCodedBuilder() must contain the expected bytes", isArrayRange(bytes, byteString.toByteArray(), 0, bytes.length)); } public void testSubstringParity() { byte[] bigBytes = getTestBytes(2048 * 1024, 113344L); int start = 512 * 1024 - 3333; int end = 512 * 1024 + 7777; ByteString concreteSubstring = ByteString.copyFrom(bigBytes).substring(start, end); boolean ok = true; for (int i = start; ok && i < end; ++i) { ok = (bigBytes[i] == concreteSubstring.byteAt(i - start)); } assertTrue("Concrete substring didn't capture the right bytes", ok); ByteString literalString = ByteString.copyFrom(bigBytes, start, end - start); assertTrue("Substring must be equal to literal string", concreteSubstring.equals(literalString)); assertEquals("Substring must have same hashcode as literal string", literalString.hashCode(), concreteSubstring.hashCode()); } public void testCompositeSubstring() { byte[] referenceBytes = getTestBytes(77748, 113344L); List<ByteString> pieces = makeConcretePieces(referenceBytes); ByteString listString = ByteString.copyFrom(pieces); int from = 1000; int to = 40000; ByteString compositeSubstring = listString.substring(from, to); byte[] substringBytes = compositeSubstring.toByteArray(); boolean stillEqual = true; for (int i = 0; stillEqual && i < to - from; ++i) { stillEqual = referenceBytes[from + i] == substringBytes[i]; } assertTrue("Substring must return correct bytes", stillEqual); stillEqual = true; for (int i = 0; stillEqual && i < to - from; ++i) { stillEqual = referenceBytes[from + i] == compositeSubstring.byteAt(i); } assertTrue("Substring must support byteAt() correctly", stillEqual); ByteString literalSubstring = ByteString.copyFrom(referenceBytes, from, to - from); assertTrue("Composite substring must equal a literal substring over the same bytes", compositeSubstring.equals(literalSubstring)); assertTrue("Literal substring must equal a composite substring over the same bytes", literalSubstring.equals(compositeSubstring)); assertEquals("We must get the same hashcodes for composite and literal substrings", literalSubstring.hashCode(), compositeSubstring.hashCode()); assertFalse("We can't be equal to a proper substring", compositeSubstring.equals(literalSubstring.substring(0, literalSubstring.size() - 1))); } public void testCopyFromList() { byte[] referenceBytes = getTestBytes(77748, 113344L); ByteString literalString = ByteString.copyFrom(referenceBytes); List<ByteString> pieces = makeConcretePieces(referenceBytes); ByteString listString = ByteString.copyFrom(pieces); assertTrue("Composite string must be equal to literal string", listString.equals(literalString)); assertEquals("Composite string must have same hashcode as literal string", literalString.hashCode(), listString.hashCode()); } public void testConcat() { byte[] referenceBytes = getTestBytes(77748, 113344L); ByteString literalString = ByteString.copyFrom(referenceBytes); List<ByteString> pieces = makeConcretePieces(referenceBytes); Iterator<ByteString> iter = pieces.iterator(); ByteString concatenatedString = iter.next(); while (iter.hasNext()) { concatenatedString = concatenatedString.concat(iter.next()); } assertTrue("Concatenated string must be equal to literal string", concatenatedString.equals(literalString)); assertEquals("Concatenated string must have same hashcode as literal string", literalString.hashCode(), concatenatedString.hashCode()); } /** * Test the Rope implementation can deal with Empty nodes, even though we * guard against them. See also {@link LiteralByteStringTest#testConcat_empty()}. */ public void testConcat_empty() { byte[] referenceBytes = getTestBytes(7748, 113344L); ByteString literalString = ByteString.copyFrom(referenceBytes); ByteString duo = RopeByteString.newInstanceForTest(literalString, literalString); ByteString temp = RopeByteString.newInstanceForTest( RopeByteString.newInstanceForTest(literalString, ByteString.EMPTY), RopeByteString.newInstanceForTest(ByteString.EMPTY, literalString)); ByteString quintet = RopeByteString.newInstanceForTest(temp, ByteString.EMPTY); assertTrue("String with concatenated nulls must equal simple concatenate", duo.equals(quintet)); assertEquals("String with concatenated nulls have same hashcode as simple concatenate", duo.hashCode(), quintet.hashCode()); ByteString.ByteIterator duoIter = duo.iterator(); ByteString.ByteIterator quintetIter = quintet.iterator(); boolean stillEqual = true; while (stillEqual && quintetIter.hasNext()) { stillEqual = (duoIter.nextByte() == quintetIter.nextByte()); } assertTrue("We must get the same characters by iterating", stillEqual); assertFalse("Iterator must be exhausted", duoIter.hasNext()); try { duoIter.nextByte(); fail("Should have thrown an exception."); } catch (NoSuchElementException e) { // This is success } try { quintetIter.nextByte(); fail("Should have thrown an exception."); } catch (NoSuchElementException e) { // This is success } // Test that even if we force empty strings in as rope leaves in this // configuration, we always get a (possibly Bounded) LiteralByteString // for a length 1 substring. // // It is possible, using the testing factory method to create deeply nested // trees of empty leaves, to make a string that will fail this test. for (int i = 1; i < duo.size(); ++i) { assertTrue("Substrings of size() < 2 must not be RopeByteStrings", duo.substring(i - 1, i) instanceof ByteString.LeafByteString); } for (int i = 1; i < quintet.size(); ++i) { assertTrue("Substrings of size() < 2 must not be RopeByteStrings", quintet.substring(i - 1, i) instanceof ByteString.LeafByteString); } } public void testStartsWith() { byte[] bytes = getTestBytes(1000, 1234L); ByteString string = ByteString.copyFrom(bytes); ByteString prefix = ByteString.copyFrom(bytes, 0, 500); ByteString suffix = ByteString.copyFrom(bytes, 400, 600); assertTrue(string.startsWith(ByteString.EMPTY)); assertTrue(string.startsWith(string)); assertTrue(string.startsWith(prefix)); assertFalse(string.startsWith(suffix)); assertFalse(prefix.startsWith(suffix)); assertFalse(suffix.startsWith(prefix)); assertFalse(ByteString.EMPTY.startsWith(prefix)); assertTrue(ByteString.EMPTY.startsWith(ByteString.EMPTY)); } public void testEndsWith() { byte[] bytes = getTestBytes(1000, 1234L); ByteString string = ByteString.copyFrom(bytes); ByteString prefix = ByteString.copyFrom(bytes, 0, 500); ByteString suffix = ByteString.copyFrom(bytes, 400, 600); assertTrue(string.endsWith(ByteString.EMPTY)); assertTrue(string.endsWith(string)); assertTrue(string.endsWith(suffix)); assertFalse(string.endsWith(prefix)); assertFalse(suffix.endsWith(prefix)); assertFalse(prefix.endsWith(suffix)); assertFalse(ByteString.EMPTY.endsWith(suffix)); assertTrue(ByteString.EMPTY.endsWith(ByteString.EMPTY)); } static List<ByteString> makeConcretePieces(byte[] referenceBytes) { List<ByteString> pieces = new ArrayList<ByteString>(); // Starting length should be small enough that we'll do some concatenating by // copying if we just concatenate all these pieces together. for (int start = 0, length = 16; start < referenceBytes.length; start += length) { length = (length << 1) - 1; if (start + length > referenceBytes.length) { length = referenceBytes.length - start; } pieces.add(ByteString.copyFrom(referenceBytes, start, length)); } return pieces; } private byte[] substringUsingWriteTo( ByteString data, int offset, int length) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); data.writeTo(output, offset, length); return output.toByteArray(); } public void testWriteToOutputStream() throws Exception { // Choose a size large enough so when two ByteStrings are concatenated they // won't be merged into one byte array due to some optimizations. final int dataSize = ByteString.CONCATENATE_BY_COPY_SIZE + 1; byte[] data1 = new byte[dataSize]; for (int i = 0; i < data1.length; i++) { data1[i] = (byte) 1; } data1[1] = (byte) 11; // Test LiteralByteString.writeTo(OutputStream,int,int) ByteString left = ByteString.wrap(data1); byte[] result = substringUsingWriteTo(left, 1, 1); assertEquals(1, result.length); assertEquals((byte) 11, result[0]); byte[] data2 = new byte[dataSize]; for (int i = 0; i < data1.length; i++) { data2[i] = (byte) 2; } ByteString right = ByteString.wrap(data2); // Concatenate two ByteStrings to create a RopeByteString. ByteString root = left.concat(right); // Make sure we are actually testing a RopeByteString with a simple tree // structure. assertEquals(1, root.getTreeDepth()); // Write parts of the left node. result = substringUsingWriteTo(root, 0, dataSize); assertEquals(dataSize, result.length); assertEquals((byte) 1, result[0]); assertEquals((byte) 1, result[dataSize - 1]); // Write parts of the right node. result = substringUsingWriteTo(root, dataSize, dataSize); assertEquals(dataSize, result.length); assertEquals((byte) 2, result[0]); assertEquals((byte) 2, result[dataSize - 1]); // Write a segment of bytes that runs across both nodes. result = substringUsingWriteTo(root, dataSize / 2, dataSize); assertEquals(dataSize, result.length); assertEquals((byte) 1, result[0]); assertEquals((byte) 1, result[dataSize - dataSize / 2 - 1]); assertEquals((byte) 2, result[dataSize - dataSize / 2]); assertEquals((byte) 2, result[dataSize - 1]); } /** * Tests ByteString uses Arrays based byte copier when running under Hotstop VM. */ public void testByteArrayCopier() throws Exception { Field field = ByteString.class.getDeclaredField("byteArrayCopier"); field.setAccessible(true); Object byteArrayCopier = field.get(null); assertNotNull(byteArrayCopier); assertTrue( byteArrayCopier.toString(), byteArrayCopier.getClass().getSimpleName().endsWith("ArraysByteArrayCopier")); } }