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   * WITHOUProxyInputStreamFixture 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.input;
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.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertSame;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
28  import static org.junit.jupiter.api.Assertions.assertTrue;
29  import static org.mockito.Mockito.spy;
30  import static org.mockito.Mockito.verify;
31  
32  import java.io.ByteArrayInputStream;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.nio.charset.StandardCharsets;
36  import java.nio.file.Files;
37  import java.nio.file.Paths;
38  import java.util.Arrays;
39  import java.util.concurrent.atomic.AtomicBoolean;
40  
41  import org.apache.commons.io.IOUtils;
42  import org.apache.commons.io.build.AbstractStreamBuilder;
43  import org.apache.commons.io.test.CustomIOException;
44  import org.junit.jupiter.api.Test;
45  
46  /**
47   * Tests {@link ProxyInputStream}.
48   *
49   * @param <T> The actual type tested.
50   */
51  public class ProxyInputStreamTest<T extends ProxyInputStream> {
52  
53      private static final class ProxyInputStreamFixture extends ProxyInputStream {
54  
55          static class Builder extends ProxyInputStream.AbstractBuilder<ProxyInputStreamFixture, Builder> {
56  
57              @Override
58              public ProxyInputStreamFixture get() throws IOException {
59                  return new ProxyInputStreamFixture(this);
60              }
61  
62          }
63  
64          static Builder builder() {
65              return new Builder();
66          }
67  
68          ProxyInputStreamFixture(final Builder builder) throws IOException {
69              super(builder);
70          }
71  
72          ProxyInputStreamFixture(final InputStream proxy) {
73              super(proxy);
74          }
75      }
76  
77      @SuppressWarnings("resource")
78      static <T, B extends AbstractStreamBuilder<T, B>> void testCloseHandleIOException(final AbstractStreamBuilder<T, B> builder) throws IOException {
79          final IOException exception = new IOException();
80          testCloseHandleIOException((ProxyInputStream) builder.setInputStream(new BrokenInputStream(() -> exception)).get());
81      }
82  
83      @SuppressWarnings("resource")
84      static void testCloseHandleIOException(final ProxyInputStream inputStream) throws IOException {
85          assertFalse(inputStream.isClosed(), "closed");
86          final ProxyInputStream spy = spy(inputStream);
87          assertThrows(IOException.class, spy::close);
88          final BrokenInputStream unwrap = (BrokenInputStream) inputStream.unwrap();
89          verify(spy).handleIOException((IOException) unwrap.getThrowable());
90          assertFalse(spy.isClosed(), "closed");
91      }
92  
93      /**
94       * Asserts that a ProxyInputStream's markSupported() equals the proxied value.
95       *
96       * @param inputStream The stream to test.
97       */
98      @SuppressWarnings("resource") // unwrap() is a getter
99      protected void assertMarkSupportedEquals(final ProxyInputStream inputStream) {
100         assertNotNull(inputStream, "inputStream");
101         assertEquals(inputStream.unwrap().markSupported(), inputStream.markSupported());
102     }
103 
104     @SuppressWarnings({ "resource", "unused", "unchecked" }) // For subclasses
105     protected T createFixture() throws IOException {
106         return (T) new ProxyInputStreamFixture(createOriginInputStream());
107     }
108 
109     @SuppressWarnings("unchecked")
110     protected T createFixture(final InputStream proxy) {
111         return (T) new ProxyInputStreamFixture(proxy);
112     }
113 
114     protected InputStream createOriginInputStream() {
115         return CharSequenceInputStream.builder().setCharSequence("abc").get();
116     }
117 
118     @SuppressWarnings("resource")
119     @Test
120     void testAvailableAfterClose() throws IOException {
121         final T shadow;
122         try (T inputStream = createFixture()) {
123             shadow = inputStream;
124         }
125         assertEquals(0, shadow.available());
126     }
127 
128     @Test
129     void testAvailableAfterOpen() throws IOException {
130         try (T inputStream = createFixture()) {
131             assertEquals(3, inputStream.available());
132         }
133     }
134 
135     @Test
136     void testAvailableAll() throws IOException {
137         try (T inputStream = createFixture()) {
138             assertEquals(3, inputStream.available());
139             IOUtils.toByteArray(inputStream);
140             assertEquals(0, inputStream.available());
141         }
142     }
143 
144     @Test
145     void testAvailableNull() throws IOException {
146         try (T inputStream = createFixture(null)) {
147             assertEquals(0, inputStream.available());
148             inputStream.setReference(createFixture());
149             assertEquals(3, inputStream.available());
150             IOUtils.toByteArray(inputStream);
151             assertEquals(0, inputStream.available());
152             inputStream.setReference(null);
153             assertEquals(0, inputStream.available());
154         }
155     }
156 
157     protected void testEos(final T inputStream) {
158         // empty
159     }
160 
161     //@Test
162     void testMarkOnNull() throws IOException {
163         try (T inputStream = createFixture(null)) {
164             inputStream.mark(1);
165             inputStream.setReference(createFixture());
166             inputStream.mark(1);
167             IOUtils.toByteArray(inputStream);
168             inputStream.mark(1);
169             inputStream.setReference(null);
170             inputStream.mark(1);
171         }
172     }
173 
174     @Test
175     void testMarkSupported() throws IOException {
176         try (T inputStream = createFixture()) {
177             assertMarkSupportedEquals(inputStream);
178         }
179     }
180 
181     @SuppressWarnings("resource")
182     @Test
183     void testMarkSupportedAfterClose() throws IOException {
184         final T shadow;
185         try (T inputStream = createFixture()) {
186             shadow = inputStream;
187         }
188         assertMarkSupportedEquals(shadow);
189     }
190 
191     @Test
192     void testMarkSupportedOnNull() throws IOException {
193         try (ProxyInputStream fixture = createFixture()) {
194             assertMarkSupportedEquals(fixture);
195             fixture.setReference(null);
196             assertFalse(fixture.markSupported());
197         }
198     }
199 
200     @Test
201     void testRead() throws IOException {
202         try (T inputStream = createFixture()) {
203             int found = inputStream.read();
204             assertEquals('a', found);
205             found = inputStream.read();
206             assertEquals('b', found);
207             found = inputStream.read();
208             assertEquals('c', found);
209             found = inputStream.read();
210             assertEquals(-1, found);
211             testEos(inputStream);
212         }
213     }
214 
215     @Test
216     void testReadAfterClose_ByteArrayInputStream() throws IOException {
217         try (InputStream inputStream = new ProxyInputStreamFixture(new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)))) {
218             inputStream.close();
219             // ByteArrayInputStream does not throw on a closed stream.
220             assertNotEquals(IOUtils.EOF, inputStream.read());
221         }
222     }
223 
224     @Test
225     void testReadAfterClose_ChannelInputStream() throws IOException {
226         try (InputStream inputStream = new ProxyInputStreamFixture(
227                 Files.newInputStream(Paths.get("src/test/resources/org/apache/commons/io/abitmorethan16k.txt")))) {
228             inputStream.close();
229             // ChannelInputStream throws when closed
230             assertThrows(IOException.class, inputStream::read);
231         }
232     }
233 
234     @Test
235     void testReadAfterClose_CharSequenceInputStream() throws IOException {
236         try (InputStream inputStream = createFixture()) {
237             inputStream.close();
238             // CharSequenceInputStream (like ByteArrayInputStream) does not throw on a closed stream.
239             assertEquals(IOUtils.EOF, inputStream.read());
240         }
241     }
242 
243     @Test
244     void testReadArrayAtMiddleFully() throws IOException {
245         try (T inputStream = createFixture()) {
246             final byte[] dest = new byte[5];
247             int found = inputStream.read(dest, 2, 3);
248             assertEquals(3, found);
249             assertArrayEquals(new byte[] { 0, 0, 'a', 'b', 'c' }, dest);
250             found = inputStream.read(dest, 2, 3);
251             assertEquals(-1, found);
252             testEos(inputStream);
253         }
254     }
255 
256     @Test
257     void testReadArrayAtStartFully() throws IOException {
258         try (T inputStream = createFixture()) {
259             final byte[] dest = new byte[5];
260             int found = inputStream.read(dest, 0, 5);
261             assertEquals(3, found);
262             assertArrayEquals(new byte[] { 'a', 'b', 'c', 0, 0 }, dest);
263             found = inputStream.read(dest, 0, 5);
264             assertEquals(-1, found);
265             testEos(inputStream);
266         }
267     }
268 
269     @Test
270     void testReadArrayAtStartPartial() throws IOException {
271         try (T inputStream = createFixture()) {
272             final byte[] dest = new byte[5];
273             int found = inputStream.read(dest, 0, 2);
274             assertEquals(2, found);
275             assertArrayEquals(new byte[] { 'a', 'b', 0, 0, 0 }, dest);
276             Arrays.fill(dest, (byte) 0);
277             found = inputStream.read(dest, 0, 2);
278             assertEquals(1, found);
279             assertArrayEquals(new byte[] { 'c', 0, 0, 0, 0 }, dest);
280             found = inputStream.read(dest, 0, 2);
281             assertEquals(-1, found);
282             testEos(inputStream);
283         }
284     }
285 
286     @Test
287     void testReadArrayFully() throws IOException {
288         try (T inputStream = createFixture()) {
289             final byte[] dest = new byte[5];
290             int found = inputStream.read(dest);
291             assertEquals(3, found);
292             assertArrayEquals(new byte[] { 'a', 'b', 'c', 0, 0 }, dest);
293             found = inputStream.read(dest);
294             assertEquals(-1, found);
295             testEos(inputStream);
296         }
297     }
298 
299     @Test
300     void testReadArrayPartial() throws IOException {
301         try (T inputStream = createFixture()) {
302             final byte[] dest = new byte[2];
303             int found = inputStream.read(dest);
304             assertEquals(2, found);
305             assertArrayEquals(new byte[] { 'a', 'b' }, dest);
306             Arrays.fill(dest, (byte) 0);
307             found = inputStream.read(dest);
308             assertEquals(1, found);
309             assertArrayEquals(new byte[] { 'c', 0 }, dest);
310             found = inputStream.read(dest);
311             assertEquals(-1, found);
312             testEos(inputStream);
313         }
314     }
315 
316     @Test
317     void testReadEof() throws Exception {
318         final ByteArrayInputStream proxy = new ByteArrayInputStream(new byte[2]);
319         try (ProxyInputStream inputStream = new ProxyInputStreamFixture(proxy)) {
320             assertSame(proxy, inputStream.unwrap());
321             int found = inputStream.read();
322             assertEquals(0, found);
323             found = inputStream.read();
324             assertEquals(0, found);
325             found = inputStream.read();
326             assertEquals(-1, found);
327         }
328     }
329 
330     @Test
331     void testSubclassAfterReadConsumer() throws Exception {
332         final byte[] hello = "Hello".getBytes(StandardCharsets.UTF_8);
333         final AtomicBoolean boolRef = new AtomicBoolean();
334         // @formatter:off
335         try (ProxyInputStreamFixture bounded = ProxyInputStreamFixture.builder()
336                 .setInputStream(new ByteArrayInputStream(hello))
337                 .setAfterRead(null) // should not blow up
338                 .setAfterRead(i -> boolRef.set(true))
339                 .get()) {
340             IOUtils.consume(bounded);
341         }
342         // @formatter:on
343         assertTrue(boolRef.get());
344         // Throwing
345         final String message = "test exception message";
346         // @formatter:off
347         try (ProxyInputStreamFixture bounded = ProxyInputStreamFixture.builder()
348                 .setInputStream(new ByteArrayInputStream(hello))
349                 .setAfterRead(i -> {
350                     throw new CustomIOException(message);
351                 })
352                 .get()) {
353             assertEquals(message, assertThrowsExactly(CustomIOException.class, () -> IOUtils.consume(bounded)).getMessage());
354         }
355         // @formatter:on
356     }
357 
358 }