Imported Upstream version 3.8.0
[platform/upstream/protobuf.git] / java / core / src / test / java / com / google / protobuf / NioByteStringTest.java
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 static com.google.protobuf.Internal.UTF_8;
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.BufferOverflowException;
45 import java.nio.ByteBuffer;
46 import java.util.Arrays;
47 import java.util.List;
48 import java.util.NoSuchElementException;
49 import junit.framework.TestCase;
50
51 /** Tests for {@link NioByteString}. */
52 public class NioByteStringTest extends TestCase {
53   private static final ByteString EMPTY = new NioByteString(ByteBuffer.wrap(new byte[0]));
54   private static final String CLASSNAME = NioByteString.class.getSimpleName();
55   private static final byte[] BYTES = ByteStringTest.getTestBytes(1234, 11337766L);
56   private static final int EXPECTED_HASH = ByteString.wrap(BYTES).hashCode();
57
58   private final ByteBuffer backingBuffer = ByteBuffer.wrap(BYTES.clone());
59   private final ByteString testString = new NioByteString(backingBuffer);
60
61   public void testExpectedType() {
62     String actualClassName = getActualClassName(testString);
63     assertEquals(CLASSNAME + " should match type exactly", CLASSNAME, actualClassName);
64   }
65
66   protected String getActualClassName(Object object) {
67     String actualClassName = object.getClass().getName();
68     actualClassName = actualClassName.substring(actualClassName.lastIndexOf('.') + 1);
69     return actualClassName;
70   }
71
72   public void testByteAt() {
73     boolean stillEqual = true;
74     for (int i = 0; stillEqual && i < BYTES.length; ++i) {
75       stillEqual = (BYTES[i] == testString.byteAt(i));
76     }
77     assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
78   }
79
80   public void testByteIterator() {
81     boolean stillEqual = true;
82     ByteString.ByteIterator iter = testString.iterator();
83     for (int i = 0; stillEqual && i < BYTES.length; ++i) {
84       stillEqual = (iter.hasNext() && BYTES[i] == iter.nextByte());
85     }
86     assertTrue(CLASSNAME + " must capture the right bytes", stillEqual);
87     assertFalse(CLASSNAME + " must have exhausted the itertor", iter.hasNext());
88
89     try {
90       iter.nextByte();
91       fail("Should have thrown an exception.");
92     } catch (NoSuchElementException e) {
93       // This is success
94     }
95   }
96
97   public void testByteIterable() {
98     boolean stillEqual = true;
99     int j = 0;
100     for (byte quantum : testString) {
101       stillEqual = (BYTES[j] == quantum);
102       ++j;
103     }
104     assertTrue(CLASSNAME + " must capture the right bytes as Bytes", stillEqual);
105     assertEquals(CLASSNAME + " iterable character count", BYTES.length, j);
106   }
107
108   public void testSize() {
109     assertEquals(CLASSNAME + " must have the expected size", BYTES.length, testString.size());
110   }
111
112   public void testGetTreeDepth() {
113     assertEquals(CLASSNAME + " must have depth 0", 0, testString.getTreeDepth());
114   }
115
116   public void testIsBalanced() {
117     assertTrue(CLASSNAME + " is technically balanced", testString.isBalanced());
118   }
119
120   public void testCopyTo_ByteArrayOffsetLength() {
121     int destinationOffset = 50;
122     int length = 100;
123     byte[] destination = new byte[destinationOffset + length];
124     int sourceOffset = 213;
125     testString.copyTo(destination, sourceOffset, destinationOffset, length);
126     boolean stillEqual = true;
127     for (int i = 0; stillEqual && i < length; ++i) {
128       stillEqual = BYTES[i + sourceOffset] == destination[i + destinationOffset];
129     }
130     assertTrue(CLASSNAME + ".copyTo(4 arg) must give the expected bytes", stillEqual);
131   }
132
133   public void testCopyTo_ByteArrayOffsetLengthErrors() {
134     int destinationOffset = 50;
135     int length = 100;
136     byte[] destination = new byte[destinationOffset + length];
137
138     try {
139       // Copy one too many bytes
140       testString.copyTo(destination, testString.size() + 1 - length, destinationOffset, length);
141       fail("Should have thrown an exception when copying too many bytes of a " + CLASSNAME);
142     } catch (IndexOutOfBoundsException expected) {
143       // This is success
144     }
145
146     try {
147       // Copy with illegal negative sourceOffset
148       testString.copyTo(destination, -1, destinationOffset, length);
149       fail("Should have thrown an exception when given a negative sourceOffset in " + CLASSNAME);
150     } catch (IndexOutOfBoundsException expected) {
151       // This is success
152     }
153
154     try {
155       // Copy with illegal negative destinationOffset
156       testString.copyTo(destination, 0, -1, length);
157       fail(
158           "Should have thrown an exception when given a negative destinationOffset in "
159               + CLASSNAME);
160     } catch (IndexOutOfBoundsException expected) {
161       // This is success
162     }
163
164     try {
165       // Copy with illegal negative size
166       testString.copyTo(destination, 0, 0, -1);
167       fail("Should have thrown an exception when given a negative size in " + CLASSNAME);
168     } catch (IndexOutOfBoundsException expected) {
169       // This is success
170     }
171
172     try {
173       // Copy with illegal too-large sourceOffset
174       testString.copyTo(destination, 2 * testString.size(), 0, length);
175       fail(
176           "Should have thrown an exception when the destinationOffset is too large in "
177               + CLASSNAME);
178     } catch (IndexOutOfBoundsException expected) {
179       // This is success
180     }
181
182     try {
183       // Copy with illegal too-large destinationOffset
184       testString.copyTo(destination, 0, 2 * destination.length, length);
185       fail(
186           "Should have thrown an exception when the destinationOffset is too large in "
187               + CLASSNAME);
188     } catch (IndexOutOfBoundsException expected) {
189       // This is success
190     }
191   }
192
193   public void testCopyTo_ByteBuffer() {
194     // Same length.
195     ByteBuffer myBuffer = ByteBuffer.allocate(BYTES.length);
196     testString.copyTo(myBuffer);
197     myBuffer.flip();
198     assertEquals(
199         CLASSNAME + ".copyTo(ByteBuffer) must give back the same bytes", backingBuffer, myBuffer);
200
201     // Target buffer bigger than required.
202     myBuffer = ByteBuffer.allocate(testString.size() + 1);
203     testString.copyTo(myBuffer);
204     myBuffer.flip();
205     assertEquals(backingBuffer, myBuffer);
206
207     // Target buffer has no space.
208     myBuffer = ByteBuffer.allocate(0);
209     try {
210       testString.copyTo(myBuffer);
211       fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
212     } catch (BufferOverflowException e) {
213       // Expected.
214     }
215
216     // Target buffer too small.
217     myBuffer = ByteBuffer.allocate(1);
218     try {
219       testString.copyTo(myBuffer);
220       fail("Should have thrown an exception when target ByteBuffer has insufficient capacity");
221     } catch (BufferOverflowException e) {
222       // Expected.
223     }
224   }
225
226   public void testMarkSupported() {
227     InputStream stream = testString.newInput();
228     assertTrue(CLASSNAME + ".newInput() must support marking", stream.markSupported());
229   }
230
231   public void testMarkAndReset() throws IOException {
232     int fraction = testString.size() / 3;
233
234     InputStream stream = testString.newInput();
235     stream.mark(testString.size()); // First, mark() the end.
236
237     skipFully(stream, fraction); // Skip a large fraction, but not all.
238     assertEquals(
239         CLASSNAME + ": after skipping to the 'middle', half the bytes are available",
240         (testString.size() - fraction),
241         stream.available());
242     stream.reset();
243     assertEquals(
244         CLASSNAME + ": after resetting, all bytes are available",
245         testString.size(),
246         stream.available());
247
248     skipFully(stream, testString.size()); // Skip to the end.
249     assertEquals(
250         CLASSNAME + ": after skipping to the end, no more bytes are available",
251         0,
252         stream.available());
253   }
254
255   /**
256    * Discards {@code n} bytes of data from the input stream. This method will block until the full
257    * amount has been skipped. Does not close the stream.
258    *
259    * <p>Copied from com.google.common.io.ByteStreams to avoid adding dependency.
260    *
261    * @param in the input stream to read from
262    * @param n the number of bytes to skip
263    * @throws EOFException if this stream reaches the end before skipping all the bytes
264    * @throws IOException if an I/O error occurs, or the stream does not support skipping
265    */
266   static void skipFully(InputStream in, long n) throws IOException {
267     long toSkip = n;
268     while (n > 0) {
269       long amt = in.skip(n);
270       if (amt == 0) {
271         // Force a blocking read to avoid infinite loop
272         if (in.read() == -1) {
273           long skipped = toSkip - n;
274           throw new EOFException(
275               "reached end of stream after skipping "
276                   + skipped
277                   + " bytes; "
278                   + toSkip
279                   + " bytes expected");
280         }
281         n--;
282       } else {
283         n -= amt;
284       }
285     }
286   }
287
288   public void testAsReadOnlyByteBuffer() {
289     ByteBuffer byteBuffer = testString.asReadOnlyByteBuffer();
290     byte[] roundTripBytes = new byte[BYTES.length];
291     assertTrue(byteBuffer.remaining() == BYTES.length);
292     assertTrue(byteBuffer.isReadOnly());
293     byteBuffer.get(roundTripBytes);
294     assertTrue(
295         CLASSNAME + ".asReadOnlyByteBuffer() must give back the same bytes",
296         Arrays.equals(BYTES, roundTripBytes));
297   }
298
299   public void testAsReadOnlyByteBufferList() {
300     List<ByteBuffer> byteBuffers = testString.asReadOnlyByteBufferList();
301     int bytesSeen = 0;
302     byte[] roundTripBytes = new byte[BYTES.length];
303     for (ByteBuffer byteBuffer : byteBuffers) {
304       int thisLength = byteBuffer.remaining();
305       assertTrue(byteBuffer.isReadOnly());
306       assertTrue(bytesSeen + thisLength <= BYTES.length);
307       byteBuffer.get(roundTripBytes, bytesSeen, thisLength);
308       bytesSeen += thisLength;
309     }
310     assertTrue(bytesSeen == BYTES.length);
311     assertTrue(
312         CLASSNAME + ".asReadOnlyByteBufferTest() must give back the same bytes",
313         Arrays.equals(BYTES, roundTripBytes));
314   }
315
316   public void testToByteArray() {
317     byte[] roundTripBytes = testString.toByteArray();
318     assertTrue(
319         CLASSNAME + ".toByteArray() must give back the same bytes",
320         Arrays.equals(BYTES, roundTripBytes));
321   }
322
323   public void testWriteTo() throws IOException {
324     ByteArrayOutputStream bos = new ByteArrayOutputStream();
325     testString.writeTo(bos);
326     byte[] roundTripBytes = bos.toByteArray();
327     assertTrue(
328         CLASSNAME + ".writeTo() must give back the same bytes",
329         Arrays.equals(BYTES, roundTripBytes));
330   }
331
332   public void testWriteToShouldNotExposeInternalBufferToOutputStream() throws IOException {
333     OutputStream os =
334         new OutputStream() {
335           @Override
336           public void write(byte[] b, int off, int len) {
337             Arrays.fill(b, off, off + len, (byte) 0);
338           }
339
340           @Override
341           public void write(int b) {
342             throw new UnsupportedOperationException();
343           }
344         };
345
346     byte[] original = Arrays.copyOf(BYTES, BYTES.length);
347     testString.writeTo(os);
348     assertTrue(
349         CLASSNAME + ".writeTo() must NOT grant access to underlying buffer",
350         Arrays.equals(original, BYTES));
351   }
352
353   public void testWriteToInternalShouldExposeInternalBufferToOutputStream() throws IOException {
354     OutputStream os =
355         new OutputStream() {
356           @Override
357           public void write(byte[] b, int off, int len) {
358             Arrays.fill(b, off, off + len, (byte) 0);
359           }
360
361           @Override
362           public void write(int b) {
363             throw new UnsupportedOperationException();
364           }
365         };
366
367     testString.writeToInternal(os, 0, testString.size());
368     byte[] allZeros = new byte[testString.size()];
369     assertTrue(
370         CLASSNAME + ".writeToInternal() must grant access to underlying buffer",
371         Arrays.equals(allZeros, backingBuffer.array()));
372   }
373
374   public void testWriteToShouldExposeInternalBufferToByteOutput() throws IOException {
375     ByteOutput out =
376         new ByteOutput() {
377           @Override
378           public void write(byte value) throws IOException {
379             throw new UnsupportedOperationException();
380           }
381
382           @Override
383           public void write(byte[] value, int offset, int length) throws IOException {
384             throw new UnsupportedOperationException();
385           }
386
387           @Override
388           public void write(ByteBuffer value) throws IOException {
389             throw new UnsupportedOperationException();
390           }
391
392           @Override
393           public void writeLazy(byte[] value, int offset, int length) throws IOException {
394             throw new UnsupportedOperationException();
395           }
396
397           @Override
398           public void writeLazy(ByteBuffer value) throws IOException {
399             Arrays.fill(
400                 value.array(), value.arrayOffset(), value.arrayOffset() + value.limit(), (byte) 0);
401           }
402         };
403
404     testString.writeTo(out);
405     byte[] allZeros = new byte[testString.size()];
406     assertTrue(
407         CLASSNAME + ".writeTo() must grant access to underlying buffer",
408         Arrays.equals(allZeros, backingBuffer.array()));
409   }
410
411   public void testNewOutput() throws IOException {
412     ByteArrayOutputStream bos = new ByteArrayOutputStream();
413     ByteString.Output output = ByteString.newOutput();
414     testString.writeTo(output);
415     assertEquals("Output Size returns correct result", output.size(), testString.size());
416     output.writeTo(bos);
417     assertTrue(
418         "Output.writeTo() must give back the same bytes", Arrays.equals(BYTES, bos.toByteArray()));
419
420     // write the output stream to itself! This should cause it to double
421     output.writeTo(output);
422     assertEquals(
423         "Writing an output stream to itself is successful",
424         testString.concat(testString),
425         output.toByteString());
426
427     output.reset();
428     assertEquals("Output.reset() resets the output", 0, output.size());
429     assertEquals("Output.reset() resets the output", EMPTY, output.toByteString());
430   }
431
432   public void testToString() {
433     String testString = "I love unicode \u1234\u5678 characters";
434     ByteString unicode = forString(testString);
435     String roundTripString = unicode.toString(UTF_8);
436     assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
437   }
438
439   public void testCharsetToString() {
440     String testString = "I love unicode \u1234\u5678 characters";
441     ByteString unicode = forString(testString);
442     String roundTripString = unicode.toString(UTF_8);
443     assertEquals(CLASSNAME + " unicode must match", testString, roundTripString);
444   }
445
446   public void testToString_returnsCanonicalEmptyString() {
447     assertSame(
448         CLASSNAME + " must be the same string references",
449         EMPTY.toString(UTF_8),
450         new NioByteString(ByteBuffer.wrap(new byte[0])).toString(UTF_8));
451   }
452
453   public void testToString_raisesException() {
454     try {
455       EMPTY.toString("invalid");
456       fail("Should have thrown an exception.");
457     } catch (UnsupportedEncodingException expected) {
458       // This is success
459     }
460
461     try {
462       testString.toString("invalid");
463       fail("Should have thrown an exception.");
464     } catch (UnsupportedEncodingException expected) {
465       // This is success
466     }
467   }
468
469   public void testEquals() {
470     assertEquals(CLASSNAME + " must not equal null", false, testString.equals(null));
471     assertEquals(CLASSNAME + " must equal self", testString, testString);
472     assertFalse(CLASSNAME + " must not equal the empty string", testString.equals(EMPTY));
473     assertEquals(CLASSNAME + " empty strings must be equal", EMPTY, testString.substring(55, 55));
474     assertEquals(
475         CLASSNAME + " must equal another string with the same value",
476         testString,
477         new NioByteString(backingBuffer));
478
479     byte[] mungedBytes = mungedBytes();
480     assertFalse(
481         CLASSNAME + " must not equal every string with the same length",
482         testString.equals(new NioByteString(ByteBuffer.wrap(mungedBytes))));
483   }
484
485   public void testEqualsLiteralByteString() {
486     ByteString literal = ByteString.copyFrom(BYTES);
487     assertEquals(CLASSNAME + " must equal LiteralByteString with same value", literal, testString);
488     assertEquals(CLASSNAME + " must equal LiteralByteString with same value", testString, literal);
489     assertFalse(
490         CLASSNAME + " must not equal the empty string", testString.equals(ByteString.EMPTY));
491     assertEquals(
492         CLASSNAME + " empty strings must be equal", ByteString.EMPTY, testString.substring(55, 55));
493
494     literal = ByteString.copyFrom(mungedBytes());
495     assertFalse(
496         CLASSNAME + " must not equal every LiteralByteString with the same length",
497         testString.equals(literal));
498     assertFalse(
499         CLASSNAME + " must not equal every LiteralByteString with the same length",
500         literal.equals(testString));
501   }
502
503   public void testEqualsRopeByteString() {
504     ByteString p1 = ByteString.copyFrom(BYTES, 0, 5);
505     ByteString p2 = ByteString.copyFrom(BYTES, 5, BYTES.length - 5);
506     ByteString rope = p1.concat(p2);
507
508     assertEquals(CLASSNAME + " must equal RopeByteString with same value", rope, testString);
509     assertEquals(CLASSNAME + " must equal RopeByteString with same value", testString, rope);
510     assertFalse(
511         CLASSNAME + " must not equal the empty string",
512         testString.equals(ByteString.EMPTY.concat(ByteString.EMPTY)));
513     assertEquals(
514         CLASSNAME + " empty strings must be equal",
515         ByteString.EMPTY.concat(ByteString.EMPTY),
516         testString.substring(55, 55));
517
518     byte[] mungedBytes = mungedBytes();
519     p1 = ByteString.copyFrom(mungedBytes, 0, 5);
520     p2 = ByteString.copyFrom(mungedBytes, 5, mungedBytes.length - 5);
521     rope = p1.concat(p2);
522     assertFalse(
523         CLASSNAME + " must not equal every RopeByteString with the same length",
524         testString.equals(rope));
525     assertFalse(
526         CLASSNAME + " must not equal every RopeByteString with the same length",
527         rope.equals(testString));
528   }
529
530   private byte[] mungedBytes() {
531     byte[] mungedBytes = new byte[BYTES.length];
532     System.arraycopy(BYTES, 0, mungedBytes, 0, BYTES.length);
533     mungedBytes[mungedBytes.length - 5] = (byte) (mungedBytes[mungedBytes.length - 5] ^ 0xFF);
534     return mungedBytes;
535   }
536
537   public void testHashCode() {
538     int hash = testString.hashCode();
539     assertEquals(CLASSNAME + " must have expected hashCode", EXPECTED_HASH, hash);
540   }
541
542   public void testPeekCachedHashCode() {
543     ByteString newString = new NioByteString(backingBuffer);
544     assertEquals(
545         CLASSNAME + ".peekCachedHashCode() should return zero at first",
546         0,
547         newString.peekCachedHashCode());
548     newString.hashCode();
549     assertEquals(
550         CLASSNAME + ".peekCachedHashCode should return zero at first",
551         EXPECTED_HASH,
552         newString.peekCachedHashCode());
553   }
554
555   public void testPartialHash() {
556     // partialHash() is more strenuously tested elsewhere by testing hashes of substrings.
557     // This test would fail if the expected hash were 1.  It's not.
558     int hash = testString.partialHash(testString.size(), 0, testString.size());
559     assertEquals(CLASSNAME + ".partialHash() must yield expected hashCode", EXPECTED_HASH, hash);
560   }
561
562   public void testNewInput() throws IOException {
563     InputStream input = testString.newInput();
564     assertEquals(
565         "InputStream.available() returns correct value", testString.size(), input.available());
566     boolean stillEqual = true;
567     for (byte referenceByte : BYTES) {
568       int expectedInt = (referenceByte & 0xFF);
569       stillEqual = (expectedInt == input.read());
570     }
571     assertEquals("InputStream.available() returns correct value", 0, input.available());
572     assertTrue(CLASSNAME + " must give the same bytes from the InputStream", stillEqual);
573     assertEquals(CLASSNAME + " InputStream must now be exhausted", -1, input.read());
574   }
575
576   public void testNewInput_skip() throws IOException {
577     InputStream input = testString.newInput();
578     int stringSize = testString.size();
579     int nearEndIndex = stringSize * 2 / 3;
580     long skipped1 = input.skip(nearEndIndex);
581     assertEquals("InputStream.skip()", skipped1, nearEndIndex);
582     assertEquals("InputStream.available()", stringSize - skipped1, input.available());
583     assertTrue("InputStream.mark() is available", input.markSupported());
584     input.mark(0);
585     assertEquals(
586         "InputStream.skip(), read()", testString.byteAt(nearEndIndex) & 0xFF, input.read());
587     assertEquals("InputStream.available()", stringSize - skipped1 - 1, input.available());
588     long skipped2 = input.skip(stringSize);
589     assertEquals("InputStream.skip() incomplete", skipped2, stringSize - skipped1 - 1);
590     assertEquals("InputStream.skip(), no more input", 0, input.available());
591     assertEquals("InputStream.skip(), no more input", -1, input.read());
592     input.reset();
593     assertEquals("InputStream.reset() succeded", stringSize - skipped1, input.available());
594     assertEquals(
595         "InputStream.reset(), read()", testString.byteAt(nearEndIndex) & 0xFF, input.read());
596   }
597
598   public void testNewCodedInput() throws IOException {
599     CodedInputStream cis = testString.newCodedInput();
600     byte[] roundTripBytes = cis.readRawBytes(BYTES.length);
601     assertTrue(
602         CLASSNAME + " must give the same bytes back from the CodedInputStream",
603         Arrays.equals(BYTES, roundTripBytes));
604     assertTrue(CLASSNAME + " CodedInputStream must now be exhausted", cis.isAtEnd());
605   }
606
607   /**
608    * Make sure we keep things simple when concatenating with empty. See also {@link
609    * ByteStringTest#testConcat_empty()}.
610    */
611   public void testConcat_empty() {
612     assertSame(
613         CLASSNAME + " concatenated with empty must give " + CLASSNAME,
614         testString.concat(EMPTY),
615         testString);
616     assertSame(
617         "empty concatenated with " + CLASSNAME + " must give " + CLASSNAME,
618         EMPTY.concat(testString),
619         testString);
620   }
621
622   public void testJavaSerialization() throws Exception {
623     ByteArrayOutputStream out = new ByteArrayOutputStream();
624     ObjectOutputStream oos = new ObjectOutputStream(out);
625     oos.writeObject(testString);
626     oos.close();
627     byte[] pickled = out.toByteArray();
628     InputStream in = new ByteArrayInputStream(pickled);
629     ObjectInputStream ois = new ObjectInputStream(in);
630     Object o = ois.readObject();
631     assertTrue("Didn't get a ByteString back", o instanceof ByteString);
632     assertEquals("Should get an equal ByteString back", testString, o);
633   }
634
635   private static ByteString forString(String str) {
636     return new NioByteString(ByteBuffer.wrap(str.getBytes(UTF_8)));
637   }
638 }