From 0841f0f9a1e3f3708d8c511a6290344e73607aab Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 21 Jun 2025 21:27:31 +0300 Subject: Move test scripts to scripts/ directory and fix simulation completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 2 +- docs/testing-guide.md | 4 +- scripts/test-protocols.sh | 70 ++++++++++ scripts/test-quick.sh | 21 +++ scripts/test-verbose.sh | 57 +++++++++ .../simulator/engine/AbstractSimulationEngine.java | 21 +-- .../simulator/engine/HeadlessSimulationEngine.java | 34 ++--- src/main/java/testing/HeadlessProtocolRunner.java | 7 +- .../java/testing/HeadlessSimulationRunner.java | 141 +++++++++++++++++---- test-protocols.sh | 70 ---------- test-verbose.sh | 54 -------- 11 files changed, 300 insertions(+), 181 deletions(-) create mode 100755 scripts/test-protocols.sh create mode 100755 scripts/test-quick.sh create mode 100755 scripts/test-verbose.sh delete mode 100755 test-protocols.sh delete mode 100755 test-verbose.sh 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/scripts/test-protocols.sh b/scripts/test-protocols.sh new file mode 100755 index 0000000..2edbef9 --- /dev/null +++ b/scripts/test-protocols.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# +# DS-Sim Protocol Test Runner +# +# This script runs protocol simulation tests in headless mode. +# GUI decoupling has been implemented, so tests run cleanly without GUI errors. +# + +echo "DS-Sim Protocol Test Runner" +echo "==========================" +echo + +# Check if we're in the right directory +if [ ! -f "pom.xml" ]; then + echo "ERROR: Please run this script from the project root directory" + exit 1 +fi + +# Build if needed +if [ ! -d "target/classes" ]; then + echo "Building project..." + mvn compile -q || { echo "Build failed!"; exit 1; } +fi + +# Menu +echo "Choose an option:" +echo "1) Run all protocol tests" +echo "2) Run specific protocol test" +echo "3) Run tests with detailed logs" +echo "4) Test GUI decoupling (verify no GUI errors)" +echo "5) Exit" +echo + +read -p "Enter choice [1-5]: " choice + +case $choice in + 1) + echo "Running all protocol tests..." + java -cp target/classes:target/test-classes -Djava.awt.headless=true testing.HeadlessProtocolRunner + ;; + 2) + echo "Available simulations:" + ls saved-simulations/*.dat | sed 's/saved-simulations\// - /g' + echo + read -p "Enter simulation name (without .dat): " sim + if [ -f "saved-simulations/${sim}.dat" ]; then + java -cp target/classes:target/test-classes -Djava.awt.headless=true \ + testing.HeadlessProtocolRunner "saved-simulations/${sim}.dat" + else + echo "Simulation not found!" + fi + ;; + 3) + echo "Running tests with detailed logs..." + java -cp target/classes:target/test-classes -Djava.awt.headless=true \ + -Dds.sim.verbose=true testing.HeadlessProtocolRunner + ;; + 4) + echo "Testing GUI decoupling..." + java -cp target/classes:target/test-classes testing.TestNoGuiErrors + ;; + 5) + echo "Exiting..." + exit 0 + ;; + *) + echo "Invalid choice!" + exit 1 + ;; +esac \ No newline at end of file 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/scripts/test-verbose.sh b/scripts/test-verbose.sh new file mode 100755 index 0000000..e3362b3 --- /dev/null +++ b/scripts/test-verbose.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# +# Test verbose logging for DS-Sim protocols +# + +echo "DS-Sim Verbose Logging Test" +echo "==========================" +echo +echo "This demonstrates real-time logging during protocol simulation." +echo + +# Compile if needed +if [ ! -d "target/classes" ]; then + echo "Building project..." + mvn compile -q || { echo "Build failed!"; exit 1; } +fi + +# Run ping-pong for just 2 seconds with verbose output +echo "Running ping-pong protocol for 2 seconds with verbose output..." +echo + +# Create a simple test that runs for a limited time +cat > /tmp/TestVerbose.java << 'EOF' +import testing.*; + +public class TestVerbose { + public static void main(String[] args) throws Exception { + String simFile = args.length > 0 ? args[0] : "saved-simulations/ping-pong.dat"; + int duration = args.length > 1 ? Integer.parseInt(args[1]) : 2000; + + System.out.println("Loading: " + simFile); + System.out.println("Duration: " + duration + "ms"); + System.out.println("\n--- Real-Time Log Output ---\n"); + + HeadlessSimulationRunner runner = new HeadlessSimulationRunner(); + runner.setPrintLogs(true); + + try { + SimulationResult result = runner.runSimulation(simFile, duration); + System.out.println("\n--- Simulation Complete ---"); + System.out.println("Total events: " + result.getAllLogs().size()); + } finally { + runner.shutdown(); + } + } +} +EOF + +# Compile and run the test +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 + +# 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 processes; protected final List 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 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 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 diff --git a/test-protocols.sh b/test-protocols.sh deleted file mode 100755 index 2edbef9..0000000 --- a/test-protocols.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -# -# DS-Sim Protocol Test Runner -# -# This script runs protocol simulation tests in headless mode. -# GUI decoupling has been implemented, so tests run cleanly without GUI errors. -# - -echo "DS-Sim Protocol Test Runner" -echo "==========================" -echo - -# Check if we're in the right directory -if [ ! -f "pom.xml" ]; then - echo "ERROR: Please run this script from the project root directory" - exit 1 -fi - -# Build if needed -if [ ! -d "target/classes" ]; then - echo "Building project..." - mvn compile -q || { echo "Build failed!"; exit 1; } -fi - -# Menu -echo "Choose an option:" -echo "1) Run all protocol tests" -echo "2) Run specific protocol test" -echo "3) Run tests with detailed logs" -echo "4) Test GUI decoupling (verify no GUI errors)" -echo "5) Exit" -echo - -read -p "Enter choice [1-5]: " choice - -case $choice in - 1) - echo "Running all protocol tests..." - java -cp target/classes:target/test-classes -Djava.awt.headless=true testing.HeadlessProtocolRunner - ;; - 2) - echo "Available simulations:" - ls saved-simulations/*.dat | sed 's/saved-simulations\// - /g' - echo - read -p "Enter simulation name (without .dat): " sim - if [ -f "saved-simulations/${sim}.dat" ]; then - java -cp target/classes:target/test-classes -Djava.awt.headless=true \ - testing.HeadlessProtocolRunner "saved-simulations/${sim}.dat" - else - echo "Simulation not found!" - fi - ;; - 3) - echo "Running tests with detailed logs..." - java -cp target/classes:target/test-classes -Djava.awt.headless=true \ - -Dds.sim.verbose=true testing.HeadlessProtocolRunner - ;; - 4) - echo "Testing GUI decoupling..." - java -cp target/classes:target/test-classes testing.TestNoGuiErrors - ;; - 5) - echo "Exiting..." - exit 0 - ;; - *) - echo "Invalid choice!" - exit 1 - ;; -esac \ No newline at end of file diff --git a/test-verbose.sh b/test-verbose.sh deleted file mode 100755 index b8fbbe3..0000000 --- a/test-verbose.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# -# Test verbose logging for DS-Sim protocols -# - -echo "DS-Sim Verbose Logging Test" -echo "==========================" -echo -echo "This demonstrates real-time logging during protocol simulation." -echo - -# Compile if needed -if [ ! -d "target/classes" ]; then - echo "Building project..." - mvn compile -q || { echo "Build failed!"; exit 1; } -fi - -# Run ping-pong for just 2 seconds with verbose output -echo "Running ping-pong protocol for 2 seconds with verbose output..." -echo - -# Create a simple test that runs for a limited time -cat > /tmp/TestVerbose.java << 'EOF' -import testing.*; - -public class TestVerbose { - public static void main(String[] args) throws Exception { - String simFile = args.length > 0 ? args[0] : "saved-simulations/ping-pong.dat"; - int duration = args.length > 1 ? Integer.parseInt(args[1]) : 2000; - - System.out.println("Loading: " + simFile); - System.out.println("Duration: " + duration + "ms"); - System.out.println("\n--- Real-Time Log Output ---\n"); - - HeadlessSimulationRunner runner = new HeadlessSimulationRunner(); - runner.setPrintLogs(true); - - try { - SimulationResult result = runner.runSimulation(simFile, duration); - System.out.println("\n--- Simulation Complete ---"); - System.out.println("Total events: " + result.getAllLogs().size()); - } finally { - runner.shutdown(); - } - } -} -EOF - -# Compile and run the test -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 -- cgit v1.2.3