View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.output;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.junit.jupiter.api.Assertions.fail;
27  
28  import java.io.ByteArrayInputStream;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.nio.file.Path;
34  import java.util.Arrays;
35  import java.util.stream.Stream;
36  
37  import org.apache.commons.io.IOUtils;
38  import org.apache.commons.io.function.IOFunction;
39  import org.apache.commons.io.input.ClosedInputStream;
40  import org.junit.jupiter.api.io.TempDir;
41  import org.junit.jupiter.params.ParameterizedTest;
42  import org.junit.jupiter.params.provider.Arguments;
43  import org.junit.jupiter.params.provider.MethodSource;
44  
45  /**
46   * Tests the alternative ByteArrayOutputStream implementations.
47   */
48  class ByteArrayOutputStreamTest {
49  
50      private interface BAOSFactory<T extends AbstractByteArrayOutputStream<T>> {
51  
52          T newInstance();
53  
54          T newInstance(int size);
55      }
56  
57      private static final class ByteArrayOutputStreamFactory implements BAOSFactory<ByteArrayOutputStream> {
58  
59          @Override
60          public ByteArrayOutputStream newInstance() {
61              return new ByteArrayOutputStream();
62          }
63  
64          @Override
65          public ByteArrayOutputStream newInstance(final int size) {
66              return new ByteArrayOutputStream(size);
67          }
68      }
69  
70      private static final class UnsynchronizedByteArrayOutputStreamFactory implements BAOSFactory<UnsynchronizedByteArrayOutputStream> {
71  
72          @Override
73          public UnsynchronizedByteArrayOutputStream newInstance() {
74              return new UnsynchronizedByteArrayOutputStream();
75          }
76  
77          @Override
78          public UnsynchronizedByteArrayOutputStream newInstance(final int size) {
79              return new UnsynchronizedByteArrayOutputStream(size);
80          }
81      }
82  
83      private static final byte[] ASCII_DATA;
84      static {
85          ASCII_DATA = new byte[64];
86          for (byte i = 0; i < ASCII_DATA.length; i++) {
87              ASCII_DATA[i] = (byte) (char) ('0' + i);
88          }
89      }
90  
91      private static Stream<Arguments> baosFactories() {
92          return Stream.of(Arguments.of(ByteArrayOutputStream.class.getSimpleName(), new ByteArrayOutputStreamFactory()),
93                  Arguments.of(UnsynchronizedByteArrayOutputStream.class.getSimpleName(), new UnsynchronizedByteArrayOutputStreamFactory()));
94      }
95  
96      private static boolean byteCmp(final byte[] src, final byte[] cmp) {
97          for (int i = 0; i < cmp.length; i++) {
98              if (src[i] != cmp[i]) {
99                  return false;
100             }
101         }
102         return true;
103     }
104 
105     private static Stream<Arguments> toBufferedInputStreamFunctionFactories() {
106         final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStream = ByteArrayOutputStream::toBufferedInputStream;
107         final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStreamWithSize = is -> ByteArrayOutputStream.toBufferedInputStream(is, 1024);
108         final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStreamWith0Size = is -> ByteArrayOutputStream.toBufferedInputStream(is, 0);
109         final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStream = UnsynchronizedByteArrayOutputStream::toBufferedInputStream;
110         final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWithSize = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(is,
111                 1024);
112         final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWith0Size = is -> UnsynchronizedByteArrayOutputStream
113                 .toBufferedInputStream(is, 0);
114         return Stream.of(Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream)", syncBaosToBufferedInputStream),
115                 Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, int)", syncBaosToBufferedInputStreamWithSize),
116                 Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, 0)", syncBaosToBufferedInputStreamWith0Size),
117                 Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream)", unSyncBaosToBufferedInputStream),
118                 Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, int)", unSyncBaosToBufferedInputStreamWithSize),
119                 Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, 0)", unSyncBaosToBufferedInputStreamWith0Size));
120     }
121 
122     private void checkByteArrays(final byte[] expected, final byte[] actual) {
123         if (expected.length != actual.length) {
124             fail("Resulting byte arrays are not equally long");
125         }
126         if (!byteCmp(expected, actual)) {
127             fail("Resulting byte arrays are not equal");
128         }
129     }
130 
131     private void checkStreams(final AbstractByteArrayOutputStream<?> actual, final java.io.ByteArrayOutputStream expected) {
132         assertEquals(expected.size(), actual.size(), "Sizes are not equal");
133         final byte[] buf = actual.toByteArray();
134         final byte[] refbuf = expected.toByteArray();
135         checkByteArrays(buf, refbuf);
136     }
137 
138     @ParameterizedTest(name = "[{index}] {0}")
139     @MethodSource("baosFactories")
140     void testInvalidParameterizedConstruction(final String baosName, final BAOSFactory<?> baosFactory) {
141         assertThrows(IllegalArgumentException.class, () -> baosFactory.newInstance(-1));
142     }
143 
144     @ParameterizedTest(name = "[{index}] {0}")
145     @MethodSource("baosFactories")
146     void testInvalidWriteLenUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
147         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance()) {
148             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, -1));
149         }
150     }
151 
152     @ParameterizedTest(name = "[{index}] {0}")
153     @MethodSource("baosFactories")
154     void testInvalidWriteOffsetAndLenOver(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
155         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance()) {
156             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, 2));
157         }
158     }
159 
160     @ParameterizedTest(name = "[{index}] {0}")
161     @MethodSource("baosFactories")
162     void testInvalidWriteOffsetAndLenUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
163         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance()) {
164             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 1, -2));
165         }
166     }
167 
168     @ParameterizedTest(name = "[{index}] {0}")
169     @MethodSource("baosFactories")
170     void testInvalidWriteOffsetOver(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
171         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance()) {
172             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(IOUtils.EMPTY_BYTE_ARRAY, 1, 0));
173         }
174     }
175 
176     @ParameterizedTest(name = "[{index}] {0}")
177     @MethodSource("baosFactories")
178     void testInvalidWriteOffsetUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
179         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance()) {
180             assertThrows(IndexOutOfBoundsException.class, () -> baout.write(null, -1, 0));
181         }
182     }
183 
184     @ParameterizedTest(name = "[{index}] {0}")
185     @MethodSource("toBufferedInputStreamFunctionFactories")
186     void testToBufferedInputStream(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) throws IOException {
187         final byte[] data = { (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE };
188         try (ByteArrayInputStream bain = new ByteArrayInputStream(data)) {
189             assertEquals(data.length, bain.available());
190             try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) {
191                 assertEquals(data.length, buffered.available());
192                 assertArrayEquals(data, IOUtils.toByteArray(buffered));
193             }
194         }
195     }
196 
197     @ParameterizedTest(name = "[{index}] {0}")
198     @MethodSource("toBufferedInputStreamFunctionFactories")
199     void testToBufferedInputStreamEmpty(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) throws IOException {
200         try (ByteArrayInputStream bain = new ByteArrayInputStream(IOUtils.EMPTY_BYTE_ARRAY)) {
201             assertEquals(0, bain.available());
202             try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) {
203                 assertEquals(0, buffered.available());
204             }
205         }
206     }
207 
208     @ParameterizedTest(name = "[{index}] {0}")
209     @MethodSource("toBufferedInputStreamFunctionFactories")
210     void testToBufferedInputStreamEmptyFile(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction,
211             final @TempDir Path temporaryFolder) throws IOException {
212         final Path emptyFile = Files.createTempFile(temporaryFolder, getClass().getSimpleName(), "-empty.txt");
213         try (InputStream is = Files.newInputStream(emptyFile)) {
214             assertEquals(0, is.available());
215             try (InputStream buffered = toBufferedInputStreamFunction.apply(is)) {
216                 assertEquals(0, buffered.available());
217                 assertArrayEquals(IOUtils.EMPTY_BYTE_ARRAY, IOUtils.toByteArray(buffered));
218             }
219         }
220     }
221 
222     @ParameterizedTest(name = "[{index}] {0}")
223     @MethodSource("baosFactories")
224     void testToInputStream(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
225         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance(); java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
226             // Write 8224 bytes
227             writeByteArrayIndex(baout, ref, 32);
228             for (int i = 0; i < 128; i++) {
229                 writeByteArrayIndex(baout, ref, 64);
230             }
231             // Get data before more writes
232             try (InputStream in = baout.toInputStream()) {
233                 byte[] refData = ref.toByteArray();
234                 // Write some more data
235                 writeByteArrayIndex(baout, ref, new int[] { 2, 4, 8, 16 });
236                 // Check original data
237                 byte[] baoutData = IOUtils.toByteArray(in);
238                 assertEquals(8224, baoutData.length);
239                 checkByteArrays(refData, baoutData);
240                 // Check all data written
241                 try (InputStream in2 = baout.toInputStream()) {
242                     baoutData = IOUtils.toByteArray(in2);
243                 }
244                 refData = ref.toByteArray();
245                 assertEquals(8254, baoutData.length);
246                 checkByteArrays(refData, baoutData);
247             }
248         }
249     }
250 
251     @ParameterizedTest(name = "[{index}] {0}")
252     @MethodSource("baosFactories")
253     void testToInputStreamEmpty(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
254         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance();
255                 // Get data before more writes
256                 InputStream in = baout.toInputStream()) {
257             assertEquals(0, in.available());
258             assertInstanceOf(ClosedInputStream.class, in);
259         }
260     }
261 
262     @ParameterizedTest(name = "[{index}] {0}")
263     @MethodSource("baosFactories")
264     void testToInputStreamWithReset(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
265         // Make sure reset() do not destroy InputStream returned from toInputStream()
266         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance(); java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
267             // Write 8224 bytes
268             writeByteArrayIndex(baout, ref, 32);
269             for (int i = 0; i < 128; i++) {
270                 writeByteArrayIndex(baout, ref, 64);
271             }
272             // Get data before reset
273             try (InputStream in = baout.toInputStream()) {
274                 byte[] refData = ref.toByteArray();
275                 // Reset and write some new data
276                 baout.reset();
277                 ref.reset();
278                 writeByteArrayIndex(baout, ref, new int[] { 2, 4, 8, 16 });
279                 // Check original data
280                 byte[] baoutData = IOUtils.toByteArray(in);
281                 assertEquals(8224, baoutData.length);
282                 checkByteArrays(refData, baoutData);
283                 // Check new data written after reset
284                 try (InputStream in2 = baout.toInputStream()) {
285                     baoutData = IOUtils.toByteArray(in2);
286                 }
287                 refData = ref.toByteArray();
288                 assertEquals(30, baoutData.length);
289                 checkByteArrays(refData, baoutData);
290             }
291         }
292     }
293 
294     @ParameterizedTest(name = "[{index}] {0}")
295     @MethodSource("baosFactories")
296     void testWriteByte(final String baosName, final BAOSFactory<?> baosFactory) throws Exception {
297         int written;
298         // The ByteArrayOutputStream is initialized with 32 bytes to match
299         // the original more closely for this test.
300         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance(32); java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
301             // First three writes
302             written = writeByte(baout, ref, new int[] { 4, 10, 22 });
303             assertEquals(36, written);
304             checkStreams(baout, ref);
305             // Another two writes to see if there are any bad effects after toByteArray()
306             written = writeByte(baout, ref, new int[] { 20, 12 });
307             assertEquals(32, written);
308             checkStreams(baout, ref);
309             // Now reset the streams
310             baout.reset();
311             ref.reset();
312             // Test again to see if reset() had any bad effects
313             written = writeByte(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 });
314             assertEquals(155, written);
315             checkStreams(baout, ref);
316             // Test the readFrom(InputStream) method
317             baout.reset();
318             written = baout.write(new ByteArrayInputStream(ref.toByteArray()));
319             assertEquals(155, written);
320             checkStreams(baout, ref);
321             // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream
322             // and vice-versa to test the writeTo() method.
323             try (AbstractByteArrayOutputStream<?> baout1 = baosFactory.newInstance(32)) {
324                 ref.writeTo(baout1);
325                 final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream();
326                 baout.writeTo(ref1);
327                 checkStreams(baout1, ref1);
328                 // Testing toString(String)
329                 final String baoutString = baout.toString("ASCII");
330                 final String refString = ref.toString("ASCII");
331                 assertEquals(refString, baoutString, "ASCII decoded String must be equal");
332                 // Make sure that empty ByteArrayOutputStreams really don't create garbage
333                 // on toByteArray()
334                 try (AbstractByteArrayOutputStream<?> baos1 = baosFactory.newInstance(); AbstractByteArrayOutputStream<?> baos2 = baosFactory.newInstance()) {
335                     assertSame(baos1.toByteArray(), baos2.toByteArray());
336                 }
337             }
338         }
339     }
340 
341     @ParameterizedTest(name = "[{index}] {0}")
342     @MethodSource("baosFactories")
343     void testWriteByteArray(final String baosName, final BAOSFactory<?> baosFactory) throws Exception {
344         int written;
345         // The ByteArrayOutputStream is initialized with 32 bytes to match
346         // the original more closely for this test.
347         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance(32); java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
348             // First three writes
349             written = writeByteArray(baout, ref, new int[] { 4, 10, 22 });
350             assertEquals(36, written);
351             checkStreams(baout, ref);
352             // Another two writes to see if there are any bad effects after toByteArray()
353             written = writeByteArray(baout, ref, new int[] { 20, 12 });
354             assertEquals(32, written);
355             checkStreams(baout, ref);
356             // Now reset the streams
357             baout.reset();
358             ref.reset();
359             // Test again to see if reset() had any bad effects
360             written = writeByteArray(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 });
361             assertEquals(155, written);
362             checkStreams(baout, ref);
363             // Test the readFrom(InputStream) method
364             baout.reset();
365             written = baout.write(new ByteArrayInputStream(ref.toByteArray()));
366             assertEquals(155, written);
367             checkStreams(baout, ref);
368             // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream
369             // and vice-versa to test the writeTo() method.
370             try (AbstractByteArrayOutputStream<?> baout1 = baosFactory.newInstance(32)) {
371                 ref.writeTo(baout1);
372                 final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream();
373                 baout.writeTo(ref1);
374                 checkStreams(baout1, ref1);
375                 // Testing toString(String)
376                 final String baoutString = baout.toString("ASCII");
377                 final String refString = ref.toString("ASCII");
378                 assertEquals(refString, baoutString, "ASCII decoded String must be equal");
379                 // Make sure that empty ByteArrayOutputStreams really don't create garbage
380                 // on toByteArray()
381                 try (AbstractByteArrayOutputStream<?> baos1 = baosFactory.newInstance(); AbstractByteArrayOutputStream<?> baos2 = baosFactory.newInstance()) {
382                     assertSame(baos1.toByteArray(), baos2.toByteArray());
383                 }
384             }
385         }
386     }
387 
388     @ParameterizedTest(name = "[{index}] {0}")
389     @MethodSource("baosFactories")
390     void testWriteByteArrayIndex(final String baosName, final BAOSFactory<?> baosFactory) throws Exception {
391         int written;
392         // The ByteArrayOutputStream is initialized with 32 bytes to match
393         // the original more closely for this test.
394         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance(32); java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
395             // First three writes
396             written = writeByteArrayIndex(baout, ref, new int[] { 4, 10, 22 });
397             assertEquals(36, written);
398             checkStreams(baout, ref);
399             // Another two writes to see if there are any bad effects after toByteArray()
400             written = writeByteArrayIndex(baout, ref, new int[] { 20, 12 });
401             assertEquals(32, written);
402             checkStreams(baout, ref);
403             // Now reset the streams
404             baout.reset();
405             ref.reset();
406             // Test again to see if reset() had any bad effects
407             written = writeByteArrayIndex(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 });
408             assertEquals(155, written);
409             checkStreams(baout, ref);
410             // Test the readFrom(InputStream) method
411             baout.reset();
412             written = baout.write(new ByteArrayInputStream(ref.toByteArray()));
413             assertEquals(155, written);
414             checkStreams(baout, ref);
415             // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream
416             // and vice-versa to test the writeTo() method.
417             try (AbstractByteArrayOutputStream<?> baout1 = baosFactory.newInstance(32)) {
418                 ref.writeTo(baout1);
419                 final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream();
420                 baout.writeTo(ref1);
421                 checkStreams(baout1, ref1);
422                 // Testing toString(String)
423                 final String baoutString = baout.toString("ASCII");
424                 final String refString = ref.toString("ASCII");
425                 assertEquals(refString, baoutString, "ASCII decoded String must be equal");
426                 // Make sure that empty ByteArrayOutputStreams really don't create garbage
427                 // on toByteArray()
428                 try (AbstractByteArrayOutputStream<?> baos1 = baosFactory.newInstance(); AbstractByteArrayOutputStream<?> baos2 = baosFactory.newInstance()) {
429                     assertSame(baos1.toByteArray(), baos2.toByteArray());
430                 }
431             }
432         }
433     }
434 
435     @ParameterizedTest(name = "[{index}] {0}")
436     @MethodSource("baosFactories")
437     void testWriteStringCharset(final String baosName, final BAOSFactory<?> baosFactory) throws Exception {
438         int written;
439         // The ByteArrayOutputStream is initialized with 32 bytes to match
440         // the original more closely for this test.
441         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance(32); java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) {
442             // First three writes
443             written = writeStringCharset(baout, ref, new int[] { 4, 10, 22 });
444             assertEquals(36, written);
445             checkStreams(baout, ref);
446             // Another two writes to see if there are any bad effects after toByteArray()
447             written = writeStringCharset(baout, ref, new int[] { 20, 12 });
448             assertEquals(32, written);
449             checkStreams(baout, ref);
450             // Now reset the streams
451             baout.reset();
452             ref.reset();
453             // Test again to see if reset() had any bad effects
454             written = writeStringCharset(baout, ref, new int[] { 5, 47, 33, 60, 1, 0, 8 });
455             assertEquals(155, written);
456             checkStreams(baout, ref);
457             // Test the readFrom(InputStream) method
458             baout.reset();
459             written = baout.write(new ByteArrayInputStream(ref.toByteArray()));
460             assertEquals(155, written);
461             checkStreams(baout, ref);
462             // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream
463             // and vice-versa to test the writeTo() method.
464             try (AbstractByteArrayOutputStream<?> baout1 = baosFactory.newInstance(32)) {
465                 ref.writeTo(baout1);
466                 final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream();
467                 baout.writeTo(ref1);
468                 checkStreams(baout1, ref1);
469                 // Testing toString(String)
470                 final String baoutString = baout.toString("ASCII");
471                 final String refString = ref.toString("ASCII");
472                 assertEquals(refString, baoutString, "ASCII decoded String must be equal");
473                 // Make sure that empty ByteArrayOutputStreams really don't create garbage
474                 // on toByteArray()
475                 try (AbstractByteArrayOutputStream<?> baos1 = baosFactory.newInstance(); AbstractByteArrayOutputStream<?> baos2 = baosFactory.newInstance()) {
476                     assertSame(baos1.toByteArray(), baos2.toByteArray());
477                 }
478             }
479         }
480     }
481 
482     @ParameterizedTest(name = "[{index}] {0}")
483     @MethodSource("baosFactories")
484     void testWriteZero(final String baosName, final BAOSFactory<?> baosFactory) throws IOException {
485         try (AbstractByteArrayOutputStream<?> baout = baosFactory.newInstance()) {
486             baout.write(IOUtils.EMPTY_BYTE_ARRAY, 0, 0);
487             assertTrue(true, "Dummy");
488         }
489     }
490 
491     private int writeByte(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int count) {
492         if (count > ASCII_DATA.length) {
493             throw new IllegalArgumentException("Requesting too many bytes");
494         }
495         if (count == 0) {
496             baout.write(100);
497             ref.write(100);
498             return 1;
499         }
500         for (int i = 0; i < count; i++) {
501             baout.write(ASCII_DATA[i]);
502             ref.write(ASCII_DATA[i]);
503         }
504         return count;
505     }
506 
507     private int writeByte(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) throws IOException {
508         int written = 0;
509         for (final int instruction : instructions) {
510             written += writeByte(baout, ref, instruction);
511         }
512         return written;
513     }
514 
515     private int writeByteArray(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int count) throws IOException {
516         if (count > ASCII_DATA.length) {
517             throw new IllegalArgumentException("Requesting too many bytes");
518         }
519         if (count == 0) {
520             // length 1 data
521             baout.write(new byte[] { 100 });
522             ref.write(new byte[] { 100 });
523             return 1;
524         }
525         baout.write(Arrays.copyOf(ASCII_DATA, count));
526         ref.write(Arrays.copyOf(ASCII_DATA, count));
527         return count;
528     }
529 
530     private int writeByteArray(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int[] instructions)
531             throws IOException {
532         int written = 0;
533         for (final int instruction : instructions) {
534             written += writeByteArray(baout, ref, instruction);
535         }
536         return written;
537     }
538 
539     private int writeByteArrayIndex(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int count) {
540         if (count > ASCII_DATA.length) {
541             throw new IllegalArgumentException("Requesting too many bytes");
542         }
543         if (count == 0) {
544             // length 1 data
545             baout.write(100);
546             ref.write(100);
547             return 1;
548         }
549         baout.write(ASCII_DATA, 0, count);
550         ref.write(ASCII_DATA, 0, count);
551         return count;
552     }
553 
554     private int writeByteArrayIndex(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) {
555         int written = 0;
556         for (final int instruction : instructions) {
557             written += writeByteArrayIndex(baout, ref, instruction);
558         }
559         return written;
560     }
561 
562     private int writeStringCharset(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int count) throws IOException {
563         if (count > ASCII_DATA.length) {
564             throw new IllegalArgumentException("Requesting too many bytes");
565         }
566         if (count == 0) {
567             // length 1 data
568             final String data = "a";
569             baout.write(data, StandardCharsets.UTF_8);
570             ref.write(data.getBytes(StandardCharsets.UTF_8));
571             return 1;
572         }
573         final String data = new String(ASCII_DATA, StandardCharsets.UTF_8).substring(0, count);
574         assertEquals(count, data.length(), () -> String.format("[%,d]:'%s'", count, data));
575         baout.write(data, StandardCharsets.UTF_8);
576         final byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
577         // For now, test assumes 1-1 size mapping from chars to bytes.
578         assertEquals(count, bytes.length, () -> String.format("[%,d]:'%s'", count, data));
579         ref.write(bytes);
580         return count;
581     }
582 
583     private int writeStringCharset(final AbstractByteArrayOutputStream<?> baout, final java.io.ByteArrayOutputStream ref, final int[] instructions)
584             throws IOException {
585         int written = 0;
586         for (final int instruction : instructions) {
587             written += writeStringCharset(baout, ref, instruction);
588         }
589         return written;
590     }
591 }