Skip to content

Commit 9fd3b66

Browse files
authored
Merge pull request #590 from nicoddemus/auto-hook
2 parents 7caf743 + fe48256 commit 9fd3b66

File tree

11 files changed

+117
-26
lines changed

11 files changed

+117
-26
lines changed

.appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ environment:
55
- TOXENV: "py37-pytestlatest"
66
- TOXENV: "py38-pytestlatest"
77
- TOXENV: "py38-pytestmaster"
8+
- TOXENV: "py38-psutil"
89

910
install:
1011
- C:\Python38\python -m pip install -U pip setuptools virtualenv

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ jobs:
4444
env: TOXENV=py39-pytestlatest
4545
- python: "3.8"
4646
env: TOXENV=py38-pytestmaster
47+
- python: "3.8"
48+
env: TOXENV=py38-psutil
4749

4850
- stage: deploy
4951
python: '3.8'

README.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ Install the plugin with::
5757

5858
pip install pytest-xdist
5959

60-
or use the package in develop/in-place mode with
61-
a checkout of the `pytest-xdist repository`_ ::
6260

63-
pip install --editable .
61+
To use ``psutil`` for detection of the number of CPUs available, install the ``psutil`` extra::
62+
63+
pip install pytest-xdist[psutil]
64+
6465

6566
.. _parallelization:
6667

changelog/585.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
New ``pytest_xdist_auto_num_workers`` hook can be implemented by plugins or ``conftest.py`` files to control the number of workers when ``--numprocesses=auto`` is given in the command-line.

changelog/585.trivial.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``psutil`` has proven to make ``pytest-xdist`` installation in certain platforms and containers problematic, so to use it for automatic number of CPUs detection users need to install the ``psutil`` extra::
2+
3+
pip install pytest-xdist[psutil]

setup.cfg

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
[bdist_wheel]
2-
universal = 1
3-
41
[metadata]
52
license_file = LICENSE
63

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup, find_packages
22

3-
install_requires = ["execnet>=1.1", "psutil>=3.0.0", "pytest>=6.0.0", "pytest-forked"]
3+
install_requires = ["execnet>=1.1", "pytest>=6.0.0", "pytest-forked"]
44

55

66
with open("README.rst") as f:
@@ -18,7 +18,7 @@
1818
platforms=["linux", "osx", "win32"],
1919
packages=find_packages(where="src"),
2020
package_dir={"": "src"},
21-
extras_require={"testing": ["filelock"]},
21+
extras_require={"testing": ["filelock"], "psutil": ["psutil>=3.0"]},
2222
entry_points={
2323
"pytest11": ["xdist = xdist.plugin", "xdist.looponfail = xdist.looponfail"]
2424
},

src/xdist/newhooks.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,13 @@ def pytest_xdist_node_collection_finished(node, ids):
5555
@pytest.mark.firstresult
5656
def pytest_xdist_make_scheduler(config, log):
5757
""" return a node scheduler implementation """
58+
59+
60+
@pytest.mark.firstresult
61+
def pytest_xdist_auto_num_workers(config):
62+
"""
63+
Return the number of workers to spawn when ``--numprocesses=auto`` is given in the
64+
command-line.
65+
66+
.. versionadded:: 2.1
67+
"""

src/xdist/plugin.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
1+
import os
12
import uuid
23

3-
import psutil
44
import py
55
import pytest
66

77

8-
def auto_detect_cpus():
9-
return psutil.cpu_count(logical=False) or psutil.cpu_count() or 1
10-
11-
12-
class AutoInt(int):
13-
"""Mark value as auto-detected."""
8+
def pytest_xdist_auto_num_workers():
9+
try:
10+
import psutil
11+
except ImportError:
12+
pass
13+
else:
14+
count = psutil.cpu_count(logical=False) or psutil.cpu_count()
15+
if count:
16+
return count
17+
try:
18+
from os import sched_getaffinity
19+
20+
def cpu_count():
21+
return len(sched_getaffinity(0))
22+
23+
except ImportError:
24+
if os.environ.get("TRAVIS") == "true":
25+
# workaround https://bitbucket.org/pypy/pypy/issues/2375
26+
return 2
27+
try:
28+
from os import cpu_count
29+
except ImportError:
30+
from multiprocessing import cpu_count
31+
try:
32+
n = cpu_count()
33+
except NotImplementedError:
34+
return 1
35+
return n if n else 1
1436

1537

1638
def parse_numprocesses(s):
1739
if s == "auto":
18-
return AutoInt(auto_detect_cpus())
40+
return "auto"
1941
elif s is not None:
2042
return int(s)
2143

@@ -168,12 +190,13 @@ def pytest_configure(config):
168190
@pytest.mark.tryfirst
169191
def pytest_cmdline_main(config):
170192
usepdb = config.getoption("usepdb", False) # a core option
171-
if isinstance(config.option.numprocesses, AutoInt):
193+
if config.option.numprocesses == "auto":
172194
if usepdb:
173195
config.option.numprocesses = 0
174196
config.option.dist = "no"
175197
else:
176-
config.option.numprocesses = int(config.option.numprocesses)
198+
auto_num_cpus = config.hook.pytest_xdist_auto_num_workers(config=config)
199+
config.option.numprocesses = auto_num_cpus
177200

178201
if config.option.numprocesses:
179202
if config.option.dist == "no":

testing/test_plugin.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
from contextlib import suppress
2+
13
import py
24
import execnet
35
from xdist.workermanage import NodeManager
46

7+
import pytest
8+
59

610
def test_dist_incompatibility_messages(testdir):
711
result = testdir.runpytest("--pdb", "--looponfail")
@@ -35,15 +39,28 @@ def test_dist_options(testdir):
3539

3640

3741
def test_auto_detect_cpus(testdir, monkeypatch):
38-
import psutil
42+
import os
3943
from xdist.plugin import pytest_cmdline_main as check_options
4044

41-
monkeypatch.setattr(psutil, "cpu_count", lambda logical=True: 99)
45+
with suppress(ImportError):
46+
import psutil
47+
48+
monkeypatch.setattr(psutil, "cpu_count", lambda logical=True: None)
49+
50+
if hasattr(os, "sched_getaffinity"):
51+
monkeypatch.setattr(os, "sched_getaffinity", lambda _pid: set(range(99)))
52+
elif hasattr(os, "cpu_count"):
53+
monkeypatch.setattr(os, "cpu_count", lambda: 99)
54+
else:
55+
import multiprocessing
56+
57+
monkeypatch.setattr(multiprocessing, "cpu_count", lambda: 99)
4258

4359
config = testdir.parseconfigure("-n2")
4460
assert config.getoption("numprocesses") == 2
4561

4662
config = testdir.parseconfigure("-nauto")
63+
check_options(config)
4764
assert config.getoption("numprocesses") == 99
4865

4966
config = testdir.parseconfigure("-nauto", "--pdb")
@@ -52,9 +69,37 @@ def test_auto_detect_cpus(testdir, monkeypatch):
5269
assert config.getoption("numprocesses") == 0
5370
assert config.getoption("dist") == "no"
5471

55-
monkeypatch.setattr(psutil, "cpu_count", lambda logical=True: None)
72+
monkeypatch.delattr(os, "sched_getaffinity", raising=False)
73+
monkeypatch.setenv("TRAVIS", "true")
5674
config = testdir.parseconfigure("-nauto")
57-
assert config.getoption("numprocesses") == 1
75+
check_options(config)
76+
assert config.getoption("numprocesses") == 2
77+
78+
79+
def test_auto_detect_cpus_psutil(testdir, monkeypatch):
80+
from xdist.plugin import pytest_cmdline_main as check_options
81+
82+
psutil = pytest.importorskip("psutil")
83+
84+
monkeypatch.setattr(psutil, "cpu_count", lambda logical=True: 42)
85+
86+
config = testdir.parseconfigure("-nauto")
87+
check_options(config)
88+
assert config.getoption("numprocesses") == 42
89+
90+
91+
def test_hook_auto_num_workers(testdir, monkeypatch):
92+
from xdist.plugin import pytest_cmdline_main as check_options
93+
94+
testdir.makeconftest(
95+
"""
96+
def pytest_xdist_auto_num_workers():
97+
return 42
98+
"""
99+
)
100+
config = testdir.parseconfigure("-nauto")
101+
check_options(config)
102+
assert config.getoption("numprocesses") == 42
58103

59104

60105
def test_boxed_with_collect_only(testdir):

0 commit comments

Comments
 (0)