mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 12:33:31 +00:00
Add ExitNodeProcess and unit tests
This commit is contained in:
382
main/src/main/java/sd/ExitNodeProcess.java
Normal file
382
main/src/main/java/sd/ExitNodeProcess.java
Normal file
@@ -0,0 +1,382 @@
|
||||
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.coordinator.SocketClient;
|
||||
import sd.model.Message;
|
||||
import sd.model.MessageType;
|
||||
import sd.model.Vehicle;
|
||||
import sd.model.VehicleType;
|
||||
import sd.protocol.MessageProtocol;
|
||||
import sd.protocol.SocketConnection;
|
||||
import sd.serialization.SerializationException;
|
||||
|
||||
/**
|
||||
* Processo responsável pelo nó de saída do sistema de simulação de tráfego distribuído.
|
||||
*
|
||||
* Este processo representa o ponto final ("S") onde os veículos completam as suas rotas.
|
||||
* As suas principais responsabilidades são:
|
||||
* - Receber veículos que terminam a sua rota vindos das interseções
|
||||
* - Calcular e agregar estatísticas finais dos veículos
|
||||
* - Enviar estatísticas periódicas para o dashboard
|
||||
* - Gerar relatórios finais ao terminar a simulação
|
||||
*/
|
||||
public class ExitNodeProcess {
|
||||
|
||||
private final SimulationConfig config;
|
||||
private ServerSocket serverSocket;
|
||||
private final ExecutorService connectionHandlerPool;
|
||||
|
||||
/** Flag para controlar a execução do processo (volatile para visibilidade entre threads) */
|
||||
private volatile boolean running;
|
||||
|
||||
/** Counter de veículos que completaram a rota */
|
||||
private int totalVehiclesReceived;
|
||||
|
||||
/** Soma dos tempos no sistema de todos os veículos */
|
||||
private double totalSystemTime;
|
||||
|
||||
/** Soma dos tempos de espera de todos os veículos */
|
||||
private double totalWaitingTime;
|
||||
|
||||
/** Soma dos tempos de travessia de todos os veículos */
|
||||
private double totalCrossingTime;
|
||||
|
||||
/** Contagem de veículos por tipo */
|
||||
private final Map<VehicleType, Integer> vehicleTypeCount;
|
||||
|
||||
/** Tempo total de espera acumulado por tipo de veículo */
|
||||
private final Map<VehicleType, Double> vehicleTypeWaitTime;
|
||||
|
||||
/** Socket para comunicação com o dashboard */
|
||||
private SocketClient dashboardClient;
|
||||
|
||||
/**
|
||||
* Método para iniciar o processo
|
||||
*
|
||||
* @param args Argumentos da linha de comandos. Se fornecido, args[0] deve ser
|
||||
* o caminho para um ficheiro de configuração personalizado.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=".repeat(60));
|
||||
System.out.println("EXIT NODE PROCESS");
|
||||
System.out.println("=".repeat(60));
|
||||
|
||||
try {
|
||||
String configFile = args.length > 0 ? args[0] : "src/main/resources/simulation.properties";
|
||||
System.out.println("Loading configuration from: " + configFile);
|
||||
|
||||
SimulationConfig config = new SimulationConfig(configFile);
|
||||
ExitNodeProcess exitNode = new ExitNodeProcess(config);
|
||||
|
||||
System.out.println("\n" + "=".repeat(60));
|
||||
exitNode.initialize();
|
||||
|
||||
System.out.println("\n" + "=".repeat(60));
|
||||
exitNode.start();
|
||||
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to start exit node: " + e.getMessage());
|
||||
System.exit(1);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Exit node error: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrói um novo processo de nó de saída.
|
||||
*
|
||||
* Inicializa todas as estruturas de dados necessárias para recolher estatísticas
|
||||
* e configura o pool de threads para processar as ligações concorrentes.
|
||||
*
|
||||
* @param config Configuração da simulação contendo portas e endereços dos serviços
|
||||
*/
|
||||
public ExitNodeProcess(SimulationConfig config) {
|
||||
this.config = config;
|
||||
this.connectionHandlerPool = Executors.newCachedThreadPool();
|
||||
this.running = false;
|
||||
|
||||
this.totalVehiclesReceived = 0;
|
||||
this.totalSystemTime = 0.0;
|
||||
this.totalWaitingTime = 0.0;
|
||||
this.totalCrossingTime = 0.0;
|
||||
this.vehicleTypeCount = new HashMap<>();
|
||||
this.vehicleTypeWaitTime = new HashMap<>();
|
||||
|
||||
// Inicializa os counters para cada tipo de veículo
|
||||
for (VehicleType type : VehicleType.values()) {
|
||||
vehicleTypeCount.put(type, 0);
|
||||
vehicleTypeWaitTime.put(type, 0.0);
|
||||
}
|
||||
|
||||
System.out.println("Exit node initialized");
|
||||
System.out.println(" - Exit port: " + config.getExitPort());
|
||||
System.out.println(" - Dashboard: " + config.getDashboardHost() + ":" + config.getDashboardPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializa o processo de ligação ao dashboard.
|
||||
*
|
||||
* Tenta conectar-se ao dashboard. Se a ligação falhar, o processo
|
||||
* continua a funcionar normalmente, mas sem enviar estatísticas.
|
||||
*
|
||||
*/
|
||||
public void initialize() {
|
||||
System.out.println("Connecting to dashboard...");
|
||||
|
||||
try {
|
||||
String host = config.getDashboardHost();
|
||||
int port = config.getDashboardPort();
|
||||
|
||||
dashboardClient = new SocketClient("Dashboard", host, port);
|
||||
dashboardClient.connect();
|
||||
|
||||
System.out.println("Successfully connected to dashboard");
|
||||
} catch (IOException e) {
|
||||
System.err.println("WARNING: Failed to connect to dashboard: " + e.getMessage());
|
||||
System.err.println("Exit node will continue without dashboard connection");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicia o socket e começa a aceitar ligações.
|
||||
*
|
||||
* Este é o loop principal do processo que:
|
||||
* 1. Cria um socket na porta definida
|
||||
* 2. Aguarda pelas ligações das interseções
|
||||
* 3. Delega cada ligação a uma thread da pool para processamento assíncrono
|
||||
*
|
||||
* @throws IOException Se o socket não puder ser criado ou houver erro na aceitação
|
||||
*/
|
||||
public void start() throws IOException {
|
||||
int port = config.getExitPort();
|
||||
serverSocket = new ServerSocket(port);
|
||||
running = true;
|
||||
|
||||
System.out.println("Exit node started on port " + port);
|
||||
System.out.println("Waiting for vehicles...\n");
|
||||
|
||||
while (running) {
|
||||
try {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
connectionHandlerPool.submit(() -> handleIncomingConnection(clientSocket));
|
||||
} catch (IOException e) {
|
||||
if (running) {
|
||||
System.err.println("Error accepting connection: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa uma ligação recebida de uma interseção.
|
||||
*
|
||||
* Mantém a ligação aberta e processa continuamente mensagens do tipo
|
||||
* VEHICLE_TRANSFER. Cada mensagem representa um veículo que chegou ao nó de saída.
|
||||
*
|
||||
* @param clientSocket Socket da ligação estabelecida com a interseção
|
||||
*/
|
||||
private void handleIncomingConnection(Socket clientSocket) {
|
||||
try (SocketConnection connection = new SocketConnection(clientSocket)) {
|
||||
|
||||
System.out.println("New connection accepted from " +
|
||||
clientSocket.getInetAddress().getHostAddress());
|
||||
|
||||
while (running && connection.isConnected()) {
|
||||
try {
|
||||
MessageProtocol message = connection.receiveMessage();
|
||||
|
||||
if (message.getType() == MessageType.VEHICLE_TRANSFER) {
|
||||
Vehicle vehicle = (Vehicle) message.getPayload();
|
||||
processExitingVehicle(vehicle);
|
||||
}
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
System.err.println("Unknown message type received: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
if (running) {
|
||||
System.err.println("Connection error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa um veículo que chegou ao nó de saída.
|
||||
*
|
||||
* Método sincronizado para garantir thread-safety ao atualizar as estatísticas.
|
||||
* Calcula as métricas finais do veículo e atualiza:
|
||||
* - Counters globais;
|
||||
* - Estatísticas por tipo de veículo;
|
||||
* - Faz update ao dashboard a cada 10 veículos.
|
||||
*
|
||||
* @param vehicle Veículo que completou a sua rota
|
||||
*/
|
||||
private synchronized void processExitingVehicle(Vehicle vehicle) {
|
||||
totalVehiclesReceived++;
|
||||
|
||||
double systemTime = vehicle.getTotalTravelTime(getCurrentTime());
|
||||
double waitTime = vehicle.getTotalWaitingTime();
|
||||
double crossingTime = vehicle.getTotalCrossingTime();
|
||||
|
||||
totalSystemTime += systemTime;
|
||||
totalWaitingTime += waitTime;
|
||||
totalCrossingTime += crossingTime;
|
||||
|
||||
VehicleType type = vehicle.getType();
|
||||
vehicleTypeCount.put(type, vehicleTypeCount.get(type) + 1);
|
||||
vehicleTypeWaitTime.put(type, vehicleTypeWaitTime.get(type) + waitTime);
|
||||
|
||||
System.out.printf("[Exit] Vehicle %s completed (type=%s, system_time=%.2fs, wait=%.2fs)%n",
|
||||
vehicle.getId(), vehicle.getType(), systemTime, waitTime);
|
||||
|
||||
if (totalVehiclesReceived % 10 == 0) {
|
||||
sendStatsToDashboard();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtém o tempo atual da simulação em segundos.
|
||||
*
|
||||
* @return Tempo atual em segundos desde "epoch"
|
||||
*
|
||||
* "Epoch" é um ponto de referência temporal Unix (1 de janeiro de 1970).
|
||||
* Este método retorna os segundos decorridos desde esse momento.
|
||||
*/
|
||||
private double getCurrentTime() {
|
||||
return System.currentTimeMillis() / 1000.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envia as estatísticas para o dashboard.
|
||||
*
|
||||
* Prepara e envia uma mensagem STATS_UPDATE com:
|
||||
* - O total de veículos processados;
|
||||
* - A média dos tempos (sistema, espera, travessia);
|
||||
* - As contagens e médias por cada tipo de veículo.
|
||||
*
|
||||
*/
|
||||
private void sendStatsToDashboard() {
|
||||
if (dashboardClient == null || !dashboardClient.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalVehicles", totalVehiclesReceived);
|
||||
stats.put("avgSystemTime", totalVehiclesReceived > 0 ? totalSystemTime / totalVehiclesReceived : 0.0);
|
||||
stats.put("avgWaitingTime", totalVehiclesReceived > 0 ? totalWaitingTime / totalVehiclesReceived : 0.0);
|
||||
stats.put("avgCrossingTime", totalVehiclesReceived > 0 ? totalCrossingTime / totalVehiclesReceived : 0.0);
|
||||
|
||||
Map<String, Integer> typeCounts = new HashMap<>();
|
||||
Map<String, Double> typeAvgWait = new HashMap<>();
|
||||
for (VehicleType type : VehicleType.values()) {
|
||||
int count = vehicleTypeCount.get(type);
|
||||
typeCounts.put(type.name(), count);
|
||||
if (count > 0) {
|
||||
typeAvgWait.put(type.name(), vehicleTypeWaitTime.get(type) / count);
|
||||
}
|
||||
}
|
||||
stats.put("vehicleTypeCounts", typeCounts);
|
||||
stats.put("vehicleTypeAvgWait", typeAvgWait);
|
||||
|
||||
Message message = new Message(MessageType.STATS_UPDATE, "ExitNode", "Dashboard", stats);
|
||||
dashboardClient.send(message);
|
||||
|
||||
System.out.printf("[Exit] Sent stats to dashboard (total=%d, avg_wait=%.2fs)%n",
|
||||
totalVehiclesReceived, totalWaitingTime / totalVehiclesReceived);
|
||||
|
||||
} catch (SerializationException | IOException e) {
|
||||
System.err.println("Failed to send stats to dashboard: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Termina o processo
|
||||
*
|
||||
* Executa a seguinte sequência:
|
||||
* Imprime as estatísticas finais no terminal;
|
||||
* Envia a última atualização de estatísticas ao dashboard;
|
||||
* Fecha o socket;
|
||||
* Aguarda pela finalização das threads;
|
||||
* Fecha a ligação com o dashboard;
|
||||
*/
|
||||
public void shutdown() {
|
||||
System.out.println("\n[Exit] Shutting down...");
|
||||
running = false;
|
||||
|
||||
printFinalStatistics();
|
||||
|
||||
sendStatsToDashboard();
|
||||
|
||||
try {
|
||||
if (serverSocket != null && !serverSocket.isClosed()) {
|
||||
serverSocket.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error closing server socket: " + e.getMessage());
|
||||
}
|
||||
|
||||
connectionHandlerPool.shutdown();
|
||||
try {
|
||||
if (!connectionHandlerPool.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
connectionHandlerPool.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
connectionHandlerPool.shutdownNow();
|
||||
}
|
||||
|
||||
if (dashboardClient != null) {
|
||||
dashboardClient.close();
|
||||
}
|
||||
|
||||
System.out.println("[Exit] Shutdown complete.");
|
||||
System.out.println("=".repeat(60));
|
||||
}
|
||||
|
||||
/**
|
||||
* Imprime as estatísticas finais detalhadas no terminal
|
||||
*
|
||||
* Gera um relatório com:
|
||||
* Total de veículos que completaram a rota;
|
||||
* Médias de tempo no sistema, espera e travessia;
|
||||
* Distribuição e médias pelo tipo de veículo (BIKE, LIGHT, HEAVY);
|
||||
*
|
||||
* Este método é chamado durante o shutdown para fornecer um resumo
|
||||
* da simulação antes de terminar o processo.
|
||||
*/
|
||||
private void printFinalStatistics() {
|
||||
System.out.println("\n=== EXIT NODE STATISTICS ===");
|
||||
System.out.printf("Total Vehicles Completed: %d%n", totalVehiclesReceived);
|
||||
|
||||
if (totalVehiclesReceived > 0) {
|
||||
System.out.printf("%nAVERAGE METRICS:%n");
|
||||
System.out.printf(" System Time: %.2f seconds%n", totalSystemTime / totalVehiclesReceived);
|
||||
System.out.printf(" Waiting Time: %.2f seconds%n", totalWaitingTime / totalVehiclesReceived);
|
||||
System.out.printf(" Crossing Time: %.2f seconds%n", totalCrossingTime / totalVehiclesReceived);
|
||||
}
|
||||
|
||||
System.out.println("\nVEHICLE TYPE DISTRIBUTION:");
|
||||
for (VehicleType type : VehicleType.values()) {
|
||||
int count = vehicleTypeCount.get(type);
|
||||
if (count > 0) {
|
||||
double percentage = (count * 100.0) / totalVehiclesReceived;
|
||||
double avgWait = vehicleTypeWaitTime.get(type) / count;
|
||||
System.out.printf(" %s: %d (%.1f%%), Avg Wait: %.2fs%n",
|
||||
type, count, percentage, avgWait);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
327
main/src/test/java/sd/ExitNodeProcessTest.java
Normal file
327
main/src/test/java/sd/ExitNodeProcessTest.java
Normal file
@@ -0,0 +1,327 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user