Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion just/dev.just
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ test kind="all":
case "{{kind}}" in
fast)
ROBOCLAWS_PYTEST_CLEAR_PROVIDER_ENV=1 {{standalone_pytest}} \
-m "not integration" -q --durations=5
-m "not integration and not local" -q --durations=5
;;

unit)
Expand Down
2 changes: 1 addition & 1 deletion roboclaws/operator_console/runtime_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def runtime_inventory_payload(
"""Return a redacted inventory of repo-relevant local background tasks."""

root = root.resolve()
port_list = _dedupe_ints([*(ports or []), DEFAULT_MCP_PORT])
port_list = _dedupe_ints([DEFAULT_MCP_PORT] if ports is None else ports)
tasks: list[dict[str, Any]] = []
tasks.extend(_operator_console_tasks(root, include_recent_terminal=include_recent_terminal))
tasks.extend(_eval_harness_tasks(root, include_recent_terminal=include_recent_terminal))
Expand Down
2 changes: 1 addition & 1 deletion roboclaws/operator_console/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ def _serve_runtime_tasks(self, query_string: str) -> None:
ports.append(int(value))
except ValueError:
continue
self._json(runtime_blockers_payload(self.repo_root, ports=ports))
self._json(runtime_blockers_payload(self.repo_root, ports=ports or None))

def _serve_route_readiness(self, query_string: str) -> None:
try:
Expand Down
67 changes: 60 additions & 7 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@
"test_code_mcp_binding_smoke.py",
}

LOCAL_ASSET_MODULES = {
# These validate private B1 / Agibot map exports or robot-data-lab scene
# assets that are present on local workstations but not in GitHub checkout.
"test_b1_map12_alignment_fit_cli.py",
"test_b1_map12_base_navigation_map.py",
"test_b1_map12_base_navigation_sidecar.py",
"test_b1_map12_correspondence_review_cli.py",
"test_b1_map12_label_tool.py",
"test_b1_map12_manual_alignment_overlay_cli.py",
"test_b1_map12_verified_alignment.py",
"test_b1_scene_topdown_diagnostic.py",
"test_molmospaces_source_pin.py",
}

LOCAL_ASSET_TESTS = {
"test_agibot_map_context_scripts.py": {
"test_agibot_nav_json_artifact_source_rejects_invalid_payloads",
"test_agibot_nav_raw_map_source_rejects_malformed_gzip_json",
"test_agibot_nav_raw_map_source_rejects_non_object_gzip_json",
"test_agibot_nav_raw_map_source_rejects_plain_json_file",
},
"test_base_waypoint_builder.py": {
"test_base_waypoint_builder_preserves_b1_map12_waypoints",
},
"test_check_molmo_realworld_cleanup_result.py": {
"test_checker_accepts_b1_robot_consumption_proof_without_rby1m_readiness",
"test_checker_rejects_b1_robot_consumption_manifest_drift",
"test_checker_rejects_b1_robot_consumption_without_manifest",
"test_checker_rejects_b1_robot_consumption_without_verified_navigation",
},
"test_cross_environment_semantic_map_parity.py": {
"test_b1_uses_dt_room_reference_and_alignment_correspondence_manifest",
},
"test_nav2_map_bundle_contract.py": {
"test_base_navigation_map_v1_validation_accepts_b1_bundle",
"test_base_navigation_map_v1_validation_rejects_contract_violations",
},
"test_scene_room_semantic_overlay.py": {
"test_b1_base_navigation_map_materializes_review_labels_without_retargeting_map",
},
}

LAYER_DIRS = ("local", "slow", "integration", "contract", "regression", "unit")

REGRESSION_NAME_PARTS = (
Expand Down Expand Up @@ -39,6 +81,7 @@
EXPLICIT_LAYER_MARKERS = ("local", "slow", "integration", "contract", "regression", "unit")


@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(config: pytest.Config, items: list[pytest.Item]) -> None:
for item in items:
layer = _layer_for_item(item)
Expand All @@ -51,20 +94,30 @@ def _layer_for_item(item: pytest.Item) -> str:
return marker_name

path = Path(str(item.path))
try:
relative_parts = path.relative_to(Path(str(item.config.rootpath)) / "tests").parts
except ValueError:
relative_parts = ()
if relative_parts and relative_parts[0] in LAYER_DIRS:
return relative_parts[0]

filename = path.name
stem = filename.removeprefix("test_").removesuffix(".py")

if filename in LOCAL_ASSET_MODULES:
return "local"
if item.name.split("[", 1)[0] in LOCAL_ASSET_TESTS.get(filename, set()):
return "local"
if filename in INTEGRATION_MODULES:
return "integration"
directory_layer = _directory_layer(item, path)
if directory_layer:
return directory_layer
if any(part in stem for part in CONTRACT_NAME_PARTS):
return "contract"
if any(part in stem for part in REGRESSION_NAME_PARTS):
return "regression"
return "unit"


def _directory_layer(item: pytest.Item, path: Path) -> str:
try:
relative_parts = path.relative_to(Path(str(item.config.rootpath)) / "tests").parts
except ValueError:
return ""
if relative_parts and relative_parts[0] in LAYER_DIRS:
return relative_parts[0]
return ""
13 changes: 8 additions & 5 deletions tests/unit/operator_console/test_runtime_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ def fake_run(command: list[str], **kwargs) -> SimpleNamespace: # noqa: ANN003
return SimpleNamespace(returncode=1, stdout="")

with patch("roboclaws.operator_console.runtime_inventory.subprocess.run", side_effect=fake_run):
payload = runtime_inventory_payload(tmp_path)
blockers = runtime_blockers_payload(tmp_path)
payload = runtime_inventory_payload(tmp_path, ports=[])
blockers = runtime_blockers_payload(tmp_path, ports=[])

task = next(item for item in payload["tasks"] if item["owner"] == "docker")
assert task["id"] == "source-error:docker:container-a:mounts"
Expand Down Expand Up @@ -373,7 +373,10 @@ def test_runtime_inventory_surfaces_invalid_nested_runtime_json_resources(
assert blockers["summary"]["active"] == 0


def test_runtime_inventory_marks_dead_eval_harness_live_row_stale(tmp_path: Path) -> None:
def test_runtime_inventory_marks_dead_eval_harness_live_row_stale(
tmp_path: Path,
free_tcp_port: int,
) -> None:
row_dir = tmp_path / "output" / "eval-harness" / "focused" / "rows" / "codex-cleanup-live"
run_dir = row_dir / "run" / "0615_1225" / "seed-7"
run_dir.mkdir(parents=True)
Expand All @@ -384,7 +387,7 @@ def test_runtime_inventory_marks_dead_eval_harness_live_row_stale(tmp_path: Path
(run_dir / "tmux_session.txt").write_text("roboclaws-molmo-codex-dead\n", encoding="utf-8")
(run_dir / "server.pid").write_text("99999999\n", encoding="utf-8")
(run_dir / "visual_backend_slot.json").write_text(
json.dumps({"slot_id": 1, "pid": 99999999, "port": 18788}),
json.dumps({"slot_id": 1, "pid": 99999999, "port": free_tcp_port}),
encoding="utf-8",
)
(tmp_path / "output" / "eval-harness" / "focused" / "eval_harness.json").write_text(
Expand All @@ -411,7 +414,7 @@ def test_runtime_inventory_marks_dead_eval_harness_live_row_stale(tmp_path: Path
encoding="utf-8",
)

payload = runtime_inventory_payload(tmp_path, ports=[18788])
payload = runtime_inventory_payload(tmp_path, ports=[free_tcp_port])

task = next(item for item in payload["tasks"] if item["id"] == "eval-row:codex-cleanup-live")
assert task["status"] == "stale"
Expand Down
Loading