Add sphinx docs folder and changelog URLs to metadata (#751)
This commit is contained in:
parent
1a5ad8ac20
commit
8cd10225df
525
README.rst
525
README.rst
|
@ -25,526 +25,9 @@ tests across multiple CPUs to speed up test execution::
|
||||||
pytest -n auto
|
pytest -n auto
|
||||||
|
|
||||||
With this call, pytest will spawn a number of workers processes equal to the number of available CPUs, and distribute
|
With this call, pytest will spawn a number of workers processes equal to the number of available CPUs, and distribute
|
||||||
the tests randomly across them. There is also a number of `distribution modes`_ to choose from.
|
the tests randomly across them.
|
||||||
|
|
||||||
**NOTE**: due to how pytest-xdist is implemented, the ``-s/--capture=no`` option does not work.
|
Documentation
|
||||||
|
=============
|
||||||
|
|
||||||
.. contents:: **Table of Contents**
|
Documentation is available at `Read The Docs <https://pytest-xdist.readthedocs.io>`__.
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
Install the plugin with::
|
|
||||||
|
|
||||||
pip install pytest-xdist
|
|
||||||
|
|
||||||
|
|
||||||
To use ``psutil`` for detection of the number of CPUs available, install the ``psutil`` extra::
|
|
||||||
|
|
||||||
pip install pytest-xdist[psutil]
|
|
||||||
|
|
||||||
|
|
||||||
Features
|
|
||||||
--------
|
|
||||||
|
|
||||||
* Test run parallelization_: tests can be executed across multiple CPUs or hosts.
|
|
||||||
This allows to speed up development or to use special resources of `remote machines`_.
|
|
||||||
|
|
||||||
* ``--looponfail``: run your tests repeatedly in a subprocess. After each run
|
|
||||||
pytest waits until a file in your project changes and then re-runs
|
|
||||||
the previously failing tests. This is repeated until all tests pass
|
|
||||||
after which again a full run is performed.
|
|
||||||
|
|
||||||
* `Multi-Platform`_ coverage: you can specify different Python interpreters
|
|
||||||
or different platforms and run tests in parallel on all of them.
|
|
||||||
|
|
||||||
Before running tests remotely, ``pytest`` efficiently "rsyncs" your
|
|
||||||
program source code to the remote place.
|
|
||||||
You may specify different Python versions and interpreters. It does not
|
|
||||||
installs/synchronize dependencies however.
|
|
||||||
|
|
||||||
**Note**: this mode exists mostly for backward compatibility, as modern development
|
|
||||||
relies on continuous integration for multi-platform testing.
|
|
||||||
|
|
||||||
.. _parallelization:
|
|
||||||
|
|
||||||
Running tests across multiple CPUs
|
|
||||||
----------------------------------
|
|
||||||
|
|
||||||
To send tests to multiple CPUs, use the ``-n`` (or ``--numprocesses``) option::
|
|
||||||
|
|
||||||
pytest -n 8
|
|
||||||
|
|
||||||
Pass ``-n auto`` to use as many processes as your computer has CPU cores. This
|
|
||||||
can lead to considerable speed ups, especially if your test suite takes a
|
|
||||||
noticeable amount of time.
|
|
||||||
|
|
||||||
* ``--maxprocesses=maxprocesses``: limit the maximum number of workers to
|
|
||||||
process the tests.
|
|
||||||
|
|
||||||
* ``--max-worker-restart``: maximum number of workers that can be restarted
|
|
||||||
when crashed (set to zero to disable this feature).
|
|
||||||
|
|
||||||
The test distribution algorithm is configured with the ``--dist`` command-line option:
|
|
||||||
|
|
||||||
.. _distribution modes:
|
|
||||||
|
|
||||||
* ``--dist load`` **(default)**: Sends pending tests to any worker that is
|
|
||||||
available, without any guaranteed order.
|
|
||||||
|
|
||||||
* ``--dist loadscope``: Tests are grouped by **module** for *test functions*
|
|
||||||
and by **class** for *test methods*. Groups are distributed to available
|
|
||||||
workers as whole units. This guarantees that all tests in a group run in the
|
|
||||||
same process. This can be useful if you have expensive module-level or
|
|
||||||
class-level fixtures. Grouping by class takes priority over grouping by
|
|
||||||
module.
|
|
||||||
|
|
||||||
* ``--dist loadfile``: Tests are grouped by their containing file. Groups are
|
|
||||||
distributed to available workers as whole units. This guarantees that all
|
|
||||||
tests in a file run in the same worker.
|
|
||||||
|
|
||||||
* ``--dist loadgroup``: Tests are grouped by the ``xdist_group`` mark. Groups are
|
|
||||||
distributed to available workers as whole units. This guarantees that all
|
|
||||||
tests with same ``xdist_group`` name run in the same worker.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@pytest.mark.xdist_group(name="group1")
|
|
||||||
def test1():
|
|
||||||
pass
|
|
||||||
|
|
||||||
class TestA:
|
|
||||||
@pytest.mark.xdist_group("group1")
|
|
||||||
def test2():
|
|
||||||
pass
|
|
||||||
|
|
||||||
This will make sure ``test1`` and ``TestA::test2`` will run in the same worker.
|
|
||||||
Tests without the ``xdist_group`` mark are distributed normally as in the ``--dist=load`` mode.
|
|
||||||
|
|
||||||
* ``--dist no``: The normal pytest execution mode, runs one test at a time (no distribution at all).
|
|
||||||
|
|
||||||
|
|
||||||
Running tests in a Python subprocess
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
To instantiate a ``python3.9`` subprocess and send tests to it, you may type::
|
|
||||||
|
|
||||||
pytest -d --tx popen//python=python3.9
|
|
||||||
|
|
||||||
This will start a subprocess which is run with the ``python3.9``
|
|
||||||
Python interpreter, found in your system binary lookup path.
|
|
||||||
|
|
||||||
If you prefix the --tx option value like this::
|
|
||||||
|
|
||||||
--tx 3*popen//python=python3.9
|
|
||||||
|
|
||||||
then three subprocesses would be created and tests
|
|
||||||
will be load-balanced across these three processes.
|
|
||||||
|
|
||||||
.. _boxed:
|
|
||||||
|
|
||||||
Running tests in a boxed subprocess
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
This functionality has been moved to the
|
|
||||||
`pytest-forked <https://github.com/pytest-dev/pytest-forked>`_ plugin, but the ``--boxed`` option
|
|
||||||
is still kept for backward compatibility.
|
|
||||||
|
|
||||||
.. _`remote machines`:
|
|
||||||
|
|
||||||
Sending tests to remote SSH accounts
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
Suppose you have a package ``mypkg`` which contains some
|
|
||||||
tests that you can successfully run locally. And you
|
|
||||||
have a ssh-reachable machine ``myhost``. Then
|
|
||||||
you can ad-hoc distribute your tests by typing::
|
|
||||||
|
|
||||||
pytest -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
|
|
||||||
|
|
||||||
This will synchronize your :code:`mypkg` package directory
|
|
||||||
to a remote ssh account and then locally collect tests
|
|
||||||
and send them to remote places for execution.
|
|
||||||
|
|
||||||
You can specify multiple :code:`--rsyncdir` directories
|
|
||||||
to be sent to the remote side.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
For pytest to collect and send tests correctly
|
|
||||||
you not only need to make sure all code and tests
|
|
||||||
directories are rsynced, but that any test (sub) directory
|
|
||||||
also has an :code:`__init__.py` file because internally
|
|
||||||
pytest references tests as a fully qualified python
|
|
||||||
module path. **You will otherwise get strange errors**
|
|
||||||
during setup of the remote side.
|
|
||||||
|
|
||||||
|
|
||||||
You can specify multiple :code:`--rsyncignore` glob patterns
|
|
||||||
to be ignored when file are sent to the remote side.
|
|
||||||
There are also internal ignores: :code:`.*, *.pyc, *.pyo, *~`
|
|
||||||
Those you cannot override using rsyncignore command-line or
|
|
||||||
ini-file option(s).
|
|
||||||
|
|
||||||
|
|
||||||
Sending tests to remote Socket Servers
|
|
||||||
--------------------------------------
|
|
||||||
|
|
||||||
Download the single-module `socketserver.py`_ Python program
|
|
||||||
and run it like this::
|
|
||||||
|
|
||||||
python socketserver.py
|
|
||||||
|
|
||||||
It will tell you that it starts listening on the default
|
|
||||||
port. You can now on your home machine specify this
|
|
||||||
new socket host with something like this::
|
|
||||||
|
|
||||||
pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
|
|
||||||
|
|
||||||
|
|
||||||
.. _`atonce`:
|
|
||||||
.. _`Multi-Platform`:
|
|
||||||
|
|
||||||
|
|
||||||
Running tests on many platforms at once
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
The basic command to run tests on multiple platforms is::
|
|
||||||
|
|
||||||
pytest --dist=each --tx=spec1 --tx=spec2
|
|
||||||
|
|
||||||
If you specify a windows host, an OSX host and a Linux
|
|
||||||
environment this command will send each tests to all
|
|
||||||
platforms - and report back failures from all platforms
|
|
||||||
at once. The specifications strings use the `xspec syntax`_.
|
|
||||||
|
|
||||||
.. _`xspec syntax`: https://codespeak.net/execnet/basics.html#xspec
|
|
||||||
|
|
||||||
.. _`socketserver.py`: https://raw.githubusercontent.com/pytest-dev/execnet/master/execnet/script/socketserver.py
|
|
||||||
|
|
||||||
.. _`execnet`: https://codespeak.net/execnet
|
|
||||||
|
|
||||||
|
|
||||||
When tests crash
|
|
||||||
----------------
|
|
||||||
|
|
||||||
If a test crashes a worker, pytest-xdist will automatically restart that worker
|
|
||||||
and report the test’s failure. You can use the ``--max-worker-restart`` option
|
|
||||||
to limit the number of worker restarts that are allowed, or disable restarting
|
|
||||||
altogether using ``--max-worker-restart 0``.
|
|
||||||
|
|
||||||
|
|
||||||
How-tos
|
|
||||||
-------
|
|
||||||
|
|
||||||
Identifying the worker process during a test
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
*New in version 1.15.*
|
|
||||||
|
|
||||||
If you need to determine the identity of a worker process in
|
|
||||||
a test or fixture, you may use the ``worker_id`` fixture to do so:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def user_account(worker_id):
|
|
||||||
""" use a different account in each xdist worker """
|
|
||||||
return "account_%s" % worker_id
|
|
||||||
|
|
||||||
When ``xdist`` is disabled (running with ``-n0`` for example), then
|
|
||||||
``worker_id`` will return ``"master"``.
|
|
||||||
|
|
||||||
Worker processes also have the following environment variables
|
|
||||||
defined:
|
|
||||||
|
|
||||||
* ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``.
|
|
||||||
* ``PYTEST_XDIST_WORKER_COUNT``: the total number of workers in this session,
|
|
||||||
e.g., ``"4"`` when ``-n 4`` is given in the command-line.
|
|
||||||
|
|
||||||
The information about the worker_id in a test is stored in the ``TestReport`` as
|
|
||||||
well, under the ``worker_id`` attribute.
|
|
||||||
|
|
||||||
Since version 2.0, the following functions are also available in the ``xdist`` module:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def is_xdist_worker(request_or_session) -> bool:
|
|
||||||
"""Return `True` if this is an xdist worker, `False` otherwise
|
|
||||||
|
|
||||||
:param request_or_session: the `pytest` `request` or `session` object
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_xdist_controller(request_or_session) -> bool:
|
|
||||||
"""Return `True` if this is the xdist controller, `False` otherwise
|
|
||||||
|
|
||||||
Note: this method also returns `False` when distribution has not been
|
|
||||||
activated at all.
|
|
||||||
|
|
||||||
:param request_or_session: the `pytest` `request` or `session` object
|
|
||||||
"""
|
|
||||||
|
|
||||||
def is_xdist_master(request_or_session) -> bool:
|
|
||||||
"""Deprecated alias for is_xdist_controller."""
|
|
||||||
|
|
||||||
def get_xdist_worker_id(request_or_session) -> str:
|
|
||||||
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
|
|
||||||
if running on the controller node.
|
|
||||||
|
|
||||||
If not distributing tests (for example passing `-n0` or not passing `-n` at all)
|
|
||||||
also return 'master'.
|
|
||||||
|
|
||||||
:param request_or_session: the `pytest` `request` or `session` object
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
Identifying workers from the system environment
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
*New in version 2.4*
|
|
||||||
|
|
||||||
If the `setproctitle`_ package is installed, ``pytest-xdist`` will use it to
|
|
||||||
update the process title (command line) on its workers to show their current
|
|
||||||
state. The titles used are ``[pytest-xdist running] file.py/node::id`` and
|
|
||||||
``[pytest-xdist idle]``, visible in standard tools like ``ps`` and ``top`` on
|
|
||||||
Linux, Mac OS X and BSD systems. For Windows, please follow `setproctitle`_'s
|
|
||||||
pointer regarding the Process Explorer tool.
|
|
||||||
|
|
||||||
This is intended purely as an UX enhancement, e.g. to track down issues with
|
|
||||||
long-running or CPU intensive tests. Errors in changing the title are ignored
|
|
||||||
silently. Please try not to rely on the title format or title changes in
|
|
||||||
external scripts.
|
|
||||||
|
|
||||||
.. _`setproctitle`: https://pypi.org/project/setproctitle/
|
|
||||||
|
|
||||||
|
|
||||||
Uniquely identifying the current test run
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
*New in version 1.32.*
|
|
||||||
|
|
||||||
If you need to globally distinguish one test run from others in your
|
|
||||||
workers, you can use the ``testrun_uid`` fixture. For instance, let's say you
|
|
||||||
wanted to create a separate database for each test run:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from posix_ipc import Semaphore, O_CREAT
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
|
||||||
def create_unique_database(testrun_uid):
|
|
||||||
""" create a unique database for this particular test run """
|
|
||||||
database_url = f"psql://myapp-{testrun_uid}"
|
|
||||||
|
|
||||||
with Semaphore(f"/{testrun_uid}-lock", flags=O_CREAT, initial_value=1):
|
|
||||||
if not database_exists(database_url):
|
|
||||||
create_database(database_url)
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def db(testrun_uid):
|
|
||||||
""" retrieve unique database """
|
|
||||||
database_url = f"psql://myapp-{testrun_uid}"
|
|
||||||
return database_get_instance(database_url)
|
|
||||||
|
|
||||||
|
|
||||||
Additionally, during a test run, the following environment variable is defined:
|
|
||||||
|
|
||||||
* ``PYTEST_XDIST_TESTRUNUID``: the unique id of the test run.
|
|
||||||
|
|
||||||
Accessing ``sys.argv`` from the controller node in workers
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
To access the ``sys.argv`` passed to the command-line of the controller node, use
|
|
||||||
``request.config.workerinput["mainargv"]``.
|
|
||||||
|
|
||||||
|
|
||||||
Specifying test exec environments in an ini file
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
You can use pytest's ini file configuration to avoid typing common options.
|
|
||||||
You can for example make running with three subprocesses your default like this:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[pytest]
|
|
||||||
addopts = -n3
|
|
||||||
|
|
||||||
You can also add default environments like this:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[pytest]
|
|
||||||
addopts = --tx ssh=myhost//python=python3.9 --tx ssh=myhost//python=python3.6
|
|
||||||
|
|
||||||
and then just type::
|
|
||||||
|
|
||||||
pytest --dist=each
|
|
||||||
|
|
||||||
to run tests in each of the environments.
|
|
||||||
|
|
||||||
|
|
||||||
Specifying "rsync" dirs in an ini-file
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
In a ``tox.ini`` or ``setup.cfg`` file in your root project directory
|
|
||||||
you may specify directories to include or to exclude in synchronisation:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[pytest]
|
|
||||||
rsyncdirs = . mypkg helperpkg
|
|
||||||
rsyncignore = .hg
|
|
||||||
|
|
||||||
These directory specifications are relative to the directory
|
|
||||||
where the configuration file was found.
|
|
||||||
|
|
||||||
.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist
|
|
||||||
.. _`pytest-xdist repository`: https://github.com/pytest-dev/pytest-xdist
|
|
||||||
.. _`pytest`: http://pytest.org
|
|
||||||
|
|
||||||
|
|
||||||
Making session-scoped fixtures execute only once
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
``pytest-xdist`` is designed so that each worker process will perform its own collection and execute
|
|
||||||
a subset of all tests. This means that tests in different processes requesting a high-level
|
|
||||||
scoped fixture (for example ``session``) will execute the fixture code more than once, which
|
|
||||||
breaks expectations and might be undesired in certain situations.
|
|
||||||
|
|
||||||
While ``pytest-xdist`` does not have a builtin support for ensuring a session-scoped fixture is
|
|
||||||
executed exactly once, this can be achieved by using a lock file for inter-process communication.
|
|
||||||
|
|
||||||
The example below needs to execute the fixture ``session_data`` only once (because it is
|
|
||||||
resource intensive, or needs to execute only once to define configuration options, etc), so it makes
|
|
||||||
use of a `FileLock <https://pypi.org/project/filelock/>`_ to produce the fixture data only once
|
|
||||||
when the first process requests the fixture, while the other processes will then read
|
|
||||||
the data from a file.
|
|
||||||
|
|
||||||
Here is the code:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from filelock import FileLock
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def session_data(tmp_path_factory, worker_id):
|
|
||||||
if worker_id == "master":
|
|
||||||
# not executing in with multiple workers, just produce the data and let
|
|
||||||
# pytest's fixture caching do its job
|
|
||||||
return produce_expensive_data()
|
|
||||||
|
|
||||||
# get the temp directory shared by all workers
|
|
||||||
root_tmp_dir = tmp_path_factory.getbasetemp().parent
|
|
||||||
|
|
||||||
fn = root_tmp_dir / "data.json"
|
|
||||||
with FileLock(str(fn) + ".lock"):
|
|
||||||
if fn.is_file():
|
|
||||||
data = json.loads(fn.read_text())
|
|
||||||
else:
|
|
||||||
data = produce_expensive_data()
|
|
||||||
fn.write_text(json.dumps(data))
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
The example above can also be use in cases a fixture needs to execute exactly once per test session, like
|
|
||||||
initializing a database service and populating initial tables.
|
|
||||||
|
|
||||||
This technique might not work for every case, but should be a starting point for many situations
|
|
||||||
where executing a high-scope fixture exactly once is important.
|
|
||||||
|
|
||||||
|
|
||||||
How does xdist work?
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
``xdist`` works by spawning one or more **workers**, which are
|
|
||||||
controlled by the **controller**. Each **worker** is responsible for
|
|
||||||
performing a full test collection and afterwards running tests as
|
|
||||||
dictated by the **controller**.
|
|
||||||
|
|
||||||
The execution flow is:
|
|
||||||
|
|
||||||
1. **controller** spawns one or more **workers** at the beginning of the
|
|
||||||
test session. The communication between **controller** and **worker**
|
|
||||||
nodes makes use of `execnet <https://codespeak.net/execnet/>`__ and
|
|
||||||
its
|
|
||||||
`gateways <https://codespeak.net/execnet/basics.html#gateways-bootstrapping-python-interpreters>`__.
|
|
||||||
The actual interpreters executing the code for the **workers** might
|
|
||||||
be remote or local.
|
|
||||||
|
|
||||||
2. Each **worker** itself is a mini pytest runner. **workers** at this
|
|
||||||
point perform a full test collection, sending back the collected
|
|
||||||
test-ids back to the **controller** which does not perform any
|
|
||||||
collection itself.
|
|
||||||
|
|
||||||
3. The **controller** receives the result of the collection from all
|
|
||||||
nodes. At this point the **controller** performs some sanity check to
|
|
||||||
ensure that all **workers** collected the same tests (including
|
|
||||||
order), bailing out otherwise. If all is well, it converts the list
|
|
||||||
of test-ids into a list of simple indexes, where each index
|
|
||||||
corresponds to the position of that test in the original collection
|
|
||||||
list. This works because all nodes have the same collection list, and
|
|
||||||
saves bandwidth because the **controller** can now tell one of the
|
|
||||||
workers to just *execute test index 3* index of passing the full test
|
|
||||||
id.
|
|
||||||
|
|
||||||
4. If **dist-mode** is **each**: the **controller** just sends the full
|
|
||||||
list of test indexes to each node at this moment.
|
|
||||||
|
|
||||||
5. If **dist-mode** is **load**: the **controller** takes around 25% of
|
|
||||||
the tests and sends them one by one to each **worker** in a round
|
|
||||||
robin fashion. The rest of the tests will be distributed later as
|
|
||||||
**workers** finish tests (see below).
|
|
||||||
|
|
||||||
6. Note that ``pytest_xdist_make_scheduler`` hook can be used to
|
|
||||||
implement custom tests distribution logic.
|
|
||||||
|
|
||||||
7. **workers** re-implement ``pytest_runtestloop``: pytest’s default
|
|
||||||
implementation basically loops over all collected items in the
|
|
||||||
``session`` object and executes the ``pytest_runtest_protocol`` for
|
|
||||||
each test item, but in xdist **workers** sit idly waiting for
|
|
||||||
**controller** to send tests for execution. As tests are received by
|
|
||||||
**workers**, ``pytest_runtest_protocol`` is executed for each test.
|
|
||||||
Here it worth noting an implementation detail: **workers** always
|
|
||||||
must keep at least one test item on their queue due to how the
|
|
||||||
``pytest_runtest_protocol(item, nextitem)`` hook is defined: in order
|
|
||||||
to pass the ``nextitem`` to the hook, the worker must wait for more
|
|
||||||
instructions from controller before executing that remaining test. If
|
|
||||||
it receives more tests, then it can safely call
|
|
||||||
``pytest_runtest_protocol`` because it knows what the ``nextitem``
|
|
||||||
parameter will be. If it receives a “shutdown” signal, then it can
|
|
||||||
execute the hook passing ``nextitem`` as ``None``.
|
|
||||||
|
|
||||||
8. As tests are started and completed at the **workers**, the results
|
|
||||||
are sent back to the **controller**, which then just forwards the
|
|
||||||
results to the appropriate pytest hooks: ``pytest_runtest_logstart``
|
|
||||||
and ``pytest_runtest_logreport``. This way other plugins (for example
|
|
||||||
``junitxml``) can work normally. The **controller** (when in
|
|
||||||
dist-mode **load**) decides to send more tests to a node when a test
|
|
||||||
completes, using some heuristics such as test durations and how many
|
|
||||||
tests each **worker** still has to run.
|
|
||||||
|
|
||||||
9. When the **controller** has no more pending tests it will send a
|
|
||||||
“shutdown” signal to all **workers**, which will then run their
|
|
||||||
remaining tests to completion and shut down. At this point the
|
|
||||||
**controller** will sit waiting for **workers** to shut down, still
|
|
||||||
processing events such as ``pytest_runtest_logreport``.
|
|
||||||
|
|
||||||
FAQ
|
|
||||||
---
|
|
||||||
|
|
||||||
**Question**: Why does each worker do its own collection, as opposed to having the
|
|
||||||
controller collect once and distribute from that collection to the
|
|
||||||
workers?
|
|
||||||
|
|
||||||
If collection was performed by controller then it would have to
|
|
||||||
serialize collected items to send them through the wire, as workers live
|
|
||||||
in another process. The problem is that test items are not easily
|
|
||||||
(impossible?) to serialize, as they contain references to the test
|
|
||||||
functions, fixture managers, config objects, etc. Even if one manages to
|
|
||||||
serialize it, it seems it would be very hard to get it right and easy to
|
|
||||||
break by any small change in pytest.
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
_build/
|
|
@ -0,0 +1,9 @@
|
||||||
|
|
||||||
|
.. _boxed:
|
||||||
|
|
||||||
|
Running tests in a boxed subprocess (moved to pytest-forked)
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
This functionality has been moved to the
|
||||||
|
`pytest-forked <https://github.com/pytest-dev/pytest-forked>`_ plugin, but the ``--boxed`` option
|
||||||
|
is still kept for backward compatibility.
|
|
@ -0,0 +1,5 @@
|
||||||
|
=========
|
||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. include:: ../CHANGELOG.rst
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file only contains a selection of the most common options. For a full
|
||||||
|
# list see the documentation:
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
# import os
|
||||||
|
# import sys
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = "pytest-xdist"
|
||||||
|
copyright = "2022, holger krekel and contributors"
|
||||||
|
author = "holger krekel and contributors"
|
||||||
|
|
||||||
|
master_doc = "index"
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
"sphinx_rtd_theme",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
|
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
# html_static_path = ['_static']
|
|
@ -0,0 +1,7 @@
|
||||||
|
When tests crash
|
||||||
|
================
|
||||||
|
|
||||||
|
If a test crashes a worker, pytest-xdist will automatically restart that worker
|
||||||
|
and report the test’s failure. You can use the ``--max-worker-restart`` option
|
||||||
|
to limit the number of worker restarts that are allowed, or disable restarting
|
||||||
|
altogether using ``--max-worker-restart 0``.
|
|
@ -0,0 +1,56 @@
|
||||||
|
.. _parallelization:
|
||||||
|
|
||||||
|
Running tests across multiple CPUs
|
||||||
|
==================================
|
||||||
|
|
||||||
|
To send tests to multiple CPUs, use the ``-n`` (or ``--numprocesses``) option::
|
||||||
|
|
||||||
|
pytest -n 8
|
||||||
|
|
||||||
|
Pass ``-n auto`` to use as many processes as your computer has CPU cores. This
|
||||||
|
can lead to considerable speed ups, especially if your test suite takes a
|
||||||
|
noticeable amount of time.
|
||||||
|
|
||||||
|
* ``--maxprocesses=maxprocesses``: limit the maximum number of workers to
|
||||||
|
process the tests.
|
||||||
|
|
||||||
|
* ``--max-worker-restart``: maximum number of workers that can be restarted
|
||||||
|
when crashed (set to zero to disable this feature).
|
||||||
|
|
||||||
|
The test distribution algorithm is configured with the ``--dist`` command-line option:
|
||||||
|
|
||||||
|
.. _distribution modes:
|
||||||
|
|
||||||
|
* ``--dist load`` **(default)**: Sends pending tests to any worker that is
|
||||||
|
available, without any guaranteed order.
|
||||||
|
|
||||||
|
* ``--dist loadscope``: Tests are grouped by **module** for *test functions*
|
||||||
|
and by **class** for *test methods*. Groups are distributed to available
|
||||||
|
workers as whole units. This guarantees that all tests in a group run in the
|
||||||
|
same process. This can be useful if you have expensive module-level or
|
||||||
|
class-level fixtures. Grouping by class takes priority over grouping by
|
||||||
|
module.
|
||||||
|
|
||||||
|
* ``--dist loadfile``: Tests are grouped by their containing file. Groups are
|
||||||
|
distributed to available workers as whole units. This guarantees that all
|
||||||
|
tests in a file run in the same worker.
|
||||||
|
|
||||||
|
* ``--dist loadgroup``: Tests are grouped by the ``xdist_group`` mark. Groups are
|
||||||
|
distributed to available workers as whole units. This guarantees that all
|
||||||
|
tests with same ``xdist_group`` name run in the same worker.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@pytest.mark.xdist_group(name="group1")
|
||||||
|
def test1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestA:
|
||||||
|
@pytest.mark.xdist_group("group1")
|
||||||
|
def test2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
This will make sure ``test1`` and ``TestA::test2`` will run in the same worker.
|
||||||
|
Tests without the ``xdist_group`` mark are distributed normally as in the ``--dist=load`` mode.
|
||||||
|
|
||||||
|
* ``--dist no``: The normal pytest execution mode, runs one test at a time (no distribution at all).
|
|
@ -0,0 +1,90 @@
|
||||||
|
How it works?
|
||||||
|
=============
|
||||||
|
|
||||||
|
``xdist`` works by spawning one or more **workers**, which are
|
||||||
|
controlled by the **controller**. Each **worker** is responsible for
|
||||||
|
performing a full test collection and afterwards running tests as
|
||||||
|
dictated by the **controller**.
|
||||||
|
|
||||||
|
The execution flow is:
|
||||||
|
|
||||||
|
1. **controller** spawns one or more **workers** at the beginning of the
|
||||||
|
test session. The communication between **controller** and **worker**
|
||||||
|
nodes makes use of `execnet <https://codespeak.net/execnet/>`__ and
|
||||||
|
its
|
||||||
|
`gateways <https://codespeak.net/execnet/basics.html#gateways-bootstrapping-python-interpreters>`__.
|
||||||
|
The actual interpreters executing the code for the **workers** might
|
||||||
|
be remote or local.
|
||||||
|
|
||||||
|
2. Each **worker** itself is a mini pytest runner. **workers** at this
|
||||||
|
point perform a full test collection, sending back the collected
|
||||||
|
test-ids back to the **controller** which does not perform any
|
||||||
|
collection itself.
|
||||||
|
|
||||||
|
3. The **controller** receives the result of the collection from all
|
||||||
|
nodes. At this point the **controller** performs some sanity check to
|
||||||
|
ensure that all **workers** collected the same tests (including
|
||||||
|
order), bailing out otherwise. If all is well, it converts the list
|
||||||
|
of test-ids into a list of simple indexes, where each index
|
||||||
|
corresponds to the position of that test in the original collection
|
||||||
|
list. This works because all nodes have the same collection list, and
|
||||||
|
saves bandwidth because the **controller** can now tell one of the
|
||||||
|
workers to just *execute test index 3* index of passing the full test
|
||||||
|
id.
|
||||||
|
|
||||||
|
4. If **dist-mode** is **each**: the **controller** just sends the full
|
||||||
|
list of test indexes to each node at this moment.
|
||||||
|
|
||||||
|
5. If **dist-mode** is **load**: the **controller** takes around 25% of
|
||||||
|
the tests and sends them one by one to each **worker** in a round
|
||||||
|
robin fashion. The rest of the tests will be distributed later as
|
||||||
|
**workers** finish tests (see below).
|
||||||
|
|
||||||
|
6. Note that ``pytest_xdist_make_scheduler`` hook can be used to
|
||||||
|
implement custom tests distribution logic.
|
||||||
|
|
||||||
|
7. **workers** re-implement ``pytest_runtestloop``: pytest’s default
|
||||||
|
implementation basically loops over all collected items in the
|
||||||
|
``session`` object and executes the ``pytest_runtest_protocol`` for
|
||||||
|
each test item, but in xdist **workers** sit idly waiting for
|
||||||
|
**controller** to send tests for execution. As tests are received by
|
||||||
|
**workers**, ``pytest_runtest_protocol`` is executed for each test.
|
||||||
|
Here it worth noting an implementation detail: **workers** always
|
||||||
|
must keep at least one test item on their queue due to how the
|
||||||
|
``pytest_runtest_protocol(item, nextitem)`` hook is defined: in order
|
||||||
|
to pass the ``nextitem`` to the hook, the worker must wait for more
|
||||||
|
instructions from controller before executing that remaining test. If
|
||||||
|
it receives more tests, then it can safely call
|
||||||
|
``pytest_runtest_protocol`` because it knows what the ``nextitem``
|
||||||
|
parameter will be. If it receives a “shutdown” signal, then it can
|
||||||
|
execute the hook passing ``nextitem`` as ``None``.
|
||||||
|
|
||||||
|
8. As tests are started and completed at the **workers**, the results
|
||||||
|
are sent back to the **controller**, which then just forwards the
|
||||||
|
results to the appropriate pytest hooks: ``pytest_runtest_logstart``
|
||||||
|
and ``pytest_runtest_logreport``. This way other plugins (for example
|
||||||
|
``junitxml``) can work normally. The **controller** (when in
|
||||||
|
dist-mode **load**) decides to send more tests to a node when a test
|
||||||
|
completes, using some heuristics such as test durations and how many
|
||||||
|
tests each **worker** still has to run.
|
||||||
|
|
||||||
|
9. When the **controller** has no more pending tests it will send a
|
||||||
|
“shutdown” signal to all **workers**, which will then run their
|
||||||
|
remaining tests to completion and shut down. At this point the
|
||||||
|
**controller** will sit waiting for **workers** to shut down, still
|
||||||
|
processing events such as ``pytest_runtest_logreport``.
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
---
|
||||||
|
|
||||||
|
**Question**: Why does each worker do its own collection, as opposed to having the
|
||||||
|
controller collect once and distribute from that collection to the
|
||||||
|
workers?
|
||||||
|
|
||||||
|
If collection was performed by controller then it would have to
|
||||||
|
serialize collected items to send them through the wire, as workers live
|
||||||
|
in another process. The problem is that test items are not easily
|
||||||
|
(impossible?) to serialize, as they contain references to the test
|
||||||
|
functions, fixture managers, config objects, etc. Even if one manages to
|
||||||
|
serialize it, it seems it would be very hard to get it right and easy to
|
||||||
|
break by any small change in pytest.
|
|
@ -0,0 +1,224 @@
|
||||||
|
How-tos
|
||||||
|
-------
|
||||||
|
|
||||||
|
This section show cases how to accomplish some specialized tasks with ``pytest-xdist``.
|
||||||
|
|
||||||
|
Identifying the worker process during a test
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
*New in version 1.15.*
|
||||||
|
|
||||||
|
If you need to determine the identity of a worker process in
|
||||||
|
a test or fixture, you may use the ``worker_id`` fixture to do so:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def user_account(worker_id):
|
||||||
|
""" use a different account in each xdist worker """
|
||||||
|
return "account_%s" % worker_id
|
||||||
|
|
||||||
|
When ``xdist`` is disabled (running with ``-n0`` for example), then
|
||||||
|
``worker_id`` will return ``"master"``.
|
||||||
|
|
||||||
|
Worker processes also have the following environment variables
|
||||||
|
defined:
|
||||||
|
|
||||||
|
* ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``.
|
||||||
|
* ``PYTEST_XDIST_WORKER_COUNT``: the total number of workers in this session,
|
||||||
|
e.g., ``"4"`` when ``-n 4`` is given in the command-line.
|
||||||
|
|
||||||
|
The information about the worker_id in a test is stored in the ``TestReport`` as
|
||||||
|
well, under the ``worker_id`` attribute.
|
||||||
|
|
||||||
|
Since version 2.0, the following functions are also available in the ``xdist`` module:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def is_xdist_worker(request_or_session) -> bool:
|
||||||
|
"""Return `True` if this is an xdist worker, `False` otherwise
|
||||||
|
|
||||||
|
:param request_or_session: the `pytest` `request` or `session` object
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_xdist_controller(request_or_session) -> bool:
|
||||||
|
"""Return `True` if this is the xdist controller, `False` otherwise
|
||||||
|
|
||||||
|
Note: this method also returns `False` when distribution has not been
|
||||||
|
activated at all.
|
||||||
|
|
||||||
|
:param request_or_session: the `pytest` `request` or `session` object
|
||||||
|
"""
|
||||||
|
|
||||||
|
def is_xdist_master(request_or_session) -> bool:
|
||||||
|
"""Deprecated alias for is_xdist_controller."""
|
||||||
|
|
||||||
|
def get_xdist_worker_id(request_or_session) -> str:
|
||||||
|
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
|
||||||
|
if running on the controller node.
|
||||||
|
|
||||||
|
If not distributing tests (for example passing `-n0` or not passing `-n` at all)
|
||||||
|
also return 'master'.
|
||||||
|
|
||||||
|
:param request_or_session: the `pytest` `request` or `session` object
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
Identifying workers from the system environment
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
*New in version 2.4*
|
||||||
|
|
||||||
|
If the `setproctitle`_ package is installed, ``pytest-xdist`` will use it to
|
||||||
|
update the process title (command line) on its workers to show their current
|
||||||
|
state. The titles used are ``[pytest-xdist running] file.py/node::id`` and
|
||||||
|
``[pytest-xdist idle]``, visible in standard tools like ``ps`` and ``top`` on
|
||||||
|
Linux, Mac OS X and BSD systems. For Windows, please follow `setproctitle`_'s
|
||||||
|
pointer regarding the Process Explorer tool.
|
||||||
|
|
||||||
|
This is intended purely as an UX enhancement, e.g. to track down issues with
|
||||||
|
long-running or CPU intensive tests. Errors in changing the title are ignored
|
||||||
|
silently. Please try not to rely on the title format or title changes in
|
||||||
|
external scripts.
|
||||||
|
|
||||||
|
.. _`setproctitle`: https://pypi.org/project/setproctitle/
|
||||||
|
|
||||||
|
|
||||||
|
Uniquely identifying the current test run
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
*New in version 1.32.*
|
||||||
|
|
||||||
|
If you need to globally distinguish one test run from others in your
|
||||||
|
workers, you can use the ``testrun_uid`` fixture. For instance, let's say you
|
||||||
|
wanted to create a separate database for each test run:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from posix_ipc import Semaphore, O_CREAT
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
def create_unique_database(testrun_uid):
|
||||||
|
""" create a unique database for this particular test run """
|
||||||
|
database_url = f"psql://myapp-{testrun_uid}"
|
||||||
|
|
||||||
|
with Semaphore(f"/{testrun_uid}-lock", flags=O_CREAT, initial_value=1):
|
||||||
|
if not database_exists(database_url):
|
||||||
|
create_database(database_url)
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def db(testrun_uid):
|
||||||
|
""" retrieve unique database """
|
||||||
|
database_url = f"psql://myapp-{testrun_uid}"
|
||||||
|
return database_get_instance(database_url)
|
||||||
|
|
||||||
|
|
||||||
|
Additionally, during a test run, the following environment variable is defined:
|
||||||
|
|
||||||
|
* ``PYTEST_XDIST_TESTRUNUID``: the unique id of the test run.
|
||||||
|
|
||||||
|
Accessing ``sys.argv`` from the controller node in workers
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To access the ``sys.argv`` passed to the command-line of the controller node, use
|
||||||
|
``request.config.workerinput["mainargv"]``.
|
||||||
|
|
||||||
|
|
||||||
|
Specifying test exec environments in an ini file
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
You can use pytest's ini file configuration to avoid typing common options.
|
||||||
|
You can for example make running with three subprocesses your default like this:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
addopts = -n3
|
||||||
|
|
||||||
|
You can also add default environments like this:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
addopts = --tx ssh=myhost//python=python3.9 --tx ssh=myhost//python=python3.6
|
||||||
|
|
||||||
|
and then just type::
|
||||||
|
|
||||||
|
pytest --dist=each
|
||||||
|
|
||||||
|
to run tests in each of the environments.
|
||||||
|
|
||||||
|
|
||||||
|
Specifying "rsync" dirs in an ini-file
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In a ``tox.ini`` or ``setup.cfg`` file in your root project directory
|
||||||
|
you may specify directories to include or to exclude in synchronisation:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
rsyncdirs = . mypkg helperpkg
|
||||||
|
rsyncignore = .hg
|
||||||
|
|
||||||
|
These directory specifications are relative to the directory
|
||||||
|
where the configuration file was found.
|
||||||
|
|
||||||
|
.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist
|
||||||
|
.. _`pytest-xdist repository`: https://github.com/pytest-dev/pytest-xdist
|
||||||
|
.. _`pytest`: http://pytest.org
|
||||||
|
|
||||||
|
|
||||||
|
Making session-scoped fixtures execute only once
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
``pytest-xdist`` is designed so that each worker process will perform its own collection and execute
|
||||||
|
a subset of all tests. This means that tests in different processes requesting a high-level
|
||||||
|
scoped fixture (for example ``session``) will execute the fixture code more than once, which
|
||||||
|
breaks expectations and might be undesired in certain situations.
|
||||||
|
|
||||||
|
While ``pytest-xdist`` does not have a builtin support for ensuring a session-scoped fixture is
|
||||||
|
executed exactly once, this can be achieved by using a lock file for inter-process communication.
|
||||||
|
|
||||||
|
The example below needs to execute the fixture ``session_data`` only once (because it is
|
||||||
|
resource intensive, or needs to execute only once to define configuration options, etc), so it makes
|
||||||
|
use of a `FileLock <https://pypi.org/project/filelock/>`_ to produce the fixture data only once
|
||||||
|
when the first process requests the fixture, while the other processes will then read
|
||||||
|
the data from a file.
|
||||||
|
|
||||||
|
Here is the code:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from filelock import FileLock
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def session_data(tmp_path_factory, worker_id):
|
||||||
|
if worker_id == "master":
|
||||||
|
# not executing in with multiple workers, just produce the data and let
|
||||||
|
# pytest's fixture caching do its job
|
||||||
|
return produce_expensive_data()
|
||||||
|
|
||||||
|
# get the temp directory shared by all workers
|
||||||
|
root_tmp_dir = tmp_path_factory.getbasetemp().parent
|
||||||
|
|
||||||
|
fn = root_tmp_dir / "data.json"
|
||||||
|
with FileLock(str(fn) + ".lock"):
|
||||||
|
if fn.is_file():
|
||||||
|
data = json.loads(fn.read_text())
|
||||||
|
else:
|
||||||
|
data = produce_expensive_data()
|
||||||
|
fn.write_text(json.dumps(data))
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
The example above can also be use in cases a fixture needs to execute exactly once per test session, like
|
||||||
|
initializing a database service and populating initial tables.
|
||||||
|
|
||||||
|
This technique might not work for every case, but should be a starting point for many situations
|
||||||
|
where executing a high-scope fixture exactly once is important.
|
|
@ -0,0 +1,63 @@
|
||||||
|
pytest-xdist
|
||||||
|
============
|
||||||
|
|
||||||
|
The `pytest-xdist`_ plugin extends pytest with new test execution modes, the most used being distributing
|
||||||
|
tests across multiple CPUs to speed up test execution::
|
||||||
|
|
||||||
|
pytest -n auto
|
||||||
|
|
||||||
|
With this call, pytest will spawn a number of workers processes equal to the number of available CPUs, and distribute
|
||||||
|
the tests randomly across them.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Due to how pytest-xdist is implemented, the ``-s/--capture=no`` option does not work.
|
||||||
|
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Install the plugin with::
|
||||||
|
|
||||||
|
pip install pytest-xdist
|
||||||
|
|
||||||
|
|
||||||
|
To use ``psutil`` for detection of the number of CPUs available, install the ``psutil`` extra::
|
||||||
|
|
||||||
|
pip install pytest-xdist[psutil]
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Test run :ref:`parallelization`: tests can be executed across multiple CPUs or hosts.
|
||||||
|
This allows to speed up development or to use special resources of :ref:`remote machines`.
|
||||||
|
|
||||||
|
* ``--looponfail``: run your tests repeatedly in a subprocess. After each run
|
||||||
|
pytest waits until a file in your project changes and then re-runs
|
||||||
|
the previously failing tests. This is repeated until all tests pass
|
||||||
|
after which again a full run is performed.
|
||||||
|
|
||||||
|
* :ref:`Multi-Platform` coverage: you can specify different Python interpreters
|
||||||
|
or different platforms and run tests in parallel on all of them.
|
||||||
|
|
||||||
|
Before running tests remotely, ``pytest`` efficiently "rsyncs" your
|
||||||
|
program source code to the remote place.
|
||||||
|
You may specify different Python versions and interpreters. It does not
|
||||||
|
installs/synchronize dependencies however.
|
||||||
|
|
||||||
|
**Note**: this mode exists mostly for backward compatibility, as modern development
|
||||||
|
relies on continuous integration for multi-platform testing.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
|
distribution
|
||||||
|
subprocess
|
||||||
|
boxed
|
||||||
|
remote
|
||||||
|
crash
|
||||||
|
how-to
|
||||||
|
how-it-works
|
||||||
|
changelog
|
|
@ -0,0 +1,72 @@
|
||||||
|
|
||||||
|
.. _`Multi-Platform`:
|
||||||
|
.. _`remote machines`:
|
||||||
|
|
||||||
|
Sending tests to remote SSH accounts
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Suppose you have a package ``mypkg`` which contains some
|
||||||
|
tests that you can successfully run locally. And you
|
||||||
|
have a ssh-reachable machine ``myhost``. Then
|
||||||
|
you can ad-hoc distribute your tests by typing::
|
||||||
|
|
||||||
|
pytest -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
|
||||||
|
|
||||||
|
This will synchronize your :code:`mypkg` package directory
|
||||||
|
to a remote ssh account and then locally collect tests
|
||||||
|
and send them to remote places for execution.
|
||||||
|
|
||||||
|
You can specify multiple :code:`--rsyncdir` directories
|
||||||
|
to be sent to the remote side.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
For pytest to collect and send tests correctly
|
||||||
|
you not only need to make sure all code and tests
|
||||||
|
directories are rsynced, but that any test (sub) directory
|
||||||
|
also has an :code:`__init__.py` file because internally
|
||||||
|
pytest references tests as a fully qualified python
|
||||||
|
module path. **You will otherwise get strange errors**
|
||||||
|
during setup of the remote side.
|
||||||
|
|
||||||
|
|
||||||
|
You can specify multiple :code:`--rsyncignore` glob patterns
|
||||||
|
to be ignored when file are sent to the remote side.
|
||||||
|
There are also internal ignores: :code:`.*, *.pyc, *.pyo, *~`
|
||||||
|
Those you cannot override using rsyncignore command-line or
|
||||||
|
ini-file option(s).
|
||||||
|
|
||||||
|
|
||||||
|
Sending tests to remote Socket Servers
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
Download the single-module `socketserver.py`_ Python program
|
||||||
|
and run it like this::
|
||||||
|
|
||||||
|
python socketserver.py
|
||||||
|
|
||||||
|
It will tell you that it starts listening on the default
|
||||||
|
port. You can now on your home machine specify this
|
||||||
|
new socket host with something like this::
|
||||||
|
|
||||||
|
pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Running tests on many platforms at once
|
||||||
|
---------------------------------------
|
||||||
|
|
||||||
|
The basic command to run tests on multiple platforms is::
|
||||||
|
|
||||||
|
pytest --dist=each --tx=spec1 --tx=spec2
|
||||||
|
|
||||||
|
If you specify a windows host, an OSX host and a Linux
|
||||||
|
environment this command will send each tests to all
|
||||||
|
platforms - and report back failures from all platforms
|
||||||
|
at once. The specifications strings use the `xspec syntax`_.
|
||||||
|
|
||||||
|
.. _`xspec syntax`: https://codespeak.net/execnet/basics.html#xspec
|
||||||
|
|
||||||
|
.. _`execnet`: https://codespeak.net/execnet
|
||||||
|
|
||||||
|
.. _`socketserver.py`: https://raw.githubusercontent.com/pytest-dev/execnet/master/execnet/script/socketserver.py
|
|
@ -0,0 +1,16 @@
|
||||||
|
Running tests in a Python subprocess
|
||||||
|
====================================
|
||||||
|
|
||||||
|
To instantiate a ``python3.9`` subprocess and send tests to it, you may type::
|
||||||
|
|
||||||
|
pytest -d --tx popen//python=python3.9
|
||||||
|
|
||||||
|
This will start a subprocess which is run with the ``python3.9``
|
||||||
|
Python interpreter, found in your system binary lookup path.
|
||||||
|
|
||||||
|
If you prefix the --tx option value like this::
|
||||||
|
|
||||||
|
--tx 3*popen//python=python3.9
|
||||||
|
|
||||||
|
then three subprocesses would be created and tests
|
||||||
|
will be load-balanced across these three processes.
|
|
@ -30,6 +30,11 @@ classifiers =
|
||||||
Programming Language :: Python :: 3.9
|
Programming Language :: Python :: 3.9
|
||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
license_file = LICENSE
|
license_file = LICENSE
|
||||||
|
project_urls =
|
||||||
|
Documentation=https://pytest-xdist.readthedocs.io/en/latest
|
||||||
|
Changelog=https://pytest-xdist.readthedocs.io/en/latest/changelog.html
|
||||||
|
Source=https://github.com/pytest-dev/pytest-xdist
|
||||||
|
Tracker=https://github.com/pytest-dev/pytest-xdist/issues
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
packages = find:
|
packages = find:
|
||||||
|
|
9
tox.ini
9
tox.ini
|
@ -49,6 +49,15 @@ deps =
|
||||||
commands =
|
commands =
|
||||||
towncrier --version {posargs} --yes
|
towncrier --version {posargs} --yes
|
||||||
|
|
||||||
|
[testenv:docs]
|
||||||
|
basepython = python3
|
||||||
|
usedevelop = True
|
||||||
|
deps =
|
||||||
|
sphinx
|
||||||
|
sphinx_rtd_theme
|
||||||
|
commands =
|
||||||
|
sphinx-build -W --keep-going -b html docs docs/_build/html {posargs:}
|
||||||
|
|
||||||
[pytest]
|
[pytest]
|
||||||
# pytest-services also defines a worker_id fixture, disable
|
# pytest-services also defines a worker_id fixture, disable
|
||||||
# it so they don't conflict with each other (#611).
|
# it so they don't conflict with each other (#611).
|
||||||
|
|
Loading…
Reference in New Issue