test(javaTest): require all history tasks to complete

This commit is contained in:
zgw
2026-07-31 14:34:38 +08:00
parent cd2e6ce953
commit 35d8fb6ec4
4 changed files with 72 additions and 21 deletions

View File

@@ -90,26 +90,33 @@ public final class HistoricalDispatchSimulator {
clearanceMoves, executions, terminal, planner, assignment, plc, clearanceMoves, executions, terminal, planner, assignment, plc,
reservations, events); reservations, events);
Set<String> traversedEdges = new LinkedHashSet<String>(); Set<String> traversedEdges = new LinkedHashSet<String>();
advanceClearanceMoves(tick, now, input, config, vehicles, clearanceMoves, ClearanceProgress clearanceProgress = advanceClearanceMoves(
tick, now, input, config, vehicles, clearanceMoves,
ledger, plc, events, traversedEdges); ledger, plc, events, traversedEdges);
advanceActiveTasks(tick, now, input, config, queue, vehicles, boolean businessProgress = advanceActiveTasks(
tick, now, input, config, queue, vehicles,
active, executions, terminal, planner, ledger, plc, archive, wms, active, executions, terminal, planner, ledger, plc, archive, wms,
invariants, reservations, clearanceMoves, clearancePlanner, invariants, reservations, clearanceMoves, clearancePlanner,
events, traversedEdges); events, traversedEdges);
verifyLedger(ledger, invariants); verifyLedger(ledger, invariants);
if (watchdog.observe(terminal.size())) { if (watchdog.observe(terminal.size(),
businessProgress || clearanceProgress.moved,
clearanceProgress.completed)) {
invariants.deadlock(); invariants.deadlock();
snapshotWaiting(queue, active, executions, terminal, plc, throw new IllegalStateException("simulation made no physical progress at tick "
"DEADLOCK_TIMEOUT", now); + tick + ", terminal=" + terminal.size()
break; + ", queued=" + queue.size() + ", active=" + active.keySet()
+ ", clearance=" + clearanceMoves.keySet());
} }
now += STEP_MILLIS; now += STEP_MILLIS;
tick++; tick++;
} }
if (terminal.size() < expected) { if (terminal.size() < expected) {
snapshotWaiting(queue, active, executions, terminal, plc, throw new IllegalStateException("simulation tick limit reached: tick=" + tick
"TICK_LIMIT", now); + ", terminal=" + terminal.size() + ", expected=" + expected
+ ", queued=" + queue.size() + ", active=" + active.keySet()
+ ", clearance=" + clearanceMoves.keySet());
} }
List<SimulationTaskResult> results = new ArrayList<SimulationTaskResult>(terminal.values()); List<SimulationTaskResult> results = new ArrayList<SimulationTaskResult>(terminal.values());
@@ -212,7 +219,6 @@ public final class HistoricalDispatchSimulator {
terminal.put(task.getSimulationTaskId(), result(executions.get( terminal.put(task.getSimulationTaskId(), result(executions.get(
task.getSimulationTaskId()), SimulationTaskStatus.FAILED, task.getSimulationTaskId()), SimulationTaskStatus.FAILED,
"NO_ROUTE", plc, now)); "NO_ROUTE", plc, now));
progress = true;
continue; continue;
} }
@@ -398,7 +404,6 @@ public final class HistoricalDispatchSimulator {
current.execution.task.getSimulationTaskId(), vehicleId, current.execution.task.getSimulationTaskId(), vehicleId,
"RETRY_EXHAUSTED_REDISPATCH", from, to)); "RETRY_EXHAUSTED_REDISPATCH", from, to));
} }
progress = true;
continue; continue;
} }
Site nextSite = input.getGridMap().getSite(to); Site nextSite = input.getGridMap().getSite(to);
@@ -421,7 +426,7 @@ public final class HistoricalDispatchSimulator {
return progress; return progress;
} }
private void advanceClearanceMoves(long tick, long now, private ClearanceProgress advanceClearanceMoves(long tick, long now,
HistoricalSimulationInput input, HistoricalSimulationInput input,
SimulationConfig config, SimulationConfig config,
Map<String, SimulatedVehicleRuntime> vehicles, Map<String, SimulatedVehicleRuntime> vehicles,
@@ -430,6 +435,8 @@ public final class HistoricalDispatchSimulator {
SimulatedPlcGateway plc, SimulatedPlcGateway plc,
List<SimulationTickEvent> events, List<SimulationTickEvent> events,
Set<String> traversedEdges) { Set<String> traversedEdges) {
boolean moved = false;
boolean completed = false;
List<String> vehicleIds = new ArrayList<String>(clearanceMoves.keySet()); List<String> vehicleIds = new ArrayList<String>(clearanceMoves.keySet());
Collections.sort(vehicleIds); Collections.sort(vehicleIds);
for (String vehicleId : vehicleIds) { for (String vehicleId : vehicleIds) {
@@ -495,15 +502,18 @@ public final class HistoricalDispatchSimulator {
ledger.release(vehicleId, Arrays.asList(ResourceKey.node(from), edge)); ledger.release(vehicleId, Arrays.asList(ResourceKey.node(from), edge));
traversedEdges.add(direction); traversedEdges.add(direction);
clearance.moved(); clearance.moved();
moved = true;
events.add(event(tick, now, "CLEARANCE_MOVE", clearance.getOwnerTaskId(), events.add(event(tick, now, "CLEARANCE_MOVE", clearance.getOwnerTaskId(),
vehicleId, "ACK", from, to)); vehicleId, "ACK", from, to));
if (clearance.isComplete()) { if (clearance.isComplete()) {
clearanceMoves.remove(vehicleId); clearanceMoves.remove(vehicleId);
completed = true;
events.add(event(tick, now, "CLEARANCE_COMPLETE", events.add(event(tick, now, "CLEARANCE_COMPLETE",
clearance.getOwnerTaskId(), vehicleId, "ROUTE_CLEARED", clearance.getOwnerTaskId(), vehicleId, "ROUTE_CLEARED",
to, to)); to, to));
} }
} }
return new ClearanceProgress(moved, completed);
} }
private boolean planClearanceForIdleBlockers( private boolean planClearanceForIdleBlockers(
@@ -714,4 +724,14 @@ public final class HistoricalDispatchSimulator {
this.route = Collections.unmodifiableList(new ArrayList<String>(route)); this.route = Collections.unmodifiableList(new ArrayList<String>(route));
} }
} }
private static final class ClearanceProgress {
private final boolean moved;
private final boolean completed;
private ClearanceProgress(boolean moved, boolean completed) {
this.moved = moved;
this.completed = completed;
}
}
} }

View File

@@ -14,10 +14,15 @@ public final class SimulationProgressWatchdog {
} }
public boolean observe(int terminalCount) { public boolean observe(int terminalCount) {
return observe(terminalCount, false, false);
}
public boolean observe(int terminalCount, boolean vehicleMoved,
boolean clearanceCompleted) {
if (terminalCount < lastTerminalCount) { if (terminalCount < lastTerminalCount) {
throw new IllegalArgumentException("terminal count must be monotonic"); throw new IllegalArgumentException("terminal count must be monotonic");
} }
if (terminalCount > lastTerminalCount) { if (terminalCount > lastTerminalCount || vehicleMoved || clearanceCompleted) {
lastTerminalCount = terminalCount; lastTerminalCount = terminalCount;
unchangedEvents = 0; unchangedEvents = 0;
return false; return false;

View File

@@ -18,12 +18,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class HistoricalDispatchSimulationRealDataTest { class HistoricalDispatchSimulationRealDataTest {
private static HistoricalDispatchSimulationResult result; private static HistoricalDispatchSimulationResult result;
private static HistoricalDispatchSimulationResult secondRun; private static HistoricalDispatchSimulationResult secondRun;
private static HistoricalSimulationInput simulationInput;
@BeforeAll @BeforeAll
static void simulateRealHistoryTwice() { static void simulateRealHistoryTwice() {
HistoricalQueueDataset dataset = new HistoricalQueueLoader().load( HistoricalQueueDataset dataset = new HistoricalQueueLoader().load(
HistoricalDispatchSimulationRealDataTest.class.getClassLoader(), "shadow/history"); HistoricalDispatchSimulationRealDataTest.class.getClassLoader(), "shadow/history");
HistoricalSimulationInput input = new HistoricalSimulationInputFactory().create(dataset); HistoricalSimulationInput input = new HistoricalSimulationInputFactory().create(dataset);
simulationInput = input;
DeterministicFaultPlan.Builder faults = DeterministicFaultPlan.builder(); DeterministicFaultPlan.Builder faults = DeterministicFaultPlan.builder();
for (int i = 0; i < input.getTasks().size(); i += 37) { for (int i = 0; i < input.getTasks().size(); i += 37) {
faults.plcFailure(input.getTasks().get(i).getSimulationTaskId(), 1, SEND_FAILURE); faults.plcFailure(input.getTasks().get(i).getSimulationTaskId(), 1, SEND_FAILURE);
@@ -46,22 +48,30 @@ class HistoricalDispatchSimulationRealDataTest {
assertNotNull(task.getRecord().getTaskNo()); assertNotNull(task.getRecord().getTaskNo());
assertNotNull(task.getRecord().getStartSite()); assertNotNull(task.getRecord().getStartSite());
assertNotNull(task.getRecord().getEndSite()); assertNotNull(task.getRecord().getEndSite());
for (int i = 1; i < task.getPlannedPath().size(); i++) {
assertTrue(simulationInput.getGridMap()
.getNeighbors(task.getPlannedPath().get(i - 1))
.contains(task.getPlannedPath().get(i)));
}
} }
@Test @Test
void completesTheRealWorkloadDeterministicallyWithoutSafetyViolations() { void completesTheRealWorkloadDeterministicallyWithoutSafetyViolations() {
assertEquals(240, result.getTaskResults().size()); assertEquals(240, result.getTaskResults().size());
assertEquals(240, result.getCompletedCount() assertEquals(240, result.getCompletedCount());
+ result.getWaitingCount() + result.getFailedCount()); assertEquals(0, result.getWaitingCount());
assertTrue(result.getCompletedCount() > 0); assertEquals(0, result.getFailedCount());
if (result.getWaitingCount() > 0) {
assertTrue(result.getInvariants().getDeadlocks() > 0);
}
assertTrue(result.getReassignmentCount() > 0); assertTrue(result.getReassignmentCount() > 0);
assertTrue(result.getRetryCount() > 0); assertTrue(result.getRetryCount() > 0);
assertEquals(result.getCompletedCount(), result.getArchiveCount()); assertEquals(240, result.getArchiveCount());
assertEquals(result.getCompletedCount(), result.getWmsBusinessEffects()); assertEquals(240, result.getWmsBusinessEffects());
assertEquals(result.getCompletedCount() * 2, result.getWmsDeliveryAttempts()); assertEquals(480, result.getWmsDeliveryAttempts());
assertEquals(0, result.getInvariants().getDeadlocks());
assertEquals(0, result.getInvariants().getCapacityViolations());
assertEquals(0, result.getInvariants().getNodeDoubleOccupancyViolations());
assertEquals(0, result.getInvariants().getOppositeEdgeViolations());
assertEquals(0, result.getInvariants().getDuplicateArchiveAttempts());
assertEquals(0, result.getInvariants().getDuplicateWmsBusinessEffects());
assertEquals(0, result.getInvariants().totalSafetyViolations()); assertEquals(0, result.getInvariants().totalSafetyViolations());
assertEquals(result.canonicalView(), secondRun.canonicalView()); assertEquals(result.canonicalView(), secondRun.canonicalView());
} }

View File

@@ -24,4 +24,20 @@ class SimulationProgressWatchdogTest {
assertFalse(watchdog.observe(1)); assertFalse(watchdog.observe(1));
assertTrue(watchdog.observe(1)); assertTrue(watchdog.observe(1));
} }
@Test
void resetsOnlyForTerminalMovementOrCompletedClearanceProgress() {
SimulationProgressWatchdog watchdog = new SimulationProgressWatchdog(3, 0);
assertFalse(watchdog.observe(0, false, false));
assertFalse(watchdog.observe(0, false, false));
assertFalse(watchdog.observe(0, true, false));
assertFalse(watchdog.observe(0, false, false));
assertFalse(watchdog.observe(0, false, true));
assertFalse(watchdog.observe(0, false, false));
assertFalse(watchdog.observe(1, false, false));
assertFalse(watchdog.observe(1, false, false));
assertFalse(watchdog.observe(1, false, false));
assertTrue(watchdog.observe(1, false, false));
}
} }