package eu.hgross.blaubot.test;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import eu.hgross.blaubot.core.Blaubot;
import eu.hgross.blaubot.core.BlaubotAdapterConfig;
import eu.hgross.blaubot.core.ConnectionStateMachineConfig;
import eu.hgross.blaubot.core.IBlaubotConnection;
import eu.hgross.blaubot.core.State;
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.messaging.BlaubotChannelManager;
import eu.hgross.blaubot.test.BlaubotJunitHelper.EthernetBeaconType;
import eu.hgross.blaubot.util.Log;
import eu.hgross.blaubot.util.Log.LogLevel;
/**
* Tests the ethernet {@link Blaubot} instances.
*
* Note: These tests are based on the {@link BlaubotEthernetFixedDeviceSetBeacon} component without multicast.
*
* @author Henning Gross {@literal (mail.to@henning-gross.de)}
*
*/
public class EthernetBlaubotWithFixedDeviceSetTest {
private static final int CONNECTIVITY_TEST_RECHECK_TIMEOUT = 10000;
private static final int STARTING_PORT_FOR_BLAUBOT_INSTANCES = 18171;
/**
* The number of blaubot instances to test with.
*/
private static final int NUMBER_OF_BLAUBOT_INSTANCES = 3; // min 3!
private static final int MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES = 30000;
private static final int MAX_START_TIME_FOR_ALL_INSTANCES = 30000;
/**
* Defines after what amount of time the set of blaubot instances has to be connected to
* one kingdom.
*/
private static final int CONNECTIVITY_TEST_TIMEOUT = 150000;
private static final long WAIT_TIME_BETWEEN_TESTS = 1000; // sleep time between tests to let the os close the sockets
private static final int START_STOP_CYCLES = 30;
private UUID blaubotTestUUID = UUID.fromString("b0ae40aa-41c2-49b3-9f7a-7a14ebc0a8e1");
private List<Blaubot> instances;
@BeforeClass
public static void setUpClass() {
// note: output streams are synchronized and therefore a high log level with many blaubot instances (= much output) will be really slow and could lead to timeouts!
// tl;dr: detailed log level and many instances will make the tests fail
Log.LOG_LEVEL = LogLevel.ERRORS;
}
@Before
public void setUp() throws UnknownHostException {
// HashSet<String> uniqueDeviceIdStrings = BlaubotJunitHelper.createEthernetUniqueDeviceIdStringsFromFirstLocalIpAddress(NUMBER_OF_BLAUBOT_INSTANCES, STARTING_PORT_FOR_BLAUBOT_INSTANCES);
HashSet<String> uniqueDeviceIdStrings = BlaubotJunitHelper.createEthernetUniqueDeviceIdStringsFromLoopbackInterface(NUMBER_OF_BLAUBOT_INSTANCES, STARTING_PORT_FOR_BLAUBOT_INSTANCES);
this.instances = BlaubotJunitHelper.setUpEthernetBlaubotInstancesFromUniqueIdSet(uniqueDeviceIdStrings, blaubotTestUUID, EthernetBeaconType.FIXED_DEVICE_SET);
}
@After
public void cleanUp() throws UnknownHostException, InterruptedException {
boolean stopped = BlaubotJunitHelper.stopBlaubotInstances(instances, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES);
Thread.sleep(WAIT_TIME_BETWEEN_TESTS);
if (!stopped) {
// on failure disconnect all connections manually
for (Blaubot blaubot : this.instances) {
List<IBlaubotConnection> conns = blaubot.getChannelManager().reset();
for (IBlaubotConnection conn : conns) {
conn.disconnect();
}
// and use the closeable impl
try {
blaubot.close();
} catch (IOException e) {
e.printStackTrace();
}
}
this.instances.clear();
throw new RuntimeException("Failed to stop blaubot instances");
}
this.instances.clear();
}
@Test
/**
* Tests multiple start/stop cycles of multiple Blaubot instances.
* @throws InterruptedException
*/
public void testManyStartStop() throws InterruptedException {
for(int i=0;i<START_STOP_CYCLES;i++) {
testStartStop();
}
}
@Test
/**
* Tests the start of multiple Blaubot instances followed by a stop command.
* @throws InterruptedException
*/
public void testStartStop() throws InterruptedException {
// check that all instances start and go at least to the FreeState after startBlaubot()
// and back to StoppedState after stopBlaubot()
final CountDownLatch freeStateLatch = new CountDownLatch(instances.size());
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) {
freeStateLatch.countDown();
} else if(state instanceof StoppedState) {
stoppedStateLatch.countDown();
}
}
};
// add listener and start
for(final Blaubot blaubot : instances) {
blaubot.getConnectionStateMachine().addConnectionStateMachineListener(connectionStateMachineListener);
new Thread(new Runnable() {
@Override
public void run() {
blaubot.startBlaubot();
}
}).start();
}
// await or fail after MAX_START_TIME_FOR_ALL_INSTANCES
boolean allGood = freeStateLatch.await(MAX_START_TIME_FOR_ALL_INSTANCES, TimeUnit.MILLISECONDS);
if(!allGood) {
Assert.fail();
}
// now stop and await stoppedState
for(Blaubot blaubot : instances) {
blaubot.stopBlaubot();
}
// await all stoppedStates or fail after
allGood = stoppedStateLatch.await(MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES, TimeUnit.MILLISECONDS);
if(!allGood) {
Assert.fail();
}
}
@Test
/**
* Lets the Blaubot instances form a kingdom and validates the participant's states after a defined
* time.
*/
public void testConnectivity() throws InterruptedException {
// start the instances
boolean started = BlaubotJunitHelper.startBlaubotInstances(instances, MAX_START_TIME_FOR_ALL_INSTANCES);
Assert.assertTrue("Blaubot instances could net bet started.", started);
boolean instancesFormOneKingdom = BlaubotJunitHelper.blockUntilWeHaveOneKingdom(instances, CONNECTIVITY_TEST_TIMEOUT);
Assert.assertTrue("The blaubot instances do not form one kingdom! " + BlaubotJunitHelper.createBlaubotCensusString(instances), instancesFormOneKingdom);
Assert.assertTrue("The blaubot instances do not form one kingdom! " + BlaubotJunitHelper.createBlaubotCensusString(instances), BlaubotJunitHelper.formOneKingdom(instances));
formOneKingdomOrFail();
boolean allStopped = BlaubotJunitHelper.stopBlaubotInstances(instances, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES);
Assert.assertTrue("Blaubot instances could not be stopped.", allStopped);
}
/**
* Utilizes the CONNECTIVITY_TEST_RECHECK_TIMEOUT constant to periodically check if a kingdom was formed by the instances.
* If after this time no kingdom was formed, the methods assert will fail.
* @throws InterruptedException
*/
private void formOneKingdomOrFail() throws InterruptedException {
// wait some time and re-check if the kingdom is still working
// we check often if we can proceed, to speed up the test
final int divisor = 10;
int i = 0;
while(!BlaubotJunitHelper.formOneKingdom(instances) && i++<divisor) {
Thread.sleep(CONNECTIVITY_TEST_RECHECK_TIMEOUT/divisor);
}
Assert.assertTrue("The blaubot instances do not form one kingdom! " + BlaubotJunitHelper.createBlaubotCensusString(instances), BlaubotJunitHelper.formOneKingdom(instances));
}
@Test(timeout=120000)
/**
* Lets the Blaubot instances form a kingdom and validates the participants states after a defined
* time.
* Then the prince will be stopped and the kingdom is checked again after another timeout to ensure
* another prince was pronounced successfully.
*/
public void testPrinceRepronounce() throws InterruptedException {
// start the instances
List<Blaubot> currentKingdom = instances;
// we need at least 3 instances to perform this test
Assert.assertTrue("Not enough blaubot instances created to perform test." + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), currentKingdom.size() >= 3);
boolean started = BlaubotJunitHelper.startBlaubotInstances(currentKingdom, MAX_START_TIME_FOR_ALL_INSTANCES);
Assert.assertTrue("Not all blaubot instances could be started." + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), started);
// Give the instances some time to form the kingdom
formOneKingdomOrFail();
// check that we have one kingdom
Assert.assertTrue("The blaubot instances could not form a kingdom fast enough (" + CONNECTIVITY_TEST_TIMEOUT + " ms)." + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.formOneKingdom(currentKingdom));
// assert we have exactly one prince
List<Blaubot> princes = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Prince, currentKingdom);
Assert.assertTrue("We do not have exactly one prince (" + princes.size() + ")" + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), princes.size() == 1);
// while we have at least 3 instances, we should have a king a prince and a peasant
// we now remove the prince until we have 2 instances left
while(currentKingdom.size() >= 3) {
// stop the kingdoms prince
boolean princeStopTimedOut = !BlaubotJunitHelper.stopBlaubotInstances(princes, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES);
Assert.assertFalse("Could not stop the prince's blaubot instance." + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), princeStopTimedOut);
// remove the prince from the kingdom
ArrayList<Blaubot> kingdomWithoutFormerPrince = new ArrayList<Blaubot>(currentKingdom);
kingdomWithoutFormerPrince.remove(princes.get(0));
currentKingdom = kingdomWithoutFormerPrince;
// wait for the new prince to be pronounced and let him time to send his ACK message
// wait 2 times as long as the keepAlive and ack timeout for the prince together
ConnectionStateMachineConfig connectionStateMachineConfig = princes.get(0).getAdapters().get(0).getConnectionStateMachineConfig();
BlaubotAdapterConfig adapterConfig = princes.get(0).getAdapters().get(0).getBlaubotAdapterConfig();
int waitTime = (adapterConfig.getKeepAliveInterval() + connectionStateMachineConfig.getPrinceAckTimeout()) * 6;
Thread.sleep(waitTime);
// now assert we have a new prince and a valid kingdom
Assert.assertTrue("No valid kingdom after " + waitTime + " milliseconds. Kingdom: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.formOneKingdom(kingdomWithoutFormerPrince));
// put the current prince to the princes variable
princes = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Prince, currentKingdom);
// set the new kingdom
currentKingdom = kingdomWithoutFormerPrince;
}
Assert.assertTrue(currentKingdom.size() == 2);
// now we stop the king and check that the prince claims to be king
List<Blaubot> kingDevice = BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, currentKingdom);
List<Blaubot> princeDevice = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Prince, currentKingdom);
Assert.assertTrue("Could not stop remaining prince or king blaubot instaces." + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.stopBlaubotInstances(kingDevice, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES));
// sleep as long as the keepAlive interval and the crowning preparation time together
ConnectionStateMachineConfig connectionStateMachineConfig = princeDevice.get(0).getAdapters().get(0).getConnectionStateMachineConfig();
BlaubotAdapterConfig adapterConfig = princeDevice.get(0).getAdapters().get(0).getBlaubotAdapterConfig();
int sleepTime = (adapterConfig.getKeepAliveInterval() + connectionStateMachineConfig.getCrowningPreparationTimeout());
// assert that we do not wait longer than the no peasants timeout!
Assert.assertTrue("Bad configuration (should: sleepTime < connectionStateMachineConfig.getKingWithoutPeasantsTimeout()) is: ("+sleepTime+" >= " + connectionStateMachineConfig.getKingWithoutPeasantsTimeout() + ").", sleepTime < connectionStateMachineConfig.getKingWithoutPeasantsTimeout());
Thread.sleep(sleepTime/2);
// now check that the former prince is king
Assert.assertTrue("The wrong blaubot instance is king. Expected " + princeDevice + "; " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, princeDevice).size() == 1);
// at last stop
boolean allStopped = BlaubotJunitHelper.stopBlaubotInstances(instances, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES);
Assert.assertTrue("Could not stop blaubot instances." + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), allStopped);
}
@Test(timeout=320000)
/**
* Lets the Blaubot instances form a kingdom and validates the participants states after a defined
* time.
* Then the king will be killed and it will be validate if the former prince is now king and all other
* devices joined him.
*/
public void testHeirToTheThrone() throws InterruptedException {
// form a kingdom
List<Blaubot> currentKingdom = instances;
Assert.assertTrue("The blaubot instances could not form a kingdom fast enough. States: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.blockUntilWeHaveOneKingdom(currentKingdom, CONNECTIVITY_TEST_TIMEOUT));
while(currentKingdom.size() >= 3) {
// find prince and king
List<Blaubot> kingDevice = BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, currentKingdom);
List<Blaubot> princeDevice = BlaubotJunitHelper.filterBlaubotInstancesByState(State.Prince, currentKingdom);
Assert.assertTrue("The kingdom has the wrong number of kings (" + kingDevice.size() + ") or princes ("+ princeDevice.size() +"). " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), kingDevice.size() == 1 && princeDevice.size() == 1);
// kill the king
Assert.assertTrue("The (king) blaubot instance did not stop fast enough.", BlaubotJunitHelper.stopBlaubotInstances(kingDevice, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES));
currentKingdom.remove(kingDevice.get(0));
// sleep a bit to let the prince follow the king to the throne
ConnectionStateMachineConfig connectionStateMachineConfig = princeDevice.get(0).getAdapters().get(0).getConnectionStateMachineConfig();
BlaubotAdapterConfig adapterConfig = princeDevice.get(0).getAdapters().get(0).getBlaubotAdapterConfig();
int sleepTime = (adapterConfig.getKeepAliveInterval() + connectionStateMachineConfig.getCrowningPreparationTimeout()) * 10;
Thread.sleep(sleepTime);
// check if we have a valid kingdom now
BlaubotJunitHelper.blockUntilWeHaveOneKingdom(currentKingdom, 60000);
Assert.assertTrue("The prince did not took over the throne fast enough. Current kingdom: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.formOneKingdom(currentKingdom));
// check if the former prince is now our king
List<Blaubot> newKing = BlaubotJunitHelper.filterBlaubotInstancesByState(State.King, currentKingdom);
Assert.assertTrue("The wrong blaubot instance took over the throne. (Expected was the former prince). Expected: " + princeDevice + ", king: " + newKing + "; Kingdom: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), newKing.get(0) == princeDevice.get(0));
}
// at last stop
boolean allStopped = BlaubotJunitHelper.stopBlaubotInstances(instances, MAX_STOPPING_TIMEOUT_FOR_ALL_INSTANCES);
Assert.assertTrue("Failed to stop blaubot instances.", allStopped);
}
@Test(timeout=320000)
public void testSubscriptions() throws InterruptedException {
// form a kingdom
List<Blaubot> currentKingdom = instances;
Assert.assertTrue("The blaubot instances could not form a kingdom fast enough. States: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.blockUntilWeHaveOneKingdom(currentKingdom, CONNECTIVITY_TEST_TIMEOUT));
// get the channel managers for this kingdom
final List<BlaubotChannelManager> channelManagers = BlaubotJunitHelper.getChannelManagersFromKingdom(currentKingdom);
ChannelManagerTest.testSubscriptions(channelManagers);
}
@Test(timeout=320000)
public void testAdminBroadcastMessages() throws InterruptedException {
// form a kingdom
List<Blaubot> currentKingdom = instances;
Assert.assertTrue("The blaubot instances could not form a kingdom fast enough. States: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.blockUntilWeHaveOneKingdom(currentKingdom, CONNECTIVITY_TEST_TIMEOUT));
// get the channel managers for this kingdom
final List<BlaubotChannelManager> channelManagers = BlaubotJunitHelper.getChannelManagersFromKingdom(currentKingdom);
ChannelManagerTest.testAdminBMessageBroadcast(channelManagers);
}
@Test(timeout=320000)
public void testMessageOrder() throws InterruptedException {
// form a kingdom
List<Blaubot> currentKingdom = instances;
Assert.assertTrue("The blaubot instances could not form a kingdom fast enough. States: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.blockUntilWeHaveOneKingdom(currentKingdom, CONNECTIVITY_TEST_TIMEOUT));
// get the channel managers for this kingdom
final List<BlaubotChannelManager> channelManagers = BlaubotJunitHelper.getChannelManagersFromKingdom(currentKingdom);
ChannelManagerTest.testMessageOrder(channelManagers);
}
@Test(timeout=320000)
public void testExcludeSender() throws InterruptedException, TimeoutException {
// form a kingdom
List<Blaubot> currentKingdom = instances;
Assert.assertTrue("The blaubot instances could not form a kingdom fast enough. States: " + BlaubotJunitHelper.createBlaubotCensusString(currentKingdom), BlaubotJunitHelper.blockUntilWeHaveOneKingdom(currentKingdom, CONNECTIVITY_TEST_TIMEOUT));
// get the channel managers for this kingdom
final List<BlaubotChannelManager> channelManagers = BlaubotJunitHelper.getChannelManagersFromKingdom(currentKingdom);
ChannelManagerTest.testExcludeSender(channelManagers);
}
}