mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 20:43:32 +00:00
removed tests
This commit is contained in:
@@ -1,527 +0,0 @@
|
|||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.Timeout;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import sd.IntersectionProcess;
|
|
||||||
import sd.model.MessageType;
|
|
||||||
import sd.model.Vehicle;
|
|
||||||
import sd.model.VehicleType;
|
|
||||||
import sd.protocol.SocketConnection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for IntersectionProcess - covers initialization, traffic lights,
|
|
||||||
* vehicle transfer and network stuff
|
|
||||||
*/
|
|
||||||
public class IntersectionProcessTest {
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
private Path configFile;
|
|
||||||
private IntersectionProcess intersectionProcess;
|
|
||||||
|
|
||||||
// setup test config before each test
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() throws IOException {
|
|
||||||
// create temp config file
|
|
||||||
configFile = tempDir.resolve("test-simulation.properties");
|
|
||||||
|
|
||||||
String configContent = """
|
|
||||||
# Test Simulation Configuration
|
|
||||||
|
|
||||||
# Intersection Network Configuration
|
|
||||||
intersection.Cr1.host=localhost
|
|
||||||
intersection.Cr1.port=18001
|
|
||||||
intersection.Cr2.host=localhost
|
|
||||||
intersection.Cr2.port=18002
|
|
||||||
intersection.Cr3.host=localhost
|
|
||||||
intersection.Cr3.port=18003
|
|
||||||
intersection.Cr4.host=localhost
|
|
||||||
intersection.Cr4.port=18004
|
|
||||||
intersection.Cr5.host=localhost
|
|
||||||
intersection.Cr5.port=18005
|
|
||||||
|
|
||||||
# Exit Configuration
|
|
||||||
exit.host=localhost
|
|
||||||
exit.port=18099
|
|
||||||
|
|
||||||
# Dashboard Configuration
|
|
||||||
dashboard.host=localhost
|
|
||||||
dashboard.port=18100
|
|
||||||
|
|
||||||
# Traffic Light Timing (seconds)
|
|
||||||
trafficLight.Cr1.East.greenTime=5.0
|
|
||||||
trafficLight.Cr1.East.redTime=5.0
|
|
||||||
trafficLight.Cr1.South.greenTime=5.0
|
|
||||||
trafficLight.Cr1.South.redTime=5.0
|
|
||||||
trafficLight.Cr1.West.greenTime=5.0
|
|
||||||
trafficLight.Cr1.West.redTime=5.0
|
|
||||||
|
|
||||||
trafficLight.Cr2.West.greenTime=4.0
|
|
||||||
trafficLight.Cr2.West.redTime=6.0
|
|
||||||
trafficLight.Cr2.East.greenTime=4.0
|
|
||||||
trafficLight.Cr2.East.redTime=6.0
|
|
||||||
trafficLight.Cr2.South.greenTime=4.0
|
|
||||||
trafficLight.Cr2.South.redTime=6.0
|
|
||||||
|
|
||||||
trafficLight.Cr3.West.greenTime=3.0
|
|
||||||
trafficLight.Cr3.West.redTime=7.0
|
|
||||||
trafficLight.Cr3.East.greenTime=3.0
|
|
||||||
trafficLight.Cr3.East.redTime=7.0
|
|
||||||
|
|
||||||
trafficLight.Cr4.East.greenTime=6.0
|
|
||||||
trafficLight.Cr4.East.redTime=4.0
|
|
||||||
|
|
||||||
trafficLight.Cr5.East.greenTime=5.0
|
|
||||||
trafficLight.Cr5.East.redTime=5.0
|
|
||||||
|
|
||||||
# Vehicle Crossing Times (seconds)
|
|
||||||
vehicle.bike.crossingTime=2.0
|
|
||||||
vehicle.light.crossingTime=3.0
|
|
||||||
vehicle.heavy.crossingTime=5.0
|
|
||||||
""";
|
|
||||||
|
|
||||||
Files.writeString(configFile, configContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void tearDown() {
|
|
||||||
if (intersectionProcess != null) {
|
|
||||||
try {
|
|
||||||
// Only shutdown if still running
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Error in tearDown: " + e.getMessage());
|
|
||||||
} finally {
|
|
||||||
intersectionProcess = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Initialization Tests ====================
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConstructor_Success() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
assertNotNull(intersectionProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testConstructor_InvalidConfig() {
|
|
||||||
Exception exception = assertThrows(IOException.class, () -> {
|
|
||||||
new IntersectionProcess("Cr1", "non-existent-config.properties");
|
|
||||||
});
|
|
||||||
assertNotNull(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInitialize_Cr1() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
assertDoesNotThrow(() -> intersectionProcess.initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInitialize_Cr2() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr2", configFile.toString());
|
|
||||||
assertDoesNotThrow(() -> intersectionProcess.initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInitialize_Cr3() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr3", configFile.toString());
|
|
||||||
assertDoesNotThrow(() -> intersectionProcess.initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInitialize_Cr4() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr4", configFile.toString());
|
|
||||||
assertDoesNotThrow(() -> intersectionProcess.initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInitialize_Cr5() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr5", configFile.toString());
|
|
||||||
assertDoesNotThrow(() -> intersectionProcess.initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
// traffic light creation tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrafficLightCreation_Cr1_HasCorrectDirections() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// cant access private fields but initialization succeds
|
|
||||||
assertNotNull(intersectionProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrafficLightCreation_Cr3_HasCorrectDirections() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr3", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// Cr3 has west and south only
|
|
||||||
assertNotNull(intersectionProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrafficLightCreation_Cr4_HasSingleDirection() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr4", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// Cr4 only has east direction
|
|
||||||
assertNotNull(intersectionProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
// server startup tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
public void testServerStart_BindsToCorrectPort() throws IOException, InterruptedException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// start server in separate thread
|
|
||||||
Thread serverThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// expected on shutdown
|
|
||||||
}
|
|
||||||
});
|
|
||||||
serverThread.start();
|
|
||||||
|
|
||||||
// Wait for server to actually start with retries
|
|
||||||
boolean serverReady = false;
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
Thread.sleep(100);
|
|
||||||
try (Socket testSocket = new Socket()) {
|
|
||||||
testSocket.connect(new java.net.InetSocketAddress("localhost", 18001), 500);
|
|
||||||
serverReady = true;
|
|
||||||
break;
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Server not ready yet, continue waiting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(serverReady, "Server should start and bind to port 18001");
|
|
||||||
|
|
||||||
// Shutdown immediately after confirming server is running
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
serverThread.join(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
public void testServerStart_MultipleIntersections() throws IOException, InterruptedException {
|
|
||||||
// test 2 intersections on diferent ports
|
|
||||||
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
|
||||||
|
|
||||||
cr1.initialize();
|
|
||||||
cr2.initialize();
|
|
||||||
|
|
||||||
Thread thread1 = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
cr1.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Thread thread2 = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
cr2.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
thread1.start();
|
|
||||||
thread2.start();
|
|
||||||
|
|
||||||
Thread.sleep(500);
|
|
||||||
|
|
||||||
// check both are running
|
|
||||||
try (Socket socket1 = new Socket("localhost", 18001);
|
|
||||||
Socket socket2 = new Socket("localhost", 18002)) {
|
|
||||||
assertTrue(socket1.isConnected());
|
|
||||||
assertTrue(socket2.isConnected());
|
|
||||||
}
|
|
||||||
|
|
||||||
cr1.shutdown();
|
|
||||||
cr2.shutdown();
|
|
||||||
thread1.join(2000);
|
|
||||||
thread2.join(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// vehicle transfer tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(10)
|
|
||||||
public void testVehicleTransfer_ReceiveVehicle() throws IOException, InterruptedException {
|
|
||||||
// setup reciever intersection
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr2", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
Thread serverThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
serverThread.start();
|
|
||||||
|
|
||||||
Thread.sleep(500);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// create test vehicle - FIXED: use 4-parameter constructor
|
|
||||||
java.util.List<String> route = Arrays.asList("Cr2", "Cr3", "S");
|
|
||||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
|
||||||
|
|
||||||
// send vehicle from Cr1 to Cr2 - FIXED: use SocketConnection
|
|
||||||
try (Socket socket = new Socket("localhost", 18002);
|
|
||||||
SocketConnection conn = new SocketConnection(socket)) {
|
|
||||||
|
|
||||||
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
|
|
||||||
conn.sendMessage(message);
|
|
||||||
|
|
||||||
Thread.sleep(1000); // wait for processing
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
serverThread.join(2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// routing config tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRoutingConfiguration_Cr1() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// indirect test - if init works routing should be ok
|
|
||||||
assertNotNull(intersectionProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRoutingConfiguration_Cr5() throws IOException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr5", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// Cr5 routes to exit
|
|
||||||
assertNotNull(intersectionProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
// shutdown tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
public void testShutdown_GracefulTermination() throws IOException, InterruptedException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
Thread serverThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
serverThread.start();
|
|
||||||
|
|
||||||
Thread.sleep(500);
|
|
||||||
|
|
||||||
// shutdown should be fast
|
|
||||||
assertDoesNotThrow(() -> intersectionProcess.shutdown());
|
|
||||||
|
|
||||||
serverThread.join(2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
public void testShutdown_ClosesServerSocket() throws IOException, InterruptedException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
// Start server in separate thread
|
|
||||||
Thread serverThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Expected on shutdown
|
|
||||||
}
|
|
||||||
});
|
|
||||||
serverThread.start();
|
|
||||||
|
|
||||||
// Wait for server to start
|
|
||||||
Thread.sleep(500);
|
|
||||||
|
|
||||||
// Shutdown
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
serverThread.join(2000);
|
|
||||||
|
|
||||||
// Give shutdown time to complete
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
// Verify we cannot connect (server socket is closed)
|
|
||||||
boolean connectionFailed = false;
|
|
||||||
try (Socket testSocket = new Socket()) {
|
|
||||||
testSocket.connect(new InetSocketAddress("localhost", 18001), 500);
|
|
||||||
} catch (IOException e) {
|
|
||||||
connectionFailed = true; // Expected - server should be closed
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(connectionFailed, "Server socket should be closed after shutdown");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
public void testShutdown_StopsTrafficLightThreads() throws IOException, InterruptedException {
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
|
|
||||||
Thread serverThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
serverThread.start();
|
|
||||||
|
|
||||||
Thread.sleep(500);
|
|
||||||
|
|
||||||
int threadCountBefore = Thread.activeCount();
|
|
||||||
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
serverThread.join(2000);
|
|
||||||
|
|
||||||
Thread.sleep(500); // wait for threads to die
|
|
||||||
|
|
||||||
// thread count should decrese (traffic light threads stop)
|
|
||||||
int threadCountAfter = Thread.activeCount();
|
|
||||||
assertTrue(threadCountAfter <= threadCountBefore);
|
|
||||||
}
|
|
||||||
|
|
||||||
// integration tests
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(15)
|
|
||||||
public void testIntegration_TwoIntersectionsVehicleTransfer() throws IOException, InterruptedException {
|
|
||||||
IntersectionProcess cr1 = null;
|
|
||||||
IntersectionProcess cr2 = null;
|
|
||||||
Thread thread1 = null;
|
|
||||||
Thread thread2 = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// setup 2 intersections
|
|
||||||
cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
|
||||||
|
|
||||||
cr1.initialize();
|
|
||||||
cr2.initialize();
|
|
||||||
|
|
||||||
// start both
|
|
||||||
final IntersectionProcess cr1Final = cr1;
|
|
||||||
thread1 = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
cr1Final.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final IntersectionProcess cr2Final = cr2;
|
|
||||||
thread2 = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
cr2Final.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
thread1.start();
|
|
||||||
thread2.start();
|
|
||||||
|
|
||||||
Thread.sleep(1000); // wait for servers
|
|
||||||
|
|
||||||
// send vehicle to Cr1 that goes to Cr2 - FIXED: use 4-parameter constructor
|
|
||||||
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
|
|
||||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
|
||||||
|
|
||||||
// FIXED: use SocketConnection
|
|
||||||
try (Socket socket = new Socket("localhost", 18001);
|
|
||||||
SocketConnection conn = new SocketConnection(socket)) {
|
|
||||||
|
|
||||||
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
|
|
||||||
conn.sendMessage(message);
|
|
||||||
|
|
||||||
Thread.sleep(2000); // time for processing
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (cr1 != null) {
|
|
||||||
cr1.shutdown();
|
|
||||||
}
|
|
||||||
if (cr2 != null) {
|
|
||||||
cr2.shutdown();
|
|
||||||
}
|
|
||||||
if (thread1 != null) {
|
|
||||||
thread1.join(2000);
|
|
||||||
}
|
|
||||||
if (thread2 != null) {
|
|
||||||
thread2.join(2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMain_MissingArguments() {
|
|
||||||
// main needs intersection ID as argument
|
|
||||||
// cant test System.exit easily in modern java
|
|
||||||
assertTrue(true, "Main method expects intersection ID as first argument");
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper class for testing vehicle messages
|
|
||||||
private static class TestVehicleMessage implements sd.protocol.MessageProtocol {
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private final String sourceNode;
|
|
||||||
private final String destinationNode;
|
|
||||||
private final Vehicle payload;
|
|
||||||
|
|
||||||
public TestVehicleMessage(String sourceNode, String destinationNode, Vehicle vehicle) {
|
|
||||||
this.sourceNode = sourceNode;
|
|
||||||
this.destinationNode = destinationNode;
|
|
||||||
this.payload = vehicle;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MessageType getType() {
|
|
||||||
return MessageType.VEHICLE_TRANSFER;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSourceNode() {
|
|
||||||
return sourceNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDestinationNode() {
|
|
||||||
return destinationNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import sd.config.SimulationConfig;
|
|
||||||
import sd.model.Intersection;
|
|
||||||
import sd.model.TrafficLight;
|
|
||||||
import sd.model.TrafficLightState;
|
|
||||||
import sd.model.Vehicle;
|
|
||||||
import sd.model.VehicleType;
|
|
||||||
import sd.util.VehicleGenerator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic tests for the simulation components.
|
|
||||||
*/
|
|
||||||
class SimulationTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testConfigurationLoading() throws IOException {
|
|
||||||
SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties");
|
|
||||||
|
|
||||||
assertEquals(60.0, config.getSimulationDuration());
|
|
||||||
assertEquals("POISSON", config.getArrivalModel());
|
|
||||||
assertEquals(0.5, config.getArrivalRate());
|
|
||||||
assertEquals(1.0, config.getStatisticsUpdateInterval());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testVehicleGeneration() throws IOException {
|
|
||||||
SimulationConfig config = new SimulationConfig("src/main/resources/simulation.properties");
|
|
||||||
VehicleGenerator generator = new VehicleGenerator(config);
|
|
||||||
|
|
||||||
Vehicle vehicle = generator.generateVehicle("TEST1", 0.0);
|
|
||||||
|
|
||||||
assertNotNull(vehicle);
|
|
||||||
assertEquals("TEST1", vehicle.getId());
|
|
||||||
assertNotNull(vehicle.getType());
|
|
||||||
assertNotNull(vehicle.getRoute());
|
|
||||||
assertTrue(!vehicle.getRoute().isEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testIntersectionVehicleQueue() {
|
|
||||||
Intersection intersection = new Intersection("TestCr");
|
|
||||||
TrafficLight light = new TrafficLight("TestCr-N", "North", 30.0, 30.0);
|
|
||||||
|
|
||||||
intersection.addTrafficLight(light);
|
|
||||||
|
|
||||||
Vehicle v1 = new Vehicle("V1", VehicleType.LIGHT, 0.0,
|
|
||||||
java.util.Arrays.asList("TestCr", "S"));
|
|
||||||
|
|
||||||
intersection.configureRoute("S", "North");
|
|
||||||
|
|
||||||
// Advance route to next destination
|
|
||||||
v1.advanceRoute();
|
|
||||||
|
|
||||||
intersection.receiveVehicle(v1);
|
|
||||||
|
|
||||||
assertEquals(1, intersection.getTotalQueueSize());
|
|
||||||
assertEquals(1, intersection.getTotalVehiclesReceived());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testTrafficLightStateChange() {
|
|
||||||
TrafficLight light = new TrafficLight("Test-Light", "North", 30.0, 30.0);
|
|
||||||
|
|
||||||
assertEquals(TrafficLightState.RED, light.getState());
|
|
||||||
|
|
||||||
light.changeState(TrafficLightState.GREEN);
|
|
||||||
assertEquals(TrafficLightState.GREEN, light.getState());
|
|
||||||
|
|
||||||
light.changeState(TrafficLightState.RED);
|
|
||||||
assertEquals(TrafficLightState.RED, light.getState());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removed testSimulationEngineInitialization as SimulationEngine has been
|
|
||||||
// removed.
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
import java.io.IOException;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.Timeout;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import sd.IntersectionProcess;
|
|
||||||
import sd.model.Message;
|
|
||||||
import sd.model.MessageType;
|
|
||||||
import sd.model.Vehicle;
|
|
||||||
import sd.model.VehicleType;
|
|
||||||
import sd.protocol.SocketConnection;
|
|
||||||
|
|
||||||
public class TravelTimeTest {
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
private Path configFile;
|
|
||||||
private IntersectionProcess intersectionProcess;
|
|
||||||
private Thread serverThread;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() throws IOException {
|
|
||||||
configFile = tempDir.resolve("test-simulation.properties");
|
|
||||||
String configContent = """
|
|
||||||
intersection.Cr1.host=localhost
|
|
||||||
intersection.Cr1.port=19001
|
|
||||||
intersection.Cr2.host=localhost
|
|
||||||
intersection.Cr2.port=19002
|
|
||||||
|
|
||||||
# Base travel time = 1.0s for testing
|
|
||||||
vehicle.travel.time.base=1.0
|
|
||||||
vehicle.travel.time.bike.multiplier=0.5
|
|
||||||
vehicle.travel.time.heavy.multiplier=4.0
|
|
||||||
|
|
||||||
# Dummy values for others
|
|
||||||
dashboard.host=localhost
|
|
||||||
dashboard.port=19100
|
|
||||||
exit.host=localhost
|
|
||||||
exit.port=19099
|
|
||||||
""";
|
|
||||||
Files.writeString(configFile, configContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void tearDown() {
|
|
||||||
if (intersectionProcess != null) {
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
}
|
|
||||||
if (serverThread != null) {
|
|
||||||
try {
|
|
||||||
serverThread.join(2000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Timeout(10)
|
|
||||||
public void testVariableTravelTimes() throws IOException, InterruptedException {
|
|
||||||
// Start Intersection Cr1
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr1", configFile.toString());
|
|
||||||
// Mock network config for Cr1 to know about Cr2
|
|
||||||
// Since we can't easily inject network config without file, we rely on
|
|
||||||
// IntersectionProcess
|
|
||||||
// using the properties file we created. But wait, IntersectionProcess loads
|
|
||||||
// network_config.json
|
|
||||||
// from classpath. This might be an issue if we need custom routing.
|
|
||||||
// However, sendVehicleToNextDestination just looks up host/port from
|
|
||||||
// properties.
|
|
||||||
// We need to ensure getOrCreateConnection works.
|
|
||||||
|
|
||||||
// Let's manually inject the connection or just rely on properties.
|
|
||||||
// The properties file has intersection.Cr2.host/port, so it should work.
|
|
||||||
|
|
||||||
// Start a "fake" Cr2 server to receive the vehicle
|
|
||||||
BlockingQueue<Long> arrivalTimes = new LinkedBlockingQueue<>();
|
|
||||||
ServerSocket fakeCr2 = new ServerSocket(19002);
|
|
||||||
Thread cr2Thread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
Socket socket = fakeCr2.accept();
|
|
||||||
SocketConnection conn = new SocketConnection(socket);
|
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
|
||||||
try {
|
|
||||||
conn.receiveMessage();
|
|
||||||
arrivalTimes.offer(System.currentTimeMillis());
|
|
||||||
} catch (Exception e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
// End
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cr2Thread.start();
|
|
||||||
|
|
||||||
// Send vehicles from Cr1
|
|
||||||
// We need to call sendVehicleToNextDestination directly.
|
|
||||||
// But we need to initialize Cr1 first (at least the executor).
|
|
||||||
// We can't easily call initialize() because it tries to connect to dashboard
|
|
||||||
// etc.
|
|
||||||
// But the constructor initializes the executors!
|
|
||||||
|
|
||||||
// 1. Light Vehicle (Base = 1.0s)
|
|
||||||
Vehicle lightVehicle = new Vehicle("V_LIGHT", VehicleType.LIGHT, 0, Arrays.asList("Cr2"));
|
|
||||||
long startLight = System.currentTimeMillis();
|
|
||||||
intersectionProcess.sendVehicleToNextDestination(lightVehicle);
|
|
||||||
|
|
||||||
Long arrivalLight = arrivalTimes.poll(2000, TimeUnit.MILLISECONDS);
|
|
||||||
assertNotNull(arrivalLight, "Light vehicle should arrive");
|
|
||||||
long durationLight = arrivalLight - startLight;
|
|
||||||
System.out.println("Light Duration: " + durationLight + "ms");
|
|
||||||
assertTrue(durationLight >= 1000, "Light vehicle should take at least 1000ms");
|
|
||||||
assertTrue(durationLight < 1500, "Light vehicle should be close to 1000ms");
|
|
||||||
|
|
||||||
// 2. Bike (0.5 * 1.0 = 0.5s)
|
|
||||||
Vehicle bikeVehicle = new Vehicle("V_BIKE", VehicleType.BIKE, 0, Arrays.asList("Cr2"));
|
|
||||||
long startBike = System.currentTimeMillis();
|
|
||||||
intersectionProcess.sendVehicleToNextDestination(bikeVehicle);
|
|
||||||
|
|
||||||
Long arrivalBike = arrivalTimes.poll(2000, TimeUnit.MILLISECONDS);
|
|
||||||
assertNotNull(arrivalBike, "Bike should arrive");
|
|
||||||
long durationBike = arrivalBike - startBike;
|
|
||||||
System.out.println("Bike Duration: " + durationBike + "ms");
|
|
||||||
assertTrue(durationBike >= 500, "Bike should take at least 500ms");
|
|
||||||
assertTrue(durationBike < 1000, "Bike should be close to 500ms");
|
|
||||||
|
|
||||||
// 3. Heavy (4.0 * 1.0 = 4.0s)
|
|
||||||
Vehicle heavyVehicle = new Vehicle("V_HEAVY", VehicleType.HEAVY, 0, Arrays.asList("Cr2"));
|
|
||||||
long startHeavy = System.currentTimeMillis();
|
|
||||||
intersectionProcess.sendVehicleToNextDestination(heavyVehicle);
|
|
||||||
|
|
||||||
Long arrivalHeavy = arrivalTimes.poll(5000, TimeUnit.MILLISECONDS);
|
|
||||||
assertNotNull(arrivalHeavy, "Heavy vehicle should arrive");
|
|
||||||
long durationHeavy = arrivalHeavy - startHeavy;
|
|
||||||
System.out.println("Heavy Duration: " + durationHeavy + "ms");
|
|
||||||
assertTrue(durationHeavy >= 4000, "Heavy vehicle should take at least 4000ms");
|
|
||||||
assertTrue(durationHeavy < 4500, "Heavy vehicle should be close to 4000ms");
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
fakeCr2.close();
|
|
||||||
cr2Thread.interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
package sd;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.Timeout;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import sd.config.SimulationConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testes unitários para a classe ExitNodeProcess.
|
|
||||||
*
|
|
||||||
* Esta classe de testes verifica:
|
|
||||||
* - Construção e inicialização do processo
|
|
||||||
* - Criação e aceitação de conexões do servidor socket
|
|
||||||
* - Gestão do ciclo de vida (start/shutdown)
|
|
||||||
* - Processamento concorrente de múltiplas conexões
|
|
||||||
* - Impressão de estatísticas finais
|
|
||||||
*
|
|
||||||
* Os testes utilizam configurações temporárias e portas dedicadas (19001)
|
|
||||||
* para evitar conflitos com outros testes ou processos em execução.
|
|
||||||
*/
|
|
||||||
public class ExitNodeProcessTest {
|
|
||||||
|
|
||||||
@TempDir
|
|
||||||
Path tempDir;
|
|
||||||
|
|
||||||
private Path configFile;
|
|
||||||
private ExitNodeProcess exitNodeProcess;
|
|
||||||
private Thread exitNodeThread;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configura o ambiente de teste antes de cada teste.
|
|
||||||
* Cria um ficheiro de configuração temporário com as definições necessárias.
|
|
||||||
*/
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() throws IOException {
|
|
||||||
configFile = tempDir.resolve("test-simulation.properties");
|
|
||||||
|
|
||||||
String configContent = """
|
|
||||||
# Test Exit Node Configuration
|
|
||||||
|
|
||||||
# Exit Configuration
|
|
||||||
exit.host=localhost
|
|
||||||
exit.port=19001
|
|
||||||
|
|
||||||
# Dashboard Configuration (will not be running in tests)
|
|
||||||
dashboard.host=localhost
|
|
||||||
dashboard.port=19000
|
|
||||||
|
|
||||||
# Vehicle Crossing Times
|
|
||||||
vehicle.bike.crossingTime=2.0
|
|
||||||
vehicle.light.crossingTime=3.0
|
|
||||||
vehicle.heavy.crossingTime=5.0
|
|
||||||
|
|
||||||
# Simulation Duration
|
|
||||||
simulation.duration=60.0
|
|
||||||
""";
|
|
||||||
|
|
||||||
Files.writeString(configFile, configContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limpa os recursos após cada teste.
|
|
||||||
* Garante que o processo e threads são terminados corretamente.
|
|
||||||
*/
|
|
||||||
@AfterEach
|
|
||||||
public void tearDown() {
|
|
||||||
if (exitNodeProcess != null) {
|
|
||||||
exitNodeProcess.shutdown();
|
|
||||||
}
|
|
||||||
if (exitNodeThread != null && exitNodeThread.isAlive()) {
|
|
||||||
exitNodeThread.interrupt();
|
|
||||||
try {
|
|
||||||
exitNodeThread.join(1000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa a construção bem-sucedida do ExitNodeProcess com configuração válida.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testConstructor_Success() throws IOException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
assertNotNull(exitNodeProcess);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa que uma exceção é lançada quando a configuração é inválida.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testConstructor_InvalidConfig() {
|
|
||||||
Exception exception = assertThrows(IOException.class, () -> {
|
|
||||||
new SimulationConfig("non-existent-config.properties");
|
|
||||||
});
|
|
||||||
assertNotNull(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa a inicialização sem dashboard disponível.
|
|
||||||
* Verifica que o processo continua a funcionar mesmo sem conexão ao dashboard.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testInitialize_WithoutDashboard() throws IOException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
assertDoesNotThrow(() -> exitNodeProcess.initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa que o servidor socket é criado corretamente na porta configurada.
|
|
||||||
* Verifica que é possível estabelecer uma conexão ao socket do servidor.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(value = 3, unit = TimeUnit.SECONDS)
|
|
||||||
public void testStart_ServerSocketCreated() throws IOException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
exitNodeProcess.initialize();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
exitNodeThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
latch.countDown();
|
|
||||||
exitNodeProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// expected when shutdown
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
exitNodeThread.start();
|
|
||||||
|
|
||||||
try {
|
|
||||||
assertTrue(latch.await(2, TimeUnit.SECONDS), "Exit node should start within timeout");
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
try (Socket testSocket = new Socket("localhost", 19001)) {
|
|
||||||
assertTrue(testSocket.isConnected());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa que o servidor aceita conexões de clientes.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(value = 3, unit = TimeUnit.SECONDS)
|
|
||||||
public void testStart_AcceptsConnection() throws IOException, InterruptedException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
exitNodeProcess.initialize();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
exitNodeThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
latch.countDown();
|
|
||||||
exitNodeProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
exitNodeThread.start();
|
|
||||||
|
|
||||||
assertTrue(latch.await(2, TimeUnit.SECONDS));
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
try (Socket socket = new Socket("localhost", 19001)) {
|
|
||||||
assertTrue(socket.isConnected());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa múltiplas inicializações e encerramentos do processo.
|
|
||||||
* Verifica que o processo pode ser iniciado e parado múltiplas vezes,
|
|
||||||
* permitindo reutilização da porta.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(value = 3, unit = TimeUnit.SECONDS)
|
|
||||||
public void testMultipleStartStop() throws IOException, InterruptedException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
exitNodeProcess.initialize();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
exitNodeThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
latch.countDown();
|
|
||||||
exitNodeProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
exitNodeThread.start();
|
|
||||||
assertTrue(latch.await(2, TimeUnit.SECONDS));
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
exitNodeProcess.shutdown();
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> {
|
|
||||||
SimulationConfig config2 = new SimulationConfig(configFile.toString());
|
|
||||||
ExitNodeProcess exitNode2 = new ExitNodeProcess(config2);
|
|
||||||
exitNode2.initialize();
|
|
||||||
exitNode2.shutdown();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa que o shutdown fecha corretamente o servidor socket.
|
|
||||||
* Após o shutdown, novas conexões ao socket devem falhar.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(value = 3, unit = TimeUnit.SECONDS)
|
|
||||||
public void testShutdown_ClosesServerSocket() throws IOException, InterruptedException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
exitNodeProcess.initialize();
|
|
||||||
|
|
||||||
CountDownLatch startLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
exitNodeThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
startLatch.countDown();
|
|
||||||
exitNodeProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
exitNodeThread.start();
|
|
||||||
assertTrue(startLatch.await(2, TimeUnit.SECONDS));
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
exitNodeProcess.shutdown();
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
assertThrows(IOException.class, () -> {
|
|
||||||
Socket socket = new Socket("localhost", 19001);
|
|
||||||
socket.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa que as estatísticas finais são impressas corretamente durante o shutdown.
|
|
||||||
* Verifica que o método não lança exceções mesmo sem dados processados.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testPrintFinalStatistics() throws IOException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
exitNodeProcess.initialize();
|
|
||||||
|
|
||||||
assertDoesNotThrow(() -> exitNodeProcess.shutdown());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Testa o processamento de múltiplas conexões concorrentes.
|
|
||||||
* Verifica que o servidor consegue lidar com vários clientes simultaneamente
|
|
||||||
* usando o pool de threads.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(value = 3, unit = TimeUnit.SECONDS)
|
|
||||||
public void testMultipleConcurrentConnections() throws IOException, InterruptedException {
|
|
||||||
SimulationConfig config = new SimulationConfig(configFile.toString());
|
|
||||||
exitNodeProcess = new ExitNodeProcess(config);
|
|
||||||
exitNodeProcess.initialize();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
exitNodeThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
latch.countDown();
|
|
||||||
exitNodeProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// expected
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
exitNodeThread.start();
|
|
||||||
assertTrue(latch.await(2, TimeUnit.SECONDS));
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
Thread[] clients = new Thread[3];
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
clients[i] = new Thread(() -> {
|
|
||||||
try (Socket socket = new Socket("localhost", 19001)) {
|
|
||||||
assertTrue(socket.isConnected());
|
|
||||||
Thread.sleep(100);
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clients[i].start();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Thread client : clients) {
|
|
||||||
client.join(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
package sd;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import sd.model.TrafficLight;
|
|
||||||
import sd.model.TrafficLightState;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test class to verify traffic light coordination within an intersection.
|
|
||||||
* Ensures that only ONE traffic light can be GREEN at any given time.
|
|
||||||
*/
|
|
||||||
public class TrafficLightCoordinationTest {
|
|
||||||
|
|
||||||
private IntersectionProcess intersectionProcess;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() throws IOException {
|
|
||||||
// Create an intersection with multiple traffic lights
|
|
||||||
intersectionProcess = new IntersectionProcess("Cr2", "src/main/resources/simulation.properties");
|
|
||||||
intersectionProcess.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void tearDown() throws InterruptedException {
|
|
||||||
if (intersectionProcess != null) {
|
|
||||||
intersectionProcess.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that verifies mutual exclusion between traffic lights.
|
|
||||||
* Monitors all traffic lights for 10 seconds and ensures that
|
|
||||||
* at most ONE light is GREEN at any point in time.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testOnlyOneGreenLightAtATime() throws InterruptedException {
|
|
||||||
System.out.println("\n=== Testing Traffic Light Mutual Exclusion ===");
|
|
||||||
|
|
||||||
// Start the intersection
|
|
||||||
Thread intersectionThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
intersectionThread.start();
|
|
||||||
|
|
||||||
// Monitor traffic lights for violations
|
|
||||||
AtomicInteger maxGreenSimultaneously = new AtomicInteger(0);
|
|
||||||
AtomicInteger violationCount = new AtomicInteger(0);
|
|
||||||
List<String> violations = new ArrayList<>();
|
|
||||||
|
|
||||||
// Monitor for 10 seconds
|
|
||||||
long endTime = System.currentTimeMillis() + 10000;
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() < endTime) {
|
|
||||||
int greenCount = 0;
|
|
||||||
StringBuilder currentState = new StringBuilder("States: ");
|
|
||||||
|
|
||||||
for (TrafficLight light : intersectionProcess.getIntersection().getTrafficLights()) {
|
|
||||||
TrafficLightState state = light.getState();
|
|
||||||
currentState.append(light.getDirection()).append("=").append(state).append(" ");
|
|
||||||
|
|
||||||
if (state == TrafficLightState.GREEN) {
|
|
||||||
greenCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update maximum simultaneous green lights
|
|
||||||
if (greenCount > maxGreenSimultaneously.get()) {
|
|
||||||
maxGreenSimultaneously.set(greenCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for violations (more than one green)
|
|
||||||
if (greenCount > 1) {
|
|
||||||
violationCount.incrementAndGet();
|
|
||||||
String violation = String.format("[VIOLATION] %d lights GREEN simultaneously: %s",
|
|
||||||
greenCount, currentState.toString());
|
|
||||||
violations.add(violation);
|
|
||||||
System.err.println(violation);
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(50); // Check every 50ms
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("\n=== Test Results ===");
|
|
||||||
System.out.println("Maximum simultaneous GREEN lights: " + maxGreenSimultaneously.get());
|
|
||||||
System.out.println("Total violations detected: " + violationCount.get());
|
|
||||||
|
|
||||||
if (!violations.isEmpty()) {
|
|
||||||
System.err.println("\nViolation details:");
|
|
||||||
violations.forEach(System.err::println);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that we never had more than one green light
|
|
||||||
assertEquals(0, violationCount.get(),
|
|
||||||
"Traffic light coordination violated! Multiple lights were GREEN simultaneously.");
|
|
||||||
assertTrue(maxGreenSimultaneously.get() <= 1,
|
|
||||||
"At most ONE light should be GREEN at any time. Found: " + maxGreenSimultaneously.get());
|
|
||||||
|
|
||||||
System.out.println("\nTraffic light coordination working correctly!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that verifies all traffic lights get a chance to be GREEN.
|
|
||||||
* Ensures fairness in the coordination mechanism.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testAllLightsGetGreenTime() throws InterruptedException {
|
|
||||||
System.out.println("\n=== Testing Traffic Light Fairness ===");
|
|
||||||
|
|
||||||
// Start the intersection
|
|
||||||
Thread intersectionThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
intersectionThread.start();
|
|
||||||
|
|
||||||
// Track which lights have been green
|
|
||||||
List<TrafficLight> lights = intersectionProcess.getIntersection().getTrafficLights();
|
|
||||||
boolean[] hasBeenGreen = new boolean[lights.size()];
|
|
||||||
|
|
||||||
// Monitor for 10 seconds (enough time for all lights to cycle: 18+18+12 = 48s)
|
|
||||||
long endTime = System.currentTimeMillis() + 10000;
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() < endTime) {
|
|
||||||
for (int i = 0; i < lights.size(); i++) {
|
|
||||||
if (lights.get(i).getState() == TrafficLightState.GREEN) {
|
|
||||||
hasBeenGreen[i] = true;
|
|
||||||
System.out.println("✓ " + lights.get(i).getDirection() + " has been GREEN");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if all lights got green time
|
|
||||||
int greenCount = 0;
|
|
||||||
System.out.println("\n=== Fairness Results ===");
|
|
||||||
for (int i = 0; i < lights.size(); i++) {
|
|
||||||
String status = hasBeenGreen[i] ? "✓ YES" : "✗ NO";
|
|
||||||
System.out.println(lights.get(i).getDirection() + " got GREEN time: " + status);
|
|
||||||
if (hasBeenGreen[i])
|
|
||||||
greenCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(greenCount > 0, "At least one light should have been GREEN during the test");
|
|
||||||
System.out.println("\n" + greenCount + "/" + lights.size() + " lights were GREEN during test period");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that verifies the state transitions are consistent.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testStateTransitionsAreConsistent() throws InterruptedException {
|
|
||||||
System.out.println("\n=== Testing State Transition Consistency ===");
|
|
||||||
|
|
||||||
Thread intersectionThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
intersectionProcess.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
intersectionThread.start();
|
|
||||||
|
|
||||||
List<TrafficLight> lights = intersectionProcess.getIntersection().getTrafficLights();
|
|
||||||
TrafficLightState[] previousStates = new TrafficLightState[lights.size()];
|
|
||||||
|
|
||||||
// Initialize previous states
|
|
||||||
for (int i = 0; i < lights.size(); i++) {
|
|
||||||
previousStates[i] = lights.get(i).getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
int transitionCount = 0;
|
|
||||||
long endTime = System.currentTimeMillis() + 8000;
|
|
||||||
|
|
||||||
while (System.currentTimeMillis() < endTime) {
|
|
||||||
for (int i = 0; i < lights.size(); i++) {
|
|
||||||
TrafficLightState currentState = lights.get(i).getState();
|
|
||||||
|
|
||||||
if (currentState != previousStates[i]) {
|
|
||||||
transitionCount++;
|
|
||||||
System.out.println(lights.get(i).getDirection() + " transitioned: " +
|
|
||||||
previousStates[i] + " → " + currentState);
|
|
||||||
previousStates[i] = currentState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread.sleep(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("\nTotal state transitions observed: " + transitionCount);
|
|
||||||
assertTrue(transitionCount > 0, "There should be state transitions during the test period");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
package sd.coordinator;
|
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.Timeout;
|
|
||||||
|
|
||||||
import sd.model.Message;
|
|
||||||
import sd.model.MessageType;
|
|
||||||
import sd.model.Vehicle;
|
|
||||||
import sd.serialization.MessageSerializer;
|
|
||||||
import sd.serialization.SerializerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests for the Coordinator-side networking.
|
|
||||||
*
|
|
||||||
* What we’re checking here:
|
|
||||||
* 1. A SocketClient can actually connect to something listening
|
|
||||||
* 2. Messages go over the wire and can be deserialized
|
|
||||||
* 3. Vehicle payloads survive the trip
|
|
||||||
* 4. Shutdown messages can be broadcast to multiple intersections
|
|
||||||
*
|
|
||||||
* We do this by spinning up a tiny mock intersection server in-process.
|
|
||||||
*/
|
|
||||||
class CoordinatorIntegrationTest {
|
|
||||||
|
|
||||||
private List<MockIntersectionServer> mockServers;
|
|
||||||
private static final int BASE_PORT = 9001; // keep clear of real ports
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
mockServers = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void tearDown() {
|
|
||||||
// Stop all mock servers
|
|
||||||
for (MockIntersectionServer server : mockServers) {
|
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
mockServers.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Can the client open a TCP connection to our fake intersection?
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
void testSocketClientConnection() throws IOException, InterruptedException {
|
|
||||||
MockIntersectionServer server = new MockIntersectionServer("Cr1", BASE_PORT);
|
|
||||||
server.start();
|
|
||||||
mockServers.add(server);
|
|
||||||
|
|
||||||
// tiny pause to let the server bind
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
SocketClient client = new SocketClient("Cr1", "localhost", BASE_PORT);
|
|
||||||
client.connect();
|
|
||||||
|
|
||||||
assertTrue(client.isConnected(), "Client should be connected to mock intersection");
|
|
||||||
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End-to-end: send a message, make sure the server actually receives it.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
void testMessageTransmission() throws Exception {
|
|
||||||
MockIntersectionServer server = new MockIntersectionServer("Cr1", BASE_PORT);
|
|
||||||
server.start();
|
|
||||||
mockServers.add(server);
|
|
||||||
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
SocketClient client = new SocketClient("Cr1", "localhost", BASE_PORT);
|
|
||||||
client.connect();
|
|
||||||
|
|
||||||
Message testMessage = new Message(
|
|
||||||
MessageType.VEHICLE_SPAWN,
|
|
||||||
"COORDINATOR",
|
|
||||||
"Cr1",
|
|
||||||
"Test payload"
|
|
||||||
);
|
|
||||||
|
|
||||||
client.send(testMessage);
|
|
||||||
|
|
||||||
// give the server a moment to read and deserialize
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
assertFalse(
|
|
||||||
server.getReceivedMessages().isEmpty(),
|
|
||||||
"Mock server should have received at least one message"
|
|
||||||
);
|
|
||||||
|
|
||||||
Message receivedMsg = server.getReceivedMessages().poll();
|
|
||||||
assertNotNull(receivedMsg, "Server should have actually received a message");
|
|
||||||
assertEquals(MessageType.VEHICLE_SPAWN, receivedMsg.getType(), "Message type should match what we sent");
|
|
||||||
assertEquals("COORDINATOR", receivedMsg.getSenderId(), "Sender ID should be preserved");
|
|
||||||
assertEquals("Cr1", receivedMsg.getDestinationId(), "Destination ID should be preserved");
|
|
||||||
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure vehicle payloads survive the trip and arrive non-null.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
void testVehicleSpawnMessage() throws Exception {
|
|
||||||
MockIntersectionServer server = new MockIntersectionServer("Cr1", BASE_PORT);
|
|
||||||
server.start();
|
|
||||||
mockServers.add(server);
|
|
||||||
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
SocketClient client = new SocketClient("Cr1", "localhost", BASE_PORT);
|
|
||||||
client.connect();
|
|
||||||
|
|
||||||
// fake a vehicle like the coordinator would send
|
|
||||||
List<String> route = List.of("Cr1", "Cr4", "Cr5", "S");
|
|
||||||
Vehicle vehicle = new Vehicle("V1", sd.model.VehicleType.LIGHT, 0.0, route);
|
|
||||||
|
|
||||||
Message spawnMessage = new Message(
|
|
||||||
MessageType.VEHICLE_SPAWN,
|
|
||||||
"COORDINATOR",
|
|
||||||
"Cr1",
|
|
||||||
vehicle
|
|
||||||
);
|
|
||||||
|
|
||||||
client.send(spawnMessage);
|
|
||||||
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
Message receivedMsg = server.getReceivedMessages().poll();
|
|
||||||
assertNotNull(receivedMsg, "Mock server should receive the spawn message");
|
|
||||||
assertEquals(MessageType.VEHICLE_SPAWN, receivedMsg.getType(), "Message should be of type VEHICLE_SPAWN");
|
|
||||||
assertNotNull(receivedMsg.getPayload(), "Payload should not be null (vehicle must arrive)");
|
|
||||||
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Broadcast shutdown to multiple mock intersections and see if all of them get it.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Timeout(5)
|
|
||||||
void testShutdownMessageBroadcast() throws Exception {
|
|
||||||
// Start a couple of fake intersections
|
|
||||||
for (int i = 1; i <= 3; i++) {
|
|
||||||
MockIntersectionServer server = new MockIntersectionServer("Cr" + i, BASE_PORT + i - 1);
|
|
||||||
server.start();
|
|
||||||
mockServers.add(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
// Connect to all of them
|
|
||||||
List<SocketClient> clients = new ArrayList<>();
|
|
||||||
for (int i = 1; i <= 3; i++) {
|
|
||||||
SocketClient client = new SocketClient("Cr" + i, "localhost", BASE_PORT + i - 1);
|
|
||||||
client.connect();
|
|
||||||
clients.add(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
Message shutdownMessage = new Message(
|
|
||||||
MessageType.SHUTDOWN,
|
|
||||||
"COORDINATOR",
|
|
||||||
"ALL",
|
|
||||||
"Simulation complete"
|
|
||||||
);
|
|
||||||
|
|
||||||
for (SocketClient client : clients) {
|
|
||||||
client.send(shutdownMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(200);
|
|
||||||
|
|
||||||
for (MockIntersectionServer server : mockServers) {
|
|
||||||
assertFalse(
|
|
||||||
server.getReceivedMessages().isEmpty(),
|
|
||||||
"Server " + server.getIntersectionId() + " should have received the shutdown message"
|
|
||||||
);
|
|
||||||
|
|
||||||
Message msg = server.getReceivedMessages().poll();
|
|
||||||
assertEquals(MessageType.SHUTDOWN, msg.getType(), "Server should receive a SHUTDOWN message");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SocketClient client : clients) {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tiny TCP server that pretends to be an intersection.
|
|
||||||
* It:
|
|
||||||
* - listens on a port
|
|
||||||
* - accepts connections
|
|
||||||
* - reads length-prefixed messages
|
|
||||||
* - deserializes them and stores them for the test to inspect
|
|
||||||
*/
|
|
||||||
private static class MockIntersectionServer {
|
|
||||||
private final String intersectionId;
|
|
||||||
private final int port;
|
|
||||||
private ServerSocket serverSocket;
|
|
||||||
private Thread serverThread;
|
|
||||||
private volatile boolean running;
|
|
||||||
private final ConcurrentLinkedQueue<Message> receivedMessages;
|
|
||||||
private final MessageSerializer serializer;
|
|
||||||
|
|
||||||
public MockIntersectionServer(String intersectionId, int port) {
|
|
||||||
this.intersectionId = intersectionId;
|
|
||||||
this.port = port;
|
|
||||||
this.receivedMessages = new ConcurrentLinkedQueue<>();
|
|
||||||
this.serializer = SerializerFactory.createDefault();
|
|
||||||
this.running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() throws IOException {
|
|
||||||
serverSocket = new ServerSocket(port);
|
|
||||||
running = true;
|
|
||||||
|
|
||||||
System.out.printf("Mock %s listening on port %d%n", intersectionId, port);
|
|
||||||
|
|
||||||
serverThread = new Thread(() -> {
|
|
||||||
try {
|
|
||||||
while (running) {
|
|
||||||
Socket clientSocket = serverSocket.accept();
|
|
||||||
handleClient(clientSocket);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (running) {
|
|
||||||
System.err.println("Mock " + intersectionId + " server error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, "mock-" + intersectionId + "-listener");
|
|
||||||
|
|
||||||
serverThread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleClient(Socket clientSocket) {
|
|
||||||
new Thread(() -> {
|
|
||||||
try (DataInputStream input = new DataInputStream(clientSocket.getInputStream())) {
|
|
||||||
while (running) {
|
|
||||||
// Read length prefix (4 bytes, big-endian)
|
|
||||||
int length = input.readInt();
|
|
||||||
byte[] data = new byte[length];
|
|
||||||
input.readFully(data);
|
|
||||||
|
|
||||||
Message message = serializer.deserialize(data, Message.class);
|
|
||||||
receivedMessages.offer(message);
|
|
||||||
|
|
||||||
System.out.println("Mock " + intersectionId + " received: " + message.getType());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (running) {
|
|
||||||
System.err.println("Mock " + intersectionId + " client handler error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("Mock " + intersectionId + " deserialization error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}, "mock-" + intersectionId + "-client").start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
running = false;
|
|
||||||
try {
|
|
||||||
if (serverSocket != null && !serverSocket.isClosed()) {
|
|
||||||
serverSocket.close();
|
|
||||||
}
|
|
||||||
if (serverThread != null) {
|
|
||||||
serverThread.interrupt();
|
|
||||||
serverThread.join(1000);
|
|
||||||
}
|
|
||||||
System.out.printf("Mock %s stopped%n", intersectionId);
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
System.err.println("Error stopping mock server " + intersectionId + ": " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConcurrentLinkedQueue<Message> getReceivedMessages() {
|
|
||||||
return receivedMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getIntersectionId() {
|
|
||||||
return intersectionId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
package sd.coordinator;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import sd.config.SimulationConfig;
|
|
||||||
import sd.model.Vehicle;
|
|
||||||
import sd.util.VehicleGenerator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for the Coordinator/vehicle-generation layer.
|
|
||||||
*
|
|
||||||
* What we’re checking here:
|
|
||||||
* 1. Coordinator can be created with a valid config
|
|
||||||
* 2. Vehicle arrival times are monotonic and sane
|
|
||||||
* 3. Vehicle IDs are created in the format we expect (V1, V2, ...)
|
|
||||||
* 4. Generated vehicles have proper routes (start at CrX, end at S)
|
|
||||||
* 5. Config actually has intersection info
|
|
||||||
* 6. Duration in config is not something crazy
|
|
||||||
*/
|
|
||||||
class CoordinatorProcessTest {
|
|
||||||
|
|
||||||
private SimulationConfig config;
|
|
||||||
private static final String TEST_CONFIG = "src/main/resources/simulation.properties";
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() throws IOException {
|
|
||||||
config = new SimulationConfig(TEST_CONFIG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void tearDown() {
|
|
||||||
config = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic smoke test: can we build a coordinator with this config?
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testCoordinatorInitialization() {
|
|
||||||
CoordinatorProcess coordinator = new CoordinatorProcess(config);
|
|
||||||
assertNotNull(coordinator, "Coordinator should be created with a valid config");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make sure the VehicleGenerator is giving us increasing arrival times,
|
|
||||||
* i.e. time doesn’t go backwards and intervals look reasonable.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testVehicleGenerationTiming() {
|
|
||||||
VehicleGenerator generator = new VehicleGenerator(config);
|
|
||||||
|
|
||||||
double currentTime = 0.0;
|
|
||||||
List<Double> arrivalTimes = new ArrayList<>();
|
|
||||||
|
|
||||||
// generate a small batch to inspect
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
double nextArrival = generator.getNextArrivalTime(currentTime);
|
|
||||||
arrivalTimes.add(nextArrival);
|
|
||||||
currentTime = nextArrival;
|
|
||||||
}
|
|
||||||
|
|
||||||
// times should strictly increase
|
|
||||||
for (int i = 1; i < arrivalTimes.size(); i++) {
|
|
||||||
assertTrue(
|
|
||||||
arrivalTimes.get(i) > arrivalTimes.get(i - 1),
|
|
||||||
"Arrival times must increase — got " + arrivalTimes.get(i - 1) + " then " + arrivalTimes.get(i)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// and they shouldn't be nonsense
|
|
||||||
for (double time : arrivalTimes) {
|
|
||||||
assertTrue(time >= 0, "Arrival time should not be negative (got " + time + ")");
|
|
||||||
assertTrue(time < 1000, "Arrival time looks suspiciously large: " + time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We generate V1..V5 manually and make sure the IDs are exactly those.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testVehicleIdGeneration() {
|
|
||||||
VehicleGenerator generator = new VehicleGenerator(config);
|
|
||||||
|
|
||||||
List<Vehicle> vehicles = new ArrayList<>();
|
|
||||||
for (int i = 1; i <= 5; i++) {
|
|
||||||
Vehicle v = generator.generateVehicle("V" + i, 0.0);
|
|
||||||
vehicles.add(v);
|
|
||||||
assertEquals("V" + i, v.getId(), "Vehicle ID should be 'V" + i + "' but got " + v.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// just to be safe, no duplicates in that small set
|
|
||||||
long distinctCount = vehicles.stream().map(Vehicle::getId).distinct().count();
|
|
||||||
assertEquals(5, distinctCount, "Vehicle IDs in this batch should all be unique");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generated vehicle should:
|
|
||||||
* - have a non-empty route
|
|
||||||
* - start in a known intersection (Cr1..Cr5)
|
|
||||||
* - end in S (exit)
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testVehicleRouteValidity() {
|
|
||||||
VehicleGenerator generator = new VehicleGenerator(config);
|
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++) {
|
|
||||||
Vehicle vehicle = generator.generateVehicle("V" + i, 0.0);
|
|
||||||
|
|
||||||
assertNotNull(vehicle.getRoute(), "Vehicle route should not be null");
|
|
||||||
assertFalse(vehicle.getRoute().isEmpty(), "Vehicle route should not be empty");
|
|
||||||
|
|
||||||
String firstHop = vehicle.getRoute().get(0);
|
|
||||||
assertTrue(
|
|
||||||
firstHop.matches("Cr[1-5]"),
|
|
||||||
"First hop should be a valid intersection (Cr1..Cr5), got: " + firstHop
|
|
||||||
);
|
|
||||||
|
|
||||||
String lastHop = vehicle.getRoute().get(vehicle.getRoute().size() - 1);
|
|
||||||
assertEquals("S", lastHop, "Last hop should be exit 'S' but got: " + lastHop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whatever is in simulation.properties should give us a sane duration.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testSimulationDuration() {
|
|
||||||
double duration = config.getSimulationDuration();
|
|
||||||
assertTrue(duration > 0, "Simulation duration must be positive");
|
|
||||||
assertTrue(duration >= 1.0, "Simulation should run at least 1 second (got " + duration + ")");
|
|
||||||
assertTrue(duration <= 86400.0, "Simulation should not run more than a day (got " + duration + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that the 5 intersections defined in the architecture
|
|
||||||
* actually exist in the config and have valid network data.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testIntersectionConfiguration() {
|
|
||||||
String[] intersectionIds = {"Cr1", "Cr2", "Cr3", "Cr4", "Cr5"};
|
|
||||||
|
|
||||||
for (String id : intersectionIds) {
|
|
||||||
String host = config.getIntersectionHost(id);
|
|
||||||
int port = config.getIntersectionPort(id);
|
|
||||||
|
|
||||||
assertNotNull(host, "Host should not be null for " + id);
|
|
||||||
assertFalse(host.isEmpty(), "Host should not be empty for " + id);
|
|
||||||
assertTrue(port > 0, "Port should be > 0 for " + id + " (got " + port + ")");
|
|
||||||
assertTrue(port < 65536, "Port should be a valid TCP port for " + id + " (got " + port + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Quick sanity check: over a bunch of generated vehicles,
|
|
||||||
* we should eventually see the different vehicle types appear.
|
|
||||||
*
|
|
||||||
* Note: this is probabilistic, so we're not being super strict.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testVehicleTypeDistribution() {
|
|
||||||
VehicleGenerator generator = new VehicleGenerator(config);
|
|
||||||
|
|
||||||
boolean hasBike = false;
|
|
||||||
boolean hasLight = false;
|
|
||||||
boolean hasHeavy = false;
|
|
||||||
|
|
||||||
// 50 is enough for a "we're probably fine" test
|
|
||||||
for (int i = 0; i < 50; i++) {
|
|
||||||
Vehicle vehicle = generator.generateVehicle("V" + i, 0.0);
|
|
||||||
|
|
||||||
switch (vehicle.getType()) {
|
|
||||||
case BIKE -> hasBike = true;
|
|
||||||
case LIGHT -> hasLight = true;
|
|
||||||
case HEAVY -> hasHeavy = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// at least one of them should have shown up — if not, RNG is cursed
|
|
||||||
assertTrue(
|
|
||||||
hasBike || hasLight || hasHeavy,
|
|
||||||
"Expected to see at least one vehicle type after 50 generations"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
package sd.dashboard;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import sd.config.SimulationConfig;
|
|
||||||
import sd.model.VehicleType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for Dashboard Server components.
|
|
||||||
*/
|
|
||||||
class DashboardTest {
|
|
||||||
|
|
||||||
private DashboardStatistics statistics;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
statistics = new DashboardStatistics();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void tearDown() {
|
|
||||||
statistics = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testInitialStatistics() {
|
|
||||||
assertEquals(0, statistics.getTotalVehiclesGenerated(),
|
|
||||||
"Initial vehicles generated should be 0");
|
|
||||||
assertEquals(0, statistics.getTotalVehiclesCompleted(),
|
|
||||||
"Initial vehicles completed should be 0");
|
|
||||||
assertEquals(0.0, statistics.getAverageSystemTime(),
|
|
||||||
"Initial average system time should be 0.0");
|
|
||||||
assertEquals(0.0, statistics.getAverageWaitingTime(),
|
|
||||||
"Initial average waiting time should be 0.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testVehicleCounters() {
|
|
||||||
statistics.incrementVehiclesGenerated();
|
|
||||||
assertEquals(1, statistics.getTotalVehiclesGenerated());
|
|
||||||
|
|
||||||
statistics.updateVehiclesGenerated(10);
|
|
||||||
assertEquals(10, statistics.getTotalVehiclesGenerated());
|
|
||||||
|
|
||||||
statistics.incrementVehiclesCompleted();
|
|
||||||
assertEquals(1, statistics.getTotalVehiclesCompleted());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testAverageCalculations() {
|
|
||||||
// Add 3 completed vehicles with known times
|
|
||||||
statistics.updateVehiclesCompleted(3);
|
|
||||||
statistics.addSystemTime(3000); // 3000ms total
|
|
||||||
statistics.addWaitingTime(1500); // 1500ms total
|
|
||||||
|
|
||||||
assertEquals(1000.0, statistics.getAverageSystemTime(), 0.01,
|
|
||||||
"Average system time should be 1000ms");
|
|
||||||
assertEquals(500.0, statistics.getAverageWaitingTime(), 0.01,
|
|
||||||
"Average waiting time should be 500ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testVehicleTypeStatistics() {
|
|
||||||
statistics.incrementVehicleType(VehicleType.LIGHT);
|
|
||||||
statistics.incrementVehicleType(VehicleType.LIGHT);
|
|
||||||
statistics.incrementVehicleType(VehicleType.HEAVY);
|
|
||||||
|
|
||||||
assertEquals(2, statistics.getVehicleTypeCount(VehicleType.LIGHT));
|
|
||||||
assertEquals(1, statistics.getVehicleTypeCount(VehicleType.HEAVY));
|
|
||||||
assertEquals(0, statistics.getVehicleTypeCount(VehicleType.BIKE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testIntersectionStatistics() {
|
|
||||||
statistics.updateIntersectionStats("Cr1", 10, 8, 2);
|
|
||||||
|
|
||||||
DashboardStatistics.IntersectionStats stats =
|
|
||||||
statistics.getIntersectionStats("Cr1");
|
|
||||||
|
|
||||||
assertNotNull(stats, "Intersection stats should not be null");
|
|
||||||
assertEquals("Cr1", stats.getIntersectionId());
|
|
||||||
assertEquals(10, stats.getTotalArrivals());
|
|
||||||
assertEquals(8, stats.getTotalDepartures());
|
|
||||||
assertEquals(2, stats.getCurrentQueueSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testMultipleIntersections() {
|
|
||||||
statistics.updateIntersectionStats("Cr1", 10, 8, 2);
|
|
||||||
statistics.updateIntersectionStats("Cr2", 15, 12, 3);
|
|
||||||
statistics.updateIntersectionStats("Cr3", 5, 5, 0);
|
|
||||||
|
|
||||||
assertEquals(3, statistics.getAllIntersectionStats().size(),
|
|
||||||
"Should have 3 intersections");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testStatsUpdatePayload() {
|
|
||||||
StatsUpdatePayload payload = new StatsUpdatePayload()
|
|
||||||
.setTotalVehiclesGenerated(50)
|
|
||||||
.setTotalVehiclesCompleted(20)
|
|
||||||
.setIntersectionArrivals(30)
|
|
||||||
.setIntersectionDepartures(25)
|
|
||||||
.setIntersectionQueueSize(5);
|
|
||||||
|
|
||||||
assertEquals(50, payload.getTotalVehiclesGenerated());
|
|
||||||
assertEquals(20, payload.getTotalVehiclesCompleted());
|
|
||||||
assertEquals(30, payload.getIntersectionArrivals());
|
|
||||||
assertEquals(25, payload.getIntersectionDepartures());
|
|
||||||
assertEquals(5, payload.getIntersectionQueueSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testStatsMessage() {
|
|
||||||
StatsUpdatePayload payload = new StatsUpdatePayload()
|
|
||||||
.setIntersectionArrivals(10);
|
|
||||||
|
|
||||||
StatsMessage message = new StatsMessage("Cr1", payload);
|
|
||||||
|
|
||||||
assertEquals("Cr1", message.getSourceNode());
|
|
||||||
assertEquals("DashboardServer", message.getDestinationNode());
|
|
||||||
assertEquals(sd.model.MessageType.STATS_UPDATE, message.getType());
|
|
||||||
assertNotNull(message.getPayload());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testThreadSafety() throws InterruptedException {
|
|
||||||
// Test concurrent updates
|
|
||||||
Thread t1 = new Thread(() -> {
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
statistics.incrementVehiclesGenerated();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Thread t2 = new Thread(() -> {
|
|
||||||
for (int i = 0; i < 100; i++) {
|
|
||||||
statistics.incrementVehiclesGenerated();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
t1.start();
|
|
||||||
t2.start();
|
|
||||||
t1.join();
|
|
||||||
t2.join();
|
|
||||||
|
|
||||||
assertEquals(200, statistics.getTotalVehiclesGenerated(),
|
|
||||||
"Concurrent increments should total 200");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDashboardServerCreation() throws Exception {
|
|
||||||
SimulationConfig config = new SimulationConfig("simulation.properties");
|
|
||||||
DashboardServer server = new DashboardServer(config);
|
|
||||||
|
|
||||||
assertNotNull(server, "Server should be created successfully");
|
|
||||||
assertNotNull(server.getStatistics(), "Statistics should be initialized");
|
|
||||||
assertFalse(server.isRunning(), "Server should not be running initially");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
package sd.des;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
import sd.model.TrafficLight;
|
|
||||||
import sd.model.TrafficLightState;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for DES (Discrete Event Simulation) components.
|
|
||||||
* Tests the core infrastructure: SimulationClock, EventQueue, SimulationEvent.
|
|
||||||
*/
|
|
||||||
public class DESComponentsTest {
|
|
||||||
|
|
||||||
private SimulationClock clock;
|
|
||||||
private EventQueue eventQueue;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setUp() {
|
|
||||||
clock = new SimulationClock();
|
|
||||||
eventQueue = new EventQueue(false); // Don't track history for basic tests
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimulationClockInitialization() {
|
|
||||||
assertEquals(0.0, clock.getCurrentTime(), 0.001, "Clock should start at time 0");
|
|
||||||
assertEquals(0.0, clock.getElapsedTime(), 0.001, "Elapsed time should be 0 initially");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimulationClockAdvancement() {
|
|
||||||
clock.advanceTo(10.0);
|
|
||||||
assertEquals(10.0, clock.getCurrentTime(), 0.001, "Clock should advance to 10.0");
|
|
||||||
|
|
||||||
clock.advanceTo(25.5);
|
|
||||||
assertEquals(25.5, clock.getCurrentTime(), 0.001, "Clock should advance to 25.5");
|
|
||||||
assertEquals(25.5, clock.getElapsedTime(), 0.001, "Elapsed time should be 25.5");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimulationClockBackwardTimePrevention() {
|
|
||||||
clock.advanceTo(20.0);
|
|
||||||
|
|
||||||
// Attempting to go backward should throw an exception
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
|
||||||
clock.advanceTo(15.0);
|
|
||||||
}, "Clock should throw exception when trying to go backward");
|
|
||||||
|
|
||||||
// Clock should still be at 20.0
|
|
||||||
assertEquals(20.0, clock.getCurrentTime(), 0.001, "Clock should remain at 20.0");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEventQueueOrdering() {
|
|
||||||
// Schedule events out of order
|
|
||||||
SimulationEvent event3 = new SimulationEvent(30.0, DESEventType.VEHICLE_ARRIVAL, null);
|
|
||||||
SimulationEvent event1 = new SimulationEvent(10.0, DESEventType.VEHICLE_GENERATION, null);
|
|
||||||
SimulationEvent event2 = new SimulationEvent(20.0, DESEventType.TRAFFIC_LIGHT_CHANGE, null);
|
|
||||||
|
|
||||||
eventQueue.schedule(event3);
|
|
||||||
eventQueue.schedule(event1);
|
|
||||||
eventQueue.schedule(event2);
|
|
||||||
|
|
||||||
// Events should come out in chronological order
|
|
||||||
SimulationEvent first = eventQueue.poll();
|
|
||||||
assertEquals(10.0, first.getTimestamp(), 0.001, "First event should be at time 10.0");
|
|
||||||
assertEquals(DESEventType.VEHICLE_GENERATION, first.getType());
|
|
||||||
|
|
||||||
SimulationEvent second = eventQueue.poll();
|
|
||||||
assertEquals(20.0, second.getTimestamp(), 0.001, "Second event should be at time 20.0");
|
|
||||||
assertEquals(DESEventType.TRAFFIC_LIGHT_CHANGE, second.getType());
|
|
||||||
|
|
||||||
SimulationEvent third = eventQueue.poll();
|
|
||||||
assertEquals(30.0, third.getTimestamp(), 0.001, "Third event should be at time 30.0");
|
|
||||||
assertEquals(DESEventType.VEHICLE_ARRIVAL, third.getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEventQueueEmpty() {
|
|
||||||
assertTrue(eventQueue.isEmpty(), "New queue should be empty");
|
|
||||||
|
|
||||||
eventQueue.schedule(new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null));
|
|
||||||
assertFalse(eventQueue.isEmpty(), "Queue should not be empty after scheduling");
|
|
||||||
|
|
||||||
eventQueue.poll();
|
|
||||||
assertTrue(eventQueue.isEmpty(), "Queue should be empty after polling all events");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEventQueueSize() {
|
|
||||||
assertEquals(0, eventQueue.size(), "New queue should have size 0");
|
|
||||||
|
|
||||||
eventQueue.schedule(new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null));
|
|
||||||
eventQueue.schedule(new SimulationEvent(10.0, DESEventType.VEHICLE_ARRIVAL, null));
|
|
||||||
assertEquals(2, eventQueue.size(), "Queue should have size 2");
|
|
||||||
|
|
||||||
eventQueue.poll();
|
|
||||||
assertEquals(1, eventQueue.size(), "Queue should have size 1 after polling");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimulationEventComparison() {
|
|
||||||
SimulationEvent early = new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null);
|
|
||||||
SimulationEvent late = new SimulationEvent(10.0, DESEventType.VEHICLE_ARRIVAL, null);
|
|
||||||
|
|
||||||
assertTrue(early.compareTo(late) < 0, "Earlier event should compare less than later event");
|
|
||||||
assertTrue(late.compareTo(early) > 0, "Later event should compare greater than earlier event");
|
|
||||||
assertEquals(0, early.compareTo(early), "Event should compare equal to itself");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimulationEventEqualTimestamp() {
|
|
||||||
// When timestamps are equal, events are ordered by type name
|
|
||||||
SimulationEvent event1 = new SimulationEvent(5.0, DESEventType.TRAFFIC_LIGHT_CHANGE, null);
|
|
||||||
SimulationEvent event2 = new SimulationEvent(5.0, DESEventType.VEHICLE_ARRIVAL, null);
|
|
||||||
|
|
||||||
int comparison = event1.compareTo(event2);
|
|
||||||
// TRAFFIC_LIGHT_CHANGE comes before VEHICLE_ARRIVAL alphabetically
|
|
||||||
assertTrue(comparison < 0, "When equal timestamp, should order by type name alphabetically");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrafficLightEvent() {
|
|
||||||
TrafficLight light = new TrafficLight("Cr1-N", "North", 10.0, 15.0);
|
|
||||||
light.changeState(TrafficLightState.RED);
|
|
||||||
TrafficLightEvent tlEvent = new TrafficLightEvent(light, "North", "Cr1");
|
|
||||||
|
|
||||||
assertEquals(light, tlEvent.getLight(), "Should return correct traffic light");
|
|
||||||
assertEquals("North", tlEvent.getDirection(), "Should return correct direction");
|
|
||||||
assertEquals("Cr1", tlEvent.getIntersectionId(), "Should return correct intersection ID");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEventHistoryTracking() {
|
|
||||||
EventQueue historyQueue = new EventQueue(true); // Enable history tracking
|
|
||||||
|
|
||||||
SimulationEvent event1 = new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null);
|
|
||||||
SimulationEvent event2 = new SimulationEvent(10.0, DESEventType.VEHICLE_ARRIVAL, null);
|
|
||||||
|
|
||||||
historyQueue.schedule(event1);
|
|
||||||
historyQueue.schedule(event2);
|
|
||||||
|
|
||||||
historyQueue.poll();
|
|
||||||
historyQueue.poll();
|
|
||||||
|
|
||||||
String history = historyQueue.exportEventHistory();
|
|
||||||
assertNotNull(history, "Event history should not be null");
|
|
||||||
assertTrue(history.contains("VEHICLE_GENERATION"), "History should contain first event type");
|
|
||||||
assertTrue(history.contains("VEHICLE_ARRIVAL"), "History should contain second event type");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEventQueuePeek() {
|
|
||||||
SimulationEvent event = new SimulationEvent(5.0, DESEventType.VEHICLE_GENERATION, null);
|
|
||||||
eventQueue.schedule(event);
|
|
||||||
|
|
||||||
SimulationEvent peeked = eventQueue.peek();
|
|
||||||
assertNotNull(peeked, "Peek should return event");
|
|
||||||
assertEquals(5.0, peeked.getTimestamp(), 0.001, "Peeked event should have correct timestamp");
|
|
||||||
|
|
||||||
// Queue should still have the event
|
|
||||||
assertEquals(1, eventQueue.size(), "Peek should not remove event from queue");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimulationEndEvent() {
|
|
||||||
SimulationEvent endEvent = new SimulationEvent(100.0, DESEventType.SIMULATION_END, null);
|
|
||||||
|
|
||||||
assertEquals(100.0, endEvent.getTimestamp(), 0.001);
|
|
||||||
assertEquals(DESEventType.SIMULATION_END, endEvent.getType());
|
|
||||||
assertNull(endEvent.getPayload(), "End event should have no payload");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package sd.serialization;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.DisplayName;
|
|
||||||
import sd.model.Message;
|
|
||||||
import sd.model.Vehicle;
|
|
||||||
import sd.model.VehicleType;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test suite for JSON serialization.
|
|
||||||
*
|
|
||||||
* Tests JSON serialization to ensure:
|
|
||||||
* - Correct serialization and deserialization
|
|
||||||
* - Data integrity during round-trip conversion
|
|
||||||
* - Proper error handling
|
|
||||||
*/
|
|
||||||
class SerializationTest {
|
|
||||||
|
|
||||||
private MessageSerializer jsonSerializer = new JsonMessageSerializer();
|
|
||||||
|
|
||||||
private Vehicle testVehicle = new Vehicle("V001", VehicleType.LIGHT, 10.5,
|
|
||||||
Arrays.asList("Cr1", "Cr2", "Cr5", "S"));
|
|
||||||
private Message testMessage = new Message(
|
|
||||||
sd.model.MessageType.VEHICLE_TRANSFER,
|
|
||||||
"Cr1",
|
|
||||||
"Cr2",
|
|
||||||
testVehicle
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// ===== JSON Serialization Tests =====
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("JSON: Should serialize and deserialize Vehicle correctly")
|
|
||||||
void testJsonVehicleRoundTrip() throws SerializationException {
|
|
||||||
// Serialize
|
|
||||||
byte[] data = jsonSerializer.serialize(testVehicle);
|
|
||||||
assertNotNull(data);
|
|
||||||
assertTrue(data.length > 0);
|
|
||||||
|
|
||||||
// Print JSON for inspection
|
|
||||||
System.out.println("JSON Vehicle:");
|
|
||||||
System.out.println(new String(data));
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
Vehicle deserialized = jsonSerializer.deserialize(data, Vehicle.class);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
assertNotNull(deserialized);
|
|
||||||
assertEquals(testVehicle.getId(), deserialized.getId());
|
|
||||||
assertEquals(testVehicle.getType(), deserialized.getType());
|
|
||||||
assertEquals(testVehicle.getEntryTime(), deserialized.getEntryTime());
|
|
||||||
assertEquals(testVehicle.getRoute(), deserialized.getRoute());
|
|
||||||
assertEquals(testVehicle.getTotalWaitingTime(), deserialized.getTotalWaitingTime());
|
|
||||||
assertEquals(testVehicle.getTotalCrossingTime(), deserialized.getTotalCrossingTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("JSON: Should serialize and deserialize Message correctly")
|
|
||||||
void testJsonMessageRoundTrip() throws SerializationException {
|
|
||||||
// Serialize
|
|
||||||
byte[] data = jsonSerializer.serialize(testMessage);
|
|
||||||
assertNotNull(data);
|
|
||||||
|
|
||||||
// Print JSON for inspection
|
|
||||||
System.out.println("\nJSON Message:");
|
|
||||||
System.out.println(new String(data));
|
|
||||||
|
|
||||||
// Deserialize
|
|
||||||
Message deserialized = jsonSerializer.deserialize(data, Message.class);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
assertNotNull(deserialized);
|
|
||||||
assertEquals(testMessage.getType(), deserialized.getType());
|
|
||||||
assertEquals(testMessage.getSenderId(), deserialized.getSenderId());
|
|
||||||
assertEquals(testMessage.getDestinationId(), deserialized.getDestinationId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("JSON: Should throw exception on null object")
|
|
||||||
void testJsonSerializeNull() {
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
|
||||||
jsonSerializer.serialize(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("JSON: Should throw exception on null data")
|
|
||||||
void testJsonDeserializeNull() {
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
|
||||||
jsonSerializer.deserialize(null, Vehicle.class);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("JSON: Should throw exception on invalid JSON")
|
|
||||||
void testJsonDeserializeInvalid() {
|
|
||||||
byte[] invalidData = "{ invalid json }".getBytes();
|
|
||||||
assertThrows(SerializationException.class, () -> {
|
|
||||||
jsonSerializer.deserialize(invalidData, Vehicle.class);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("JSON: Should preserve data integrity for complex objects")
|
|
||||||
void testDataIntegrity() throws SerializationException {
|
|
||||||
// Create a more complex vehicle
|
|
||||||
Vehicle vehicle = new Vehicle("V999", VehicleType.HEAVY, 100.5,
|
|
||||||
Arrays.asList("Cr1", "Cr2", "Cr3", "Cr4", "Cr5", "S"));
|
|
||||||
vehicle.addWaitingTime(10.5);
|
|
||||||
vehicle.addWaitingTime(5.3);
|
|
||||||
vehicle.addCrossingTime(2.1);
|
|
||||||
vehicle.advanceRoute();
|
|
||||||
vehicle.advanceRoute();
|
|
||||||
|
|
||||||
// Serialize and deserialize
|
|
||||||
byte[] jsonData = jsonSerializer.serialize(vehicle);
|
|
||||||
Vehicle deserialized = jsonSerializer.deserialize(jsonData, Vehicle.class);
|
|
||||||
|
|
||||||
// Verify all fields match
|
|
||||||
assertEquals(vehicle.getId(), deserialized.getId());
|
|
||||||
assertEquals(vehicle.getType(), deserialized.getType());
|
|
||||||
assertEquals(vehicle.getTotalWaitingTime(), deserialized.getTotalWaitingTime());
|
|
||||||
assertEquals(vehicle.getCurrentRouteIndex(), deserialized.getCurrentRouteIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== Factory Tests =====
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Factory: Should create JSON serializer by default")
|
|
||||||
void testFactoryDefault() {
|
|
||||||
MessageSerializer serializer = SerializerFactory.createDefault();
|
|
||||||
assertNotNull(serializer);
|
|
||||||
assertEquals("JSON (Gson)", serializer.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user