11import signal
22import textwrap
33
4+ import pexpect
45import 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
@@ -243,20 +247,39 @@ def pytest_configure(config):
243247 assert result .ret == 0
244248
245249
246- def test_mypy_indirect (testdir , xdist_args ):
250+ @pytest .mark .parametrize (
251+ "module_name" ,
252+ [
253+ pytest .param (
254+ "__init__" ,
255+ marks = pytest .mark .xfail (
256+ (3 , 10 ) <= PYTEST_VERSION < (6 , 2 ),
257+ raises = AssertionError ,
258+ reason = "https://github.com/pytest-dev/pytest/issues/8016" ,
259+ ),
260+ ),
261+ "good" ,
262+ ],
263+ )
264+ def test_mypy_indirect (testdir , xdist_args , module_name ):
247265 """Verify that uncollected files checked by mypy cause a failure."""
248266 testdir .makepyfile (
249267 bad = """
250268 def pyfunc(x: int) -> str:
251269 return x * 2
252270 """ ,
253271 )
254- testdir .makepyfile (
255- good = """
256- import bad
257- """ ,
272+ pyfile = testdir .makepyfile (
273+ ** {
274+ module_name : """
275+ import bad
276+ """ ,
277+ },
258278 )
259- result = testdir .runpytest_subprocess ("--mypy" , * xdist_args , "good.py" )
279+ result = testdir .runpytest_subprocess ("--mypy" , * xdist_args , str (pyfile ))
280+ mypy_file_checks = 1
281+ mypy_status_check = 1
282+ result .assert_outcomes (passed = mypy_file_checks , failed = mypy_status_check )
260283 assert result .ret != 0
261284
262285
@@ -309,7 +332,8 @@ def pyfunc(x):
309332 assert result .ret != 0
310333
311334
312- def test_looponfail (testdir ):
335+ @pytest .mark .parametrize ("module_name" , ["__init__" , "test_demo" ])
336+ def test_looponfail (testdir , module_name ):
313337 """Ensure that the plugin works with --looponfail."""
314338
315339 pass_source = textwrap .dedent (
@@ -324,7 +348,7 @@ def pyfunc(x: int) -> str:
324348 return x * 2
325349 """ ,
326350 )
327- pyfile = testdir .makepyfile (fail_source )
351+ pyfile = testdir .makepyfile (** { module_name : fail_source } )
328352 looponfailroot = testdir .mkdir ("looponfailroot" )
329353 looponfailroot_pyfile = looponfailroot .join (pyfile .basename )
330354 pyfile .move (looponfailroot_pyfile )
@@ -345,6 +369,14 @@ def pyfunc(x: int) -> str:
345369 expect_timeout = 30.0 ,
346370 )
347371
372+ num_tests = 2
373+ if module_name == "__init__" and (3 , 10 ) <= PYTEST_VERSION < (6 , 2 ):
374+ # https://github.com/pytest-dev/pytest/issues/8016
375+ # Pytest had a bug where it assumed only a Package would have a basename of
376+ # __init__.py. In this test, Pytest mistakes MypyFile for a Package and
377+ # returns after collecting only one object (the MypyFileItem).
378+ num_tests = 1
379+
348380 def _expect_session ():
349381 child .expect ("==== test session starts ====" )
350382
@@ -353,10 +385,11 @@ def _expect_failure():
353385 child .expect ("==== FAILURES ====" )
354386 child .expect (pyfile .basename + " ____" )
355387 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" )
388+ # if num_tests == 2:
389+ # # These only show with mypy>=0.730:
390+ # child.expect("==== mypy ====")
391+ # child.expect("Found 1 error in 1 file (checked 1 source file)")
392+ child .expect (str (num_tests ) + " failed" )
360393 child .expect ("#### LOOPONFAILING ####" )
361394 _expect_waiting ()
362395
@@ -375,10 +408,27 @@ def _expect_changed():
375408 def _expect_success ():
376409 for _ in range (2 ):
377410 _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" )
411+ # if num_tests == 2:
412+ # # These only show with mypy>=0.730:
413+ # child.expect("==== mypy ====")
414+ # child.expect("Success: no issues found in 1 source file")
415+ try :
416+ child .expect (str (num_tests ) + " passed" )
417+ except pexpect .exceptions .TIMEOUT :
418+ if module_name == "__init__" and (6 , 0 ) <= PYTEST_VERSION < (6 , 2 ):
419+ # MypyItems hit the __init__.py bug too when --looponfail
420+ # re-collects them after the failing file is modified.
421+ # Unlike MypyFile, MypyItem is not a Collector, so this used
422+ # to cause an AttributeError until a workaround was added
423+ # (MypyItem.collect was defined to yield itself).
424+ # Mypy probably noticed the __init__.py problem during the
425+ # development of Pytest 6.0, but the error was addressed
426+ # with an isinstance assertion, which broke the workaround.
427+ # Here, we hit that assertion:
428+ child .expect ("AssertionError" )
429+ child .expect ("1 error" )
430+ pytest .xfail ("https://github.com/pytest-dev/pytest/issues/8016" )
431+ raise
382432 _expect_waiting ()
383433
384434 def _break ():
@@ -391,3 +441,27 @@ def _break():
391441 _break ()
392442 _fix ()
393443 child .kill (signal .SIGTERM )
444+
445+
446+ def test_mypy_item_collect (testdir , xdist_args ):
447+ """Ensure coverage for a 3.10<=pytest<6.0 workaround."""
448+ testdir .makepyfile (
449+ """
450+ def test_mypy_item_collect(request):
451+ plugin = request.config.pluginmanager.getplugin("mypy")
452+ mypy_items = [
453+ item
454+ for item in request.session.items
455+ if isinstance(item, plugin.MypyItem)
456+ ]
457+ assert mypy_items
458+ for mypy_item in mypy_items:
459+ assert all(item is mypy_item for item in mypy_item.collect())
460+ """ ,
461+ )
462+ result = testdir .runpytest_subprocess ("--mypy" , * xdist_args )
463+ test_count = 1
464+ mypy_file_checks = 1
465+ mypy_status_check = 1
466+ result .assert_outcomes (passed = test_count + mypy_file_checks + mypy_status_check )
467+ assert result .ret == 0
0 commit comments