package rescuecore2.standard.kernel.comms;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import rescuecore2.config.Config;
import rescuecore2.messages.Command;
import rescuecore2.worldmodel.WorldModel;
import rescuecore2.worldmodel.Entity;
import rescuecore2.worldmodel.EntityID;
import rescuecore2.standard.entities.StandardWorldModel;
import rescuecore2.standard.entities.Civilian;
import rescuecore2.standard.entities.FireStation;
import rescuecore2.standard.entities.AmbulanceCentre;
import rescuecore2.standard.entities.PoliceOffice;
import rescuecore2.standard.entities.FireBrigade;
import rescuecore2.standard.entities.AmbulanceTeam;
import rescuecore2.standard.entities.PoliceForce;
import rescuecore2.standard.entities.Road;
import rescuecore2.standard.messages.AKSubscribe;
import rescuecore2.standard.messages.AKSpeak;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Iterator;
import java.io.PrintStream;
import java.io.ByteArrayOutputStream;
public class ChannelCommunicationModelTest {
private final static byte[] TEST_BYTES = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
private final static byte[] TEST_BYTES_2 = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
private final static Collection<AKSpeak> NO_MESSAGES = new HashSet<AKSpeak>();
private ChannelCommunicationModel model;
private Civilian civ1;
private Civilian civ2;
private Civilian civ3;
private Civilian civ4;
private FireBrigade fb1;
private FireBrigade fb2;
private FireStation fs1;
private FireStation fs2;
private PoliceForce pf1;
private PoliceForce pf2;
private PoliceOffice po1;
private PoliceOffice po2;
private AmbulanceTeam at1;
private AmbulanceTeam at2;
private AmbulanceCentre ac1;
private AmbulanceCentre ac2;
private Road road;
private Collection<Command> commands;
private Collection<AKSpeak> expected;
private Collection<AKSpeak> civ1Expected;
private Collection<AKSpeak> civ2Expected;
private Collection<AKSpeak> civ3Expected;
private Collection<AKSpeak> civ4Expected;
private ByteArrayOutputStream logBytes;
@Before
public void setup() {
model = new ChannelCommunicationModel();
commands = new ArrayList<Command>();
expected = new ArrayList<AKSpeak>();
civ1Expected = new ArrayList<AKSpeak>();
civ2Expected = new ArrayList<AKSpeak>();
civ3Expected = new ArrayList<AKSpeak>();
civ4Expected = new ArrayList<AKSpeak>();
logBytes = new ByteArrayOutputStream();
PrintStream buffer = new PrintStream(logBytes);
System.setOut(buffer);
}
@After
public void cleanup() {
System.err.println(new String(logBytes.toByteArray()));
}
@Test
public void testName() {
assertEquals("Channel communication model", model.toString());
}
@Test
public void testSubscribeBasics() {
model.initialise(createRadioConfig(), createWorldModel());
commands.add(new AKSubscribe(civ1.getID(), 1, 0));
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
commands.add(speak1);
civ1Expected.add(speak1);
model.process(1, commands);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ2Expected);
}
@Test
public void testSubscribeMultipleChannels() {
model.initialise(createRadioConfig(), createWorldModel());
commands.add(new AKSubscribe(civ1.getID(), 1, 0));
commands.add(new AKSubscribe(civ2.getID(), 1, 1));
commands.add(new AKSubscribe(civ3.getID(), 1, 0, 1));
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ1.getID(), 1, 1, TEST_BYTES);
AKSpeak speak3 = new AKSpeak(civ1.getID(), 1, 2, TEST_BYTES);
commands.add(speak1);
commands.add(speak2);
commands.add(speak3);
civ1Expected.add(speak1);
civ2Expected.add(speak2);
civ3Expected.add(speak1);
civ3Expected.add(speak2);
model.process(1, commands);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ2Expected);
checkHearing(civ3, civ3Expected);
checkHearing(civ4, civ4Expected);
}
@Test
public void testSubscribeMultipleTimesteps() {
model.initialise(createRadioConfig(), createWorldModel());
AKSpeak speak1 = new AKSpeak(civ1.getID(), 2, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ1.getID(), 3, 0, TEST_BYTES);
commands.add(new AKSubscribe(civ1.getID(), 1, 0));
commands.add(new AKSubscribe(civ2.getID(), 1, 0));
model.process(1, commands);
commands.clear();
commands.add(speak1);
model.process(2, commands);
civ1Expected.add(speak1);
civ2Expected.add(speak1);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ2Expected);
commands.clear();
civ1Expected.clear();
civ2Expected.clear();
commands.add(speak2);
civ1Expected.add(speak2);
civ2Expected.add(speak2);
model.process(3, commands);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ2Expected);
}
@Test
public void testVoiceDoesNotNeedSubscribe() {
model.initialise(createVoiceConfig(), createWorldModel());
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
commands.add(speak1);
commands.add(speak2);
civ1Expected.add(speak1);
civ1Expected.add(speak2);
model.process(1, commands);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ1Expected);
checkHearing(civ3, civ1Expected);
checkHearing(civ4, civ1Expected);
}
@Test
public void testVoiceWorksWithSubscribe() {
model.initialise(createCombinedConfig(), createWorldModel());
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
commands.add(new AKSubscribe(civ2.getID(), 1, 0));
commands.add(new AKSubscribe(civ3.getID(), 1, 1));
commands.add(new AKSubscribe(civ4.getID(), 1, 0, 1));
commands.add(speak1);
commands.add(speak2);
civ1Expected.add(speak1);
civ1Expected.add(speak2);
model.process(1, commands);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ1Expected);
checkHearing(civ3, civ1Expected);
checkHearing(civ4, civ1Expected);
}
@Test
public void testVoiceDistance() {
model.initialise(createVoiceConfig(), createWorldModel());
civ1.setX(0);
civ1.setY(0);
civ2.setX(10000);
civ2.setY(0);
civ3.setX(20000);
civ3.setY(0);
civ4.setX(-10000);
civ4.setY(-10000);
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ2.getID(), 1, 0, TEST_BYTES);
AKSpeak speak3 = new AKSpeak(civ3.getID(), 1, 0, TEST_BYTES);
AKSpeak speak4 = new AKSpeak(civ4.getID(), 1, 0, TEST_BYTES);
commands.add(speak1);
commands.add(speak2);
commands.add(speak3);
commands.add(speak4);
civ1Expected.add(speak1);
civ1Expected.add(speak2);
civ2Expected.add(speak1);
civ2Expected.add(speak2);
civ2Expected.add(speak3);
civ3Expected.add(speak2);
civ3Expected.add(speak3);
civ4Expected.add(speak4);
model.process(1, commands);
checkHearing(civ1, civ1Expected);
checkHearing(civ2, civ2Expected);
checkHearing(civ3, civ3Expected);
checkHearing(civ4, civ4Expected);
}
@Test
public void testInvalidConfig() {
model.initialise(createInvalidChannelTypeConfig(), createWorldModel());
// Check that a warning went to the log
assertLogContains("Unrecognised channel type");
// Check that no channels exist
assertEquals(0, model.getAllChannels().size());
}
@Test
public void testPartlyInvalidConfig() {
model.initialise(createInvalidChannelTypeConfigWithSomeValid(), createWorldModel());
// Check that a warning went to the log
assertLogContains("Unrecognised channel type");
// Check that one channel was still created
assertEquals(1, model.getAllChannels().size());
}
@Test(expected=rescuecore2.config.NoSuchConfigOptionException.class)
public void testMissingChannelsConfig() {
model.initialise(createMissingChannelsConfig(), createWorldModel());
}
@Test
public void testExtraChannelsConfig() {
model.initialise(createExtraChannelsConfig(), createWorldModel());
// Check that only one channel was created
assertEquals(1, model.getAllChannels().size());
}
@Test
public void testIncorrectChannel() {
model.initialise(createRadioConfig(), createWorldModel());
commands.add(new AKSpeak(civ1.getID(), 1, 10, TEST_BYTES));
model.process(1, commands);
assertLogContains("Unrecognised channel: 10");
// Check that no-one heard the message
checkHearing(civ1, NO_MESSAGES);
checkHearing(civ2, NO_MESSAGES);
checkHearing(civ3, NO_MESSAGES);
checkHearing(civ4, NO_MESSAGES);
}
@Test
public void testSubscribeNonExistantEntity() {
model.initialise(createRadioConfig(), createWorldModel());
commands.add(new AKSubscribe(new EntityID(-5), 1, 0));
commands.add(new AKSpeak(civ1.getID(), 1, 1, TEST_BYTES));
model.process(1, commands);
assertLogContains("Couldn't find entity -5");
// Check that no-one heard any messages on channel 0
checkHearing(civ1, NO_MESSAGES);
checkHearing(civ2, NO_MESSAGES);
checkHearing(civ3, NO_MESSAGES);
checkHearing(civ4, NO_MESSAGES);
}
@Test
public void testSubscriptionLimits() {
model.initialise(createRadioConfig(), createWorldModel());
commands.add(new AKSubscribe(civ1.getID(), 1, 0, 1));
commands.add(new AKSubscribe(civ2.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(fb1.getID(), 1, 0, 1));
commands.add(new AKSubscribe(fb2.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(pf1.getID(), 1, 0, 1));
commands.add(new AKSubscribe(pf2.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(at1.getID(), 1, 0, 1));
commands.add(new AKSubscribe(at2.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(fs1.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(fs2.getID(), 1, 0, 1, 2, 3));
commands.add(new AKSubscribe(po1.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(po2.getID(), 1, 0, 1, 2, 3));
commands.add(new AKSubscribe(ac1.getID(), 1, 0, 1, 2));
commands.add(new AKSubscribe(ac2.getID(), 1, 0, 1, 2, 3));
AKSpeak speak = new AKSpeak(fb1.getID(), 1, 0, TEST_BYTES);
commands.add(speak);
expected.add(speak);
model.process(1, commands);
assertLogContains("Agent " + civ2.getID() + " tried to subscribe to 3 channels but only 2 allowed");
assertLogContains("Agent " + fb2.getID() + " tried to subscribe to 3 channels but only 2 allowed");
assertLogContains("Agent " + pf2.getID() + " tried to subscribe to 3 channels but only 2 allowed");
assertLogContains("Agent " + at2.getID() + " tried to subscribe to 3 channels but only 2 allowed");
assertLogContains("Agent " + fs2.getID() + " tried to subscribe to 4 channels but only 3 allowed");
assertLogContains("Agent " + po2.getID() + " tried to subscribe to 4 channels but only 3 allowed");
assertLogContains("Agent " + ac2.getID() + " tried to subscribe to 4 channels but only 3 allowed");
checkHearing(civ1, expected);
checkHearing(fb1, expected);
checkHearing(fs1, expected);
checkHearing(pf1, expected);
checkHearing(po1, expected);
checkHearing(at1, expected);
checkHearing(ac1, expected);
checkHearing(civ2, NO_MESSAGES);
checkHearing(fb2, NO_MESSAGES);
checkHearing(fs2, NO_MESSAGES);
checkHearing(pf2, NO_MESSAGES);
checkHearing(po2, NO_MESSAGES);
checkHearing(at2, NO_MESSAGES);
checkHearing(ac2, NO_MESSAGES);
}
@Test
public void testSubscriptionRemovesOldChannels() {
model.initialise(createRadioConfig(), createWorldModel());
// Subscribe to channel 0
commands.add(new AKSubscribe(civ1.getID(), 1, 0));
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ1.getID(), 1, 1, TEST_BYTES_2);
AKSpeak speak3 = new AKSpeak(civ1.getID(), 2, 0, TEST_BYTES);
AKSpeak speak4 = new AKSpeak(civ1.getID(), 2, 1, TEST_BYTES_2);
commands.add(speak1);
commands.add(speak2);
expected.add(speak1);
model.process(1, commands);
checkHearing(civ1, expected);
// Now subscribe to channel 1
commands.clear();
expected.clear();
commands.add(new AKSubscribe(civ1.getID(), 2, 1));
commands.add(speak3);
commands.add(speak4);
expected.add(speak4);
model.process(2, commands);
checkHearing(civ1, expected);
}
@Test
public void testBadSubscriptionKeepsOKChannels() {
model.initialise(createRadioConfig(), createWorldModel());
// Subscribe to channel 0
commands.add(new AKSubscribe(civ1.getID(), 1, 0));
AKSpeak speak1 = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
AKSpeak speak2 = new AKSpeak(civ1.getID(), 1, 1, TEST_BYTES_2);
AKSpeak speak3 = new AKSpeak(civ1.getID(), 2, 0, TEST_BYTES);
AKSpeak speak4 = new AKSpeak(civ1.getID(), 2, 1, TEST_BYTES_2);
commands.add(speak1);
commands.add(speak2);
expected.add(speak1);
model.process(1, commands);
checkHearing(civ1, expected);
// Now subscribe to channels 1 and 100
commands.clear();
expected.clear();
commands.add(new AKSubscribe(civ1.getID(), 2, 1, 100));
commands.add(speak3);
commands.add(speak4);
expected.add(speak4);
model.process(2, commands);
assertLogContains("Agent " + civ1.getID() + " tried to subscribe to non-existant channel 100");
checkHearing(civ1, expected);
}
@Test
public void testSubscribeFromNonAgent() {
model.initialise(createRadioConfig(), createWorldModel());
// Subscribe to channel 0
commands.add(new AKSubscribe(civ1.getID(), 1, 0));
commands.add(new AKSubscribe(road.getID(), 1, 0));
AKSpeak speak = new AKSpeak(civ1.getID(), 1, 0, TEST_BYTES);
commands.add(speak);
expected.add(speak);
model.process(1, commands);
checkHearing(civ1, expected);
assertLogContains("I don't know how to handle subscriptions for this entity: " + road.toString());
}
private Config createRadioConfig() {
Config config = new Config();
config.setValue("comms.channels.count", "4");
config.setValue("comms.channels.0.type", "radio");
config.setValue("comms.channels.0.bandwidth", "128");
config.setValue("comms.channels.1.type", "radio");
config.setValue("comms.channels.1.bandwidth", "128");
config.setValue("comms.channels.2.type", "radio");
config.setValue("comms.channels.2.bandwidth", "128");
config.setValue("comms.channels.3.type", "radio");
config.setValue("comms.channels.3.bandwidth", "128");
config.setValue("comms.channels.max.platoon", "2");
config.setValue("comms.channels.max.centre", "3");
return config;
}
private Config createVoiceConfig() {
Config config = new Config();
config.setValue("comms.channels.count", "1");
config.setValue("comms.channels.0.type", "voice");
config.setValue("comms.channels.0.range", "10000");
config.setValue("comms.channels.0.messages.size", "100");
config.setValue("comms.channels.0.messages.max", "2");
return config;
}
private Config createCombinedConfig() {
Config config = new Config();
config.setValue("comms.channels.count", "4");
config.setValue("comms.channels.0.type", "voice");
config.setValue("comms.channels.0.range", "10000");
config.setValue("comms.channels.0.messages.size", "100");
config.setValue("comms.channels.0.messages.max", "2");
config.setValue("comms.channels.1.type", "radio");
config.setValue("comms.channels.1.bandwidth", "128");
config.setValue("comms.channels.2.type", "radio");
config.setValue("comms.channels.2.bandwidth", "128");
config.setValue("comms.channels.3.type", "radio");
config.setValue("comms.channels.3.bandwidth", "128");
config.setValue("comms.channels.max.platoon", "2");
config.setValue("comms.channels.max.centre", "2");
return config;
}
private Config createInvalidChannelTypeConfig() {
Config config = new Config();
config.setValue("comms.channels.count", "1");
config.setValue("comms.channels.0.type", "blah");
return config;
}
private Config createInvalidChannelTypeConfigWithSomeValid() {
Config config = new Config();
config.setValue("comms.channels.count", "2");
config.setValue("comms.channels.0.type", "blah");
config.setValue("comms.channels.1.type", "radio");
config.setValue("comms.channels.1.bandwidth", "128");
return config;
}
private Config createMissingChannelsConfig() {
Config config = new Config();
config.setValue("comms.channels.count", "2");
config.setValue("comms.channels.0.type", "radio");
config.setValue("comms.channels.0.bandwidth", "128");
return config;
}
private Config createExtraChannelsConfig() {
Config config = new Config();
config.setValue("comms.channels.count", "1");
config.setValue("comms.channels.0.type", "radio");
config.setValue("comms.channels.0.bandwidth", "128");
config.setValue("comms.channels.1.type", "radio");
config.setValue("comms.channels.1.bandwidth", "128");
return config;
}
private WorldModel<? extends Entity> createWorldModel() {
StandardWorldModel world = new StandardWorldModel();
civ1 = new Civilian(new EntityID(1));
civ2 = new Civilian(new EntityID(2));
civ3 = new Civilian(new EntityID(3));
civ4 = new Civilian(new EntityID(4));
fb1 = new FireBrigade(new EntityID(5));
fb2 = new FireBrigade(new EntityID(6));
pf1 = new PoliceForce(new EntityID(7));
pf2 = new PoliceForce(new EntityID(8));
at1 = new AmbulanceTeam(new EntityID(9));
at2 = new AmbulanceTeam(new EntityID(10));
fs1 = new FireStation(new EntityID(11));
fs2 = new FireStation(new EntityID(12));
po1 = new PoliceOffice(new EntityID(13));
po2 = new PoliceOffice(new EntityID(14));
ac1 = new AmbulanceCentre(new EntityID(15));
ac2 = new AmbulanceCentre(new EntityID(16));
road = new Road(new EntityID(17));
civ1.setX(0);
civ1.setY(0);
civ2.setX(0);
civ2.setY(0);
civ3.setX(0);
civ3.setY(0);
civ4.setX(0);
civ4.setY(0);
world.addEntity(civ1);
world.addEntity(civ2);
world.addEntity(civ3);
world.addEntity(civ4);
world.addEntity(fb1);
world.addEntity(fb2);
world.addEntity(pf1);
world.addEntity(pf2);
world.addEntity(at1);
world.addEntity(at2);
world.addEntity(fs1);
world.addEntity(fs2);
world.addEntity(po1);
world.addEntity(po2);
world.addEntity(ac1);
world.addEntity(ac2);
world.addEntity(road);
return world;
}
private void checkHearing(Entity agent, Collection<AKSpeak> expected) {
Collection<Command> hearing = model.getHearing(agent);
System.err.println("Expected: " + expected);
System.err.println("Heard : " + hearing);
assertEquals(expected.size(), hearing.size());
Collection<AKSpeak> remaining = new HashSet<AKSpeak>(expected);
for (Command next : hearing) {
AKSpeak speak = (AKSpeak)next;
assertTrue(find(speak, remaining));
}
}
private boolean find(AKSpeak speak, Collection<AKSpeak> possible) {
for (Iterator<AKSpeak> it = possible.iterator(); it.hasNext(); ) {
AKSpeak next = it.next();
if (next.getAgentID().equals(speak.getAgentID())
&& next.getChannel() == speak.getChannel()
&& Arrays.equals(next.getContent(), speak.getContent())
&& next.getTime() == speak.getTime()) {
it.remove();
return true;
}
}
return false;
}
private void assertLogContains(String s) {
System.err.println("Looking for " + s);
String test = new String(logBytes.toByteArray());
if (!test.contains(s)) {
System.err.println("Couldn't find '" + s + "'");
}
assertTrue(test.contains(s));
}
}