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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- 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)
- `apm install` now works on macOS git 2.53.0 (Homebrew): bare-cache commands switch to `--git-dir` to satisfy the `safe.bareRepository=explicit` default; fetched SHAs are pinned as synthetic refs so `git clone --local --shared` no longer silently omits them. (#1268)
- Set the unit-test hermetic HOME at conftest import time so a single xdist worker on the `windows-2025-vs2026` runner can no longer race fixture setup and re-trigger the 53 `Path.home()` failures the session-scoped autouse fixture was supposed to prevent. (#1271)

## [0.13.0] - 2026-05-11

Expand Down
51 changes: 24 additions & 27 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
"""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:
This module sets ``HOME`` (POSIX) / ``USERPROFILE`` + ``HOMEDRIVE`` +
``HOMEPATH`` (Windows) to a process-wide tmp directory **at import
time**, before any fixture or test resolution. 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
triplet is not set under the pytest-xdist 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.
directory.``

Why import-time and not a session-scoped autouse fixture: a previous
attempt used ``@pytest.fixture(scope="session", autouse=True)`` and
still failed on a single xdist worker (``gw2`` on Windows). Whatever
the cause (worker scheduling order, a downstream fixture resolving
``Path.home()`` before the autouse fixture's setup completed), running
the env mutation at conftest import time guarantees the env vars are
in place before pytest collects, schedules, or runs anything in this
worker process. Pytest imports each test directory's conftest.py once
per worker, before any fixtures run.

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.
override this baseline; monkeypatch's snapshot/restore cycle preserves
this baseline across tests.
"""

from __future__ import annotations

import os
import tempfile
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.
(with ``HOMEDRIVE`` + ``HOMEPATH`` fallback) on Windows. We
overwrite unconditionally because the Windows runner sometimes
leaves these keys present but empty, which still trips
``Path.home()``.
"""
home_str = str(home)
os.environ["HOME"] = home_str
Expand All @@ -45,19 +56,5 @@ def _set_home_env(home: Path) -> None:
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
_TMP_HOME = Path(tempfile.mkdtemp(prefix="apm-unit-home-"))
_set_home_env(_TMP_HOME)
Comment on lines +59 to +60
Loading