forked from pytest-dev/pytest
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Rework Session and Package collection
Fix pytest-dev#7777.
- Loading branch information
Showing
33 changed files
with
865 additions
and
333 deletions.
There are no files selected for viewing
This file contains 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,87 @@ | ||
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 (this 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> |
This file contains 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 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 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,78 @@ | ||
.. _`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. | ||
This plugin 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 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,22 @@ | ||
# content of conftest.py | ||
import json | ||
|
||
import pytest | ||
|
||
|
||
class ManifestDirectory(pytest.Directory): | ||
def collect(self): | ||
manifest_path = self.path / "manifest.json" | ||
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): | ||
if path.joinpath("manifest.json").is_file(): | ||
return ManifestDirectory.from_parent(parent=parent, path=path) | ||
return None |
Empty file.
This file contains 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 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 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 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 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 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 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 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 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.