mirror of
https://github.com/davidalves04/Trabalho-Pratico-SD.git
synced 2025-12-08 20:43:32 +00:00
Add ExitNodeProcess and unit tests
This commit is contained in:
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