From f829a1ef066825487dbe831b4076ef8fb1ec6bda Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 18 Feb 2024 08:04:42 -0300 Subject: [PATCH] Fix PytestPluginManager._is_in_confcutdir on Windows On Windows two paths might be in different drives, so if a path is not one of the parents of the confcutdir, it does not necessarily mean it is inside the confcutdir either. Use `Path.is_relative_to` instead which directly answers that question for us. The previous implementation could cause pytest to inspect all the parent paths of a path from one drive if pytest was invoked from another drive. Moved the implementation to a function because it would be too convoluted to test the `PytestPluginManager._is_in_confcutdir` method directly. --- src/_pytest/config/__init__.py | 16 +++++----- testing/test_pluginmanager.py | 55 +++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 3d448214c..1e4cda856 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -580,11 +580,9 @@ class PytestPluginManager(PluginManager): def _is_in_confcutdir(self, path: Path) -> bool: """Whether a path is within the confcutdir. - When false, should not load conftest. + When false, should not load conftest or recurse into path for collection. """ - if self._confcutdir is None: - return True - return path not in self._confcutdir.parents + return path_within_confcutdir(path=path, confcutdir=self._confcutdir) def _try_load_conftest( self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path @@ -609,9 +607,6 @@ class PytestPluginManager(PluginManager): if directory in self._dirpath2confmods: return - # XXX these days we may rather want to use config.rootpath - # and allow users to opt into looking into the rootdir parent - # directories instead of requiring to specify confcutdir. clist = [] for parent in reversed((directory, *directory.parents)): if self._is_in_confcutdir(parent): @@ -1908,3 +1903,10 @@ def apply_warning_filters( for arg in cmdline_filters: warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) + + +def path_within_confcutdir(*, path: Path, confcutdir: Optional[Path]) -> bool: + # Extracted into a function for unit-testing. + if confcutdir is None: + return True + return path.is_relative_to(confcutdir) diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index f68f143f4..a68536234 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,12 +1,15 @@ # mypy: allow-untyped-defs import os +from pathlib import Path import shutil import sys import types from typing import List +from typing import Optional from _pytest.config import Config from _pytest.config import ExitCode +from _pytest.config import path_within_confcutdir from _pytest.config import PytestPluginManager from _pytest.config.exceptions import UsageError from _pytest.main import Session @@ -404,7 +407,7 @@ class TestPytestPluginManager: pytestpm.consider_conftest(mod, registration_name="unused") -class TestPytestPluginManagerBootstrapming: +class TestPytestPluginManagerBootstrapping: def test_preparse_args(self, pytestpm: PytestPluginManager) -> None: pytest.raises( ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) @@ -464,3 +467,53 @@ class TestPytestPluginManagerBootstrapming: assert pytestpm.has_plugin("abc") assert not pytestpm.is_blocked("abc") assert not pytestpm.is_blocked("pytest_abc") + + +skip_if_win = pytest.mark.skipif( + not sys.platform.startswith("win"), reason="Windows only" +) + + +@pytest.mark.parametrize( + "path, confcutdir, expected", + [ + (Path("/projects/app/tests"), Path("/projects/app"), True), + (Path("/projects/app"), Path("/projects/app"), True), + (Path("/projects"), Path("/projects/app"), False), + (Path("/"), Path("/projects/app"), False), + pytest.param( + Path("e:/projects/app/tests"), + Path("e:/projects/app"), + True, + marks=skip_if_win, + ), + pytest.param( + Path("e:/projects/app"), + Path("e:/projects/app"), + True, + marks=skip_if_win, + ), + pytest.param( + Path("e:/"), + Path("e:/projects/app"), + False, + marks=skip_if_win, + ), + pytest.param( + Path("c:/testing"), + Path("e:/projects/app"), + False, + marks=skip_if_win, + ), + pytest.param( + Path("c:/projects/app"), + Path("e:/projects/app"), + False, + marks=skip_if_win, + ), + ], +) +def test_path_within_confcutdir( + path: Path, confcutdir: Optional[Path], expected: bool +) -> None: + assert path_within_confcutdir(path=path, confcutdir=confcutdir) == expected