Skip to content

Commit e7eac24

Browse files
committed
Implement MypyItem.collect for pytest < 6.0
1 parent 4802444 commit e7eac24

File tree

2 files changed

+72
-10
lines changed

2 files changed

+72
-10
lines changed

src/pytest_mypy.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ def __init__(self, *args, **kwargs):
145145
super().__init__(*args, **kwargs)
146146
self.add_marker(self.MARKER)
147147

148+
def collect(self):
149+
"""
150+
Partially work around https://github.com/pytest-dev/pytest/issues/8016
151+
for pytest < 6.0 with --looponfail.
152+
"""
153+
yield self
154+
148155
@classmethod
149156
def from_parent(cls, *args, **kwargs):
150157
"""Override from_parent for compatibility."""

tests/test_pytest_mypy.py

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import signal
22
import textwrap
33

4+
import pexpect
45
import pytest
56

67

8+
PYTEST_VERSION = tuple(int(v) for v in pytest.__version__.split(".")[:2])
9+
10+
711
@pytest.fixture(
812
params=[
913
True, # xdist enabled, active
@@ -309,7 +313,8 @@ def pyfunc(x):
309313
assert result.ret != 0
310314

311315

312-
def test_looponfail(testdir):
316+
@pytest.mark.parametrize("module_name", ["__init__", "test_demo"])
317+
def test_looponfail(testdir, module_name):
313318
"""Ensure that the plugin works with --looponfail."""
314319

315320
pass_source = textwrap.dedent(
@@ -324,7 +329,7 @@ def pyfunc(x: int) -> str:
324329
return x * 2
325330
""",
326331
)
327-
pyfile = testdir.makepyfile(fail_source)
332+
pyfile = testdir.makepyfile(**{module_name: fail_source})
328333
looponfailroot = testdir.mkdir("looponfailroot")
329334
looponfailroot_pyfile = looponfailroot.join(pyfile.basename)
330335
pyfile.move(looponfailroot_pyfile)
@@ -345,6 +350,14 @@ def pyfunc(x: int) -> str:
345350
expect_timeout=30.0,
346351
)
347352

353+
num_tests = 2
354+
if module_name == "__init__" and (3, 10) <= PYTEST_VERSION < (6, 2):
355+
# https://github.com/pytest-dev/pytest/issues/8016
356+
# Pytest had a bug where it assumed only a Package would have a basename of
357+
# __init__.py. In this test, Pytest mistakes MypyFile for a Package and
358+
# returns after collecting only one object (the MypyFileItem).
359+
num_tests = 1
360+
348361
def _expect_session():
349362
child.expect("==== test session starts ====")
350363

@@ -353,10 +366,11 @@ def _expect_failure():
353366
child.expect("==== FAILURES ====")
354367
child.expect(pyfile.basename + " ____")
355368
child.expect("2: error: Incompatible return value")
356-
# These only show with mypy>=0.730:
357-
# child.expect("==== mypy ====")
358-
# child.expect("Found 1 error in 1 file (checked 1 source file)")
359-
child.expect("2 failed")
369+
# if num_tests == 2:
370+
# # These only show with mypy>=0.730:
371+
# child.expect("==== mypy ====")
372+
# child.expect("Found 1 error in 1 file (checked 1 source file)")
373+
child.expect(str(num_tests) + " failed")
360374
child.expect("#### LOOPONFAILING ####")
361375
_expect_waiting()
362376

@@ -375,10 +389,27 @@ def _expect_changed():
375389
def _expect_success():
376390
for _ in range(2):
377391
_expect_session()
378-
# These only show with mypy>=0.730:
379-
# child.expect("==== mypy ====")
380-
# child.expect("Success: no issues found in 1 source file")
381-
child.expect("2 passed")
392+
# if num_tests == 2:
393+
# # These only show with mypy>=0.730:
394+
# child.expect("==== mypy ====")
395+
# child.expect("Success: no issues found in 1 source file")
396+
try:
397+
child.expect(str(num_tests) + " passed")
398+
except pexpect.exceptions.TIMEOUT:
399+
if module_name == "__init__" and (6, 0) <= PYTEST_VERSION < (6, 2):
400+
# MypyItems hit the __init__.py bug too when --looponfail
401+
# re-collects them after the failing file is modified.
402+
# Unlike MypyFile, MypyItem is not a Collector, so this used
403+
# to cause an AttributeError until a workaround was added
404+
# (MypyItem.collect was defined to yield itself).
405+
# Mypy probably noticed the __init__.py problem during the
406+
# development of Pytest 6.0, but the error was addressed
407+
# with an isinstance assertion, which broke the workaround.
408+
# Here, we hit that assertion:
409+
child.expect("AssertionError")
410+
child.expect("1 error")
411+
pytest.xfail("https://github.com/pytest-dev/pytest/issues/8016")
412+
raise
382413
_expect_waiting()
383414

384415
def _break():
@@ -391,3 +422,27 @@ def _break():
391422
_break()
392423
_fix()
393424
child.kill(signal.SIGTERM)
425+
426+
427+
def test_mypy_item_collect(testdir, xdist_args):
428+
"""Ensure coverage for a 3.10<=pytest<6.0 workaround."""
429+
testdir.makepyfile(
430+
"""
431+
def test_mypy_item_collect(request):
432+
plugin = request.config.pluginmanager.getplugin("mypy")
433+
mypy_items = [
434+
item
435+
for item in request.session.items
436+
if isinstance(item, plugin.MypyItem)
437+
]
438+
assert mypy_items
439+
for mypy_item in mypy_items:
440+
assert all(item is mypy_item for item in mypy_item.collect())
441+
""",
442+
)
443+
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
444+
test_count = 1
445+
mypy_file_checks = 1
446+
mypy_status_check = 1
447+
result.assert_outcomes(passed=test_count + mypy_file_checks + mypy_status_check)
448+
assert result.ret == 0

0 commit comments

Comments
 (0)