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).