/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2017, Telestax Inc and individual contributors
* by the @authors tag.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.restcomm.media.network.netty.channel;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import org.junit.After;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.restcomm.media.network.api.AsynchronousNetworkManager;
import org.squirrelframework.foundation.fsm.StateMachineLogger;
import org.squirrelframework.foundation.fsm.StateMachinePerformanceMonitor;
import com.google.common.util.concurrent.FutureCallback;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
/**
* @author Henrique Rosa (henrique.rosa@telestax.com)
*
*/
public class AsyncNettyNetworkChannelFsmTest {
private NettyNetworkChannelFsm fsm;
private StateMachineLogger fsmLogger;
private StateMachinePerformanceMonitor fsmMonitor;
@After
public void after() {
if(fsmLogger != null) {
this.fsmLogger.stopLogging();
this.fsmLogger = null;
}
if (fsm != null) {
if (fsm.isStarted()) {
fsm.terminate();
}
if(this.fsmMonitor != null) {
this.fsm.removeDeclarativeListener(fsmMonitor);
System.out.println(fsmMonitor.getPerfModel());
}
fsm = null;
}
this.fsmMonitor = null;
}
@SuppressWarnings("unchecked")
@Test
public void testChannelLifecycleStepByStep() {
// given
final SocketAddress localAddress = new InetSocketAddress("127.0.0.1", 2427);
final SocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 2727);
final Channel channel = mock(Channel.class);
final AsynchronousNetworkManager<Channel> networkManager = mock(AsynchronousNetworkManager.class);
final NettyNetworkChannelGlobalContext globalContext = new NettyNetworkChannelGlobalContext(networkManager);
this.fsm = NettyNetworkChannelFsmBuilder.INSTANCE.build(globalContext);
this.fsmLogger = new StateMachineLogger(fsm);
this.fsmMonitor = new StateMachinePerformanceMonitor("testChannelLifecycleStepByStep");
final FutureCallback<Void> openCallback = mock(FutureCallback.class);
final FutureCallback<Void> bindCallback = mock(FutureCallback.class);
final FutureCallback<Void> connectCallback = mock(FutureCallback.class);
final FutureCallback<Void> disconnectCallback = mock(FutureCallback.class);
final FutureCallback<Void> closeCallback = mock(FutureCallback.class);
final NettyNetworkChannelTransitionContext openContext = new NettyNetworkChannelTransitionContext().setCallback(openCallback);
final NettyNetworkChannelTransitionContext bindContext = new NettyNetworkChannelTransitionContext().setCallback(bindCallback);
final NettyNetworkChannelTransitionContext connectContext = new NettyNetworkChannelTransitionContext().setCallback(connectCallback);
final NettyNetworkChannelTransitionContext disconnectContext = new NettyNetworkChannelTransitionContext().setCallback(disconnectCallback);
final NettyNetworkChannelTransitionContext closeContext = new NettyNetworkChannelTransitionContext().setCallback(closeCallback);
final ChannelFuture bindFuture = mock(ChannelFuture.class);
final ChannelFuture connectFuture = mock(ChannelFuture.class);
final ChannelFuture disconnectFuture = mock(ChannelFuture.class);
final ChannelFuture closeFuture = mock(ChannelFuture.class);
doAnswer(new SuccessfulAnswer(openCallback)).when(networkManager).openChannel(any(FutureCallback.class));
when(channel.bind(eq(localAddress))).thenReturn(bindFuture);
when(channel.connect(eq(remoteAddress))).thenReturn(connectFuture);
when(channel.disconnect()).thenReturn(disconnectFuture);
when(channel.close()).thenReturn(closeFuture);
when(bindFuture.addListener(any(ChannelFutureListener.class))).thenAnswer(new SuccessfulAnswer(bindCallback));
when(connectFuture.addListener(any(ChannelFutureListener.class))).thenAnswer(new SuccessfulAnswer(connectCallback));
when(disconnectFuture.addListener(any(ChannelFutureListener.class))).thenAnswer(new SuccessfulAnswer(disconnectCallback));
when(closeFuture.addListener(any(ChannelFutureListener.class))).thenAnswer(new SuccessfulAnswer(closeCallback));
// when
fsm.addDeclarativeListener(this.fsmMonitor);
fsm.start();
fsmLogger.startLogging();
fsm.fire(NettyNetworkChannelEvent.OPEN, openContext);
// then
assertEquals(NettyNetworkChannelState.OPENING, fsm.getCurrentState());
verify(networkManager, only()).openChannel(any(FutureCallback.class));
// when
globalContext.setChannel(channel);
fsm.fire(NettyNetworkChannelEvent.OPENED);
// then
assertEquals(NettyNetworkChannelState.OPEN, fsm.getCurrentState());
// when
globalContext.setLocalAddress(localAddress);
fsm.fire(NettyNetworkChannelEvent.BIND, bindContext);
// then
assertEquals(NettyNetworkChannelState.BINDING, fsm.getCurrentState());
verify(bindCallback, only()).onSuccess(null);
verify(channel, times(1)).bind(localAddress);
// when
fsm.fire(NettyNetworkChannelEvent.BOUND);
// then
assertEquals(NettyNetworkChannelState.BOUND, fsm.getCurrentState());
// when
globalContext.setRemoteAddress(remoteAddress);
fsm.fire(NettyNetworkChannelEvent.CONNECT, connectContext);
// then
assertEquals(NettyNetworkChannelState.CONNECTING, fsm.getCurrentState());
verify(channel, times(1)).connect(remoteAddress);
verify(connectCallback, only()).onSuccess(null);
// when
fsm.fire(NettyNetworkChannelEvent.CONNECTED);
// then
assertEquals(NettyNetworkChannelState.CONNECTED, fsm.getCurrentState());
// when
fsm.fire(NettyNetworkChannelEvent.DISCONNECT, disconnectContext);
// then
assertEquals(NettyNetworkChannelState.DISCONNECTING, fsm.getCurrentState());
verify(channel, times(1)).disconnect();
verify(disconnectCallback, only()).onSuccess(null);
// when
fsm.fire(NettyNetworkChannelEvent.DISCONNECTED);
// then
assertEquals(NettyNetworkChannelState.BOUND, fsm.getCurrentState());
assertNull(globalContext.getRemoteAddress());
// when
fsm.fire(NettyNetworkChannelEvent.CLOSE, closeContext);
// then
assertEquals(NettyNetworkChannelState.CLOSING, fsm.getCurrentState());
verify(channel, times(1)).close();
verify(closeCallback, only()).onSuccess(null);
// when
fsm.fire(NettyNetworkChannelEvent.CLOSED);
// then
assertEquals(NettyNetworkChannelState.CLOSED, fsm.getCurrentState());
assertNull(globalContext.getLocalAddress());
assertTrue(fsm.isTerminated());
}
private class SuccessfulAnswer implements Answer<Void> {
private final FutureCallback<?> callback;
public SuccessfulAnswer(FutureCallback<?> callback) {
super();
this.callback = callback;
}
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
callback.onSuccess(null);
return null;
}
}
}