-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Rework Session and Package collection #11646
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass. | ||
This is analogous to the existing :class:`pytest.File` for file nodes. | ||
|
||
Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`. | ||
A ``Package`` represents a filesystem directory which is a Python package, | ||
i.e. contains an ``__init__.py`` file. | ||
|
||
:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively. | ||
Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy. | ||
|
||
Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`. | ||
This node represents a filesystem directory, which is not a :class:`pytest.Package`, | ||
i.e. does not contain an ``__init__.py`` file. | ||
Similarly to ``Package``, it only collects the files in its own directory, | ||
while collecting sub-directories as sub-collector nodes. | ||
|
||
Added a new hook :hook:`pytest_collect_directory`, | ||
which is called by filesystem-traversing collector nodes, | ||
such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`, | ||
to create a collector node for a sub-directory. | ||
It is expected to return a subclass of :class:`pytest.Directory`. | ||
This hook allows plugins to :ref:`customize the collection of directories <custom directory collectors>`. | ||
|
||
:class:`pytest.Session` now only collects the initial arguments, without recursing into directories. | ||
This work is now done by the :func:`recursive expansion process <pytest.Collector.collect>` of directory collector nodes. | ||
|
||
:attr:`session.name <pytest.Session.name>` is now ``""``; previously it was the rootdir directory name. | ||
This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`. | ||
|
||
Files and directories are now collected in alphabetical order jointly, unless changed by a plugin. | ||
Previously, files were collected before directories. | ||
|
||
The collection tree now contains directories/packages up to the :ref:`rootdir <rootdir>`, | ||
for initial arguments that are found within the rootdir. | ||
For files outside the rootdir, only the immediate directory/package is collected -- | ||
note however that collecting from outside the rootdir is discouraged. | ||
|
||
As an example, given the following filesystem tree:: | ||
|
||
myroot/ | ||
pytest.ini | ||
top/ | ||
├── aaa | ||
│ └── test_aaa.py | ||
├── test_a.py | ||
├── test_b | ||
│ ├── __init__.py | ||
│ └── test_b.py | ||
├── test_c.py | ||
└── zzz | ||
├── __init__.py | ||
└── test_zzz.py | ||
|
||
the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity, | ||
is now the following:: | ||
|
||
<Session> | ||
<Dir myroot> | ||
<Dir top> | ||
<Dir aaa> | ||
<Module test_aaa.py> | ||
<Function test_it> | ||
<Module test_a.py> | ||
<Function test_it> | ||
<Package test_b> | ||
<Module test_b.py> | ||
<Function test_it> | ||
<Module test_c.py> | ||
<Function test_it> | ||
<Package zzz> | ||
<Module test_zzz.py> | ||
<Function test_it> | ||
|
||
Previously, it was:: | ||
|
||
<Session> | ||
<Module top/test_a.py> | ||
<Function test_it> | ||
<Module top/test_c.py> | ||
<Function test_it> | ||
<Module top/aaa/test_aaa.py> | ||
<Function test_it> | ||
<Package test_b> | ||
<Module test_b.py> | ||
<Function test_it> | ||
<Package zzz> | ||
<Module test_zzz.py> | ||
<Function test_it> | ||
|
||
Code/plugins which rely on a specific shape of the collection tree might need to update. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
collect_ignore = ["nonpython"] | ||
collect_ignore = ["nonpython", "customdirectory"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
.. _`custom directory collectors`: | ||
|
||
Using a custom directory collector | ||
==================================================== | ||
|
||
By default, pytest collects directories using :class:`pytest.Package`, for directories with ``__init__.py`` files, | ||
and :class:`pytest.Dir` for other directories. | ||
If you want to customize how a directory is collected, you can write your own :class:`pytest.Directory` collector, | ||
and use :hook:`pytest_collect_directory` to hook it up. | ||
|
||
.. _`directory manifest plugin`: | ||
|
||
A basic example for a directory manifest file | ||
-------------------------------------------------------------- | ||
|
||
Suppose you want to customize how collection is done on a per-directory basis. | ||
Here is an example ``conftest.py`` plugin that allows directories to contain a ``manifest.json`` file, | ||
which defines how the collection should be done for the directory. | ||
In this example, only a simple list of files is supported, | ||
however you can imagine adding other keys, such as exclusions and globs. | ||
|
||
.. include:: customdirectory/conftest.py | ||
:literal: | ||
|
||
You can create a ``manifest.json`` file and some test files: | ||
|
||
.. include:: customdirectory/tests/manifest.json | ||
:literal: | ||
|
||
.. include:: customdirectory/tests/test_first.py | ||
:literal: | ||
|
||
.. include:: customdirectory/tests/test_second.py | ||
:literal: | ||
|
||
.. include:: customdirectory/tests/test_third.py | ||
:literal: | ||
|
||
An you can now execute the test specification: | ||
|
||
.. code-block:: pytest | ||
|
||
customdirectory $ pytest | ||
=========================== test session starts ============================ | ||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y | ||
rootdir: /home/sweet/project/customdirectory | ||
configfile: pytest.ini | ||
collected 2 items | ||
|
||
tests/test_first.py . [ 50%] | ||
tests/test_second.py . [100%] | ||
|
||
============================ 2 passed in 0.12s ============================= | ||
|
||
.. regendoc:wipe | ||
|
||
Notice how ``test_three.py`` was not executed, because it is not listed in the manifest. | ||
|
||
You can verify that your custom collector appears in the collection tree: | ||
|
||
.. code-block:: pytest | ||
|
||
customdirectory $ pytest --collect-only | ||
=========================== test session starts ============================ | ||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y | ||
rootdir: /home/sweet/project/customdirectory | ||
configfile: pytest.ini | ||
collected 2 items | ||
|
||
<Dir customdirectory> | ||
<ManifestDirectory tests> | ||
<Module test_first.py> | ||
<Function test_1> | ||
<Module test_second.py> | ||
<Function test_2> | ||
|
||
======================== 2 tests collected in 0.12s ======================== |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# content of conftest.py | ||
import json | ||
|
||
import pytest | ||
|
||
|
||
class ManifestDirectory(pytest.Directory): | ||
def collect(self): | ||
# The standard pytest behavior is to loop over all `test_*.py` files and | ||
# call `pytest_collect_file` on each file. This collector instead reads | ||
# the `manifest.json` file and only calls `pytest_collect_file` for the | ||
# files defined there. | ||
manifest_path = self.path / "manifest.json" | ||
nicoddemus marked this conversation as resolved.
Show resolved
Hide resolved
|
||
manifest = json.loads(manifest_path.read_text(encoding="utf-8")) | ||
ihook = self.ihook | ||
for file in manifest["files"]: | ||
yield from ihook.pytest_collect_file( | ||
file_path=self.path / file, parent=self | ||
) | ||
|
||
|
||
@pytest.hookimpl | ||
def pytest_collect_directory(path, parent): | ||
# Use our custom collector for directories containing a `mainfest.json` file. | ||
if path.joinpath("manifest.json").is_file(): | ||
return ManifestDirectory.from_parent(parent=parent, path=path) | ||
# Otherwise fallback to the standard behavior. | ||
return None |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"files": [ | ||
"test_first.py", | ||
"test_second.py" | ||
] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# content of test_first.py | ||
def test_1(): | ||
pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# content of test_second.py | ||
def test_2(): | ||
pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# content of test_third.py | ||
def test_3(): | ||
pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.