Files
hermes-agent/tests/pm/test_features.py
2026-10-08 17:22:54 +02:00

126 lines
5.2 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
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):
assert feats.read_features() is None
path = feats.write_features(["web", "acp", "web"])
assert path.is_file()
got = feats.read_features()
assert got == ["acp", "web"] # sorted, deduped
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_sync_venv_refuses_outside_frozen_extras(rooted, monkeypatch):
feats.write_features(["web", "acp"])
import pm.install as ensure_mod
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)
from pm import receipt
saved = receipt.latest()
assert saved["refusal"]["code"] == "lazy-install"
assert saved["outcome"] == "failed" and saved["exit_code"] != 0
assert saved["steps"][-1]["ok"] is False
assert "slack" in saved["steps"][-1]["detail"]
assert "hermes pm install" in saved["steps"][-1]["detail"]
def test_sync_venv_allows_frozen_extras_when_lazy_off(rooted, monkeypatch):
feats.write_features(["web"])
from pm import paths
from pm.lock import Facts
from pm.environments import install_state_dir, runtime_facts_path
import pm.install as ensure_mod
# Matching stamp alone cannot certify a vanished environment. Reuse only
# the recorded selection while retaining the disabled acquisition policy.
repo = rooted / "repo"
repo.mkdir()
monkeypatch.setattr(paths, "repo_root", lambda: repo)
environment = install_state_dir(repo) / "environments" / "frozen" / "venv"
environment.mkdir(parents=True)
(environment / "pyvenv.cfg").write_text("home = fixture\n")
Facts(runtime_facts_path(repo)).record_state("venv", "stamp", ["web"], environment=environment)
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(venv_pkg, "apply", lambda *args, **kwargs: pytest.fail("current frozen environment rebuilt"))
ensure_mod.sync_venv(["web"])
(environment / "pyvenv.cfg").unlink()
from pm.package import InstallError
with pytest.raises(InstallError, match="lazy installs are disabled"):
ensure_mod.sync_venv(["web"])
def test_lazy_sync_never_creates_the_first_selection_for_a_foreign_interpreter(rooted, monkeypatch):
"""A process running from an environment PM did not select (a build_environment test venv,
a dev venv, nix) imports an adapter whose extra is missing. Letting that lazy sync commit
the install's FIRST selection strands every later process: they boot into a generation
that lacks whatever the foreign interpreter carried (the CI "anthropic/aiohttp vanished"
class). Only an explicit install may create it. Exercised at the client seam every
ensure_import caller goes through, in the in-process (is_runtime) shape."""
from pm import client
import pm.install as ensure_mod
from pm import paths
from pm.package import InstallError
from pm.environments import runtime_facts_path
repo = rooted / "repo"
repo.mkdir()
(repo / "venv").mkdir() # the install's own base venv, which this pytest process is NOT running from
monkeypatch.setattr(paths, "repo_root", lambda: repo)
monkeypatch.setattr(client, "is_runtime", lambda: True)
monkeypatch.setattr(ensure_mod, "lazy_installs_allowed", lambda: True)
venv_pkg = ensure_mod.get_package("venv")
monkeypatch.setattr(venv_pkg, "expected_stamp", lambda extras, **kwargs: "stamp")
monkeypatch.setattr(
venv_pkg, "apply", lambda *args, **kwargs: pytest.fail("lazy sync built a generation for a foreign interpreter"))
assert not runtime_facts_path(repo).exists()
with pytest.raises(InstallError, match="not running from the install's dependency environment"):
client.sync_venv(["bedrock"])
assert not runtime_facts_path(repo).exists(), "a refused sync must not commit a selection"
# The remedy the refusal names still works: an explicit install creates the selection.
monkeypatch.setattr(venv_pkg, "apply", lambda *args, **kwargs: {})
client.sync_venv(["bedrock"], explicit=True)
assert runtime_facts_path(repo).is_file()