Package com.tryge.xocotl.io

Source Code of com.tryge.xocotl.io.StreamedDuplexChannelTest

package com.tryge.xocotl.io;

import com.tryge.xocotl.util.internal.Nop;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.OngoingStubbing;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

/**
* @author michael.zehender@me.com
*/
public class StreamedDuplexChannelTest {
  private final List<byte[]> DUMMY = new ArrayList<byte[]>(1);

  {
    DUMMY.add(new byte[] { 1 });
  }

  private StreamedDuplexChannel channel;
  private StreamSource source;
  private Stream stream;
  private Thread threadRead;
  private Thread threadWrite;
  private Responder responder;
  private MessageDecoder decoder;
  private ChannelListener channelListener;
  private MessageListener messageListener;

  @Before
  public void setUp() throws IOException {
    source = mock(StreamSource.class);
    stream = mock(Stream.class);
    threadRead = mock(Thread.class);
    threadWrite = mock(Thread.class);

    channelListener = mock(ChannelListener.class);
    messageListener = mock(MessageListener.class);
    decoder = mock(MessageDecoder.class);
    responder = mock(Responder.class);

    ThreadFactory factory = mock(ThreadFactory.class);

    channel = new StreamedDuplexChannel(factory, responder, decoder, source, 32);

    when(source.open()).thenReturn(stream);
    when(factory.newThread(isA(StreamedDuplexChannel.Reader.class))).thenReturn(threadRead);
    when(factory.newThread(isA(StreamedDuplexChannel.Writer.class))).thenReturn(threadWrite);
  }

  @Test(expected = NullPointerException.class)
  public void testChannelListenerProtection() {
    channel.setChannelListener(null);
  }

  @Test
  public void testChannelListenerAcceptance() {
    channel.setChannelListener(channelListener);
  }


  @Test(expected = NullPointerException.class)
  public void testMessageListenerProtection() {
    channel.setMessageListener(null);
  }

  @Test
  public void testMessageListenerAcceptance() {
    channel.setMessageListener(messageListener);
  }

  @Test
  public void testOpenCloseCycle() throws Exception {
    InOrder mocks = inOrder(source, stream, threadRead, threadWrite);

    channel.open();
    assertTrue(channel.isOpen());

    channel.close();
    assertTrue(channel.isClosed());

    mocks.verify(source).open();
    mocks.verify(threadRead).start();
    mocks.verify(threadWrite).start();
    mocks.verify(threadRead).interrupt();
    mocks.verify(threadWrite).interrupt();
    mocks.verify(stream).close();

    // can't verify that join is called
    // mocks.verify(threadRead).join();
    // mocks.verify(threadWrite).join();
  }

  @Test(expected = IllegalStateException.class)
  public void doubleOpenThrows() throws IOException {
    when(source.open()).thenReturn(stream);

    channel.open();
    channel.open();
  }

  @Test
  public void doubleCloseIsIgnored() throws IOException {
    when(source.open()).thenReturn(stream);

    channel.open();
    channel.close();
    channel.close();
  }

  @SuppressWarnings("unchecked")
  @Test(expected = IOException.class)
  public void testOpenThrowsIfStreamSourceThrows() throws IOException {
    when(source.open()).thenThrow(IOException.class);
    channel.open();
  }

  @Test(expected = IOException.class)
  public void testCloseThrowsIfStreamThrows() throws IOException {
    when(source.open()).thenReturn(stream);
    doThrow(IOException.class).when(stream).close();

    channel.open();
    channel.close();
  }

  @Test(expected = IllegalStateException.class)
  public void testSendWhenClosed1() throws IOException {
    Message message = mock(Message.class);
    when(source.open()).thenReturn(stream);

    channel.open();
    channel.close();
    channel.send(message);
  }

  @Test
  public void testSendMessageAndForget() throws Exception {
    final CyclicBarrier barrier = new CyclicBarrier(2);
    Message message = mock(Message.class);
    OutputStream out = mock(OutputStream.class);

    when(source.open()).thenReturn(stream);
    when(stream.getOutputStream()).thenReturn(out);
    doAnswer(new Answer() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        barrier.await(1, TimeUnit.SECONDS);
        return null;
      }
    }).when(message).writeTo(same(out));

    channel.open();

    Thread thread = new Thread(channel.writer);
    thread.start();

    channel.sendAndForget(message);

    barrier.await(1, TimeUnit.SECONDS);

    thread.interrupt();
    channel.sendAndForget(StreamedDuplexChannel.POISON);
    thread.join();

    verify(message).writeTo(same(out));
  }

  @Test
  public void testSendMessageEx() throws Exception {
    final CyclicBarrier barrier = new CyclicBarrier(2);
    Message message = mock(Message.class);
    OutputStream out = mock(OutputStream.class);

    when(source.open()).thenReturn(stream);
    when(stream.getOutputStream()).thenReturn(out);
    doAnswer(new Answer() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        barrier.await(1, TimeUnit.SECONDS);
        return null;
      }
    }).when(message).writeTo(same(out));

    channel.open();

    Thread thread = new Thread(channel.writer);
    thread.start();

    Future<Void> future = channel.sendEx(message);

    barrier.await(1, TimeUnit.SECONDS);

    assertTrue(future.isDone());
    // if it doesn't throw an exception it was successful
    future.get();

    thread.interrupt();
    channel.sendAndForget(StreamedDuplexChannel.POISON);
    thread.join();

    verify(message).writeTo(same(out));
  }

  @Test
  public void testSendMessage() throws Exception {
    final CyclicBarrier barrier = new CyclicBarrier(2);
    Message message = mock(Message.class);
    final Message response = mock(Message.class);
    OutputStream out = mock(OutputStream.class);

    final ResponderMessage responderMessage = new ResponderMessage(message, new Nop());

    when(responder.register(same(channel), same(message))).thenReturn(responderMessage);
    when(message.getId()).thenReturn("test");
    when(response.isResponseTo()).thenReturn("test");
    when(source.open()).thenReturn(stream);
    when(stream.getOutputStream()).thenReturn(out);
    doAnswer(new Answer() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        responderMessage.future().succeeded(response);
        barrier.await(1, TimeUnit.SECONDS);
        return null;
      }
    }).when(message).writeTo(same(out));

    channel.open();

    Thread thread = new Thread(channel.writer);
    thread.start();

    Future<Message> future = channel.send(message);

    barrier.await(1, TimeUnit.SECONDS);

    assertTrue(future.isDone());
    // if it doesn't throw an exception it was successful
    Message received = future.get();

    thread.interrupt();
    channel.sendAndForget(StreamedDuplexChannel.POISON);
    thread.join();

    verify(message).writeTo(same(out));
    assertSame(response, received);
  }

  /**
   * This test case assumes that the read method of the input stream just
   * returns -1 if the stream is closed.
   *
   * @throws InterruptedException
   * @throws IOException
   */
  @Test
  public void testReadCycleEOF() throws Exception {
    InputStream in = testReadCycleStartup(DUMMY, new Answer<Integer>() {
      @Override
      public Integer answer(InvocationOnMock invocation) throws Throwable {
        try {
          //noinspection InfiniteLoopStatement
          for (; ; ) {
            Thread.sleep(100);
          }
        } catch (InterruptedException ie) {
          return -1;
        }
      }
    });

    //noinspection ResultOfMethodCallIgnored
    verify(in, times(2)).read(isA(byte[].class));
  }

  /**
   * This test case assumes that the read method of the input stream throws
   * an exception if closed or something bad happens
   *
   * @throws InterruptedException
   * @throws IOException
   */
  @Test
  public void testReadCycleException() throws Exception {
    InputStream in = testReadCycleStartup(DUMMY, new Answer<Integer>() {
      @Override
      public Integer answer(InvocationOnMock invocation) throws Throwable {
        try {
          //noinspection InfiniteLoopStatement
          for (;;) {
            Thread.sleep(100);
          }
        } catch (InterruptedException ie) {
          throw new IOException("test");
        }
      }
    });

    //noinspection ResultOfMethodCallIgnored
    verify(in, times(2)).read(isA(byte[].class));
  }

  @Test
  public void testReadMessage() throws Exception {
    Message message = mock(Message.class);

    when(decoder.decode(notNull(ByteBuffer.class)))
      .thenReturn(message)
      .thenReturn(null);
    when(message.isResponse()).thenReturn(false);

    testReadCycleStartup(DUMMY, eofAnswer);

    verify(decoder, atLeast(1)).decode(isA(ByteBuffer.class));
    verify(messageListener).onMessage(same(message));
    verify(channelListener).onClose(same(channel));
    verifyNoMoreInteractions(messageListener, channelListener);
  }

  @Test
  public void testReadResponse() throws Exception {
    Message message = mock(Message.class);

    when(decoder.decode(notNull(ByteBuffer.class)))
      .thenReturn(message)
      .thenReturn(null);
    when(message.isResponse()).thenReturn(true);

    testReadCycleStartup(DUMMY, eofAnswer);

    verify(decoder, atLeast(1)).decode(isA(ByteBuffer.class));
    verify(responder).responseReceived(same(channel), same(message));
    verify(channelListener).onClose(same(channel));
    verifyNoMoreInteractions(messageListener, channelListener);
  }

  @Test
  public void testReadMultiple() throws Exception {
    List<byte[]> messages = new ArrayList<byte[]>(3);
    messages.add(new byte[] {1, 2, 3, 4, 1});
    messages.add(new byte[] {2, 3});
    messages.add(new byte[] {4});

    final Message message = mock(Message.class);

    when(decoder.decode(notNull(ByteBuffer.class)))
      .thenAnswer(new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
          ByteBuffer buffer = (ByteBuffer)invocation.getArguments()[0];
          if (buffer.remaining() >= 4) {
            byte[] check = new byte[4];

            buffer.get(check);
            if (check[0] != 1 || check[1] != 2 || check[2] != 3 || check[3] != 4) {
              throw new IllegalArgumentException("buffer doesn't contain the correct data");
            }
            return message;
          } else if (buffer.remaining() >= 1) {
            buffer.get();
          }
          return null;
        }
      });
    when(message.isResponse()).thenReturn(false);

    testReadCycleStartup(messages, eofAnswer);

    verify(messageListener, times(2)).onMessage(same(message));
  }

  private final Answer<Integer> eofAnswer = new Answer<Integer>() {
    @Override
    public Integer answer(InvocationOnMock invocation) throws Throwable {
      return -1;
    }
  };

  private InputStream testReadCycleStartup(List<byte[]> messages, final Answer<Integer> answer) throws Exception {
    final CyclicBarrier barrier = new CyclicBarrier(2);

    InputStream in = mock(InputStream.class);

    when(stream.getInputStream()).thenReturn(in);

    OngoingStubbing<Integer> stubbing = when(in.read(isA(byte[].class)));
    // first return all the messages to be read
    for (byte[] msg : messages) {
      final byte[] message = msg;
      stubbing = stubbing.thenAnswer(new Answer<Integer>() {
        @Override
        public Integer answer(InvocationOnMock invocation) throws Throwable {
          byte[] param = (byte[]) invocation.getArguments()[0];
          if (param.length < message.length) {
            throw new RuntimeException("Test Case Invalid: message must be smaller than param");
          }

          System.arraycopy(message, 0, param, 0, message.length);
          return message.length;
        }
      });
    }
    // further reads execute the specified answer
    stubbing.thenAnswer(new Answer<Integer>() {
      @Override
      public Integer answer(InvocationOnMock invocation) throws Throwable {
        if (!barrier.isBroken()) {
          barrier.await();
        }
        return answer.answer(invocation);
      }
    });

    // initialize the stream
    channel.setChannelListener(channelListener);
    channel.setMessageListener(messageListener);
    channel.open();

    Thread thread = new Thread(channel.reader);
    thread.start();

    barrier.await(1, TimeUnit.SECONDS);

    thread.interrupt();
    thread.join();

    return in;
  }
}
TOP

Related Classes of com.tryge.xocotl.io.StreamedDuplexChannelTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.