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.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  import java.util.concurrent.atomic.AtomicInteger;
30  
31  import org.junit.jupiter.api.Test;
32  
33  /**
34   * Tests {@link ThresholdingOutputStream}. See also the subclass {@link DeferredFileOutputStream}.
35   *
36   * @see DeferredFileOutputStream
37   */
38  class ThresholdingOutputStreamTest {
39  
40      /**
41       * Asserts initial state without changing it.
42       *
43       * @param out the stream to test.
44       * @param expectedThreshold the expected threshold.
45       * @param expectedByeCount the expected byte count.
46       */
47      static void assertThresholdingInitialState(final ThresholdingOutputStream out, final int expectedThreshold, final int expectedByeCount) {
48          assertFalse(out.isThresholdExceeded());
49          assertEquals(expectedThreshold, out.getThreshold());
50          assertEquals(expectedByeCount, out.getByteCount());
51      }
52  
53      @Test
54      void testResetByteCount() throws IOException {
55          final int threshold = 1;
56          final AtomicInteger counter = new AtomicInteger();
57          try (ByteArrayOutputStream os = new ByteArrayOutputStream(); ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, tos -> {
58              counter.incrementAndGet();
59              tos.resetByteCount();
60          }, o -> os)) {
61              assertThresholdingInitialState(out, threshold, 0);
62              assertEquals(0, counter.get());
63              out.write('a');
64              assertFalse(out.isThresholdExceeded());
65              out.write('a');
66              assertEquals(1, counter.get());
67              assertFalse(out.isThresholdExceeded());
68              out.write('a');
69              out.write('a');
70              assertEquals(3, counter.get());
71          }
72      }
73  
74      @Test
75      void testResetByteCountBrokenOutputStream() throws IOException {
76          final int threshold = 1;
77          final AtomicInteger counter = new AtomicInteger();
78          final IOException e = assertThrows(IOException.class, () -> {
79              try (ByteArrayOutputStream os = new ByteArrayOutputStream(); ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, tos -> {
80                  counter.incrementAndGet();
81                  tos.resetByteCount();
82              }, o -> BrokenOutputStream.INSTANCE)) {
83                  assertThresholdingInitialState(out, threshold, 0);
84                  assertEquals(0, counter.get());
85                  assertThrows(IOException.class, () -> out.write('a'));
86                  assertFalse(out.isThresholdExceeded());
87                  assertThrows(IOException.class, () -> out.write('a'));
88                  assertEquals(0, counter.get());
89                  assertFalse(out.isThresholdExceeded());
90                  assertThrows(IOException.class, () -> out.write('a'));
91                  assertThrows(IOException.class, () -> out.write('a'));
92                  assertEquals(0, counter.get());
93              }
94          });
95          // Should only happen on close
96          assertEquals("Broken output stream: close()", e.getMessage());
97      }
98  
99      @Test
100     void testSetByteCountOutputStream() throws IOException {
101         final AtomicBoolean reached = new AtomicBoolean();
102         final int initCount = 2;
103         final int threshold = 3;
104         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold) {
105             {
106                 setByteCount(initCount);
107             }
108 
109             @Override
110             protected OutputStream getOutputStream() throws IOException {
111                 return new ByteArrayOutputStream(4);
112             }
113 
114             @Override
115             protected void thresholdReached() throws IOException {
116                 reached.set(true);
117             }
118         }) {
119             assertThresholdingInitialState(out, threshold, initCount);
120             out.write('a');
121             assertFalse(reached.get());
122             assertFalse(out.isThresholdExceeded());
123             out.write('a');
124             assertTrue(reached.get());
125             assertTrue(out.isThresholdExceeded());
126         }
127     }
128 
129     @Test
130     void testSetByteCountStream() throws IOException {
131         final AtomicBoolean reached = new AtomicBoolean();
132         final int initCount = 2;
133         final int threshold = 3;
134         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold) {
135             {
136                 setByteCount(initCount);
137             }
138 
139             @Override
140             protected OutputStream getStream() throws IOException {
141                 return new ByteArrayOutputStream(4);
142             }
143 
144             @Override
145             protected void thresholdReached() throws IOException {
146                 reached.set(true);
147             }
148         }) {
149             assertThresholdingInitialState(out, threshold, initCount);
150             out.write('a');
151             assertFalse(reached.get());
152             assertFalse(out.isThresholdExceeded());
153             out.write('a');
154             assertTrue(reached.get());
155             assertTrue(out.isThresholdExceeded());
156         }
157     }
158 
159     @Test
160     void testThresholdIOConsumer() throws IOException {
161         final int threshold = 1;
162         // Null threshold consumer
163         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, null,
164             os -> new ByteArrayOutputStream(4))) {
165             assertThresholdingInitialState(out, threshold, 0);
166             out.write('a');
167             assertFalse(out.isThresholdExceeded());
168             out.write('a');
169             assertTrue(out.isThresholdExceeded());
170         }
171         // Null output stream function
172         final AtomicBoolean reached = new AtomicBoolean();
173         reached.set(false);
174         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, os -> reached.set(true), null)) {
175             assertThresholdingInitialState(out, threshold, 0);
176             out.write('a');
177             assertFalse(reached.get());
178             assertFalse(out.isThresholdExceeded());
179             out.write('a');
180             assertTrue(reached.get());
181             assertTrue(out.isThresholdExceeded());
182         }
183         // non-null inputs.
184         reached.set(false);
185         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, os -> reached.set(true),
186             os -> new ByteArrayOutputStream(4))) {
187             assertThresholdingInitialState(out, threshold, 0);
188             out.write('a');
189             assertFalse(reached.get());
190             assertFalse(out.isThresholdExceeded());
191             out.write('a');
192             assertTrue(reached.get());
193             assertTrue(out.isThresholdExceeded());
194         }
195     }
196 
197     @Test
198     void testThresholdIOConsumerIOException() throws IOException {
199         final int threshold = 1;
200         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, os -> {
201             throw new IOException("Threshold reached.");
202         }, os -> new ByteArrayOutputStream(4))) {
203             assertThresholdingInitialState(out, threshold, 0);
204             out.write('a');
205             assertFalse(out.isThresholdExceeded());
206             assertThrows(IOException.class, () -> out.write('a'));
207             assertFalse(out.isThresholdExceeded());
208         }
209     }
210 
211     @Test
212     void testThresholdIOConsumerUncheckedException() throws IOException {
213         final int threshold = 1;
214         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold, os -> {
215             throw new IllegalStateException("Threshold reached.");
216         }, os -> new ByteArrayOutputStream(4))) {
217             assertThresholdingInitialState(out, threshold, 0);
218             out.write('a');
219             assertFalse(out.isThresholdExceeded());
220             assertThrows(IllegalStateException.class, () -> out.write('a'));
221             assertFalse(out.isThresholdExceeded());
222             assertInstanceOf(ByteArrayOutputStream.class, out.getOutputStream());
223             assertFalse(out.isThresholdExceeded());
224         }
225     }
226 
227     /**
228      * Tests the case where the threshold is negative.
229      * The threshold is not reached until something is written to the stream.
230      */
231     @Test
232     void testThresholdLessThanZero() throws IOException {
233         final AtomicBoolean reached = new AtomicBoolean();
234         try (ThresholdingOutputStream out = new ThresholdingOutputStream(-1) {
235             @Override
236             protected void thresholdReached() throws IOException {
237                 reached.set(true);
238             }
239         }) {
240             assertThresholdingInitialState(out, 0, 0);
241             assertFalse(reached.get());
242             out.write(89);
243             assertTrue(reached.get());
244             assertTrue(out.isThresholdExceeded());
245             assertInstanceOf(NullOutputStream.class, out.getOutputStream());
246             assertTrue(out.isThresholdExceeded());
247         }
248     }
249 
250     @Test
251     void testThresholdZero() throws IOException {
252         final AtomicBoolean reached = new AtomicBoolean();
253         final int threshold = 0;
254         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold) {
255             @Override
256             protected void thresholdReached() throws IOException {
257                 reached.set(true);
258             }
259         }) {
260             assertThresholdingInitialState(out, threshold, 0);
261             out.write(89);
262             assertTrue(reached.get());
263             assertTrue(out.isThresholdExceeded());
264             assertInstanceOf(NullOutputStream.class, out.getOutputStream());
265             assertTrue(out.isThresholdExceeded());
266         }
267     }
268 
269     /**
270      * Tests the case where no bytes are written.
271      * The threshold is not reached until something is written to the stream.
272      */
273     @Test
274     void testThresholdZeroWrite() throws IOException {
275         final AtomicBoolean reached = new AtomicBoolean();
276         final int threshold = 7;
277         try (ThresholdingOutputStream out = new ThresholdingOutputStream(threshold) {
278             @Override
279             protected void thresholdReached() throws IOException {
280                 super.thresholdReached();
281                 reached.set(true);
282             }
283         }) {
284             assertThresholdingInitialState(out, threshold, 0);
285             assertFalse(reached.get());
286             out.write(new byte[0]);
287             assertFalse(out.isThresholdExceeded());
288             assertFalse(reached.get());
289             assertInstanceOf(NullOutputStream.class, out.getOutputStream());
290             assertFalse(out.isThresholdExceeded());
291         }
292     }
293 }