Skip to content

Commit

Permalink
main: only include package parents in collection tree for --pyargs co…
Browse files Browse the repository at this point in the history
…llection 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 pytest-dev#11904.
  • Loading branch information
bluetech committed Mar 1, 2024
1 parent c14ff86 commit 20ca1f5
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 5 deletions.
20 changes: 15 additions & 5 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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.
Expand Down
45 changes: 45 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/subpkg/__init__.py": "",
"venv/lib/site-packages/pkg/subpkg/test_it.py": "def test(): pass",
}
)

result = pytester.runpytest("--pyargs", "--collect-only", "pkg.subpkg.test_it")
assert result.ret == ExitCode.OK
result.stdout.fnmatch_lines(
[
"<Package venv/lib/site-packages/pkg>",
" <Package subpkg>",
" <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.subpkg.test_it")
assert result.ret == ExitCode.OK
result.stdout.fnmatch_lines(
[
"<Package */pkg>",
" <Package subpkg>",
" <Module test_it.py>",
" <Function test>",
],
consecutive=True,
)

0 comments on commit 20ca1f5

Please sign in to comment.