mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 20:43:32 +00:00
Merge branch 'dev' into 10-create-network-communication-classes
This commit is contained in:
61
.github/workflows/maven.yml
vendored
Normal file
61
.github/workflows/maven.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Java CI with Maven
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
cache: maven
|
||||
|
||||
- name: Build with Maven
|
||||
run: mvn -B package
|
||||
working-directory: main
|
||||
|
||||
- name: Upload built JAR
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: package
|
||||
path: main/target/*.jar
|
||||
|
||||
- name: Generate dependency graph
|
||||
run: mvn -B -f main/pom.xml com.github.ferstl:depgraph-maven-plugin:4.0.1:graph
|
||||
|
||||
- name: Upload dependency graph artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dependency-graph
|
||||
path: main/target/**
|
||||
|
||||
publish-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Download built JAR
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: package
|
||||
path: main/target/
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: main/target/*.jar
|
||||
134
STEP2_SUMMARY.md
Normal file
134
STEP2_SUMMARY.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# 🏁 Single-Process Prototype — Implementation Summary
|
||||
|
||||
**Status:** ✅ Complete
|
||||
**Date:** October 22, 2025
|
||||
**Branch:** `8-single-process-prototype`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The single-process prototype implements a **discrete event simulation (DES)** of a 3×3 urban grid with five intersections, realistic vehicle behavior, and fully synchronized traffic lights. Everything runs under one process, laying the groundwork for the distributed architecture in Phase 3.
|
||||
|
||||
---
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### **SimulationEngine**
|
||||
|
||||
Drives the DES loop with a priority queue of timestamped events — vehicles, lights, crossings, and periodic stats updates. Handles five intersections (Cr1–Cr5) and six event types.
|
||||
|
||||
**Main loop:**
|
||||
|
||||
```
|
||||
while (events && time < duration):
|
||||
event = nextEvent()
|
||||
time = event.timestamp
|
||||
handle(event)
|
||||
```
|
||||
|
||||
### **VehicleGenerator**
|
||||
|
||||
Spawns vehicles via:
|
||||
|
||||
* **Poisson arrivals** (λ = 0.5 veh/s) or fixed intervals
|
||||
* **Probabilistic routes** from E1–E3
|
||||
* **Type distribution**: 20% BIKE, 60% LIGHT, 20% HEAVY
|
||||
|
||||
### **StatisticsCollector**
|
||||
|
||||
Tracks system-wide and per-type metrics: throughput, avg. wait, queue sizes, light cycles — updated every 10 s and at simulation end.
|
||||
|
||||
---
|
||||
|
||||
## Model Highlights
|
||||
|
||||
* **Vehicle** – type, route, timings, lifecycle.
|
||||
* **Intersection** – routing tables, traffic lights, queues.
|
||||
* **TrafficLight** – red/green cycles with FIFO queues.
|
||||
* **Event** – timestamped, comparable; 6 types for all DES actions.
|
||||
|
||||
---
|
||||
|
||||
## Configuration (`simulation.properties`)
|
||||
|
||||
```properties
|
||||
simulation.duration=60.0
|
||||
simulation.arrival.model=POISSON
|
||||
simulation.arrival.rate=0.5
|
||||
|
||||
vehicle.bike.crossingTime=1.5
|
||||
vehicle.light.crossingTime=2.0
|
||||
vehicle.heavy.crossingTime=4.0
|
||||
|
||||
statistics.update.interval=10.0
|
||||
```
|
||||
|
||||
**Speed logic:**
|
||||
`t_bike = 0.5×t_car`, `t_heavy = 2×t_car`.
|
||||
|
||||
---
|
||||
|
||||
## Topology
|
||||
|
||||
```
|
||||
E1→Cr1→Cr4→Cr5→S
|
||||
E2→Cr2→Cr5→S
|
||||
E3→Cr3→S
|
||||
Bi-dir: Cr1↔Cr2, Cr2↔Cr3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Results
|
||||
|
||||
**Unit Tests:** 7/7 ✅
|
||||
**60-Second Simulation:**
|
||||
|
||||
* Generated: 22 vehicles
|
||||
* Completed: 5 (22.7%)
|
||||
* Avg system time: 15.47 s
|
||||
* Throughput: 0.08 veh/s
|
||||
* All lights & intersections operational
|
||||
|
||||
**Performance:**
|
||||
~0.03 s real-time run (≈2000× speed-up), < 50 MB RAM.
|
||||
|
||||
---
|
||||
|
||||
## Code Structure
|
||||
|
||||
```
|
||||
sd/
|
||||
├── engine/SimulationEngine.java
|
||||
├── model/{Vehicle,Intersection,TrafficLight,Event}.java
|
||||
├── util/{VehicleGenerator,StatisticsCollector}.java
|
||||
└── config/SimulationConfig.java
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Flow
|
||||
|
||||
1. Initialize intersections, lights, first events.
|
||||
2. Process events chronologically.
|
||||
3. Vehicles follow routes → queue → cross → exit.
|
||||
4. Lights toggle, queues drain, stats update.
|
||||
5. Print summary and performance metrics.
|
||||
|
||||
---
|
||||
|
||||
## Next Steps — Phase 3
|
||||
|
||||
* Split intersections into independent **processes**.
|
||||
* Add **socket-based communication**.
|
||||
* Run **traffic lights as threads**.
|
||||
* Enable **distributed synchronization** and fault handling.
|
||||
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
Solid single-process DES ✅
|
||||
Everything’s working — traffic lights, routing, vehicles, stats.
|
||||
Ready to go distributed next.
|
||||
25
TODO.md
25
TODO.md
@@ -1,3 +1,26 @@
|
||||
## ✅ SINGLE-PROCESS PROTOTYPE - COMPLETED
|
||||
|
||||
### Phase 2 Status: DONE ✅
|
||||
|
||||
All components for the single-process prototype have been successfully implemented and tested:
|
||||
|
||||
- ✅ **SimulationEngine** - Priority queue-based discrete event simulation
|
||||
- ✅ **VehicleGenerator** - Poisson and Fixed arrival models
|
||||
- ✅ **StatisticsCollector** - Comprehensive metrics tracking
|
||||
- ✅ **Entry point** - Main simulation runner
|
||||
- ✅ **60s test simulation** - Successfully validated event processing and routing
|
||||
|
||||
### Test Results:
|
||||
- All 7 unit tests passing
|
||||
- 60-second simulation completed successfully
|
||||
- Generated 22 vehicles with 5 completing their routes
|
||||
- Traffic light state changes working correctly
|
||||
- Vehicle routing through intersections validated
|
||||
|
||||
---
|
||||
|
||||
## NEXT: Distributed Architecture Implementation
|
||||
|
||||
### Compreender os Conceitos Fundamentais
|
||||
|
||||
Primeiro, as tecnologias e paradigmas chave necessários para este projeto devem ser totalmente compreendidos.
|
||||
@@ -16,7 +39,7 @@ Primeiro, as tecnologias e paradigmas chave necessários para este projeto devem
|
||||
|
||||
- Uma **lista de eventos** central, frequentemente uma fila de prioridades, será necessária para armazenar eventos futuros, ordenados pelo seu timestamp. O ciclo principal da simulação retira o próximo evento da lista, processa-o e adiciona quaisquer novos eventos que resultem dele.
|
||||
|
||||
- **Processo de Poisson:** Para o modelo "mais realista" de chegadas de veículos, é especificado um processo de Poisson. A principal conclusão é que o tempo _entre_ chegadas consecutivas de veículos segue uma **distribuição exponencial**. Em Java, este intervalo pode ser gerado usando `Math.log(1 - Math.random()) / -lambda`, onde `lambda` (λi) é a taxa de chegada especificada.
|
||||
- **Processo de Poisson:** Para o modelo 'mais realista' de chegadas de veículos, é especificado um processo de Poisson. A principal conclusão é que o tempo _entre_ chegadas consecutivas de veículos segue uma **distribuição exponencial**. Em Java, este intervalo pode ser gerado usando `Math.log(1 - Math.random()) / -lambda`, onde `lambda` (λi) é a taxa de chegada especificada.
|
||||
|
||||
|
||||
---
|
||||
|
||||
20
main/pom.xml
20
main/pom.xml
@@ -42,6 +42,26 @@
|
||||
<mainClass>sd.Entry</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>sd.Entry</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
501
main/src/main/java/sd/IntersectionProcess.java
Normal file
501
main/src/main/java/sd/IntersectionProcess.java
Normal file
@@ -0,0 +1,501 @@
|
||||
package sd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import sd.config.SimulationConfig;
|
||||
import sd.model.Intersection;
|
||||
import sd.model.MessageType;
|
||||
import sd.model.TrafficLight;
|
||||
import sd.model.TrafficLightState;
|
||||
import sd.model.Vehicle;
|
||||
import sd.protocol.MessageProtocol;
|
||||
import sd.protocol.SocketConnection;
|
||||
|
||||
/**
|
||||
* Main class for an Intersection Process in the distributed traffic simulation.
|
||||
* * Each IntersectionProcess runs as an independent Java application (JVM instance)
|
||||
* representing one of the five intersections (Cr1-Cr5) in the network.
|
||||
*/
|
||||
public class IntersectionProcess {
|
||||
|
||||
private final String intersectionId;
|
||||
|
||||
private final SimulationConfig config;
|
||||
|
||||
private final Intersection intersection;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
private final Map<String, SocketConnection> outgoingConnections;
|
||||
|
||||
private final ExecutorService connectionHandlerPool;
|
||||
|
||||
private final ExecutorService trafficLightPool;
|
||||
|
||||
private volatile boolean running; //Quando uma thread escreve um valor volatile, todas as outras
|
||||
//threads veem a mudança imediatamente.
|
||||
|
||||
/**
|
||||
* Constructs a new IntersectionProcess.
|
||||
*
|
||||
* @param intersectionId The ID of this intersection (e.g., "Cr1").
|
||||
* @param configFilePath Path to the simulation.properties file.
|
||||
* @throws IOException If configuration cannot be loaded.
|
||||
*/
|
||||
public IntersectionProcess(String intersectionId, String configFilePath) throws IOException {
|
||||
this.intersectionId = intersectionId;
|
||||
this.config = new SimulationConfig(configFilePath);
|
||||
this.intersection = new Intersection(intersectionId);
|
||||
this.outgoingConnections = new HashMap<>();
|
||||
this.connectionHandlerPool = Executors.newCachedThreadPool();
|
||||
this.trafficLightPool = Executors.newFixedThreadPool(4); // Max 4 directions
|
||||
this.running = false;
|
||||
|
||||
System.out.println("=".repeat(60));
|
||||
System.out.println("INTERSECTION PROCESS: " + intersectionId);
|
||||
System.out.println("=".repeat(60));
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
System.out.println("\n[" + intersectionId + "] Initializing intersection...");
|
||||
|
||||
createTrafficLights();
|
||||
|
||||
configureRouting();
|
||||
|
||||
startTrafficLights();
|
||||
|
||||
System.out.println("[" + intersectionId + "] Initialization complete.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates traffic lights for this intersection based on its physical connections.
|
||||
* Each intersection has different number and directions of traffic lights
|
||||
* according to the network topology.
|
||||
*/
|
||||
private void createTrafficLights() {
|
||||
System.out.println("\n[" + intersectionId + "] Creating traffic lights...");
|
||||
|
||||
String[] directions = new String[0];
|
||||
switch (intersectionId) {
|
||||
case "Cr1":
|
||||
directions = new String[]{"East", "South"};
|
||||
break;
|
||||
case "Cr2":
|
||||
directions = new String[]{"West", "East", "South"};
|
||||
break;
|
||||
case "Cr3":
|
||||
directions = new String[]{"West", "South"};
|
||||
break;
|
||||
case "Cr4":
|
||||
directions = new String[]{"East"};
|
||||
break;
|
||||
case "Cr5":
|
||||
directions = new String[]{"East"};
|
||||
break;
|
||||
}
|
||||
|
||||
for (String direction : directions) {
|
||||
double greenTime = config.getTrafficLightGreenTime(intersectionId, direction);
|
||||
double redTime = config.getTrafficLightRedTime(intersectionId, direction);
|
||||
|
||||
TrafficLight light = new TrafficLight(
|
||||
intersectionId + "-" + direction,
|
||||
direction,
|
||||
greenTime,
|
||||
redTime
|
||||
);
|
||||
|
||||
intersection.addTrafficLight(light);
|
||||
System.out.println(" Created traffic light: " + direction +
|
||||
" (Green: " + greenTime + "s, Red: " + redTime + "s)");
|
||||
}
|
||||
}
|
||||
|
||||
private void configureRouting() {
|
||||
System.out.println("\n[" + intersectionId + "] Configuring routing...");
|
||||
|
||||
switch (intersectionId) {
|
||||
case "Cr1":
|
||||
intersection.configureRoute("Cr2", "East");
|
||||
intersection.configureRoute("Cr4", "South");
|
||||
break;
|
||||
|
||||
case "Cr2":
|
||||
intersection.configureRoute("Cr1", "West");
|
||||
intersection.configureRoute("Cr3", "East");
|
||||
intersection.configureRoute("Cr5", "South");
|
||||
break;
|
||||
|
||||
case "Cr3":
|
||||
intersection.configureRoute("Cr2", "West");
|
||||
intersection.configureRoute("S", "South");
|
||||
break;
|
||||
|
||||
case "Cr4":
|
||||
intersection.configureRoute("Cr5", "East");
|
||||
break;
|
||||
|
||||
case "Cr5":
|
||||
intersection.configureRoute("S", "East");
|
||||
break;
|
||||
|
||||
default:
|
||||
System.err.println(" Error: unknown intersection ID: " + intersectionId);
|
||||
}
|
||||
|
||||
System.out.println(" Routing configured.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts all traffic light threads.
|
||||
*/
|
||||
private void startTrafficLights() {
|
||||
System.out.println("\n[" + intersectionId + "] Starting traffic light threads...");
|
||||
|
||||
for (TrafficLight light : intersection.getTrafficLights()) {
|
||||
trafficLightPool.submit(() -> runTrafficLightCycle(light));
|
||||
System.out.println(" Started thread for: " + light.getDirection());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The main loop for a traffic light thread.
|
||||
* Continuously cycles between GREEN and RED states.
|
||||
*
|
||||
* @param light The traffic light to control.
|
||||
*/
|
||||
private void runTrafficLightCycle(TrafficLight light) {
|
||||
System.out.println("[" + light.getId() + "] Traffic light thread started.");
|
||||
|
||||
while (running) {
|
||||
try {
|
||||
// Green state
|
||||
light.changeState(TrafficLightState.GREEN);
|
||||
System.out.println("[" + light.getId() + "] State: GREEN");
|
||||
|
||||
// Process vehicles while green
|
||||
processGreenLight(light);
|
||||
|
||||
// Wait for green duration
|
||||
Thread.sleep((long) (light.getGreenTime() * 1000));
|
||||
|
||||
// RED state
|
||||
light.changeState(TrafficLightState.RED);
|
||||
System.out.println("[" + light.getId() + "] State: RED");
|
||||
|
||||
// Wait for red duration
|
||||
Thread.sleep((long) (light.getRedTime() * 1000));
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("[" + light.getId() + "] Traffic light thread interrupted.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("[" + light.getId() + "] Traffic light thread stopped.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes vehicles when a traffic light is GREEN.
|
||||
* Dequeues vehicles and sends them to their next destination.
|
||||
*
|
||||
* @param light The traffic light that is currently green.
|
||||
*/
|
||||
private void processGreenLight(TrafficLight light) {
|
||||
while (light.getState() == TrafficLightState.GREEN && light.getQueueSize() > 0) {
|
||||
Vehicle vehicle = light.removeVehicle();
|
||||
|
||||
if (vehicle != null) {
|
||||
// Get crossing time based on vehicle type
|
||||
double crossingTime = getCrossingTimeForVehicle(vehicle);
|
||||
|
||||
// Simulate crossing time
|
||||
try {
|
||||
Thread.sleep((long) (crossingTime * 1000));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
|
||||
// Update vehicle statistics
|
||||
vehicle.addCrossingTime(crossingTime);
|
||||
|
||||
// Update intersection statistics
|
||||
intersection.incrementVehiclesSent();
|
||||
|
||||
// Send vehicle to next destination
|
||||
sendVehicleToNextDestination(vehicle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the crossing time for a vehicle based on its type.
|
||||
*
|
||||
* @param vehicle The vehicle.
|
||||
* @return The crossing time in seconds.
|
||||
*/
|
||||
private double getCrossingTimeForVehicle(Vehicle vehicle) {
|
||||
switch (vehicle.getType()) {
|
||||
case BIKE:
|
||||
return config.getBikeVehicleCrossingTime();
|
||||
case LIGHT:
|
||||
return config.getLightVehicleCrossingTime();
|
||||
case HEAVY:
|
||||
return config.getHeavyVehicleCrossingTime();
|
||||
default:
|
||||
return config.getLightVehicleCrossingTime();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a vehicle to its next destination via socket connection.
|
||||
*
|
||||
* @param vehicle The vehicle that has crossed this intersection.
|
||||
*/
|
||||
private void sendVehicleToNextDestination(Vehicle vehicle) {
|
||||
String nextDestination = vehicle.getCurrentDestination();
|
||||
|
||||
try {
|
||||
// Get or create connection to next destination
|
||||
SocketConnection connection = getOrCreateConnection(nextDestination);
|
||||
|
||||
// Create and send message
|
||||
MessageProtocol message = new VehicleTransferMessage(
|
||||
intersectionId,
|
||||
nextDestination,
|
||||
vehicle
|
||||
);
|
||||
|
||||
connection.sendMessage(message);
|
||||
|
||||
System.out.println("[" + intersectionId + "] Sent vehicle " + vehicle.getId() +
|
||||
" to " + nextDestination);
|
||||
|
||||
// Note: vehicle route is advanced when it arrives at the next intersection
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
System.err.println("[" + intersectionId + "] Failed to send vehicle " +
|
||||
vehicle.getId() + " to " + nextDestination + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an existing connection to a destination or creates a new one.
|
||||
*
|
||||
* @param destinationId The ID of the destination node.
|
||||
* @return The SocketConnection to that destination.
|
||||
* @throws IOException If connection cannot be established.
|
||||
* @throws InterruptedException If connection attempt is interrupted.
|
||||
*/
|
||||
private synchronized SocketConnection getOrCreateConnection(String destinationId)
|
||||
throws IOException, InterruptedException {
|
||||
|
||||
if (!outgoingConnections.containsKey(destinationId)) {
|
||||
String host = getHostForDestination(destinationId);
|
||||
int port = getPortForDestination(destinationId);
|
||||
|
||||
System.out.println("[" + intersectionId + "] Creating connection to " +
|
||||
destinationId + " at " + host + ":" + port);
|
||||
|
||||
SocketConnection connection = new SocketConnection(host, port);
|
||||
outgoingConnections.put(destinationId, connection);
|
||||
}
|
||||
|
||||
return outgoingConnections.get(destinationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the host address for a destination node from configuration.
|
||||
*
|
||||
* @param destinationId The destination node ID.
|
||||
* @return The host address.
|
||||
*/
|
||||
private String getHostForDestination(String destinationId) {
|
||||
if (destinationId.equals("S")) {
|
||||
return config.getExitHost();
|
||||
} else {
|
||||
return config.getIntersectionHost(destinationId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the port number for a destination node from configuration.
|
||||
*
|
||||
* @param destinationId The destination node ID.
|
||||
* @return The port number.
|
||||
*/
|
||||
private int getPortForDestination(String destinationId) {
|
||||
if (destinationId.equals("S")) {
|
||||
return config.getExitPort();
|
||||
} else {
|
||||
return config.getIntersectionPort(destinationId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server socket and begins accepting incoming connections.
|
||||
* This is the main listening loop of the process.
|
||||
*
|
||||
* @throws IOException If the server socket cannot be created.
|
||||
*/
|
||||
public void start() throws IOException {
|
||||
int port = config.getIntersectionPort(intersectionId);
|
||||
serverSocket = new ServerSocket(port);
|
||||
running = true;
|
||||
|
||||
System.out.println("\n[" + intersectionId + "] Server started on port " + port);
|
||||
System.out.println("[" + intersectionId + "] Waiting for incoming connections...\n");
|
||||
|
||||
// Main accept loop
|
||||
while (running) {
|
||||
try {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
|
||||
// Handle each connection in a separate thread
|
||||
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
|
||||
|
||||
} catch (IOException e) {
|
||||
if (running) {
|
||||
System.err.println("[" + intersectionId + "] Error accepting connection: " +
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an incoming connection from another process.
|
||||
* Continuously listens for vehicle transfer messages.
|
||||
*
|
||||
* @param clientSocket The accepted socket connection.
|
||||
*/
|
||||
private void handleIncomingConnection(Socket clientSocket) {
|
||||
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
||||
|
||||
System.out.println("[" + intersectionId + "] New connection accepted from " +
|
||||
clientSocket.getInetAddress().getHostAddress());
|
||||
|
||||
// Continuously receive messages while connection is active
|
||||
while (running && connection.isConnected()) {
|
||||
try {
|
||||
MessageProtocol message = connection.receiveMessage();
|
||||
|
||||
if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
||||
Vehicle vehicle = (Vehicle) message.getPayload();
|
||||
|
||||
System.out.println("[" + intersectionId + "] Received vehicle: " +
|
||||
vehicle.getId() + " from " + message.getSourceNode());
|
||||
|
||||
// Add vehicle to appropriate queue
|
||||
intersection.receiveVehicle(vehicle);
|
||||
}
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("[" + intersectionId + "] Unknown message type received: " +
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
if (running) {
|
||||
System.err.println("[" + intersectionId + "] Connection error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the intersection process gracefully.
|
||||
* Shuts down all threads and closes all connections.
|
||||
*/
|
||||
public void shutdown() {
|
||||
System.out.println("\n[" + intersectionId + "] Shutting down...");
|
||||
running = false;
|
||||
|
||||
// Close server socket
|
||||
try {
|
||||
if (serverSocket != null && !serverSocket.isClosed()) {
|
||||
serverSocket.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("[" + intersectionId + "] Error closing server socket: " +
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
// Shutdown thread pools
|
||||
trafficLightPool.shutdown();
|
||||
connectionHandlerPool.shutdown();
|
||||
|
||||
try {
|
||||
if (!trafficLightPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
trafficLightPool.shutdownNow();
|
||||
}
|
||||
if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
connectionHandlerPool.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
trafficLightPool.shutdownNow();
|
||||
connectionHandlerPool.shutdownNow();
|
||||
}
|
||||
|
||||
// Close all outgoing connections
|
||||
for (Map.Entry<String, SocketConnection> entry : outgoingConnections.entrySet()) {
|
||||
try {
|
||||
entry.getValue().close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("[" + intersectionId + "] Error closing connection to " +
|
||||
entry.getKey() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("[" + intersectionId + "] Shutdown complete.");
|
||||
System.out.println("=".repeat(60));
|
||||
}
|
||||
|
||||
// --- Inner class for Vehicle Transfer Messages ---
|
||||
|
||||
/**
|
||||
* Implementation of MessageProtocol for vehicle transfers between processes.
|
||||
*/
|
||||
private static class VehicleTransferMessage implements MessageProtocol {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final String sourceNode;
|
||||
private final String destinationNode;
|
||||
private final Vehicle payload;
|
||||
|
||||
public VehicleTransferMessage(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ public class SimulationConfig {
|
||||
* (por exemplo quando executado a partir do classpath/jar),
|
||||
* faz fallback para carregar a partir do classpath usando o ClassLoader.
|
||||
*/
|
||||
IOException lastException = null;
|
||||
IOException lastException = null; //FIXME: melhorar esta parte para reportar erros de forma mais clara
|
||||
|
||||
try {
|
||||
try (InputStream input = new FileInputStream(filePath)) {
|
||||
|
||||
@@ -264,32 +264,19 @@ public class SimulationEngine {
|
||||
*/
|
||||
private void processEvent(Event event) {
|
||||
switch (event.getType()) {
|
||||
case VEHICLE_GENERATION:
|
||||
handleVehicleGeneration();
|
||||
break;
|
||||
case VEHICLE_GENERATION -> handleVehicleGeneration();
|
||||
|
||||
case VEHICLE_ARRIVAL:
|
||||
handleVehicleArrival(event);
|
||||
break;
|
||||
case VEHICLE_ARRIVAL -> handleVehicleArrival(event);
|
||||
|
||||
case TRAFFIC_LIGHT_CHANGE:
|
||||
handleTrafficLightChange(event);
|
||||
break;
|
||||
case TRAFFIC_LIGHT_CHANGE -> handleTrafficLightChange(event);
|
||||
|
||||
case CROSSING_START:
|
||||
handleCrossingStart(event);
|
||||
break;
|
||||
case CROSSING_START -> handleCrossingStart(event);
|
||||
|
||||
case CROSSING_END:
|
||||
handleCrossingEnd(event);
|
||||
break;
|
||||
case CROSSING_END -> handleCrossingEnd(event);
|
||||
|
||||
case STATISTICS_UPDATE:
|
||||
handleStatisticsUpdate();
|
||||
break;
|
||||
case STATISTICS_UPDATE -> handleStatisticsUpdate();
|
||||
|
||||
default:
|
||||
System.err.println("Unknown event type: " + event.getType());
|
||||
default -> System.err.println("Unknown event type: " + event.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +373,7 @@ public class SimulationEngine {
|
||||
* @param vehicle The vehicle to process.
|
||||
* @param intersection The intersection where the vehicle is.
|
||||
*/
|
||||
private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) {
|
||||
private void tryProcessVehicle(Vehicle vehicle, Intersection intersection) { //FIXME
|
||||
// Find the direction (and light) this vehicle is queued at
|
||||
// This logic is a bit flawed: it just finds the *first* non-empty queue
|
||||
// A better approach would be to get the light from the vehicle's route
|
||||
@@ -591,16 +578,12 @@ public class SimulationEngine {
|
||||
* @return The crossing time in seconds.
|
||||
*/
|
||||
private double getCrossingTime(VehicleType type) {
|
||||
switch (type) {
|
||||
case BIKE:
|
||||
return config.getBikeVehicleCrossingTime();
|
||||
case LIGHT:
|
||||
return config.getLightVehicleCrossingTime();
|
||||
case HEAVY:
|
||||
return config.getHeavyVehicleCrossingTime();
|
||||
default:
|
||||
return 2.0; // Default fallback
|
||||
}
|
||||
return switch (type) {
|
||||
case BIKE -> config.getBikeVehicleCrossingTime();
|
||||
case LIGHT -> config.getLightVehicleCrossingTime();
|
||||
case HEAVY -> config.getHeavyVehicleCrossingTime();
|
||||
default -> 2.0;
|
||||
}; // Default fallback
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,16 +104,28 @@ public class Intersection {
|
||||
* Accepts an incoming vehicle and places it in the correct queue.
|
||||
* * This method:
|
||||
* 1. Increments the {@link #totalVehiclesReceived} counter.
|
||||
* 2. Gets the vehicle's *next* destination (from {@link Vehicle#getCurrentDestination()}).
|
||||
* 3. Uses the {@link #routing} map to find the correct *direction* for that destination.
|
||||
* 4. Adds the vehicle to the queue of the {@link TrafficLight} for that direction.
|
||||
* 2. Advances the vehicle's route (since it just arrived here)
|
||||
* 3. Gets the vehicle's *next* destination (from {@link Vehicle#getCurrentDestination()}).
|
||||
* 4. Uses the {@link #routing} map to find the correct *direction* for that destination.
|
||||
* 5. Adds the vehicle to the queue of the {@link TrafficLight} for that direction.
|
||||
*
|
||||
* @param vehicle The {@link Vehicle} arriving at the intersection.
|
||||
*/
|
||||
public void receiveVehicle(Vehicle vehicle) {
|
||||
totalVehiclesReceived++;
|
||||
|
||||
// Advance route since vehicle just arrived at this intersection
|
||||
vehicle.advanceRoute();
|
||||
|
||||
String nextDestination = vehicle.getCurrentDestination();
|
||||
|
||||
// Check if vehicle reached final destination
|
||||
if (nextDestination == null) {
|
||||
System.out.printf("[%s] Vehicle %s reached final destination%n",
|
||||
this.id, vehicle.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
String direction = routing.get(nextDestination);
|
||||
|
||||
if (direction != null && trafficLights.containsKey(direction)) {
|
||||
|
||||
473
main/src/test/java/IntersectionProcessTest.java
Normal file
473
main/src/test/java/IntersectionProcessTest.java
Normal file
@@ -0,0 +1,473 @@
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// cleanup after tests
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
if (intersectionProcess != null) {
|
||||
intersectionProcess.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 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 seperate thread
|
||||
Thread serverThread = new Thread(() -> {
|
||||
try {
|
||||
intersectionProcess.start();
|
||||
} catch (IOException e) {
|
||||
// expected on shutdown
|
||||
}
|
||||
});
|
||||
serverThread.start();
|
||||
|
||||
Thread.sleep(500); // wait for server to start
|
||||
|
||||
// try connecting to check if its running
|
||||
try (Socket clientSocket = new Socket("localhost", 18001)) {
|
||||
assertTrue(clientSocket.isConnected());
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// create test vehicle
|
||||
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
|
||||
try (Socket socket = new Socket("localhost", 18002)) {
|
||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
||||
|
||||
TestVehicleMessage message = new TestVehicleMessage("Cr1", "Cr2", vehicle);
|
||||
out.writeObject(message);
|
||||
out.flush();
|
||||
|
||||
Thread.sleep(1000); // wait for procesing
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Thread serverThread = new Thread(() -> {
|
||||
try {
|
||||
intersectionProcess.start();
|
||||
} catch (IOException e) { }
|
||||
});
|
||||
serverThread.start();
|
||||
|
||||
Thread.sleep(500);
|
||||
|
||||
// verify server running
|
||||
try (Socket socket = new Socket("localhost", 18001)) {
|
||||
assertTrue(socket.isConnected());
|
||||
}
|
||||
|
||||
intersectionProcess.shutdown();
|
||||
serverThread.join(2000);
|
||||
|
||||
// after shutdown conection should fail
|
||||
Thread.sleep(500);
|
||||
Exception exception = assertThrows(IOException.class, () -> {
|
||||
Socket socket = new Socket("localhost", 18001);
|
||||
socket.close();
|
||||
});
|
||||
assertNotNull(exception);
|
||||
}
|
||||
|
||||
@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 {
|
||||
// setup 2 intersections
|
||||
IntersectionProcess cr1 = new IntersectionProcess("Cr1", configFile.toString());
|
||||
IntersectionProcess cr2 = new IntersectionProcess("Cr2", configFile.toString());
|
||||
|
||||
cr1.initialize();
|
||||
cr2.initialize();
|
||||
|
||||
// start both
|
||||
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(1000); // wait for servers
|
||||
|
||||
// send vehicle to Cr1 that goes to Cr2
|
||||
java.util.List<String> route = Arrays.asList("Cr1", "Cr2", "S");
|
||||
Vehicle vehicle = new Vehicle("V001", VehicleType.LIGHT, 0.0, route);
|
||||
|
||||
try (Socket socket = new Socket("localhost", 18001)) {
|
||||
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
|
||||
|
||||
TestVehicleMessage message = new TestVehicleMessage("Entry", "Cr1", vehicle);
|
||||
out.writeObject(message);
|
||||
out.flush();
|
||||
|
||||
Thread.sleep(2000); // time for processing
|
||||
}
|
||||
|
||||
cr1.shutdown();
|
||||
cr2.shutdown();
|
||||
thread1.join(2000);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ class SimulationTest {
|
||||
assertEquals("TEST1", vehicle.getId());
|
||||
assertNotNull(vehicle.getType());
|
||||
assertNotNull(vehicle.getRoute());
|
||||
assertTrue(vehicle.getRoute().size() > 0);
|
||||
assertTrue(!vehicle.getRoute().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user