test(conftest): relocate pytest's basetemp when it sits inside the operator's Hermes home
Every per-test sandbox is <basetemp>/.../hermes_test, and get_default_hermes_root() prefers the platform-native home whenever HERMES_HOME sits under it. A basetemp inside ~/.hermes (pytest --basetemp, or TMPDIR/TEMP pointing there — the default on Windows, where the home is %LOCALAPPDATA%\hermes) therefore turned every sandbox back into the live install, and any test that resolves the default profile wrote fixtures over the operator's config.yaml, .env and MEMORY.md. Hook into pytest_configure after _pytest.tmpdir has built the TempPathFactory and move a basetemp that resolves under the native home to a fresh tempdir outside it (falling back to the repo's ignored .pytest_cache when the system temp dir is itself inside the home). The fix lives at the basetemp seam so it covers every test, not only the two writers in test_profiles.py, and needs no per-test fixture or opt-out marker. Fixes #111101
This commit is contained in:
@@ -1227,8 +1227,40 @@ _OS_MARKS = {
|
||||
}
|
||||
|
||||
|
||||
def _relocate_basetemp_outside_operator_home(config) -> None:
|
||||
"""Move pytest's basetemp out of the operator's platform-native Hermes home.
|
||||
|
||||
Every per-test sandbox is ``<basetemp>/.../hermes_test``. ``get_default_hermes_root()``
|
||||
prefers the platform-native home whenever ``HERMES_HOME`` sits *under* it, so a basetemp
|
||||
inside ``~/.hermes`` (or ``%LOCALAPPDATA%\\hermes``, where ``TEMP`` commonly lives on
|
||||
Windows) turns the sandbox back into the live install and ``get_profile_dir("default")``
|
||||
writes fixtures over the operator's config.yaml / .env / MEMORY.md (#111101).
|
||||
"""
|
||||
from hermes_constants import _get_platform_default_hermes_home
|
||||
|
||||
native = _get_platform_default_hermes_home().resolve()
|
||||
factory = config._tmp_path_factory
|
||||
given = factory._given_basetemp
|
||||
candidate = given if given is not None else Path(
|
||||
os.environ.get("PYTEST_DEBUG_TEMPROOT") or tempfile.gettempdir()
|
||||
)
|
||||
if not candidate.resolve().is_relative_to(native):
|
||||
return
|
||||
# The system temp dir may itself be inside the home (Windows TEMP under the
|
||||
# Hermes home); the repo's ignored cache dir is always outside it.
|
||||
fallback = PROJECT_ROOT / ".pytest_cache"
|
||||
safe_root = None if not Path(tempfile.gettempdir()).resolve().is_relative_to(native) else fallback
|
||||
if safe_root is not None:
|
||||
safe_root.mkdir(exist_ok=True)
|
||||
safe = Path(tempfile.mkdtemp(prefix="hermes-pytest-basetemp-", dir=safe_root))
|
||||
factory._given_basetemp = safe
|
||||
config.option.basetemp = str(safe)
|
||||
|
||||
|
||||
@pytest.hookimpl(trylast=True) # after _pytest.tmpdir has built config._tmp_path_factory
|
||||
def pytest_configure(config): # noqa: D401 — pytest hook
|
||||
"""Register markers used by hermetic conftest."""
|
||||
_relocate_basetemp_outside_operator_home(config)
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
f"{_LIVE_SYSTEM_GUARD_BYPASS_MARK}: bypass the live-system guard "
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""pytest's basetemp must never sit inside the operator's platform-native Hermes home.
|
||||
|
||||
Every per-test sandbox is ``<basetemp>/.../hermes_test`` and ``get_default_hermes_root()``
|
||||
prefers the platform-native home whenever ``HERMES_HOME`` sits *under* it — so a basetemp
|
||||
inside the home silently turns the sandbox back into the live install (#111101).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import hermes_constants
|
||||
from tests import conftest as suite_conftest
|
||||
|
||||
|
||||
def _config_with_basetemp(given: Path | None) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
_tmp_path_factory=SimpleNamespace(_given_basetemp=given),
|
||||
option=SimpleNamespace(basetemp=str(given) if given else None),
|
||||
)
|
||||
|
||||
|
||||
def test_basetemp_inside_the_native_home_is_relocated_outside_it(tmp_path, monkeypatch):
|
||||
native = tmp_path / "native-home"
|
||||
native.mkdir()
|
||||
monkeypatch.setattr(hermes_constants, "_get_platform_default_hermes_home", lambda: native)
|
||||
config = _config_with_basetemp(native / ".repro")
|
||||
|
||||
suite_conftest._relocate_basetemp_outside_operator_home(config)
|
||||
|
||||
relocated = config._tmp_path_factory._given_basetemp
|
||||
assert relocated is not None and not relocated.resolve().is_relative_to(native.resolve())
|
||||
assert config.option.basetemp == str(relocated)
|
||||
# The sandbox derived from it no longer resolves to the native root.
|
||||
monkeypatch.setenv("HERMES_HOME", str(relocated / "t0" / "hermes_test"))
|
||||
assert hermes_constants.get_default_hermes_root() == relocated / "t0" / "hermes_test"
|
||||
|
||||
|
||||
def test_basetemp_outside_the_native_home_is_left_alone(tmp_path, monkeypatch):
|
||||
native = tmp_path / "native-home"
|
||||
native.mkdir()
|
||||
monkeypatch.setattr(hermes_constants, "_get_platform_default_hermes_home", lambda: native)
|
||||
given = tmp_path / "elsewhere"
|
||||
config = _config_with_basetemp(given)
|
||||
|
||||
suite_conftest._relocate_basetemp_outside_operator_home(config)
|
||||
|
||||
assert config._tmp_path_factory._given_basetemp == given
|
||||
assert config.option.basetemp == str(given)
|
||||
Reference in New Issue
Block a user