8000 Refactor xdist integration by dmtucker · Pull Request #175 · realpython/pytest-mypy · GitHub
[go: up one dir, main page]

Skip to content

Refactor xdist integration #175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = Myp 8000 yResults.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
Loading
0