package vnet.sms.gateway.nettytest.embedded; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeoutException; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelEvent; import org.jboss.netty.channel.DefaultExceptionEvent; import org.jboss.netty.channel.ExceptionEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.UpstreamMessageEvent; import org.junit.Test; import com.google.common.base.Predicate; public class TimedFilteringChannelEventFutureTest { @Test public final void assertThatCancelReturnsFalse() { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); assertFalse( "cancel() should always return false as TimedFilteringChannelEventFuture is not cancellable", objectUnderTest.cancel(true)); } @Test public final void assertThatGetReturnsMatchingMessageEventIfFutureCompletesSuccessfully() throws InterruptedException, ExecutionException { final Object expectedPayload = new Object(); final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); final Channel mockChannel = createNiceMock(Channel.class); expect(mockChannel.getRemoteAddress()).andReturn( new InetSocketAddress(1)).anyTimes(); replay(mockChannel); final MessageEvent matchingMessageEvent = new UpstreamMessageEvent( mockChannel, expectedPayload, null); objectUnderTest.acceptsChannelEvent(matchingMessageEvent); assertEquals( "get() should return MessageEvent matching the supplied filter if a matching MessageEvent has been offered", matchingMessageEvent, objectUnderTest.get().get()); } @Test(expected = ExecutionException.class) public final void assertThatGetThrowsExecutionExceptionIfFutureCompletesUnsuccessfully() throws InterruptedException, ExecutionException { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); final ExceptionEvent exEvent = new DefaultExceptionEvent( createNiceMock(Channel.class), new RuntimeException( "assertThatGetThrowsExecutionExceptionIfFutureCompletesUnsuccessfully")); objectUnderTest.acceptsExceptionEvent(exEvent); objectUnderTest.get().get(); } @Test public final void assertThatTimedGetReturnsMatchingMessageEventIfFutureCompletesSuccessfully() throws InterruptedException, ExecutionException, TimeoutException { final Object expectedPayload = new Object(); final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); final Channel mockChannel = createNiceMock(Channel.class); expect(mockChannel.getRemoteAddress()).andReturn( new InetSocketAddress(1)).anyTimes(); replay(mockChannel); final MessageEvent matchingMessageEvent = new UpstreamMessageEvent( mockChannel, expectedPayload, null); objectUnderTest.acceptsChannelEvent(matchingMessageEvent); assertEquals( "get(200L, MILLISECONDS) should return MessageEvent matching the supplied filter if a matching MessageEvent has been offered", matchingMessageEvent, objectUnderTest.get(200L, MILLISECONDS) .get()); } @Test(expected = ExecutionException.class) public final void assertThatTimedGetThrowsExecutionExceptionIfFutureCompletesUnsuccessfully() throws InterruptedException, ExecutionException, TimeoutException { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); final ExceptionEvent exEvent = new DefaultExceptionEvent( createNiceMock(Channel.class), new RuntimeException( "assertThatTimedGetThrowsExecutionExceptionIfFutureCompletesUnsuccessfully")); objectUnderTest.acceptsExceptionEvent(exEvent); objectUnderTest.get(200L, MILLISECONDS).get(); } @Test public final void assertThatTimedGetThrowsTimeoutExceptionIfNoMatchIsOfferedWithinSpecifiedTimeout() throws InterruptedException, ExecutionException { final long timeoutMillis = 200L; final long confidenceInterval = 30L; long before = 0L; try { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); before = System.currentTimeMillis(); objectUnderTest.get(timeoutMillis, MILLISECONDS); fail("get(200L, MILLISECONDS) should have thrown TimeoutException"); } catch (final TimeoutException e) { final long duration = System.currentTimeMillis() - before; assertTrue( "get(" + timeoutMillis + ", MILLISECONDS) did not time out within the specified timeout but " + duration, (timeoutMillis - confidenceInterval < duration) && (timeoutMillis + confidenceInterval > duration)); } } @Test public final void assertThatIsCancelledReturnsFalse() { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); assertFalse( "isCancelled() should always return false as TimedFilteringChannelEventFuture<ChannelEvent> is not cancelable", objectUnderTest.isCancelled()); } @Test public final void asserThatIsDoneReturnsFalseAsLongAsNoMatchingMessageEventIsOffered() { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); assertFalse( "isDone() should return false as long a no MessageEvent matching the supplied filter is offered", objectUnderTest.isDone()); } @Test public final void asserThatIsDoneReturnsTrueIfMatchingMessageEventHasBeenOffered() { final Object expectedPayload = new Object(); final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); final MessageEvent matchingMessageEvent = new UpstreamMessageEvent( createNiceMock(Channel.class), expectedPayload, null); objectUnderTest.acceptsChannelEvent(matchingMessageEvent); assertTrue( "isDone() should return true as soon as a MessageEvent matching the supplied filter has been offered", objectUnderTest.isDone()); } @Test public final void asserThatIsDoneReturnsTrueIfExceptionEventHasBeenRecorded() { final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( ChannelEventFilters.ofType(MessageEvent.class)); final ExceptionEvent exEvent = new DefaultExceptionEvent( createNiceMock(Channel.class), new RuntimeException( "asserThatIsDoneReturnsTrueIfExceptionEventHasBeenRecorded")); objectUnderTest.acceptsExceptionEvent(exEvent); assertTrue( "isDone() should return true as soon as an ExceptionEvent has been recorded", objectUnderTest.isDone()); } @Test public final void assertThatWhenAccessedConcurrentlyOnlyOneThreadGetsToCompleteTimedFilteringChannelEventFuture() throws InterruptedException, BrokenBarrierException, ExecutionException { final int numberOfSuccessfulCompletors = 30; final int numberOfFailedCompletors = 20; final CyclicBarrier startBarrier = new CyclicBarrier( numberOfSuccessfulCompletors + numberOfFailedCompletors + 1); final int numberOfRounds = 200; final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest = new TimedFilteringChannelEventFuture<ChannelEvent>( new Predicate<ChannelEvent>() { @Override public boolean apply(final ChannelEvent event) { return MessageEvent.class.isInstance(event) && String.class.isInstance(MessageEvent.class .cast(event).getMessage()); } }); final ExecutorService exec = Executors .newFixedThreadPool(numberOfSuccessfulCompletors + numberOfFailedCompletors); final CompletionService<List<Completion>> completionService = new ExecutorCompletionService<List<Completion>>( exec); for (int i = 0; i < numberOfFailedCompletors; i++) { completionService.submit(new FailedCompletor(objectUnderTest, numberOfRounds, startBarrier)); } for (int i = 0; i < numberOfSuccessfulCompletors; i++) { completionService.submit(new SuccessfulCompletor(objectUnderTest, numberOfRounds, startBarrier)); } startBarrier.await(); final List<Completion> allCompletions = new ArrayList<Completion>(1); for (int i = 0; i < numberOfSuccessfulCompletors + numberOfFailedCompletors; i++) { allCompletions.addAll(completionService.take().get()); } assertEquals( "Our TimedFilteringChannelEventFuture<ChannelEvent> should have been completed exactly once by one thread", 1, allCompletions.size()); final Completion theCompletion = allCompletions.get(0); try { final ChannelEvent matchedMessageEvent = objectUnderTest.get() .get(); assertEquals( "TimedFilteringChannelEventFuture should have stored the MessageEvent set by the Thread that saw 'true' returned from onMessageEvent(...)", theCompletion.channelEvent.getMessage(), MessageEvent.class .cast(matchedMessageEvent).getMessage()); } catch (final ExecutionException e) { assertEquals( "TimedFilteringChannelEventFuture should have stored the Exception set by the Thread that saw 'true' returned from onExceptionEvent(...)", theCompletion.exception.getMessage(), e.getCause() .getMessage()); } } private static final class Completion { final MessageEvent channelEvent; final Exception exception; Completion(final MessageEvent messageEvent, final Exception exception) { this.channelEvent = messageEvent; this.exception = exception; } } private static final class SuccessfulCompletor implements Callable<List<Completion>> { private final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest; private final long numberOfRounds; private final MessageEvent matchingChannelEvent; private final CyclicBarrier startBarrier; private final Random jitter = new Random( hashCode()); SuccessfulCompletor( final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest, final long numberOfRounds, final CyclicBarrier startBarrier) { this.objectUnderTest = objectUnderTest; this.numberOfRounds = numberOfRounds; this.startBarrier = startBarrier; this.matchingChannelEvent = new UpstreamMessageEvent( createNiceMock(Channel.class), toString(), null); } @Override public List<Completion> call() throws Exception { this.startBarrier.await(); final List<Completion> result = new ArrayList<Completion>(1); Thread.sleep(this.jitter.nextInt(10)); for (long i = 0; i < this.numberOfRounds; i++) { if (this.objectUnderTest .acceptsChannelEvent(this.matchingChannelEvent)) { result.add(new Completion(this.matchingChannelEvent, null)); } Thread.sleep(this.jitter.nextInt(10)); } return result; } @Override public String toString() { return "SuccessfulCompletor@" + this.hashCode(); } } private static final class FailedCompletor implements Callable<List<Completion>> { private final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest; private final long numberOfRounds; private final CyclicBarrier startBarrier; private final Exception exception; private final Random jitter = new Random( hashCode()); FailedCompletor( final TimedFilteringChannelEventFuture<ChannelEvent> objectUnderTest, final long numberOfRounds, final CyclicBarrier startBarrier) { this.objectUnderTest = objectUnderTest; this.numberOfRounds = numberOfRounds; this.startBarrier = startBarrier; this.exception = new RuntimeException(toString()); } @Override public List<Completion> call() throws Exception { this.startBarrier.await(); final List<Completion> result = new ArrayList<Completion>(1); Thread.sleep(this.jitter.nextInt(10)); for (long i = 0; i < this.numberOfRounds; i++) { if (this.objectUnderTest .acceptsExceptionEvent(new DefaultExceptionEvent( createNiceMock(Channel.class), this.exception))) { result.add(new Completion(null, this.exception)); } Thread.sleep(this.jitter.nextInt(10)); } return result; } @Override public String toString() { return "FailedCompletor@" + this.hashCode(); } } }