Skip to content

Commit

Permalink
Merge pull request #175 from dmtucker/xdist
Browse files Browse the repository at this point in the history
Refactor xdist integration
  • Loading branch information
dmtucker authored Aug 25, 2024
2 parents 282e0a6 + 8a5fedc commit 749aae2
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 75 deletions.
102 changes: 48 additions & 54 deletions src/pytest_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,28 @@ def pytest_addoption(parser):
)


XDIST_WORKERINPUT_ATTRIBUTE_NAMES = (
"workerinput",
# xdist < 2.0.0:
"slaveinput",
)
def _xdist_worker(config):
try:
return {"input": _xdist_workerinput(config)}
except AttributeError:
return {}


def _get_xdist_workerinput(config_node):
workerinput = None
for attr_name in XDIST_WORKERINPUT_ATTRIBUTE_NAMES:
workerinput = getattr(config_node, attr_name, None)
if workerinput is not None:
break
return workerinput
def _xdist_workerinput(node):
try:
return node.workerinput
except AttributeError: # compat xdist < 2.0
return node.slaveinput


def _is_xdist_controller(config):
"""
True if the code running the given pytest.config object is running in
an xdist controller node or not running xdist at all.
"""
return _get_xdist_workerinput(config) is None
class MypyXdistControllerPlugin:
"""A plugin that is only registered on xdist controller processes."""

def pytest_configure_node(self, node):
"""Pass the config stash to workers."""
_xdist_workerinput(node)["mypy_config_stash_serialized"] = node.config.stash[
stash_key["config"]
].serialized()


def pytest_configure(config):
Expand All @@ -89,7 +89,9 @@ def pytest_configure(config):
register a custom marker for MypyItems,
and configure the plugin based on the CLI.
"""
if _is_xdist_controller(config):
xdist_worker = _xdist_worker(config)
if not xdist_worker:
config.pluginmanager.register(MypyReportingPlugin())

# Get the path to a temporary file and delete it.
# The first MypyItem to run will see the file does not exist,
Expand All @@ -104,15 +106,12 @@ def pytest_configure(config):
# If xdist is enabled, then the results path should be exposed to
# the workers so that they know where to read parsed results from.
if config.pluginmanager.getplugin("xdist"):

class _MypyXdistPlugin:
def pytest_configure_node(self, node): # xdist hook
"""Pass the mypy results path to workers."""
_get_xdist_workerinput(node)["mypy_config_stash_serialized"] = (
node.config.stash[stash_key["config"]].serialized()
)

config.pluginmanager.register(_MypyXdistPlugin())
config.pluginmanager.register(MypyXdistControllerPlugin())
else:
# xdist workers create the stash using input from the controller plugin.
config.stash[stash_key["config"]] = MypyConfigStash.from_serialized(
xdist_worker["input"]["mypy_config_stash_serialized"]
)

config.addinivalue_line(
"markers",
Expand Down Expand Up @@ -278,13 +277,7 @@ def from_mypy(
@classmethod
def from_session(cls, session) -> "MypyResults":
"""Load (or generate) cached mypy results for a pytest session."""
if _is_xdist_controller(session.config):
mypy_config_stash = session.config.stash[stash_key["config"]]
else:
mypy_config_stash = MypyConfigStash.from_serialized(
_get_xdist_workerinput(session.config)["mypy_config_stash_serialized"]
)
mypy_results_path = mypy_config_stash.mypy_results_path
mypy_results_path = session.config.stash[stash_key["config"]].mypy_results_path
with FileLock(str(mypy_results_path) + ".lock"):
try:
with open(mypy_results_path, mode="r") as results_f:
Expand Down Expand Up @@ -313,22 +306,23 @@ class MypyWarning(pytest.PytestWarning):
"""A non-failure message regarding the mypy run."""


def pytest_terminal_summary(terminalreporter, config):
"""Report stderr and unrecognized lines from stdout."""
if not _is_xdist_controller(config):
return
mypy_results_path = config.stash[stash_key["config"]].mypy_results_path
try:
with open(mypy_results_path, mode="r") as results_f:
results = MypyResults.load(results_f)
except FileNotFoundError:
# No MypyItems executed.
return
if results.unmatched_stdout or results.stderr:
terminalreporter.section(terminal_summary_title)
if results.unmatched_stdout:
color = {"red": True} if results.status else {"green": True}
terminalreporter.write_line(results.unmatched_stdout, **color)
if results.stderr:
terminalreporter.write_line(results.stderr, yellow=True)
mypy_results_path.unlink()
class MypyReportingPlugin:
"""A Pytest plugin that reports mypy results."""

def pytest_terminal_summary(self, terminalreporter, config):
"""Report stderr and unrecognized lines from stdout."""
mypy_results_path = config.stash[stash_key["config"]].mypy_results_path
try:
with open(mypy_results_path, mode="r") as results_f:
results = MypyResults.load(results_f)
except FileNotFoundError:
# No MypyItems executed.
return
if results.unmatched_stdout or results.stderr:
terminalreporter.section(terminal_summary_title)
if results.unmatched_stdout:
color = {"red": True} if results.status else {"green": True}
terminalreporter.write_line(results.unmatched_stdout, **color)
if results.stderr:
terminalreporter.write_line(results.stderr, yellow=True)
mypy_results_path.unlink()
11 changes: 3 additions & 8 deletions tests/test_pytest_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,14 +518,10 @@ def test_mypy_no_output(testdir, xdist_args):
conftest="""
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_terminal_summary(config):
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
pytest_mypy = config.pluginmanager.getplugin("mypy")
try:
mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]]
except KeyError:
# xdist worker
return
mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]]
with open(mypy_config_stash.mypy_results_path, mode="w") as results_f:
pytest_mypy.MypyResults(
opts=[],
Expand All @@ -535,7 +531,6 @@ def pytest_terminal_summary(config):
abspath_errors={},
unmatched_stdout="",
).dump(results_f)
yield
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
Expand Down
30 changes: 17 additions & 13 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@
minversion = 4.4
isolated_build = true
envlist =
py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}
py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
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}
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}
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}
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}
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}
publish
static

[gh-actions]
python =
3.7: py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}
3.8: py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}, publish, static
3.9: py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
3.10: py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
3.11: py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
3.12: py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}
3.7: py37-pytest{7.0, 7.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
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
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}
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}
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}
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}

[testenv]
constrain_package_deps = true
Expand All @@ -30,11 +30,15 @@ deps =
pytest8.x: pytest ~= 8.0
mypy1.0: mypy ~= 1.0.0
mypy1.x: mypy ~= 1.0
xdist1.x: pytest-xdist ~= 1.0
xdist2.0: pytest-xdist ~= 2.0.0
xdist2.x: pytest-xdist ~= 2.0
xdist3.0: pytest-xdist ~= 3.0.0
xdist3.x: pytest-xdist ~= 3.0

packaging ~= 21.3
pytest-cov ~= 4.1.0
pytest-randomly ~= 3.4
pytest-xdist ~= 1.34

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

Expand Down

0 comments on commit 749aae2

Please sign in to comment.