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    *     http://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.input;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  
32  import org.apache.commons.io.IOUtils;
33  import org.apache.commons.io.input.UnsynchronizedBufferedInputStream.Builder;
34  import org.apache.commons.lang3.StringUtils;
35  import org.junit.jupiter.api.AfterEach;
36  import org.junit.jupiter.api.BeforeEach;
37  import org.junit.jupiter.api.Test;
38  
39  /**
40   * Tests {@link UnsynchronizedBufferedInputStream}.
41   * <p>
42   * Provenance: Apache Harmony and modified.
43   * </p>
44   */
45  public class UnsynchronizedBufferedInputStreamTest {
46  
47      private static final int BUFFER_SIZE = 4096;
48  
49      public static final String DATA = StringUtils.repeat("This is a test.", 500);
50  
51      Path fileName;
52  
53      private UnsynchronizedBufferedInputStream is;
54  
55      private InputStream isFile;
56  
57      byte[] ibuf = new byte[BUFFER_SIZE];
58  
59      private Builder builder() {
60          return new UnsynchronizedBufferedInputStream.Builder();
61      }
62  
63      /**
64       * Sets up the fixture, for example, open a network connection. This method is called before a test is executed.
65       *
66       * @throws IOException Thrown on test failure.
67       */
68      @BeforeEach
69      protected void setUp() throws IOException {
70          fileName = Files.createTempFile(getClass().getSimpleName(), ".tst");
71          Files.write(fileName, DATA.getBytes(StandardCharsets.UTF_8));
72  
73          isFile = Files.newInputStream(fileName);
74          is = builder().setInputStream(isFile).get();
75      }
76  
77      /**
78       * Tears down the fixture, for example, close a network connection. This method is called after a test is executed.
79       *
80       * @throws IOException Thrown on test failure.
81       */
82      @AfterEach
83      protected void tearDown() throws IOException {
84          IOUtils.closeQuietly(is);
85          Files.deleteIfExists(fileName);
86      }
87  
88      /**
89       * Tests {@link UnsynchronizedBufferedInputStream#available()}.
90       *
91       * @throws IOException Thrown on test failure.
92       */
93      @Test
94      public void test_available() throws IOException {
95          assertEquals(DATA.length(), is.available(), "Returned incorrect number of available bytes");
96  
97          // Test that a closed stream throws an IOE for available()
98          final UnsynchronizedBufferedInputStream bis = builder()
99                  .setInputStream(new ByteArrayInputStream(new byte[] { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' })).get();
100         final int available = bis.available();
101         bis.close();
102         assertTrue(available != 0);
103 
104         assertThrows(IOException.class, () -> bis.available(), "Expected test to throw IOE.");
105     }
106 
107     /**
108      * Tests {@link UnsynchronizedBufferedInputStream#close()}.
109      *
110      * @throws IOException Thrown on test failure.
111      */
112     @Test
113     public void test_close() throws IOException {
114         builder().setInputStream(isFile).get().close();
115 
116         try (InputStream in = new InputStream() {
117             Object lock = new Object();
118 
119             @Override
120             public void close() {
121                 synchronized (lock) {
122                     lock.notifyAll();
123                 }
124             }
125 
126             @Override
127             public int read() {
128                 return 1;
129             }
130 
131             @Override
132             public int read(final byte[] buf, final int offset, final int length) {
133                 synchronized (lock) {
134                     try {
135                         lock.wait(3000);
136                     } catch (final InterruptedException e) {
137                         // Ignore
138                     }
139                 }
140                 return 1;
141             }
142         }) {
143             final UnsynchronizedBufferedInputStream bufin = builder().setInputStream(in).get();
144             final Thread thread = new Thread(() -> {
145                 try {
146                     Thread.sleep(1000);
147                     bufin.close();
148                 } catch (final Exception e) {
149                     // Ignored
150                 }
151             });
152             thread.start();
153             assertThrows(IOException.class, () -> bufin.read(new byte[100], 0, 99), "Should throw IOException");
154         }
155     }
156 
157     /*
158      * Tests {@link UnsynchronizedBufferedInputStream#Builder()}.
159      */
160     @Test
161     public void test_ConstructorLjava_io_InputStream() {
162         assertThrows(NullPointerException.class, () -> builder().setInputStream(null).get());
163     }
164 
165     /*
166      * Tests {@link UnsynchronizedBufferedInputStream#Builder()}.
167      */
168     @Test
169     public void test_ConstructorLjava_io_InputStreamI() throws IOException {
170         assertThrows(NullPointerException.class, () -> builder().setInputStream(null).setBufferSize(1).get());
171 
172         // Test for method UnsynchronizedBufferedInputStream(InputStream, int)
173 
174         // Create buffer with exact size of file
175         is = builder().setInputStream(isFile).setBufferSize(DATA.length()).get();
176         // Ensure buffer gets filled by evaluating one read
177         is.read();
178         // Close underlying FileInputStream, all but 1 buffered bytes should
179         // still be available.
180         isFile.close();
181         // Read the remaining buffered characters, no IOException should
182         // occur.
183         is.skip(DATA.length() - 2);
184         is.read();
185         // is.read should now throw an exception because it will have to be filled.
186         assertThrows(IOException.class, () -> is.read());
187 
188         assertThrows(NullPointerException.class, () -> builder().setInputStream(null).setBufferSize(100).get());
189         assertThrows(NullPointerException.class, () -> builder().setInputStream(null));
190     }
191 
192     /**
193      * Tests {@link UnsynchronizedBufferedInputStream#mark(int)}.
194      *
195      * @throws IOException Thrown on test failure.
196      */
197     @Test
198     public void test_markI() throws IOException {
199         final byte[] buf1 = new byte[100];
200         final byte[] buf2 = new byte[100];
201         is.skip(3000);
202         is.mark(1000);
203         is.read(buf1, 0, buf1.length);
204         is.reset();
205         is.read(buf2, 0, buf2.length);
206         is.reset();
207         assertTrue(new String(buf1, 0, buf1.length).equals(new String(buf2, 0, buf2.length)), "Failed to mark correct position");
208 
209         byte[] bytes = new byte[256];
210         for (int i = 0; i < 256; i++) {
211             bytes[i] = (byte) i;
212         }
213         InputStream in = builder().setInputStream(new ByteArrayInputStream(bytes)).setBufferSize(12).get();
214         in.skip(6);
215         in.mark(14);
216         in.read(new byte[14], 0, 14);
217         in.reset();
218         assertTrue(in.read() == 6 && in.read() == 7, "Wrong bytes");
219 
220         in = builder().setInputStream(new ByteArrayInputStream(bytes)).setBufferSize(12).get();
221         in.skip(6);
222         in.mark(8);
223         in.skip(7);
224         in.reset();
225         assertTrue(in.read() == 6 && in.read() == 7, "Wrong bytes 2");
226 
227         UnsynchronizedBufferedInputStream buf = builder().setInputStream(new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4 })).setBufferSize(2).get();
228         buf.mark(3);
229         bytes = new byte[3];
230         int result = buf.read(bytes);
231         assertEquals(3, result);
232         assertEquals(0, bytes[0], "Assert 0:");
233         assertEquals(1, bytes[1], "Assert 1:");
234         assertEquals(2, bytes[2], "Assert 2:");
235         assertEquals(3, buf.read(), "Assert 3:");
236 
237         buf = builder().setInputStream(new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4 })).setBufferSize(2).get();
238         buf.mark(3);
239         bytes = new byte[4];
240         result = buf.read(bytes);
241         assertEquals(4, result);
242         assertEquals(0, bytes[0], "Assert 4:");
243         assertEquals(1, bytes[1], "Assert 5:");
244         assertEquals(2, bytes[2], "Assert 6:");
245         assertEquals(3, bytes[3], "Assert 7:");
246         assertEquals(4, buf.read(), "Assert 8:");
247         assertEquals(-1, buf.read(), "Assert 9:");
248 
249         buf = builder().setInputStream(new ByteArrayInputStream(new byte[] { 0, 1, 2, 3, 4 })).setBufferSize(2).get();
250         buf.mark(Integer.MAX_VALUE);
251         buf.read();
252         buf.close();
253     }
254 
255     /**
256      * Tests {@link UnsynchronizedBufferedInputStream#markSupported()}.
257      */
258     @Test
259     public void test_markSupported() {
260         assertTrue(is.markSupported(), "markSupported returned incorrect value");
261     }
262 
263     /**
264      * Tests {@link UnsynchronizedBufferedInputStream#read()}.
265      *
266      * @throws IOException Thrown on test failure.
267      */
268     @Test
269     public void test_read() throws IOException {
270         final InputStreamReader isr = new InputStreamReader(is);
271         final int c = isr.read();
272         assertEquals(DATA.charAt(0), c, "read returned incorrect char");
273 
274         final byte[] bytes = new byte[256];
275         for (int i = 0; i < 256; i++) {
276             bytes[i] = (byte) i;
277         }
278         final InputStream in = builder().setInputStream(new ByteArrayInputStream(bytes)).setBufferSize(12).get();
279         assertEquals(0, in.read(), "Wrong initial byte"); // Fill the buffer
280         final byte[] buf = new byte[14];
281         in.read(buf, 0, 14); // Read greater than the buffer
282         assertTrue(new String(buf, 0, 14).equals(new String(bytes, 1, 14)), "Wrong block read data");
283         assertEquals(15, in.read(), "Wrong bytes"); // Check next byte
284     }
285 
286     /**
287      * Tests {@link UnsynchronizedBufferedInputStream#read(byte[], int, int)}.
288      *
289      * @throws IOException Thrown on test failure.
290      */
291     @Test
292     public void test_read$BII() throws IOException {
293         final byte[] buf1 = new byte[100];
294         is.skip(3000);
295         is.mark(1000);
296         is.read(buf1, 0, buf1.length);
297         assertTrue(new String(buf1, 0, buf1.length).equals(DATA.substring(3000, 3100)), "Failed to read correct data");
298 
299         try (UnsynchronizedBufferedInputStream bufin = builder().setInputStream(new InputStream() {
300             int size = 2, pos = 0;
301 
302             byte[] contents = new byte[size];
303 
304             @Override
305             public int available() {
306                 return size - pos;
307             }
308 
309             @Override
310             public int read() throws IOException {
311                 if (pos >= size) {
312                     throw new IOException("Read past end of data");
313                 }
314                 return contents[pos++];
315             }
316 
317             @Override
318             public int read(final byte[] buf, final int off, final int len) throws IOException {
319                 if (pos >= size) {
320                     throw new IOException("Read past end of data");
321                 }
322                 int toRead = len;
323                 if (toRead > available()) {
324                     toRead = available();
325                 }
326                 System.arraycopy(contents, pos, buf, off, toRead);
327                 pos += toRead;
328                 return toRead;
329             }
330         }).get()) {
331             bufin.read();
332             final int result = bufin.read(new byte[2], 0, 2);
333             assertEquals(1, result, () -> "Incorrect result: " + result);
334         }
335     }
336 
337     /**
338      * Tests {@link UnsynchronizedBufferedInputStream#reset()}.
339      *
340      * @throws IOException Thrown on test failure.
341      */
342     @Test
343     public void test_reset() throws IOException {
344         final byte[] buf1 = new byte[10];
345         final byte[] buf2 = new byte[10];
346         is.mark(2000);
347         is.read(buf1, 0, 10);
348         is.reset();
349         is.read(buf2, 0, 10);
350         is.reset();
351         assertTrue(new String(buf1, 0, buf1.length).equals(new String(buf2, 0, buf2.length)), "Reset failed");
352 
353         final UnsynchronizedBufferedInputStream bIn = builder().setInputStream(new ByteArrayInputStream("1234567890".getBytes())).get();
354         bIn.mark(10);
355         for (int i = 0; i < 11; i++) {
356             bIn.read();
357         }
358         bIn.reset();
359     }
360 
361     /**
362      * Tests {@link UnsynchronizedBufferedInputStream#reset()}.
363      *
364      * @throws IOException Thrown on test failure.
365      */
366     @Test
367     public void test_reset_scenario1() throws IOException {
368         final byte[] input = "12345678900".getBytes();
369         final UnsynchronizedBufferedInputStream bufin = builder().setInputStream(new ByteArrayInputStream(input)).get();
370         bufin.read();
371         bufin.mark(5);
372         bufin.skip(5);
373         bufin.reset();
374     }
375 
376     /**
377      * Tests {@link UnsynchronizedBufferedInputStream#reset()}.
378      *
379      * @throws IOException Thrown on test failure.
380      */
381     @Test
382     public void test_reset_scenario2() throws IOException {
383         final byte[] input = "12345678900".getBytes();
384         final UnsynchronizedBufferedInputStream bufin = builder().setInputStream(new ByteArrayInputStream(input)).get();
385         bufin.mark(5);
386         bufin.skip(6);
387         bufin.reset();
388     }
389 
390     /**
391      * Tests {@link UnsynchronizedBufferedInputStream#skip(long)}.
392      *
393      * @throws IOException Thrown on test failure.
394      */
395     @Test
396     public void test_skip_NullInputStream() throws IOException {
397         assertThrows(NullPointerException.class, () -> builder().setInputStream(null).setBufferSize(5).get());
398     }
399 
400     /**
401      * Tests {@link UnsynchronizedBufferedInputStream#skip(long)}.
402      *
403      * @throws IOException Thrown on test failure.
404      */
405     @Test
406     public void test_skipJ() throws IOException {
407         final byte[] buf1 = new byte[10];
408         is.mark(2000);
409         is.skip(1000);
410         is.read(buf1, 0, buf1.length);
411         is.reset();
412         assertTrue(new String(buf1, 0, buf1.length).equals(DATA.substring(1000, 1010)), "Failed to skip to correct position");
413     }
414 }