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  package org.apache.commons.io.output;
18  
19  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.nio.file.Files;
31  import java.nio.file.Path;
32  import java.util.stream.IntStream;
33  
34  import org.apache.commons.io.IOUtils;
35  import org.apache.commons.io.file.AbstractTempDirTest;
36  import org.junit.jupiter.api.Test;
37  import org.junit.jupiter.api.io.TempDir;
38  import org.junit.jupiter.params.ParameterizedTest;
39  import org.junit.jupiter.params.provider.MethodSource;
40  
41  /**
42   * Tests {@code DeferredFileOutputStream}.
43   */
44  public class DeferredFileOutputStreamTest extends AbstractTempDirTest {
45  
46      public static IntStream data() {
47          return IntStream.of(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096);
48      }
49  
50      /**
51       * The test data as a string (which is the simplest form).
52       */
53      private final String testString = "0123456789";
54  
55      /**
56       * The test data as a byte array, derived from the string.
57       */
58      private final byte[] testBytes = testString.getBytes();
59  
60      /**
61       * Tests the case where the amount of data exceeds the threshold, and is therefore written to disk. The actual data
62       * written to disk is verified, as is the file itself.
63       */
64      @ParameterizedTest(name = "initialBufferSize = {0}")
65      @MethodSource("data")
66      public void testAboveThreshold(final int initialBufferSize) throws IOException {
67          final File testFile = Files.createTempFile(tempDirPath, "testAboveThreshold", "dat").toFile();
68          try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
69                  .setThreshold(testBytes.length - 5)
70                  .setBufferSize(initialBufferSize)
71                  .setOutputFile(testFile)
72                  .get()) {
73              dfos.write(testBytes, 0, testBytes.length);
74              dfos.close();
75              assertFalse(dfos.isInMemory());
76              assertNull(dfos.getData());
77              assertEquals(testFile.length(), dfos.getByteCount());
78              verifyResultFile(testFile);
79          }
80      }
81  
82      /**
83       * Tests the case where the amount of data exceeds the threshold, and is therefore written to disk. The actual data
84       * written to disk is verified, as is the file itself.
85       * Testing the getInputStream() method.
86       */
87      @ParameterizedTest(name = "initialBufferSize = {0}")
88      @MethodSource("data")
89      public void testAboveThresholdGetInputStream(final int initialBufferSize, final @TempDir Path tempDir) throws IOException {
90          final File testFile = Files.createTempFile(tempDirPath, "testAboveThreshold", "dat").toFile();
91          try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
92                  .setThreshold(testBytes.length - 5)
93                  .setBufferSize(initialBufferSize)
94                  .setOutputFile(testFile)
95                  .get()) {
96              dfos.write(testBytes, 0, testBytes.length);
97              dfos.close();
98              assertFalse(dfos.isInMemory());
99              assertEquals(testFile.length(), dfos.getByteCount());
100             try (InputStream is = dfos.toInputStream()) {
101                 assertArrayEquals(testBytes, IOUtils.toByteArray(is));
102             }
103             verifyResultFile(testFile);
104         }
105     }
106 
107     /**
108      * Tests the case where the amount of data is exactly the same as the threshold. The behavior should be the same as
109      * that for the amount of data being below (i.e. not exceeding) the threshold.
110      */
111     @ParameterizedTest(name = "initialBufferSize = {0}")
112     @MethodSource("data")
113     public void testAtThreshold(final int initialBufferSize) throws IOException {
114         try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
115             // @formatter:off
116                 .setThreshold(testBytes.length)
117                 .setBufferSize(initialBufferSize)
118                 .get()) {
119             // @formatter:on
120             dfos.write(testBytes, 0, testBytes.length);
121             dfos.close();
122             assertTrue(dfos.isInMemory());
123             assertEquals(testBytes.length, dfos.getByteCount());
124             final byte[] resultBytes = dfos.getData();
125             assertEquals(testBytes.length, resultBytes.length);
126             assertArrayEquals(resultBytes, testBytes);
127         }
128     }
129 
130     /**
131      * Tests the case where the amount of data falls below the threshold, and is therefore confined to memory.
132      * @throws IOException
133      */
134     @ParameterizedTest(name = "initialBufferSize = {0}")
135     @MethodSource("data")
136     public void testBelowThreshold(final int initialBufferSize) throws IOException {
137         try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
138             // @formatter:off
139                 .setThreshold(testBytes.length + 42)
140                 .setBufferSize(initialBufferSize)
141                 .get()) {
142             // @formatter:on
143             dfos.write(testBytes, 0, testBytes.length);
144             dfos.close();
145             assertTrue(dfos.isInMemory());
146             assertEquals(testBytes.length, dfos.getByteCount());
147             final byte[] resultBytes = dfos.getData();
148             assertEquals(testBytes.length, resultBytes.length);
149             assertArrayEquals(resultBytes, testBytes);
150         }
151     }
152 
153     /**
154      * Tests the case where the amount of data falls below the threshold, and is therefore confined to memory.
155      * Testing the getInputStream() method.
156      */
157     @ParameterizedTest(name = "initialBufferSize = {0}")
158     @MethodSource("data")
159     public void testBelowThresholdGetInputStream(final int initialBufferSize) throws IOException {
160         // @formatter:off
161         try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
162                 .setThreshold(testBytes.length + 42)
163                 .setBufferSize(initialBufferSize)
164                 .get()) {
165         // @formatter:on
166             dfos.write(testBytes, 0, testBytes.length);
167             dfos.close();
168             assertTrue(dfos.isInMemory());
169             assertEquals(testBytes.length, dfos.getByteCount());
170             try (InputStream is = dfos.toInputStream()) {
171                 assertArrayEquals(testBytes, IOUtils.toByteArray(is));
172             }
173         }
174     }
175 
176     /**
177      * Tests specifying a temporary file and the threshold is reached.
178      */
179     @ParameterizedTest(name = "initialBufferSize = {0}")
180     @MethodSource("data")
181     public void testTempFileAboveThreshold(final int initialBufferSize) throws IOException {
182         final String prefix = "commons-io-test";
183         final String suffix = ".out";
184         // @formatter:off
185         try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
186                 .setThreshold(testBytes.length - 5)
187                 .setBufferSize(initialBufferSize)
188                 .setPrefix(prefix)
189                 .setSuffix(suffix)
190                 .setDirectory(tempDirFile)
191                 .setDirectory(tempDirPath.toFile())
192                 .get()) {
193         // @formatter:on
194             assertNull(dfos.getFile(), "Check File is null-A");
195             assertNull(dfos.getPath(), "Check Path is null-A");
196             dfos.write(testBytes, 0, testBytes.length);
197             dfos.close();
198             assertFalse(dfos.isInMemory());
199             assertEquals(testBytes.length, dfos.getByteCount());
200             assertNull(dfos.getData());
201             assertNotNull(dfos.getFile(), "Check file not null");
202             assertTrue(dfos.getFile().exists(), "Check file exists");
203             assertTrue(dfos.getFile().getName().startsWith(prefix), "Check prefix");
204             assertTrue(dfos.getFile().getName().endsWith(suffix), "Check suffix");
205             assertEquals(tempDirPath, dfos.getPath().getParent(), "Check dir");
206             verifyResultFile(dfos.getFile());
207         }
208     }
209 
210     /**
211      * Tests specifying a temporary file and the threshold is reached.
212      * @throws IOException
213      */
214     @ParameterizedTest(name = "initialBufferSize = {0}")
215     @MethodSource("data")
216     public void testTempFileAboveThresholdPrefixOnly(final int initialBufferSize) throws IOException {
217         final String prefix = "commons-io-test";
218         final String suffix = null;
219         try (final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
220             // @formatter:off
221                 .setThreshold(testBytes.length - 5)
222                 .setBufferSize(initialBufferSize)
223                 .setPrefix(prefix)
224                 .setSuffix(suffix)
225                 .setDirectory((Path) null)
226                 .get()) {
227             // @formatter:on
228             try {
229                 assertNull(dfos.getFile(), "Check File is null-A");
230                 assertNull(dfos.getPath(), "Check Path is null-A");
231                 dfos.write(testBytes, 0, testBytes.length);
232                 dfos.close();
233                 assertFalse(dfos.isInMemory());
234                 assertNull(dfos.getData());
235                 assertEquals(testBytes.length, dfos.getByteCount());
236                 assertNotNull(dfos.getFile(), "Check file not null");
237                 assertTrue(dfos.getFile().exists(), "Check file exists");
238                 assertTrue(dfos.getFile().getName().startsWith(prefix), "Check prefix");
239                 assertTrue(dfos.getFile().getName().endsWith(".tmp"), "Check suffix"); // ".tmp" is default
240                 verifyResultFile(dfos.getFile());
241             } finally {
242                 // Delete the temporary file.
243                 dfos.getFile().delete();
244             }
245         }
246     }
247 
248     /**
249      * Tests specifying a temporary file and the threshold not reached.
250      * @throws IOException
251      */
252     @ParameterizedTest(name = "initialBufferSize = {0}")
253     @MethodSource("data")
254     public void testTempFileBelowThreshold(final int initialBufferSize) throws IOException {
255         final String prefix = "commons-io-test";
256         final String suffix = ".out";
257         try (final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length + 42, initialBufferSize, prefix, suffix, tempDirFile)) {
258             assertNull(dfos.getFile(), "Check File is null-A");
259             assertNull(dfos.getPath(), "Check Path is null-A");
260             dfos.write(testBytes, 0, testBytes.length);
261             dfos.close();
262             assertTrue(dfos.isInMemory());
263             assertEquals(testBytes.length, dfos.getByteCount());
264             assertNull(dfos.getFile(), "Check file is null-B");
265         }
266     }
267 
268     /**
269      * Tests specifying a temporary file and the threshold is reached.
270      *
271      * @throws Exception
272      */
273     @Test
274     public void testTempFileError() throws Exception {
275         final String prefix = null;
276         final String suffix = ".out";
277         assertThrows(NullPointerException.class, () -> new DeferredFileOutputStream(testBytes.length - 5, prefix, suffix, tempDirFile));
278     }
279 
280     /**
281      * Tests the case where the threshold is negative, and therefore the data is always written to disk. The actual data
282      * written to disk is verified, as is the file itself.
283      */
284     @ParameterizedTest(name = "initialBufferSize = {0}")
285     @MethodSource("data")
286     public void testThresholdNegative(final int initialBufferSize) throws IOException {
287         final File testFile = Files.createTempFile(tempDirPath, "testThresholdNegative", "dat").toFile();
288         try (DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
289                 .setThreshold(-1)
290                 .setBufferSize(initialBufferSize)
291                 .setOutputFile(testFile)
292                 .get()) {
293             dfos.write(testBytes, 0, testBytes.length);
294             dfos.close();
295             assertFalse(dfos.isInMemory());
296             assertNull(dfos.getData());
297             assertEquals(testFile.length(), dfos.getByteCount());
298             verifyResultFile(testFile);
299         }
300     }
301 
302     /**
303      * Tests the case where there are multiple writes beyond the threshold, to ensure that the
304      * {@code thresholdReached()} method is only called once, as the threshold is crossed for the first time.
305      * @throws IOException
306      */
307     @ParameterizedTest(name = "initialBufferSize = {0}")
308     @MethodSource("data")
309     public void testThresholdReached(final int initialBufferSize) throws IOException {
310         final File testFile = Files.createTempFile(tempDirPath, "testThresholdReached", "dat").toFile();
311         try (final DeferredFileOutputStream dfos = DeferredFileOutputStream.builder()
312             // @formatter:off
313                 .setThreshold(testBytes.length /2)
314                 .setBufferSize(initialBufferSize)
315                 .setOutputFile(testFile)
316                 .get()) {
317             // @formatter:on
318             final int chunkSize = testBytes.length / 3;
319             dfos.write(testBytes, 0, chunkSize);
320             dfos.write(testBytes, chunkSize, chunkSize);
321             dfos.write(testBytes, chunkSize * 2, testBytes.length - chunkSize * 2);
322             dfos.close();
323             assertFalse(dfos.isInMemory());
324             assertNull(dfos.getData());
325             assertEquals(testBytes.length, dfos.getByteCount());
326             verifyResultFile(testFile);
327         }
328     }
329 
330     /**
331      * Tests whether writeTo() properly writes large content.
332      */
333     @ParameterizedTest(name = "initialBufferSize = {0}")
334     @MethodSource("data")
335     public void testWriteToLarge(final int initialBufferSize) throws IOException {
336         final File testFile = Files.createTempFile(tempDirPath, "testWriteToFile", "dat").toFile();
337         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(initialBufferSize);
338                 DeferredFileOutputStream dfos = DeferredFileOutputStream.builder().setThreshold(testBytes.length / 2).setOutputFile(testFile).get()) {
339             dfos.write(testBytes);
340 
341             assertTrue(testFile.exists());
342             assertFalse(dfos.isInMemory());
343             assertEquals(testBytes.length, dfos.getByteCount());
344 
345             assertThrows(IOException.class, () -> dfos.writeTo(baos));
346 
347             dfos.close();
348             dfos.writeTo(baos);
349             final byte[] copiedBytes = baos.toByteArray();
350             assertArrayEquals(testBytes, copiedBytes);
351             verifyResultFile(testFile);
352         }
353     }
354 
355     /**
356      * Tests whether writeTo() properly writes large content.
357      */
358     @ParameterizedTest(name = "initialBufferSize = {0}")
359     @MethodSource("data")
360     public void testWriteToLargeCtor(final int initialBufferSize) throws IOException {
361         final File testFile = Files.createTempFile(tempDirPath, "testWriteToFile", "dat").toFile();
362         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(initialBufferSize);
363                 DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length / 2, testFile)) {
364             dfos.write(testBytes);
365 
366             assertTrue(testFile.exists());
367             assertFalse(dfos.isInMemory());
368 
369             assertThrows(IOException.class, () -> dfos.writeTo(baos));
370             assertEquals(testBytes.length, dfos.getByteCount());
371 
372             dfos.close();
373             dfos.writeTo(baos);
374             final byte[] copiedBytes = baos.toByteArray();
375             assertArrayEquals(testBytes, copiedBytes);
376             verifyResultFile(testFile);
377         }
378     }
379 
380     /**
381      * Tests whether writeTo() properly writes small content.
382      * @throws IOException
383      */
384     @ParameterizedTest(name = "initialBufferSize = {0}")
385     @MethodSource("data")
386     public void testWriteToSmall(final int initialBufferSize) throws IOException {
387         final File testFile = Files.createTempFile(tempDirPath, "testWriteToMem", "dat").toFile();
388         try (ByteArrayOutputStream baos = new ByteArrayOutputStream(initialBufferSize);
389                 final DeferredFileOutputStream dfos = new DeferredFileOutputStream(testBytes.length * 2, initialBufferSize, testFile)) {
390             dfos.write(testBytes);
391 
392             assertTrue(dfos.isInMemory());
393 
394             assertThrows(IOException.class, () -> dfos.writeTo(baos));
395             assertEquals(testBytes.length, dfos.getByteCount());
396 
397             dfos.close();
398             dfos.writeTo(baos);
399             final byte[] copiedBytes = baos.toByteArray();
400             assertArrayEquals(testBytes, copiedBytes);
401         }
402     }
403 
404     /**
405      * Verifies that the specified file contains the same data as the original test data.
406      *
407      * @param testFile The file containing the test output.
408      */
409     private void verifyResultFile(final File testFile) throws IOException {
410         try (final InputStream fis = Files.newInputStream(testFile.toPath())) {
411             assertEquals(testBytes.length, fis.available());
412 
413             final byte[] resultBytes = new byte[testBytes.length];
414             assertEquals(testBytes.length, fis.read(resultBytes));
415 
416             assertArrayEquals(resultBytes, testBytes);
417             assertEquals(-1, fis.read(resultBytes));
418         }
419     }
420 }