From cf88db42974f0b8803f1bd78ace42e8af731cd02 Mon Sep 17 00:00:00 2001 From: David Alves Date: Wed, 5 Nov 2025 12:09:32 +0000 Subject: [PATCH] Add traffic light coordination and tests Sorry to add this on this branch ahah --- .../src/main/java/sd/IntersectionProcess.java | 74 ++++++- .../java/sd/TrafficLightCoordinationTest.java | 206 ++++++++++++++++++ 2 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 main/src/test/java/sd/TrafficLightCoordinationTest.java diff --git a/main/src/main/java/sd/IntersectionProcess.java b/main/src/main/java/sd/IntersectionProcess.java index 11db78d..95ed592 100644 --- a/main/src/main/java/sd/IntersectionProcess.java +++ b/main/src/main/java/sd/IntersectionProcess.java @@ -8,6 +8,8 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import sd.config.SimulationConfig; import sd.model.Intersection; @@ -42,6 +44,19 @@ public class IntersectionProcess { private volatile boolean running; //Quando uma thread escreve um valor volatile, todas as outras //threads veem a mudança imediatamente. + // Traffic Light Coordination + /** + * Lock to ensure mutual exclusion between traffic lights. + * Only one traffic light can be green at any given time within this intersection. + */ + private final Lock trafficCoordinationLock; + + /** + * Tracks which direction currently has the green light. + * null means no direction is currently green (all are red). + */ + private volatile String currentGreenDirection; + /** * Constructs a new IntersectionProcess. * @@ -57,6 +72,8 @@ public class IntersectionProcess { this.connectionHandlerPool = Executors.newCachedThreadPool(); this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions this.running = false; + this.trafficCoordinationLock = new ReentrantLock(); + this.currentGreenDirection = null; System.out.println("=".repeat(60)); System.out.println("INTERSECTION PROCESS: " + intersectionId); @@ -70,8 +87,6 @@ public class IntersectionProcess { configureRouting(); - startTrafficLights(); - System.out.println("[" + intersectionId + "] Initialization complete."); } @@ -168,7 +183,9 @@ public class IntersectionProcess { /** * The main loop for a traffic light thread. - * Continuously cycles between GREEN and RED states. + * Continuously cycles between green and red states. + * + * only one traffic light can be green at any given time in this intersection. * * @param light The traffic light to control. */ @@ -177,9 +194,28 @@ public class IntersectionProcess { while (running) { try { - // Green state - light.changeState(TrafficLightState.GREEN); - System.out.println("[" + light.getId() + "] State: GREEN"); + // Acquire coordination lock to become green + trafficCoordinationLock.lock(); + try { + // Wait until no other direction is green + while (currentGreenDirection != null && running) { + trafficCoordinationLock.unlock(); + Thread.sleep(100); // Brief wait before retrying + trafficCoordinationLock.lock(); + } + + if (!running) { + break; // Exit if shutting down + } + + // Mark this direction as the current green light + currentGreenDirection = light.getDirection(); + light.changeState(TrafficLightState.GREEN); + System.out.println("[" + light.getId() + "] State: GREEN"); + + } finally { + trafficCoordinationLock.unlock(); + } // Process vehicles while green processGreenLight(light); @@ -187,9 +223,15 @@ public class IntersectionProcess { // Wait for green duration Thread.sleep((long) (light.getGreenTime() * 1000)); - // RED state - light.changeState(TrafficLightState.RED); - System.out.println("[" + light.getId() + "] State: RED"); + // Release coordination lock (turn red) + trafficCoordinationLock.lock(); + try { + light.changeState(TrafficLightState.RED); + currentGreenDirection = null; // Release exclusive access + System.out.println("[" + light.getId() + "] State: RED (RELEASED ACCESS)"); + } finally { + trafficCoordinationLock.unlock(); + } // Wait for red duration Thread.sleep((long) (light.getRedTime() * 1000)); @@ -353,6 +395,10 @@ public class IntersectionProcess { running = true; System.out.println("\n[" + intersectionId + "] Server started on port " + port); + + // Start traffic light threads when running is true + startTrafficLights(); + System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n"); // Main accept loop @@ -460,6 +506,16 @@ public class IntersectionProcess { System.out.println("=".repeat(60)); } + /** + * Gets the Intersection object managed by this process. + * Useful for testing and monitoring. + * + * @return The Intersection object. + */ + public Intersection getIntersection() { + return intersection; + } + // --- Inner class for Vehicle Transfer Messages --- /** diff --git a/main/src/test/java/sd/TrafficLightCoordinationTest.java b/main/src/test/java/sd/TrafficLightCoordinationTest.java new file mode 100644 index 0000000..1fe51a3 --- /dev/null +++ b/main/src/test/java/sd/TrafficLightCoordinationTest.java @@ -0,0 +1,206 @@ +package sd; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +import sd.model.TrafficLight; +import sd.model.TrafficLightState; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 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 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("\n✅ Traffic 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 lights = intersectionProcess.getIntersection().getTrafficLights(); + boolean[] hasBeenGreen = new boolean[lights.size()]; + + // Monitor for 15 seconds (enough time for all lights to cycle) + long endTime = System.currentTimeMillis() + 15000; + + 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 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"); + } +}