package eu.hgross.blaubot.test;
import org.junit.Assert;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import eu.hgross.blaubot.core.Blaubot;
import eu.hgross.blaubot.core.BlaubotFactory;
import eu.hgross.blaubot.core.IBlaubotDevice;
import eu.hgross.blaubot.core.State;
import eu.hgross.blaubot.core.statemachine.ConnectionStateMachine;
import eu.hgross.blaubot.core.statemachine.IBlaubotConnectionStateMachineListener;
import eu.hgross.blaubot.core.statemachine.states.FreeState;
import eu.hgross.blaubot.core.statemachine.states.IBlaubotState;
import eu.hgross.blaubot.core.statemachine.states.StoppedState;
import eu.hgross.blaubot.ethernet.BlaubotEthernetFixedDeviceSetBeacon;
import eu.hgross.blaubot.ethernet.FixedDeviceSetHelper;
import eu.hgross.blaubot.messaging.BlaubotChannelManager;
import eu.hgross.blaubot.util.Log;
/**
* Utility class for some test methods.
*
* @author Henning Gross {@literal (mail.to@henning-gross.de)}
*
*/
public class BlaubotJunitHelper {
public static final String LOG_TAG = "BlaubotJunitHelper";
/**
* see startBlaubotInstances(List<Blaubot> instances, int timeout) {
* @param timeout
* @param instances
* @return
*/
public static boolean startBlaubotInstances(int timeout, Blaubot ... instances) {
return startBlaubotInstances(Arrays.asList(instances), timeout);
}
/**
* Starts a list of blaubot instances in a new thread.
* Blocks until started.
*
* @param instances {@link Blaubot} to start
* @param timeout the timeout for the start operation
* @return true iff all blaubot instances were started, false if the timeout occured or not all instaces were started
*/
public static boolean startBlaubotInstances(List<Blaubot> instances, int timeout) {
final CountDownLatch freeStateLatch = new CountDownLatch(instances.size());
final IBlaubotConnectionStateMachineListener connectionStateMachineListener = new IBlaubotConnectionStateMachineListener() {
@Override
public void onStateMachineStopped() {
}
@Override
public void onStateMachineStarted() {
}
@Override
public void onStateChanged(IBlaubotState oldState, IBlaubotState state) {
if (state instanceof FreeState) {
freeStateLatch.countDown();
} else if (state instanceof StoppedState) {
}
}
};
// add listener and start
for (final Blaubot blaubot : instances) {
blaubot.getConnectionStateMachine().addConnectionStateMachineListener(connectionStateMachineListener);
if(blaubot.isStarted()) {
freeStateLatch.countDown();
} else {
new Thread(new Runnable() {
@Override
public void run() {
blaubot.startBlaubot();
}
}).start();
}
}
// await or fail after MAX_START_TIME_FOR_ALL_INSTANCES
try {
return freeStateLatch.await(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* Blocks until the given list of blaubot instances are stopped or a timeout occured.
*
* @param instances the blaubot instances to stop
* @param timeout the max time to wait for the instances to stop
* @return true if all instances were stopped, false on timeout
*/
public static boolean stopBlaubotInstances(List<Blaubot> instances, int timeout) {
final CountDownLatch stoppedStateLatch = new CountDownLatch(instances.size());
final IBlaubotConnectionStateMachineListener connectionStateMachineListener = new IBlaubotConnectionStateMachineListener() {
@Override
public void onStateMachineStopped() {
}
@Override
public void onStateMachineStarted() {
}
@Override
public void onStateChanged(IBlaubotState oldState, IBlaubotState state) {
if (state instanceof FreeState) {
} else if (state instanceof StoppedState) {
stoppedStateLatch.countDown();
}
}
};
// now stop and await stoppedState
for (Blaubot blaubot : instances) {
blaubot.getConnectionStateMachine().addConnectionStateMachineListener(connectionStateMachineListener);
if(blaubot.isStarted()) {
blaubot.stopBlaubot();
} else {
stoppedStateLatch.countDown();
}
}
// await all stoppedStates or fail after
try {
final boolean timedOut = !stoppedStateLatch.await(timeout, TimeUnit.MILLISECONDS);
if (timedOut) {
// it is possible that we missed an event while attaching the listener and stopping the instance above.
// so we recheck here
boolean allStopped = true;
for (Blaubot blaubot : instances) {
if (blaubot.isStarted()) {
allStopped = false;
break;
}
}
return allStopped;
}
return !timedOut;
} catch (InterruptedException e) {
return false;
}
}
/**
* Creates a set of config {@link String}s starting from startPort using two consecutive
* ports for each instance that can be used to create some FixedDeviceSetBlaubotDevice
* instances from.
*
* @param count the number of configStrings to create
* @param startPort the starting port
* @param inetAddress the inetAddress to use
*/
public static HashSet<String> createEthernetFixedDevicesConfigStrings(int count, int startPort, InetAddress inetAddress) {
HashSet<String> deviceSet = new HashSet<String>();
for (int i = 0; i < count; i += 1, startPort += 2) {
int acceptorPort = startPort;
int beaconPort = startPort + 1;
String uniqueIdString = FixedDeviceSetHelper.createFixedDeviceSetConfigString(inetAddress, acceptorPort, beaconPort);
deviceSet.add(uniqueIdString);
}
return deviceSet;
}
/**
* Creates a set of uniqueDeviceId {@link String}s starting from startPort using two consecutive
* ports for each instance using the loopback interface (127.0.0.1).
*
* @param count the number of uniqueId Strings to create
* @param startPort the starting port
* @return
* @throws UnknownHostException if 127.0.0.1 is not available .. ;-)
*/
public static HashSet<String> createEthernetUniqueDeviceIdStringsFromLoopbackInterface(int count, int startPort) throws UnknownHostException {
return createEthernetFixedDevicesConfigStrings(count, startPort, InetAddress.getByName("127.0.0.1"));
}
/**
* Creates a set of uniqueDeviceId {@link String}s starting from startPort using two consecutive
* ports for each instance using the first found local ip address.
*
* Note that this could be anything (vpn, local network, ...).
* It is recommended to use an explicitly chosen {@link InetAddress}.
*
* @param count the number of uniqueId Strings to create
* @param startPort the starting port
* @return
*/
public static HashSet<String> createEthernetUniqueDeviceIdStringsFromFirstLocalIpAddress(int count, int startPort) {
return createEthernetFixedDevicesConfigStrings(count, startPort, BlaubotFactory.getLocalIpAddress());
}
public enum EthernetBeaconType {
FIXED_DEVICE_SET, MULTICAST_BROADCAST
}
/**
* Creates the {@link Blaubot} (ethernet) instances based on the deviceSet of uniqueId strings
*
* @param deviceSet set of config strings to create blaubot instances from
* @param blaubotUUID the UUID for all the blaubot instances
* @param beaconType specifies which beacon should be used for the {@link Blaubot} instances.
* @return
* @throws UnknownHostException
*/
public static List<Blaubot> setUpEthernetBlaubotInstancesFromUniqueIdSet(Set<String> deviceSet, UUID blaubotUUID, EthernetBeaconType beaconType) throws UnknownHostException {
List<Blaubot> instances = new CopyOnWriteArrayList<Blaubot>();
int multiCastBeaconBroadcastPort = -1;
for(String configString : deviceSet) {
int acceptorPort = FixedDeviceSetHelper.getAcceptorPortFromFixedDeviceConfigString(configString);
int beaconPort = FixedDeviceSetHelper.getBeaconPortFromFixedDeviceConfigString(configString);
if (multiCastBeaconBroadcastPort == -1) {
multiCastBeaconBroadcastPort = beaconPort;
}
InetAddress ownInetAddress = FixedDeviceSetHelper.getInetAddressFromConfigString(configString);
IBlaubotDevice ownDevice = new BlaubotEthernetFixedDeviceSetBeacon.FixedDeviceSetBlaubotDevice(configString, ownInetAddress, beaconPort);
Blaubot blaubotInstance = null;
if(beaconType.equals(EthernetBeaconType.MULTICAST_BROADCAST)) {
blaubotInstance = BlaubotFactory.createEthernetBlaubot(blaubotUUID, acceptorPort, beaconPort, multiCastBeaconBroadcastPort, ownInetAddress);
} else if(beaconType.equals(EthernetBeaconType.FIXED_DEVICE_SET)) {
blaubotInstance = BlaubotFactory.createEthernetBlaubotWithFixedDevicesBeacon(blaubotUUID, ownDevice, acceptorPort, beaconPort, ownInetAddress, deviceSet);
} else {
throw new IllegalArgumentException("Unknown BeaconType.");
}
instances.add(blaubotInstance);
}
return instances;
}
/**
*
* @param state
* @param blaubotInstances
* @return a sublist of blaubotInstaces containing only the blaubot instances which {@link ConnectionStateMachine}s current {@link IBlaubotState} matches state.
*/
public static List<Blaubot> filterBlaubotInstancesByState(State state, List<Blaubot> blaubotInstances) {
ArrayList<Blaubot> filtered = new ArrayList<Blaubot>();
for(Blaubot blaubot : blaubotInstances) {
State bState = State.getStateByStatemachineClass(blaubot.getConnectionStateMachine().getCurrentState().getClass());
if(bState.equals(state)) {
filtered.add(blaubot);
}
}
return filtered;
}
/**
* Starts the blaubot instances and then blocks until one kingdom is formed
* by the given instances or the timeout occured. If a timeout occurs, false
* is returned.
*
* @param timeout the max time to wait to form the kingdom
* @param instances the blaubot instances
* @return true iff all instances build one kingdom
*/
public static boolean blockUntilWeHaveOneKingdom(int timeout, Blaubot... instances) {
return blockUntilWeHaveOneKingdom(Arrays.asList(instances), timeout);
}
/**
* Starts the blaubot instances and then blocks until one kingdom is formed
* by the given instances or the timeout occured. If a timeout occurs, false
* is returned.
*
* @param instances the blaubot instances
* @param timeout the max time to wait to form the kingdom
* @return true iff all instances build one kingdom
*/
public static boolean blockUntilWeHaveOneKingdom(final List<Blaubot> instances, int timeout) {
final CountDownLatch latch = new CountDownLatch(1);
IBlaubotConnectionStateMachineListener connectionStateMachineListener = new IBlaubotConnectionStateMachineListener() {
private void checkKingdomFormed() {
System.out.println(""+createBlaubotCensusString(instances));
if(formOneKingdom(instances)) {
latch.countDown();
}
}
@Override
public void onStateMachineStopped() {
}
@Override
public void onStateMachineStarted() {
}
@Override
public void onStateChanged(IBlaubotState oldState, IBlaubotState state) {
checkKingdomFormed();
}
};
for(Blaubot b : instances) {
b.getConnectionStateMachine().addConnectionStateMachineListener(connectionStateMachineListener);
}
for(final Blaubot b : instances) {
new Thread(new Runnable() {
@Override
public void run() {
b.startBlaubot();
}
}).start();
}
if(formOneKingdom(instances)) {
latch.countDown();
}
try {
boolean timedOut = !latch.await(timeout, TimeUnit.MILLISECONDS);
for(Blaubot b : instances) {
b.getConnectionStateMachine().removeConnectionStateMachineListener(connectionStateMachineListener);
}
if(timedOut) {
Log.e(LOG_TAG, "Could not create a kingdom in time (" + timeout + " ms) - Kingdom: " + createBlaubotCensusString(instances));
return false;
}
Log.d(LOG_TAG, "Created a kingdom: " + createBlaubotCensusString(instances));
return true;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* True if the given instances form one (and only one) valid kingdom.
*
* @param instances the blaubot instances
* @return true iff the instances form one kingdom
*/
public static boolean formOneKingdom(List<Blaubot> instances) {
int peasantCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Peasant, instances).size();
int kingCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, instances).size();
int princeCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Prince, instances).size();
int freeCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Free, instances).size();
if (instances.size() == 1) {
return (peasantCount == 0 && kingCount == 0 && princeCount == 0 && freeCount == 1) || (peasantCount == 0 && kingCount == 1 && princeCount == 0 && freeCount == 0);
} else if (instances.size() == 2) {
return peasantCount == 0 && kingCount == 1 && princeCount == 1 && freeCount == 0;
} else if (instances.size() >= 3) {
return peasantCount == instances.size() - 2 && kingCount == 1 && princeCount == 1 && freeCount == 0;
}
return false;
}
/**
* Creates a string containing informations about the states of a given set of {@link Blaubot} instances
* for logging purposes.
*
* @param instances the blaubot instances
* @return
*/
public static String createBlaubotCensusString(Blaubot... instances) {
return createBlaubotCensusString(Arrays.asList(instances));
}
/**
* Creates a string containing informations about the states of a given set of {@link Blaubot} instances
* for logging purposes.
*
* @param instances the blaubot instances
* @return
*/
public static String createBlaubotCensusString(List<Blaubot> instances) {
int stoppedCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Stopped, instances).size();
int peasantCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Peasant, instances).size();
int kingCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, instances).size();
int princeCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Prince, instances).size();
int freeCount = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Free, instances).size();
StringBuilder sb = new StringBuilder();
sb.append("Stopped: ").append(stoppedCount).append(", ");
sb.append("Free: ").append(freeCount).append(", ");
sb.append("Peasant: ").append(peasantCount).append(", ");
sb.append("Prince: ").append(princeCount).append(", ");
sb.append("King: ").append(kingCount);
return sb.toString();
}
/**
* Extracts the channelManagers from the kingdom list.
*
* @param kingdom the blaubot instances
* @return the list of channel managers of the blaubot instances with the king at index 0
*/
public static List<BlaubotChannelManager> getChannelManagersFromKingdom(List<Blaubot> kingdom) {
// Assert that the kindom is really one kingdom
Assert.assertTrue("Given instances do not form one kingdom: " + createBlaubotCensusString(kingdom), BlaubotJunitHelper.formOneKingdom(kingdom));
// find out who is king
final List<Blaubot> kings = BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, kingdom);
Assert.assertTrue("No ore more than one king?", kings.size() == 1);
// build the channel manager list
final ArrayList<BlaubotChannelManager> managers = new ArrayList<>();
managers.add(kings.get(0).getChannelManager());
for(Blaubot instance : kingdom) {
final BlaubotChannelManager channelManager = instance.getChannelManager();
if (!managers.contains(channelManager)) {
managers.add(channelManager);
}
}
return managers;
}
}