diff --git a/AUTHORS b/AUTHORS index aa32b21857e..fb530d8957e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -212,6 +212,7 @@ Harshna Henk-Jaap Wagenaar Henry Schreiner Holger Kohr +Huashuo Lei Hugo van Kemenade Hui Wang (coldnight) Ian Bicking diff --git a/changelog/15251.bugfix.rst b/changelog/15251.bugfix.rst new file mode 100644 index 00000000000..ec2c2fb00e5 --- /dev/null +++ b/changelog/15251.bugfix.rst @@ -0,0 +1 @@ +Reused a parent directory's collection results when multiple test files from that directory are supplied as explicit command-line arguments. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d43a68b4679..1135cd34b95 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -990,10 +990,13 @@ def collect(self) -> Iterator[nodes.Item | nodes.Collector]: assert isinstance(matchparts[0], Path) subnodes = matchnode._collect_path(matchparts[0], path_cache) else: - # For backward compat, files given directly multiple - # times on the command line should not be deduplicated. + # A file argument reaches this collector as its final path + # part, but this is still the parent directory. Reuse the + # cached collection unless --keep-duplicates needs a fresh + # parent collection for an overlapping file argument. handle_dupes = not ( - len(matchparts) == 1 + self.config.getoption("keepduplicates") + and len(matchparts) == 1 and isinstance(matchparts[0], Path) and matchparts[0].is_file() ) diff --git a/testing/test_collection.py b/testing/test_collection.py index f7cdc60a1c3..7eec42ffde4 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -2142,6 +2142,35 @@ def test_a(): pass consecutive=True, ) + def test_explicit_file_arguments_collect_shared_directory_once( + self, pytester: Pytester + ) -> None: + files = {f"tests/test_{i}.py": "def test_example(): pass" for i in range(8)} + pytester.makepyfile(**files) + pytester.makeconftest( + """ + from collections import Counter + from pathlib import Path + + collected = Counter() + + def pytest_collect_file(file_path, parent): + if file_path.name.startswith("test_"): + collected[file_path.name] += 1 + + def pytest_sessionfinish(session, exitstatus): + Path("collect-counts.txt").write_text(str(sum(collected.values())), encoding="utf-8") + """ + ) + + file_args = [f"tests/test_{i}.py" for i in range(4)] + result = pytester.runpytest_subprocess("--collect-only", *file_args) + + assert result.ret == ExitCode.OK + assert int( + pytester.path.joinpath("collect-counts.txt").read_text(encoding="utf-8") + ) == len(files) + def test_keep_duplicates_flag(self, pytester: Pytester) -> None: """Test that --keep-duplicates allows duplication.""" pytester.makepyfile(