feat(k1): complete primary acquisition lifecycle
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import lz4.block
|
||||
@@ -301,11 +304,13 @@ def test_live_runtime_surfaces_preamble_failure_as_terminal_error(
|
||||
monkeypatch.setattr(runtime_module, "_write_live_session_preamble", fail_preamble)
|
||||
runtime = VisualizationRuntime(normalizer=normalize_k1_message)
|
||||
|
||||
runtime.start_live(
|
||||
"192.168.1.20",
|
||||
tmp_path / "unwritable-evidence",
|
||||
duration_seconds=1.0,
|
||||
)
|
||||
with pytest.raises(RuntimeError, match="session clock"):
|
||||
runtime.start_live(
|
||||
"192.168.1.20",
|
||||
tmp_path / "unwritable-evidence",
|
||||
duration_seconds=1.0,
|
||||
project_name="Preamble failure test",
|
||||
)
|
||||
deadline = time.monotonic() + 2.0
|
||||
snapshot = runtime.snapshot()
|
||||
while snapshot["phase"] == "starting_live" and time.monotonic() < deadline:
|
||||
@@ -317,3 +322,80 @@ def test_live_runtime_surfaces_preamble_failure_as_terminal_error(
|
||||
assert snapshot["source_ready"] is False
|
||||
assert "synthetic evidence directory failure" in snapshot["message"]
|
||||
runtime.close()
|
||||
|
||||
|
||||
def test_live_start_waits_for_capture_clock_before_returning(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
entered_capture = threading.Event()
|
||||
allow_clock = threading.Event()
|
||||
returned = threading.Event()
|
||||
failures: list[BaseException] = []
|
||||
recording = FakeRecording()
|
||||
|
||||
def bridge_factory(**kwargs: object) -> RerunBridge:
|
||||
return RerunBridge(
|
||||
recording_factory=lambda _: recording, # type: ignore[arg-type]
|
||||
**kwargs, # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
def delayed_capture(
|
||||
_host: str,
|
||||
_out_dir: Path,
|
||||
*,
|
||||
on_clock_established: Callable[[], None],
|
||||
should_stop: Callable[[], bool],
|
||||
**_kwargs: object,
|
||||
) -> dict[str, object]:
|
||||
entered_capture.set()
|
||||
assert allow_clock.wait(timeout=2.0)
|
||||
on_clock_established()
|
||||
while not should_stop():
|
||||
time.sleep(0.005)
|
||||
return {"message_count": 0}
|
||||
|
||||
monkeypatch.setattr(runtime_module, "capture_mqtt", delayed_capture)
|
||||
runtime = VisualizationRuntime(
|
||||
bridge_factory=bridge_factory,
|
||||
normalizer=normalize_k1_message,
|
||||
)
|
||||
|
||||
def start() -> None:
|
||||
try:
|
||||
runtime.start_live(
|
||||
"192.168.1.20",
|
||||
tmp_path / "delayed-clock",
|
||||
duration_seconds=30.0,
|
||||
project_name="Clock barrier test",
|
||||
)
|
||||
except BaseException as exc:
|
||||
failures.append(exc)
|
||||
finally:
|
||||
returned.set()
|
||||
|
||||
caller = threading.Thread(target=start)
|
||||
caller.start()
|
||||
assert entered_capture.wait(timeout=2.0)
|
||||
assert not returned.wait(timeout=0.05)
|
||||
allow_clock.set()
|
||||
assert returned.wait(timeout=2.0)
|
||||
assert failures == []
|
||||
runtime.stop()
|
||||
caller.join(timeout=2.0)
|
||||
runtime.close()
|
||||
|
||||
|
||||
def test_live_session_manifest_retains_project_display_name(tmp_path: Path) -> None:
|
||||
out_dir = tmp_path / "evidence-session"
|
||||
|
||||
runtime_module._write_live_session_preamble(
|
||||
out_dir,
|
||||
"192.168.1.20",
|
||||
60.0,
|
||||
"K1 Route Alpha",
|
||||
)
|
||||
|
||||
manifest = json.loads((out_dir / "manifest.redacted.json").read_text(encoding="utf-8"))
|
||||
assert manifest["project_name"] == "K1 Route Alpha"
|
||||
assert "192.168.1.20" not in json.dumps(manifest)
|
||||
|
||||
Reference in New Issue
Block a user