hermes_cli.runtime_paths (venv generations, selection, activation) moves to pm.environments, and gains venv_bin_dir / venv_python / project_python. Every in-tree caller asks pm for an interpreter now; pm no longer reaches back into hermes_cli for its own environment layout (pm.packages, pm.extras, pm.ensure, pm.paths imported hermes_cli.runtime_paths). The three open-coded "Scripts/python.exe or bin/python" ladders in pm collapse onto venv_python. hermes_constants.venv_python_path / venv_bin_dir and hermes_cli.runtime_paths stay as frozen-updater-surface shims only (tests/compat/old_updater_surface.json). To keep the boot path light, pm/__init__ resolves its facade lazily (PEP 562) and pm.registry loads the built-in package definitions on first read instead of at import: `import hermes_bootstrap` now loads pm + pm.environments only (25ms, was 37ms with the eager facade dragging in the downloader). The stripped-payload fixtures that ship only pre-import files keep working for the same reason. Also restores two frozen-surface re-exports the F401 sweep dropped (banner._github_compare_behind, cua_backend.resolve_cua_driver_cmd).
63 lines
2.6 KiB
Python
63 lines
2.6 KiB
Python
"""Process death cannot leave new plugin code paired with the old dependency selection."""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.parametrize("committed", [False, True])
|
|
def test_boot_recovers_plugin_publication_after_process_death(tmp_path, committed):
|
|
home = tmp_path / "home"
|
|
target = home / "plugins" / "example"
|
|
target.mkdir(parents=True)
|
|
(target / "__init__.py").write_text("old code", encoding="utf-8")
|
|
metadata = target.parent / ".install-metadata.json"
|
|
metadata.write_text('{"example":{"revision":"old"}}\n', encoding="utf-8")
|
|
previous = metadata.read_bytes()
|
|
staged = tmp_path / "staged"
|
|
staged.mkdir()
|
|
(staged / "__init__.py").write_text("new code", encoding="utf-8")
|
|
project = tmp_path / "project"
|
|
project.mkdir()
|
|
env = {**os.environ, "HERMES_HOME": str(home), "PYTHONPATH": str(Path(__file__).resolve().parents[2])}
|
|
program = '''
|
|
from pathlib import Path
|
|
import os,sys
|
|
from pm.publication import StagedPlugin
|
|
from pm.store import tree_digest
|
|
from pm.environments import runtime_facts_path
|
|
from pm.lock import Facts
|
|
project,staged,target = map(Path,sys.argv[1:4])
|
|
StagedPlugin({"staged": str(staged), "target": str(target), "target_digest": tree_digest(target),
|
|
"old_metadata": {"example":{"revision":"old"}},
|
|
"new_metadata": {"example":{"revision":"new"}}}).publish(project)
|
|
if sys.argv[4] == "True":
|
|
Facts(runtime_facts_path(project)).record_state("venv","new",[])
|
|
os._exit(17)
|
|
'''
|
|
child = subprocess.run([sys.executable, "-c", program, str(project), str(staged), str(target), str(committed)],
|
|
env=env, cwd=tmp_path, capture_output=True, text=True, timeout=30)
|
|
assert child.returncode == 17, child.stderr
|
|
recovery = '''
|
|
from pathlib import Path
|
|
import sys
|
|
from hermes_cli.runtime_state import runtime_lock,recover_publication
|
|
project=Path(sys.argv[1])
|
|
with runtime_lock(project):
|
|
recover_publication(project)
|
|
recover_publication(project)
|
|
'''
|
|
result = subprocess.run([sys.executable, "-c", recovery, str(project)], env=env, cwd=tmp_path,
|
|
capture_output=True, text=True, timeout=30)
|
|
assert result.returncode == 0, result.stderr
|
|
assert (target / "__init__.py").read_text(encoding="utf-8") == ("new code" if committed else "old code")
|
|
assert json.loads(metadata.read_text(encoding="utf-8"))["example"]["revision"] == ("new" if committed else "old")
|
|
if not committed:
|
|
assert metadata.read_bytes() == previous
|
|
assert not list(target.parent.glob(".previous-*"))
|