Files
hermes-agent/tests/pm/test_setup_lock_format.py

102 lines
4.4 KiB
Python

"""Exercise setup's pre-Python pin reader through real downloads and extraction.
Only the downloaded uv executable and the PM command at the handoff are fixtures;
the copied setup script, curl, hashing, archive staging, and Python process are real.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import shlex
import shutil
import subprocess
import sys
import pytest
from pm.store import current_target
from tests.pm._fixtures import make_tar, served # noqa: F401 -- shared HTTP fixture
pytestmark = pytest.mark.platforms("posix")
REPO = Path(__file__).resolve().parents[2]
@pytest.mark.parametrize("indent,blank_lines,mirror_indent,fallback", [
(2, False, 2, False), (4, False, 7, False), (0, False, 2, False),
("\t", False, 7, False), (4, True, 2, False), (2, False, 9, True),
])
def test_setup_reads_pins_independent_of_indentation(tmp_path, served, indent, blank_lines, mirror_indent, fallback):
bash = shutil.which("bash")
assert bash, "the shell bootstrap contract requires Bash"
core = tmp_path / "checkout with spaces"
(core / "pm").mkdir(parents=True)
shutil.copy2(REPO / "setup-hermes.sh", core / "setup-hermes.sh")
home = tmp_path / "home"
home.mkdir()
runtime = tmp_path / "runtime"
interpreter = str(Path(sys._base_executable).resolve())
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
uv_version = "fixture-pin"
calls = tmp_path / "uv-calls"
receipt = core / "handoff.json"
(core / "pm" / "__init__.py").touch()
(core / "pm" / "cli.py").write_text(
"import json, pathlib, sys\n"
"assert sys.argv[1:] == ['install', '--test-environment', '--trust-recorded'], sys.argv\n"
f"pathlib.Path({str(receipt)!r}).write_text(json.dumps(sys.argv[1:]))\n",
encoding="utf-8",
)
uv_script = (
f"#!{bash}\nset -eu\n"
f"printf '%s\\n' \"$*\" >> {shlex.quote(str(calls))}\n"
'case "$*" in\n'
f' --version) printf \'%s\\n\' "uv {uv_version}" ;;\n'
f' "python install --no-bin --no-registry {py_version}") ;;\n'
f' "python find --managed-python {py_version}") printf \'%s\\n\' {shlex.quote(interpreter)} ;;\n'
' *) exit 91 ;;\nesac\n'
)
docroot, base_url = served
filename, digest = make_tar(docroot, "uv.tar.gz", {"uv-fixture/uv": uv_script})
artifact = {"sha256": digest, "url": f"{base_url}/{filename}"}
if fallback:
(docroot / "mirror").mkdir()
shutil.copy2(docroot / filename, docroot / "mirror" / digest)
artifact["url"] = "http://127.0.0.1:1/uv.tar.gz"
decoy = {"sha256": "0" * 64, "url": f"{base_url}/wrong-target.tar.gz"}
target = current_target()
data = {"packages": {
"before": {"artifacts": {target: decoy}, "version": "wrong-before"},
"python": {"artifacts": {target: decoy}, "version": f"{py_version}.7+fixture"},
"uv": {"artifacts": {"before-target": decoy, target: artifact, "after-target": decoy},
"version": uv_version},
"after": {"artifacts": {target: decoy}, "version": "wrong-after"},
}}
content = json.dumps(data, indent=indent)
if blank_lines:
content = content.replace("\n", "\n \t\n")
(core / "pm" / "lock.json").write_text(content + "\n", encoding="utf-8")
(core / "pm" / "artifact-mirror.json").write_text(
json.dumps({"origin": base_url, "prefix": "mirror/"}, indent=mirror_indent), encoding="utf-8",
)
env = {"PATH": os.environ["PATH"], "HOME": str(home),
"HERMES_HOME": str(home / ".hermes"), "HERMES_RUNTIME_DIR": str(runtime),
"PYTHONNOUSERSITE": "1"}
result = subprocess.run(
[bash, str(core / "setup-hermes.sh"), "--runtime-only"], cwd=tmp_path,
env=env, capture_output=True, text=True, timeout=30,
)
assert result.returncode == 0, result.stdout + result.stderr
assert (runtime / f"uv-{uv_version}-{target}" / "uv").read_text() == uv_script
assert json.loads(receipt.read_text()) == ["install", "--test-environment", "--trust-recorded"]
assert calls.read_text().splitlines() == [
"--version", f"python install --no-bin --no-registry {py_version}",
f"python find --managed-python {py_version}",
]
assert not (home / ".local").exists()
assert not (core / ".env").exists()
assert not (home / ".hermes" / "skills").exists()
print(f"runtime-only bootstrap: indent={indent!r}, blank_lines={blank_lines}: exit {result.returncode}")
print(result.stdout)