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