Merge pull request #762 from marcosanchotene/multiple-log-files
This commit is contained in:
@@ -4,6 +4,11 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet, --target-version, py35]
|
args: [--safe, --quiet, --target-version, py35]
|
||||||
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
|
rev: v1.12.0
|
||||||
|
hooks:
|
||||||
|
- id: blacken-docs
|
||||||
|
additional_dependencies: [black==20.8b1]
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.1.0
|
rev: v4.1.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ master_doc = "index"
|
|||||||
# ones.
|
# ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
"sphinx_rtd_theme",
|
"sphinx_rtd_theme",
|
||||||
|
"sphinx.ext.autodoc",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ The test distribution algorithm is configured with the ``--dist`` command-line o
|
|||||||
def test1():
|
def test1():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestA:
|
class TestA:
|
||||||
@pytest.mark.xdist_group("group1")
|
@pytest.mark.xdist_group("group1")
|
||||||
def test2():
|
def test2():
|
||||||
|
|||||||
@@ -24,45 +24,24 @@ When ``xdist`` is disabled (running with ``-n0`` for example), then
|
|||||||
Worker processes also have the following environment variables
|
Worker processes also have the following environment variables
|
||||||
defined:
|
defined:
|
||||||
|
|
||||||
* ``PYTEST_XDIST_WORKER``: the name of the worker, e.g., ``"gw2"``.
|
.. envvar:: PYTEST_XDIST_WORKER
|
||||||
* ``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 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
|
The information about the worker_id in a test is stored in the ``TestReport`` as
|
||||||
well, under the ``worker_id`` attribute.
|
well, under the ``worker_id`` attribute.
|
||||||
|
|
||||||
Since version 2.0, the following functions are also available in the ``xdist`` module:
|
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
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
.. 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
|
Identifying workers from the system environment
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -98,6 +77,7 @@ wanted to create a separate database for each test run:
|
|||||||
import pytest
|
import pytest
|
||||||
from posix_ipc import Semaphore, O_CREAT
|
from posix_ipc import Semaphore, O_CREAT
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
def create_unique_database(testrun_uid):
|
def create_unique_database(testrun_uid):
|
||||||
""" create a unique database for this particular test run """
|
""" create a unique database for this particular test run """
|
||||||
@@ -107,6 +87,7 @@ wanted to create a separate database for each test run:
|
|||||||
if not database_exists(database_url):
|
if not database_exists(database_url):
|
||||||
create_database(database_url)
|
create_database(database_url)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def db(testrun_uid):
|
def db(testrun_uid):
|
||||||
""" retrieve unique database """
|
""" retrieve unique database """
|
||||||
@@ -116,7 +97,9 @@ wanted to create a separate database for each test run:
|
|||||||
|
|
||||||
Additionally, during a test run, the following environment variable is defined:
|
Additionally, during a test run, the following environment variable is defined:
|
||||||
|
|
||||||
* ``PYTEST_XDIST_TESTRUNUID``: the unique id of the test run.
|
.. envvar:: PYTEST_XDIST_TESTRUNUID
|
||||||
|
|
||||||
|
The unique id of the test run.
|
||||||
|
|
||||||
Accessing ``sys.argv`` from the controller node in workers
|
Accessing ``sys.argv`` from the controller node in workers
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -222,3 +205,46 @@ 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
|
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.
|
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`
|
||||||
|
an option to ``pytest.ini`` for the file base name. Then, in ``conftest.py``,
|
||||||
|
register it with ``pytest_addoption(parser)`` and use ``pytest_configure(config)``
|
||||||
|
to rename it with the worker id.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
log_file_format = %(asctime)s %(name)s %(levelname)s %(message)s
|
||||||
|
log_file_level = INFO
|
||||||
|
worker_log_file = tests_{worker_id}.log
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# content of conftest.py
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
parser.addini(
|
||||||
|
"worker_log_file",
|
||||||
|
help="Similar to log_file, but %w will be replaced with a worker identifier.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_configure(config):
|
||||||
|
worker_id = os.environ.get("PYTEST_XDIST_WORKER")
|
||||||
|
if worker_id is not None:
|
||||||
|
log_file = config.getini("worker_log_file")
|
||||||
|
logging.basicConfig(
|
||||||
|
format=config.getini("log_file_format"),
|
||||||
|
filename=log_file.format(worker_id=worker_id),
|
||||||
|
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``.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from typing import cast
|
|
||||||
|
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
import shutil
|
import shutil
|
||||||
@@ -125,7 +123,7 @@ class TestRemoteControl:
|
|||||||
failures = control.runsession()
|
failures = control.runsession()
|
||||||
assert failures
|
assert failures
|
||||||
control.setup()
|
control.setup()
|
||||||
item_path = item.path if PYTEST_GTE_7 else Path(cast(py.path.local, item.fspath)) # type: ignore[attr-defined]
|
item_path = item.path if PYTEST_GTE_7 else Path(str(item.fspath)) # type: ignore[attr-defined]
|
||||||
item_path.write_text("def test_func():\n assert 1\n")
|
item_path.write_text("def test_func():\n assert 1\n")
|
||||||
removepyc(item_path)
|
removepyc(item_path)
|
||||||
topdir, failures = control.runsession()[:2]
|
topdir, failures = control.runsession()[:2]
|
||||||
@@ -146,7 +144,7 @@ class TestRemoteControl:
|
|||||||
if PYTEST_GTE_7:
|
if PYTEST_GTE_7:
|
||||||
modcol_path = modcol.path # type:ignore[attr-defined]
|
modcol_path = modcol.path # type:ignore[attr-defined]
|
||||||
else:
|
else:
|
||||||
modcol_path = Path(cast(py.path.local, modcol.fspath))
|
modcol_path = Path(str(modcol.fspath))
|
||||||
|
|
||||||
modcol_path.write_text(
|
modcol_path.write_text(
|
||||||
textwrap.dedent(
|
textwrap.dedent(
|
||||||
@@ -179,7 +177,7 @@ class TestRemoteControl:
|
|||||||
if PYTEST_GTE_7:
|
if PYTEST_GTE_7:
|
||||||
parent = modcol.path.parent.parent # type: ignore[attr-defined]
|
parent = modcol.path.parent.parent # type: ignore[attr-defined]
|
||||||
else:
|
else:
|
||||||
parent = Path(cast(py.path.local, modcol.fspath).dirpath().dirpath())
|
parent = Path(modcol.fspath.dirpath().dirpath())
|
||||||
monkeypatch.chdir(parent)
|
monkeypatch.chdir(parent)
|
||||||
modcol.config.args = [
|
modcol.config.args = [
|
||||||
str(Path(x).relative_to(parent)) for x in modcol.config.args
|
str(Path(x).relative_to(parent)) for x in modcol.config.args
|
||||||
|
|||||||
Reference in New Issue
Block a user