mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
528 lines
17 KiB
Java
528 lines
17 KiB
Java
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;
|
|
}
|
|
}
|
|
}
|