import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import io.netty.channel.Channel;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.Promise;
-
+import io.netty.util.concurrent.SucceededFuture;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
public class ServerTest {
SimpleDispatcher clientDispatcher, dispatcher;
- final SimpleSessionListener pce = new SimpleSessionListener();
-
SimpleSession session = null;
ChannelFuture server = null;
InetSocketAddress serverAddress;
private NioEventLoopGroup eventLoopGroup;
-
+ // Dedicated loop group for server, needed for testing reconnection client
+ // With dedicated server group we can simulate session drop by shutting only the server group down
+ private NioEventLoopGroup serverLoopGroup;
@Before
public void setUp() {
final int port = 10000 + (int)(10000 * Math.random());
serverAddress = new InetSocketAddress("127.0.0.1", port);
eventLoopGroup = new NioEventLoopGroup();
+ serverLoopGroup = new NioEventLoopGroup();
+ }
+
+ @After
+ public void tearDown() throws IOException, InterruptedException, ExecutionException {
+ if(server != null) {
+ this.server.channel().close();
+ }
+ this.eventLoopGroup.shutdownGracefully().get();
+ this.serverLoopGroup.shutdownGracefully().get();
+ try {
+ Thread.sleep(500);
+ } catch (final InterruptedException e) {
+ throw new RuntimeException(e);
+ }
}
@Test
- public void testConnectionEstablished() throws Exception {
+ public void testConnectionRefused() throws Exception {
+ this.clientDispatcher = getClientDispatcher();
+
+ final ReconnectStrategy mockReconnectStrategy = getMockedReconnectStrategy();
+
+ this.clientDispatcher.createClient(this.serverAddress, mockReconnectStrategy, SimpleSessionListener::new);
+
+ Mockito.verify(mockReconnectStrategy, timeout(5000).atLeast(2)).scheduleReconnect(any(Throwable.class));
+ }
+
+ @Test
+ public void testConnectionReestablishInitial() throws Exception {
+ this.clientDispatcher = getClientDispatcher();
+
+ final ReconnectStrategy mockReconnectStrategy = getMockedReconnectStrategy();
+
+ this.clientDispatcher.createClient(this.serverAddress, mockReconnectStrategy, SimpleSessionListener::new);
+
+ Mockito.verify(mockReconnectStrategy, timeout(5000).atLeast(2)).scheduleReconnect(any(Throwable.class));
+
final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
+ this.dispatcher = getServerDispatcher(p);
+
+ this.server = this.dispatcher.createServer(this.serverAddress, SimpleSessionListener::new);
+
+ this.server.get();
+
+ assertEquals(true, p.get(3, TimeUnit.SECONDS));
+ }
- this.dispatcher = new SimpleDispatcher(new SessionNegotiatorFactory<SimpleMessage, SimpleSession, SimpleSessionListener>() {
+ @Test
+ public void testConnectionDrop() throws Exception {
+ final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
- @Override
- public SessionNegotiator<SimpleSession> getSessionNegotiator(final SessionListenerFactory<SimpleSessionListener> factory,
- final Channel channel, final Promise<SimpleSession> promise) {
- p.setSuccess(true);
- return new SimpleSessionNegotiator(promise, channel);
- }
- }, new DefaultPromise<SimpleSession>(GlobalEventExecutor.INSTANCE), eventLoopGroup);
+ this.dispatcher = getServerDispatcher(p);
- this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory<SimpleSessionListener>() {
- @Override
- public SimpleSessionListener getSessionListener() {
- return new SimpleSessionListener();
- }
- });
+ this.server = this.dispatcher.createServer(this.serverAddress, SimpleSessionListener::new);
this.server.get();
- this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory<SimpleMessage, SimpleSession, SimpleSessionListener>() {
- @Override
- public SessionNegotiator<SimpleSession> getSessionNegotiator(final SessionListenerFactory<SimpleSessionListener> factory,
- final Channel channel, final Promise<SimpleSession> promise) {
- return new SimpleSessionNegotiator(promise, channel);
- }
- }, new DefaultPromise<SimpleSession>(GlobalEventExecutor.INSTANCE), eventLoopGroup);
+ this.clientDispatcher = getClientDispatcher();
+ final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy();
this.session = this.clientDispatcher.createClient(this.serverAddress,
- new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), new SessionListenerFactory<SimpleSessionListener>() {
- @Override
- public SimpleSessionListener getSessionListener() {
- return new SimpleSessionListener();
- }
- }).get(6, TimeUnit.SECONDS);
+ reconnectStrategy, SimpleSessionListener::new).get(6, TimeUnit.SECONDS);
assertEquals(true, p.get(3, TimeUnit.SECONDS));
+
+ shutdownServer();
+
+ // No reconnect should be scheduled after server drops connection with not-reconnecting client
+ verify(reconnectStrategy, times(0)).scheduleReconnect(any(Throwable.class));
}
@Test
- public void testConnectionFailed() throws IOException, InterruptedException, ExecutionException, TimeoutException {
+ public void testConnectionReestablishAfterDrop() throws Exception {
final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
- this.dispatcher = new SimpleDispatcher(new SessionNegotiatorFactory<SimpleMessage, SimpleSession, SimpleSessionListener>() {
+ this.dispatcher = getServerDispatcher(p);
+
+ this.server = this.dispatcher.createServer(this.serverAddress, SimpleSessionListener::new);
+
+ this.server.get();
+
+ this.clientDispatcher = getClientDispatcher();
- @Override
- public SessionNegotiator<SimpleSession> getSessionNegotiator(final SessionListenerFactory<SimpleSessionListener> factory,
- final Channel channel, final Promise<SimpleSession> promise) {
- p.setSuccess(true);
- return new SimpleSessionNegotiator(promise, channel);
- }
- }, new DefaultPromise<SimpleSession>(GlobalEventExecutor.INSTANCE), eventLoopGroup);
+ final ReconnectStrategyFactory reconnectStrategyFactory = mock(ReconnectStrategyFactory.class);
+ final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy();
+ doReturn(reconnectStrategy).when(reconnectStrategyFactory).createReconnectStrategy();
- this.server = this.dispatcher.createServer(this.serverAddress, new SessionListenerFactory<SimpleSessionListener>() {
- @Override
- public SimpleSessionListener getSessionListener() {
- return new SimpleSessionListener();
- }
- });
+ this.clientDispatcher.createReconnectingClient(this.serverAddress,
+ reconnectStrategyFactory, SimpleSessionListener::new);
+
+ assertEquals(true, p.get(3, TimeUnit.SECONDS));
+ shutdownServer();
+
+ verify(reconnectStrategyFactory, timeout(20000).atLeast(2)).createReconnectStrategy();
+ }
+
+ @Test
+ public void testConnectionEstablished() throws Exception {
+ final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
+
+ this.dispatcher = getServerDispatcher(p);
+
+ this.server = this.dispatcher.createServer(this.serverAddress, SimpleSessionListener::new);
this.server.get();
- this.clientDispatcher = new SimpleDispatcher(new SessionNegotiatorFactory<SimpleMessage, SimpleSession, SimpleSessionListener>() {
- @Override
- public SessionNegotiator<SimpleSession> getSessionNegotiator(final SessionListenerFactory<SimpleSessionListener> factory,
- final Channel channel, final Promise<SimpleSession> promise) {
- return new SimpleSessionNegotiator(promise, channel);
- }
- }, new DefaultPromise<SimpleSession>(GlobalEventExecutor.INSTANCE), eventLoopGroup);
+ this.clientDispatcher = getClientDispatcher();
this.session = this.clientDispatcher.createClient(this.serverAddress,
- new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), new SessionListenerFactory<SimpleSessionListener>() {
- @Override
- public SimpleSessionListener getSessionListener() {
- return new SimpleSessionListener();
- }
- }).get(6, TimeUnit.SECONDS);
+ new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), SimpleSessionListener::new).get(6,
+ TimeUnit.SECONDS);
+
+ assertEquals(true, p.get(3, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testConnectionFailed() throws IOException, InterruptedException, ExecutionException, TimeoutException {
+ final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
+
+ this.dispatcher = getServerDispatcher(p);
+
+ this.server = this.dispatcher.createServer(this.serverAddress, SimpleSessionListener::new);
+
+ this.server.get();
+
+ this.clientDispatcher = getClientDispatcher();
+
+ this.session = this.clientDispatcher.createClient(this.serverAddress,
+ new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), SimpleSessionListener::new).get(6,
+ TimeUnit.SECONDS);
final Future<?> session = this.clientDispatcher.createClient(this.serverAddress,
- new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), new SessionListenerFactory<SimpleSessionListener>() {
- @Override
- public SimpleSessionListener getSessionListener() {
- return new SimpleSessionListener();
- }
- });
+ new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE, 5000), SimpleSessionListener::new);
assertFalse(session.isSuccess());
}
- @After
- public void tearDown() throws IOException, InterruptedException {
- this.server.channel().close();
- this.eventLoopGroup.shutdownGracefully();
- try {
- Thread.sleep(500);
- } catch (final InterruptedException e) {
- throw new RuntimeException(e);
- }
+ @Test
+ public void testNegotiationFailedReconnect() throws Exception {
+ final Promise<Boolean> p = new DefaultPromise<>(GlobalEventExecutor.INSTANCE);
+
+ this.dispatcher = getServerDispatcher(p);
+
+ this.server = this.dispatcher.createServer(this.serverAddress, SimpleSessionListener::new);
+
+ this.server.get();
+
+ this.clientDispatcher = new SimpleDispatcher(
+ (factory, channel, promise) -> new SimpleSessionNegotiator(promise, channel) {
+ @Override
+ protected void startNegotiation() throws Exception {
+ negotiationFailed(new IllegalStateException("Negotiation failed"));
+ }
+ }, new DefaultPromise<>(GlobalEventExecutor.INSTANCE), eventLoopGroup);
+
+ final ReconnectStrategyFactory reconnectStrategyFactory = mock(ReconnectStrategyFactory.class);
+ final ReconnectStrategy reconnectStrategy = getMockedReconnectStrategy();
+ doReturn(reconnectStrategy).when(reconnectStrategyFactory).createReconnectStrategy();
+
+ this.clientDispatcher.createReconnectingClient(this.serverAddress,
+ reconnectStrategyFactory, SimpleSessionListener::new);
+
+
+ // Reconnect strategy should be consulted at least twice, for initial connect and reconnect attempts after drop
+ verify(reconnectStrategyFactory, timeout((int) TimeUnit.MINUTES.toMillis(3)).atLeast(2)).createReconnectStrategy();
+ }
+
+ private SimpleDispatcher getClientDispatcher() {
+ return new SimpleDispatcher((factory, channel, promise) -> new SimpleSessionNegotiator(promise, channel), new DefaultPromise<>(GlobalEventExecutor.INSTANCE), eventLoopGroup);
}
+
+ private ReconnectStrategy getMockedReconnectStrategy() throws Exception {
+ final ReconnectStrategy mockReconnectStrategy = mock(ReconnectStrategy.class);
+ final Future<Void> future = new SucceededFuture<>(GlobalEventExecutor.INSTANCE, null);
+ doReturn(future).when(mockReconnectStrategy).scheduleReconnect(any(Throwable.class));
+ doReturn(5000).when(mockReconnectStrategy).getConnectTimeout();
+ doNothing().when(mockReconnectStrategy).reconnectSuccessful();
+ return mockReconnectStrategy;
+ }
+
+
+ private void shutdownServer() throws InterruptedException, ExecutionException {
+ // Shutdown server
+ server.channel().close().get();
+ // Closing server channel does not close established connections, eventLoop has to be closed as well to simulate dropped session
+ serverLoopGroup.shutdownGracefully().get();
+ }
+
+ private SimpleDispatcher getServerDispatcher(final Promise<Boolean> p) {
+ return new SimpleDispatcher((factory, channel, promise) -> {
+ p.setSuccess(true);
+ return new SimpleSessionNegotiator(promise, channel);
+ }, null, serverLoopGroup);
+ }
+
}