Skip to content

Commit 749aae2

Browse files
authored
Merge pull request #175 from dmtucker/xdist
Refactor xdist integration
2 parents 282e0a6 + 8a5fedc commit 749aae2

File tree

3 files changed

+68
-75
lines changed

3 files changed

+68
-75
lines changed

src/pytest_mypy.py

Lines changed: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -59,28 +59,28 @@ def pytest_addoption(parser):
5959
)
6060

6161

62-
XDIST_WORKERINPUT_ATTRIBUTE_NAMES = (
63-
"workerinput",
64-
# xdist < 2.0.0:
65-
"slaveinput",
66-
)
62+
def _xdist_worker(config):
63+
try:
64+
return {"input": _xdist_workerinput(config)}
65+
except AttributeError:
66+
return {}
6767

6868

69-
def _get_xdist_workerinput(config_node):
70-
workerinput = None
71-
for attr_name in XDIST_WORKERINPUT_ATTRIBUTE_NAMES:
72-
workerinput = getattr(config_node, attr_name, None)
73-
if workerinput is not None:
74-
break
75-
return workerinput
69+
def _xdist_workerinput(node):
70+
try:
71+
return node.workerinput
72+
except AttributeError: # compat xdist < 2.0
73+
return node.slaveinput
7674

7775

78-
def _is_xdist_controller(config):
79-
"""
80-
True if the code running the given pytest.config object is running in
81-
an xdist controller node or not running xdist at all.
82-
"""
83-
return _get_xdist_workerinput(config) is None
76+
class MypyXdistControllerPlugin:
77+
"""A plugin that is only registered on xdist controller processes."""
78+
79+
def pytest_configure_node(self, node):
80+
"""Pass the config stash to workers."""
81+
_xdist_workerinput(node)["mypy_config_stash_serialized"] = node.config.stash[
82+
stash_key["config"]
83+
].serialized()
8484

8585

8686
def pytest_configure(config):
@@ -89,7 +89,9 @@ def pytest_configure(config):
8989
register a custom marker for MypyItems,
9090
and configure the plugin based on the CLI.
9191
"""
92-
if _is_xdist_controller(config):
92+
xdist_worker = _xdist_worker(config)
93+
if not xdist_worker:
94+
config.pluginmanager.register(MypyReportingPlugin())
9395

9496
# Get the path to a temporary file and delete it.
9597
# The first MypyItem to run will see the file does not exist,
@@ -104,15 +106,12 @@ def pytest_configure(config):
104106
# If xdist is enabled, then the results path should be exposed to
105107
# the workers so that they know where to read parsed results from.
106108
if config.pluginmanager.getplugin("xdist"):
107-
108-
class _MypyXdistPlugin:
109-
def pytest_configure_node(self, node): # xdist hook
110-
"""Pass the mypy results path to workers."""
111-
_get_xdist_workerinput(node)["mypy_config_stash_serialized"] = (
112-
node.config.stash[stash_key["config"]].serialized()
113-
)
114-
115-
config.pluginmanager.register(_MypyXdistPlugin())
109+
config.pluginmanager.register(MypyXdistControllerPlugin())
110+
else:
111+
# xdist workers create the stash using input from the controller plugin.
112+
config.stash[stash_key["config"]] = MypyConfigStash.from_serialized(
113+
xdist_worker["input"]["mypy_config_stash_serialized"]
114+
)
116115

117116
config.addinivalue_line(
118117
"markers",
@@ -278,13 +277,7 @@ def from_mypy(
278277
@classmethod
279278
def from_session(cls, session) -> "MypyResults":
280279
"""Load (or generate) cached mypy results for a pytest session."""
281-
if _is_xdist_controller(session.config):
282-
mypy_config_stash = session.config.stash[stash_key["config"]]
283-
else:
284-
mypy_config_stash = MypyConfigStash.from_serialized(
285-
_get_xdist_workerinput(session.config)["mypy_config_stash_serialized"]
286-
)
287-
mypy_results_path = mypy_config_stash.mypy_results_path
280+
mypy_results_path = session.config.stash[stash_key["config"]].mypy_results_path
288281
with FileLock(str(mypy_results_path) + ".lock"):
289282
try:
290283
with open(mypy_results_path, mode="r") as results_f:
@@ -313,22 +306,23 @@ class MypyWarning(pytest.PytestWarning):
313306
"""A non-failure message regarding the mypy run."""
314307

315308

316-
def pytest_terminal_summary(terminalreporter, config):
317-
"""Report stderr and unrecognized lines from stdout."""
318-
if not _is_xdist_controller(config):
319-
return
320-
mypy_results_path = config.stash[stash_key["config"]].mypy_results_path
321-
try:
322-
with open(mypy_results_path, mode="r") as results_f:
323-
results = MypyResults.load(results_f)
324-
except FileNotFoundError:
325-
# No MypyItems executed.
326-
return
327-
if results.unmatched_stdout or results.stderr:
328-
terminalreporter.section(terminal_summary_title)
329-
if results.unmatched_stdout:
330-
color = {"red": True} if results.status else {"green": True}
331-
terminalreporter.write_line(results.unmatched_stdout, **color)
332-
if results.stderr:
333-
terminalreporter.write_line(results.stderr, yellow=True)
334-
mypy_results_path.unlink()
309+
class MypyReportingPlugin:
310+
"""A Pytest plugin that reports mypy results."""
311+
312+
def pytest_terminal_summary(self, terminalreporter, config):
313+
"""Report stderr and unrecognized lines from stdout."""
314+
mypy_results_path = config.stash[stash_key["config"]].mypy_results_path
315+
try:
316+
with open(mypy_results_path, mode="r") as results_f:
317+
results = MypyResults.load(results_f)
318+
except FileNotFoundError:
319+
# No MypyItems executed.
320+
return
321+
if results.unmatched_stdout or results.stderr:
322+
terminalreporter.section(terminal_summary_title)
323+
if results.unmatched_stdout:
324+
color = {"red": True} if results.status else {"green": True}
325+
terminalreporter.write_line(results.unmatched_stdout, **color)
326+
if results.stderr:
327+
terminalreporter.write_line(results.stderr, yellow=True)
328+
mypy_results_path.unlink()

tests/test_pytest_mypy.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -518,14 +518,10 @@ def test_mypy_no_output(testdir, xdist_args):
518518
conftest="""
519519
import pytest
520520
521-
@pytest.hookimpl(hookwrapper=True)
522-
def pytest_terminal_summary(config):
521+
@pytest.hookimpl(trylast=True)
522+
def pytest_configure(config):
523523
pytest_mypy = config.pluginmanager.getplugin("mypy")
524-
try:
525-
mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]]
526-
except KeyError:
527-
# xdist worker
528-
return
524+
mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]]
529525
with open(mypy_config_stash.mypy_results_path, mode="w") as results_f:
530526
pytest_mypy.MypyResults(
531527
opts=[],
@@ -535,7 +531,6 @@ def pytest_terminal_summary(config):
535531
abspath_errors={},
536532
unmatched_stdout="",
537533
).dump(results_f)
538-
yield
539534
""",
540535
)
541536
result = testdir.runpytest_subprocess("--mypy", *xdist_args)

tox.ini

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@
33
minversion = 4.4
44
isolated_build = true
55
envlist =
6-
py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}
7-
py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
8-
py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
9-
py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
10-
py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
11-
py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
6+
py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
7+
py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
8+
py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
9+
py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
10+
py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
11+
py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
1212
publish
1313
static
1414

1515
[gh-actions]
1616
python =
17-
3.7: py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}
18-
3.8: py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}, publish, static
19-
3.9: py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
20-
3.10: py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
21-
3.11: py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
22-
3.12: py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
17+
3.7: py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
18+
3.8: py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}, publish, static
19+
3.9: py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
20+
3.10: py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
21+
3.11: py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
22+
3.12: py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
2323

2424
[testenv]
2525
constrain_package_deps = true
@@ -30,11 +30,15 @@ deps =
3030
pytest8.x: pytest ~= 8.0
3131
mypy1.0: mypy ~= 1.0.0
3232
mypy1.x: mypy ~= 1.0
33+
xdist1.x: pytest-xdist ~= 1.0
34+
xdist2.0: pytest-xdist ~= 2.0.0
35+
xdist2.x: pytest-xdist ~= 2.0
36+
xdist3.0: pytest-xdist ~= 3.0.0
37+
xdist3.x: pytest-xdist ~= 3.0
3338

3439
packaging ~= 21.3
3540
pytest-cov ~= 4.1.0
3641
pytest-randomly ~= 3.4
37-
pytest-xdist ~= 1.34
3842

3943
commands = pytest -p no:mypy {posargs:--cov pytest_mypy --cov-branch --cov-fail-under 100 --cov-report term-missing -n auto}
4044

0 commit comments

Comments
 (0)