| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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. |
| */ |
| package org.apache.commons.io.output; |
| |
| import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertSame; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assertions.fail; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.stream.Stream; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.io.function.IOFunction; |
| import org.apache.commons.io.input.ClosedInputStream; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.Arguments; |
| import org.junit.jupiter.params.provider.MethodSource; |
| |
| /** |
| * Tests the alternative ByteArrayOutputStream implementations. |
| */ |
| public class ByteArrayOutputStreamTest { |
| |
| private interface BAOSFactory<T extends AbstractByteArrayOutputStream> { |
| T newInstance(); |
| |
| T newInstance(final int size); |
| } |
| |
| private static final class ByteArrayOutputStreamFactory implements BAOSFactory<ByteArrayOutputStream> { |
| @Override |
| public ByteArrayOutputStream newInstance() { |
| return new ByteArrayOutputStream(); |
| } |
| |
| @Override |
| public ByteArrayOutputStream newInstance(final int size) { |
| return new ByteArrayOutputStream(size); |
| } |
| } |
| |
| private static final class UnsynchronizedByteArrayOutputStreamFactory implements BAOSFactory<UnsynchronizedByteArrayOutputStream> { |
| @Override |
| public UnsynchronizedByteArrayOutputStream newInstance() { |
| return new UnsynchronizedByteArrayOutputStream(); |
| } |
| |
| @Override |
| public UnsynchronizedByteArrayOutputStream newInstance(final int size) { |
| return new UnsynchronizedByteArrayOutputStream(size); |
| } |
| } |
| |
| private static final byte[] DATA; |
| |
| static { |
| DATA = new byte[64]; |
| for (byte i = 0; i < 64; i++) { |
| DATA[i] = i; |
| } |
| } |
| |
| private static Stream<Arguments> baosFactories() { |
| return Stream.of(Arguments.of(ByteArrayOutputStream.class.getSimpleName(), new ByteArrayOutputStreamFactory()), |
| Arguments.of(UnsynchronizedByteArrayOutputStream.class.getSimpleName(), new UnsynchronizedByteArrayOutputStreamFactory())); |
| } |
| |
| private static boolean byteCmp(final byte[] src, final byte[] cmp) { |
| for (int i = 0; i < cmp.length; i++) { |
| if (src[i] != cmp[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static Stream<Arguments> toBufferedInputStreamFunctionFactories() { |
| final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStream = ByteArrayOutputStream::toBufferedInputStream; |
| final IOFunction<InputStream, InputStream> syncBaosToBufferedInputStreamWithSize = is -> ByteArrayOutputStream.toBufferedInputStream(is, 1024); |
| final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStream = UnsynchronizedByteArrayOutputStream::toBufferedInputStream; |
| final IOFunction<InputStream, InputStream> unSyncBaosToBufferedInputStreamWithSize = is -> UnsynchronizedByteArrayOutputStream.toBufferedInputStream(is, |
| 1024); |
| |
| return Stream.of(Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream)", syncBaosToBufferedInputStream), |
| Arguments.of("ByteArrayOutputStream.toBufferedInputStream(InputStream, int)", syncBaosToBufferedInputStreamWithSize), |
| Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream)", unSyncBaosToBufferedInputStream), |
| Arguments.of("UnsynchronizedByteArrayOutputStream.toBufferedInputStream(InputStream, int)", unSyncBaosToBufferedInputStreamWithSize)); |
| } |
| |
| private void checkByteArrays(final byte[] expected, final byte[] actual) { |
| if (expected.length != actual.length) { |
| fail("Resulting byte arrays are not equally long"); |
| } |
| if (!byteCmp(expected, actual)) { |
| fail("Resulting byte arrays are not equal"); |
| } |
| } |
| |
| private void checkStreams(final AbstractByteArrayOutputStream actual, final java.io.ByteArrayOutputStream expected) { |
| assertEquals(expected.size(), actual.size(), "Sizes are not equal"); |
| final byte[] buf = actual.toByteArray(); |
| final byte[] refbuf = expected.toByteArray(); |
| checkByteArrays(buf, refbuf); |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testInvalidParameterizedConstruction(final String baosName, final BAOSFactory<?> baosFactory) { |
| assertThrows(IllegalArgumentException.class, () -> baosFactory.newInstance(-1)); |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testInvalidWriteLenUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { |
| assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, -1)); |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testInvalidWriteOffsetAndLenOver(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { |
| assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 0, 2)); |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testInvalidWriteOffsetAndLenUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { |
| assertThrows(IndexOutOfBoundsException.class, () -> baout.write(new byte[1], 1, -2)); |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testInvalidWriteOffsetOver(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { |
| assertThrows(IndexOutOfBoundsException.class, () -> baout.write(IOUtils.EMPTY_BYTE_ARRAY, 1, 0)); |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testInvalidWriteOffsetUnder(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { |
| assertThrows(IndexOutOfBoundsException.class, () -> baout.write(null, -1, 0)); |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testStream(final String baosName, final BAOSFactory<?> baosFactory) throws Exception { |
| int written; |
| |
| // The ByteArrayOutputStream is initialized with 32 bytes to match |
| // the original more closely for this test. |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(32); |
| final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { |
| |
| // First three writes |
| written = writeData(baout, ref, new int[] {4, 10, 22}); |
| assertEquals(36, written); |
| checkStreams(baout, ref); |
| |
| // Another two writes to see if there are any bad effects after toByteArray() |
| written = writeData(baout, ref, new int[] {20, 12}); |
| assertEquals(32, written); |
| checkStreams(baout, ref); |
| |
| // Now reset the streams |
| baout.reset(); |
| ref.reset(); |
| |
| // Test again to see if reset() had any bad effects |
| written = writeData(baout, ref, new int[] {5, 47, 33, 60, 1, 0, 8}); |
| assertEquals(155, written); |
| checkStreams(baout, ref); |
| |
| // Test the readFrom(InputStream) method |
| baout.reset(); |
| written = baout.write(new ByteArrayInputStream(ref.toByteArray())); |
| assertEquals(155, written); |
| checkStreams(baout, ref); |
| |
| // Write the commons Byte[]OutputStream to a java.io.Byte[]OutputStream |
| // and vice-versa to test the writeTo() method. |
| try (AbstractByteArrayOutputStream baout1 = baosFactory.newInstance(32)) { |
| ref.writeTo(baout1); |
| final java.io.ByteArrayOutputStream ref1 = new java.io.ByteArrayOutputStream(); |
| baout.writeTo(ref1); |
| checkStreams(baout1, ref1); |
| |
| // Testing toString(String) |
| final String baoutString = baout.toString("ASCII"); |
| final String refString = ref.toString("ASCII"); |
| assertEquals(refString, baoutString, "ASCII decoded String must be equal"); |
| |
| // Make sure that empty ByteArrayOutputStreams really don't create garbage |
| // on toByteArray() |
| try (AbstractByteArrayOutputStream baos1 = baosFactory.newInstance(); |
| final AbstractByteArrayOutputStream baos2 = baosFactory.newInstance()) { |
| assertSame(baos1.toByteArray(), baos2.toByteArray()); |
| } |
| } |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("toBufferedInputStreamFunctionFactories") |
| public void testToBufferedInputStream(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) throws IOException { |
| final byte[] data = {(byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE}; |
| |
| try (ByteArrayInputStream bain = new ByteArrayInputStream(data)) { |
| assertEquals(data.length, bain.available()); |
| |
| try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) { |
| assertEquals(data.length, buffered.available()); |
| |
| assertArrayEquals(data, IOUtils.toByteArray(buffered)); |
| |
| } |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("toBufferedInputStreamFunctionFactories") |
| public void testToBufferedInputStreamEmpty(final String baosName, final IOFunction<InputStream, InputStream> toBufferedInputStreamFunction) |
| throws IOException { |
| try (ByteArrayInputStream bain = new ByteArrayInputStream(IOUtils.EMPTY_BYTE_ARRAY)) { |
| assertEquals(0, bain.available()); |
| |
| try (InputStream buffered = toBufferedInputStreamFunction.apply(bain)) { |
| assertEquals(0, buffered.available()); |
| |
| } |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testToInputStream(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); |
| final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { |
| |
| // Write 8224 bytes |
| writeData(baout, ref, 32); |
| for (int i = 0; i < 128; i++) { |
| writeData(baout, ref, 64); |
| } |
| |
| // Get data before more writes |
| try (InputStream in = baout.toInputStream()) { |
| byte[] refData = ref.toByteArray(); |
| |
| // Write some more data |
| writeData(baout, ref, new int[] {2, 4, 8, 16}); |
| |
| // Check original data |
| byte[] baoutData = IOUtils.toByteArray(in); |
| assertEquals(8224, baoutData.length); |
| checkByteArrays(refData, baoutData); |
| |
| // Check all data written |
| try (InputStream in2 = baout.toInputStream()) { |
| baoutData = IOUtils.toByteArray(in2); |
| } |
| refData = ref.toByteArray(); |
| assertEquals(8254, baoutData.length); |
| checkByteArrays(refData, baoutData); |
| } |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testToInputStreamEmpty(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); |
| // Get data before more writes |
| final InputStream in = baout.toInputStream()) { |
| assertEquals(0, in.available()); |
| assertTrue(in instanceof ClosedInputStream); |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testToInputStreamWithReset(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| // Make sure reset() do not destroy InputStream returned from toInputStream() |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance(); |
| final java.io.ByteArrayOutputStream ref = new java.io.ByteArrayOutputStream()) { |
| |
| // Write 8224 bytes |
| writeData(baout, ref, 32); |
| for (int i = 0; i < 128; i++) { |
| writeData(baout, ref, 64); |
| } |
| |
| // Get data before reset |
| try (InputStream in = baout.toInputStream()) { |
| byte[] refData = ref.toByteArray(); |
| |
| // Reset and write some new data |
| baout.reset(); |
| ref.reset(); |
| writeData(baout, ref, new int[] {2, 4, 8, 16}); |
| |
| // Check original data |
| byte[] baoutData = IOUtils.toByteArray(in); |
| assertEquals(8224, baoutData.length); |
| checkByteArrays(refData, baoutData); |
| |
| // Check new data written after reset |
| try (InputStream in2 = baout.toInputStream()) { |
| baoutData = IOUtils.toByteArray(in2); |
| } |
| refData = ref.toByteArray(); |
| assertEquals(30, baoutData.length); |
| checkByteArrays(refData, baoutData); |
| } |
| } |
| } |
| |
| @ParameterizedTest(name = "[{index}] {0}") |
| @MethodSource("baosFactories") |
| public void testWriteZero(final String baosName, final BAOSFactory<?> baosFactory) throws IOException { |
| try (AbstractByteArrayOutputStream baout = baosFactory.newInstance()) { |
| baout.write(IOUtils.EMPTY_BYTE_ARRAY, 0, 0); |
| assertTrue(true, "Dummy"); |
| } |
| } |
| |
| private int writeData(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int count) { |
| if (count > DATA.length) { |
| throw new IllegalArgumentException("Requesting too many bytes"); |
| } |
| if (count == 0) { |
| baout.write(100); |
| ref.write(100); |
| return 1; |
| } |
| baout.write(DATA, 0, count); |
| ref.write(DATA, 0, count); |
| return count; |
| } |
| |
| private int writeData(final AbstractByteArrayOutputStream baout, final java.io.ByteArrayOutputStream ref, final int[] instructions) { |
| int written = 0; |
| for (final int instruction : instructions) { |
| written += writeData(baout, ref, instruction); |
| } |
| return written; |
| } |
| } |