Skip to content
Closed
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 AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Ariel Pillemer
Armin Rigo
Aron Coyle
Aron Curzon
Arron Zou
Arthur Richard
Ashish Kurmi
Ashley Whetter
Expand Down
1 change: 1 addition & 0 deletions changelog/13319.bugfix.rst
Original file line number Diff line number Diff line change
@@ -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.
47 changes: 46 additions & 1 deletion testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(
[
"<Dir *>",
" <Dir parent>",
" <Dir sub1>",
" <Module test_sub1.py>",
" <Function test_sub1>",
" <Dir sub2>",
" <Module test_sub2.py>",
" <Function test_sub2>",
" <Dir sub3>",
" <Module test_sub3.py>",
" <Function test_sub3>",
"",
],
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(
Expand Down
36 changes: 36 additions & 0 deletions testing/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).

Expand Down