resolve() on both sides also collapses a venv interpreter onto the store binary it links to, so a caller still running in its old venv stopped re-executing into the store interpreter (two tests in tests/pm/test_source_update_launch.py go red). The loop in #122513 is a spelling difference, not a symlink: PM spells the store Python through a HERMES_HOME that may contain '..', and the OS reports sys.executable normalized. normcase(abspath()) matches those spellings and keeps a venv interpreter distinct. Tests: the '..' spelling is current (red on main); a venv python symlinked to the store binary still relaunches (red with resolve()).
65 lines
2.5 KiB
Python
65 lines
2.5 KiB
Python
"""prepare_launch() recognizes the store interpreter it would re-exec into (#122513).
|
|
|
|
PM spells the store Python through HERMES_HOME, which may contain '..', while
|
|
the OS reports ``sys.executable`` normalized. The identity check must be
|
|
lexical: comparing the raw spellings relaunched every child forever, and
|
|
following symlinks would treat a venv interpreter as the store interpreter.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from hermes_cli import venv_sync
|
|
|
|
|
|
@pytest.fixture
|
|
def launch(tmp_path, monkeypatch):
|
|
import pm
|
|
from hermes_cli import _launchers
|
|
|
|
root = tmp_path / "checkout"
|
|
(root / ".git").mkdir(parents=True)
|
|
(root / "pyproject.toml").write_text("[project]\nname='example'\n", encoding="utf-8")
|
|
(root / "install-stamp.json").write_text('{"updateMechanism": "self"}', encoding="utf-8")
|
|
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "home"))
|
|
monkeypatch.delenv("HERMES_DISABLE_LAZY_INSTALLS", raising=False)
|
|
monkeypatch.setattr(pm, "venv_is_current", lambda **kw: True)
|
|
store_python = tmp_path / "store" / "cpython-3.14" / "bin" / "python3"
|
|
store_python.parent.mkdir(parents=True)
|
|
store_python.write_text("#!/bin/sh\n", encoding="utf-8")
|
|
published = []
|
|
monkeypatch.setattr(venv_sync, "publish_launchers", published.append)
|
|
|
|
def run(*, store_spelling: Path, executable: Path):
|
|
monkeypatch.setattr(_launchers, "resolve_store_python", lambda _: store_spelling)
|
|
monkeypatch.setattr(sys, "executable", str(executable))
|
|
return venv_sync.prepare_launch(root, []), published
|
|
|
|
return tmp_path, store_python, run
|
|
|
|
|
|
def test_store_python_spelled_through_dotdot_home_is_current(launch):
|
|
"""HERMES_HOME=<x>/work/../store...: the normalized sys.executable is the same interpreter."""
|
|
tmp_path, store_python, run = launch
|
|
dotted = tmp_path / "work" / ".." / store_python.relative_to(tmp_path)
|
|
(tmp_path / "work").mkdir()
|
|
|
|
assert run(store_spelling=dotted, executable=store_python) == (None, [])
|
|
|
|
|
|
@pytest.mark.platforms("posix")
|
|
def test_venv_python_symlinked_to_the_store_binary_still_relaunches(launch):
|
|
"""Same binary, different interpreter: the venv carries its own sys.prefix."""
|
|
tmp_path, store_python, run = launch
|
|
venv_python = tmp_path / "venv" / "bin" / "python"
|
|
venv_python.parent.mkdir(parents=True)
|
|
venv_python.symlink_to(store_python)
|
|
|
|
target, published = run(store_spelling=store_python, executable=venv_python)
|
|
assert target == store_python
|
|
assert published == [(tmp_path / "checkout").resolve()]
|