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.
This commit is contained in:
parent
f20e32c982
commit
31026a2df2
|
@ -0,0 +1,3 @@
|
||||||
|
Fixed a regression in pytest 8.0.0 that would cause test collection to fail due to permission errors when using ``--pyargs``.
|
||||||
|
|
||||||
|
This change improves the collection tree for tests specified using ``--pyargs``, see :pull:`12043` for a comparison with pytest 8.0 and <8.
|
|
@ -846,6 +846,7 @@ class Session(nodes.Collector):
|
||||||
|
|
||||||
argpath = collection_argument.path
|
argpath = collection_argument.path
|
||||||
names = collection_argument.parts
|
names = collection_argument.parts
|
||||||
|
module_name = collection_argument.module_name
|
||||||
|
|
||||||
# resolve_collection_argument() ensures this.
|
# resolve_collection_argument() ensures this.
|
||||||
if argpath.is_dir():
|
if argpath.is_dir():
|
||||||
|
@ -854,11 +855,20 @@ class Session(nodes.Collector):
|
||||||
paths = [argpath]
|
paths = [argpath]
|
||||||
# Add relevant parents of the path, from the root, e.g.
|
# Add relevant parents of the path, from the root, e.g.
|
||||||
# /a/b/c.py -> [/, /a, /a/b, /a/b/c.py]
|
# /a/b/c.py -> [/, /a, /a/b, /a/b/c.py]
|
||||||
|
if module_name is None:
|
||||||
# Paths outside of the confcutdir should not be considered.
|
# Paths outside of the confcutdir should not be considered.
|
||||||
for path in argpath.parents:
|
for path in argpath.parents:
|
||||||
if not pm._is_in_confcutdir(path):
|
if not pm._is_in_confcutdir(path):
|
||||||
break
|
break
|
||||||
paths.insert(0, path)
|
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
|
# Start going over the parts from the root, collecting each level
|
||||||
# and discarding all nodes which don't match the level's part.
|
# and discarding all nodes which don't match the level's part.
|
||||||
|
|
|
@ -1787,3 +1787,48 @@ def test_collect_short_file_windows(pytester: Pytester) -> None:
|
||||||
test_file.write_text("def test(): pass", encoding="UTF-8")
|
test_file.write_text("def test(): pass", encoding="UTF-8")
|
||||||
result = pytester.runpytest(short_path)
|
result = pytester.runpytest(short_path)
|
||||||
assert result.parseoutcomes() == {"passed": 1}
|
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 include parents in the import path, 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(
|
||||||
|
[
|
||||||
|
"<Package venv/lib/site-packages/pkg>",
|
||||||
|
" <Package sub>",
|
||||||
|
" <Module test_it.py>",
|
||||||
|
" <Function test>",
|
||||||
|
],
|
||||||
|
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(
|
||||||
|
[
|
||||||
|
"<Package *pkg>",
|
||||||
|
" <Package sub>",
|
||||||
|
" <Module test_it.py>",
|
||||||
|
" <Function test>",
|
||||||
|
],
|
||||||
|
consecutive=True,
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue