@@ -235,8 +235,10 @@ def repr_failure(
235
235
return super ().repr_failure (excinfo )
236
236
237
237
238
- def _error_severity (error : str ) -> str :
239
- components = [component .strip () for component in error .split (":" )]
238
+ def _error_severity (line : str ) -> Optional [str ]:
239
+ components = [component .strip () for component in line .split (":" , 3 )]
240
+ if len (components ) < 2 :
241
+ return None
240
242
# The second component is either the line or the severity:
241
243
# demo/note.py:2: note: By default the bodies of untyped functions are not checked
242
244
# demo/sub/conftest.py: error: Duplicate module named "conftest"
@@ -249,20 +251,22 @@ class MypyFileItem(MypyItem):
249
251
def runtest (self ) -> None :
250
252
"""Raise an exception if mypy found errors for this item."""
251
253
results = MypyResults .from_session (self .session )
252
- abspath = str (self .path .resolve ())
253
- errors = [
254
- error .partition (":" )[2 ].strip ()
255
- for error in results .abspath_errors .get (abspath , [])
256
- ]
257
- if errors and not all (_error_severity (error ) == "note" for error in errors ):
254
+ lines = results .path_lines .get (self .path .resolve (), [])
255
+ if lines and not all (_error_severity (line ) == "note" for line in lines ):
258
256
if self .session .config .option .mypy_xfail :
259
257
self .add_marker (
260
258
pytest .mark .xfail (
261
259
raises = MypyError ,
262
260
reason = "mypy errors are expected by --mypy-xfail." ,
263
261
)
264
262
)
265
- raise MypyError (file_error_formatter (self , results , errors ))
263
+ raise MypyError (
264
+ file_error_formatter (
265
+ self ,
266
+ results ,
267
+ errors = [line .partition (":" )[2 ].strip () for line in lines ],
268
+ )
269
+ )
266
270
267
271
def reportinfo (self ) -> Tuple [str , None , str ]:
268
272
"""Produce a heading for the test report."""
@@ -296,24 +300,32 @@ def runtest(self) -> None:
296
300
class MypyResults :
297
301
"""Parsed results from Mypy."""
298
302
299
- _abspath_errors_type = typing .Dict [str , typing .List [str ]]
300
303
_encoding = "utf-8"
301
304
302
305
opts : List [str ]
306
+ args : List [str ]
303
307
stdout : str
304
308
stderr : str
305
309
status : int
306
- abspath_errors : _abspath_errors_type
307
- unmatched_stdout : str
310
+ path_lines : Dict [Optional [Path ], List [str ]]
308
311
309
312
def dump (self , results_f : IO [bytes ]) -> None :
310
313
"""Cache results in a format that can be parsed by load()."""
311
- results_f .write (json .dumps (vars (self )).encode (self ._encoding ))
314
+ prepared = vars (self ).copy ()
315
+ prepared ["path_lines" ] = {
316
+ str (path or "" ): lines for path , lines in prepared ["path_lines" ].items ()
317
+ }
318
+ results_f .write (json .dumps (prepared ).encode (self ._encoding ))
312
319
313
320
@classmethod
314
321
def load (cls , results_f : IO [bytes ]) -> MypyResults :
315
322
"""Get results cached by dump()."""
316
- return cls (** json .loads (results_f .read ().decode (cls ._encoding )))
323
+ prepared = json .loads (results_f .read ().decode (cls ._encoding ))
324
+ prepared ["path_lines" ] = {
325
+ Path (path ) if path else None : lines
326
+ for path , lines in prepared ["path_lines" ].items ()
327
+ }
328
+ return cls (** prepared )
317
329
318
330
@classmethod
319
331
def from_mypy (
@@ -326,33 +338,31 @@ def from_mypy(
326
338
327
339
if opts is None :
328
340
opts = mypy_argv [:]
329
- abspath_errors = {
330
- str (path .resolve ()): [] for path in paths
331
- } # type: MypyResults._abspath_errors_type
341
+ args = [str (path ) for path in paths ]
332
342
333
- cwd = Path .cwd ()
334
- stdout , stderr , status = mypy .api .run (
335
- opts + [str (Path (key ).relative_to (cwd )) for key in abspath_errors .keys ()]
336
- )
343
+ stdout , stderr , status = mypy .api .run (opts + args )
337
344
338
- unmatched_lines = []
345
+ path_lines : Dict [Optional [Path ], List [str ]] = {
346
+ path .resolve (): [] for path in paths
347
+ }
348
+ path_lines [None ] = []
339
349
for line in stdout .split ("\n " ):
340
350
if not line :
341
351
continue
342
- path , _ , error = line .partition (":" )
343
- abspath = str (Path (path ).resolve ())
352
+ path = Path (line .partition (":" )[0 ]).resolve ()
344
353
try :
345
- abspath_errors [ abspath ]. append ( line )
354
+ lines = path_lines [ path ]
346
355
except KeyError :
347
- unmatched_lines .append (line )
356
+ lines = path_lines [None ]
357
+ lines .append (line )
348
358
349
359
return cls (
350
360
opts = opts ,
361
+ args = args ,
351
362
stdout = stdout ,
352
363
stderr = stderr ,
353
364
status = status ,
354
- abspath_errors = abspath_errors ,
355
- unmatched_stdout = "\n " .join (unmatched_lines ),
365
+ path_lines = path_lines ,
356
366
)
357
367
358
368
@classmethod
@@ -364,9 +374,10 @@ def from_session(cls, session: pytest.Session) -> MypyResults:
364
374
with open (mypy_results_path , mode = "rb" ) as results_f :
365
375
results = cls .load (results_f )
366
376
except FileNotFoundError :
377
+ cwd = Path .cwd ()
367
378
results = cls .from_mypy (
368
379
[
369
- item .path
380
+ item .path . relative_to ( cwd )
370
381
for item in session .items
371
382
if isinstance (item , MypyFileItem )
372
383
],
@@ -408,14 +419,17 @@ def pytest_terminal_summary(
408
419
else :
409
420
for note in (
410
421
unreported_note
411
- for errors in results .abspath_errors .values ()
412
- if all (_error_severity (error ) == "note" for error in errors )
413
- for unreported_note in errors
422
+ for path , lines in results .path_lines .items ()
423
+ if path is not None
424
+ if all (_error_severity (line ) == "note" for line in lines )
425
+ for unreported_note in lines
414
426
):
415
427
terminalreporter .write_line (note )
416
- if results .unmatched_stdout :
428
+ if results .path_lines . get ( None ) :
417
429
color = {"red" : True } if results .status else {"green" : True }
418
- terminalreporter .write_line (results .unmatched_stdout , ** color )
430
+ terminalreporter .write_line (
431
+ "\n " .join (results .path_lines [None ]), ** color
432
+ )
419
433
if results .stderr :
420
434
terminalreporter .write_line (results .stderr , yellow = True )
421
435
0 commit comments