summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-06-21 21:27:31 +0300
committerPaul Buetow <paul@buetow.org>2025-06-21 21:27:31 +0300
commit0841f0f9a1e3f3708d8c511a6290344e73607aab (patch)
tree4ae7fe60878b693d6975093acb491d977d247acd
parentce82046a11521b0537ac2150a07a4de54aec883a (diff)
Move test scripts to scripts/ directory and fix simulation completion
- Moved test-protocols.sh, test-quick.sh, test-verbose.sh to scripts/ - Updated references in README.md and docs/testing-guide.md - Fixed HeadlessSimulationRunner to properly run simulations to completion - Fixed message delivery timing (now respects 500-2000ms delays) - Added proper process time synchronization - Fixed HeadlessProtocolRunner to exit cleanly 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--README.md2
-rw-r--r--docs/testing-guide.md4
-rwxr-xr-xscripts/test-protocols.sh (renamed from test-protocols.sh)0
-rwxr-xr-xscripts/test-quick.sh21
-rwxr-xr-xscripts/test-verbose.sh (renamed from test-verbose.sh)5
-rw-r--r--src/main/java/simulator/engine/AbstractSimulationEngine.java21
-rw-r--r--src/main/java/simulator/engine/HeadlessSimulationEngine.java34
-rw-r--r--src/main/java/testing/HeadlessProtocolRunner.java7
-rw-r--r--src/main/java/testing/HeadlessSimulationRunner.java141
9 files changed, 177 insertions, 58 deletions
diff --git a/README.md b/README.md
index 9c1ac63..e7a7573 100644
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ mvn clean package -DskipTests
DS-Sim includes a framework for testing protocol simulations:
```bash
# Interactive test runner (Note: produces GUI errors in headless mode)
-./test-protocols.sh
+./scripts/test-protocols.sh
```
For detailed testing information, see [docs/testing-guide.md](docs/testing-guide.md).
diff --git a/docs/testing-guide.md b/docs/testing-guide.md
index b7f3182..b40c1bb 100644
--- a/docs/testing-guide.md
+++ b/docs/testing-guide.md
@@ -28,7 +28,7 @@ With the GUI decoupling implementation complete, protocol simulations now run cl
```bash
# Use the interactive test script
-./test-protocols.sh
+./scripts/test-protocols.sh
# Run all protocol tests directly
java -cp target/classes:target/test-classes -Djava.awt.headless=true \
@@ -181,7 +181,7 @@ All tests now work in CI/CD environments:
- name: Run all tests
run: |
mvn test
- ./test-protocols.sh
+ ./scripts/test-protocols.sh
```
## Troubleshooting
diff --git a/test-protocols.sh b/scripts/test-protocols.sh
index 2edbef9..2edbef9 100755
--- a/test-protocols.sh
+++ b/scripts/test-protocols.sh
diff --git a/scripts/test-quick.sh b/scripts/test-quick.sh
new file mode 100755
index 0000000..a37c399
--- /dev/null
+++ b/scripts/test-quick.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Quick test to verify simulation runs correctly
+#
+
+echo "Quick DS-Sim Protocol Test"
+echo "========================="
+echo
+
+# Test ping-pong with shorter timeout
+echo "Testing ping-pong protocol..."
+timeout 5 java -cp target/classes:target/test-classes \
+ -Djava.awt.headless=true \
+ -Dds.sim.headless=true \
+ testing.HeadlessProtocolRunner saved-simulations/ping-pong.dat
+
+if [ $? -eq 124 ]; then
+ echo "✗ Test timed out after 5 seconds"
+else
+ echo "✓ Test completed successfully"
+fi \ No newline at end of file
diff --git a/test-verbose.sh b/scripts/test-verbose.sh
index b8fbbe3..e3362b3 100755
--- a/test-verbose.sh
+++ b/scripts/test-verbose.sh
@@ -51,4 +51,7 @@ javac -cp target/classes /tmp/TestVerbose.java -d /tmp
java -cp /tmp:target/classes:target/test-classes -Djava.awt.headless=true TestVerbose "$@"
# Clean up
-rm -f /tmp/TestVerbose.java /tmp/TestVerbose.class \ No newline at end of file
+rm -f /tmp/TestVerbose.java /tmp/TestVerbose.class
+
+# Exit cleanly
+exit 0 \ No newline at end of file
diff --git a/src/main/java/simulator/engine/AbstractSimulationEngine.java b/src/main/java/simulator/engine/AbstractSimulationEngine.java
index 21be5c7..45b2522 100644
--- a/src/main/java/simulator/engine/AbstractSimulationEngine.java
+++ b/src/main/java/simulator/engine/AbstractSimulationEngine.java
@@ -16,7 +16,7 @@ public abstract class AbstractSimulationEngine implements SimulationEngine {
protected final VSPrefs prefs;
protected final List<VSInternalProcess> processes;
protected final List<SimulationVisualizer> visualizers;
- protected final VSTaskManager taskManager;
+ protected VSTaskManager taskManager;
protected VSLogging loging;
protected long time;
@@ -38,19 +38,16 @@ public abstract class AbstractSimulationEngine implements SimulationEngine {
@Override
public void sendMessage(VSMessage message) {
+ // Calculate proper delivery time
+ long deliveryTime = calculateDeliveryTime(message);
+
// Schedule message delivery to all processes (broadcast model)
- scheduleMessageDelivery(message, time);
+ scheduleMessageDelivery(message, deliveryTime);
// Notify visualizers
for (SimulationVisualizer visualizer : visualizers) {
visualizer.onMessageSent(message);
}
-
- // Log the message
- if (loging != null) {
- loging.log("Message sent; ID: " + message.getMessageID() +
- "; Protocol: " + message.getName());
- }
}
protected abstract long calculateDeliveryTime(VSMessage message);
@@ -100,6 +97,14 @@ public abstract class AbstractSimulationEngine implements SimulationEngine {
return taskManager;
}
+ /**
+ * Set the task manager for this engine.
+ * Used when integrating with existing simulation infrastructure.
+ */
+ public void setTaskManager(VSTaskManager taskManager) {
+ this.taskManager = taskManager;
+ }
+
@Override
public long getTime() {
return time;
diff --git a/src/main/java/simulator/engine/HeadlessSimulationEngine.java b/src/main/java/simulator/engine/HeadlessSimulationEngine.java
index 36a64a7..fa6dde8 100644
--- a/src/main/java/simulator/engine/HeadlessSimulationEngine.java
+++ b/src/main/java/simulator/engine/HeadlessSimulationEngine.java
@@ -24,23 +24,12 @@ public class HeadlessSimulationEngine extends AbstractSimulationEngine {
return time; // Deliver immediately if process not found
}
- // Calculate network delay
- long networkDelay = prefs.getLong("sim.network.delay");
- long variability = prefs.getLong("sim.network.variability");
+ // Use the process's getDurationTime method to get the message duration
+ // This respects the message.sendingtime.min and message.sendingtime.max preferences
+ long durationTime = source.getDurationTime();
- // Add random variability
- if (variability > 0) {
- long variance = (long)(Math.random() * variability * 2) - variability;
- networkDelay += variance;
- }
-
- // Ensure minimum delay
- if (networkDelay < 0) {
- networkDelay = 0;
- }
-
- // Calculate delivery time based on source process time
- return source.getTime() + networkDelay;
+ // Calculate delivery time based on source process's global time + duration
+ return source.getGlobalTime() + durationTime;
}
@Override
@@ -49,6 +38,13 @@ public class HeadlessSimulationEngine extends AbstractSimulationEngine {
VSInternalProcess sendingProcess = (VSInternalProcess) message.getSendingProcess();
boolean recvOwn = prefs.getBoolean("sim.message.own.recv");
+ // Debug logging
+ if (loging != null) {
+ loging.log("Message " + message.getMessageID() + " scheduled for delivery at time " +
+ deliveryTime + " (sent at globalTime=" + sendingProcess.getGlobalTime() +
+ ", duration=" + (deliveryTime - sendingProcess.getGlobalTime()) + "ms)");
+ }
+
// Schedule delivery to all processes
for (VSInternalProcess receiverProcess : processes) {
if (receiverProcess.equals(sendingProcess)) {
@@ -62,12 +58,6 @@ public class HeadlessSimulationEngine extends AbstractSimulationEngine {
VSMessageReceiveEvent receiveEvent = new VSMessageReceiveEvent(message);
VSTask task = new VSTask(deliveryTime, receiverProcess, receiveEvent, VSTask.GLOBAL);
taskManager.addTask(task);
-
- if (loging != null) {
- loging.log("Message scheduled for delivery to process " +
- receiverProcess.getProcessNum() + "; ID: " +
- message.getMessageID() + "; Time: " + deliveryTime);
- }
}
}
diff --git a/src/main/java/testing/HeadlessProtocolRunner.java b/src/main/java/testing/HeadlessProtocolRunner.java
index d68d743..daf96aa 100644
--- a/src/main/java/testing/HeadlessProtocolRunner.java
+++ b/src/main/java/testing/HeadlessProtocolRunner.java
@@ -38,7 +38,7 @@ public class HeadlessProtocolRunner {
try {
long startTime = System.currentTimeMillis();
- SimulationResult result = runner.runSimulation(simFile, 5000); // 5 second timeout
+ SimulationResult result = runner.runSimulation(simFile, 10000); // 10 second timeout
long duration = System.currentTimeMillis() - startTime;
if (verbose) {
@@ -59,6 +59,9 @@ public class HeadlessProtocolRunner {
} finally {
runner.shutdown();
}
+
+ // Exit explicitly when running single simulation
+ System.exit(0);
}
private static void runAllSimulations(boolean verbose) throws Exception {
@@ -87,7 +90,7 @@ public class HeadlessProtocolRunner {
try {
long startTime = System.currentTimeMillis();
- SimulationResult result = runner.runSimulation(simFile.getPath(), 3000); // 3 second timeout
+ SimulationResult result = runner.runSimulation(simFile.getPath(), 10000); // 10 second timeout
long duration = System.currentTimeMillis() - startTime;
System.out.println("✓ PASSED in " + duration + "ms");
diff --git a/src/main/java/testing/HeadlessSimulationRunner.java b/src/main/java/testing/HeadlessSimulationRunner.java
index ef50995..9d2274c 100644
--- a/src/main/java/testing/HeadlessSimulationRunner.java
+++ b/src/main/java/testing/HeadlessSimulationRunner.java
@@ -70,12 +70,16 @@ public class HeadlessSimulationRunner {
}
installLogCapture();
- System.out.println("Running simulation for " + maxTime + "ms...");
+ // Get the simulation's configured end time
+ long untilTime = viz.getUntilTime();
+ long actualMaxTime = Math.min(maxTime, untilTime);
+
+ System.out.println("Running simulation for up to " + actualMaxTime + "ms (until time: " + untilTime + "ms)...");
// Run simulation
Future<Void> runFuture = executor.submit(() -> {
try {
- runSimulationSteps(maxTime);
+ runSimulationSteps(actualMaxTime);
} catch (Exception e) {
System.err.println("Error during simulation: " + e.getMessage());
e.printStackTrace();
@@ -85,7 +89,7 @@ public class HeadlessSimulationRunner {
// Wait for completion or timeout
try {
- runFuture.get(maxTime * 2, TimeUnit.MILLISECONDS);
+ runFuture.get(actualMaxTime * 2, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
System.out.println("Simulation timeout - stopping...");
runFuture.cancel(true);
@@ -113,31 +117,126 @@ public class HeadlessSimulationRunner {
.getDeclaredField("time");
timeField.setAccessible(true);
- // Find runTasks method with correct signature
- Method runTasksMethod = VSTaskManager.class
- .getDeclaredMethod("runTasks", long.class, long.class, long.class);
- runTasksMethod.setAccessible(true);
+ // Get simulatorTime field for accurate time tracking
+ Field simulatorTimeField = VSSimulatorVisualization.class
+ .getDeclaredField("simulatorTime");
+ simulatorTimeField.setAccessible(true);
+
+ // Get isPaused and hasFinished fields
+ Field isPausedField = VSSimulatorVisualization.class
+ .getDeclaredField("isPaused");
+ isPausedField.setAccessible(true);
+ Field hasFinishedField = VSSimulatorVisualization.class
+ .getDeclaredField("hasFinished");
+ hasFinishedField.setAccessible(true);
+
+ // Get clockSpeed field and ensure it's set
+ Field clockSpeedField = VSSimulatorVisualization.class
+ .getDeclaredField("clockSpeed");
+ clockSpeedField.setAccessible(true);
+ double clockSpeed = clockSpeedField.getDouble(viz);
+ if (clockSpeed == 0) {
+ // Set default clock speed if not initialized
+ clockSpeed = 1.0;
+ clockSpeedField.setDouble(viz, clockSpeed);
+ }
+
+ // Get task queue fields for checking if tasks remain
+ Field globalTasksField = VSTaskManager.class.getDeclaredField("globalTasks");
+ globalTasksField.setAccessible(true);
+
+ // Set isPaused to false to allow simulation to run
+ isPausedField.setBoolean(viz, false);
+ hasFinishedField.setBoolean(viz, false);
long startTime = timeField.getLong(viz);
long currentTime = startTime;
+ long endTime = startTime + maxTime;
+ int noActivityCount = 0;
+ int lastLogCount = 0;
+ long lastActiveTime = 0;
- while (currentTime - startTime < maxTime) {
- // Update time
- timeField.setLong(viz, currentTime);
-
- // Sync process times
+ // Call updateSimulator method to advance simulation
+ Method updateSimulatorMethod = VSSimulatorVisualization.class
+ .getDeclaredMethod("updateSimulator", long.class, long.class);
+ updateSimulatorMethod.setAccessible(true);
+
+ System.out.println("Starting simulation at time " + startTime + ", running until " + endTime);
+
+ while (currentTime < endTime) {
+ // Sync all process times BEFORE running tasks
for (int i = 0; i < viz.getNumProcesses(); i++) {
- viz.getProcess(i).syncTime(currentTime);
+ VSInternalProcess process = viz.getProcess(i);
+ if (process != null) {
+ process.syncTime(currentTime);
+ }
}
- // Run tasks (step, offset, lastGlobalTime)
- runTasksMethod.invoke(taskManager, currentTime, 0L, currentTime - 1);
+ // Update simulation time - this also runs tasks internally
+ updateSimulatorMethod.invoke(viz, currentTime, currentTime - 1);
+ long simulatorTime = simulatorTimeField.getLong(viz);
+
+ // Check if there's been any activity
+ int currentLogCount = logCapture.getTotalLogCount();
+ boolean hasActivity = currentLogCount > lastLogCount ||
+ hasPendingActivity(taskManager, globalTasksField, simulatorTime);
+
+ if (hasActivity) {
+ noActivityCount = 0;
+ lastLogCount = currentLogCount;
+ lastActiveTime = currentTime;
+ } else {
+ noActivityCount++;
+ // If no activity for 3000ms (3 seconds) of simulation time, stop
+ // This accounts for message delivery times of 500-2000ms plus some buffer
+ if (noActivityCount > 3000 && (currentTime - lastActiveTime) > 3000) {
+ System.out.println("No activity detected for 3 seconds - simulation complete at time " + simulatorTime);
+ break;
+ }
+ }
// Advance time by 1ms
currentTime++;
+ timeField.setLong(viz, currentTime);
- // Small delay to prevent CPU spinning
- Thread.sleep(1);
+ // No delay needed - let simulation run at full speed
+ }
+
+ // Set isPaused back to true when done
+ isPausedField.setBoolean(viz, true);
+ }
+
+ private boolean hasPendingActivity(VSTaskManager taskManager, Field globalTasksField, long currentTime) {
+ try {
+ // Check global tasks
+ Queue<?> globalTasks = (Queue<?>) globalTasksField.get(taskManager);
+ if (globalTasks != null && !globalTasks.isEmpty()) {
+ return true; // If any global tasks exist, keep running
+ }
+
+ // Check process-specific tasks
+ for (int i = 0; i < viz.getNumProcesses(); i++) {
+ VSInternalProcess process = viz.getProcess(i);
+ if (process != null) {
+ Queue<VSTask> tasks = process.getTasks();
+ if (tasks != null && !tasks.isEmpty()) {
+ return true; // If any process tasks exist, keep running
+ }
+ }
+ }
+
+ // Check for messages in transit
+ Field messageLinesField = VSSimulatorVisualization.class.getDeclaredField("messageLines");
+ messageLinesField.setAccessible(true);
+ LinkedList<?> messageLines = (LinkedList<?>) messageLinesField.get(viz);
+ if (messageLines != null && !messageLines.isEmpty()) {
+ return true; // Messages are still being delivered
+ }
+
+ return false;
+ } catch (Exception e) {
+ // If we can't check, assume there might be activity
+ return true;
}
}
@@ -202,6 +301,9 @@ public class HeadlessSimulationRunner {
// Create a headless simulation engine
HeadlessSimulationEngine engine = new HeadlessSimulationEngine(prefs, logCapture);
+ // Set the task manager from the visualization
+ engine.setTaskManager(viz.getTaskManager());
+
// Copy processes to engine
for (int i = 0; i < viz.getNumProcesses(); i++) {
VSInternalProcess process = viz.getProcess(i);
@@ -213,10 +315,5 @@ public class HeadlessSimulationRunner {
process.setMessageHandler(handler);
}
}
-
- // Note: Task manager state is not copied because:
- // - Global tasks are in VSTaskManager.globalTasks
- // - Local tasks are stored in each VSInternalProcess.tasks
- // - The engine already has references to the processes which contain their tasks
}
} \ No newline at end of file