package io.scalecube.transport;
import static io.scalecube.transport.TransportTestUtils.createTransport;
import static io.scalecube.transport.TransportTestUtils.destroyTransport;
import static io.scalecube.transport.TransportTestUtils.send;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import io.scalecube.testlib.BaseTest;
import org.junit.After;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Subscriber;
import java.io.IOException;
import java.net.BindException;
import java.nio.channels.UnresolvedAddressException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class TransportTest extends BaseTest {
private static final Logger LOGGER = LoggerFactory.getLogger(TransportTest.class);
// Auto-destroyed on tear down
private Transport client;
private Transport server;
@After
public void tearDown() throws Exception {
destroyTransport(client);
destroyTransport(server);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidListenConfig() {
Transport transport = null;
try {
TransportConfig config = TransportConfig.builder().listenInterface("eth0").listenAddress("10.10.10.10").build();
transport = Transport.bindAwait(config);
} finally {
destroyTransport(transport);
}
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidListenInterface() {
Transport transport = null;
try {
TransportConfig config = TransportConfig.builder().listenInterface("yadayada").build();
transport = Transport.bindAwait(config);
} finally {
destroyTransport(transport);
}
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidListenAddress() {
Transport transport = null;
try {
TransportConfig config = TransportConfig.builder().listenAddress("0.0.0.0").build();
transport = Transport.bindAwait(config);
} finally {
destroyTransport(transport);
}
}
@Test
public void testPortAutoIncrementRaceConditions() throws Exception {
int count = 30;
TransportConfig config = TransportConfig.builder()
.port(6000)
.portAutoIncrement(true)
.portCount(count)
.build();
Map<CompletableFuture<Transport>, Boolean> transports = new ConcurrentHashMap<>();
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < count; i++) {
executor.execute(() -> transports.put(Transport.bind(config), true));
}
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);
CompletableFuture<Void> allFuturesResult =
CompletableFuture.allOf(transports.keySet().toArray(new CompletableFuture[transports.size()]));
// Destroy transports
try {
allFuturesResult.get(60, TimeUnit.SECONDS);
} finally {
for (CompletableFuture<Transport> transportFuture : transports.keySet()) {
if (transportFuture.isDone()) {
destroyTransport(transportFuture.get());
}
}
}
}
@Test
public void testBindExceptionWithoutPortAutoIncrement() throws Exception {
TransportConfig config = TransportConfig.builder()
.port(6000)
.portAutoIncrement(false)
.portCount(100)
.build();
Transport transport1 = null;
Transport transport2 = null;
try {
transport1 = Transport.bindAwait(config);
transport2 = Transport.bindAwait(config);
fail("Didn't get expected bind exception");
} catch (Throwable throwable) {
// Check that get address already in use exception
assertTrue(throwable instanceof BindException || throwable.getMessage().contains("Address already in use"));
} finally {
destroyTransport(transport1);
destroyTransport(transport2);
}
}
@Test
public void testValidListenAddress() {
Transport transport = null;
try {
TransportConfig config = TransportConfig.builder().listenAddress("127.0.0.1").build();
transport = Transport.bindAwait(config);
} finally {
destroyTransport(transport);
}
}
@Test
public void testUnresolvedHostConnection() throws Exception {
client = createTransport();
// create transport with wrong host
CompletableFuture<Void> sendPromise0 = new CompletableFuture<>();
client.send(Address.from("wronghost:49255"), Message.fromData("q"), sendPromise0);
try {
sendPromise0.get(5, TimeUnit.SECONDS);
fail();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
assertNotNull(cause);
assertEquals("Unexpected exception class", UnresolvedAddressException.class, cause.getClass());
}
}
@Test
public void testInteractWithNoConnection() throws Exception {
Address serverAddress = Address.from("localhost:49255");
for (int i = 0; i < 10; i++) {
LOGGER.info("####### {} : iteration = {}", testName.getMethodName(), i);
client = createTransport();
// create transport and don't wait just send message
CompletableFuture<Void> sendPromise0 = new CompletableFuture<>();
client.send(serverAddress, Message.fromData("q"), sendPromise0);
try {
sendPromise0.get(3, TimeUnit.SECONDS);
fail();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
assertNotNull(cause);
assertTrue("Unexpected exception type (expects IOException)", cause instanceof IOException);
}
// send second message: no connection yet and it's clear that there's no connection
CompletableFuture<Void> sendPromise1 = new CompletableFuture<>();
client.send(serverAddress, Message.fromData("q"), sendPromise1);
try {
sendPromise1.get(3, TimeUnit.SECONDS);
fail();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
assertNotNull(cause);
assertTrue("Unexpected exception type (expects IOException)", cause instanceof IOException);
}
destroyTransport(client);
}
}
@Test
public void testPingPongClientTFListenAndServerTFListen() throws Exception {
client = createTransport();
server = createTransport();
server.listen().subscribe(message -> {
Address address = message.sender();
assertEquals("Expected clientAddress", client.address(), address);
send(server, address, Message.fromQualifier("hi client"));
});
CompletableFuture<Message> messageFuture = new CompletableFuture<>();
client.listen().subscribe(messageFuture::complete);
send(client, server.address(), Message.fromQualifier("hello server"));
Message result = messageFuture.get(3, TimeUnit.SECONDS);
assertNotNull("No response from serverAddress", result);
assertEquals("hi client", result.qualifier());
}
@Test
public void testNetworkSettings() throws InterruptedException {
client = createTransport();
server = createTransport();
int lostPercent = 50;
int mean = 0;
client.networkEmulator().setLinkSettings(server.address(), lostPercent, mean);
final List<Message> serverMessageList = new ArrayList<>();
server.listen().subscribe(serverMessageList::add);
int total = 1000;
for (int i = 0; i < total; i++) {
client.send(server.address(), Message.fromData("q" + i));
}
Thread.sleep(1000);
int expectedMax = total / 100 * lostPercent + total / 100 * 5; // +5% for maximum possible lost messages
int size = serverMessageList.size();
assertTrue("expectedMax=" + expectedMax + ", actual size=" + size, size < expectedMax);
}
@Test
public void testPingPongOnSingleChannel() throws Exception {
server = createTransport();
client = createTransport();
server.listen().buffer(2).subscribe(messages -> {
for (Message message : messages) {
Message echo = Message.fromData("echo/" + message.qualifier());
server.send(message.sender(), echo);
}
});
final CompletableFuture<List<Message>> targetFuture = new CompletableFuture<>();
client.listen().buffer(2).subscribe(targetFuture::complete);
client.send(server.address(), Message.fromData("q1"));
client.send(server.address(), Message.fromData("q2"));
List<Message> target = targetFuture.get(1, TimeUnit.SECONDS);
assertNotNull(target);
assertEquals(2, target.size());
}
@Test
public void testPingPongOnSeparateChannel() throws Exception {
server = createTransport();
client = createTransport();
server.listen().buffer(2).subscribe(messages -> {
for (Message message : messages) {
Message echo = Message.fromData("echo/" + message.qualifier());
server.send(message.sender(), echo);
}
});
final CompletableFuture<List<Message>> targetFuture = new CompletableFuture<>();
client.listen().buffer(2).subscribe(targetFuture::complete);
client.send(server.address(), Message.fromData("q1"));
client.send(server.address(), Message.fromData("q2"));
List<Message> target = targetFuture.get(1, TimeUnit.SECONDS);
assertNotNull(target);
assertEquals(2, target.size());
}
@Test
public void testCompleteObserver() throws Exception {
server = createTransport();
client = createTransport();
final CompletableFuture<Boolean> completeLatch = new CompletableFuture<>();
final CompletableFuture<Message> messageLatch = new CompletableFuture<>();
server.listen().subscribe(new Subscriber<Message>() {
@Override
public void onCompleted() {
completeLatch.complete(true);
}
@Override
public void onError(Throwable e) {}
@Override
public void onNext(Message message) {
messageLatch.complete(message);
}
});
CompletableFuture<Void> send = new CompletableFuture<>();
client.send(server.address(), Message.fromData("q"), send);
send.get(1, TimeUnit.SECONDS);
assertNotNull(messageLatch.get(1, TimeUnit.SECONDS));
CompletableFuture<Void> close = new CompletableFuture<>();
server.stop(close);
close.get();
assertTrue(completeLatch.get(1, TimeUnit.SECONDS));
}
@Test
public void testObserverThrowsException() throws Exception {
server = createTransport();
client = createTransport();
server.listen().subscribe(message -> {
String qualifier = message.data();
if (qualifier.startsWith("throw")) {
throw new RuntimeException("" + message);
}
if (qualifier.startsWith("q")) {
Message echo = Message.fromData("echo/" + message.qualifier());
server.send(message.sender(), echo);
}
}, Throwable::printStackTrace);
// send "throw" and raise exception on server subscriber
final CompletableFuture<Message> messageFuture0 = new CompletableFuture<>();
client.listen().subscribe(messageFuture0::complete);
client.send(server.address(), Message.fromData("throw"));
Message message0 = null;
try {
message0 = messageFuture0.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// ignore since expected behavior
}
assertNull(message0);
// send normal message and check whether server subscriber is broken (no response)
final CompletableFuture<Message> messageFuture1 = new CompletableFuture<>();
client.listen().subscribe(messageFuture1::complete);
client.send(server.address(), Message.fromData("q"));
Message transportMessage1 = null;
try {
transportMessage1 = messageFuture1.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// ignore since expected behavior
}
assertNull(transportMessage1);
}
@Test
public void testBlockAndUnblockTraffic() throws Exception {
client = createTransport();
server = createTransport();
server.listen().subscribe(message -> server.send(message.sender(), message));
final List<Message> resp = new ArrayList<>();
client.listen().subscribe(resp::add);
// test at unblocked transport
send(client, server.address(), Message.fromQualifier("q/unblocked"));
// then block client->server messages
Thread.sleep(1000);
client.networkEmulator().block(server.address());
send(client, server.address(), Message.fromQualifier("q/blocked"));
Thread.sleep(1000);
assertEquals(1, resp.size());
assertEquals("q/unblocked", resp.get(0).qualifier());
}
}