Skip to content

Commit 31026a2

Browse files
committed
main: only include package parents in collection tree for --pyargs collection arguments
(diff better viewed ignoring whitespace) In pytest<8, the collection tree for `pyargs` arguments in an invocation like this: pytest --collect-only --pyargs pyflakes.test.test_undefined_names looked like this: ``` <Package test> <Module test_undefined_names.py> <UnitTestCase Test> <TestCaseFunction test_annotationUndefined> ... snipped ... ``` The pytest 8 collection improvements changed it to this: ``` <Dir pytest> <Dir .tox> <Dir venv> <Dir lib> <Dir python3.11> <Dir site-packages> <Package pyflakes> <Package test> <Module test_undefined_names.py> <UnitTestCase Test> <TestCaseFunction test_annotationUndefined> ... snipped ... ``` Besides being egregious (and potentially even worse than the above, going all the way to the root, for system-installed packages, as is apparently common in CI), this also caused permission errors when trying to probe some of those intermediate directories. This change makes `--pyargs` arguments no longer try to add parent directories to the collection tree according to the `--confcutdir` like they're regular arguments. Instead, only add the parents that are in the import path. This now looks like this: ``` <Package .tox/venv/lib/python3.11/site-packages/pyflakes> <Package test> <Module test_undefined_names.py> <UnitTestCase Test> <TestCaseFunction test_annotationUndefined> ... snipped ... ``` Fix #11904.
1 parent f20e32c commit 31026a2

File tree

3 files changed

+63
-5
lines changed

3 files changed

+63
-5
lines changed

Diff for: changelog/11904.bugfix.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fixed a regression in pytest 8.0.0 that would cause test collection to fail due to permission errors when using ``--pyargs``.
2+
3+
This change improves the collection tree for tests specified using ``--pyargs``, see :pull:`12043` for a comparison with pytest 8.0 and <8.

Diff for: src/_pytest/main.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
846846

847847
argpath = collection_argument.path
848848
names = collection_argument.parts
849+
module_name = collection_argument.module_name
849850

850851
# resolve_collection_argument() ensures this.
851852
if argpath.is_dir():
@@ -854,11 +855,20 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
854855
paths = [argpath]
855856
# Add relevant parents of the path, from the root, e.g.
856857
# /a/b/c.py -> [/, /a, /a/b, /a/b/c.py]
857-
# Paths outside of the confcutdir should not be considered.
858-
for path in argpath.parents:
859-
if not pm._is_in_confcutdir(path):
860-
break
861-
paths.insert(0, path)
858+
if module_name is None:
859+
# Paths outside of the confcutdir should not be considered.
860+
for path in argpath.parents:
861+
if not pm._is_in_confcutdir(path):
862+
break
863+
paths.insert(0, path)
864+
else:
865+
# For --pyargs arguments, only consider paths matching the module
866+
# name. Paths beyond the package hierarchy are not included.
867+
module_name_parts = module_name.split(".")
868+
for i, path in enumerate(argpath.parents, 2):
869+
if i > len(module_name_parts) or path.stem != module_name_parts[-i]:
870+
break
871+
paths.insert(0, path)
862872

863873
# Start going over the parts from the root, collecting each level
864874
# and discarding all nodes which don't match the level's part.

Diff for: testing/test_collection.py

+45
Original file line numberDiff line numberDiff line change
@@ -1787,3 +1787,48 @@ def test_collect_short_file_windows(pytester: Pytester) -> None:
17871787
test_file.write_text("def test(): pass", encoding="UTF-8")
17881788
result = pytester.runpytest(short_path)
17891789
assert result.parseoutcomes() == {"passed": 1}
1790+
1791+
1792+
def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
1793+
"""When using `--pyargs`, the collection tree of a pyargs collection
1794+
argument should only include parents in the import path, not up to confcutdir.
1795+
1796+
Regression test for #11904.
1797+
"""
1798+
site_packages = pytester.path / "venv/lib/site-packages"
1799+
site_packages.mkdir(parents=True)
1800+
monkeypatch.syspath_prepend(site_packages)
1801+
pytester.makepyfile(
1802+
**{
1803+
"venv/lib/site-packages/pkg/__init__.py": "",
1804+
"venv/lib/site-packages/pkg/sub/__init__.py": "",
1805+
"venv/lib/site-packages/pkg/sub/test_it.py": "def test(): pass",
1806+
}
1807+
)
1808+
1809+
result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it")
1810+
assert result.ret == ExitCode.OK
1811+
result.stdout.fnmatch_lines(
1812+
[
1813+
"<Package venv/lib/site-packages/pkg>",
1814+
" <Package sub>",
1815+
" <Module test_it.py>",
1816+
" <Function test>",
1817+
],
1818+
consecutive=True,
1819+
)
1820+
1821+
# Now with an unrelated rootdir with unrelated files.
1822+
monkeypatch.chdir(tempfile.gettempdir())
1823+
1824+
result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it")
1825+
assert result.ret == ExitCode.OK
1826+
result.stdout.fnmatch_lines(
1827+
[
1828+
"<Package *pkg>",
1829+
" <Package sub>",
1830+
" <Module test_it.py>",
1831+
" <Function test>",
1832+
],
1833+
consecutive=True,
1834+
)

0 commit comments

Comments
 (0)