233 lines
7.5 KiB
ReStructuredText
233 lines
7.5 KiB
ReStructuredText
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:
|
|
|
|
.. envvar:: PYTEST_XDIST_WORKER
|
|
|
|
The name of the worker, e.g., ``"gw2"``.
|
|
|
|
.. envvar:: 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:
|
|
|
|
|
|
.. autofunction:: xdist.is_xdist_worker
|
|
.. autofunction:: xdist.is_xdist_controller
|
|
.. autofunction:: xdist.is_xdist_master
|
|
.. autofunction:: xdist.get_xdist_worker_id
|
|
|
|
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:
|
|
|
|
.. envvar:: 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.
|
|
|
|
|
|
Creating one log file for each worker
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
To create one log file for each worker with ``pytest-xdist``, you can leverage :envvar:`PYTEST_XDIST_WORKER`
|
|
to generate a unique filename for each worker.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
# content of conftest.py
|
|
def pytest_configure(config):
|
|
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
|
if worker_id is not None:
|
|
logging.basicConfig(
|
|
format=config.getini("log_file_format"),
|
|
filename=f"tests_{worker_id}.log",
|
|
level=config.getini("log_file_level"),
|
|
)
|
|
|
|
|
|
When running the tests with ``-n3``, for example, three files will be created in the current directory:
|
|
``tests_gw0.log``, ``tests_gw1.log`` and ``tests_gw2.log``.
|