Files
mcm-26-b/tests/test_hrl_scheduler_revision.py
T
2026-09-11 21:00:39 +08:00

87 lines
3.3 KiB
Python

"""HRL 协议执行的分支回归;所有模拟器交互仅在本地进行。"""
from unittest.mock import patch
import pytest
pytest.importorskip("numpy")
from mcm26b.config import ProblemConfig
from mcm26b.harness import LocalSimTransport
from mcm26b.hrl.constants import FALLBACK_OFFSET, N_CANDIDATES, STATION_OFFSET
from mcm26b.hrl.scheduler import Candidate
from mcm26b.planner.hrl_loop import HRLLoop
from mcm26b.protocol import SimulatorClient
from referee import LocalSimulator, Source
def make_loop(*, problem=4, high_policy=None):
simulator = LocalSimulator(
[Source(channel=20, x=45.0, y=0.0, r_eff_m=1000.0)],
robot_id="hrl-branch-test")
client = SimulatorClient("hrl-branch-test", transport=LocalSimTransport(simulator))
client.enter()
return HRLLoop(client, ProblemConfig(), problem=problem, high_policy=high_policy)
def test_fallback_failure_then_success_records_actual_time_position_and_clear():
loop = make_loop()
try:
observation = loop.client.measure((0.0, 0.0), 20)
loop._record_measure(20, (0.0, 0.0), observation)
loop.blocked_probes[20] = [(0.0, 0.0)]
candidate = Candidate(FALLBACK_OFFSET + 19, "fallback", (999.0, 999.0),
channel=20)
with patch("mcm26b.hrl.scheduler.fallback_clear_cells",
return_value=[(20.0, 0.0), (40.0, 0.0)]):
outcome = loop.hrl_step(candidate.slot, [candidate])
assert outcome.clear_success == (20,)
assert loop.cleared == [20]
assert outcome.position_reached == (40.0, 0.0)
# 40m / 5m/s + 3s失败 + 5s成功;clear不产生换频时间。
assert outcome.delta_virtual_s == pytest.approx(16.0)
assert loop.client.state.channel == 20
assert loop.fallback_log[-1]["attempts"] == 2
assert 20 not in loop.blocked_probes
assert loop.anomalies == []
finally:
loop.client.exit()
def test_invalid_policy_output_is_logged_and_replaced_by_valid_action():
class InvalidPolicy:
def select(self, observation, mask):
return N_CANDIDATES + 1
loop = make_loop(problem=3, high_policy=InvalidPolicy())
candidate = Candidate(STATION_OFFSET, "station", (0.0, 0.0), station_index=0)
executed = []
def execute(action, candidates):
executed.append(action)
loop.stop_reason = "test_complete"
try:
with patch.object(loop, "build_candidates", return_value=[candidate]), \
patch.object(loop, "hrl_observation", return_value=None), \
patch.object(loop, "heuristic_action", return_value=candidate.slot), \
patch.object(loop, "hrl_step", side_effect=execute):
loop._coverage_phase()
assert executed == [candidate.slot]
assert loop.anomalies[0]["kind"] == "hrl_invalid_policy_action"
finally:
loop.client.exit()
def test_opportunistic_measure_precedes_candidate_snapshot():
loop = make_loop(problem=3)
order = []
try:
assert loop.config.bayes.opportunistic_measure
with patch.object(loop, "_measure_at_stop", side_effect=lambda: order.append("measure")), \
patch.object(loop, "build_candidates", side_effect=lambda: order.append("snapshot") or []):
loop._coverage_phase()
assert order == ["measure", "snapshot"]
finally:
loop.client.exit()