You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2024/08/07 02:11:42 UTC

(commons-io) 03/04: Revert *InputStream.read() should return -1 (EOF) after the stream is closed

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git

commit cc62ff74731a880cd7b5f9cec8f011d20b3e9af3
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Tue Aug 6 22:11:24 2024 -0400

    Revert *InputStream.read() should return -1 (EOF) after the stream is
    closed
    
    - The InputStream#read(*) Javadocs suggests we should throw an
    IOException in that case
    - BOMInputStream
    - ProxyInputStream
    - BufferedFileChannelInputStream
    - MemoryMappedFileInputStream
    - RandomAccessFileInputStream
---
 src/changes/changes.xml                            |  8 +----
 .../commons/io/input/AbstractInputStream.java      | 21 +++++++++++++
 .../apache/commons/io/input/BOMInputStream.java    |  4 +--
 .../io/input/BufferedFileChannelInputStream.java   |  4 +--
 .../io/input/MemoryMappedFileInputStream.java      | 10 +------
 .../apache/commons/io/input/ProxyInputStream.java  | 13 ++++++--
 .../io/input/RandomAccessFileInputStream.java      |  3 +-
 .../apache/commons/io/input/ReaderInputStream.java |  4 +--
 .../commons/io/input/BOMInputStreamTest.java       |  6 ++--
 .../input/BufferedFileChannelInputStreamTest.java  |  4 +--
 .../io/input/MemoryMappedFileInputStreamTest.java  |  7 ++---
 .../MessageDigestCalculatingInputStreamTest.java   | 31 ++++++++++++++-----
 .../io/input/MessageDigestInputStreamTest.java     | 28 ++++++++++++-----
 .../io/input/ObservableInputStreamTest.java        | 29 +++++++++++++-----
 .../commons/io/input/ProxyInputStreamTest.java     | 35 ++++++++++++++++------
 .../io/input/RandomAccessFileInputStreamTest.java  |  3 +-
 .../commons/io/input/ReaderInputStreamTest.java    | 16 +++++-----
 17 files changed, 143 insertions(+), 83 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 1fb4418cd..b07c67c63 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -74,20 +74,14 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">CircularInputStream.available() should return 0 when the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">InfiniteCircularInputStream.available() should return 0 when the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">ChecksumInputStream(InputStream, Checksum, long, long) should fail-fast on null Checksum input.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">BufferedFileChannelInputStream.read() should return -1 (EOF) after the stream is closed.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">BOMInputStream.read() should return -1 (EOF) after the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">Deprecate NullInputStream.INSTANCE in favor of constructors.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">NullInputStream.available() should return 0 after the stream is closed.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">NullInputStream.read() should return -1 (EOF) after the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">MemoryMappedFileInputStream.available() should return 0 after the stream is closed.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">MemoryMappedFileInputStream.read() should return -1 (EOF) after the stream is closed.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">ProxyInputStream.read() should return -1 (EOF) after the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">RandomAccessFileInputStream.available() should return 0 after the stream is closed.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">RandomAccessFileInputStream.read() should return -1 (EOF) after the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">ReaderInputStream.available() should return 0 after the stream is closed.</action>
-      <action dev="ggregory" type="fix"                due-to="Gary Gregory">ReaderInputStream.read() should return -1 (EOF) after the stream is closed.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">AutoCloseInputStream does not call handleIOException() on close() when the proxied stream throws an IOException.</action>
       <action dev="ggregory" type="fix"                due-to="Gary Gregory">BoundedInputStream does not call handleIOException() on close() when the proxied stream throws an IOException.</action>
+      <action dev="ggregory" type="fix"                due-to="Gary Gregory">NullInputStream.read() can be configured to return -1 (EOF) after the stream is closed.</action>
       <!-- UPDATE -->
       <action dev="ggregory" type="update"             due-to="Dependabot">Bump tests commons.bytebuddy.version from 1.14.13 to 1.14.18 #615, #621, #631, #635, #642.</action>
       <action dev="ggregory" type="update"             due-to="Dependabot">Bump tests commons-codec:commons-codec from 1.16.1 to 1.17.1 #644.</action>
diff --git a/src/main/java/org/apache/commons/io/input/AbstractInputStream.java b/src/main/java/org/apache/commons/io/input/AbstractInputStream.java
index 5c71facd1..e7f3c20c3 100644
--- a/src/main/java/org/apache/commons/io/input/AbstractInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/AbstractInputStream.java
@@ -27,11 +27,32 @@ import java.io.InputStream;
  */
 public abstract class AbstractInputStream extends InputStream {
 
+    /**
+     * Throws an IOException on false input.
+     *
+     * @param isOpen whether the stream is open or not.
+     * @throws IOException if this instance is closed.
+     */
+    static void checkOpen(final boolean isOpen) throws IOException {
+        if (!isOpen) {
+            throw new IOException("The stream is closed.");
+        }
+    }
+
     /**
      * Whether {@link #close()} completed successfully.
      */
     private boolean closed;
 
+    /**
+     * Checks if this instance is closed and throws an IOException if so.
+     *
+     * @throws IOException if this instance is closed.
+     */
+    void checkOpen() throws IOException {
+        checkOpen(!isClosed());
+    }
+
     @Override
     public void close() throws IOException {
         super.close();
diff --git a/src/main/java/org/apache/commons/io/input/BOMInputStream.java b/src/main/java/org/apache/commons/io/input/BOMInputStream.java
index 7bd305b70..73dadaa9a 100644
--- a/src/main/java/org/apache/commons/io/input/BOMInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/BOMInputStream.java
@@ -419,9 +419,7 @@ public class BOMInputStream extends ProxyInputStream {
      */
     @Override
     public int read() throws IOException {
-        if (isClosed()) {
-            return EOF;
-        }
+        checkOpen();
         final int b = readFirstBytes();
         return b >= 0 ? b : in.read();
     }
diff --git a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java
index e503912b7..70075e3af 100644
--- a/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/BufferedFileChannelInputStream.java
@@ -243,9 +243,7 @@ public final class BufferedFileChannelInputStream extends InputStream {
      * @throws IOException if an I/O error occurs.
      */
     private boolean refill() throws IOException {
-        if (!fileChannel.isOpen()) {
-            return false;
-        }
+        AbstractInputStream.checkOpen(fileChannel.isOpen());
         if (!byteBuffer.hasRemaining()) {
             byteBuffer.clear();
             int nRead = 0;
diff --git a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java
index 02c02a9dc..4363d9599 100644
--- a/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/MemoryMappedFileInputStream.java
@@ -170,12 +170,6 @@ public final class MemoryMappedFileInputStream extends AbstractInputStream {
         return buffer.remaining();
     }
 
-    private void checkOpen() throws IOException {
-        if (isClosed()) {
-            throw new IOException("Stream closed");
-        }
-    }
-
     private void cleanBuffer() {
         if (ByteBufferCleaner.isSupported() && buffer.isDirect()) {
             ByteBufferCleaner.clean(buffer);
@@ -210,9 +204,7 @@ public final class MemoryMappedFileInputStream extends AbstractInputStream {
 
     @Override
     public int read() throws IOException {
-        if (isClosed()) {
-            return EOF;
-        }
+        checkOpen();
         if (!buffer.hasRemaining()) {
             nextBuffer();
             if (!buffer.hasRemaining()) {
diff --git a/src/main/java/org/apache/commons/io/input/ProxyInputStream.java b/src/main/java/org/apache/commons/io/input/ProxyInputStream.java
index fd43678e8..523d07ab5 100644
--- a/src/main/java/org/apache/commons/io/input/ProxyInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ProxyInputStream.java
@@ -143,6 +143,15 @@ public abstract class ProxyInputStream extends FilterInputStream {
         // no-op default
     }
 
+    /**
+     * Checks if this instance is closed and throws an IOException if so.
+     *
+     * @throws IOException if this instance is closed.
+     */
+    void checkOpen() throws IOException {
+        AbstractInputStream.checkOpen(!isClosed());
+    }
+
     /**
      * Invokes the delegate's {@link InputStream#close()} method.
      *
@@ -210,9 +219,6 @@ public abstract class ProxyInputStream extends FilterInputStream {
      */
     @Override
     public int read() throws IOException {
-        if (isClosed()) {
-            return EOF;
-        }
         try {
             beforeRead(1);
             final int b = in.read();
@@ -328,4 +334,5 @@ public abstract class ProxyInputStream extends FilterInputStream {
     public InputStream unwrap() {
         return in;
     }
+
 }
diff --git a/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java b/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
index 3bb10c93d..afeccb4d5 100644
--- a/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/RandomAccessFileInputStream.java
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.util.Objects;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.RandomAccessFileMode;
 import org.apache.commons.io.build.AbstractOrigin;
 import org.apache.commons.io.build.AbstractStreamBuilder;
@@ -210,7 +209,7 @@ public class RandomAccessFileInputStream extends AbstractInputStream {
 
     @Override
     public int read() throws IOException {
-        return isClosed() ? IOUtils.EOF : randomAccessFile.read();
+        return randomAccessFile.read();
     }
 
     @Override
diff --git a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
index 1b0ad36ea..3767029b9 100644
--- a/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
+++ b/src/main/java/org/apache/commons/io/input/ReaderInputStream.java
@@ -408,9 +408,7 @@ public class ReaderInputStream extends AbstractInputStream {
      */
     @Override
     public int read() throws IOException {
-        if (isClosed()) {
-            return EOF;
-        }
+        checkOpen();
         for (;;) {
             if (encoderOut.hasRemaining()) {
                 return encoderOut.get() & 0xFF;
diff --git a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
index c5d914414..350d099d4 100644
--- a/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/BOMInputStreamTest.java
@@ -37,7 +37,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.commons.io.ByteOrderMark;
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.SystemProperties;
 import org.junit.jupiter.api.Test;
 import org.w3c.dom.Document;
@@ -429,12 +428,11 @@ public class BOMInputStreamTest {
     @Test
     public void testReadAfterClose() throws Exception {
         final byte[] data = { 'A', 'B', 'C', 'D' };
-        final InputStream shadow;
         try (InputStream in = BOMInputStream.builder().setInputStream(createUtf8Input(data, true)).get()) {
             assertEquals(7, in.available());
-            shadow = in;
+            in.close();
+            assertThrows(IOException.class, in::read);
         }
-        assertEquals(IOUtils.EOF, shadow.read());
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java
index 7c8baf0fe..1e1f6f46d 100644
--- a/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/BufferedFileChannelInputStreamTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.commons.io.input;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -24,7 +23,6 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.StandardOpenOption;
 
-import org.apache.commons.io.IOUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -71,7 +69,7 @@ public class BufferedFileChannelInputStreamTest extends AbstractInputStreamTest
     public void testReadAfterClose() throws Exception {
         for (final InputStream inputStream : inputStreams) {
             inputStream.close();
-            assertEquals(IOUtils.EOF, inputStream.read());
+            assertThrows(IOException.class, inputStream::read);
         }
     }
 
diff --git a/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java
index 690c56c7f..0687a8200 100644
--- a/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/MemoryMappedFileInputStreamTest.java
@@ -19,6 +19,7 @@ package org.apache.commons.io.input;
 import static org.apache.commons.lang3.ArrayUtils.EMPTY_BYTE_ARRAY;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -146,16 +147,14 @@ public class MemoryMappedFileInputStreamTest {
         }
     }
 
-    @SuppressWarnings("resource")
     @ParameterizedTest
     @MethodSource(AbstractInputStreamTest.ARRAY_LENGTHS_NAME)
     public void testReadAfterClose(final int len) throws Exception {
         final Path file = createTestFile(len);
-        final InputStream shadow;
         try (InputStream inputStream = newInputStream(file, 1024)) {
-            shadow = inputStream;
+            inputStream.close();
+            assertThrows(IOException.class, inputStream::read);
         }
-        assertEquals(IOUtils.EOF, shadow.read());
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java
index e1038678f..46ae17641 100644
--- a/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/MessageDigestCalculatingInputStreamTest.java
@@ -20,10 +20,13 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.MessageDigest;
 
 import org.apache.commons.codec.digest.DigestUtils;
@@ -39,8 +42,12 @@ import org.junit.jupiter.api.Test;
 public class MessageDigestCalculatingInputStreamTest {
 
     private InputStream createInputStream() throws IOException {
-        return MessageDigestCalculatingInputStream.builder()
-                .setInputStream(new ByteArrayInputStream(MessageDigestInputStreamTest.generateRandomByteStream(256))).get();
+        final ByteArrayInputStream origin = new ByteArrayInputStream(MessageDigestInputStreamTest.generateRandomByteStream(256));
+        return createInputStream(origin);
+    }
+
+    private MessageDigestCalculatingInputStream createInputStream(final InputStream origin) throws IOException {
+        return MessageDigestCalculatingInputStream.builder().setInputStream(origin).get();
     }
 
     @SuppressWarnings("resource")
@@ -111,15 +118,23 @@ public class MessageDigestCalculatingInputStreamTest {
         }
     }
 
-    @SuppressWarnings("resource")
     @Test
-    public void testReadAfterClose() throws Exception {
-        final InputStream shadow;
+    public void testReadAfterClose_ByteArrayInputStream() throws Exception {
         try (InputStream in = createInputStream()) {
-            assertTrue(in.available() > 0);
-            shadow = in;
+            in.close();
+            // ByteArrayInputStream does not throw on a closed stream.
+            assertNotEquals(IOUtils.EOF, in.read());
+        }
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testReadAfterClose_ChannelInputStream() throws Exception {
+        try (InputStream in = createInputStream(Files.newInputStream(Paths.get("src/test/resources/org/apache/commons/io/abitmorethan16k.txt")))) {
+            in.close();
+            // ChannelInputStream throws when closed
+            assertThrows(IOException.class, in::read);
         }
-        assertEquals(IOUtils.EOF, shadow.read());
     }
 
 }
diff --git a/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java b/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java
index 3f6f263ff..8f7208e08 100644
--- a/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/MessageDigestInputStreamTest.java
@@ -25,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Random;
@@ -47,10 +49,15 @@ public class MessageDigestInputStreamTest {
     }
 
     private InputStream createInputStream() throws IOException, NoSuchAlgorithmException {
+        return createInputStream(new ByteArrayInputStream(MessageDigestInputStreamTest.generateRandomByteStream(256)));
+    }
+
+    private InputStream createInputStream(final InputStream origin) throws IOException, NoSuchAlgorithmException {
         // @formatter:off
         return MessageDigestInputStream.builder()
                 .setMessageDigest(MessageDigest.getInstance(MessageDigestAlgorithms.SHA_512))
-                .setInputStream(new ByteArrayInputStream(MessageDigestInputStreamTest.generateRandomByteStream(256))).get();
+                .setInputStream(origin)
+                .get();
         // @formatter:on
     }
 
@@ -98,15 +105,22 @@ public class MessageDigestInputStreamTest {
         }
     }
 
-    @SuppressWarnings("resource")
     @Test
-    public void testReadAfterClose() throws Exception {
-        final InputStream shadow;
+    public void testReadAfterClose_ByteArrayInputStream() throws Exception {
         try (InputStream in = createInputStream()) {
-            assertTrue(in.available() > 0);
-            shadow = in;
+            in.close();
+            assertNotEquals(IOUtils.EOF, in.read());
+        }
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testReadAfterClose_ChannelInputStream() throws Exception {
+        try (InputStream in = createInputStream(Files.newInputStream(Paths.get("src/test/resources/org/apache/commons/io/abitmorethan16k.txt")))) {
+            in.close();
+            // ChannelInputStream throws when closed
+            assertThrows(IOException.class, in::read);
         }
-        assertEquals(IOUtils.EOF, shadow.read());
     }
 
 }
diff --git a/src/test/java/org/apache/commons/io/input/ObservableInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ObservableInputStreamTest.java
index 685cfd56d..f45861403 100644
--- a/src/test/java/org/apache/commons/io/input/ObservableInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/ObservableInputStreamTest.java
@@ -25,6 +25,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.input.ObservableInputStream.Observer;
@@ -134,7 +136,11 @@ public class ObservableInputStreamTest {
 
     private InputStream createInputStream() {
         final byte[] buffer = MessageDigestInputStreamTest.generateRandomByteStream(IOUtils.DEFAULT_BUFFER_SIZE);
-        return new ObservableInputStream(new ByteArrayInputStream(buffer));
+        return createInputStream(new ByteArrayInputStream(buffer));
+    }
+
+    private ObservableInputStream createInputStream(final InputStream origin) {
+        return new ObservableInputStream(origin);
     }
 
     @SuppressWarnings("resource")
@@ -246,7 +252,7 @@ public class ObservableInputStreamTest {
     public void testDataBytesCalled() throws Exception {
         final byte[] buffer = MessageDigestInputStreamTest.generateRandomByteStream(IOUtils.DEFAULT_BUFFER_SIZE);
         try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
-                final ObservableInputStream ois = new ObservableInputStream(bais)) {
+                final ObservableInputStream ois = createInputStream(bais)) {
             final DataViewObserver observer = new DataViewObserver();
             final byte[] readBuffer = new byte[23];
             assertNull(observer.buffer);
@@ -333,15 +339,22 @@ public class ObservableInputStreamTest {
         testNotificationCallbacks(IOUtils.DEFAULT_BUFFER_SIZE);
     }
 
-    @SuppressWarnings("resource")
     @Test
-    public void testReadAfterClose() throws Exception {
-        final InputStream shadow;
+    public void testReadAfterClose_ByteArrayInputStream() throws Exception {
         try (InputStream in = createInputStream()) {
-            assertTrue(in.available() > 0);
-            shadow = in;
+            in.close();
+            assertNotEquals(IOUtils.EOF, in.read());
+        }
+    }
+
+    @SuppressWarnings("resource")
+    @Test
+    public void testReadAfterClose_ChannelInputStream() throws Exception {
+        try (InputStream in = createInputStream(Files.newInputStream(Paths.get("src/test/resources/org/apache/commons/io/abitmorethan16k.txt")))) {
+            in.close();
+            // ChannelInputStream throws when closed
+            assertThrows(IOException.class, in::read);
         }
-        assertEquals(IOUtils.EOF, shadow.read());
     }
 
 }
diff --git a/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
index b6d44fdff..db047b1e0 100644
--- a/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
@@ -20,6 +20,7 @@ package org.apache.commons.io.input;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -30,6 +31,8 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.Arrays;
 
 import org.apache.commons.io.IOUtils;
@@ -188,18 +191,32 @@ public class ProxyInputStreamTest<T extends ProxyInputStream> {
         }
     }
 
-    @SuppressWarnings("resource")
     @Test
-    public void testReadAfterClose() throws IOException {
-        InputStream shadow;
-        try (InputStream inputStream = createFixture()) {
-            shadow = inputStream;
-        }
-        assertEquals(IOUtils.EOF, shadow.read());
+    public void testReadAfterClose_ByteArrayInputStream() throws IOException {
         try (InputStream inputStream = new ProxyInputStreamFixture(new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)))) {
-            shadow = inputStream;
+            inputStream.close();
+            // ByteArrayInputStream does not throw on a closed stream.
+            assertNotEquals(IOUtils.EOF, inputStream.read());
+        }
+    }
+
+    @Test
+    public void testReadAfterClose_ChannelInputStream() throws IOException {
+        try (InputStream inputStream = new ProxyInputStreamFixture(
+                Files.newInputStream(Paths.get("src/test/resources/org/apache/commons/io/abitmorethan16k.txt")))) {
+            inputStream.close();
+            // ChannelInputStream throws when closed
+            assertThrows(IOException.class, inputStream::read);
+        }
+    }
+
+    @Test
+    public void testReadAfterClose_CharSequenceInputStream() throws IOException {
+        try (InputStream inputStream = createFixture()) {
+            inputStream.close();
+            // CharSequenceInputStream (like ByteArrayInputStream) does not throw on a closed stream.
+            assertEquals(IOUtils.EOF, inputStream.read());
         }
-        assertEquals(IOUtils.EOF, shadow.read());
     }
 
     @Test
diff --git a/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java b/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
index 56880152a..08540914b 100644
--- a/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/RandomAccessFileInputStreamTest.java
@@ -33,7 +33,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.RandomAccessFileMode;
 import org.junit.jupiter.api.Test;
 
@@ -205,7 +204,7 @@ public class RandomAccessFileInputStreamTest {
         try (RandomAccessFileInputStream inputStream = new RandomAccessFileInputStream(createRandomAccessFile(),
             true)) {
             inputStream.close();
-            assertEquals(IOUtils.EOF, inputStream.read());
+            assertThrows(IOException.class, inputStream::read);
         }
     }
 
diff --git a/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java b/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java
index d89ac3a1b..b4322a368 100644
--- a/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java
+++ b/src/test/java/org/apache/commons/io/input/ReaderInputStreamTest.java
@@ -257,6 +257,14 @@ public class ReaderInputStreamTest {
         testWithSingleByteRead(LARGE_TEST_STRING, UTF_8);
     }
 
+    @Test
+    public void testReadAfterClose() throws IOException {
+        try (InputStream inputStream = createInputStream()) {
+            inputStream.close();
+            assertThrows(IOException.class, inputStream::read);
+        }
+    }
+
     @Test
     public void testReadEofTwice() throws IOException {
         try (ReaderInputStream reader = ReaderInputStream.builder().setCharset(StandardCharsets.UTF_8).setReader(new StringReader("123")).get()) {
@@ -301,14 +309,6 @@ public class ReaderInputStreamTest {
         }
     }
 
-    @Test
-    public void testReadAfterClose() throws IOException {
-        try (InputStream inputStream = createInputStream()) {
-            inputStream.close();
-            assertEquals(IOUtils.EOF, inputStream.read());
-        }
-    }
-
     @Test
     public void testResetCharset() {
         assertNotNull(ReaderInputStream.builder().setReader(new StringReader("\uD800")).setCharset((Charset) null).getCharset());