feat(simulation): qualify real S1B provider lifecycle
This commit is contained in:
@@ -22,12 +22,16 @@ from k1link.simulation import (
|
||||
RunState,
|
||||
S0WorkerGuard,
|
||||
SimulationApplicationService,
|
||||
StockRoverTargetPaths,
|
||||
WorkerAdmissionError,
|
||||
WorkerStartResult,
|
||||
WorkerStopResult,
|
||||
load_stock_rover_lifecycle_profile,
|
||||
stock_rover_process_specs,
|
||||
)
|
||||
|
||||
PROFILE = Path(__file__).resolve().parents[1] / "simulation/s0/qualification-profile.yaml"
|
||||
S1B_PROFILE = Path(__file__).resolve().parents[1] / "simulation/s1/stock-rover-lifecycle.yaml"
|
||||
SHA_A = "a" * 64
|
||||
SHA_B = "b" * 64
|
||||
|
||||
@@ -336,6 +340,116 @@ def test_process_supervisor_rolls_back_partial_start(tmp_path: Path) -> None:
|
||||
assert supervisor.residue() == ()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="target supervisor is POSIX-only")
|
||||
def test_process_supervisor_waits_for_all_declared_ready_markers(tmp_path: Path) -> None:
|
||||
child = (
|
||||
"import signal,time;"
|
||||
"signal.signal(signal.SIGINT,lambda *_:exit(0));"
|
||||
"print('provider booting',flush=True);"
|
||||
"time.sleep(0.05);"
|
||||
"print('transport connected',flush=True);"
|
||||
"time.sleep(60)"
|
||||
)
|
||||
supervisor = PosixProcessSupervisor(tmp_path / "runtime")
|
||||
|
||||
records = supervisor.start(
|
||||
"run-ready-001",
|
||||
(
|
||||
ProcessSpec(
|
||||
process_id="ready-provider",
|
||||
argv=(sys.executable, "-c", child),
|
||||
start_order=1,
|
||||
shutdown_order=1,
|
||||
ready_log_patterns=("provider booting", "transport connected"),
|
||||
health_timeout_seconds=1,
|
||||
health_poll_interval_seconds=0.01,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert tuple(record.process_id for record in records) == ("ready-provider",)
|
||||
assert supervisor.stop().residue_process_ids == ()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="target supervisor is POSIX-only")
|
||||
def test_process_supervisor_readiness_timeout_rolls_back_without_residue(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
supervisor = PosixProcessSupervisor(tmp_path / "runtime")
|
||||
|
||||
with pytest.raises(ProcessSupervisorError, match="missing markers: never-ready"):
|
||||
supervisor.start(
|
||||
"run-ready-timeout-001",
|
||||
(
|
||||
ProcessSpec(
|
||||
process_id="not-ready",
|
||||
argv=(sys.executable, "-c", "import time;time.sleep(60)"),
|
||||
start_order=1,
|
||||
shutdown_order=1,
|
||||
ready_log_patterns=("never-ready",),
|
||||
health_timeout_seconds=0.05,
|
||||
health_poll_interval_seconds=0.01,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert supervisor.residue() == ()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "posix", reason="target supervisor is POSIX-only")
|
||||
def test_process_supervisor_rejects_exit_before_readiness(tmp_path: Path) -> None:
|
||||
supervisor = PosixProcessSupervisor(tmp_path / "runtime")
|
||||
|
||||
with pytest.raises(ProcessSupervisorError, match="exited before readiness"):
|
||||
supervisor.start(
|
||||
"run-ready-exit-001",
|
||||
(
|
||||
ProcessSpec(
|
||||
process_id="early-exit",
|
||||
argv=(sys.executable, "-c", "raise SystemExit(7)"),
|
||||
start_order=1,
|
||||
shutdown_order=1,
|
||||
ready_log_patterns=("never-ready",),
|
||||
health_timeout_seconds=1,
|
||||
health_poll_interval_seconds=0.01,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assert supervisor.residue() == ()
|
||||
|
||||
|
||||
def test_stock_rover_profile_builds_exact_reviewed_provider_graph() -> None:
|
||||
profile = load_stock_rover_lifecycle_profile(S1B_PROFILE)
|
||||
paths = StockRoverTargetPaths.from_d_root(Path("/mnt/d/NDC_MISSIONCORE/simulation"))
|
||||
|
||||
specs = stock_rover_process_specs(paths, profile)
|
||||
|
||||
assert profile.profile_id == "s1b-stock-rover-lifecycle-v1"
|
||||
assert tuple(spec.process_id for spec in specs) == (
|
||||
"micro-xrce-dds-agent",
|
||||
"px4-gazebo-stock-rover",
|
||||
)
|
||||
assert specs[0].ready_log_patterns == ("running...",)
|
||||
assert specs[1].argv[-2:] == ("px4_sitl", "gz_rover_ackermann")
|
||||
assert specs[1].ready_log_patterns[-1] == "Running, connected"
|
||||
assert dict(specs[1].environment) == {
|
||||
"HEADLESS": "1",
|
||||
"PX4_SIM_SPEED_FACTOR": "1",
|
||||
}
|
||||
|
||||
|
||||
def test_ready_patterns_require_a_positive_timeout() -> None:
|
||||
with pytest.raises(ValueError, match="positive health timeout"):
|
||||
ProcessSpec(
|
||||
process_id="unsafe-readiness",
|
||||
argv=(sys.executable, "-c", "pass"),
|
||||
start_order=1,
|
||||
shutdown_order=1,
|
||||
ready_log_patterns=("ready",),
|
||||
)
|
||||
|
||||
|
||||
def test_s0_worker_guard_binds_exact_digest_and_d_only_run_path() -> None:
|
||||
guard = S0WorkerGuard(PROFILE)
|
||||
run = _run(host_profile_sha256=hashlib.sha256(PROFILE.read_bytes()).hexdigest())
|
||||
|
||||
Reference in New Issue
Block a user