/*******************************************************************************
* Copyright 2015 Klaus Pfeiffer <klaus@allpiper.com>
*
* 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 com.jfastnet;
import com.esotericsoftware.kryo.Kryo;
import com.jfastnet.config.SerialiserConfig;
import com.jfastnet.messages.*;
import com.jfastnet.peers.javanet.JavaNetPeer;
import com.jfastnet.processors.MessageLogProcessor;
import com.jfastnet.serialiser.KryoSerialiser;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import org.junit.After;
import org.junit.Assert;
import org.objenesis.strategy.StdInstantiatorStrategy;
import java.util.ArrayList;
import java.util.List;
/** @author Klaus Pfeiffer - klaus@allpiper.com */
@Slf4j
public abstract class AbstractTest {
public interface Callable<V> {
V call();
}
public static final int DEFAULT_INTERVAL = 100;
public Server server;
public List<Client> clients;
public Client client1;
public Client client2;
public void start() {
start(1);
}
public void start(int clients ) {
start(clients, () -> newClientConfig());
}
@SneakyThrows
public void start(int clientCount, Config serverConfig, Callable<Config> clientConfig ) {
start(clientCount, clientConfig);
}
@SneakyThrows
public void start(int clientCount, Callable<Config> clientConfig ) {
//server = new Server(serverConfig);
server = new Server(clientConfig.call().setBindPort(15150).setPort(0));
clients = new ArrayList<>();
for (int i = 0; i < clientCount; i++) {
Config config = clientConfig.call();
config.setSenderId(i + 1);
clients.add(new Client(config));
if (i == 0) client1 = clients.get(i);
if (i == 1) client2 = clients.get(i);
}
log.info("main thread id: {}", Thread.currentThread().getId());
log.info("Start server");
server.start();
log.info("Start clients");
clients.forEach(Client::start);
log.info("Wait for clients to successfully connect to server");
// We need this loop to also process the server while waiting for the clients to connect
final boolean[] proceed = {false};
while (!proceed[0]) {
proceed[0] = true;
server.process();
for (Client client : clients) {
client.process();
if (!client.isConnected()) {
proceed[0] = false;
log.info("Client {} not connected!", client.getConfig().senderId);
}
}
Thread.sleep(200);
}
clients.forEach(Client::blockingWaitUntilConnected);
long unconnectedClientCount = clients.stream().filter(client -> !client.isConnected()).count();
if (unconnectedClientCount > 0) {
Assert.fail(unconnectedClientCount + " clients could not connect!");
}
log.info("All clients connected successfully!");
waitForCondition("Not all clients joined.", 3,
() -> server.state.getClientStates().size() == clientCount,
() -> "Clients: " + server.getState().getClientStates().size() + ", Expected: " + clientCount);
}
public Message getLastReceivedMessage() {
return getLastReceivedMessageFromLog(server.getState().getProcessorOf(MessageLogProcessor.class).getMessageLog().getReceived(), true, null);
}
public Message getLastReceivedMessage(int clientIndex) {
return getLastReceivedMessage(clientIndex, true, null);
}
public Message getLastReceivedMessage(int clientIndex, Class messageType) {
return getLastReceivedMessage(clientIndex, true, messageType);
}
public Message getLastReceivedMessage(int clientIndex, boolean ignoreSystemMessages, Class messageType) {
CircularFifoQueue<Message> received = clients.get(clientIndex).getState().getProcessorOf(MessageLogProcessor.class).getMessageLog().getReceived();
return getLastReceivedMessageFromLog(received, ignoreSystemMessages, messageType);
}
public Message getLastReceivedMessageFromLog(CircularFifoQueue<Message> received, boolean ignoreSystemMessages, Class type) {
if (received.size() == 0) {
return null;
}
for (int i = received.size() - 1; i >= 0; i--) {
Message message = received.get(i);
if (type != null) {
if (!type.isAssignableFrom(message.getClass())) {
continue;
}
} else if (ignoreSystemMessages) {
if (message instanceof SequenceKeepAlive) continue;
if (message instanceof AckMessage) continue;
if (message instanceof ConnectResponse) continue;
if (message instanceof RequestSeqIdsMessage) continue;
if (message instanceof StackedMessage) continue;
}
return message;
}
// If all are system messages, return the last
return received.get(received.size() - 1);
}
public List<Message> getLastReceivedMessagesFromLog(Class type) {
List<Message> messages = new ArrayList<>();
CircularFifoQueue<Message> received = server.getState().getProcessorOf(MessageLogProcessor.class).getMessageLog().getReceived();
if (received.size() == 0) {
return null;
}
for (int i = received.size() - 1; i >= 0; i--) {
Message message = received.get(i);
if (type != null) {
if (type.isAssignableFrom(message.getClass())) {
messages.add(message);
}
}
}
return messages;
}
public boolean allClientsReceivedMessageTypeOf(Class msgClass) {
for (int i = 0; i < clients.size(); i++) {
Message msg = getLastReceivedMessage(i);
if (msg == null) {
log.info("Message from client index {} was null", i);
log.info("Received count: {} / {}", i, clients.size());
return false;
}
if (!msgClass.isAssignableFrom(msg.getClass())) {
//log.info("{} is not assignable from {}", msgClass, msg.getClass());
log.info("Received count: {} / {}", i, clients.size());
for (int j = 0; j < clients.size(); j++) {
log.info(" -- {}: {}", clients.get(j).config.senderId, getLastReceivedMessage(j));
}
return false;
}
}
return true;
}
public List<Message> getAllClientsReceivedMessageTypeOf(Class msgClass) {
List<Message> messages = new ArrayList<>();
for (int i = 0; i < clients.size(); i++) {
Message msg = getLastReceivedMessage(i);
if (msg == null) {
log.info("Message from client index {} was null", i);
log.info("Received count: {} / {}", i, clients.size());
}
if (!msgClass.isAssignableFrom(msg.getClass())) {
//log.info("{} is not assignable from {}", msgClass, msg.getClass());
log.info("Received count: {} / {}", i, clients.size());
for (int j = 0; j < clients.size(); j++) {
log.info(" -- {}: {}", clients.get(j).config.senderId, getLastReceivedMessage(j));
}
} else {
messages.add(msg);
}
}
return messages;
}
@After
public void tearDown() {
if (server != null) {
server.stop();
}
if (clients != null) {
clients.forEach(Client::stop);
}
}
public Config newClientDebugConfig(int lostPacketsPercentage) {
Config config = newClientConfig();
config.debug.enabled = true;
config.debug.lostPacketsPercentage = lostPacketsPercentage;
return config;
}
public Config newClientConfig() {
ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
protected Kryo initialValue() {
Kryo kryo = new Kryo();
kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
customizeKryo(kryo);
return kryo;
}
};
Config config = new Config();
config.host = "localhost";
config.port = 15150;
config.bindPort = 0;
config.connectTimeout = 10000;
config.serialiser = new KryoSerialiser(new SerialiserConfig(), kryos);
config.udpPeerClass = JavaNetPeer.class;
// config.udpPeer = new KryoNettyPeer(config);
config.externalReceiver = message1 -> message1.process(null);
config.keepAliveInterval = 700;
config.getAdditionalConfig(MessageLogProcessor.ProcessorConfig.class).messageLogReceiveFilter = message -> true;
config.compressBigMessages = true;
config.autoSplitTooBigMessages = true;
return (config);
}
public void customizeKryo(Kryo kryo) {}
public Config newServerConfig() {
return newClientConfig().setBindPort(15150).setPort(0);
}
public void logBig(String text) {
//log.info("");
System.out.println();
log.info("***************************************************************");
log.info("* " + text);
log.info("***************************************************************");
System.out.println();
}
public void waitForCondition(String errorMsg, int timeoutInSeconds, Callable<Boolean> condition) {
waitForCondition(errorMsg, timeoutInSeconds, DEFAULT_INTERVAL, condition);
}
public void waitForCondition(String errorMsg, int timeoutInSeconds, Callable<Boolean> condition, Callable<String> out) {
waitForCondition(errorMsg, timeoutInSeconds, DEFAULT_INTERVAL, condition, out);
}
public void waitForCondition(String errorMsg, int timeoutInSeconds, int interval, Callable<Boolean> condition) {
waitForCondition(errorMsg, timeoutInSeconds, interval, condition, ()->"");
}
public void waitForCondition(String errorMsg, int timeoutInSeconds, int interval, Callable<Boolean> condition, Callable<String> out) {
int intervalInMilis = interval;
int i = timeoutInSeconds * 1000 + 1000;
int icall = 0;
try {
Boolean call = condition.call();
while (!call) {
Thread.sleep(intervalInMilis);
server.process();
clients.forEach(Client::process);
i -= interval;
icall += interval;
if (icall > 1000 || i <= 0) {
icall = 0;
// only check every second to not completely mess up console log messages
call = condition.call();
}
if (i <= 0 && !call) {
// log.info(out.call());
String outCallStr = out.call();
System.err.println(outCallStr);
Assert.fail(errorMsg + " [Condition didn't evaluate to true in time. Timeout was " + timeoutInSeconds + "] " + outCallStr);
}
}
} catch (Exception e) {
try {
//log.error(out.call());
System.err.println(out.call());
} catch (Exception e1) {
e1.printStackTrace();
}
e.printStackTrace();
Assert.fail(errorMsg);
}
}
}