5151 */
5252class BoundedInputStreamTest {
5353
54+ static Stream <Arguments > testAvailableAfterClose () throws IOException {
55+ // Case 1: behaves like ByteArrayInputStream — close() is a no-op, available() still returns a value (e.g., 42).
56+ final InputStream noOpClose = mock (InputStream .class );
57+ when (noOpClose .available ()).thenReturn (42 , 42 );
58+
59+ // Case 2: returns 0 after close (Commons memory-backed streams that ignore close but report 0 when exhausted).
60+ final InputStream returnsZeroAfterClose = mock (InputStream .class );
61+ when (returnsZeroAfterClose .available ()).thenReturn (42 , 0 );
62+
63+ // Case 3: throws IOException after close (e.g., FileInputStream-like behavior).
64+ final InputStream throwsAfterClose = mock (InputStream .class );
65+ when (throwsAfterClose .available ()).thenReturn (42 ).thenThrow (new IOException ("Stream closed" ));
66+
67+ return Stream .of (
68+ Arguments .of ("underlying stream still returns 42 after close" , noOpClose , 42 ),
69+ Arguments .of ("underlying stream returns 0 after close" , returnsZeroAfterClose , 42 ),
70+ Arguments .of ("underlying stream throws IOException after close" , throwsAfterClose , 42 ));
71+ }
72+
73+ static Stream <Arguments > testAvailableUpperLimit () {
74+ final byte [] helloWorld = "Hello World" .getBytes (StandardCharsets .UTF_8 );
75+ return Stream .of (
76+ // Limited by maxCount
77+ Arguments .of (new ByteArrayInputStream (helloWorld ), helloWorld .length - 1 , helloWorld .length - 1 , 0 ),
78+ // Limited by data length
79+ Arguments .of (new ByteArrayInputStream (helloWorld ), helloWorld .length + 1 , helloWorld .length , 0 ),
80+ // Limited by Integer.MAX_VALUE
81+ Arguments .of (
82+ new NullInputStream (Long .MAX_VALUE ), Long .MAX_VALUE , Integer .MAX_VALUE , Integer .MAX_VALUE ));
83+ }
84+
85+ static Stream <Arguments > testReadAfterClose () throws IOException {
86+ // Case 1: no-op close (ByteArrayInputStream-like): read() still returns a value after close
87+ final InputStream noOpClose = mock (InputStream .class );
88+ when (noOpClose .read ()).thenReturn (42 );
89+
90+ // Case 2: returns EOF (-1) after close
91+ final InputStream returnsEofAfterClose = mock (InputStream .class );
92+ when (returnsEofAfterClose .read ()).thenReturn (IOUtils .EOF );
93+
94+ // Case 3: throws IOException after close (FileInputStream-like)
95+ final InputStream throwsAfterClose = mock (InputStream .class );
96+ final IOException closed = new IOException ("Stream closed" );
97+ when (throwsAfterClose .read ()).thenThrow (closed );
98+
99+ return Stream .of (
100+ Arguments .of ("underlying stream still reads data after close" , noOpClose , 42 ),
101+ Arguments .of ("underlying stream returns EOF after close" , returnsEofAfterClose , IOUtils .EOF ),
102+ Arguments .of ("underlying stream throws IOException after close" , throwsAfterClose , closed ));
103+ }
104+
105+ static Stream <Arguments > testRemaining () {
106+ return Stream .of (
107+ // Unbounded: any negative maxCount is treated as "no limit".
108+ Arguments .of ("unbounded (EOF constant)" , IOUtils .EOF , Long .MAX_VALUE ),
109+ Arguments .of ("unbounded (arbitrary negative)" , Long .MIN_VALUE , Long .MAX_VALUE ),
110+
111+ // Bounded: remaining equals the configured limit, regardless of underlying data size.
112+ Arguments .of ("bounded (zero)" , 0L , 0L ),
113+ Arguments .of ("bounded (small)" , 1024L , 1024L ),
114+ Arguments .of ("bounded (Integer.MAX_VALUE)" , Integer .MAX_VALUE , (long ) Integer .MAX_VALUE ),
115+
116+ // Bounded but extremely large: still not 'unbounded'.
117+ Arguments .of ("bounded (Long.MAX_VALUE)" , Long .MAX_VALUE , Long .MAX_VALUE ));
118+ }
119+
54120 private void compare (final String message , final byte [] expected , final byte [] actual ) {
55121 assertEquals (expected .length , actual .length , () -> message + " (array length equals check)" );
56122 final MutableInt mi = new MutableInt ();
@@ -89,25 +155,6 @@ void testAfterReadConsumer() throws Exception {
89155 // @formatter:on
90156 }
91157
92- static Stream <Arguments > testAvailableAfterClose () throws IOException {
93- // Case 1: behaves like ByteArrayInputStream — close() is a no-op, available() still returns a value (e.g., 42).
94- final InputStream noOpClose = mock (InputStream .class );
95- when (noOpClose .available ()).thenReturn (42 , 42 );
96-
97- // Case 2: returns 0 after close (Commons memory-backed streams that ignore close but report 0 when exhausted).
98- final InputStream returnsZeroAfterClose = mock (InputStream .class );
99- when (returnsZeroAfterClose .available ()).thenReturn (42 , 0 );
100-
101- // Case 3: throws IOException after close (e.g., FileInputStream-like behavior).
102- final InputStream throwsAfterClose = mock (InputStream .class );
103- when (throwsAfterClose .available ()).thenReturn (42 ).thenThrow (new IOException ("Stream closed" ));
104-
105- return Stream .of (
106- Arguments .of ("underlying stream still returns 42 after close" , noOpClose , 42 ),
107- Arguments .of ("underlying stream returns 0 after close" , returnsZeroAfterClose , 42 ),
108- Arguments .of ("underlying stream throws IOException after close" , throwsAfterClose , 42 ));
109- }
110-
111158 @ ParameterizedTest (name = "{index} — {0}" )
112159 @ MethodSource
113160 void testAvailableAfterClose (String caseName , InputStream delegate , int expectedBeforeClose )
@@ -130,18 +177,6 @@ void testAvailableAfterClose(String caseName, InputStream delegate, int expected
130177 verifyNoMoreInteractions (delegate );
131178 }
132179
133- static Stream <Arguments > testAvailableUpperLimit () {
134- final byte [] helloWorld = "Hello World" .getBytes (StandardCharsets .UTF_8 );
135- return Stream .of (
136- // Limited by maxCount
137- Arguments .of (new ByteArrayInputStream (helloWorld ), helloWorld .length - 1 , helloWorld .length - 1 , 0 ),
138- // Limited by data length
139- Arguments .of (new ByteArrayInputStream (helloWorld ), helloWorld .length + 1 , helloWorld .length , 0 ),
140- // Limited by Integer.MAX_VALUE
141- Arguments .of (
142- new NullInputStream (Long .MAX_VALUE ), Long .MAX_VALUE , Integer .MAX_VALUE , Integer .MAX_VALUE ));
143- }
144-
145180 @ ParameterizedTest
146181 @ MethodSource
147182 void testAvailableUpperLimit (InputStream input , long maxCount , int expectedBeforeSkip , int expectedAfterSkip )
@@ -506,26 +541,6 @@ void testPublicConstructors() throws IOException {
506541 }
507542 }
508543
509- static Stream <Arguments > testReadAfterClose () throws IOException {
510- // Case 1: no-op close (ByteArrayInputStream-like): read() still returns a value after close
511- final InputStream noOpClose = mock (InputStream .class );
512- when (noOpClose .read ()).thenReturn (42 );
513-
514- // Case 2: returns EOF (-1) after close
515- final InputStream returnsEofAfterClose = mock (InputStream .class );
516- when (returnsEofAfterClose .read ()).thenReturn (IOUtils .EOF );
517-
518- // Case 3: throws IOException after close (FileInputStream-like)
519- final InputStream throwsAfterClose = mock (InputStream .class );
520- final IOException closed = new IOException ("Stream closed" );
521- when (throwsAfterClose .read ()).thenThrow (closed );
522-
523- return Stream .of (
524- Arguments .of ("underlying stream still reads data after close" , noOpClose , 42 ),
525- Arguments .of ("underlying stream returns EOF after close" , returnsEofAfterClose , IOUtils .EOF ),
526- Arguments .of ("underlying stream throws IOException after close" , throwsAfterClose , closed ));
527- }
528-
529544 @ ParameterizedTest (name = "{index} — {0}" )
530545 @ MethodSource ("testReadAfterClose" )
531546 void testReadAfterClose (
@@ -599,46 +614,6 @@ void testReadArray() throws Exception {
599614 }
600615 }
601616
602- static Stream <Arguments > testRemaining () {
603- return Stream .of (
604- // Unbounded: any negative maxCount is treated as "no limit".
605- Arguments .of ("unbounded (EOF constant)" , IOUtils .EOF , Long .MAX_VALUE ),
606- Arguments .of ("unbounded (arbitrary negative)" , Long .MIN_VALUE , Long .MAX_VALUE ),
607-
608- // Bounded: remaining equals the configured limit, regardless of underlying data size.
609- Arguments .of ("bounded (zero)" , 0L , 0L ),
610- Arguments .of ("bounded (small)" , 1024L , 1024L ),
611- Arguments .of ("bounded (Integer.MAX_VALUE)" , Integer .MAX_VALUE , (long ) Integer .MAX_VALUE ),
612-
613- // Bounded but extremely large: still not 'unbounded'.
614- Arguments .of ("bounded (Long.MAX_VALUE)" , Long .MAX_VALUE , Long .MAX_VALUE ));
615- }
616-
617- @ ParameterizedTest (name = "{index}: {0} -> initial remaining {2}" )
618- @ MethodSource
619- void testRemaining (final String caseName , final long maxCount , final long expectedInitialRemaining )
620- throws Exception {
621- final byte [] data = "Hello World" .getBytes (StandardCharsets .UTF_8 ); // 11 bytes
622-
623- try (BoundedInputStream in = BoundedInputStream .builder ()
624- .setByteArray (data )
625- .setMaxCount (maxCount )
626- .get ()) {
627- // Initial remaining respects the imposed limit (or is Long.MAX_VALUE if unbounded).
628- assertEquals (expectedInitialRemaining , in .getRemaining (), caseName + " (initial)" );
629-
630- // Skip more than the data length to exercise both bounded and unbounded paths.
631- final long skipped = IOUtils .skip (in , 42 );
632-
633- // For unbounded streams (EOF == -1), remaining stays the same.
634- // For bounded, it decreases by 'skipped'.
635- final long expectedAfterSkip =
636- in .getMaxCount () == IOUtils .EOF ? expectedInitialRemaining : expectedInitialRemaining - skipped ;
637-
638- assertEquals (expectedAfterSkip , in .getRemaining (), caseName + " (after skip)" );
639- }
640- }
641-
642617 @ Test
643618 void testReadSingle () throws Exception {
644619 final byte [] helloWorld = "Hello World" .getBytes (StandardCharsets .UTF_8 );
@@ -677,6 +652,31 @@ void testReadSingle() throws Exception {
677652 }
678653 }
679654
655+ @ ParameterizedTest (name = "{index}: {0} -> initial remaining {2}" )
656+ @ MethodSource
657+ void testRemaining (final String caseName , final long maxCount , final long expectedInitialRemaining )
658+ throws Exception {
659+ final byte [] data = "Hello World" .getBytes (StandardCharsets .UTF_8 ); // 11 bytes
660+
661+ try (BoundedInputStream in = BoundedInputStream .builder ()
662+ .setByteArray (data )
663+ .setMaxCount (maxCount )
664+ .get ()) {
665+ // Initial remaining respects the imposed limit (or is Long.MAX_VALUE if unbounded).
666+ assertEquals (expectedInitialRemaining , in .getRemaining (), caseName + " (initial)" );
667+
668+ // Skip more than the data length to exercise both bounded and unbounded paths.
669+ final long skipped = IOUtils .skip (in , 42 );
670+
671+ // For unbounded streams (EOF == -1), remaining stays the same.
672+ // For bounded, it decreases by 'skipped'.
673+ final long expectedAfterSkip =
674+ in .getMaxCount () == IOUtils .EOF ? expectedInitialRemaining : expectedInitialRemaining - skipped ;
675+
676+ assertEquals (expectedAfterSkip , in .getRemaining (), caseName + " (after skip)" );
677+ }
678+ }
679+
680680 @ Test
681681 void testReset () throws Exception {
682682 final byte [] helloWorld = "Hello World" .getBytes (StandardCharsets .UTF_8 );
0 commit comments