package com.tryge.xocotl.util;
import com.tryge.xocotl.io.*;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.OngoingStubbing;
import org.mockito.stubbing.Stubber;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.*;
/**
* @author michael.zehender@me.com
*/
public class BoundedServerTest {
private ChannelFactory channelFactory;
private ChannelListener channelListener;
private MessageDecoder messageDecoder;
private MessageListener messageListener;
private ServerSocket serverSocket;
private Socket client;
private DuplexChannel channel;
private ArgumentCaptor<ChannelListener> channelListenerCaptor;
private Thread runner;
private TestThreadHelper testThreadHelper;
@Before
public void setUp() throws Exception {
testThreadHelper = new TestThreadHelper();
channelFactory = mock(ChannelFactory.class);
channelListener = mock(ChannelListener.class);
messageDecoder = mock(MessageDecoder.class);
messageListener = mock(MessageListener.class);
serverSocket = mock(ServerSocket.class);
client = mock(Socket.class);
channel = mock(DuplexChannel.class);
channelListenerCaptor = ArgumentCaptor.forClass(ChannelListener.class);
when(serverSocket.accept())
.thenReturn(client)
.thenThrow(new SocketException());
when(channelFactory.newSocketChannel(same(client), same(messageDecoder)))
.thenReturn(channel);
}
@Test
public void canBuildMinimalServer() throws Exception {
TestThreadHelper helper = new TestThreadHelper();
captureChannelListener();
helper.signalServerReady().when(channel).open();
runner = new Thread(new BoundedServer.Builder()
.setChannelFactory(channelFactory)
.setMessageDecoder(messageDecoder)
.setMessageListener(messageListener)
.setMaximumConnections(2)
.build(serverSocket));
runner.start();
helper.waitForServerThread();
signalChannelClose();
verify(serverSocket, times(2)).accept();
}
@Test
public void canCreateChannelOnAccept() throws Exception {
buildServer(2).run();
verify(serverSocket, times(2)).accept();
verify(channelFactory).newSocketChannel(same(client), same(messageDecoder));
verifyChannelIsInitialized(channel);
}
@Test
public void doesRespectMaximumConnections() throws Exception {
InOrder inOrder = inOrder(channel, channelListener, serverSocket);
captureChannelListener();
testThreadHelper.signalServerReady().when(channel).open();
startServer(1);
testThreadHelper.waitForServerThread();
signalChannelClose();
inOrder.verify(serverSocket).accept();
inOrder.verify(channelListener).onClose(same(channel));
inOrder.verify(serverSocket).accept();
}
@Test
public void closesServerSocketOnStop() throws Exception {
testThreadHelper.signalServerReadyAndBlock().when(serverSocket).accept();
startAndStopServer();
verifyServerHasStopped();
}
@Test
public void closesChannelsOnStop() throws Exception {
testThreadHelper.signalServerReady().when(channel).open();
testThreadHelper.doBlock(when(serverSocket.accept()).thenReturn(client));
startAndStopServer();
verifyServerHasStopped();
verify(channel).close();
}
private void startAndStopServer() throws InterruptedException, BrokenBarrierException {
BoundedServer server = startServer(1);
testThreadHelper.waitForServerThread();
server.stop();
}
private BoundedServer startServer(int maximumConnections) {
BoundedServer server = buildServer(maximumConnections);
runner = new Thread(server);
runner.start();
return server;
}
private BoundedServer buildServer(int maximumConnections) {
return new BoundedServer.Builder()
.setChannelFactory(channelFactory)
.setChannelListener(channelListener)
.setMessageDecoder(messageDecoder)
.setMessageListener(messageListener)
.setMaximumConnections(maximumConnections)
.build(serverSocket);
}
private void captureChannelListener() {
doNothing().when(channel).setChannelListener(channelListenerCaptor.capture());
}
private void signalChannelClose() {
ChannelListener listener = channelListenerCaptor.getValue();
listener.onClose(channel);
}
private void verifyChannelIsInitialized(DuplexChannel channel) throws IOException {
verify(channel).setChannelListener(notNull(ChannelListener.class));
verify(channel).setMessageListener(same(messageListener));
verify(channel).open();
}
private void verifyServerHasStopped() throws IOException {
verify(serverSocket).close();
assertFalse(runner.isAlive());
}
private class TestThreadHelper {
private final CyclicBarrier barrier = new CyclicBarrier(2);
public OngoingStubbing<?> doBlock(OngoingStubbing<?> stubbing) {
return stubbing.thenAnswer(newBlockAnswer());
}
public Stubber signalServerReadyAndBlock() {
return doAnswer(newAwaitAndBlockAnswer());
}
public Stubber signalServerReady() {
return doAnswer(newAwaitBarrierAnswer());
}
public void waitForServerThread() throws InterruptedException, BrokenBarrierException {
barrier.await();
}
private Answer newAwaitAndBlockAnswer() {
return new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
newAwaitBarrierAnswer().answer(invocation);
newBlockAnswer().answer(invocation);
return null;
}
};
}
private Answer newAwaitBarrierAnswer() {
return new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
barrier.await();
return null;
}
};
}
private Answer newBlockAnswer() {
return new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(Long.MAX_VALUE);
return null;
}
};
}
}
}