From b94bb2229ba57f7afb6b026a3fd7f0abca661b1e Mon Sep 17 00:00:00 2001 From: Eryx-Z Date: Sat, 17 Oct 2026 07:57:17 +0800 Subject: [PATCH 1/3] Cache directory collection when multiple file args share a parent Session.collect disabled the collection cache for a file's parent Directory whenever the remaining match part was a single file path. That made N explicit file arguments from the same directory re-walk the directory N times (quadratic in directory size). Keep using the cache for Directory nodes unless --keep-duplicates is set; file-level non-dedup is already handled in genitems. Fixes #15187. --- src/_pytest/main.py | 12 +++++++++++- testing/test_collection.py | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d43a68b4679..ab754a96449 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -992,11 +992,21 @@ def collect(self) -> Iterator[nodes.Item | nodes.Collector]: else: # For backward compat, files given directly multiple # times on the command line should not be deduplicated. - handle_dupes = not ( + # That intent applies to File nodes (handled in genitems); + # Directory/Package parents must still use the collection + # cache, otherwise N file args from the same directory + # re-walk the directory N times (#15187). Keep the cache + # off under --keep-duplicates so a file named twice still + # gets two Module nodes. + file_arg_leaf = ( len(matchparts) == 1 and isinstance(matchparts[0], Path) and matchparts[0].is_file() ) + handle_dupes = ( + isinstance(matchnode, nodes.Directory) + and not self.config.option.keepduplicates + ) or not file_arg_leaf rep, duplicate = self._collect_one_node(matchnode, handle_dupes) if not duplicate and not rep.passed: # Report collection failures here to avoid failing to diff --git a/testing/test_collection.py b/testing/test_collection.py index f7cdc60a1c3..9834e1e58a8 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -2807,3 +2807,27 @@ def test1(x): "Duplicates: a", ] ) + + +def test_directory_collection_cached_for_multiple_file_args(pytester: Pytester) -> None: + """File args from one directory must not re-collect that directory each time. + + See https://github.com/pytest-dev/pytest/issues/15187 + """ + calls: dict[str, int] = {} + + class Plugin: + def pytest_collect_file(self, file_path: Path) -> None: + calls[file_path.name] = calls.get(file_path.name, 0) + 1 + + for i in range(5): + pytester.makepyfile(**{f"test_{i:04d}": "def test_x():\n pass\n"}) + + files = [pytester.path / f"test_{i:04d}.py" for i in range(5)] + result = pytester.runpytest( + "--collect-only", "-q", "-p", "no:cacheprovider", *map(str, files), plugins=[Plugin()] + ) + assert result.ret == 0 + # Each file offered once (plus possibly non-test files; assert the repeated ones) + for i in range(5): + assert calls.get(f"test_{i:04d}.py", 0) == 1, calls From eb2baaa0634d434181cab98f6433348a5eac5060 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 23:59:08 +0000 Subject: [PATCH 2/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_collection.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testing/test_collection.py b/testing/test_collection.py index 9834e1e58a8..881c560ff59 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -2825,7 +2825,12 @@ def pytest_collect_file(self, file_path: Path) -> None: files = [pytester.path / f"test_{i:04d}.py" for i in range(5)] result = pytester.runpytest( - "--collect-only", "-q", "-p", "no:cacheprovider", *map(str, files), plugins=[Plugin()] + "--collect-only", + "-q", + "-p", + "no:cacheprovider", + *map(str, files), + plugins=[Plugin()], ) assert result.ret == 0 # Each file offered once (plus possibly non-test files; assert the repeated ones) From 67e984f7135e7996bda0496790ac93893620af4b Mon Sep 17 00:00:00 2001 From: Eryx-Z <41898282+Eryx-Z@users.noreply.github.com> Date: Sat, 17 Oct 2026 09:44:37 +0800 Subject: [PATCH 3/3] Add changelog entry for directory collection cache --- changelog/15187.bugfix.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/15187.bugfix.rst diff --git a/changelog/15187.bugfix.rst b/changelog/15187.bugfix.rst new file mode 100644 index 00000000000..27f285e2804 --- /dev/null +++ b/changelog/15187.bugfix.rst @@ -0,0 +1,3 @@ +Directory collection is now cached when multiple file path arguments share a +parent directory (unless ``--keep-duplicates`` is set), avoiding repeated +directory scans during collection.