/* * Copyright 2015 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package io.reactivex.netty.channel; import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.embedded.EmbeddedChannel; import io.reactivex.netty.channel.BackpressureManagingHandler.RequestReadIfRequiredEvent; import io.reactivex.netty.channel.events.ConnectionEventListener; import io.reactivex.netty.test.util.MockEventPublisher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExternalResource; import org.junit.runner.Description; import org.junit.runners.model.Statement; import rx.observers.TestSubscriber; import static io.reactivex.netty.test.util.MockEventPublisher.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; public class AbstractConnectionToChannelBridgeTest { @Rule public final ConnectionHandlerRule connectionHandlerRule = new ConnectionHandlerRule(); @Test(timeout = 60000) public void testChannelActive() throws Exception { ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(false); connectionHandlerRule.activateConnectionAndAssert(subscriber); assertThat("Duplicate channel active event sent a notification", subscriber.getOnNextEvents(), hasSize(1)); connectionHandlerRule.handler.channelActive(connectionHandlerRule.ctx); // duplicate event should not trigger onNext. /*One item from activation*/ assertThat("Duplicate channel active event sent a notification", subscriber.getOnNextEvents(), hasSize(1)); } @Test(timeout = 60000) public void testEagerContentSubscriptionFail() throws Exception { connectionHandlerRule.channel.config().setAutoRead(true); // should mandate eager content subscription ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(false); connectionHandlerRule.activateConnectionAndAssert(subscriber); ConnectionInputSubscriber inputSubscriber = connectionHandlerRule.enableConnectionInputSubscriber(); subscriber.assertTerminalEvent(); assertThat("Unexpected first notification kind.", inputSubscriber.getOnErrorEvents(), hasSize(1)); } @Test(timeout = 60000) public void testEagerContentSubscriptionPass() throws Exception { connectionHandlerRule.channel.config().setAutoRead(true); // should mandate eager content subscription ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(true); connectionHandlerRule.activateConnectionAndAssert(subscriber); // eagerly subscribes to input. ConnectionInputSubscriber inputSubscriber = subscriber.getInputSubscriber(); assertThat("Unexpected notifications count after channel active.", inputSubscriber.getOnNextEvents(), hasSize(0)); inputSubscriber.assertNoErrors(); assertThat("Input subscriber is unsubscribed.", inputSubscriber.isUnsubscribed(), is(false)); } @Test(timeout = 60000) public void testLazyContentSubscription() throws Exception { connectionHandlerRule.channel.config().setAutoRead(false); ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(false); //lazy input sub. connectionHandlerRule.activateConnectionAndAssert(subscriber); ConnectionInputSubscriber inputSubscriber = connectionHandlerRule.enableConnectionInputSubscriber(); inputSubscriber.assertNoErrors(); assertThat("Unexpected on next events after channel active.", inputSubscriber.getOnNextEvents(), hasSize(0)); assertThat("Unexpected on completed events after channel active.", inputSubscriber.getOnCompletedEvents(), hasSize(0)); assertThat("Input subscriber is unsubscribed.", inputSubscriber.isUnsubscribed(), is(false)); connectionHandlerRule.startRead(); connectionHandlerRule.testSendInputMsgs(inputSubscriber, "hello1"); } @Test(timeout = 60000) public void testInputCompleteOnChannelUnregister() throws Exception { connectionHandlerRule.channel.config().setAutoRead(false); ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(true); connectionHandlerRule.activateConnectionAndAssert(subscriber); ConnectionInputSubscriber inputSubscriber = subscriber.getInputSubscriber(); // since sub is eager. connectionHandlerRule.startRead(); connectionHandlerRule.testSendInputMsgs(inputSubscriber, "hello1"); assertThat("Unexpected notifications count after channel active.", inputSubscriber.getOnNextEvents(), hasSize(1)); inputSubscriber.unsubscribe(); // else channel close will generate error if subscribed connectionHandlerRule.handler.channelUnregistered(connectionHandlerRule.ctx); inputSubscriber.assertNoErrors(); assertThat("Unexpected notifications count after channel active.", inputSubscriber.getOnNextEvents(), hasSize(1)); } @Test(timeout = 60000) public void testMultipleInputSubscriptions() throws Exception { connectionHandlerRule.channel.config().setAutoRead(false); ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(true); connectionHandlerRule.activateConnectionAndAssert(subscriber); // one subscription ConnectionInputSubscriber inputSubscriber = connectionHandlerRule.enableConnectionInputSubscriber(); inputSubscriber.assertTerminalEvent(); assertThat("Unexpected on next events for second subscriber.", inputSubscriber.getOnNextEvents(), hasSize(0)); assertThat("Unexpected notification type for second subscriber.", inputSubscriber.getOnErrorEvents(), hasSize(1)); } @Test(timeout = 60000) public void testInputSubscriptionReset() throws Exception { connectionHandlerRule.channel.config().setAutoRead(false); ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(true); connectionHandlerRule.activateConnectionAndAssert(subscriber); // one subscription ConnectionInputSubscriber inputSubscriber = connectionHandlerRule.enableConnectionInputSubscriber(); inputSubscriber.assertTerminalEvent(); assertThat("Unexpected on next events for second subscriber.", inputSubscriber.getOnNextEvents(), hasSize(0)); connectionHandlerRule.handler.userEventTriggered(connectionHandlerRule.ctx, new ConnectionInputSubscriberResetEvent() { }); inputSubscriber = connectionHandlerRule.enableConnectionInputSubscriber(); assertThat("Unexpected on next count for input subscriber post reset.", inputSubscriber.getOnNextEvents(), hasSize(0)); assertThat("Unexpected on error count for input subscriber post reset.", inputSubscriber.getOnErrorEvents(), hasSize(0)); assertThat("Unexpected on completed count for input subscriber post reset.", inputSubscriber.getOnCompletedEvents(), hasSize(0)); } @Test(timeout = 60000) public void testErrorBeforeConnectionActive() throws Exception { ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(true); final NullPointerException exception = new NullPointerException(); connectionHandlerRule.handler.exceptionCaught(connectionHandlerRule.ctx, exception); subscriber.assertTerminalEvent(); assertThat("Unexpected on next notifications count post exception.", subscriber.getOnNextEvents(), hasSize(0)); assertThat("Unexpected notification type post exception.", subscriber.getOnErrorEvents(), hasSize(1)); } @Test(timeout = 60000) public void testErrorPostInputSubscribe() throws Exception { ConnectionSubscriber subscriber = connectionHandlerRule.enableConnectionSubscriberAndAssert(true); connectionHandlerRule.activateConnectionAndAssert(subscriber); ConnectionInputSubscriber inputSubscriber = subscriber.getInputSubscriber(); // since sub is eager. assertThat("Unexpected on next notifications count pre exception.", inputSubscriber.getOnNextEvents(), hasSize(0)); assertThat("Unexpected on error notifications count pre exception.", inputSubscriber.getOnErrorEvents(), hasSize(0)); assertThat("Unexpected on completed notifications count pre exception.", inputSubscriber.getOnCompletedEvents(), hasSize(0)); final NullPointerException exception = new NullPointerException(); connectionHandlerRule.handler.exceptionCaught(connectionHandlerRule.ctx, exception); inputSubscriber.assertTerminalEvent(); assertThat("Unexpected on next notifications count post exception.", inputSubscriber.getOnNextEvents(), hasSize(0)); assertThat("Unexpected on error notifications count post exception.", inputSubscriber.getOnErrorEvents(), hasSize(1)); } public static class ConnectionHandlerRule extends ExternalResource { private Channel channel; private ChannelHandlerContext ctx; private AbstractConnectionToChannelBridge<String, String> handler; @Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { channel = new EmbeddedChannel(new ChannelDuplexHandler()); ctx = channel.pipeline().firstContext(); handler = new AbstractConnectionToChannelBridge<String, String>("foo", new ConnectionEventListener() { }, disabled()) { }; base.evaluate(); } }; } public void startRead() throws Exception { handler.userEventTriggered(ctx, new RequestReadIfRequiredEvent() { @Override protected boolean shouldReadMore(ChannelHandlerContext ctx) { return true; } }); } public ConnectionSubscriber enableConnectionSubscriberAndAssert(boolean eagerSubToInput) throws Exception { ConnectionSubscriber toReturn = new ConnectionSubscriber(eagerSubToInput, this); handler.userEventTriggered(ctx, new ChannelSubscriberEvent<>(toReturn)); assertThat("Unexpected on next notifications count before channel active.", toReturn.getOnNextEvents(), hasSize(0)); assertThat("Unexpected on error notifications count before channel active.", toReturn.getOnErrorEvents(), hasSize(0)); assertThat("Unexpected on complete notifications count before channel active.", toReturn.getOnCompletedEvents(), hasSize(0)); return toReturn; } public ConnectionInputSubscriber enableConnectionInputSubscriber() throws Exception { ConnectionInputSubscriber toReturn = new ConnectionInputSubscriber(); handler.userEventTriggered(ctx, new ConnectionInputSubscriberEvent<>(toReturn)); return toReturn; } public void activateConnectionAndAssert(ConnectionSubscriber subscriber) throws Exception { handler.userEventTriggered(ctx, EmitConnectionEvent.INSTANCE); subscriber.assertTerminalEvent(); subscriber.assertNoErrors(); assertThat("No connections received.", subscriber.getOnNextEvents(), is(not(empty()))); assertThat("Unexpected channel in new connection.", subscriber.getOnNextEvents().get(0), is(channel)); } public void testSendInputMsgs(ConnectionInputSubscriber inputSubscriber, String... msgs) throws Exception { for (String msg: msgs) { handler.channelRead(ctx, msg); } assertThat("Unexpected notifications count after read.", inputSubscriber.getOnNextEvents(), hasSize(msgs.length)); assertThat("Unexpected notifications count after read.", inputSubscriber.getOnNextEvents(), contains(msgs)); assertThat("Input subscriber is unsubscribed after read.", inputSubscriber.isUnsubscribed(), is(false)); } } public static class ConnectionSubscriber extends TestSubscriber<Channel> { private final boolean subscribeToInput; private final ConnectionHandlerRule rule; private ConnectionInputSubscriber inputSubscriber; public ConnectionSubscriber(boolean subscribeToInput, ConnectionHandlerRule rule) { this.subscribeToInput = subscribeToInput; this.rule = rule; } @Override public void onNext(Channel channel) { super.onNext(channel); try { if (subscribeToInput) { inputSubscriber = rule.enableConnectionInputSubscriber(); } } catch (Exception e) { onError(e); } } public ConnectionInputSubscriber getInputSubscriber() { return inputSubscriber; } } public static class ConnectionInputSubscriber extends TestSubscriber<String> { private final long requestAtStart; public ConnectionInputSubscriber() { this(Long.MAX_VALUE); } public ConnectionInputSubscriber(long requestAtStart) { this.requestAtStart = requestAtStart; } @Override public void onStart() { request(requestAtStart); } } }