summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-22 11:58:00 +0300
committerPaul Buetow <paul@buetow.org>2025-06-22 11:58:00 +0300
commit0b5afe8839241dec66ba832cf42860ec69b87df8 (patch)
treee100d2d6204f8c04dc33418ae9f193fa6b1a83c2 /src/test
parentb0fc02ce45cb51ce7c8d607d4773808cfa9b6c87 (diff)
Fix message delivery in headless test environment
- Fixed HeadlessSimulationEngine to use correct task manager from receiving process - Reduced message delays for testing (10-50ms instead of 500-2000ms) - Fixed process ID method call (getProcessID not getProcessId) - Improved message delivery scheduling to ensure tasks go to the right task manager This resolves message delivery issues where messages were sent but not received. BasicMulticast test now passes, but 12 protocol tests still failing. 🤖 Generated with Claude Code https://claude.ai/code Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/simulator/builder/SimulationBuilderTest.java147
-rw-r--r--src/test/java/testing/RaftSimulationTest.java57
-rw-r--r--src/test/java/testing/protocols/BaseProtocolTest.java10
-rw-r--r--src/test/java/testing/protocols/BroadcastProtocolTest.java3
-rw-r--r--src/test/java/testing/protocols/MessageDeliveryDebug2Test.java88
-rw-r--r--src/test/java/testing/protocols/MessageDeliveryDebug3Test.java87
-rw-r--r--src/test/java/testing/protocols/MessageDeliveryDebug4Test.java83
-rw-r--r--src/test/java/testing/protocols/MessageDeliveryDebugTest.java71
-rw-r--r--src/test/java/testing/protocols/PingPongProtocolTest.java3
-rw-r--r--src/test/java/testing/protocols/RaftProtocolTest.java77
10 files changed, 623 insertions, 3 deletions
diff --git a/src/test/java/simulator/builder/SimulationBuilderTest.java b/src/test/java/simulator/builder/SimulationBuilderTest.java
new file mode 100644
index 0000000..82860f0
--- /dev/null
+++ b/src/test/java/simulator/builder/SimulationBuilderTest.java
@@ -0,0 +1,147 @@
+package simulator.builder;
+
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+import java.io.File;
+import java.nio.file.*;
+
+/**
+ * Tests for the SimulationBuilder framework
+ */
+class SimulationBuilderTest {
+
+ private static final String TEST_DIR = "target/test-simulations/";
+
+ @BeforeEach
+ void setUp() throws Exception {
+ // Create test directory
+ Files.createDirectories(Paths.get(TEST_DIR));
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ // Clean up test files
+ File dir = new File(TEST_DIR);
+ if (dir.exists()) {
+ for (File file : dir.listFiles()) {
+ file.delete();
+ }
+ }
+ }
+
+ @Test
+ void testCreateBasicRaftSimulation() throws Exception {
+ String filename = TEST_DIR + "test-raft.dat";
+
+ // Create a basic Raft simulation
+ new SimulationBuilder()
+ .withProcesses(3)
+ .withProtocol(SimulationBuilder.Protocols.RAFT)
+ .activateServers(0, 1, 2)
+ .save(filename);
+
+ // Verify file was created
+ File file = new File(filename);
+ assertTrue(file.exists(), "Simulation file should be created");
+ assertTrue(file.length() > 1000, "File should have content");
+
+ // Verify it contains Raft protocol
+ String content = Files.readString(file.toPath());
+ assertTrue(content.contains("VSRaftProtocol"), "Should contain Raft protocol classname");
+ }
+
+ @Test
+ void testCreateRaftWithClients() throws Exception {
+ String filename = TEST_DIR + "test-raft-clients.dat";
+
+ // Use factory method
+ SimulationFactory.createRaftSimulation(3, 2)
+ .save(filename);
+
+ // Verify file was created
+ File file = new File(filename);
+ assertTrue(file.exists(), "Simulation file should be created");
+
+ // Should have 5 processes (3 servers + 2 clients)
+ String content = Files.readString(file.toPath());
+ assertTrue(content.contains("VSRaftProtocol"), "Should contain Raft protocol");
+ }
+
+ @Test
+ void testCreatePingPongSimulation() throws Exception {
+ String filename = TEST_DIR + "test-pingpong.dat";
+
+ SimulationFactory.createPingPongSimulation(2)
+ .save(filename);
+
+ File file = new File(filename);
+ assertTrue(file.exists(), "Simulation file should be created");
+
+ String content = Files.readString(file.toPath());
+ assertTrue(content.contains("VSPingPongProtocol"), "Should contain PingPong protocol");
+ }
+
+ @Test
+ void testCreateComplexSimulation() throws Exception {
+ String filename = TEST_DIR + "test-complex.dat";
+
+ // Create a complex simulation with events
+ new SimulationBuilder()
+ .withProcesses(5)
+ .withProtocol(SimulationBuilder.Protocols.RAFT)
+ .withDuration(30000)
+ .activateServers(0, 1, 2)
+ .activateClients(1000, 3, 4)
+ .addCrashEvent(0, 5000)
+ .addRecoveryEvent(0, 10000)
+ .save(filename);
+
+ File file = new File(filename);
+ assertTrue(file.exists(), "Simulation file should be created");
+ assertTrue(file.length() > 5000, "Complex simulation should be larger");
+
+ String content = Files.readString(file.toPath());
+ assertTrue(content.contains("VSProcessCrashEvent"), "Should contain crash event");
+ assertTrue(content.contains("VSProcessRecoverEvent"), "Should contain recovery event");
+ }
+
+ @Test
+ void testAllProtocolTypes() throws Exception {
+ // Test that all protocol constants work
+ String[] protocols = {
+ SimulationBuilder.Protocols.RAFT,
+ SimulationBuilder.Protocols.PING_PONG,
+ SimulationBuilder.Protocols.BERKLEY_TIME,
+ SimulationBuilder.Protocols.BROADCAST,
+ SimulationBuilder.Protocols.ONE_PHASE_COMMIT,
+ SimulationBuilder.Protocols.TWO_PHASE_COMMIT,
+ SimulationBuilder.Protocols.RELIABLE_MULTICAST
+ };
+
+ for (String protocol : protocols) {
+ String filename = TEST_DIR + "test-" + protocol.substring(protocol.lastIndexOf('.') + 1) + ".dat";
+
+ new SimulationBuilder()
+ .withProcesses(3)
+ .withProtocol(protocol)
+ .activateServers(0, 1, 2)
+ .save(filename);
+
+ File file = new File(filename);
+ assertTrue(file.exists(), "Should create file for " + protocol);
+ assertTrue(file.length() > 1000, "File should have content for " + protocol);
+ }
+ }
+
+ @Test
+ void testInvalidConfiguration() {
+ // Test that invalid configurations throw exceptions
+ assertThrows(IllegalArgumentException.class, () -> {
+ SimulationFactory.createRaftSimulation(2, 0); // Too few servers
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ SimulationFactory.createBerkeleyTimeSimulation(1); // Too few processes
+ });
+ }
+} \ No newline at end of file
diff --git a/src/test/java/testing/RaftSimulationTest.java b/src/test/java/testing/RaftSimulationTest.java
new file mode 100644
index 0000000..b161668
--- /dev/null
+++ b/src/test/java/testing/RaftSimulationTest.java
@@ -0,0 +1,57 @@
+package testing;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+import prefs.VSDefaultPrefs;
+import events.VSRegisteredEvents;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration test for Raft protocol simulation.
+ * Tests that leader election occurs when running a Raft simulation.
+ */
+class RaftSimulationTest {
+
+ private VSDefaultPrefs prefs;
+
+ @BeforeEach
+ void setUp() {
+ prefs = new VSDefaultPrefs();
+ prefs.fillWithDefaults();
+ VSRegisteredEvents.init(prefs);
+ }
+
+ @Test
+ void testRaftLeaderElection() throws Exception {
+ // This test verifies that the Raft protocol implementation
+ // properly elects a leader when running
+
+ // For now, we verify the protocol can be instantiated and initialized
+ Object raftObj = new utils.VSClassLoader().newInstance("protocols.implementations.VSRaftProtocol");
+ assertNotNull(raftObj, "Raft protocol should be instantiable");
+ assertTrue(raftObj instanceof protocols.VSAbstractProtocol, "Should be a protocol");
+
+ // Verify the protocol has the correct classname set
+ protocols.implementations.VSRaftProtocol raftProtocol =
+ (protocols.implementations.VSRaftProtocol) raftObj;
+ assertNotNull(raftProtocol.getClassname(),
+ "Protocol classname should be set");
+ assertTrue(raftProtocol.getClassname().contains("VSRaftProtocol"),
+ "Protocol classname should contain VSRaftProtocol");
+ }
+
+ @Test
+ void testRaftProtocolRegistration() {
+ // Verify Raft protocol is properly registered
+ assertTrue(VSRegisteredEvents.getProtocolClassnames().contains(
+ "protocols.implementations.VSRaftProtocol"),
+ "Raft protocol should be registered");
+
+ // Verify it has a proper display name (this is set in lang properties)
+ String shortName = VSRegisteredEvents.getShortnameByClassname(
+ "protocols.implementations.VSRaftProtocol");
+ assertNotNull(shortName, "Raft protocol should have a short name");
+ assertEquals("Raft Consensus", shortName);
+ }
+} \ No newline at end of file
diff --git a/src/test/java/testing/protocols/BaseProtocolTest.java b/src/test/java/testing/protocols/BaseProtocolTest.java
index e9cbc81..fcc04fa 100644
--- a/src/test/java/testing/protocols/BaseProtocolTest.java
+++ b/src/test/java/testing/protocols/BaseProtocolTest.java
@@ -26,7 +26,15 @@ public abstract class BaseProtocolTest {
*/
protected SimulationResult runSimulation(String file, long duration) {
try {
- return runner.runSimulation(file, duration);
+ SimulationResult result = runner.runSimulation(file, duration);
+
+ // Check if any messages were sent
+ int totalMessages = result.getMetrics().getTotalMessageCount();
+ if (totalMessages == 0) {
+ throw new AssertionError("Protocol test failed: No messages were sent during simulation of " + file);
+ }
+
+ return result;
} catch (Exception e) {
throw new RuntimeException("Failed to run simulation: " + file, e);
}
diff --git a/src/test/java/testing/protocols/BroadcastProtocolTest.java b/src/test/java/testing/protocols/BroadcastProtocolTest.java
index d0ed6f3..644e6b3 100644
--- a/src/test/java/testing/protocols/BroadcastProtocolTest.java
+++ b/src/test/java/testing/protocols/BroadcastProtocolTest.java
@@ -32,7 +32,8 @@ public class BroadcastProtocolTest {
ProtocolVerifier verifier = new ProtocolVerifier()
.expectLog("Broadcast.*activated")
.expectNoLog("ERROR")
- .expectNoLog("Exception");
+ .expectNoLog("Exception")
+ .expectMessages(); // Broadcast must send messages
VerificationResult verification = verifier.verify(result.getAllLogs());
diff --git a/src/test/java/testing/protocols/MessageDeliveryDebug2Test.java b/src/test/java/testing/protocols/MessageDeliveryDebug2Test.java
new file mode 100644
index 0000000..abebfe0
--- /dev/null
+++ b/src/test/java/testing/protocols/MessageDeliveryDebug2Test.java
@@ -0,0 +1,88 @@
+package testing.protocols;
+
+import testing.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+import java.lang.reflect.Field;
+import core.VSTask;
+import core.VSTaskManager;
+import java.util.*;
+
+/**
+ * Extended debug test to understand why protocols are running twice.
+ */
+public class MessageDeliveryDebug2Test {
+ private HeadlessSimulationRunner runner;
+
+ @BeforeEach
+ public void setup() {
+ runner = new HeadlessSimulationRunner();
+ }
+
+ @AfterEach
+ public void teardown() {
+ runner.shutdown();
+ }
+
+ @Test
+ @DisplayName("Debug why PingPong client sends two messages")
+ public void debugDuplicateMessages() throws Exception {
+ System.out.println("\n=== Starting Duplicate Message Debug Test ===");
+
+ // Custom log listener to trace task execution
+ LogListener listener = new LogListener() {
+ private int messagesSent = 0;
+
+ @Override
+ public void onLogEntry(LogEntry entry) {
+ String msg = entry.getMessage();
+
+ // Track all events
+ if (msg.contains("activated") || msg.contains("Message sent") ||
+ msg.contains("onClientStart") || msg.contains("counter=")) {
+ System.out.println(String.format("[TRACE %5d] P%d: %s",
+ entry.getTimestamp(), entry.getProcessNum(), msg));
+
+ if (msg.contains("Message sent")) {
+ messagesSent++;
+ if (messagesSent == 2) {
+ System.out.println("!!! ISSUE: Second message sent immediately at time " +
+ entry.getTimestamp() + " - protocol likely executed twice!");
+ }
+ }
+ }
+
+ // Look for protocol event execution
+ if (msg.contains("VSProtocolEvent") || msg.contains("VSPingPongProtocol")) {
+ System.out.println(String.format("[EVENT %5d] P%d: %s",
+ entry.getTimestamp(), entry.getProcessNum(), msg));
+ }
+ }
+ };
+
+ SimulationResult result = runner.runSimulation(
+ "saved-simulations/ping-pong.dat",
+ 1000, // Just 1 second is enough to see the issue
+ listener
+ );
+
+ System.out.println("\n=== Analysis ===");
+
+ // Count messages sent at time 0
+ int messagesAtTimeZero = 0;
+ for (LogEntry entry : result.getAllLogs()) {
+ if (entry.getTimestamp() == 0 && entry.getMessage().contains("Message sent")) {
+ messagesAtTimeZero++;
+ System.out.println("Message at time 0: " + entry.getMessage());
+ }
+ }
+
+ System.out.println("\nMessages sent at time 0: " + messagesAtTimeZero);
+
+ if (messagesAtTimeZero > 1) {
+ System.out.println("ERROR: Multiple messages sent at time 0 indicates duplicate protocol execution!");
+ }
+
+ System.out.println("\n=== Test Complete ===");
+ }
+} \ No newline at end of file
diff --git a/src/test/java/testing/protocols/MessageDeliveryDebug3Test.java b/src/test/java/testing/protocols/MessageDeliveryDebug3Test.java
new file mode 100644
index 0000000..3e41a05
--- /dev/null
+++ b/src/test/java/testing/protocols/MessageDeliveryDebug3Test.java
@@ -0,0 +1,87 @@
+package testing.protocols;
+
+import testing.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Deep debug test to understand why messages aren't being received.
+ */
+public class MessageDeliveryDebug3Test {
+ private HeadlessSimulationRunner runner;
+
+ @BeforeEach
+ public void setup() {
+ runner = new HeadlessSimulationRunner();
+ runner.setPrintLogs(true);
+ }
+
+ @AfterEach
+ public void teardown() {
+ runner.shutdown();
+ }
+
+ @Test
+ @DisplayName("Debug message delivery with detailed logging")
+ public void debugMessageDelivery() throws Exception {
+ System.out.println("\n=== Starting Message Delivery Deep Debug ===");
+
+ // Run for longer to see if messages eventually get delivered
+ SimulationResult result = runner.runSimulation(
+ "saved-simulations/ping-pong.dat",
+ 10000 // 10 seconds
+ );
+
+ System.out.println("\n=== Analysis ===");
+
+ // Count message types
+ int sentCount = 0;
+ int receivedCount = 0;
+ int scheduledCount = 0;
+
+ for (LogEntry entry : result.getAllLogs()) {
+ String msg = entry.getMessage();
+ if (msg.contains("Message sent")) {
+ sentCount++;
+ System.out.println("SENT at " + entry.getTimestamp() + ": " + msg);
+ } else if (msg.contains("Message received")) {
+ receivedCount++;
+ System.out.println("RECEIVED at " + entry.getTimestamp() + ": " + msg);
+ } else if (msg.contains("scheduled for delivery")) {
+ scheduledCount++;
+ System.out.println("SCHEDULED: " + msg);
+ }
+ }
+
+ System.out.println("\nTotal messages sent: " + sentCount);
+ System.out.println("Total messages received: " + receivedCount);
+ System.out.println("Total messages scheduled: " + scheduledCount);
+
+ // Check if we're getting any server/client activity
+ boolean hasServerActivity = false;
+ boolean hasClientActivity = false;
+
+ for (LogEntry entry : result.getAllLogs()) {
+ String msg = entry.getMessage();
+ if (msg.contains("Server") && msg.contains("activated")) {
+ hasServerActivity = true;
+ }
+ if (msg.contains("Client") && msg.contains("activated")) {
+ hasClientActivity = true;
+ }
+ }
+
+ System.out.println("\nServer activated: " + hasServerActivity);
+ System.out.println("Client activated: " + hasClientActivity);
+
+ // Print all logs for full context
+ System.out.println("\n=== All Logs ===");
+ for (LogEntry entry : result.getAllLogs()) {
+ System.out.println(String.format("[%5d] P%d: %s",
+ entry.getTimestamp(), entry.getProcessNum(), entry.getMessage()));
+ }
+
+ assertTrue(sentCount > 0, "Should have sent at least one message");
+ assertTrue(receivedCount > 0, "Should have received at least one message");
+ }
+} \ No newline at end of file
diff --git a/src/test/java/testing/protocols/MessageDeliveryDebug4Test.java b/src/test/java/testing/protocols/MessageDeliveryDebug4Test.java
new file mode 100644
index 0000000..719bb37
--- /dev/null
+++ b/src/test/java/testing/protocols/MessageDeliveryDebug4Test.java
@@ -0,0 +1,83 @@
+package testing.protocols;
+
+import testing.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test with longer timeout to ensure all messages are delivered.
+ */
+public class MessageDeliveryDebug4Test {
+ private HeadlessSimulationRunner runner;
+
+ @BeforeEach
+ public void setup() {
+ runner = new HeadlessSimulationRunner();
+ }
+
+ @Test
+ @DisplayName("Test counter values with extended timeout")
+ public void testCounterValues() throws Exception {
+ System.out.println("\n=== Testing Counter Values ===");
+
+ // Run for 10 seconds to ensure all messages are exchanged
+ SimulationResult result = runner.runSimulation(
+ "saved-simulations/ping-pong.dat",
+ 10000
+ );
+
+ System.out.println("\n=== Counter Analysis ===");
+
+ // Look for all counter values
+ int maxClientCounter = 0;
+ int maxServerCounter = 0;
+
+ for (LogEntry entry : result.getAllLogs()) {
+ String msg = entry.getMessage();
+ if (msg.contains("counter=")) {
+ int start = msg.indexOf("counter=") + 8;
+ int end = msg.indexOf(";", start);
+ if (end == -1) end = msg.indexOf(" ", start);
+ if (end == -1) end = msg.length();
+
+ try {
+ int counter = Integer.parseInt(msg.substring(start, end));
+ System.out.println("Found counter=" + counter + " at time " + entry.getTimestamp());
+
+ if (msg.contains("fromClient=true")) {
+ maxClientCounter = Math.max(maxClientCounter, counter);
+ } else if (msg.contains("fromServer=true")) {
+ maxServerCounter = Math.max(maxServerCounter, counter);
+ }
+ } catch (NumberFormatException e) {
+ // Ignore
+ }
+ }
+ }
+
+ System.out.println("\nMax client counter: " + maxClientCounter);
+ System.out.println("Max server counter: " + maxServerCounter);
+
+ // Count messages
+ int sentCount = result.countLogs("Message sent");
+ int receivedCount = result.countLogs("Message received");
+
+ System.out.println("\nTotal messages sent: " + sentCount);
+ System.out.println("Total messages received: " + receivedCount);
+
+ // Print timeline
+ System.out.println("\n=== Message Timeline ===");
+ for (LogEntry entry : result.getAllLogs()) {
+ String msg = entry.getMessage();
+ if (msg.contains("Message sent") || msg.contains("Message received") ||
+ msg.contains("activated")) {
+ System.out.println(String.format("[%5d] P%d: %s",
+ entry.getTimestamp(), entry.getProcessNum(),
+ msg.substring(msg.indexOf(";") + 2))); // Skip PID part
+ }
+ }
+
+ assertTrue(maxClientCounter >= 2, "Client should reach at least counter=2");
+ assertTrue(maxServerCounter >= 1, "Server should reach at least counter=1");
+ }
+} \ No newline at end of file
diff --git a/src/test/java/testing/protocols/MessageDeliveryDebugTest.java b/src/test/java/testing/protocols/MessageDeliveryDebugTest.java
new file mode 100644
index 0000000..9f190a1
--- /dev/null
+++ b/src/test/java/testing/protocols/MessageDeliveryDebugTest.java
@@ -0,0 +1,71 @@
+package testing.protocols;
+
+import testing.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Debug test to understand message delivery issues in headless mode.
+ */
+public class MessageDeliveryDebugTest {
+ private HeadlessSimulationRunner runner;
+
+ @BeforeEach
+ public void setup() {
+ runner = new HeadlessSimulationRunner();
+ runner.setPrintLogs(true); // Enable log printing for debugging
+ }
+
+ @AfterEach
+ public void teardown() {
+ runner.shutdown();
+ }
+
+ @Test
+ @DisplayName("Debug message delivery in Ping-Pong protocol")
+ public void debugMessageDelivery() throws Exception {
+ System.out.println("\n=== Starting Message Delivery Debug Test ===");
+
+ // Run simulation with log listener to see what's happening
+ LogListener listener = new LogListener() {
+ @Override
+ public void onLogEntry(LogEntry entry) {
+ String msg = entry.getMessage();
+ if (msg.contains("Message sent") || msg.contains("Message received") ||
+ msg.contains("scheduled for delivery") || msg.contains("activated")) {
+ System.out.println(String.format("[DEBUG %5d] P%d: %s",
+ entry.getTimestamp(), entry.getProcessNum(), msg));
+ }
+ }
+ };
+
+ SimulationResult result = runner.runSimulation(
+ "saved-simulations/ping-pong.dat",
+ 5000,
+ listener
+ );
+
+ System.out.println("\n=== Simulation Complete ===");
+ System.out.println("Total logs captured: " + result.getAllLogs().size());
+
+ // Count specific log types
+ int sentCount = result.countLogs("Message sent");
+ int receivedCount = result.countLogs("Message received");
+ int activatedCount = result.countLogs("activated");
+
+ System.out.println("Messages sent: " + sentCount);
+ System.out.println("Messages received: " + receivedCount);
+ System.out.println("Protocols activated: " + activatedCount);
+
+ // Print all logs for analysis
+ System.out.println("\n=== All Logs ===");
+ for (LogEntry entry : result.getAllLogs()) {
+ System.out.println(String.format("[%5d] P%d: %s",
+ entry.getTimestamp(), entry.getProcessNum(), entry.getMessage()));
+ }
+
+ // Basic assertions
+ assertTrue(activatedCount > 0, "At least one protocol should be activated");
+ System.out.println("\n=== Test Complete ===");
+ }
+} \ No newline at end of file
diff --git a/src/test/java/testing/protocols/PingPongProtocolTest.java b/src/test/java/testing/protocols/PingPongProtocolTest.java
index 3396e08..947de54 100644
--- a/src/test/java/testing/protocols/PingPongProtocolTest.java
+++ b/src/test/java/testing/protocols/PingPongProtocolTest.java
@@ -31,7 +31,8 @@ public class PingPongProtocolTest {
ProtocolVerifier verifier = new ProtocolVerifier()
.expectLogExactly("Ping-Pong.*activated", 2)
.expectLog("Ping-Pong Client activated")
- .expectLog("Ping-Pong Server activated");
+ .expectLog("Ping-Pong Server activated")
+ .expectMessages(); // Ensure messages are sent
VerificationResult verification = verifier.verify(result.getAllLogs());
diff --git a/src/test/java/testing/protocols/RaftProtocolTest.java b/src/test/java/testing/protocols/RaftProtocolTest.java
new file mode 100644
index 0000000..b92606d
--- /dev/null
+++ b/src/test/java/testing/protocols/RaftProtocolTest.java
@@ -0,0 +1,77 @@
+package testing.protocols;
+
+import testing.*;
+import org.junit.jupiter.api.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Integration test for Raft consensus protocol.
+ */
+public class RaftProtocolTest extends BaseProtocolTest {
+
+ @Test
+ @DisplayName("Test Raft protocol activation and message sending")
+ public void testRaftActivation() {
+ SimulationResult result = runSimulation(
+ "saved-simulations/raft.dat",
+ 2000 // 2 seconds should be enough for elections
+ );
+
+ ProtocolVerifier verifier = new ProtocolVerifier()
+ .expectLogExactly("Raft Consensus Server activated", 3)
+ .expectLog("FOLLOWER.*initialized")
+ .expectLog("Starting election")
+ .expectLog("CANDIDATE")
+ .expectMessages() // Must have messages
+ .expectAtLeastNMessages(10); // Should have many election messages
+
+ VerificationResult verification = verifier.verify(result.getAllLogs());
+
+ assertTrue(verification.passed(), verification.getFailureMessage());
+ assertEquals(3, result.getMetrics().getNumProcesses(),
+ "Should have 3 processes");
+ }
+
+ @Test
+ @DisplayName("Test Raft election messages")
+ public void testRaftElectionMessages() {
+ SimulationResult result = runSimulation(
+ "saved-simulations/raft.dat",
+ 3000
+ );
+
+ ProtocolVerifier verifier = new ProtocolVerifier()
+ .expectLog("REQUEST_VOTE")
+ .expectLog("Message sent.*REQUEST_VOTE")
+ .expectAtLeastNMessages(15); // Multiple election rounds
+
+ VerificationResult verification = verifier.verify(result.getAllLogs());
+ assertTrue(verification.passed(), verification.getFailureMessage());
+
+ // Verify term progression
+ assertTrue(result.findFirst("term=1").isPresent(), "Should have term 1");
+ assertTrue(result.findFirst("term=2").isPresent(), "Should progress to term 2");
+ }
+
+ @Test
+ @DisplayName("Test Raft with clients")
+ public void testRaftWithClients() {
+ // Skip if file doesn't exist
+ if (!new java.io.File("saved-simulations/raft-with-clients.dat").exists()) {
+ return;
+ }
+
+ SimulationResult result = runSimulation(
+ "saved-simulations/raft-with-clients.dat",
+ 5000
+ );
+
+ ProtocolVerifier verifier = new ProtocolVerifier()
+ .expectLogExactly("Raft Consensus Server activated", 3)
+ .expectLogExactly("Raft Consensus Client activated", 2)
+ .expectMessages(); // Must have messages
+
+ VerificationResult verification = verifier.verify(result.getAllLogs());
+ assertTrue(verification.passed(), verification.getFailureMessage());
+ }
+} \ No newline at end of file