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:
Ran Benita 2024-03-01 12:19:30 +02:00
parent f20e32c982
commit 31026a2df2
3 changed files with 63 additions and 5 deletions

View File

@ -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.

View File

@ -846,6 +846,7 @@ class Session(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 @@ class Session(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.

View File

@ -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 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,
)