Skip to content

Commit

Permalink
Merge pull request #172 from dmtucker/tests-stash
Browse files Browse the repository at this point in the history
Use config.stash to store the results path
  • Loading branch information
dmtucker authored Aug 17, 2024
2 parents 02795e2 + 90e1f32 commit bf700d9
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 75 deletions.
31 changes: 17 additions & 14 deletions src/pytest_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

mypy_argv = []
nodeid_name = "mypy"
stash_keys = {
"mypy_results_path": pytest.StashKey[Path](),
}
terminal_summary_title = "mypy"


Expand Down Expand Up @@ -80,18 +83,18 @@ def pytest_configure(config):
# Subsequent MypyItems will see the file exists,
# and they will read the parsed results.
with NamedTemporaryFile(delete=True) as tmp_f:
config._mypy_results_path = tmp_f.name
config.stash[stash_keys["mypy_results_path"]] = Path(tmp_f.name)

# 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 config._mypy_results_path to workers."""
_get_xdist_workerinput(node)[
"_mypy_results_path"
] = node.config._mypy_results_path
"""Pass the mypy results path to workers."""
_get_xdist_workerinput(node)["_mypy_results_path"] = str(
node.config.stash[stash_keys["mypy_results_path"]]
)

config.pluginmanager.register(_MypyXdistPlugin())

Expand Down Expand Up @@ -259,14 +262,14 @@ def from_mypy(
@classmethod
def from_session(cls, session) -> "MypyResults":
"""Load (or generate) cached mypy results for a pytest session."""
results_path = (
session.config._mypy_results_path
mypy_results_path = Path(
session.config.stash[stash_keys["mypy_results_path"]]
if _is_xdist_controller(session.config)
else _get_xdist_workerinput(session.config)["_mypy_results_path"]
)
with FileLock(results_path + ".lock"):
with FileLock(str(mypy_results_path) + ".lock"):
try:
with open(results_path, mode="r") as results_f:
with open(mypy_results_path, mode="r") as results_f:
results = cls.load(results_f)
except FileNotFoundError:
results = cls.from_mypy(
Expand All @@ -276,7 +279,7 @@ def from_session(cls, session) -> "MypyResults":
if isinstance(item, MypyFileItem)
],
)
with open(results_path, mode="w") as results_f:
with open(mypy_results_path, mode="w") as results_f:
results.dump(results_f)
return results

Expand All @@ -295,10 +298,10 @@ class MypyWarning(pytest.PytestWarning):
def pytest_terminal_summary(terminalreporter, config):
"""Report stderr and unrecognized lines from stdout."""
if not _is_xdist_controller(config):
# This isn't hit in pytest 5.0 for some reason.
return # pragma: no cover
return
mypy_results_path = config.stash[stash_keys["mypy_results_path"]]
try:
with open(config._mypy_results_path, mode="r") as results_f:
with open(mypy_results_path, mode="r") as results_f:
results = MypyResults.load(results_f)
except FileNotFoundError:
# No MypyItems executed.
Expand All @@ -310,4 +313,4 @@ def pytest_terminal_summary(terminalreporter, config):
terminalreporter.write_line(results.unmatched_stdout, **color)
if results.stderr:
terminalreporter.write_line(results.stderr, yellow=True)
Path(config._mypy_results_path).unlink()
mypy_results_path.unlink()
75 changes: 15 additions & 60 deletions tests/test_pytest_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@

import mypy.version
from packaging.version import Version
import pexpect
import pytest

import pytest_mypy


MYPY_VERSION = Version(mypy.version.__version__)
PYTEST_VERSION = Version(pytest.__version__)
PYTHON_VERSION = Version(
".".join(
str(token)
Expand Down Expand Up @@ -325,14 +323,7 @@ def pytest_configure(config):
@pytest.mark.parametrize(
"module_name",
[
pytest.param(
"__init__",
marks=pytest.mark.xfail(
Version("3.10") <= PYTEST_VERSION < Version("6.2"),
raises=AssertionError,
reason="https://github.com/pytest-dev/pytest/issues/8016",
),
),
"__init__",
"good",
],
)
Expand Down Expand Up @@ -464,14 +455,6 @@ def pyfunc(x: int) -> str:
expect_timeout=60.0,
)

num_tests = 2
if module_name == "__init__" and Version("3.10") <= PYTEST_VERSION < Version("6.2"):
# https://github.com/pytest-dev/pytest/issues/8016
# Pytest had a bug where it assumed only a Package would have a basename of
# __init__.py. In this test, Pytest mistakes MypyFile for a Package and
# returns after collecting only one object (the MypyFileItem).
num_tests = 1

def _expect_session():
child.expect("==== test session starts ====")

Expand All @@ -480,11 +463,9 @@ def _expect_failure():
child.expect("==== FAILURES ====")
child.expect(pyfile.basename + " ____")
child.expect("2: error: Incompatible return value")
# if num_tests == 2:
# # These only show with mypy>=0.730:
# child.expect("==== mypy ====")
# child.expect("Found 1 error in 1 file (checked 1 source file)")
child.expect(str(num_tests) + " failed")
child.expect("==== mypy ====")
child.expect("Found 1 error in 1 file (checked 1 source file)")
child.expect("2 failed")
child.expect("#### LOOPONFAILING ####")
_expect_waiting()

Expand All @@ -503,29 +484,9 @@ def _expect_changed():
def _expect_success():
for _ in range(2):
_expect_session()
# if num_tests == 2:
# # These only show with mypy>=0.730:
# child.expect("==== mypy ====")
# child.expect("Success: no issues found in 1 source file")
try:
child.expect(str(num_tests) + " passed")
except pexpect.exceptions.TIMEOUT:
if module_name == "__init__" and (
Version("6.0") <= PYTEST_VERSION < Version("6.2")
):
# MypyItems hit the __init__.py bug too when --looponfail
# re-collects them after the failing file is modified.
# Unlike MypyFile, MypyItem is not a Collector, so this used
# to cause an AttributeError until a workaround was added
# (MypyItem.collect was defined to yield itself).
# Mypy probably noticed the __init__.py problem during the
# development of Pytest 6.0, but the error was addressed
# with an isinstance assertion, which broke the workaround.
# Here, we hit that assertion:
child.expect("AssertionError")
child.expect("1 error")
pytest.xfail("https://github.com/pytest-dev/pytest/issues/8016")
raise
child.expect("==== mypy ====")
child.expect("Success: no issues found in 1 source file")
child.expect("2 passed")
_expect_waiting()

def _break():
Expand All @@ -550,35 +511,29 @@ def test_mypy_results_from_mypy_with_opts():

def test_mypy_no_output(testdir, xdist_args):
"""No terminal summary is shown if there is no output from mypy."""
type_ignore = (
"# type: ignore"
if (
PYTEST_VERSION
< Version("6.0") # Pytest didn't add type annotations until 6.0.
)
else ""
)
testdir.makepyfile(
# Mypy prints a success message to stderr by default:
# "Success: no issues found in 1 source file"
# Clear stderr and unmatched_stdout to simulate mypy having no output:
conftest=f"""
import pytest {type_ignore}
conftest="""
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_terminal_summary(config):
mypy_results_path = getattr(config, "_mypy_results_path", None)
if not mypy_results_path:
pytest_mypy = config.pluginmanager.getplugin("mypy")
stash_key = pytest_mypy.stash_keys["mypy_results_path"]
try:
mypy_results_path = config.stash[stash_key]
except KeyError:
# xdist worker
return
pytest_mypy = config.pluginmanager.getplugin("mypy")
with open(mypy_results_path, mode="w") as results_f:
pytest_mypy.MypyResults(
opts=[],
stdout="",
stderr="",
status=0,
abspath_errors={{}},
abspath_errors={},
unmatched_stdout="",
).dump(results_f)
yield
Expand Down
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ deps =
mypy1.x: mypy ~= 1.0

packaging ~= 21.3
pexpect ~= 4.8.0
pytest-cov ~= 4.1.0
pytest-randomly ~= 3.4
pytest-xdist ~= 1.34
Expand Down

0 comments on commit bf700d9

Please sign in to comment.