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 2025/09/25 14:40:22 UTC
(commons-io) branch master updated: Add tests
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
The following commit(s) were added to refs/heads/master by this push:
new 37cab2b8e Add tests
37cab2b8e is described below
commit 37cab2b8e2adf24fc5493812cb9dd41b90511098
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Thu Sep 25 10:40:17 2025 -0400
Add tests
- Remove unused exception from signature of
CloseShieldChannelHandler.invokeObjectMethod(Object, Method, Object[])
- Test AsynchronousChannel
- Test AsynchronousByteChannel
- Test NetworkChannel
- Test MulticastChannel
- Test InterruptibleChannel
- Add @SuppressWarnings value
- Inline single use local variable
- Javadoc
- Sort members
- Use final
---
src/changes/changes.xml | 2 +-
.../commons/io/channels/CloseShieldChannel.java | 57 +++---
.../io/channels/CloseShieldChannelHandler.java | 119 ++++++------
.../io/channels/CloseShieldChannelTest.java | 208 +++++++++++----------
4 files changed, 197 insertions(+), 189 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 439c5c162..861107d82 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -66,7 +66,7 @@ The <action> type attribute can be add,update,fix,remove.
<action dev="pkarwasz" type="add" due-to="Piotr P. Karwasz">Add org.apache.commons.io.file.PathUtils.getPath(String, String).</action>
<action dev="ggregory" type="add" due-to="Gary Gregory">Add org.apache.commons.io.channels.ByteArraySeekableByteChannel.</action>
<action dev="ggregory" type="add" due-to="Gary Gregory">Add IOIterable.asIterable().</action>
- <action dev="pkarwasz" type="add" due-to="Piotr P. Karwasz">Add Channels.closeShield(Channel) for close-shielded NIO Channel proxies.</action>
+ <action dev="pkarwasz" type="add" due-to="Piotr P. Karwasz">Add CloseShieldChannel to close-shielded NIO Channels.</action>
<!-- UPDATE -->
<action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 85 to 88 #774, #783.</action>
<action type="update" dev="ggregory" due-to="Gary Gregory">[test] Bump commons-codec:commons-codec from 1.18.0 to 1.19.0.</action>
diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
index 00f269f9a..e100082dc 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannel.java
@@ -14,9 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.io.channels;
-import java.lang.reflect.InvocationHandler;
+import java.io.Closeable;
import java.lang.reflect.Proxy;
import java.nio.channels.Channel;
import java.util.LinkedHashSet;
@@ -24,16 +25,31 @@
import java.util.Set;
/**
- * Utility to create a close-shielding proxy for a {@link Channel}.
+ * Creates a close-shielding proxy for a {@link Channel}.
*
- * <p>The returned proxy will implement all {@link Channel} sub-interfaces that the delegate implements.</p>
+ * <p>
+ * The returned proxy will implement all {@link Channel} sub-interfaces that the delegate implements.
+ * </p>
*
+ * @see Channel
+ * @see Closeable
* @since 2.21.0
*/
public final class CloseShieldChannel {
- private CloseShieldChannel() {
- // no instance
+ private static Class<?>[] collectChannelInterfaces(final Class<?> type) {
+ final Set<Class<?>> out = new LinkedHashSet<>();
+ collectChannelInterfaces(type, out);
+ return out.toArray(new Class<?>[0]);
+ }
+
+ private static void collectChannelInterfaces(final Class<?> type, final Set<Class<?>> out) {
+ // Visit interfaces
+ for (final Class<?> iface : type.getInterfaces()) {
+ if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
+ collectChannelInterfaces(iface, out);
+ }
+ }
}
/**
@@ -43,42 +59,25 @@ private CloseShieldChannel() {
* @param <T> Any Channel type (interface or class).
* @return A proxy that shields {@code close()} and enforces closed semantics on other calls.
*/
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({ "unchecked", "resource" }) // caller closes
public static <T extends Channel> T wrap(final T channel) {
Objects.requireNonNull(channel, "channel");
-
// Fast path: already our shield
if (Proxy.isProxyClass(channel.getClass())) {
- final InvocationHandler handler = Proxy.getInvocationHandler(channel);
- if (handler instanceof CloseShieldChannelHandler) {
+ if (Proxy.getInvocationHandler(channel) instanceof CloseShieldChannelHandler) {
return channel;
}
}
-
// Collect only Channel sub-interfaces.
Class<?>[] ifaces = collectChannelInterfaces(channel.getClass());
if (ifaces.length == 0) {
- ifaces = new Class<?>[] {Channel.class}; // fallback to minimal surface
+ ifaces = new Class<?>[] { Channel.class }; // fallback to root surface
}
-
- return (T) Proxy.newProxyInstance(
- channel.getClass().getClassLoader(), // use delegate's loader
- ifaces,
- new CloseShieldChannelHandler(channel));
- }
-
- private static Class<?>[] collectChannelInterfaces(final Class<?> type) {
- final Set<Class<?>> out = new LinkedHashSet<>();
- collectChannelInterfaces(type, out);
- return out.toArray(new Class<?>[0]);
+ return (T) Proxy.newProxyInstance(channel.getClass().getClassLoader(), // use delegate's loader
+ ifaces, new CloseShieldChannelHandler(channel));
}
- private static void collectChannelInterfaces(final Class<?> type, final Set<Class<?>> out) {
- // Visit interfaces
- for (Class<?> iface : type.getInterfaces()) {
- if (Channel.class.isAssignableFrom(iface) && out.add(iface)) {
- collectChannelInterfaces(iface, out);
- }
- }
+ private CloseShieldChannel() {
+ // no instance
}
}
diff --git a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
index 7fcdea4d5..67386e35c 100644
--- a/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
+++ b/src/main/java/org/apache/commons/io/channels/CloseShieldChannelHandler.java
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.io.channels;
import java.lang.reflect.InvocationHandler;
@@ -28,6 +29,39 @@
final class CloseShieldChannelHandler implements InvocationHandler {
+ /**
+ * Tests whether the given method is allowed to be called after the shield is closed.
+ *
+ * @param declaringClass The class declaring the method.
+ * @param name The method name.
+ * @param parameterCount The number of parameters.
+ * @return {@code true} if the method is allowed after {@code close()}, {@code false} otherwise.
+ */
+ private static boolean isAllowedAfterClose(final Class<?> declaringClass, final String name, final int parameterCount) {
+ // JDK explicitly allows NetworkChannel.supportedOptions() post-close
+ return parameterCount == 0 && name.equals("supportedOptions") && NetworkChannel.class.equals(declaringClass);
+ }
+
+ /**
+ * Tests whether the given method returns 'this' (the channel) as per JDK spec.
+ *
+ * @param declaringClass The class declaring the method.
+ * @param name The method name.
+ * @param parameterCount The number of parameters.
+ * @return {@code true} if the method returns 'this', {@code false} otherwise.
+ */
+ private static boolean returnsThis(final Class<?> declaringClass, final String name, final int parameterCount) {
+ if (SeekableByteChannel.class.equals(declaringClass)) {
+ // SeekableByteChannel.position(long) and truncate(long) return 'this'
+ return parameterCount == 1 && (name.equals("position") || name.equals("truncate"));
+ }
+ if (NetworkChannel.class.equals(declaringClass)) {
+ // NetworkChannel.bind and NetworkChannel.setOption returns 'this'
+ return parameterCount == 1 && name.equals("bind") || parameterCount == 2 && name.equals("setOption");
+ }
+ return false;
+ }
+
private final Channel delegate;
private volatile boolean closed;
@@ -40,96 +74,57 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg
final Class<?> declaringClass = method.getDeclaringClass();
final String name = method.getName();
final int parameterCount = method.getParameterCount();
-
// 1) java.lang.Object methods
if (declaringClass == Object.class) {
return invokeObjectMethod(proxy, method, args);
}
-
// 2) Channel.close(): mark shield closed, do NOT close the delegate
if (parameterCount == 0 && name.equals("close")) {
closed = true;
return null;
}
-
// 3) Channel.isOpen(): reflect shield state only
if (parameterCount == 0 && name.equals("isOpen")) {
return !closed && delegate.isOpen();
}
-
// 4) After the shield is closed, only allow a tiny allowlist of safe queries
if (closed && !isAllowedAfterClose(declaringClass, name, parameterCount)) {
throw new ClosedChannelException();
}
-
// 5) Delegate to the underlying channel and unwrap target exceptions
try {
final Object result = method.invoke(delegate, args);
return returnsThis(declaringClass, name, parameterCount) ? proxy : result;
- } catch (InvocationTargetException e) {
+ } catch (final InvocationTargetException e) {
throw e.getCause();
}
}
- /**
- * Tests whether the given method is allowed to be called after the shield is closed.
- *
- * @param declaringClass The class declaring the method.
- * @param name The method name.
- * @param parameterCount The number of parameters.
- * @return {@code true} if the method is allowed after {@code close()}, {@code false} otherwise.
- */
- private static boolean isAllowedAfterClose(Class<?> declaringClass, String name, int parameterCount) {
- // JDK explicitly allows NetworkChannel.supportedOptions() post-close
- return parameterCount == 0 && name.equals("supportedOptions") && NetworkChannel.class.equals(declaringClass);
- }
-
- /**
- * Tests whether the given method returns 'this' (the channel) as per JDK spec.
- *
- * @param declaringClass The class declaring the method.
- * @param name The method name.
- * @param parameterCount The number of parameters.
- * @return {@code true} if the method returns 'this', {@code false} otherwise.
- */
- private static boolean returnsThis(Class<?> declaringClass, String name, int parameterCount) {
- if (SeekableByteChannel.class.equals(declaringClass)) {
- // SeekableByteChannel.position(long) and truncate(long) return 'this'
- return parameterCount == 1 && (name.equals("position") || name.equals("truncate"));
- }
- if (NetworkChannel.class.equals(declaringClass)) {
- // NetworkChannel.bind and NetworkChannel.setOption returns 'this'
- return parameterCount == 1 && name.equals("bind") || parameterCount == 2 && name.equals("setOption");
- }
- return false;
- }
-
- private Object invokeObjectMethod(final Object proxy, final Method method, final Object[] args)
- throws ReflectiveOperationException {
+ private Object invokeObjectMethod(final Object proxy, final Method method, final Object[] args) {
switch (method.getName()) {
- case "toString":
- return "CloseShield(" + delegate + ")";
- case "hashCode":
- return Objects.hashCode(delegate);
- case "equals": {
- final Object other = args[0];
- if (other == null) {
- return false;
- }
- if (proxy == other) {
- return true;
- }
- if (Proxy.isProxyClass(other.getClass())) {
- final InvocationHandler h = Proxy.getInvocationHandler(other);
- if (h instanceof CloseShieldChannelHandler) {
- return Objects.equals(((CloseShieldChannelHandler) h).delegate, this.delegate);
- }
- }
+ case "toString":
+ return "CloseShield(" + delegate + ")";
+ case "hashCode":
+ return Objects.hashCode(delegate);
+ case "equals": {
+ final Object other = args[0];
+ if (other == null) {
return false;
}
- default:
- // Not possible, all non-final Object methods are handled above
- return null;
+ if (proxy == other) {
+ return true;
+ }
+ if (Proxy.isProxyClass(other.getClass())) {
+ final InvocationHandler h = Proxy.getInvocationHandler(other);
+ if (h instanceof CloseShieldChannelHandler) {
+ return Objects.equals(((CloseShieldChannelHandler) h).delegate, this.delegate);
+ }
+ }
+ return false;
+ }
+ default:
+ // Not possible, all non-final Object methods are handled above
+ return null;
}
}
}
diff --git a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
index 6b704ad1e..ac7e85b7a 100644
--- a/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
+++ b/src/test/java/org/apache/commons/io/channels/CloseShieldChannelTest.java
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.io.channels;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@@ -31,10 +32,14 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import java.nio.channels.AsynchronousByteChannel;
+import java.nio.channels.AsynchronousChannel;
import java.nio.channels.ByteChannel;
import java.nio.channels.Channel;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.GatheringByteChannel;
+import java.nio.channels.InterruptibleChannel;
+import java.nio.channels.MulticastChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.ScatteringByteChannel;
@@ -46,32 +51,54 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
+/**
+ * Tests {@link CloseShieldChannel}.
+ */
class CloseShieldChannelTest {
static Stream<Class<? extends Channel>> testedInterfaces() {
+ // @formatter:off
return Stream.of(
+ AsynchronousByteChannel.class,
+ AsynchronousChannel.class,
+ ByteChannel.class,
Channel.class,
+ GatheringByteChannel.class,
+ InterruptibleChannel.class,
+ MulticastChannel.class,
+ NetworkChannel.class,
+ NetworkChannel.class,
ReadableByteChannel.class,
ScatteringByteChannel.class,
- WritableByteChannel.class,
- GatheringByteChannel.class,
- ByteChannel.class,
SeekableByteChannel.class,
- NetworkChannel.class);
+ WritableByteChannel.class);
+ // @formatter:on
}
@ParameterizedTest
@MethodSource("testedInterfaces")
- void testPreservesInterfaces(Class<? extends Channel> channelClass) {
+ void testCloseDoesNotCloseDelegate(final Class<? extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
- assertNotSame(channel, shield);
- assertTrue(channelClass.isInstance(shield));
+ shield.close();
+ verify(channel, never()).close();
}
@ParameterizedTest
@MethodSource("testedInterfaces")
- void testCloseIsShielded(Class<? extends Channel> channelClass) throws Exception {
+ void testCloseIsIdempotent(final Class<? extends Channel> channelClass) throws Exception {
+ final Channel channel = mock(channelClass);
+ final Channel shield = CloseShieldChannel.wrap(channel);
+ shield.close();
+ assertFalse(shield.isOpen());
+ shield.close();
+ assertFalse(shield.isOpen());
+ verifyNoInteractions(channel);
+ }
+
+ @ParameterizedTest
+ @MethodSource("testedInterfaces")
+ void testCloseIsShielded(final Class<? extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
when(channel.isOpen()).thenReturn(true, false, true, false);
final Channel shield = CloseShieldChannel.wrap(channel);
@@ -79,7 +106,6 @@ void testCloseIsShielded(Class<? extends Channel> channelClass) throws Exception
assertTrue(shield.isOpen(), "isOpen reflects delegate state");
assertFalse(shield.isOpen(), "isOpen reflects delegate state");
verify(channel, times(2)).isOpen();
-
shield.close();
// Reflects shield state after close
assertFalse(shield.isOpen(), "isOpen reflects shield state");
@@ -87,30 +113,17 @@ void testCloseIsShielded(Class<? extends Channel> channelClass) throws Exception
verify(channel, times(2)).isOpen();
}
- @ParameterizedTest
- @MethodSource("testedInterfaces")
- void testCloseDoesNotCloseDelegate(Class<? extends Channel> channelClass) throws Exception {
- final Channel channel = mock(channelClass);
- final Channel shield = CloseShieldChannel.wrap(channel);
- shield.close();
- verify(channel, never()).close();
- }
-
- @ParameterizedTest
- @MethodSource("testedInterfaces")
- void testCloseIsIdempotent(Class<? extends Channel> channelClass) throws Exception {
- final Channel channel = mock(channelClass);
- final Channel shield = CloseShieldChannel.wrap(channel);
- shield.close();
- assertFalse(shield.isOpen());
- shield.close();
- assertFalse(shield.isOpen());
- verifyNoInteractions(channel);
+ @Test
+ void testDoesNotDoubleWrap() {
+ final ByteChannel channel = mock(ByteChannel.class);
+ final ByteChannel shield1 = CloseShieldChannel.wrap(channel);
+ final ByteChannel shield2 = CloseShieldChannel.wrap(shield1);
+ assertSame(shield1, shield2);
}
@ParameterizedTest
@MethodSource("testedInterfaces")
- void testEquals(Class<? extends Channel> channelClass) throws Exception {
+ void testEquals(final Class<? extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
final Channel anotherShield = CloseShieldChannel.wrap(channel);
@@ -120,9 +133,24 @@ void testEquals(Class<? extends Channel> channelClass) throws Exception {
assertTrue(shield.equals(anotherShield), "shields of same delegate are equal");
}
+ @Test
+ void testGatheringByteChannelMethods() throws Exception {
+ final GatheringByteChannel channel = mock(GatheringByteChannel.class);
+ when(channel.isOpen()).thenReturn(true);
+ final GatheringByteChannel shield = CloseShieldChannel.wrap(channel);
+ // Before close write() should delegate
+ when(channel.write(null, 0, 0)).thenReturn(42L);
+ assertEquals(42, shield.write(null, 0, 0));
+ verify(channel).write(null, 0, 0);
+ // After close write() should throw ClosedChannelException
+ shield.close();
+ assertThrows(ClosedChannelException.class, () -> shield.write(null, 0, 0));
+ verifyNoMoreInteractions(channel);
+ }
+
@ParameterizedTest
@MethodSource("testedInterfaces")
- void testHashCode(Class<? extends Channel> channelClass) throws Exception {
+ void testHashCode(final Class<? extends Channel> channelClass) throws Exception {
final Channel channel = mock(channelClass);
final Channel shield = CloseShieldChannel.wrap(channel);
final Channel anotherShield = CloseShieldChannel.wrap(channel);
@@ -130,15 +158,43 @@ void testHashCode(Class<? extends Channel> channelClass) throws Exception {
assertEquals(shield.hashCode(), anotherShield.hashCode(), "shields of same delegate have same hashCode");
}
+ @Test
+ void testNetworkChannelMethods() throws Exception {
+ final NetworkChannel channel = mock(NetworkChannel.class);
+ when(channel.isOpen()).thenReturn(true);
+ final NetworkChannel shield = CloseShieldChannel.wrap(channel);
+ // Before close getOption(), setOption(), getLocalAddress() and bind() should delegate
+ when(channel.getOption(null)).thenReturn("foo");
+ when(channel.setOption(null, null)).thenReturn(channel);
+ when(channel.getLocalAddress()).thenReturn(null);
+ when(channel.bind(null)).thenReturn(channel);
+ assertEquals("foo", shield.getOption(null));
+ assertEquals(shield, shield.setOption(null, null));
+ assertEquals(null, shield.getLocalAddress());
+ assertEquals(shield, shield.bind(null));
+ verify(channel).getOption(null);
+ verify(channel).setOption(null, null);
+ verify(channel).getLocalAddress();
+ verify(channel).bind(null);
+ // After close supportedOptions() should still work
+ shield.close();
+ assertDoesNotThrow(shield::supportedOptions);
+ verify(channel).supportedOptions();
+ // But the remaining methods should throw ClosedChannelException
+ assertThrows(ClosedChannelException.class, () -> shield.setOption(null, null));
+ assertThrows(ClosedChannelException.class, () -> shield.getOption(null));
+ assertThrows(ClosedChannelException.class, shield::getLocalAddress);
+ assertThrows(ClosedChannelException.class, () -> shield.bind(null));
+ verifyNoMoreInteractions(channel);
+ }
+
@ParameterizedTest
@MethodSource("testedInterfaces")
- void testToString(Class<? extends Channel> channelClass) throws Exception {
+ void testPreservesInterfaces(final Class<? extends Channel> channelClass) {
final Channel channel = mock(channelClass);
- when(channel.toString()).thenReturn("MyChannel");
final Channel shield = CloseShieldChannel.wrap(channel);
- final String shieldString = shield.toString();
- assertTrue(shieldString.contains("CloseShield"));
- assertTrue(shieldString.contains("MyChannel"));
+ assertNotSame(channel, shield);
+ assertTrue(channelClass.isInstance(shield));
}
@Test
@@ -171,36 +227,6 @@ void testScatteringByteChannelMethods() throws Exception {
verifyNoMoreInteractions(channel);
}
- @Test
- void testWritableByteChannelMethods() throws Exception {
- final WritableByteChannel channel = mock(WritableByteChannel.class);
- when(channel.isOpen()).thenReturn(true);
- final WritableByteChannel shield = CloseShieldChannel.wrap(channel);
- // Before close write() should delegate
- when(channel.write(null)).thenReturn(42);
- assertEquals(42, shield.write(null));
- verify(channel).write(null);
- // After close write() should throw ClosedChannelException
- shield.close();
- assertThrows(ClosedChannelException.class, () -> shield.write(null));
- verifyNoMoreInteractions(channel);
- }
-
- @Test
- void testGatheringByteChannelMethods() throws Exception {
- final GatheringByteChannel channel = mock(GatheringByteChannel.class);
- when(channel.isOpen()).thenReturn(true);
- final GatheringByteChannel shield = CloseShieldChannel.wrap(channel);
- // Before close write() should delegate
- when(channel.write(null, 0, 0)).thenReturn(42L);
- assertEquals(42, shield.write(null, 0, 0));
- verify(channel).write(null, 0, 0);
- // After close write() should throw ClosedChannelException
- shield.close();
- assertThrows(ClosedChannelException.class, () -> shield.write(null, 0, 0));
- verifyNoMoreInteractions(channel);
- }
-
@Test
void testSeekableByteChannelMethods() throws Exception {
final SeekableByteChannel channel = mock(SeekableByteChannel.class);
@@ -229,41 +255,29 @@ void testSeekableByteChannelMethods() throws Exception {
verifyNoMoreInteractions(channel);
}
+ @ParameterizedTest
+ @MethodSource("testedInterfaces")
+ void testToString(final Class<? extends Channel> channelClass) throws Exception {
+ final Channel channel = mock(channelClass);
+ when(channel.toString()).thenReturn("MyChannel");
+ final Channel shield = CloseShieldChannel.wrap(channel);
+ final String shieldString = shield.toString();
+ assertTrue(shieldString.contains("CloseShield"));
+ assertTrue(shieldString.contains("MyChannel"));
+ }
+
@Test
- void testNetworkChannelMethods() throws Exception {
- final NetworkChannel channel = mock(NetworkChannel.class);
+ void testWritableByteChannelMethods() throws Exception {
+ final WritableByteChannel channel = mock(WritableByteChannel.class);
when(channel.isOpen()).thenReturn(true);
- final NetworkChannel shield = CloseShieldChannel.wrap(channel);
- // Before close getOption(), setOption(), getLocalAddress() and bind() should delegate
- when(channel.getOption(null)).thenReturn("foo");
- when(channel.setOption(null, null)).thenReturn(channel);
- when(channel.getLocalAddress()).thenReturn(null);
- when(channel.bind(null)).thenReturn(channel);
- assertEquals("foo", shield.getOption(null));
- assertEquals(shield, shield.setOption(null, null));
- assertEquals(null, shield.getLocalAddress());
- assertEquals(shield, shield.bind(null));
- verify(channel).getOption(null);
- verify(channel).setOption(null, null);
- verify(channel).getLocalAddress();
- verify(channel).bind(null);
- // After close supportedOptions() should still work
+ final WritableByteChannel shield = CloseShieldChannel.wrap(channel);
+ // Before close write() should delegate
+ when(channel.write(null)).thenReturn(42);
+ assertEquals(42, shield.write(null));
+ verify(channel).write(null);
+ // After close write() should throw ClosedChannelException
shield.close();
- assertDoesNotThrow(shield::supportedOptions);
- verify(channel).supportedOptions();
- // But the remaining methods should throw ClosedChannelException
- assertThrows(ClosedChannelException.class, () -> shield.setOption(null, null));
- assertThrows(ClosedChannelException.class, () -> shield.getOption(null));
- assertThrows(ClosedChannelException.class, shield::getLocalAddress);
- assertThrows(ClosedChannelException.class, () -> shield.bind(null));
+ assertThrows(ClosedChannelException.class, () -> shield.write(null));
verifyNoMoreInteractions(channel);
}
-
- @Test
- void testDoesNotDoubleWrap() {
- final ByteChannel channel = mock(ByteChannel.class);
- final ByteChannel shield1 = CloseShieldChannel.wrap(channel);
- final ByteChannel shield2 = CloseShieldChannel.wrap(shield1);
- assertSame(shield1, shield2);
- }
}