/*
* 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.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.FileRegion;
import io.netty.channel.embedded.EmbeddedChannel;
import io.reactivex.netty.test.util.FlushSelector;
import io.reactivex.netty.test.util.MockEventPublisher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import rx.Observable;
import rx.functions.Action1;
import rx.observers.TestSubscriber;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import static io.reactivex.netty.test.util.MockEventPublisher.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
public class DefaultChannelOperationsTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Rule
public final ChannelOpRule channelOpRule = new ChannelOpRule();
@Test(timeout = 60000)
public void testWrite() throws Exception {
final String msg = "Hello";
Observable<Void> writeO = channelOpRule.channelOperations.write(ChannelOpRule.bbJust(msg));
_testWrite(writeO, msg);
}
@Test(timeout = 60000)
public void testWriteWithFlushSelector() throws Exception {
final String msg1 = "Hello1";
final String msg2 = "Hello2";
Observable<Void> writeO = channelOpRule.channelOperations.write(ChannelOpRule.bbJust(msg1, msg2),
new FlushSelector<ByteBuf>(1));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteAndFlushOnEach() throws Exception {
final String msg1 = "Hello1";
final String msg2 = "Hello2";
Observable<Void> writeO = channelOpRule.channelOperations.writeAndFlushOnEach(ChannelOpRule.bbJust(msg1, msg2));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteString() throws Exception {
final String msg = "Hello";
Observable<Void> writeO = channelOpRule.channelOperations.writeString(Observable.just(msg));
_testWrite(writeO, msg);
}
@Test(timeout = 60000)
public void testWriteStringWithFlushSelector() throws Exception {
final String msg1 = "Hello1";
final String msg2 = "Hello2";
Observable<Void> writeO = channelOpRule.channelOperations.writeString(Observable.just(msg1, msg2),
new FlushSelector<String>(1));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteStringAndFlushOnEach() throws Exception {
final String msg1 = "Hello1";
final String msg2 = "Hello2";
Observable<Void> writeO = channelOpRule.channelOperations.writeStringAndFlushOnEach(Observable.just(msg1, msg2));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteBytes() throws Exception {
final String msg = "Hello";
Observable<Void> writeO = channelOpRule.channelOperations.writeBytes(Observable.just(msg.getBytes()));
_testWrite(writeO, msg);
}
@Test(timeout = 60000)
public void testWriteBytesWithFlushSelector() throws Exception {
final String msg1 = "Hello1";
final String msg2 = "Hello2";
Observable<Void> writeO = channelOpRule.channelOperations.writeBytes(Observable.just(msg1.getBytes(),
msg2.getBytes()),
new FlushSelector<byte[]>(1));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteBytesAndFlushOnEach() throws Exception {
final String msg1 = "Hello1";
final String msg2 = "Hello2";
Observable<Void> writeO = channelOpRule.channelOperations.writeBytesAndFlushOnEach(
Observable.just(msg1.getBytes(),
msg2.getBytes()));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteFileRegion() throws Exception {
FileRegion msg = new DefaultFileRegion(folder.newFile("msg.txt"), 0, 0);
Observable<Void> writeO = channelOpRule.channelOperations.writeFileRegion(Observable.just(msg));
_testWrite(writeO, msg);
}
@Test(timeout = 60000)
public void testWriteFileRegionWithFlushSelector() throws Exception {
FileRegion msg1 = new DefaultFileRegion(folder.newFile("msg1.txt"), 0, 0);
FileRegion msg2 = new DefaultFileRegion(folder.newFile("msg2.txt"), 0, 0);
Observable<Void> writeO = channelOpRule.channelOperations.writeFileRegion(Observable.just(msg1, msg2),
new FlushSelector<FileRegion>(1));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testWriteFileRegionAndFlushOnEach() throws Exception {
FileRegion msg1 = new DefaultFileRegion(folder.newFile("msg1.txt"), 0, 0);
FileRegion msg2 = new DefaultFileRegion(folder.newFile("msg2.txt"), 0, 0);
Observable<Void> writeO = channelOpRule.channelOperations
.writeFileRegionAndFlushOnEach(Observable.just(msg1, msg2));
_testWithFlushSelector(writeO, msg1, msg2);
}
@Test(timeout = 60000)
public void testFlush() throws Exception {
String msg = "Hello";
channelOpRule.channel.write(Unpooled.buffer().writeBytes(msg.getBytes()));
channelOpRule.channelOperations.flush();
channelOpRule.verifyOutboundMessages(msg);
}
@Test(timeout = 60000)
public void testCloseWithFlush() throws Exception {
TestSubscriber<Void> subscriber = new TestSubscriber<>();
channelOpRule.channelOperations.close().subscribe(subscriber);
subscriber.assertTerminalEvent();
subscriber.assertNoErrors();
assertThat("Channel not closed.", channelOpRule.channel.isOpen(), is(false));
}
@Test(timeout = 60000)
public void testCloseWithoutFlush() throws Exception {
TestSubscriber<Void> subscriber = new TestSubscriber<>();
channelOpRule.channel.write("Hello");
channelOpRule.channelOperations.close(false).subscribe(subscriber);
subscriber.assertTerminalEvent();
subscriber.assertNoErrors();
channelOpRule.verifyOutboundMessages();
assertThat("Channel not closed.", channelOpRule.channel.isOpen(), is(false));
}
private void _testWithFlushSelector(Observable<Void> writeObservable, Object expected1, Object expected2) {
final TestSubscriber<Void> writeSub = new TestSubscriber<>();
writeObservable.subscribe(writeSub);
assertThat("Unexpected write subscribers on the channel.", channelOpRule.writeObservableSubscribers,
hasSize(1));
ChannelOpRule.TestWriteSubscriber testSubscriber = channelOpRule.writeObservableSubscribers.remove(0);
channelOpRule.verifyOutboundMessages(expected1);
channelOpRule.channel.outboundMessages().clear();
testSubscriber.requestMore(1);
channelOpRule.verifyOutboundMessages(expected2);
testSubscriber.awaitTerminalEvent();
testSubscriber.finishOverarchingWritePromiseIfAllPromisesFinished();
writeSub.assertNoErrors();
writeSub.assertTerminalEvent();
}
private void _testWrite(Observable<Void> writeObservable, Object expected) {
final TestSubscriber<Void> writeSub = new TestSubscriber<>();
writeObservable.subscribe(writeSub);
assertThat("Unexpected write subscribers on the channel.", channelOpRule.writeObservableSubscribers,
hasSize(1));
ChannelOpRule.TestWriteSubscriber testSubscriber = channelOpRule.writeObservableSubscribers.remove(0);
testSubscriber.finishOverarchingWritePromiseIfAllPromisesFinished();
testSubscriber.awaitTerminalEvent();
writeSub.assertNoErrors();
writeSub.assertTerminalEvent();
channelOpRule.verifyOutboundMessages(expected);
}
@Test(timeout = 60000)
public void testCloseListener() throws Exception {
Observable<Void> closeListener = channelOpRule.channelOperations.closeListener();
TestSubscriber<Void> subscriber = new TestSubscriber<>();
closeListener.subscribe(subscriber);
subscriber.assertNoTerminalEvent();
subscriber.unsubscribe();
subscriber.assertNoTerminalEvent();
channelOpRule.channel.close().sync();
subscriber.assertNoTerminalEvent();
}
public static class ChannelOpRule extends ExternalResource {
private DefaultChannelOperations<ByteBuf> channelOperations;
private EmbeddedChannel channel;
private List<TestWriteSubscriber> writeObservableSubscribers;
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
writeObservableSubscribers = new ArrayList<>();
/*Since, the appropriate handler is not added to the pipeline that handles O<> writes.*/
channel = new EmbeddedChannel(new HandleObservableWrite(writeObservableSubscribers));
channelOperations = new DefaultChannelOperations<>(channel, null, disabled());
base.evaluate();
}
};
}
public static Observable<ByteBuf> bbJust(String... items) {
List<ByteBuf> bbItems = new ArrayList<>();
for (String item : items) {
bbItems.add(Unpooled.buffer().writeBytes(item.getBytes()));
}
return Observable.from(bbItems);
}
public void verifyOutboundMessages(Object... msgs) {
boolean stringConversionRequired = msgs != null && msgs.length != 0 && msgs[0] instanceof String;
final List<Object> outMsgsToTest = new ArrayList<>();
for (Object next : channel.outboundMessages()) {
if (stringConversionRequired) {
if (next instanceof ByteBuf) {
outMsgsToTest.add(((ByteBuf) next).toString(Charset.defaultCharset()));
}
} else {
outMsgsToTest.add(next);
}
}
if (null == msgs || msgs.length == 0) {
assertThat("Unexpected messages written on the channel.", outMsgsToTest, is(empty()));
} else {
assertThat("Unexpected messages written on the channel.", outMsgsToTest, contains(msgs));
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static class HandleObservableWrite extends ChannelDuplexHandler {
private final List<TestWriteSubscriber> writeObservableSubscribers;
public HandleObservableWrite(List<TestWriteSubscriber> writeObservableSubscribers) {
this.writeObservableSubscribers = writeObservableSubscribers;
}
@Override
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
throws Exception {
if (msg instanceof Observable) {
Observable msgO = (Observable) msg;
final TestWriteSubscriber testSubscriber = new TestWriteSubscriber(promise);
msgO.doOnNext(new Action1() {
@Override
public void call(Object o) {
final ChannelPromise channelPromise = ctx.newPromise();
testSubscriber.allPromises.add(channelPromise);
if (o instanceof String) {
o = Unpooled.buffer().writeBytes(((String) o).getBytes());
} else if (o instanceof byte[]) {
o = Unpooled.buffer().writeBytes((byte[]) o);
}
ctx.write(o, channelPromise);
}
}).doOnError(new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
ctx.fireExceptionCaught(throwable);
}
}).subscribe(testSubscriber);
writeObservableSubscribers.add(testSubscriber);
} else {
super.write(ctx, msg, promise);
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static class TestWriteSubscriber extends TestSubscriber {
private final List<ChannelPromise> allPromises = new ArrayList<>();
private final ChannelPromise overarchingPromise;
public TestWriteSubscriber(ChannelPromise promise) {
overarchingPromise = promise;
}
public void finishOverarchingWritePromiseIfAllPromisesFinished() {
for (ChannelPromise aPromise : allPromises) {
if (aPromise.isDone()) {
if (!aPromise.isSuccess()) {
overarchingPromise.tryFailure(aPromise.cause());
return;
}
} else {
overarchingPromise.tryFailure(new IllegalStateException("A write promise did not complete."));
return;
}
}
overarchingPromise.trySuccess();
}
@Override
public void onStart() {
request(1);
}
}
}
}