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

View File

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

View File

@@ -24,4 +24,20 @@ class SimulationProgressWatchdogTest {
assertFalse(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));
}
}