fix(k1): stabilize live recovery and media admission
This commit is contained in:
@@ -326,6 +326,7 @@ def test_live_rerun_process_and_close_failure_preserve_capture_then_recover(
|
||||
allow_second_bridge = threading.Event()
|
||||
second_bridge_ready = threading.Event()
|
||||
recovery_confirmed = threading.Event()
|
||||
raw_capture_completed = threading.Event()
|
||||
raw_sequences: list[int] = []
|
||||
should_stop_before_explicit_stop: list[bool] = []
|
||||
confirmed_attempts: list[int] = []
|
||||
@@ -389,6 +390,7 @@ def test_live_rerun_process_and_close_failure_preserve_capture_then_recover(
|
||||
assert second_bridge_ready.wait(timeout=2.0)
|
||||
raw_sequences.append(10)
|
||||
enqueue(_captured_live_point_cloud(10)) # type: ignore[operator]
|
||||
raw_capture_completed.set()
|
||||
assert recovery_confirmed.wait(timeout=2.0)
|
||||
|
||||
while not should_stop(): # type: ignore[operator]
|
||||
@@ -409,6 +411,7 @@ def test_live_rerun_process_and_close_failure_preserve_capture_then_recover(
|
||||
)
|
||||
|
||||
assert recovery_confirmed.wait(timeout=3.0)
|
||||
assert raw_capture_completed.wait(timeout=3.0)
|
||||
snapshot = runtime.snapshot()
|
||||
assert snapshot["phase"] == "live"
|
||||
assert snapshot["source_ready"] is True
|
||||
@@ -510,6 +513,66 @@ def test_live_latest_point_cloud_survives_pose_and_perception_pressure(
|
||||
runtime.close()
|
||||
|
||||
|
||||
def test_live_pose_is_published_during_continuous_point_cloud_pressure(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
producer_started = threading.Event()
|
||||
producer_finished = threading.Event()
|
||||
pose_published_during_pressure = threading.Event()
|
||||
|
||||
class SlowPointCloudBridge(MetricRuntimeBridgeStub):
|
||||
def process(self, envelope: object) -> None:
|
||||
if isinstance(envelope, DecodedPointCloudView):
|
||||
# Keep another PCL waiting in the latest-wins slot. The
|
||||
# scheduler must still admit pose while pressure continues.
|
||||
time.sleep(0.01)
|
||||
elif (
|
||||
isinstance(envelope, DecodedPoseView)
|
||||
and not producer_finished.is_set()
|
||||
):
|
||||
pose_published_during_pressure.set()
|
||||
super().process(envelope)
|
||||
|
||||
def fake_capture_mqtt(
|
||||
_host: str,
|
||||
_out_dir: Path,
|
||||
**callbacks: object,
|
||||
) -> dict[str, object]:
|
||||
callbacks["on_clock_established"]() # type: ignore[operator]
|
||||
callbacks["on_ready"]() # type: ignore[operator]
|
||||
enqueue = callbacks["on_message_recorded"]
|
||||
producer_started.set()
|
||||
for index in range(300):
|
||||
enqueue(_captured_live_point_cloud(index * 2 + 1)) # type: ignore[operator]
|
||||
enqueue(_captured_live_pose(index * 2 + 2)) # type: ignore[operator]
|
||||
time.sleep(0.001)
|
||||
producer_finished.set()
|
||||
|
||||
should_stop = callbacks["should_stop"]
|
||||
while not should_stop(): # type: ignore[operator]
|
||||
time.sleep(0.005)
|
||||
return {"message_count": 600}
|
||||
|
||||
monkeypatch.setattr(runtime_module, "capture_mqtt", fake_capture_mqtt)
|
||||
runtime = VisualizationRuntime(
|
||||
bridge_factory=lambda **kwargs: SlowPointCloudBridge(kwargs["metrics"]), # type: ignore[arg-type]
|
||||
normalizer=normalize_k1_message,
|
||||
)
|
||||
runtime.start_live(
|
||||
"192.168.1.50",
|
||||
tmp_path / "pose-fairness",
|
||||
duration_seconds=None,
|
||||
project_name="POSEFAIRNESS001",
|
||||
)
|
||||
|
||||
assert producer_started.wait(timeout=2.0)
|
||||
assert producer_finished.wait(timeout=2.0)
|
||||
runtime.stop(wait_seconds=2.0)
|
||||
runtime.close()
|
||||
assert pose_published_during_pressure.is_set()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("attempt", [5, 1025, 10**100])
|
||||
def test_live_rerun_recovery_backoff_saturates(attempt: int) -> None:
|
||||
assert runtime_module._rerun_recovery_backoff_seconds(attempt) == 5.0 # noqa: SLF001
|
||||
|
||||
Reference in New Issue
Block a user