diff --git a/AUTHORS b/AUTHORS index 47e310c2340..fff5a2fef76 100644 --- a/AUTHORS +++ b/AUTHORS @@ -58,6 +58,7 @@ Ariel Pillemer Armin Rigo Aron Coyle Aron Curzon +Arron Zou Arthur Richard Ashish Kurmi Ashley Whetter diff --git a/changelog/13319.bugfix.rst b/changelog/13319.bugfix.rst new file mode 100644 index 00000000000..9a4a8f67567 --- /dev/null +++ b/changelog/13319.bugfix.rst @@ -0,0 +1 @@ +Fixed test collection when both a parent directory and one of its subfolders are specified on the command line, so tests in sibling folders are collected as well. diff --git a/testing/test_collection.py b/testing/test_collection.py index 093162ddec4..9d4ec5a2e8e 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -2038,7 +2038,7 @@ def test_namespace_packages(pytester: Pytester, import_mode: str): class TestOverlappingCollectionArguments: """Test that overlapping collection arguments (e.g. `pytest a/b a - a/c::TestIt) are handled correctly (#12083).""" + a/c::TestIt) are handled correctly (#12083, #13319).""" @pytest.mark.parametrize("args", [("a", "a/b"), ("a/b", "a")]) def test_parent_child(self, pytester: Pytester, args: tuple[str, ...]) -> None: @@ -2074,6 +2074,51 @@ def test_b2(): pass consecutive=True, ) + @pytest.mark.parametrize( + "args", + [ + ("parent/sub1", "parent/"), + ("parent/", "parent/sub1"), + ("parent/sub1", "parent"), + ("parent", "parent/sub1"), + ], + ) + def test_parent_and_subfolder_collects_sibling_dirs( + self, pytester: Pytester, args: tuple[str, ...] + ) -> None: + """Collecting a parent dir plus one subfolder must include sibling folders. + + The parent directory has no tests of its own — only tests in subfolders. + Regression test for #13319. + """ + pytester.makepyfile( + **{ + "parent/sub1/test_sub1.py": "def test_sub1(): pass", + "parent/sub2/test_sub2.py": "def test_sub2(): pass", + "parent/sub3/test_sub3.py": "def test_sub3(): pass", + } + ) + + result = pytester.runpytest("--collect-only", *args) + + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + "", + ], + consecutive=True, + ) + def test_multiple_nested_paths(self, pytester: Pytester) -> None: """Test that 'pytest a/b a a/b/c' collects all tests from 'a'.""" pytester.makepyfile( diff --git a/testing/test_main.py b/testing/test_main.py index 41d7055df26..0ec08bb8788 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -9,6 +9,7 @@ from _pytest.config import ExitCode from _pytest.config import UsageError from _pytest.main import CollectionArgument +from _pytest.main import normalize_collection_arguments from _pytest.main import resolve_collection_argument from _pytest.main import validate_basetemp from _pytest.pytester import Pytester @@ -302,6 +303,41 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N ) +class TestNormalizeCollectionArguments: + def _arg( + self, path: Path, index: int, parts: tuple[str, ...] = () + ) -> CollectionArgument: + return CollectionArgument( + path=path, + parts=parts, + parametrization=None, + module_name=None, + original_index=index, + ) + + def test_parent_dir_subsumes_child_dir(self, tmp_path: Path) -> None: + """A parent directory argument must replace a more specific subfolder. + + Regression test for #13319: `pytest parent/sub1 parent/` should collect + as if only `parent/` was given, so sibling folders are not skipped. + """ + parent = tmp_path / "parent" + child = parent / "sub1" + args = [self._arg(child, 0), self._arg(parent, 1)] + assert normalize_collection_arguments(args) == [self._arg(parent, 1)] + + args = [self._arg(parent, 0), self._arg(child, 1)] + assert normalize_collection_arguments(args) == [self._arg(parent, 0)] + + def test_sibling_dirs_are_kept(self, tmp_path: Path) -> None: + parent = tmp_path / "parent" + args = [ + self._arg(parent / "sub1", 0), + self._arg(parent / "sub2", 1), + ] + assert normalize_collection_arguments(args) == args + + def test_module_full_path_without_drive(pytester: Pytester) -> None: """Collect and run test using full path except for the drive letter (#7628).