9
9
import enum
10
10
import importlib
11
11
import inspect
12
- import io
12
+ import os
13
13
import re
14
14
import sys
15
15
import types
16
16
import warnings
17
17
from contextlib import redirect_stdout , redirect_stderr
18
18
from functools import singledispatch
19
19
from pathlib import Path
20
- from typing import Any , Dict , Generic , Iterator , List , Optional , Tuple , TypeVar , Union , cast , Set
20
+ from typing import Any , Dict , Generic , Iterator , List , Optional , Tuple , TypeVar , Union , cast
21
21
22
- from typing_extensions import Type , Literal
22
+ from typing_extensions import Type
23
23
24
24
import mypy .build
25
25
import mypy .modulefinder
28
28
import mypy .version
29
29
from mypy import nodes
30
30
from mypy .config_parser import parse_config_file
31
- from mypy .messages import plural_s
32
31
from mypy .options import Options
33
- from mypy .util import FancyFormatter , bytes_to_human_readable_repr , is_dunder
32
+ from mypy .util import FancyFormatter , bytes_to_human_readable_repr , plural_s , is_dunder
34
33
35
34
36
35
class Missing :
@@ -54,14 +53,8 @@ def _style(message: str, **kwargs: Any) -> str:
54
53
return _formatter .style (message , ** kwargs )
55
54
56
55
57
- def log_error (message : str ) -> Literal [1 ]:
58
- """Print a bold red message."""
59
- print (_style (message , color = "red" , bold = True ))
60
- return 1
61
-
62
-
63
- class Failure (Exception ):
64
- """Used to indicate a handled failure state"""
56
+ class StubtestFailure (Exception ):
57
+ pass
65
58
66
59
67
60
class Error :
@@ -164,7 +157,7 @@ def get_description(self, concise: bool = False) -> str:
164
157
# ====================
165
158
166
159
167
- def test_module (module_name : str , concise : bool = False ) -> Iterator [Error ]:
160
+ def test_module (module_name : str ) -> Iterator [Error ]:
168
161
"""Tests a given module's stub against introspecting it at runtime.
169
162
170
163
Requires the stub to have been built already, accomplished by a call to ``build_stubs``.
@@ -177,37 +170,18 @@ def test_module(module_name: str, concise: bool = False) -> Iterator[Error]:
177
170
yield Error ([module_name ], "failed to find stubs" , MISSING , None , runtime_desc = "N/A" )
178
171
return
179
172
180
- argv = sys .argv
181
- sys .argv = []
182
- output = io .StringIO ()
183
- outerror = io .StringIO ()
184
173
try :
185
- with warnings .catch_warnings (), redirect_stdout (output ), redirect_stderr (outerror ):
186
- warnings .simplefilter ("ignore" )
187
- runtime = importlib .import_module (module_name )
188
- # Also run the equivalent of `from module import *`
189
- # This could have the additional effect of loading not-yet-loaded submodules
190
- # mentioned in __all__
191
- __import__ (module_name , fromlist = ["*" ])
192
- except KeyboardInterrupt :
193
- raise
194
- except BaseException as e : # to catch every possible error
195
- yield Error ([module_name ], f"failed to import: { type (e ).__name__ } { e } " , stub , MISSING ,
196
- stub_desc = stub .path , runtime_desc = "Missing due to failed import" )
174
+ with open (os .devnull , "w" ) as devnull :
175
+ with warnings .catch_warnings (), redirect_stdout (devnull ), redirect_stderr (devnull ):
176
+ warnings .simplefilter ("ignore" )
177
+ runtime = importlib .import_module (module_name )
178
+ # Also run the equivalent of `from module import *`
179
+ # This could have the additional effect of loading not-yet-loaded submodules
180
+ # mentioned in __all__
181
+ __import__ (module_name , fromlist = ["*" ])
182
+ except Exception as e :
183
+ yield Error ([module_name ], f"failed to import, { type (e ).__name__ } : { e } " , stub , MISSING )
197
184
return
198
- finally :
199
- sys .argv = argv
200
- stdout = output .getvalue ()
201
- stderr = outerror .getvalue ()
202
- if stdout or stderr and not concise :
203
- print (f"Found output while loading '{ module_name } '" )
204
- if stdout :
205
- print (_style ("======= standard output ============" , bold = True ))
206
- print (stdout , end = "" if stdout [- 1 ] == "\n " else "\n " )
207
- if stderr :
208
- print (_style ("======= standard error =============" , bold = True ))
209
- print (stderr , end = "" if stderr [- 1 ] == "\n " else "\n " )
210
- print (_style ("====================================" , bold = True ))
211
185
212
186
with warnings .catch_warnings ():
213
187
warnings .simplefilter ("ignore" )
@@ -494,21 +468,21 @@ def get_name(arg: Any) -> str:
494
468
return arg .name
495
469
if isinstance (arg , nodes .Argument ):
496
470
return arg .variable .name
497
- raise Failure
471
+ raise AssertionError
498
472
499
473
def get_type (arg : Any ) -> Optional [str ]:
500
474
if isinstance (arg , inspect .Parameter ):
501
475
return None
502
476
if isinstance (arg , nodes .Argument ):
503
477
return str (arg .variable .type or arg .type_annotation )
504
- raise Failure
478
+ raise AssertionError
505
479
506
480
def has_default (arg : Any ) -> bool :
507
481
if isinstance (arg , inspect .Parameter ):
508
482
return arg .default != inspect .Parameter .empty
509
483
if isinstance (arg , nodes .Argument ):
510
484
return arg .kind .is_optional ()
511
- raise Failure
485
+ raise AssertionError
512
486
513
487
def get_desc (arg : Any ) -> str :
514
488
arg_type = get_type (arg )
@@ -543,7 +517,7 @@ def from_funcitem(stub: nodes.FuncItem) -> "Signature[nodes.Argument]":
543
517
elif stub_arg .kind == nodes .ARG_STAR2 :
544
518
stub_sig .varkw = stub_arg
545
519
else :
546
- raise Failure
520
+ raise AssertionError
547
521
return stub_sig
548
522
549
523
@staticmethod
@@ -562,7 +536,7 @@ def from_inspect_signature(signature: inspect.Signature) -> "Signature[inspect.P
562
536
elif runtime_arg .kind == inspect .Parameter .VAR_KEYWORD :
563
537
runtime_sig .varkw = runtime_arg
564
538
else :
565
- raise Failure
539
+ raise AssertionError
566
540
return runtime_sig
567
541
568
542
@staticmethod
@@ -641,7 +615,7 @@ def get_kind(arg_name: str) -> nodes.ArgKind:
641
615
elif arg .kind == nodes .ARG_STAR2 :
642
616
sig .varkw = arg
643
617
else :
644
- raise Failure
618
+ raise AssertionError
645
619
return sig
646
620
647
621
@@ -952,8 +926,10 @@ def apply_decorator_to_funcitem(
952
926
return func
953
927
if decorator .fullname == "builtins.classmethod" :
954
928
if func .arguments [0 ].variable .name not in ("cls" , "mcs" , "metacls" ):
955
- log_error (f"Error: bad class argument name: { func .arguments [0 ].variable .name } " )
956
- raise Failure
929
+ raise StubtestFailure (
930
+ f"unexpected class argument name { func .arguments [0 ].variable .name !r} "
931
+ f"in { dec .fullname } "
932
+ )
957
933
# FuncItem is written so that copy.copy() actually works, even when compiled
958
934
ret = copy .copy (func )
959
935
# Remove the cls argument, since it's not present in inspect.signature of classmethods
@@ -1191,7 +1167,7 @@ def anytype() -> mypy.types.AnyType:
1191
1167
elif arg .kind == inspect .Parameter .VAR_KEYWORD :
1192
1168
arg_kinds .append (nodes .ARG_STAR2 )
1193
1169
else :
1194
- raise Failure
1170
+ raise AssertionError
1195
1171
else :
1196
1172
arg_types = [anytype (), anytype ()]
1197
1173
arg_kinds = [nodes .ARG_STAR , nodes .ARG_STAR2 ]
@@ -1286,20 +1262,9 @@ def build_stubs(modules: List[str], options: Options, find_submodules: bool = Fa
1286
1262
try :
1287
1263
res = mypy .build .build (sources = sources , options = options )
1288
1264
except mypy .errors .CompileError as e :
1289
- output = [
1290
- _style ("error: " , color = "red" , bold = True ),
1291
- "not checking stubs due to failed mypy compile:\n " ,
1292
- str (e ),
1293
- ]
1294
- print ("" .join (output ))
1295
- raise Failure from e
1265
+ raise StubtestFailure (f"failed mypy compile:\n { e } " ) from e
1296
1266
if res .errors :
1297
- output = [
1298
- _style ("error: " , color = "red" , bold = True ),
1299
- "not checking stubs due to mypy build errors:\n " ,
1300
- ]
1301
- print ("" .join (output ) + "\n " .join (res .errors ))
1302
- raise Failure
1267
+ raise StubtestFailure ("mypy build errors:\n " + "\n " .join (res .errors ))
1303
1268
1304
1269
global _all_stubs
1305
1270
_all_stubs = res .files
@@ -1309,10 +1274,7 @@ def build_stubs(modules: List[str], options: Options, find_submodules: bool = Fa
1309
1274
1310
1275
def get_stub (module : str ) -> Optional [nodes .MypyFile ]:
1311
1276
"""Returns a stub object for the given module, if we've built one."""
1312
- result = _all_stubs .get (module )
1313
- if result and result .is_stub :
1314
- return result
1315
- return None
1277
+ return _all_stubs .get (module )
1316
1278
1317
1279
1318
1280
def get_typeshed_stdlib_modules (
@@ -1367,7 +1329,7 @@ def strip_comments(s: str) -> str:
1367
1329
yield entry
1368
1330
1369
1331
1370
- class Arguments :
1332
+ class _Arguments :
1371
1333
modules : List [str ]
1372
1334
concise : bool
1373
1335
ignore_missing_stub : bool
@@ -1379,10 +1341,9 @@ class Arguments:
1379
1341
custom_typeshed_dir : str
1380
1342
check_typeshed : bool
1381
1343
version : str
1382
- error_summary : bool
1383
1344
1384
1345
1385
- def test_stubs (args : Arguments , use_builtins_fixtures : bool = False ) -> int :
1346
+ def test_stubs (args : _Arguments , use_builtins_fixtures : bool = False ) -> int :
1386
1347
"""This is stubtest! It's time to test the stubs!"""
1387
1348
# Load the allowlist. This is a series of strings corresponding to Error.object_desc
1388
1349
# Values in the dict will store whether we used the allowlist entry or not.
@@ -1399,14 +1360,22 @@ def test_stubs(args: Arguments, use_builtins_fixtures: bool = False) -> int:
1399
1360
modules = args .modules
1400
1361
if args .check_typeshed :
1401
1362
if args .modules :
1402
- return log_error ("Cannot pass both --check-typeshed and a list of modules" )
1363
+ print (
1364
+ _style ("error:" , color = "red" , bold = True ),
1365
+ "cannot pass both --check-typeshed and a list of modules" ,
1366
+ )
1367
+ return 1
1403
1368
modules = get_typeshed_stdlib_modules (args .custom_typeshed_dir )
1404
1369
# typeshed added a stub for __main__, but that causes stubtest to check itself
1405
1370
annoying_modules = {"antigravity" , "this" , "__main__" }
1406
1371
modules = [m for m in modules if m not in annoying_modules ]
1407
1372
1408
1373
if not modules :
1409
- return log_error ("No modules to check" )
1374
+ print (
1375
+ _style ("error:" , color = "red" , bold = True ),
1376
+ "no modules to check" ,
1377
+ )
1378
+ return 1
1410
1379
1411
1380
options = Options ()
1412
1381
options .incremental = False
@@ -1421,14 +1390,17 @@ def set_strict_flags() -> None: # not needed yet
1421
1390
1422
1391
try :
1423
1392
modules = build_stubs (modules , options , find_submodules = not args .check_typeshed )
1424
- except Failure :
1393
+ except StubtestFailure as stubtest_failure :
1394
+ print (
1395
+ _style ("error:" , color = "red" , bold = True ),
1396
+ f"not checking stubs due to { stubtest_failure } " ,
1397
+ )
1425
1398
return 1
1426
1399
1427
1400
exit_code = 0
1428
1401
error_count = 0
1429
- error_modules : Set [str ] = set ()
1430
1402
for module in modules :
1431
- for error in test_module (module , args . concise ):
1403
+ for error in test_module (module ):
1432
1404
# Filter errors
1433
1405
if args .ignore_missing_stub and error .is_missing_stub ():
1434
1406
continue
@@ -1453,7 +1425,6 @@ def set_strict_flags() -> None: # not needed yet
1453
1425
continue
1454
1426
print (error .get_description (concise = args .concise ))
1455
1427
error_count += 1
1456
- error_modules .add (module )
1457
1428
1458
1429
# Print unused allowlist entries
1459
1430
if not args .ignore_unused_allowlist :
@@ -1462,33 +1433,35 @@ def set_strict_flags() -> None: # not needed yet
1462
1433
# This lets us allowlist errors that don't manifest at all on some systems
1463
1434
if not allowlist [w ] and not allowlist_regexes [w ].fullmatch ("" ):
1464
1435
exit_code = 1
1436
+ error_count += 1
1465
1437
print (f"note: unused allowlist entry { w } " )
1466
1438
1467
1439
# Print the generated allowlist
1468
1440
if args .generate_allowlist :
1469
1441
for e in sorted (generated_allowlist ):
1470
1442
print (e )
1471
1443
exit_code = 0
1472
-
1473
- if args .error_summary :
1474
- if not error_count :
1444
+ elif not args .concise :
1445
+ if error_count :
1475
1446
print (
1476
1447
_style (
1477
- f"Success: no issues found in { len (modules )} module{ plural_s (modules )} " ,
1478
- color = "green" , bold = True
1448
+ f"Found { error_count } error{ plural_s (error_count )} "
1449
+ f" (checked { len (modules )} module{ plural_s (modules )} )" ,
1450
+ color = "red" , bold = True
1479
1451
)
1480
1452
)
1481
1453
else :
1482
- log_error (
1483
- f"Found { error_count } error{ plural_s (error_count )} in { len (error_modules )} "
1484
- f" module{ plural_s (error_modules )} "
1485
- f" (checked { len (modules )} module{ plural_s (modules )} )"
1454
+ print (
1455
+ _style (
1456
+ f"Success: no issues found in { len (modules )} module{ plural_s (modules )} " ,
1457
+ color = "green" , bold = True
1458
+ )
1486
1459
)
1487
1460
1488
1461
return exit_code
1489
1462
1490
1463
1491
- def parse_options (args : List [str ]) -> Arguments :
1464
+ def parse_options (args : List [str ]) -> _Arguments :
1492
1465
parser = argparse .ArgumentParser (
1493
1466
description = "Compares stubs to objects introspected from the runtime."
1494
1467
)
@@ -1549,24 +1522,13 @@ def parse_options(args: List[str]) -> Arguments:
1549
1522
parser .add_argument (
1550
1523
"--version" , action = "version" , version = "%(prog)s " + mypy .version .__version__
1551
1524
)
1552
- parser .add_argument (
1553
- "--no-error-summary" , action = "store_false" , dest = "error_summary" ,
1554
- help = "Don't output an error summary"
1555
- )
1556
1525
1557
- return parser .parse_args (args , namespace = Arguments ())
1526
+ return parser .parse_args (args , namespace = _Arguments ())
1558
1527
1559
1528
1560
1529
def main () -> int :
1561
1530
mypy .util .check_python_version ("stubtest" )
1562
- try :
1563
- return test_stubs (parse_options (sys .argv [1 :]))
1564
- except KeyboardInterrupt :
1565
- return log_error ("Interrupted" )
1566
- except Failure :
1567
- return log_error ("Stubtest has failed and exited early" )
1568
- except Exception :
1569
- return log_error ("Internal error encountered" )
1531
+ return test_stubs (parse_options (sys .argv [1 :]))
1570
1532
1571
1533
1572
1534
if __name__ == "__main__" :
0 commit comments