feat(perception): qualify 1x realtime replay envelope
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import stat
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
@@ -97,3 +98,80 @@ def test_shadow_router_accepts_only_validated_diagnostic_results_back() -> None:
|
||||
assert websocket.accepted is True
|
||||
assert published.is_set()
|
||||
assert received == [encoded]
|
||||
|
||||
|
||||
def test_shadow_router_does_not_discard_ingress_while_receiving_results() -> None:
|
||||
class SlowIngress(LivePerceptionIngress):
|
||||
def take_next(
|
||||
self,
|
||||
consumer_id: str,
|
||||
*,
|
||||
timeout: float | None = None,
|
||||
):
|
||||
time.sleep(0.01)
|
||||
return super().take_next(consumer_id, timeout=timeout)
|
||||
|
||||
ingress = SlowIngress()
|
||||
received: list[bytes] = []
|
||||
router = build_live_perception_shadow_router(
|
||||
ingress,
|
||||
"test-plugin",
|
||||
bearer_token="x" * 43,
|
||||
result_receiver=lambda payload: not received.append(payload),
|
||||
)
|
||||
ingress.begin_session("session-1")
|
||||
for modality, sequence in (
|
||||
("camera-init", 0),
|
||||
("camera-frame", 1),
|
||||
("lidar", 1),
|
||||
("pose", 1),
|
||||
):
|
||||
assert ingress.publish(
|
||||
modality=modality,
|
||||
source_id="sensor.camera.right",
|
||||
source_sequence=sequence,
|
||||
captured_at_epoch_ns=sequence,
|
||||
received_monotonic_ns=sequence,
|
||||
payload=modality.encode(),
|
||||
)
|
||||
ingress.end_session("session-1")
|
||||
expected_events = sum(
|
||||
int(queue["depth"]) for queue in ingress.snapshot()["queues"].values()
|
||||
)
|
||||
|
||||
class DuplexFakeWebSocket:
|
||||
def __init__(self) -> None:
|
||||
self.headers = {"authorization": f"Bearer {'x' * 43}"}
|
||||
self.results_remaining = 8
|
||||
self.sent: list[bytes] = []
|
||||
|
||||
async def accept(self) -> None:
|
||||
return
|
||||
|
||||
async def receive(self) -> dict[str, object]:
|
||||
if self.results_remaining:
|
||||
self.results_remaining -= 1
|
||||
await asyncio.sleep(0)
|
||||
return {"type": "websocket.receive", "bytes": b"result"}
|
||||
deadline = asyncio.get_running_loop().time() + 1
|
||||
while len(self.sent) < expected_events:
|
||||
if asyncio.get_running_loop().time() >= deadline:
|
||||
break
|
||||
await asyncio.sleep(0.005)
|
||||
return {"type": "websocket.disconnect"}
|
||||
|
||||
async def send_bytes(self, payload: bytes) -> None:
|
||||
self.sent.append(payload)
|
||||
|
||||
async def close(self, **_: object) -> None:
|
||||
return
|
||||
|
||||
websocket = DuplexFakeWebSocket()
|
||||
asyncio.run(router.routes[0].endpoint(websocket)) # type: ignore[attr-defined]
|
||||
|
||||
assert len(received) == 8
|
||||
assert len(websocket.sent) == expected_events
|
||||
snapshot = ingress.snapshot()
|
||||
assert sum(
|
||||
int(queue["consumed"]) for queue in snapshot["queues"].values()
|
||||
) == expected_events
|
||||
|
||||
Reference in New Issue
Block a user