Skip to content

Commit c168706

Browse files
authored
Get -nauto default from PYTEST_XDIST_AUTO_NUM_WORKERS (#829)
* Get `-nauto` default from `PYTEST_XDIST_AUTO_NUM_WORKERS` A few additional tests for existing functionality are added too. And the ``-n logical`` option is documented. Fixes: #792
1 parent ab95a4b commit c168706

File tree

5 files changed

+155
-6
lines changed

5 files changed

+155
-6
lines changed

changelog/792.feature

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The environment variable ``PYTEST_XDIST_AUTO_NUM_WORKERS`` can now be used to
2+
specify the default for ``-n auto`` and ``-n logical``.

changelog/829.doc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document the ``-n logical`` option.

docs/distribution.rst

+21-5
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,29 @@ noticeable amount of time.
1212

1313
With ``-n auto``, pytest-xdist will use as many processes as your computer
1414
has CPU cores.
15+
16+
Use ``-n logical`` to use the number of *logical* CPU cores rather than
17+
physical ones. This currently requires the ``psutils`` package to be installed;
18+
if it is not, pytest-xdist will fall back to ``-n auto`` behavior.
19+
1520
Pass a number, e.g. ``-n 8``, to specify the number of processes explicitly.
1621

17-
To specify a different meaning for ``-n auto`` for your tests,
18-
you can implement the ``pytest_xdist_auto_num_workers``
19-
`pytest hook <https://docs.pytest.org/en/latest/how-to/writing_plugins.html>`__
20-
(a function named ``pytest_xdist_auto_num_workers`` in e.g. ``conftest.py``)
21-
that returns the number of processes to use.
22+
To specify a different meaning for ``-n auto`` and ``-n logical`` for your
23+
tests, you can:
24+
25+
* Set the environment variable ``PYTEST_XDIST_AUTO_NUM_WORKERS`` to the
26+
desired number of processes.
27+
28+
* Implement the ``pytest_xdist_auto_num_workers``
29+
`pytest hook <https://docs.pytest.org/en/latest/how-to/writing_plugins.html>`__
30+
(a ``pytest_xdist_auto_num_workers(config)`` function in e.g. ``conftest.py``)
31+
that returns the number of processes to use.
32+
The hook can use ``config.option.numprocesses`` to determine if the user
33+
asked for ``"auto"`` or ``"logical"``, and it can return ``None`` to fall
34+
back to the default.
35+
36+
If both the hook and environment variable are specified, the hook takes
37+
priority.
2238

2339

2440
Parallelization can be configured further with these options:

src/xdist/plugin.py

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import uuid
33
import sys
4+
import warnings
45

56
import pytest
67

@@ -12,6 +13,13 @@
1213

1314
@pytest.hookimpl
1415
def pytest_xdist_auto_num_workers(config):
16+
env_var = os.environ.get("PYTEST_XDIST_AUTO_NUM_WORKERS")
17+
if env_var:
18+
try:
19+
return int(env_var)
20+
except ValueError:
21+
warnings.warn("PYTEST_XDIST_AUTO_NUM_WORKERS is not a number: {env_var!r}. Ignoring it.")
22+
1523
try:
1624
import psutil
1725
except ImportError:

testing/test_plugin.py

+123-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,22 @@
11
from contextlib import suppress
22
from pathlib import Path
3+
import sys
4+
import os
35

46
import execnet
57
from xdist.workermanage import NodeManager
68

79
import pytest
810

911

12+
@pytest.fixture
13+
def monkeypatch_3_cpus(monkeypatch: pytest.MonkeyPatch):
14+
"""Make pytest-xdist believe the system has 3 CPUs"""
15+
monkeypatch.setitem(sys.modules, "psutil", None) # block import
16+
monkeypatch.delattr(os, "sched_getaffinity", raising=False)
17+
monkeypatch.setattr(os, "cpu_count", lambda: 3)
18+
19+
1020
def test_dist_incompatibility_messages(pytester: pytest.Pytester) -> None:
1121
result = pytester.runpytest("--pdb", "--looponfail")
1222
assert result.ret != 0
@@ -41,7 +51,6 @@ def test_dist_options(pytester: pytest.Pytester) -> None:
4151
def test_auto_detect_cpus(
4252
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
4353
) -> None:
44-
import os
4554
from xdist.plugin import pytest_cmdline_main as check_options
4655

4756
with suppress(ImportError):
@@ -102,6 +111,20 @@ def test_auto_detect_cpus_psutil(
102111
assert config.getoption("numprocesses") == 84
103112

104113

114+
def test_auto_detect_cpus_os(
115+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, monkeypatch_3_cpus
116+
) -> None:
117+
from xdist.plugin import pytest_cmdline_main as check_options
118+
119+
config = pytester.parseconfigure("-nauto")
120+
check_options(config)
121+
assert config.getoption("numprocesses") == 3
122+
123+
config = pytester.parseconfigure("-nlogical")
124+
check_options(config)
125+
assert config.getoption("numprocesses") == 3
126+
127+
105128
def test_hook_auto_num_workers(
106129
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
107130
) -> None:
@@ -122,6 +145,105 @@ def pytest_xdist_auto_num_workers():
122145
assert config.getoption("numprocesses") == 42
123146

124147

148+
def test_hook_auto_num_workers_arg(
149+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
150+
) -> None:
151+
# config.option.numprocesses is a pytest feature,
152+
# but we document it so let's test it.
153+
from xdist.plugin import pytest_cmdline_main as check_options
154+
155+
pytester.makeconftest(
156+
"""
157+
def pytest_xdist_auto_num_workers(config):
158+
if config.option.numprocesses == 'auto':
159+
return 42
160+
if config.option.numprocesses == 'logical':
161+
return 8
162+
"""
163+
)
164+
config = pytester.parseconfigure("-nauto")
165+
check_options(config)
166+
assert config.getoption("numprocesses") == 42
167+
168+
config = pytester.parseconfigure("-nlogical")
169+
check_options(config)
170+
assert config.getoption("numprocesses") == 8
171+
172+
173+
def test_hook_auto_num_workers_none(
174+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, monkeypatch_3_cpus
175+
) -> None:
176+
# Returning None from a hook to skip it is pytest behavior,
177+
# but we document it so let's test it.
178+
from xdist.plugin import pytest_cmdline_main as check_options
179+
180+
pytester.makeconftest(
181+
"""
182+
def pytest_xdist_auto_num_workers():
183+
return None
184+
"""
185+
)
186+
config = pytester.parseconfigure("-nauto")
187+
check_options(config)
188+
assert config.getoption("numprocesses") == 3
189+
190+
monkeypatch.setenv("PYTEST_XDIST_AUTO_NUM_WORKERS", "5")
191+
192+
config = pytester.parseconfigure("-nauto")
193+
check_options(config)
194+
assert config.getoption("numprocesses") == 5
195+
196+
197+
def test_envvar_auto_num_workers(
198+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch
199+
) -> None:
200+
from xdist.plugin import pytest_cmdline_main as check_options
201+
202+
monkeypatch.setenv("PYTEST_XDIST_AUTO_NUM_WORKERS", "7")
203+
204+
config = pytester.parseconfigure("-nauto")
205+
check_options(config)
206+
assert config.getoption("numprocesses") == 7
207+
208+
config = pytester.parseconfigure("-nlogical")
209+
check_options(config)
210+
assert config.getoption("numprocesses") == 7
211+
212+
213+
def test_envvar_auto_num_workers_warn(
214+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, monkeypatch_3_cpus
215+
) -> None:
216+
from xdist.plugin import pytest_cmdline_main as check_options
217+
218+
monkeypatch.setenv("PYTEST_XDIST_AUTO_NUM_WORKERS", "fourscore")
219+
220+
config = pytester.parseconfigure("-nauto")
221+
with pytest.warns(UserWarning):
222+
check_options(config)
223+
assert config.getoption("numprocesses") == 3
224+
225+
226+
def test_auto_num_workers_hook_overrides_envvar(
227+
pytester: pytest.Pytester, monkeypatch: pytest.MonkeyPatch, monkeypatch_3_cpus
228+
) -> None:
229+
from xdist.plugin import pytest_cmdline_main as check_options
230+
231+
monkeypatch.setenv("PYTEST_XDIST_AUTO_NUM_WORKERS", "987")
232+
pytester.makeconftest(
233+
"""
234+
def pytest_xdist_auto_num_workers():
235+
return 2
236+
"""
237+
)
238+
config = pytester.parseconfigure("-nauto")
239+
check_options(config)
240+
assert config.getoption("numprocesses") == 2
241+
242+
config = pytester.parseconfigure("-nauto")
243+
check_options(config)
244+
assert config.getoption("numprocesses") == 2
245+
246+
125247
def test_dsession_with_collect_only(pytester: pytest.Pytester) -> None:
126248
from xdist.plugin import pytest_cmdline_main as check_options
127249
from xdist.plugin import pytest_configure as configure

0 commit comments

Comments
 (0)