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