summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-27 15:23:17 +0200
committerPaul Buetow <paul@buetow.org>2026-03-27 15:23:17 +0200
commit69c5dbf50c36ae622c02b943cc6c471254bb2fa9 (patch)
tree01173b3404af9b2f156c78a7aff72367b074cb39 /src/test
parentca9d7633d76871cb0ea00dd46af351daf8ef4895 (diff)
Strengthen replay schedule regression checks
Diffstat (limited to 'src/test')
-rw-r--r--src/test/java/core/VSTaskManagerCrashRecoveryIntegrationTest.java40
-rw-r--r--src/test/java/simulator/builder/SimulationBuilderTest.java78
2 files changed, 113 insertions, 5 deletions
diff --git a/src/test/java/core/VSTaskManagerCrashRecoveryIntegrationTest.java b/src/test/java/core/VSTaskManagerCrashRecoveryIntegrationTest.java
index 81ceeb8..7bb6038 100644
--- a/src/test/java/core/VSTaskManagerCrashRecoveryIntegrationTest.java
+++ b/src/test/java/core/VSTaskManagerCrashRecoveryIntegrationTest.java
@@ -75,12 +75,46 @@ class VSTaskManagerCrashRecoveryIntegrationTest {
loadedSimulatorToStop = loaded.getSimulator();
VSSimulatorVisualization visualization = loaded.getVisualization();
- runUntil(visualization, 20);
+ assertFalse(visualization.getProcess(0).isCrashed(),
+ "process 0 should start alive after replay load");
+ assertFalse(visualization.getProcess(1).isCrashed(),
+ "process 1 should start alive after replay load");
+
+ runUntil(visualization, 4);
+ assertFalse(visualization.getProcess(0).isCrashed(),
+ "process 0 should stay alive before its crash point");
+ assertFalse(visualization.getProcess(1).isCrashed(),
+ "process 1 should stay alive before process 0 crashes");
+
+ runUntil(visualization, 6);
+ assertTrue(visualization.getProcess(0).isCrashed(),
+ "process 0 should crash immediately after its scheduled crash point");
+ assertFalse(visualization.getProcess(1).isCrashed(),
+ "process 1 should still be alive while process 0 is crashed");
+
+ runUntil(visualization, 10);
+ assertTrue(visualization.getProcess(0).isCrashed(),
+ "process 0 should remain crashed before its recover point");
+ assertFalse(visualization.getProcess(1).isCrashed(),
+ "process 1 should still be alive before its later crash");
+
+ runUntil(visualization, 11);
+ assertFalse(visualization.getProcess(0).isCrashed(),
+ "process 0 should recover immediately after its scheduled recover point");
+ assertFalse(visualization.getProcess(1).isCrashed(),
+ "process 1 should still be alive before its crash point");
+
+ runUntil(visualization, 15);
+ assertFalse(visualization.getProcess(0).isCrashed(),
+ "process 0 should stay recovered before process 1 crashes");
+ assertFalse(visualization.getProcess(1).isCrashed(),
+ "process 1 should stay alive before its later crash point");
+ runUntil(visualization, 16);
assertFalse(visualization.getProcess(0).isCrashed(),
- "process 0 should recover after replay load");
+ "process 0 should remain recovered after replay load");
assertTrue(visualization.getProcess(1).isCrashed(),
- "process 1 should still crash later in the replay");
+ "process 1 should crash immediately after its later replay point");
}
@Test
diff --git a/src/test/java/simulator/builder/SimulationBuilderTest.java b/src/test/java/simulator/builder/SimulationBuilderTest.java
index c2d6511..6d16fa1 100644
--- a/src/test/java/simulator/builder/SimulationBuilderTest.java
+++ b/src/test/java/simulator/builder/SimulationBuilderTest.java
@@ -2,10 +2,17 @@ package simulator.builder;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
import java.io.File;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
+import core.VSInternalProcess;
+import simulator.VSSimulator;
+import simulator.VSSimulatorVisualization;
+import testing.HeadlessLoader;
+
/**
* Tests for the SimulationBuilder framework
*/
@@ -76,8 +83,7 @@ class SimulationBuilderTest {
String filename = TEST_DIR + "test-raft.dat";
SimulationBuilder builder = SimulationFactory.createRaftSimulation();
- builder
- .save(filename);
+ VSSimulator simulator = builder.save(filename).getSimulator();
File file = new File(filename);
assertTrue(file.exists(), "Simulation file should be created");
@@ -92,6 +98,42 @@ class SimulationBuilderTest {
assertTrue(content.contains("VSProcessRecoverEvent"), "Should contain recovery event");
assertTrue(countOccurrences(content, "VSProcessCrashEvent") >= 2,
"Should contain two crash events for different processes");
+
+ HeadlessLoader.LoadedSimulation loaded =
+ HeadlessLoader.load(filename, simulator.getPrefs());
+ VSSimulator loadedSimulator = loaded.getSimulator();
+ VSSimulatorVisualization visualization = loaded.getVisualization();
+ try {
+ VSInternalProcess process0 = visualization.getProcess(0);
+ VSInternalProcess process2 = visualization.getProcess(2);
+
+ runUntil(visualization, 3499);
+ assertFalse(process0.isCrashed(), "leader should stay alive before 3500ms");
+ assertFalse(process2.isCrashed(), "process 2 should stay alive before 3500ms");
+
+ runUntil(visualization, 3501);
+ assertTrue(process0.isCrashed(), "leader should crash immediately after 3500ms");
+ assertFalse(process2.isCrashed(), "process 2 should still be alive after leader crash");
+
+ runUntil(visualization, 12000);
+ assertTrue(process0.isCrashed(), "leader should remain crashed before 12000ms recovery executes");
+ assertFalse(process2.isCrashed(), "process 2 should stay alive before 20000ms");
+
+ runUntil(visualization, 12001);
+ assertFalse(process0.isCrashed(), "leader should recover immediately after 12000ms");
+ assertFalse(process2.isCrashed(), "process 2 should still be alive after leader recovery");
+
+ runUntil(visualization, 20000);
+ assertFalse(process0.isCrashed(), "leader should remain recovered before 20000ms crash executes");
+ assertFalse(process2.isCrashed(), "process 2 should stay alive before its crash point");
+
+ runUntil(visualization, 20001);
+ assertFalse(process0.isCrashed(), "leader should remain recovered at 20000ms");
+ assertTrue(process2.isCrashed(), "process 2 should crash immediately after 20000ms");
+ } finally {
+ loadedSimulator.getSimulatorCanvas().stopThread();
+ simulator.getSimulatorCanvas().stopThread();
+ }
}
@Test
@@ -140,4 +182,36 @@ class SimulationBuilderTest {
return count;
}
+
+ private void runUntil(VSSimulatorVisualization visualization, long targetTime)
+ throws Exception {
+ setBooleanField(visualization, "isPaused", false);
+ setBooleanField(visualization, "hasFinished", false);
+ setDoubleField(visualization, "clockSpeed", 1.0d);
+
+ Method updateSimulator = VSSimulatorVisualization.class.getDeclaredMethod(
+ "updateSimulator", long.class, long.class);
+ updateSimulator.setAccessible(true);
+
+ long wallTime = visualization.getTime();
+ while (visualization.getTime() < targetTime) {
+ long nextWallTime = wallTime + 1L;
+ updateSimulator.invoke(visualization, nextWallTime, wallTime);
+ wallTime = nextWallTime;
+ }
+ }
+
+ private void setBooleanField(Object target, String fieldName, boolean value)
+ throws Exception {
+ Field field = target.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.setBoolean(target, value);
+ }
+
+ private void setDoubleField(Object target, String fieldName, double value)
+ throws Exception {
+ Field field = target.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.setDouble(target, value);
+ }
}