Files
hermes-agent/tests/pm/test_bootstrap_import_closure.py
Teknium 6e1a8e057a fix(home): mkdir_under_hermes_home always takes the elevated-grant path; no ImportError fallback
mkdir_under_hermes_home fell back to plain mkdir when hermes_dir_acl
could not be imported, so whether an elevated run left the dir usable
by the same user depended on which files happened to be present (the
same fact that broke the two minimal-snapshot tests). hermes_dir_acl is
stdlib-only; no launcher/recovery closure module calls this function
(only hermes_logging, plugins and tools do), so it now imports it
unconditionally (still lazily, so hermes_constants imports alone).

tests/pm/test_bootstrap_import_closure.py pins both halves in a -S
staged closure: hermes_constants + hermes_dir_acl create a home dir,
hermes_constants alone still imports, and a missing hermes_dir_acl is a
loud ImportError rather than a silent plain mkdir (red before).

Review finding by @alt-glitch on #140490 (suggestion 3)
Refs #134889 (W8)
Refs #140337
2026-10-17 08:03:54 -07:00

128 lines
5.5 KiB
Python

"""The tool-only build stage imports PM without third-party dependencies."""
import os
from pathlib import Path
import shutil
import subprocess
import sys
import pytest
def test_minimal_bootstrap_closure_reaches_pm_paths_and_locks(tmp_path):
repo = Path(__file__).resolve().parents[2]
stage = tmp_path / "stage"
shutil.copytree(repo / "pm", stage / "pm", ignore=shutil.ignore_patterns("__pycache__"))
shutil.copy2(repo / "hermes_constants.py", stage / "hermes_constants.py")
(stage / "hermes_cli").mkdir()
for name in ("__init__.py", "runtime_state.py"):
shutil.copy2(repo / "hermes_cli" / name, stage / "hermes_cli" / name)
store = stage / "tools"
env = dict(os.environ, HERMES_HOME=str(tmp_path / "home"),
HERMES_RUNTIME_DIR=str(store), PYTHONPATH=str(stage))
script = """
from pathlib import Path
import pm.paths
from pm.store import Store
from pm.lock import Facts
root = pm.paths.store_root()
with Store(root).install_lock():
facts = Facts(root / 'facts.json')
facts.record_state('probe', 'checked', [])
assert facts.path.is_file()
print(root)
"""
result = subprocess.run([sys.executable, "-S", "-c", script], cwd=stage, env=env,
capture_output=True, text=True, timeout=15, check=False)
assert result.returncode == 0, result.stdout + result.stderr
assert Path(result.stdout.strip()) == store
def _constants_stage(tmp_path, *, with_acl: bool) -> Path:
repo = Path(__file__).resolve().parents[2]
stage = tmp_path / "stage"
stage.mkdir()
names = ["hermes_constants.py", "hermes_dir_acl.py"] if with_acl else ["hermes_constants.py"]
for name in names:
shutil.copy2(repo / name, stage / name)
return stage
def _mkdir_in_stage(stage: Path, home: Path) -> subprocess.CompletedProcess:
script = "import hermes_constants, sys; print(hermes_constants.mkdir_under_hermes_home(sys.argv[1]))"
return subprocess.run([sys.executable, "-S", "-c", script, str(home / "logs")], cwd=stage,
env=dict(os.environ, HERMES_HOME=str(home), PYTHONPATH=str(stage)),
capture_output=True, text=True, timeout=15, check=False)
def test_home_mkdir_carries_the_acl_module_in_the_closure(tmp_path):
"""hermes_dir_acl is stdlib-only and ships beside hermes_constants: the two together create a
home dir, and hermes_constants still imports alone (the import is lazy)."""
home = tmp_path / "home"
home.mkdir()
result = _mkdir_in_stage(_constants_stage(tmp_path, with_acl=True), home)
assert result.returncode == 0, result.stderr
assert (home / "logs").is_dir()
def test_home_mkdir_never_silently_skips_the_elevated_grant(tmp_path):
"""Review #140490: an ImportError fallback to plain mkdir made whether an elevated run grants
the user access depend on which files happened to be present. A missing module is loud."""
home = tmp_path / "home"
home.mkdir()
stage = _constants_stage(tmp_path, with_acl=False)
alone = subprocess.run([sys.executable, "-S", "-c", "import hermes_constants"], cwd=stage,
env=dict(os.environ, PYTHONPATH=str(stage)), capture_output=True, text=True,
timeout=15, check=False)
assert alone.returncode == 0, alone.stderr
result = _mkdir_in_stage(stage, home)
assert result.returncode != 0 and "hermes_dir_acl" in result.stderr, result.stdout + result.stderr
def test_managed_python_signing_import_needs_no_pm_or_application(tmp_path):
repo = Path(__file__).resolve().parents[2]
script = """
import sys
class NoApplication:
def find_spec(self, fullname, path=None, target=None):
if fullname == 'pm' or fullname == 'utils' or fullname.startswith(('pm.', 'agent.')):
raise AssertionError('signing imported ' + fullname)
sys.meta_path.insert(0, NoApplication())
from hermes_cli.macos_signing import sign_managed_python
assert callable(sign_managed_python)
"""
result = subprocess.run([sys.executable, "-S", "-c", script], cwd=repo,
env=dict(os.environ, HERMES_HOME=str(tmp_path / "home")),
capture_output=True, text=True, timeout=15, check=False)
assert result.returncode == 0, result.stderr
@pytest.mark.platforms("linux", "macos", "windows")
def test_runtime_staging_streams_uv_output_without_tomllib(tmp_path):
"""Bootstrap runs before PM selects its Python: Docker has 3.10 and
historical Windows updaters have 3.11, without os.set_blocking for pipes.
"""
repo = Path(__file__).resolve().parents[2]
script = """
import os
import sys
if sys.platform == 'win32' and hasattr(os, 'set_blocking'):
del os.set_blocking
class NoTomllib:
def find_spec(self, fullname, path=None, target=None):
if fullname in ('tomllib', 'pm.workspace', 'pm.plugin_declarations'):
raise AssertionError('runtime staging imported ' + fullname)
sys.meta_path.insert(0, NoTomllib())
import pm.runtime_stage
from pm.environment import _run_streaming
import subprocess
result = _run_streaming([sys.executable, '-c', 'print(\"no solution found\")'],
cwd='.', env={}, timeout=30, output=sys.stderr)
assert result.returncode == 0, result
"""
result = subprocess.run([sys.executable, "-S", "-c", script], cwd=repo,
env=dict(os.environ, HERMES_HOME=str(tmp_path / "home")),
capture_output=True, text=True, timeout=60, check=False)
assert result.returncode == 0, result.stdout + result.stderr