Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 63 additions & 0 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -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 ``<tmp>`` 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
2 changes: 2 additions & 0 deletions tests/unit/test_update_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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."""
Expand Down
Loading