diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b612c26e..09c55d10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Pin `Path.home()` under unit tests via a session-scoped autouse conftest fixture, fixing 56 Windows runner failures on the new `windows-2025-vs2026` GitHub-hosted image where `USERPROFILE`/`HOMEDRIVE`+`HOMEPATH` are not seeded for pytest workers; also patch the `_check_and_notify_updates` import binding in the disabled-self-update test so it no longer races on the version-check cache. (#1270) + ## [0.13.0] - 2026-05-11 ### Breaking Changes diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 000000000..ba9a55ef6 --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,63 @@ +"""Unit-test conftest: hermetic HOME isolation. + +The session-scoped autouse fixture below pins ``Path.home()`` to a tmp +directory for the entire unit-test session. Two reasons: + +1. Hermeticity. Unit tests must not read or write the contributor's real + ``~`` (config files, runtimes, caches). Tests that call + ``RuntimeManager()`` or ``Path.home()`` directly previously inherited + whatever HOME the runner had. +2. Windows runner robustness. On the ``windows-2025-vs2026`` GitHub + Actions image the ``USERPROFILE`` / ``HOMEDRIVE`` + ``HOMEPATH`` + triplet is not set under the pytest worker subprocess, so + ``Path.home()`` raises ``RuntimeError: Could not determine home + directory.`` This fixture sets the platform-correct trio so + ``Path.home()`` always resolves to ```` regardless of runner + image. + +Per-test fixtures that need a different HOME (e.g. tests/unit/integration +that exercise scope resolution) keep using ``monkeypatch.setenv`` and +override this baseline; the function-scoped monkeypatch wins over the +session-scoped baseline for the duration of the test. +""" + +from __future__ import annotations + +import os +from pathlib import Path + +import pytest + + +def _set_home_env(home: Path) -> None: + """Set ``HOME`` and the Windows-equivalent vars to ``home``. + + ``Path.home()`` consults ``HOME`` on POSIX but ``USERPROFILE`` + (with ``HOMEDRIVE`` + ``HOMEPATH`` fallback) on Windows. + """ + home_str = str(home) + os.environ["HOME"] = home_str + if os.name == "nt": + os.environ["USERPROFILE"] = home_str + drive, _, tail = home_str.partition(":") + if tail: + os.environ["HOMEDRIVE"] = f"{drive}:" + os.environ["HOMEPATH"] = tail + + +@pytest.fixture(scope="session", autouse=True) +def _hermetic_home(tmp_path_factory: pytest.TempPathFactory) -> None: + """Pin ``Path.home()`` to a per-session tmp dir for all unit tests.""" + home = tmp_path_factory.mktemp("apm-unit-home") + previous = { + key: os.environ.get(key) for key in ("HOME", "USERPROFILE", "HOMEDRIVE", "HOMEPATH") + } + _set_home_env(home) + try: + yield + finally: + for key, value in previous.items(): + if value is None: + os.environ.pop(key, None) + else: + os.environ[key] = value diff --git a/tests/unit/test_update_command.py b/tests/unit/test_update_command.py index 275dbf178..2236d2af5 100644 --- a/tests/unit/test_update_command.py +++ b/tests/unit/test_update_command.py @@ -40,6 +40,7 @@ def test_manual_update_command_uses_windows_installer(self): self.assertIn("powershell", command.lower()) @patch("apm_cli.commands.self_update.is_self_update_enabled", return_value=False) + @patch("apm_cli.commands._helpers.is_self_update_enabled", return_value=False) @patch( "apm_cli.commands.self_update.get_self_update_disabled_message", return_value="Update with: pixi update apm-cli", @@ -51,6 +52,7 @@ def test_update_command_respects_disabled_policy( mock_get, mock_run, mock_message, + mock_enabled_helpers, mock_enabled, ): """Disabled self-update policy should print guidance and skip installer."""