From 7afa237f3f74c2e508d08a2501efd66601431c27 Mon Sep 17 00:00:00 2001 From: Jestan Nirojan Date: Sun, 30 Sep 2012 14:14:34 +0800 Subject: [PATCH] Forward ported SCTP Echo Testcases --- .../transport/socket/AbstractSctpTest.java | 74 +++++++ .../transport/socket/SctpEchoTest.java | 201 ++++++++++++++++++ .../socket/SocketTestPermutation.java | 82 +++++++ .../io/netty/testsuite/util/TestUtils.java | 30 ++- 4 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSctpTest.java create mode 100644 testsuite/src/test/java/io/netty/testsuite/transport/socket/SctpEchoTest.java diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSctpTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSctpTest.java new file mode 100644 index 0000000000..45ec98aa41 --- /dev/null +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/AbstractSctpTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.logging.InternalLogger; +import io.netty.logging.InternalLoggerFactory; +import io.netty.testsuite.transport.socket.SocketTestPermutation.Factory; +import io.netty.testsuite.util.TestUtils; +import io.netty.util.NetworkConstants; +import org.junit.Rule; +import org.junit.rules.TestName; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.Map.Entry; + +public abstract class AbstractSctpTest { + + private static final List, Factory>> COMBO = + SocketTestPermutation.sctpChannel(); + + @Rule + public final TestName testName = new TestName(); + + protected final InternalLogger logger = InternalLoggerFactory.getInstance(getClass()); + + protected volatile ServerBootstrap sb; + protected volatile Bootstrap cb; + protected volatile InetSocketAddress addr; + protected volatile Factory currentBootstrap; + + protected void run() throws Throwable { + int i = 0; + for (Entry, Factory> e: COMBO) { + currentBootstrap = e.getValue(); + sb = e.getKey().newInstance(); + cb = e.getValue().newInstance(); + addr = new InetSocketAddress( + NetworkConstants.LOCALHOST, TestUtils.getFreePort()); + sb.localAddress(addr); + cb.remoteAddress(addr); + + logger.info(String.format( + "Running: %s %d of %d", testName.getMethodName(), ++ i, COMBO.size())); + try { + Method m = getClass().getDeclaredMethod( + testName.getMethodName(), ServerBootstrap.class, Bootstrap.class); + m.invoke(this, sb, cb); + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } finally { + sb.shutdown(); + cb.shutdown(); + } + } + } +} diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SctpEchoTest.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SctpEchoTest.java new file mode 100644 index 0000000000..7899b4cc9f --- /dev/null +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SctpEchoTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.testsuite.transport.socket; + +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundByteHandlerAdapter; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SctpChannel; +import io.netty.handler.codec.sctp.SctpInboundByteStreamHandler; +import io.netty.handler.codec.sctp.SctpMessageCompletionHandler; +import io.netty.handler.codec.sctp.SctpOutboundByteStreamHandler; +import io.netty.testsuite.util.TestUtils; +import org.junit.Assume; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.assertEquals; + +public class SctpEchoTest extends AbstractSctpTest { + + private static final Random random = new Random(); + static final byte[] data = new byte[4096];//could not test ultra jumbo frames + + static { + random.nextBytes(data); + } + + @Test + public void testSimpleEcho() throws Throwable { + Assume.assumeTrue(TestUtils.isSctpSupported()); + run(); + } + + public void testSimpleEcho(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, Integer.MAX_VALUE); + } + + @Test + @Ignore("TODO: fix this after OioSctp EventLoop done") + public void testSimpleEchoWithBoundedBuffer() throws Throwable { + Assume.assumeTrue(TestUtils.isSctpSupported()); + run(); + } + + public void testSimpleEchoWithBoundedBuffer(ServerBootstrap sb, Bootstrap cb) throws Throwable { + testSimpleEcho0(sb, cb, 32); + } + + private static void testSimpleEcho0(ServerBootstrap sb, Bootstrap cb, int maxInboundBufferSize) throws Throwable { + final EchoHandler sh = new EchoHandler(maxInboundBufferSize); + final EchoHandler ch = new EchoHandler(maxInboundBufferSize); + + sb.childHandler(new ChannelInitializer() { + @Override + public void initChannel(SctpChannel c) throws Exception { + c.pipeline().addLast( + new SctpMessageCompletionHandler(), + new SctpInboundByteStreamHandler(0, 0), + new SctpOutboundByteStreamHandler(0, 0), + sh); + } + }); + cb.handler(new ChannelInitializer() { + @Override + public void initChannel(SctpChannel c) throws Exception { + c.pipeline().addLast( + new SctpMessageCompletionHandler(), + new SctpInboundByteStreamHandler(0, 0), + new SctpOutboundByteStreamHandler(0, 0), + ch); + } + }); + + Channel sc = sb.bind().sync().channel(); + Channel cc = cb.connect().sync().channel(); + + for (int i = 0; i < data.length; ) { + int length = Math.min(random.nextInt(1024 * 64), data.length - i); + cc.write(Unpooled.wrappedBuffer(data, i, length)); + i += length; + } + + while (ch.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + while (sh.counter < data.length) { + if (sh.exception.get() != null) { + break; + } + if (ch.exception.get() != null) { + break; + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore. + } + } + + sh.channel.close().sync(); + ch.channel.close().sync(); + sc.close().sync(); + + if (sh.exception.get() != null && !(sh.exception.get() instanceof IOException)) { + throw sh.exception.get(); + } + if (ch.exception.get() != null && !(ch.exception.get() instanceof IOException)) { + throw ch.exception.get(); + } + if (sh.exception.get() != null) { + throw sh.exception.get(); + } + if (ch.exception.get() != null) { + throw ch.exception.get(); + } + } + + private static class EchoHandler extends ChannelInboundByteHandlerAdapter { + private final int maxInboundBufferSize; + volatile Channel channel; + final AtomicReference exception = new AtomicReference(); + volatile int counter; + + EchoHandler(int maxInboundBufferSize) { + this.maxInboundBufferSize = maxInboundBufferSize; + } + + @Override + public ByteBuf newInboundBuffer(ChannelHandlerContext ctx) throws Exception { + return Unpooled.buffer(0, maxInboundBufferSize); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) + throws Exception { + channel = ctx.channel(); + } + + @Override + public void inboundBufferUpdated( + ChannelHandlerContext ctx, ByteBuf in) + throws Exception { + byte[] actual = new byte[in.readableBytes()]; + in.readBytes(actual); + + int lastIdx = counter; + for (int i = 0; i < actual.length; i++) { + assertEquals(data[i + lastIdx], actual[i]); + } + + if (channel.parent() != null) { + channel.write(Unpooled.wrappedBuffer(actual)); + } + + counter += actual.length; + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, + Throwable cause) throws Exception { + if (exception.compareAndSet(null, cause)) { + ctx.close(); + } + } + } +} diff --git a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java index ab35cb658c..2eabc7a89c 100644 --- a/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java +++ b/testsuite/src/test/java/io/netty/testsuite/transport/socket/SocketTestPermutation.java @@ -26,11 +26,15 @@ import io.netty.channel.socket.aio.AioSocketChannel; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSctpServerChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.channel.socket.nio.NioSctpChannel; import io.netty.channel.socket.oio.OioDatagramChannel; import io.netty.channel.socket.oio.OioEventLoopGroup; import io.netty.channel.socket.oio.OioServerSocketChannel; +import io.netty.channel.socket.oio.OioSctpServerChannel; import io.netty.channel.socket.oio.OioSocketChannel; +import io.netty.channel.socket.oio.OioSctpChannel; import java.util.ArrayList; import java.util.List; @@ -130,6 +134,43 @@ final class SocketTestPermutation { return list; } + static List, Factory>> sctpChannel() { + List, Factory>> list = + new ArrayList, Factory>>(); + + // Make the list of SCTP ServerBootstrap factories. + List> sbfs = sctpServerChannel(); + + // Make the list of SCTP Bootstrap factories. + List> cbfs = sctpClientChannel(); + + // Populate the combinations + for (Factory sbf: sbfs) { + for (Factory cbf: cbfs) { + final Factory sbf0 = sbf; + final Factory cbf0 = cbf; + list.add(new Entry, Factory>() { + @Override + public Factory getKey() { + return sbf0; + } + + @Override + public Factory getValue() { + return cbf0; + } + + @Override + public Factory setValue(Factory value) { + throw new UnsupportedOperationException(); + } + }); + } + } + + return list; + } + static List> serverSocket() { List> list = new ArrayList>(); @@ -197,6 +238,47 @@ final class SocketTestPermutation { return list; } + static List> sctpServerChannel() { + List> list = new ArrayList>(); + + // Make the list of ServerBootstrap factories. + list.add(new Factory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap(). + group(new NioEventLoopGroup(), new NioEventLoopGroup()). + channel(NioSctpServerChannel.class); + } + }); + list.add(new Factory() { + @Override + public ServerBootstrap newInstance() { + return new ServerBootstrap(). + group(new OioEventLoopGroup(), new OioEventLoopGroup()). + channel(OioSctpServerChannel.class); + } + }); + + return list; + } + + static List> sctpClientChannel() { + List> list = new ArrayList>(); + list.add(new Factory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(new NioEventLoopGroup()).channel(NioSctpChannel.class); + } + }); + list.add(new Factory() { + @Override + public Bootstrap newInstance() { + return new Bootstrap().group(new OioEventLoopGroup()).channel(OioSctpChannel.class); + } + }); + return list; + } + private SocketTestPermutation() {} interface Factory { diff --git a/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java b/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java index 38e2f8ac78..0eacf567e0 100644 --- a/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java +++ b/testsuite/src/test/java/io/netty/testsuite/util/TestUtils.java @@ -15,15 +15,13 @@ */ package io.netty.testsuite.util; +import com.sun.nio.sctp.SctpChannel; import io.netty.util.NetworkConstants; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; +import java.util.*; public class TestUtils { @@ -78,5 +76,29 @@ public class TestUtils { throw new RuntimeException("unable to find a free port"); } + /** + * Return true if SCTP is supported by the running os. + * + */ + public static boolean isSctpSupported() { + String os = System.getProperty("os.name").toLowerCase(Locale.UK); + if (os.equals("unix") || os.equals("linux") || os.equals("sun") || os.equals("solaris")) { + try { + SctpChannel.open(); + } catch (IOException e) { + // ignore + } catch (UnsupportedOperationException e) { + // This exception may get thrown if the OS does not have + // the shared libs installed. + System.out.print("Not supported: " + e.getMessage()); + return false; + + } + + return true; + } + return false; + } + private TestUtils() { } } \ No newline at end of file