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
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ $ uv add libvcs --prerelease allow
_Notes on the upcoming release will go here._
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

### Fixes

- libvcs's bundled pytest plugin no longer overrides pytest's own
collection-ignore rules; suites that build their docs before running
no longer abort on the generated `_build/` output (#544).

### Documentation

#### Documentation examples now run as doctests (#540)
Expand Down
24 changes: 18 additions & 6 deletions src/libvcs/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,18 +119,30 @@ def __next__(self) -> str:
namer = RandomStrSequence()


def pytest_ignore_collect(collection_path: pathlib.Path, config: pytest.Config) -> bool:
"""Skip tests if VCS binaries are missing."""
def pytest_ignore_collect(
collection_path: pathlib.Path,
config: pytest.Config,
) -> bool | None:
"""Skip collection of tests that require a missing VCS binary.

``pytest_ignore_collect`` is a ``firstresult`` hook: the first
implementation to return a non-``None`` value wins and short-circuits the
rest. This hook must therefore return ``None`` (abstain) for paths it has
no opinion on, so that other implementations -- gp-libs' Sphinx ``_build``
skip and pytest's own ``__pycache__``/``norecursedirs``/``collect_ignore``
handling -- still run. Returning ``False`` here would suppress all of them.
"""
if (not shutil.which("svn") or not shutil.which("svnadmin")) and any(
needle in str(collection_path) for needle in ["svn", "subversion"]
):
return True
if not shutil.which("git") and "git" in str(collection_path):
return True
return bool(
not shutil.which("hg")
and any(needle in str(collection_path) for needle in ["hg", "mercurial"]),
)
if not shutil.which("hg") and any(
needle in str(collection_path) for needle in ["hg", "mercurial"]
):
return True
return None


@pytest.fixture(scope="session")
Expand Down
45 changes: 45 additions & 0 deletions tests/test_pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pytest

from libvcs._internal.run import run
from libvcs.pytest_plugin import pytest_ignore_collect

if t.TYPE_CHECKING:
import pathlib
Expand Down Expand Up @@ -311,3 +312,47 @@ def test_git_repo_fixture_submodule_file_protocol(
f"git submodule add failed: {result}\n"
"git_repo fixture needs set_home dependency for child processes"
)


def test_pytest_ignore_collect_abstains_on_non_vcs_path(
pytestconfig: pytest.Config,
tmp_path: pathlib.Path,
) -> None:
"""Non-VCS paths abstain (``None``) rather than voting ``False``.

``pytest_ignore_collect`` is a ``firstresult`` hook, so returning ``False``
would short-circuit the chain and suppress gp-libs' Sphinx ``_build`` skip
and pytest's builtin ignores. A path mentioning no VCS must yield ``None``
regardless of which VCS binaries are installed.
"""
build_artifact = tmp_path / "docs" / "_build" / "html" / "history.md"

assert pytest_ignore_collect(build_artifact, config=pytestconfig) is None


def test_pytest_ignore_collect_ignores_missing_vcs(
pytestconfig: pytest.Config,
monkeypatch: pytest.MonkeyPatch,
tmp_path: pathlib.Path,
) -> None:
"""Paths for a missing VCS binary are ignored (``True``)."""
monkeypatch.setattr(
shutil,
"which",
lambda cmd, *args, **kwargs: None if cmd == "git" else f"/usr/bin/{cmd}",
)
git_test = tmp_path / "tests" / "sync" / "test_git.py"

assert pytest_ignore_collect(git_test, config=pytestconfig) is True


def test_pytest_ignore_collect_abstains_when_binaries_present(
pytestconfig: pytest.Config,
monkeypatch: pytest.MonkeyPatch,
tmp_path: pathlib.Path,
) -> None:
"""A VCS path abstains when its binary is present, deferring to others."""
monkeypatch.setattr(shutil, "which", lambda cmd, *args, **kwargs: f"/usr/bin/{cmd}")
git_test = tmp_path / "tests" / "sync" / "test_git.py"

assert pytest_ignore_collect(git_test, config=pytestconfig) is None