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

75 lines
3.1 KiB
Python

"""Feature inventory reads the selected dependency tree, not the builder."""
import json
import os
from pathlib import Path
import subprocess
import sys
import packaging
import pytest
from pm import extras
from pm import features
from pm.environments import site_packages
def test_inventory_uses_the_target_and_requires_every_anchor(tmp_path, monkeypatch):
target_root = tmp_path / "target"
subprocess.run(
[sys.executable, "-m", "venv", "--without-pip", str(target_root)],
capture_output=True, check=True, timeout=60,
)
target = target_root / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
dependencies = tmp_path / "dependencies"
site = site_packages(dependencies)
site.mkdir(parents=True)
witness = tmp_path / "child.json"
package = site / "inventory_anchor"
package.mkdir()
(package / "__init__.py").write_text(
"import json, pathlib, sys\n"
f"pathlib.Path({str(witness)!r}).write_text(json.dumps(sys.executable), encoding='utf-8')\n",
encoding="utf-8",
)
(package / "present.py").write_text("VALUE = 'target'\n", encoding="utf-8")
(site / "one_part.py").write_text("", encoding="utf-8")
editable = tmp_path / "editable"
editable.mkdir()
(editable / "editable_anchor.py").write_text("", encoding="utf-8")
(site / "editable.pth").write_text(str(editable) + "\n", encoding="utf-8")
launches = tmp_path / "launches.jsonl"
(site / "launches.pth").write_text(
f"import json, pathlib, sys; p = pathlib.Path({str(launches)!r}); "
"text = p.read_text(encoding='utf-8') if p.exists() else ''; "
"p.write_text(text + json.dumps(sys.executable) + '\\n', encoding='utf-8')\n",
encoding="utf-8",
)
mapping = {
"complete": "inventory_anchor.present",
"partial": ("one_part", "absent_part"),
"host-only": "packaging",
"editable": "editable_anchor",
}
repo = tmp_path / "source"
repo.mkdir()
(repo / "pyproject.toml").write_text(
"[project.optional-dependencies]\n" + "".join(f'"{name}"=[]\n' for name in mapping),
encoding="utf-8",
)
monkeypatch.setattr(extras, "ANCHORS", mapping)
before_path = list(sys.path)
before_env = dict(os.environ)
assert packaging.__file__ and "packaging" in sys.modules
result = features.installed_extras(repo, dependencies, python_exe=target)
assert result == ["complete", "editable"]
assert Path(json.loads(witness.read_text(encoding="utf-8"))) == target
assert [Path(json.loads(line)) for line in launches.read_text(encoding="utf-8").splitlines()] == [target]
assert "inventory_anchor" not in sys.modules
assert sys.path == before_path
assert dict(os.environ) == before_env
(site / "absent_part.py").write_text("", encoding="utf-8")
assert features.installed_extras(repo, dependencies, python_exe=target) == ["complete", "editable", "partial"]
with pytest.raises(features.FeatureProbeError, match="feature inventory failed"):
features.installed_extras(repo, dependencies, python_exe=tmp_path / "missing-python")