f89d275650
Layered polish on top of 593d142f (Tier 2 deterministic dispatchers)
plus the systemd-supervised launcher that realises the SystemExit(2)
restart contract in production.
Code:
- Promote `_opencode_worker.list_sessions(server_url)` to public so the
coexistence guard no longer reaches into private `_request`.
- `assert_no_legacy_supervisor` truncates with `(showing first N)` and
takes `Iterable[str]` for supervisor_tags.
- Collapse `_sanitize_release_detail` to a single `str.replace()`
(substitute has no backticks; second pass was always a no-op).
- Telemetry `_api_cycles` routes through a single `_DISPATCH_TABLES`
dict so the rows query and breakdown query can never drift onto
different tables.
- `dispatch_one` docstring now lists `labels-fetch-failed` alongside
the other terminal states.
- `_loader.load_sibling` raises `ImportError` up front for a missing
file (was bubbling `FileNotFoundError` from `exec_module`).
Operations:
- `scripts/dispatchers-launcher.sh` supervises both dispatchers in one
process, restarts on non-zero exit with backoff, enforces a
per-child crash-loop budget, and forwards SIGTERM cleanly.
- `contrib/systemd/cleveragents-dispatchers.service` wires that into a
systemd unit with hardening defaults and journalctl visibility.
Tests (350 pass, 1 skipped):
- New `tests/auto_agents/test_loader.py` (4 cases).
- New `tests/auto_agents/test_telemetry_server.py` (5 synthetic-row
cases covering both tables, composite breakdown, unknown driver,
table isolation).
- Extended `test_dispatch_runtime.py` with truncation-suffix and
`list_sessions` direct tests; refit existing supervisor-guard tests
to monkeypatch the public helper.
Docs:
- AGENTS.md cross-links the launcher / systemd unit.
- CHANGELOG.md entry under [Unreleased] dated 2026-05-07.
Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
"""Unit tests for the shared sibling-module loader.
|
|
|
|
``tools/_loader.py`` is the single place that knows how to import
|
|
non-package modules out of ``tools/`` for the deterministic drivers.
|
|
The dispatcher tests exercise it transitively, but a direct test
|
|
locks the contract — most importantly that the loader caches via
|
|
``sys.modules`` on the second call so a driver and a sibling that
|
|
both import the same module observe the same instance and share
|
|
monkey-patches in tests.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def _load_loader():
|
|
path = Path(__file__).resolve().parents[2] / "tools" / "_loader.py"
|
|
spec = importlib.util.spec_from_file_location("_loader_under_test", path)
|
|
assert spec and spec.loader
|
|
mod = importlib.util.module_from_spec(spec)
|
|
sys.modules["_loader_under_test"] = mod
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
@pytest.fixture
|
|
def loader():
|
|
return _load_loader()
|
|
|
|
|
|
def test_load_sibling_imports_module_by_filename(loader):
|
|
"""A first call must resolve ``filename`` under ``tools/`` and
|
|
register the module in :data:`sys.modules`."""
|
|
sys.modules.pop("_claim_runtime_under_test", None)
|
|
mod = loader.load_sibling("_claim_runtime_under_test", "_claim_runtime.py")
|
|
assert mod is not None
|
|
assert sys.modules.get("_claim_runtime_under_test") is mod
|
|
# ``_claim_runtime`` exposes ``REPO_OWNER`` / ``REPO_NAME`` at module
|
|
# scope; if those aren't visible the loader didn't actually execute
|
|
# the file body.
|
|
assert hasattr(mod, "REPO_OWNER")
|
|
assert hasattr(mod, "REPO_NAME")
|
|
sys.modules.pop("_claim_runtime_under_test", None)
|
|
|
|
|
|
def test_load_sibling_returns_cached_instance_on_second_call(loader):
|
|
"""The loader must return the SAME module object on a re-load.
|
|
|
|
This is what lets the conflict driver and a dispatcher running in
|
|
the same process share monkey-patches and per-module caches like
|
|
``_LABEL_ID_CACHE``; a fresh ``module_from_spec`` per call would
|
|
silently double-instantiate state.
|
|
"""
|
|
sys.modules.pop("_claim_runtime_cache_test", None)
|
|
first = loader.load_sibling("_claim_runtime_cache_test", "_claim_runtime.py")
|
|
second = loader.load_sibling("_claim_runtime_cache_test", "_claim_runtime.py")
|
|
assert first is second
|
|
sys.modules.pop("_claim_runtime_cache_test", None)
|
|
|
|
|
|
def test_load_sibling_raises_on_missing_file(loader):
|
|
"""A missing filename must raise ``ImportError`` rather than fall
|
|
through silently — a missing sibling is a programmer error, not a
|
|
runtime condition we want to swallow."""
|
|
with pytest.raises(ImportError) as excinfo:
|
|
loader.load_sibling("_does_not_exist_under_test", "_does_not_exist.py")
|
|
assert "_does_not_exist.py" in str(excinfo.value) or "_does_not_exist_under_test" in str(excinfo.value)
|
|
|
|
|
|
def test_tools_dir_resolves_to_repo_tools_directory(loader):
|
|
"""Sanity check: the resolved ``TOOLS_DIR`` must point at the
|
|
repository's ``tools/`` directory; otherwise everything else in
|
|
this module is testing the wrong path."""
|
|
expected = Path(__file__).resolve().parents[2] / "tools"
|
|
assert loader.TOOLS_DIR == expected
|