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.function;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  
29  import java.io.IOException;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.NoSuchElementException;
33  import java.util.concurrent.atomic.AtomicInteger;
34  import java.util.concurrent.atomic.AtomicReference;
35  import java.util.stream.Collectors;
36  import java.util.stream.DoubleStream;
37  import java.util.stream.IntStream;
38  import java.util.stream.LongStream;
39  import java.util.stream.Stream;
40  
41  import org.apache.commons.lang3.JavaVersion;
42  import org.apache.commons.lang3.SystemUtils;
43  import org.junit.jupiter.api.Test;
44  
45  /**
46   * Tests {@link IOStream}.
47   */
48  class IOStreamTest {
49  
50      private static final boolean AT_LEAST_JAVA_11 = SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11);
51      private static final boolean AT_LEAST_JAVA_17 = SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_17);
52  
53      private void compareAndSetIO(final AtomicReference<String> ref, final String expected, final String update) throws IOException {
54          TestUtils.compareAndSetThrowsIO(ref, expected, update);
55      }
56  
57      private void compareAndSetRE(final AtomicReference<String> ref, final String expected, final String update) {
58          TestUtils.compareAndSetThrowsRE(ref, expected, update);
59      }
60  
61      private void ioExceptionOnNull(final Object test) throws IOException {
62          if (test == null) {
63              throw new IOException("Unexpected");
64          }
65      }
66  
67      @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
68      @Test
69      void testAdapt() {
70          assertEquals(0, IOStream.adapt((Stream<?>) null).count());
71          assertEquals(0, IOStream.adapt(Stream.empty()).count());
72          assertEquals(1, IOStream.adapt(Stream.of("A")).count());
73      }
74  
75      @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
76      @Test
77      void testAdaptAsParallel() {
78          assertEquals(0, IOStream.adapt((Stream<?>) null).parallel().count());
79          assertEquals(0, IOStream.adapt(Stream.empty()).parallel().count());
80          assertEquals(1, IOStream.adapt(Stream.of("A")).parallel().count());
81      }
82  
83      @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
84      @Test
85      void testAdaptParallelAndCount() {
86          final IOStream<Object> adaptedObj = IOStream.adapt(Stream.empty().parallel());
87          assertTrue(adaptedObj.isParallel());
88          assertEquals(0, adaptedObj.count());
89          final IOStream<String> adaptedStr = IOStream.adapt(Stream.of("A").parallel());
90          assertTrue(adaptedObj.isParallel());
91          assertEquals(1, adaptedStr.count());
92      }
93  
94      @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
95      @Test
96      void testAllMatch() throws IOException {
97          assertThrows(IOException.class, () -> IOStream.of("A", "B").allMatch(TestConstants.THROWING_IO_PREDICATE));
98          assertTrue(IOStream.of("A", "B").allMatch(IOPredicate.alwaysTrue()));
99          assertFalse(IOStream.of("A", "B").allMatch(IOPredicate.alwaysFalse()));
100     }
101 
102     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
103     @Test
104     void testAnyMatch() throws IOException {
105         assertThrows(IOException.class, () -> IOStream.of("A", "B").anyMatch(TestConstants.THROWING_IO_PREDICATE));
106         assertTrue(IOStream.of("A", "B").anyMatch(IOPredicate.alwaysTrue()));
107         assertFalse(IOStream.of("A", "B").anyMatch(IOPredicate.alwaysFalse()));
108     }
109 
110     @Test
111     void testClose() {
112         IOStream.of("A", "B").close();
113     }
114 
115     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
116     @Test
117     void testCollectCollectorOfQsuperTAR() {
118         // TODO IOCollector?
119         IOStream.of("A", "B").collect(Collectors.toList());
120     }
121 
122     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
123     @Test
124     void testCollectSupplierOfRBiConsumerOfRQsuperTBiConsumerOfRR() throws IOException {
125         // TODO Need an IOCollector?
126         IOStream.of("A", "B").collect(() -> "A", (t, u) -> {
127         }, (t, u) -> {
128         });
129         assertEquals("AB", Stream.of("A", "B").collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString());
130         assertEquals("AB", IOStream.of("A", "B").collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString());
131         // Exceptions
132         assertThrows(IOException.class, () -> IOStream.of("A", "B").collect(TestUtils.throwingIOSupplier(), (t, u) -> {
133         }, (t, u) -> {
134         }));
135         assertThrows(IOException.class, () -> IOStream.of("A", "B").collect(() -> "A", TestUtils.throwingIOBiConsumer(), (t, u) -> {
136         }));
137     }
138 
139     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
140     @Test
141     void testCount() {
142         assertEquals(0, IOStream.of().count());
143         assertEquals(1, IOStream.of("A").count());
144         assertEquals(2, IOStream.of("A", "B").count());
145         assertEquals(3, IOStream.of("A", "B", "C").count());
146         assertEquals(3, IOStream.of("A", "A", "A").count());
147     }
148 
149     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
150     @Test
151     void testDistinct() {
152         assertEquals(0, IOStream.of().distinct().count());
153         assertEquals(1, IOStream.of("A").distinct().count());
154         assertEquals(2, IOStream.of("A", "B").distinct().count());
155         assertEquals(3, IOStream.of("A", "B", "C").distinct().count());
156         assertEquals(1, IOStream.of("A", "A", "A").distinct().count());
157     }
158 
159     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
160     @Test
161     void testEmpty() throws IOException {
162         assertEquals(0, Stream.empty().count());
163         assertEquals(0, IOStream.empty().count());
164         IOStream.empty().forEach(TestUtils.throwingIOConsumer());
165     }
166 
167     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
168     @Test
169     void testFilter() throws IOException {
170         IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE);
171         // compile vs type
172         assertThrows(IOException.class, () -> IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE).count());
173         // compile vs inline lambda
174         assertThrows(IOException.class, () -> IOStream.of("A").filter(e -> {
175             throw new IOException("Failure");
176         }).count());
177     }
178 
179     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
180     @Test
181     void testFindAny() throws IOException {
182         // compile vs type
183         assertThrows(IOException.class, () -> IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE).findAny());
184         // compile vs inline lambda
185         assertThrows(IOException.class, () -> IOStream.of("A").filter(e -> {
186             throw new IOException("Failure");
187         }).findAny());
188 
189         assertTrue(IOStream.of("A", "B").filter(IOPredicate.alwaysTrue()).findAny().isPresent());
190         assertFalse(IOStream.of("A", "B").filter(IOPredicate.alwaysFalse()).findAny().isPresent());
191     }
192 
193     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
194     @Test
195     void testFindFirst() throws IOException {
196         // compile vs type
197         assertThrows(IOException.class, () -> IOStream.of("A").filter(TestConstants.THROWING_IO_PREDICATE).findFirst());
198         // compile vs inline lambda
199         assertThrows(IOException.class, () -> IOStream.of("A").filter(e -> {
200             throw new IOException("Failure");
201         }).findAny());
202 
203         assertTrue(IOStream.of("A", "B").filter(IOPredicate.alwaysTrue()).findFirst().isPresent());
204         assertFalse(IOStream.of("A", "B").filter(IOPredicate.alwaysFalse()).findFirst().isPresent());
205     }
206 
207     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
208     @Test
209     void testFlatMap() throws IOException {
210         assertEquals(Arrays.asList("A", "B", "C", "D"),
211                 IOStream.of(IOStream.of("A", "B"), IOStream.of("C", "D")).flatMap(IOFunction.identity()).collect(Collectors.toList()));
212     }
213 
214     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
215     @Test
216     void testFlatMapToDouble() throws IOException {
217         assertEquals('A' + 'B', IOStream.of("A", "B").flatMapToDouble(e -> DoubleStream.of(e.charAt(0))).sum());
218     }
219 
220     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
221     @Test
222     void testFlatMapToInt() throws IOException {
223         assertEquals('A' + 'B', IOStream.of("A", "B").flatMapToInt(e -> IntStream.of(e.charAt(0))).sum());
224     }
225 
226     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
227     @Test
228     void testFlatMapToLong() throws IOException {
229         assertEquals('A' + 'B', IOStream.of("A", "B").flatMapToLong(e -> LongStream.of(e.charAt(0))).sum());
230     }
231 
232     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
233     @Test
234     void testForaAllIOConsumer() throws IOException {
235         // compile vs type
236         assertThrows(IOException.class, () -> IOStream.of("A").forAll(TestUtils.throwingIOConsumer()));
237         // compile vs inline
238         assertThrows(IOException.class, () -> IOStream.of("A").forAll(e -> {
239             throw new IOException("Failure");
240         }));
241         assertThrows(IOException.class, () -> IOStream.of("A", "B").forAll(TestUtils.throwingIOConsumer()));
242         final StringBuilder sb = new StringBuilder();
243         IOStream.of("A", "B").forAll(sb::append);
244         assertEquals("AB", sb.toString());
245     }
246 
247     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
248     @Test
249     void testForaAllIOConsumerBiFunction() throws IOException {
250         // compile vs type
251         assertThrows(IOException.class, () -> IOStream.of("A").forAll(TestUtils.throwingIOConsumer(), (i, e) -> e));
252         // compile vs inline
253         assertThrows(IOException.class, () -> IOStream.of("A").forAll(e -> {
254             throw new IOException("Failure");
255         }, (i, e) -> e));
256         assertThrows(IOException.class, () -> IOStream.of("A", "B").forAll(TestUtils.throwingIOConsumer(), (i, e) -> e));
257         final StringBuilder sb = new StringBuilder();
258         IOStream.of("A", "B").forAll(sb::append, (i, e) -> e);
259         assertEquals("AB", sb.toString());
260     }
261 
262     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
263     @Test
264     void testForaAllIOConsumerBiFunctionNull() throws IOException {
265         // compile vs type
266         assertDoesNotThrow(() -> IOStream.of("A").forAll(TestUtils.throwingIOConsumer(), null));
267         // compile vs inline
268         assertDoesNotThrow(() -> IOStream.of("A").forAll(e -> {
269             throw new IOException("Failure");
270         }, null));
271         assertDoesNotThrow(() -> IOStream.of("A", "B").forAll(TestUtils.throwingIOConsumer(), null));
272         final StringBuilder sb = new StringBuilder();
273         IOStream.of("A", "B").forAll(sb::append, null);
274         assertEquals("AB", sb.toString());
275     }
276 
277     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
278     @Test
279     void testForEachIOConsumerOfQsuperT() throws IOException {
280         // compile vs type
281         assertThrows(IOException.class, () -> IOStream.of("A").forEach(TestUtils.throwingIOConsumer()));
282         // compile vs inline
283         assertThrows(IOException.class, () -> IOStream.of("A").forEach(e -> {
284             throw new IOException("Failure");
285         }));
286         assertThrows(IOException.class, () -> IOStream.of("A", "B").forEach(TestUtils.throwingIOConsumer()));
287         final StringBuilder sb = new StringBuilder();
288         IOStream.of("A", "B").forEachOrdered(sb::append);
289         assertEquals("AB", sb.toString());
290     }
291 
292     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
293     @Test
294     void testForEachOrdered() throws IOException {
295         // compile vs type
296         assertThrows(IOException.class, () -> IOStream.of("A").forEach(TestUtils.throwingIOConsumer()));
297         // compile vs inline
298         assertThrows(IOException.class, () -> IOStream.of("A").forEach(e -> {
299             throw new IOException("Failure");
300         }));
301         assertThrows(IOException.class, () -> IOStream.of("A", "B").forEach(TestUtils.throwingIOConsumer()));
302         final StringBuilder sb = new StringBuilder();
303         IOStream.of("A", "B").forEachOrdered(sb::append);
304         assertEquals("AB", sb.toString());
305     }
306 
307     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
308     @Test
309     void testForEachOrderedAdaptParallel() throws IOException {
310         // compile vs type
311         assertThrows(IOException.class, () -> IOStream.adapt(Stream.of("A").parallel()).forEach(TestUtils.throwingIOConsumer()));
312         // compile vs inline
313         assertThrows(IOException.class, () -> IOStream.adapt(Stream.of("A").parallel()).forEach(e -> {
314             throw new IOException("Failure");
315         }));
316         assertThrows(IOException.class, () -> IOStream.adapt(Stream.of("A", "B").parallel()).forEach(TestUtils.throwingIOConsumer()));
317         final StringBuilder sb = new StringBuilder();
318         IOStream.adapt(Stream.of("A", "B").parallel()).forEachOrdered(sb::append);
319         assertEquals("AB", sb.toString());
320         sb.setLength(0);
321         IOStream.adapt(Stream.of("A", "B", "C").parallel()).forEachOrdered(sb::append);
322         assertEquals("ABC", sb.toString());
323         sb.setLength(0);
324         IOStream.adapt(Stream.of("A", "B", "C", "D").parallel()).forEachOrdered(sb::append);
325         assertEquals("ABCD", sb.toString());
326     }
327 
328     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
329     @Test
330     void testForEachOrderedAsParallel() throws IOException {
331         // compile vs type
332         assertThrows(IOException.class, () -> IOStream.of("A").parallel().forEach(TestUtils.throwingIOConsumer()));
333         // compile vs inline
334         assertThrows(IOException.class, () -> IOStream.of("A").parallel().forEach(e -> {
335             throw new IOException("Failure");
336         }));
337         assertThrows(IOException.class, () -> IOStream.of("A", "B").parallel().forEach(TestUtils.throwingIOConsumer()));
338         final StringBuilder sb = new StringBuilder();
339         IOStream.of("A", "B").parallel().forEachOrdered(sb::append);
340         assertEquals("AB", sb.toString());
341     }
342 
343     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
344     @Test
345     void testIsParallel() {
346         assertFalse(IOStream.of("A", "B").isParallel());
347         assertTrue(IOStream.of("A", "B").parallel().isParallel());
348     }
349 
350     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
351     @Test
352     void testIterateException() throws IOException {
353         final IOStream<Long> stream = IOStream.iterate(1L, TestUtils.throwingIOUnaryOperator());
354         final IOIterator<Long> iterator = stream.iterator();
355         assertEquals(1L, iterator.next());
356         assertThrows(NoSuchElementException.class, () -> iterator.next());
357     }
358 
359     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
360     @Test
361     void testIterateLong() throws IOException {
362         final IOStream<Long> stream = IOStream.iterate(1L, i -> i + 1);
363         final IOIterator<Long> iterator = stream.iterator();
364         assertEquals(1L, iterator.next());
365         assertEquals(2L, iterator.next());
366     }
367 
368     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
369     @Test
370     void testIterator() throws IOException {
371         final AtomicInteger ref = new AtomicInteger();
372         IOStream.of("A", "B").iterator().forEachRemaining(e -> ref.incrementAndGet());
373         assertEquals(2, ref.get());
374     }
375 
376     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
377     @Test
378     void testLimit() {
379         assertEquals(1, IOStream.of("A", "B").limit(1).count());
380     }
381 
382     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
383     @Test
384     void testMap() throws IOException {
385         assertEquals(Arrays.asList("AC", "BC"), IOStream.of("A", "B").map(e -> e + "C").collect(Collectors.toList()));
386     }
387 
388     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
389     @Test
390     void testMapToDouble() {
391         assertArrayEquals(new double[] { Double.parseDouble("1"), Double.parseDouble("2") }, IOStream.of("1", "2").mapToDouble(Double::parseDouble).toArray());
392     }
393 
394     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
395     @Test
396     void testMapToInt() {
397         assertArrayEquals(new int[] { 1, 2 }, IOStream.of("1", "2").mapToInt(Integer::parseInt).toArray());
398     }
399 
400     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
401     @Test
402     void testMapToLong() {
403         assertArrayEquals(new long[] { 1L, 2L }, IOStream.of("1", "2").mapToLong(Long::parseLong).toArray());
404     }
405 
406     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
407     @Test
408     void testMax() throws IOException {
409         assertEquals("B", IOStream.of("A", "B").max(String::compareTo).get());
410     }
411 
412     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
413     @Test
414     void testMin() throws IOException {
415         assertEquals("A", IOStream.of("A", "B").min(String::compareTo).get());
416     }
417 
418     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
419     @Test
420     void testNoneMatch() throws IOException {
421         assertThrows(IOException.class, () -> IOStream.of("A", "B").noneMatch(TestConstants.THROWING_IO_PREDICATE));
422         assertFalse(IOStream.of("A", "B").noneMatch(IOPredicate.alwaysTrue()));
423         assertTrue(IOStream.of("A", "B").noneMatch(IOPredicate.alwaysFalse()));
424     }
425 
426     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
427     @Test
428     void testOfArray() {
429         assertEquals(0, IOStream.of((String[]) null).count());
430         assertEquals(0, IOStream.of().count());
431         assertEquals(2, IOStream.of("A", "B").count());
432     }
433 
434     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
435     @Test
436     void testOfIterable() {
437         assertEquals(0, IOStream.of((Iterable<?>) null).count());
438         assertEquals(0, IOStream.of(Collections.emptyList()).count());
439         assertEquals(0, IOStream.of(Collections.emptySet()).count());
440         assertEquals(0, IOStream.of(Collections.emptySortedSet()).count());
441         assertEquals(1, IOStream.of(Arrays.asList("a")).count());
442         assertEquals(2, IOStream.of(Arrays.asList("a", "b")).count());
443     }
444 
445     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
446     @Test
447     void testOfOne() {
448         assertEquals(1, IOStream.of("A").count());
449     }
450 
451     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
452     @Test
453     void testOnClose() throws IOException {
454         assertThrows(IOException.class, () -> IOStream.of("A").onClose(TestConstants.THROWING_IO_RUNNABLE).close());
455         final AtomicReference<String> ref = new AtomicReference<>();
456         IOStream.of("A").onClose(() -> compareAndSetIO(ref, null, "new1")).close();
457         assertEquals("new1", ref.get());
458     }
459 
460     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
461     @Test
462     void testOnCloseMultipleHandlers() {
463         //
464         final AtomicReference<String> ref = new AtomicReference<>();
465         // Sanity check
466         ref.set(null);
467         final RuntimeException thrownRE = assertThrows(RuntimeException.class, () -> {
468             // @formatter:off
469             final Stream<String> stream = Stream.of("A")
470                 .onClose(() -> compareAndSetRE(ref, null, "new1"))
471                 .onClose(() -> TestConstants.throwRuntimeException("Failure 2"));
472             // @formatter:on
473             stream.close();
474         });
475         assertEquals("new1", ref.get());
476         assertEquals("Failure 2", thrownRE.getMessage());
477         assertEquals(0, thrownRE.getSuppressed().length);
478         // Test
479         ref.set(null);
480         final IOException thrownIO = assertThrows(IOException.class, () -> {
481             // @formatter:off
482             final IOStream<String> stream = IOStream.of("A")
483                 .onClose(() -> compareAndSetIO(ref, null, "new1"))
484                 .onClose(() -> TestConstants.throwIOException("Failure 2"));
485             // @formatter:on
486             stream.close();
487         });
488         assertEquals("new1", ref.get());
489         assertEquals("Failure 2", thrownIO.getMessage());
490         assertEquals(0, thrownIO.getSuppressed().length);
491         //
492         final IOException thrownB = assertThrows(IOException.class, () -> {
493             // @formatter:off
494             final IOStream<String> stream = IOStream.of("A")
495                 .onClose(TestConstants.throwIOException("Failure 1"))
496                 .onClose(TestConstants.throwIOException("Failure 2"));
497             // @formatter:on
498             stream.close();
499         });
500         assertEquals("Failure 1", thrownB.getMessage());
501         assertEquals(0, thrownB.getSuppressed().length);
502     }
503 
504     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
505     @Test
506     void testParallel() {
507         assertEquals(2, IOStream.of("A", "B").parallel().count());
508     }
509 
510     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
511     @Test
512     void testPeek() throws IOException {
513         final AtomicReference<String> ref = new AtomicReference<>();
514         // Stream sanity check
515         assertEquals(1, Stream.of("A").peek(e -> compareAndSetRE(ref, null, e)).count());
516         // TODO Resolve, abstract or document these differences?
517         assertEquals(AT_LEAST_JAVA_11 ? null : "A", ref.get());
518         if (AT_LEAST_JAVA_11) {
519             assertEquals(1, IOStream.of("B").peek(e -> compareAndSetRE(ref, null, e)).count());
520             assertEquals(1, IOStream.of("B").peek(e -> compareAndSetIO(ref, null, e)).count());
521             assertNull(ref.get());
522         } else {
523             // Java 8
524             assertThrows(RuntimeException.class, () -> IOStream.of("B").peek(e -> compareAndSetRE(ref, null, e)).count());
525             assertThrows(IOException.class, () -> IOStream.of("B").peek(e -> compareAndSetIO(ref, null, e)).count());
526             assertEquals("A", ref.get());
527         }
528     }
529 
530     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
531     @Test
532     void testReduceBinaryOperatorOfT() throws IOException {
533         assertEquals("AB", IOStream.of("A", "B").reduce((t, u) -> t + u).get());
534         assertEquals(TestConstants.ABS_PATH_A.toRealPath(),
535                 IOStream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B).reduce((t, u) -> t.toRealPath()).get());
536     }
537 
538     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
539     @Test
540     void testReduceTBinaryOperatorOfT() throws IOException {
541         assertEquals("_AB", IOStream.of("A", "B").reduce("_", (t, u) -> t + u));
542         assertEquals(TestConstants.ABS_PATH_A.toRealPath(),
543                 IOStream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B).reduce(TestConstants.ABS_PATH_A, (t, u) -> t.toRealPath()));
544     }
545 
546     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
547     @Test
548     void testReduceUBiFunctionOfUQsuperTUBinaryOperatorOfU() throws IOException {
549         assertEquals("_AB", IOStream.of("A", "B").reduce("_", (t, u) -> t + u, (t, u) -> t + u));
550         assertEquals(TestConstants.ABS_PATH_A.toRealPath(), IOStream.of(TestConstants.ABS_PATH_A, TestConstants.ABS_PATH_B).reduce(TestConstants.ABS_PATH_A,
551                 (t, u) -> t.toRealPath(), (t, u) -> u.toRealPath()));
552     }
553 
554     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
555     @Test
556     void testSequential() {
557         assertEquals(2, IOStream.of("A", "B").sequential().count());
558     }
559 
560     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
561     @Test
562     void testSkip() throws IOException {
563         final AtomicReference<String> ref = new AtomicReference<>();
564         assertEquals(1, Stream.of("A", "B").skip(1).peek(e -> compareAndSetRE(ref, null, e)).count());
565         // TODO Resolve, abstract or document these differences?
566         assertEquals(AT_LEAST_JAVA_17 ? null : "B", ref.get());
567         if (AT_LEAST_JAVA_17) {
568             assertEquals(1, IOStream.of("C", "D").skip(1).peek(e -> compareAndSetRE(ref, null, e)).count());
569             assertEquals(1, IOStream.of("C", "D").skip(1).peek(e -> compareAndSetIO(ref, null, e)).count());
570             assertNull(ref.get());
571         } else {
572             if (AT_LEAST_JAVA_11) {
573                 assertThrows(RuntimeException.class, () -> IOStream.of("C", "D").skip(1).peek(e -> compareAndSetRE(ref, null, e)).count());
574                 assertThrows(IOException.class, () -> IOStream.of("C", "D").skip(1).peek(e -> compareAndSetIO(ref, null, e)).count());
575             } else {
576                 assertThrows(RuntimeException.class, () -> IOStream.of("C", "D").skip(1).peek(e -> compareAndSetRE(ref, null, e)).count());
577                 assertThrows(IOException.class, () -> IOStream.of("C", "D").skip(1).peek(e -> compareAndSetIO(ref, null, e)).count());
578             }
579             assertEquals("B", ref.get());
580         }
581     }
582 
583     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
584     @Test
585     void testSorted() throws IOException {
586         assertEquals(Arrays.asList("A", "B", "C", "D"), IOStream.of("D", "A", "B", "C").sorted().collect(Collectors.toList()));
587         assertEquals(Arrays.asList("A", "B", "C", "D"), IOStream.of("D", "A", "B", "C").sorted().peek(this::ioExceptionOnNull).collect(Collectors.toList()));
588     }
589 
590     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
591     @Test
592     void testSortedComparatorOfQsuperT() throws IOException {
593         assertEquals(Arrays.asList("A", "B", "C", "D"), IOStream.of("D", "A", "B", "C").sorted(String::compareTo).collect(Collectors.toList()));
594         assertEquals(Arrays.asList("A", "B", "C", "D"),
595                 IOStream.of("D", "A", "B", "C").sorted(String::compareTo).peek(this::ioExceptionOnNull).collect(Collectors.toList()));
596     }
597 
598     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
599     @Test
600     void testSpliterator() {
601         final AtomicInteger ref = new AtomicInteger();
602         IOStream.of("A", "B").spliterator().forEachRemaining(e -> ref.incrementAndGet());
603         assertEquals(2, ref.get());
604     }
605 
606     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
607     @Test
608     void testToArray() {
609         assertArrayEquals(new String[] { "A", "B" }, IOStream.of("A", "B").toArray());
610     }
611 
612     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
613     @Test
614     void testToArrayIntFunctionOfA() {
615         assertArrayEquals(new String[] { "A", "B" }, IOStream.of("A", "B").toArray(String[]::new));
616     }
617 
618     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
619     @Test
620     void testUnordered() {
621         // Sanity check
622         assertArrayEquals(new String[] { "A", "B" }, Stream.of("A", "B").unordered().toArray());
623         // Test
624         assertArrayEquals(new String[] { "A", "B" }, IOStream.of("A", "B").unordered().toArray());
625     }
626 
627     @SuppressWarnings("resource") // custom stream not recognized by compiler warning machinery
628     @Test
629     void testUnwrap() {
630         final Stream<String> unwrap = IOStream.of("A", "B").unwrap();
631         assertNotNull(unwrap);
632         assertEquals(2, unwrap.count());
633     }
634 
635 }