Skip to content

Commit 2868896

Browse files
authored
Merge pull request #762 from marcosanchotene/multiple-log-files
2 parents 29c4cce + 7f04ce3 commit 2868896

File tree

5 files changed

+69
-38
lines changed

5 files changed

+69
-38
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ repos:
44
hooks:
55
- id: black
66
args: [--safe, --quiet, --target-version, py35]
7+
- repo: https://github.com/asottile/blacken-docs
8+
rev: v1.12.0
9+
hooks:
10+
- id: blacken-docs
11+
additional_dependencies: [black==20.8b1]
712
- repo: https://github.com/pre-commit/pre-commit-hooks
813
rev: v4.1.0
914
hooks:

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
# ones.
3131
extensions = [
3232
"sphinx_rtd_theme",
33+
"sphinx.ext.autodoc",
3334
]
3435

3536
# Add any paths that contain templates here, relative to this directory.

docs/distribution.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ The test distribution algorithm is configured with the ``--dist`` command-line o
4545
def test1():
4646
pass
4747
48+
4849
class TestA:
4950
@pytest.mark.xdist_group("group1")
5051
def test2():

docs/how-to.rst

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,45 +24,24 @@ When ``xdist`` is disabled (running with ``-n0`` for example), then
2424
Worker processes also have the following environment variables
2525
defined:
2626

27-
* ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``.
28-
* ``PYTEST_XDIST_WORKER_COUNT``: the total number of workers in this session,
29-
e.g., ``"4"`` when ``-n 4`` is given in the command-line.
27+
.. envvar:: PYTEST_XDIST_WORKER
3028

31-
The information about the worker_id in a test is stored in the ``TestReport`` as
32-
well, under the ``worker_id`` attribute.
33-
34-
Since version 2.0, the following functions are also available in the ``xdist`` module:
35-
36-
.. code-block:: python
37-
38-
def is_xdist_worker(request_or_session) -> bool:
39-
"""Return `True` if this is an xdist worker, `False` otherwise
40-
41-
:param request_or_session: the `pytest` `request` or `session` object
42-
"""
29+
The name of the worker, e.g., ``"gw2"``.
4330

44-
def is_xdist_controller(request_or_session) -> bool:
45-
"""Return `True` if this is the xdist controller, `False` otherwise
31+
.. envvar:: PYTEST_XDIST_WORKER_COUNT
4632

47-
Note: this method also returns `False` when distribution has not been
48-
activated at all.
33+
The total number of workers in this session, e.g., ``"4"`` when ``-n 4`` is given in the command-line.
4934

50-
:param request_or_session: the `pytest` `request` or `session` object
51-
"""
52-
53-
def is_xdist_master(request_or_session) -> bool:
54-
"""Deprecated alias for is_xdist_controller."""
55-
56-
def get_xdist_worker_id(request_or_session) -> str:
57-
"""Return the id of the current worker ('gw0', 'gw1', etc) or 'master'
58-
if running on the controller node.
35+
The information about the worker_id in a test is stored in the ``TestReport`` as
36+
well, under the ``worker_id`` attribute.
5937

60-
If not distributing tests (for example passing `-n0` or not passing `-n` at all)
61-
also return 'master'.
38+
Since version 2.0, the following functions are also available in the ``xdist`` module:
6239

63-
:param request_or_session: the `pytest` `request` or `session` object
64-
"""
6540

41+
.. autofunction:: xdist.is_xdist_worker
42+
.. autofunction:: xdist.is_xdist_controller
43+
.. autofunction:: xdist.is_xdist_master
44+
.. autofunction:: xdist.get_xdist_worker_id
6645

6746
Identifying workers from the system environment
6847
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -98,6 +77,7 @@ wanted to create a separate database for each test run:
9877
import pytest
9978
from posix_ipc import Semaphore, O_CREAT
10079
80+
10181
@pytest.fixture(scope="session", autouse=True)
10282
def create_unique_database(testrun_uid):
10383
""" create a unique database for this particular test run """
@@ -107,6 +87,7 @@ wanted to create a separate database for each test run:
10787
if not database_exists(database_url):
10888
create_database(database_url)
10989
90+
11091
@pytest.fixture()
11192
def db(testrun_uid):
11293
""" retrieve unique database """
@@ -116,7 +97,9 @@ wanted to create a separate database for each test run:
11697
11798
Additionally, during a test run, the following environment variable is defined:
11899

119-
* ``PYTEST_XDIST_TESTRUNUID``: the unique id of the test run.
100+
.. envvar:: PYTEST_XDIST_TESTRUNUID
101+
102+
The unique id of the test run.
120103

121104
Accessing ``sys.argv`` from the controller node in workers
122105
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -222,3 +205,46 @@ initializing a database service and populating initial tables.
222205

223206
This technique might not work for every case, but should be a starting point for many situations
224207
where executing a high-scope fixture exactly once is important.
208+
209+
210+
Creating one log file for each worker
211+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
212+
213+
To create one log file for each worker with ``pytest-xdist``, you can leverage :envvar:`PYTEST_XDIST_WORKER`
214+
an option to ``pytest.ini`` for the file base name. Then, in ``conftest.py``,
215+
register it with ``pytest_addoption(parser)`` and use ``pytest_configure(config)``
216+
to rename it with the worker id.
217+
218+
Example:
219+
220+
.. code-block:: ini
221+
222+
[pytest]
223+
log_file_format = %(asctime)s %(name)s %(levelname)s %(message)s
224+
log_file_level = INFO
225+
worker_log_file = tests_{worker_id}.log
226+
227+
228+
.. code-block:: python
229+
230+
# content of conftest.py
231+
def pytest_addoption(parser):
232+
parser.addini(
233+
"worker_log_file",
234+
help="Similar to log_file, but %w will be replaced with a worker identifier.",
235+
)
236+
237+
238+
def pytest_configure(config):
239+
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
240+
if worker_id is not None:
241+
log_file = config.getini("worker_log_file")
242+
logging.basicConfig(
243+
format=config.getini("log_file_format"),
244+
filename=log_file.format(worker_id=worker_id),
245+
level=config.getini("log_file_level"),
246+
)
247+
248+
249+
When running the tests with ``-n3``, for example, three files will be created in the current directory:
250+
``tests_gw0.log``, ``tests_gw1.log`` and ``tests_gw2.log``.

testing/test_looponfail.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import cast
2-
31
import py
42
import pytest
53
import shutil
@@ -125,7 +123,7 @@ def test_failures_somewhere(self, pytester: pytest.Pytester) -> None:
125123
failures = control.runsession()
126124
assert failures
127125
control.setup()
128-
item_path = item.path if PYTEST_GTE_7 else Path(cast(py.path.local, item.fspath)) # type: ignore[attr-defined]
126+
item_path = item.path if PYTEST_GTE_7 else Path(str(item.fspath)) # type: ignore[attr-defined]
129127
item_path.write_text("def test_func():\n assert 1\n")
130128
removepyc(item_path)
131129
topdir, failures = control.runsession()[:2]
@@ -146,7 +144,7 @@ def test_func():
146144
if PYTEST_GTE_7:
147145
modcol_path = modcol.path # type:ignore[attr-defined]
148146
else:
149-
modcol_path = Path(cast(py.path.local, modcol.fspath))
147+
modcol_path = Path(str(modcol.fspath))
150148

151149
modcol_path.write_text(
152150
textwrap.dedent(
@@ -179,7 +177,7 @@ def test_func():
179177
if PYTEST_GTE_7:
180178
parent = modcol.path.parent.parent # type: ignore[attr-defined]
181179
else:
182-
parent = Path(cast(py.path.local, modcol.fspath).dirpath().dirpath())
180+
parent = Path(modcol.fspath.dirpath().dirpath())
183181
monkeypatch.chdir(parent)
184182
modcol.config.args = [
185183
str(Path(x).relative_to(parent)) for x in modcol.config.args

0 commit comments

Comments
 (0)