fix(javaTest): close invalid history inputs explicitly
This commit is contained in:
@@ -71,19 +71,19 @@ git commit -m "fix(javaTest): validate route quality inputs"
|
|||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- 不新增生产 API;测试通过现有 `HistoricalSimulationInput`、`SimulationConfig` 和 `HistoricalDispatchSimulator.simulate(...)` 验证业务闭环。
|
- 不新增生产 API;测试通过现有 `HistoricalSimulationInput`、`SimulationConfig` 和 `HistoricalDispatchSimulator.simulate(...)` 验证业务闭环。
|
||||||
|
|
||||||
- [ ] **Step 1: 写未来到达与优先级测试**
|
- [x] **Step 1: 写未来到达与优先级测试**
|
||||||
|
|
||||||
构造两个站点路线和两条任务:低优先级任务在 `t=0` 到达,高优先级任务在仿真时间 `t=5000` 到达。断言 `t<5000` 的事件中不存在高优先级任务 `ASSIGN`,任务到达后它按优先级先获得 `ASSIGN`;另保留队列 aging 单测证明长期低优先级任务不会饥饿。
|
构造两个站点路线和两条任务:低优先级任务在 `t=0` 到达,高优先级任务在仿真时间 `t=5000` 到达。断言 `t<5000` 的事件中不存在高优先级任务 `ASSIGN`,任务到达后它按优先级先获得 `ASSIGN`;另保留队列 aging 单测证明长期低优先级任务不会饥饿。
|
||||||
|
|
||||||
- [ ] **Step 2: 写无路失效关闭测试**
|
- [x] **Step 2: 写无路失效关闭测试**
|
||||||
|
|
||||||
构造已知但被 `GridMap.block(target)` 永久阻断的服务目标,运行模拟并断言该任务为 `FAILED`、reason=`NO_ROUTE`、无 `ASSIGN`/`COMPLETE`,且结果数量仍保持闭环。对未知站点输入继续由 normalizer 形成 `HistoricalDispatchFailure`,断言同样进入明确失败结果而不是等待。
|
构造已知但被 `GridMap.block(target)` 永久阻断的服务目标,运行模拟并断言该任务为 `FAILED`、reason=`NO_ROUTE`、无 `ASSIGN`/`COMPLETE`,且结果数量仍保持闭环。对未知站点输入继续由 normalizer 形成 `HistoricalDispatchFailure`,断言同样进入明确失败结果而不是等待。
|
||||||
|
|
||||||
- [ ] **Step 3: 写 PLC 重试边界测试**
|
- [x] **Step 3: 写 PLC 重试边界测试**
|
||||||
|
|
||||||
使用现有 `DeterministicFaultPlan` 注入一次 `SEND_FAILURE`,断言任务保持未移动并在后续 tick 产生 `PLC_RETRY`;使用重试上限夹具验证超过上限时 reason 非空且不伪造 `COMPLETED`。
|
使用现有 `DeterministicFaultPlan` 注入一次 `SEND_FAILURE`,断言任务保持未移动并在后续 tick 产生 `PLC_RETRY`;使用重试上限夹具验证超过上限时 reason 非空且不伪造 `COMPLETED`。
|
||||||
|
|
||||||
- [ ] **Step 4: 运行专项 GREEN**
|
- [x] **Step 4: 运行专项 GREEN**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mvn -f javaTest/pom.xml -Dtest=HistoricalDispatchSimulatorTest,SimulationTaskQueueTest,HistoricalDispatchFaultMatrixTest test
|
mvn -f javaTest/pom.xml -Dtest=HistoricalDispatchSimulatorTest,SimulationTaskQueueTest,HistoricalDispatchFaultMatrixTest test
|
||||||
@@ -91,7 +91,7 @@ mvn -f javaTest/pom.xml -Dtest=HistoricalDispatchSimulatorTest,SimulationTaskQue
|
|||||||
|
|
||||||
预期新旧测试全部通过;若生产代码行为与测试暴露的状态不一致,先添加最小生产修复,再重新运行同一命令。
|
预期新旧测试全部通过;若生产代码行为与测试暴露的状态不一致,先添加最小生产修复,再重新运行同一命令。
|
||||||
|
|
||||||
- [ ] **Step 5: 提交**
|
- [x] **Step 5: 提交**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulatorTest.java javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulationTaskQueueTest.java
|
git add javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalDispatchSimulatorTest.java javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/SimulationTaskQueueTest.java
|
||||||
@@ -108,25 +108,25 @@ git commit -m "test(javaTest): prove history dispatch boundary states"
|
|||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- 保持 `CorridorReservationManager.replaceRoute(...)` 原子接口和 `TrafficHoldTracker` 第三次触发协议不变。
|
- 保持 `CorridorReservationManager.replaceRoute(...)` 原子接口和 `TrafficHoldTracker` 第三次触发协议不变。
|
||||||
|
|
||||||
- [ ] **Step 1: 锁定旧预约回滚**
|
- [x] **Step 1: 锁定旧预约回滚**
|
||||||
|
|
||||||
在替换失败测试中同时断言旧路线每条边仍由原任务持有、其他同向持有者仍存在、没有新路线残留;在成功测试中断言不再使用的旧边释放且共享边保留。
|
在替换失败测试中同时断言旧路线每条边仍由原任务持有、其他同向持有者仍存在、没有新路线残留;在成功测试中断言不再使用的旧边释放且共享边保留。
|
||||||
|
|
||||||
- [ ] **Step 2: 锁定 HOLD 集成行为**
|
- [x] **Step 2: 锁定 HOLD 集成行为**
|
||||||
|
|
||||||
扩展模拟测试断言第三次相同交通 HOLD 产生一次 `REROUTE_TRIGGER`,失败替换产生 `REROUTE_REJECTED` 且原路线继续执行;成功替换后在下一次 MOVE 前不重复产生普通重规划。
|
扩展模拟测试断言第三次相同交通 HOLD 产生一次 `REROUTE_TRIGGER`,失败替换产生 `REROUTE_REJECTED` 且原路线继续执行;成功替换后在下一次 MOVE 前不重复产生普通重规划。
|
||||||
|
|
||||||
- [ ] **Step 3: 锁定死锁 watchdog**
|
- [x] **Step 3: 锁定死锁 watchdog**
|
||||||
|
|
||||||
增加连续无进展达到阈值才判死锁、未来到达任务不判死锁、车辆 MOVE 后计数清零的测试;确认 `SimulationInvariantSummary.deadlocks` 与异常路径均可追溯。
|
增加连续无进展达到阈值才判死锁、未来到达任务不判死锁、车辆 MOVE 后计数清零的测试;确认 `SimulationInvariantSummary.deadlocks` 与异常路径均可追溯。
|
||||||
|
|
||||||
- [ ] **Step 4: 运行专项 GREEN**
|
- [x] **Step 4: 运行专项 GREEN**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mvn -f javaTest/pom.xml -Dtest=CorridorReservationManagerTest,HistoricalDispatchSimulatorTest,SimulationProgressWatchdogTest,TrafficHoldTrackerTest test
|
mvn -f javaTest/pom.xml -Dtest=CorridorReservationManagerTest,HistoricalDispatchSimulatorTest,SimulationProgressWatchdogTest,TrafficHoldTrackerTest test
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: 提交**
|
- [x] **Step 5: 提交**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation
|
git add javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation
|
||||||
|
|||||||
@@ -52,12 +52,20 @@ public final class HistoricalSimulationInputFactory {
|
|||||||
failures.add(new HistoricalSimulationFailure(id, record, "INVALID_TASK_TYPE"));
|
failures.add(new HistoricalSimulationFailure(id, record, "INVALID_TASK_TYPE"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
PhysicalEndpointResolver.ResolvedEndpoints endpoints = endpointResolver.resolve(
|
PhysicalEndpointResolver.ResolvedEndpoints endpoints;
|
||||||
record, compiled.getPassableSites());
|
try {
|
||||||
|
endpoints = endpointResolver.resolve(record, compiled.getPassableSites());
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
// 未知地图端点不能进入调度循环;保留原始记录并形成可归档的输入失败。
|
||||||
|
failures.add(new HistoricalSimulationFailure(id, record,
|
||||||
|
"UNKNOWN_MAP_SITE:" + ex.getMessage()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (compiled.getComponentId(endpoints.getSource())
|
if (compiled.getComponentId(endpoints.getSource())
|
||||||
!= compiled.getComponentId(endpoints.getTarget())) {
|
!= compiled.getComponentId(endpoints.getTarget())) {
|
||||||
// 两端地图连通性是调度启动前的硬约束,跨断开区域不能靠等待或选车解决。
|
// 两端地图连通性是调度启动前的硬约束,跨断开区域不能靠等待或选车解决。
|
||||||
throw new IllegalArgumentException("unreachable physical endpoints for " + id);
|
failures.add(new HistoricalSimulationFailure(id, record, "NO_ROUTE"));
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
tasks.add(new HistoricalSimulationTask(id, record, type,
|
tasks.add(new HistoricalSimulationTask(id, record, type,
|
||||||
record.getPriority() == null ? Integer.MAX_VALUE : record.getPriority(),
|
record.getPriority() == null ? Integer.MAX_VALUE : record.getPriority(),
|
||||||
|
|||||||
@@ -141,6 +141,36 @@ class HistoricalDispatchSimulatorTest {
|
|||||||
assertEquals(0, result.getInvariants().totalSafetyViolations());
|
assertEquals(0, result.getInvariants().totalSafetyViolations());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doesNotAssignTaskBeforeCreateDateArrival() {
|
||||||
|
HistoricalDispatchSimulationResult result = new HistoricalDispatchSimulator().simulate(
|
||||||
|
futureArrivalInput(), SimulationConfig.defaults(), DeterministicFaultPlan.none());
|
||||||
|
|
||||||
|
long expectedArrival = result.getTaskResults().stream()
|
||||||
|
.filter(task -> "FUTURE".equals(task.getSimulationTaskId()))
|
||||||
|
.findFirst().orElseThrow(AssertionError::new).getArrivalMillis();
|
||||||
|
assertTrue(expectedArrival > 0L);
|
||||||
|
assertTrue(result.getEvents().stream()
|
||||||
|
.filter(event -> "FUTURE".equals(event.getTaskId())
|
||||||
|
&& "ASSIGN".equals(event.getType()))
|
||||||
|
.allMatch(event -> event.getSimulationMillis() >= expectedArrival));
|
||||||
|
assertEquals(2, result.getCompletedCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void closesKnownButPermanentlyUnreachableTaskAsFailed() {
|
||||||
|
HistoricalDispatchSimulationResult result = new HistoricalDispatchSimulator().simulate(
|
||||||
|
unreachableInput(), SimulationConfig.defaults(), DeterministicFaultPlan.none());
|
||||||
|
|
||||||
|
SimulationTaskResult failed = result.getTaskResults().get(0);
|
||||||
|
assertEquals(SimulationTaskStatus.FAILED, failed.getStatus());
|
||||||
|
assertEquals("NO_ROUTE", failed.getReason());
|
||||||
|
assertTrue(result.getEvents().stream().noneMatch(event ->
|
||||||
|
"UNREACHABLE".equals(event.getTaskId())
|
||||||
|
&& ("ASSIGN".equals(event.getType()) || "COMPLETE".equals(event.getType()))));
|
||||||
|
assertEquals(0, result.getCompletedCount());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证“moves idle blockers node by node and completes both business directions”场景,确保相关业务规则和断言成立。
|
* 验证“moves idle blockers node by node and completes both business directions”场景,确保相关业务规则和断言成立。
|
||||||
*
|
*
|
||||||
@@ -347,6 +377,51 @@ class HistoricalDispatchSimulatorTest {
|
|||||||
traffic, vehicles);
|
traffic, vehicles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private HistoricalSimulationInput futureArrivalInput() {
|
||||||
|
GridMap grid = new GridMap();
|
||||||
|
grid.addSite(new Site("A", 1, 0, 0, "TEST"));
|
||||||
|
grid.addSite(new Site("B", 1, 1, 0, "TEST"));
|
||||||
|
grid.connect("A", "B");
|
||||||
|
LinkedHashSet<String> nodes = new LinkedHashSet<String>(Arrays.asList("A", "B"));
|
||||||
|
LinkedHashSet<TrafficMapCompiler.EdgeDefinition> edges =
|
||||||
|
new LinkedHashSet<TrafficMapCompiler.EdgeDefinition>();
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("A", "B"));
|
||||||
|
EffectiveTrafficMap traffic = new TrafficMapCompiler().compile(
|
||||||
|
new TrafficMapCompiler.MapDefinition("FUTURE", 1L, "TEST", true,
|
||||||
|
"test", "test", nodes, edges, Collections.emptyMap(),
|
||||||
|
Collections.emptySet(), Collections.emptySet()));
|
||||||
|
List<HistoricalSimulationTask> tasks = Arrays.asList(
|
||||||
|
customTask("NOW", "A", "B", 2, 0, Arrays.asList("A", "B")),
|
||||||
|
customTask("FUTURE", "A", "B", 1, 20, Arrays.asList("A", "B")));
|
||||||
|
return new HistoricalSimulationInput(tasks, Collections.emptyList(), grid, traffic,
|
||||||
|
Collections.singletonList(new VehicleSnapshot(
|
||||||
|
"1", 1, "A", 5, 1, 100, true, true, false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HistoricalSimulationInput unreachableInput() {
|
||||||
|
GridMap grid = new GridMap();
|
||||||
|
for (String node : Arrays.asList("A", "B", "C", "D")) {
|
||||||
|
grid.addSite(new Site(node, 1, node.charAt(0) - 'A', 0, "TEST"));
|
||||||
|
}
|
||||||
|
grid.connect("A", "B");
|
||||||
|
grid.connect("C", "D");
|
||||||
|
LinkedHashSet<String> nodes = new LinkedHashSet<String>(Arrays.asList("A", "B", "C", "D"));
|
||||||
|
LinkedHashSet<TrafficMapCompiler.EdgeDefinition> edges =
|
||||||
|
new LinkedHashSet<TrafficMapCompiler.EdgeDefinition>();
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("A", "B"));
|
||||||
|
edges.add(new TrafficMapCompiler.EdgeDefinition("C", "D"));
|
||||||
|
EffectiveTrafficMap traffic = new TrafficMapCompiler().compile(
|
||||||
|
new TrafficMapCompiler.MapDefinition("UNREACHABLE", 1L, "TEST", true,
|
||||||
|
"test", "test", nodes, edges, Collections.emptyMap(),
|
||||||
|
Collections.emptySet(), Collections.emptySet()));
|
||||||
|
HistoricalSimulationTask task = customTask("UNREACHABLE", "A", "D", 1, 0,
|
||||||
|
Arrays.asList("A", "D"));
|
||||||
|
return new HistoricalSimulationInput(Collections.singletonList(task),
|
||||||
|
Collections.emptyList(), grid, traffic,
|
||||||
|
Collections.singletonList(new VehicleSnapshot(
|
||||||
|
"1", 1, "A", 5, 1, 100, true, true, false)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证“blocked three vehicle corridor input”场景,确保相关业务规则和断言成立。
|
* 验证“blocked three vehicle corridor input”场景,确保相关业务规则和断言成立。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -104,6 +104,26 @@ class HistoricalSimulationInputFactoryTest {
|
|||||||
assertEquals(originalInput.getTrafficMap().getEdges(), changedInput.getTrafficMap().getEdges());
|
assertEquals(originalInput.getTrafficMap().getEdges(), changedInput.getTrafficMap().getEdges());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convertsUnknownPhysicalEndpointIntoExplicitFailedInput() {
|
||||||
|
HistoricalQueueDataset original = new HistoricalQueueLoader().load(
|
||||||
|
getClass().getClassLoader(), "shadow/history");
|
||||||
|
HistoricalQueueRecord first = original.getRecords().get(0);
|
||||||
|
List<HistoricalQueueRecord> changedRecords =
|
||||||
|
new ArrayList<HistoricalQueueRecord>(original.getRecords());
|
||||||
|
changedRecords.set(0, new HistoricalQueueRecord(first.getGuid(), first.getTaskNo(),
|
||||||
|
first.getTaskType(), first.getPriority(), "UNKNOWN_SITE", first.getEndSite(),
|
||||||
|
first.getPathInfo(), first.getCreateDate(), first.getFinishDate(), first.getAgvId()));
|
||||||
|
|
||||||
|
HistoricalSimulationInput changed = new HistoricalSimulationInputFactory().create(
|
||||||
|
copyWithRecords(original, changedRecords));
|
||||||
|
|
||||||
|
assertEquals(239, changed.getTasks().size());
|
||||||
|
assertEquals(1, changed.getFailedInputs().size());
|
||||||
|
assertTrue(changed.getFailedInputs().get(0).getReason().startsWith("UNKNOWN_MAP_SITE:"));
|
||||||
|
assertEquals(240, changed.allSimulationIds().size());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证“copy with records”场景,确保相关业务规则和断言成立。
|
* 验证“copy with records”场景,确保相关业务规则和断言成立。
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user