diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 2b9218e7e78..b26173d74a0 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -846,6 +846,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: argpath = collection_argument.path names = collection_argument.parts + module_name = collection_argument.module_name # resolve_collection_argument() ensures this. if argpath.is_dir(): @@ -854,11 +855,20 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: paths = [argpath] # Add relevant parents of the path, from the root, e.g. # /a/b/c.py -> [/, /a, /a/b, /a/b/c.py] - # Paths outside of the confcutdir should not be considered. - for path in argpath.parents: - if not pm._is_in_confcutdir(path): - break - paths.insert(0, path) + if module_name is None: + # Paths outside of the confcutdir should not be considered. + for path in argpath.parents: + if not pm._is_in_confcutdir(path): + break + paths.insert(0, path) + else: + # For --pyargs arguments, only consider paths matching the module + # name. Paths beyond the package hierarchy are not included. + module_name_parts = module_name.split(".") + for i, path in enumerate(argpath.parents, 2): + if i > len(module_name_parts) or path.stem != module_name_parts[-i]: + break + paths.insert(0, path) # Start going over the parts from the root, collecting each level # and discarding all nodes which don't match the level's part. diff --git a/testing/test_collection.py b/testing/test_collection.py index 0507400455c..c79db0441b6 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1787,3 +1787,48 @@ def test_collect_short_file_windows(pytester: Pytester) -> None: test_file.write_text("def test(): pass", encoding="UTF-8") result = pytester.runpytest(short_path) assert result.parseoutcomes() == {"passed": 1} + + +def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + """When using `--pyargs`, the collection tree of a pyargs collection + argument should only parents in the import tree, not up to confcutdir. + + Regression test for #11904. + """ + site_packages = pytester.path / "venv/lib/site-packages" + site_packages.mkdir(parents=True) + monkeypatch.syspath_prepend(site_packages) + pytester.makepyfile( + **{ + "venv/lib/site-packages/pkg/__init__.py": "", + "venv/lib/site-packages/pkg/sub/__init__.py": "", + "venv/lib/site-packages/pkg/sub/test_it.py": "def test(): pass", + } + ) + + result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + ], + consecutive=True, + ) + + # Now with an unrelated rootdir with unrelated files. + monkeypatch.chdir(tempfile.gettempdir()) + + result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + [ + "", + " ", + " ", + " ", + ], + consecutive=True, + )