Files
hermes-agent/tests/pm/test_features.py
ethernet f2db4349e8 fix(pm): inventory features in the staged interpreter
The builder's imported modules could mark an empty dependency tree as
installed. Probe every anchor in one isolated target process. Require
all anchors for a multi-module extra, and process only the selected
tree's .pth files so editable packages retain their launch behavior.

The native builder passes its staged Python explicitly and stops before
manifest publication when the inventory fails. Correct the Hindsight
and Teams anchors using the namespaces in their locked wheels.

Verified: 31 tests passed, 3 host skips. A real target child records its
identity, and a caller mutation back to the builder Python fails the
regression. Both downloaded SDK wheels match uv.lock and pass inventory
and availability checks. Ruff and added-comment checks passed.

No full native package or signing run is claimed.
2026-09-09 21:47:54 -04:00

111 lines
3.3 KiB
Python

"""pm.features: the frozen bundle feature set (enabled-features.json).
Lazy installs OFF = the bundle's feature list is FROZEN to the file the
bundle wrote (the EXACT extras that installed on that target); pm sync
never deviates and never installs a plugin member.
"""
from __future__ import annotations
from pathlib import Path
import pytest
import pm.features as feats
@pytest.fixture
def rooted(tmp_path, monkeypatch):
"""Point features_path at a temp runtime dir (store_root().parent)."""
store = tmp_path / "tools"
store.mkdir()
monkeypatch.setattr("pm.paths.store_root", lambda: store)
return tmp_path
def test_write_then_read_roundtrip(rooted):
path = feats.write_features(["web", "acp", "web"])
assert path.is_file()
got = feats.read_features()
assert got == ["acp", "web"] # sorted, deduped
def test_read_features_none_when_absent(rooted):
assert feats.read_features() is None
def test_read_features_none_on_garbage(rooted):
feats.features_path().write_text("{ not json", encoding="utf-8")
assert feats.read_features() is None
def test_features_path_in_bundle_uses_payload_root(rooted):
payload = rooted / "payload"
payload.mkdir()
assert feats.features_path(payload) == payload / "enabled-features.json"
def test_installed_extras_reports_only_anchor_resolved(tmp_path, monkeypatch):
import sys
repo = tmp_path / "repo"
repo.mkdir()
(repo / "pyproject.toml").write_text(
"[project]\n"
'name = "hermes-agent"\n'
"[project.optional-dependencies]\n"
'present = ["x"]\n'
'absent = ["y"]\n',
encoding="utf-8",
)
venv = tmp_path / "venv"
from hermes_cli.runtime_paths import site_packages
site = site_packages(venv)
site.mkdir(parents=True)
(site / "somepkg.py").write_text("x = 1\n", encoding="utf-8")
import pm.extras as extras_mod
monkeypatch.setattr(
extras_mod,
"ANCHORS",
{**extras_mod.ANCHORS, "present": "somepkg", "absent": "missingmod"},
)
got = feats.installed_extras(repo, venv, python_exe=Path(sys.executable))
assert "present" in got
assert "absent" not in got
def test_sync_venv_refuses_outside_frozen_extras(rooted, monkeypatch):
feats.write_features(["web", "acp"])
import sys
ensure_mod = sys.modules["pm.ensure"]
from pm.package import InstallError
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False)
with pytest.raises(InstallError) as exc:
ensure_mod.sync_venv(["slack"], explicit=True)
assert "frozen" in str(exc.value) or "outside" in str(exc.value)
def test_sync_venv_allows_frozen_extras_when_lazy_off(rooted, monkeypatch):
feats.write_features(["web"])
import sys
ensure_mod = sys.modules["pm.ensure"]
# lazy off, request within the frozen set: passes the gate (may still
# no-op on the stamp — we only assert no refusal here, by making the
# stamp match so sync_venv returns early)
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: False)
venv_pkg = ensure_mod.get_package("venv")
monkeypatch.setattr(
venv_pkg, "expected_stamp", lambda extras: "stamp"
)
monkeypatch.setattr(ensure_mod, "_facts", lambda: {"venv": {"stamp": "stamp", "extras": ["web"]}})
ensure_mod.sync_venv(["web"]) # no raise