"""A payload carries PM's independent dependency graph, not the app's imports.""" import json import os from pathlib import Path import shutil import subprocess import sys import pytest def _exercise_relocated_pm_runtime(tmp_path, monkeypatch): from scripts.bundles import native monkeypatch.setattr(Path, "home", lambda: tmp_path / "home") monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home")) monkeypatch.setenv("HERMES_RUNTIME_DIR", str(tmp_path / "tools")) uv = shutil.which("uv") assert uv, "the packaging test requires uv" root = tmp_path / "build" repo = root / "hermes-agent" source = Path(__file__).resolve().parents[2] shutil.copytree(source / "pm", repo / "pm", ignore=shutil.ignore_patterns("__pycache__", ".hermes-tmp.*")) (repo / "hermes_cli").mkdir() for name in ("__init__.py", "runtime_paths.py", "runtime_state.py"): shutil.copy2(source / "hermes_cli" / name, repo / "hermes_cli" / name) shutil.copy2(source / "hermes_constants.py", repo / "hermes_constants.py") # Copy the base executable, not a venv's launcher. The test host provides # its stdlib; production's package stage provides the complete distribution. python = root / "tools" / "python" / ("python.exe" if os.name == "nt" else "bin/python") python.parent.mkdir(parents=True) if os.name == "nt": shutil.copytree(Path(sys.base_prefix), python.parent, dirs_exist_ok=True) else: shutil.copy2(Path(sys._base_executable).resolve(), python) stage = getattr(native, "stage_pm_runtime", None) assert callable(stage), "native payload has no isolated PM runtime stage" cache = tmp_path / "build-cache" monkeypatch.setattr("pm.client._request", lambda *args, **kwargs: pytest.fail("runtime staging must not bootstrap a worker")) monkeypatch.setattr("pm._uv._toolchain", lambda **kwargs: (Path(uv), Path(sys.executable))) stage(root, python, repo, cache=cache) assert cache.is_dir() assert not (tmp_path / "home/cache/uv").exists() stage(root, python, repo, offline=True, cache=cache) (root / "manifest.json").write_text(json.dumps({"repo": "hermes-agent"})) assert not (repo / ".venv").exists() moved = tmp_path / "installed elsewhere" root.rename(moved) runtime = moved / "pm-runtime" manifest = json.loads((runtime / "pm-runtime.json").read_text()) base = runtime / manifest["python"] site = runtime / manifest["sitePackages"] assert base.is_file() and site.is_dir() assert not Path(manifest["python"]).is_absolute() probe = """ import importlib.util, json, sys sys.path.insert(0, sys.argv[1]) from ruamel.yaml import YAML import packaging, tomli_w assert importlib.util.find_spec('openai') is None assert importlib.util.find_spec('yaml') is None print(json.dumps(YAML(typ='safe').load('isolated: true'))) """ checked = subprocess.run([str(base), "-I", "-S", "-B", "-c", probe, str(site)], cwd=tmp_path, capture_output=True, text=True, timeout=30) assert checked.returncode == 0, checked.stderr assert json.loads(checked.stdout) == {"isolated": True} from pm import runtime as runtime_api, paths monkeypatch.setattr(paths, "repo_root", lambda: moved / "hermes-agent") command = runtime_api.runtime_command(moved / "hermes-agent/pm/launch.py", ["status"]) checked = subprocess.run(command, cwd=tmp_path, env=runtime_api.runtime_environment(), capture_output=True, text=True, timeout=30) assert checked.returncode == 0, checked.stderr assert "no pm sync receipt" in checked.stdout assert str(root) not in (runtime / "pyvenv.cfg").read_text() for link in (runtime / "bin").glob("python*"): if link.is_symlink(): assert not os.path.isabs(os.readlink(link)) assert link.exists() @pytest.mark.platforms("posix") def test_native_pm_runtime_survives_payload_move(tmp_path, monkeypatch): _exercise_relocated_pm_runtime(tmp_path, monkeypatch) @pytest.mark.platforms("windows") def test_resident_pm_bypasses_windows_redirector_after_move(tmp_path, monkeypatch): _exercise_relocated_pm_runtime(tmp_path, monkeypatch) @pytest.mark.parametrize("poison", ["cwd", "global"]) def test_pm_builder_ignores_ambient_uv_configuration(tmp_path, monkeypatch, poison): from pm import stage_manager_runtime monkeypatch.setattr(Path, "home", lambda: tmp_path / "home") monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home")) monkeypatch.setenv("HERMES_RUNTIME_DIR", str(tmp_path / "tools")) monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "config")) monkeypatch.setenv("APPDATA", str(tmp_path / "config")) monkeypatch.chdir(tmp_path) config = tmp_path / "uv.toml" if poison == "cwd" else tmp_path / "config/uv/uv.toml" config.parent.mkdir(parents=True, exist_ok=True) config.write_text('required-version = "<0.1"\n', encoding="utf-8") uv = shutil.which("uv") assert uv monkeypatch.setattr("pm.client._request", lambda *args, **kwargs: pytest.fail("runtime staging must not bootstrap a worker")) monkeypatch.setattr("pm._uv._toolchain", lambda **kwargs: (Path(uv), Path(sys.executable))) executable = stage_manager_runtime(python=Path(sys.executable), destination=tmp_path / "runtime") assert executable.is_file() def test_native_stage_builds_pm_before_application_environment(tmp_path, monkeypatch): from scripts.bundles import native, payload from types import SimpleNamespace class StopAfterPM(Exception): pass class Facts: def __init__(self, *args, **kwargs): pass def retain(self, names): pass def entries_in_use(self): return set() def get(self, name): return {"entry": "python"} calls = [] lock = tmp_path / "hermes-agent/pm/lock.json" lock.parent.mkdir(parents=True) shutil.copy2(Path(__file__).resolve().parents[2] / "pm/lock.json", lock) monkeypatch.setattr(payload, "snapshot", lambda *args: None) monkeypatch.setattr(native, "_bundle_package_names", lambda: []) monkeypatch.setattr(native, "_install_names", lambda names: 0) monkeypatch.setattr(native, "Facts", Facts) monkeypatch.setattr(native, "_facts", Facts) monkeypatch.setattr(native, "_store", lambda: SimpleNamespace(entry=lambda name: tmp_path / "tools" / name)) monkeypatch.setattr(native, "get_package", lambda name: SimpleNamespace(binary=lambda path, target: path / "python")) cache_dir = tmp_path / "cache" monkeypatch.setenv("UV_CACHE_DIR", str(cache_dir)) def staged(root, python, repo, *, cache): assert cache == cache_dir calls.append((root, python, repo)) raise StopAfterPM monkeypatch.setattr(native, "stage_pm_runtime", staged) with pytest.raises(StopAfterPM): native._stage_native(SimpleNamespace(out=str(tmp_path), ref="HEAD")) assert calls == [(tmp_path, tmp_path / "tools/python/python", tmp_path / "hermes-agent")]