Skip to content

Commit 8a87d68

Browse files
author
hauntsaninja
committed
make changes directly
1 parent 4af6a67 commit 8a87d68

File tree

3 files changed

+86
-161
lines changed

3 files changed

+86
-161
lines changed

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
)
4343
from mypy.sametypes import is_same_type
4444
from mypy.typeops import separate_union_literals
45-
from mypy.util import unmangle, plural_s as plural_s
45+
from mypy.util import unmangle, plural_s
4646
from mypy.errorcodes import ErrorCode
4747
from mypy import message_registry, errorcodes as codes
4848

mypy/stubtest.py

Lines changed: 63 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
import enum
1010
import importlib
1111
import inspect
12-
import io
12+
import os
1313
import re
1414
import sys
1515
import types
1616
import warnings
1717
from contextlib import redirect_stdout, redirect_stderr
1818
from functools import singledispatch
1919
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
2121

22-
from typing_extensions import Type, Literal
22+
from typing_extensions import Type
2323

2424
import mypy.build
2525
import mypy.modulefinder
@@ -28,9 +28,8 @@
2828
import mypy.version
2929
from mypy import nodes
3030
from mypy.config_parser import parse_config_file
31-
from mypy.messages import plural_s
3231
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
3433

3534

3635
class Missing:
@@ -54,14 +53,8 @@ def _style(message: str, **kwargs: Any) -> str:
5453
return _formatter.style(message, **kwargs)
5554

5655

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
6558

6659

6760
class Error:
@@ -164,7 +157,7 @@ def get_description(self, concise: bool = False) -> str:
164157
# ====================
165158

166159

167-
def test_module(module_name: str, concise: bool = False) -> Iterator[Error]:
160+
def test_module(module_name: str) -> Iterator[Error]:
168161
"""Tests a given module's stub against introspecting it at runtime.
169162
170163
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]:
177170
yield Error([module_name], "failed to find stubs", MISSING, None, runtime_desc="N/A")
178171
return
179172

180-
argv = sys.argv
181-
sys.argv = []
182-
output = io.StringIO()
183-
outerror = io.StringIO()
184173
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)
197184
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))
211185

212186
with warnings.catch_warnings():
213187
warnings.simplefilter("ignore")
@@ -494,21 +468,21 @@ def get_name(arg: Any) -> str:
494468
return arg.name
495469
if isinstance(arg, nodes.Argument):
496470
return arg.variable.name
497-
raise Failure
471+
raise AssertionError
498472

499473
def get_type(arg: Any) -> Optional[str]:
500474
if isinstance(arg, inspect.Parameter):
501475
return None
502476
if isinstance(arg, nodes.Argument):
503477
return str(arg.variable.type or arg.type_annotation)
504-
raise Failure
478+
raise AssertionError
505479

506480
def has_default(arg: Any) -> bool:
507481
if isinstance(arg, inspect.Parameter):
508482
return arg.default != inspect.Parameter.empty
509483
if isinstance(arg, nodes.Argument):
510484
return arg.kind.is_optional()
511-
raise Failure
485+
raise AssertionError
512486

513487
def get_desc(arg: Any) -> str:
514488
arg_type = get_type(arg)
@@ -543,7 +517,7 @@ def from_funcitem(stub: nodes.FuncItem) -> "Signature[nodes.Argument]":
543517
elif stub_arg.kind == nodes.ARG_STAR2:
544518
stub_sig.varkw = stub_arg
545519
else:
546-
raise Failure
520+
raise AssertionError
547521
return stub_sig
548522

549523
@staticmethod
@@ -562,7 +536,7 @@ def from_inspect_signature(signature: inspect.Signature) -> "Signature[inspect.P
562536
elif runtime_arg.kind == inspect.Parameter.VAR_KEYWORD:
563537
runtime_sig.varkw = runtime_arg
564538
else:
565-
raise Failure
539+
raise AssertionError
566540
return runtime_sig
567541

568542
@staticmethod
@@ -641,7 +615,7 @@ def get_kind(arg_name: str) -> nodes.ArgKind:
641615
elif arg.kind == nodes.ARG_STAR2:
642616
sig.varkw = arg
643617
else:
644-
raise Failure
618+
raise AssertionError
645619
return sig
646620

647621

@@ -952,8 +926,10 @@ def apply_decorator_to_funcitem(
952926
return func
953927
if decorator.fullname == "builtins.classmethod":
954928
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+
)
957933
# FuncItem is written so that copy.copy() actually works, even when compiled
958934
ret = copy.copy(func)
959935
# Remove the cls argument, since it's not present in inspect.signature of classmethods
@@ -1191,7 +1167,7 @@ def anytype() -> mypy.types.AnyType:
11911167
elif arg.kind == inspect.Parameter.VAR_KEYWORD:
11921168
arg_kinds.append(nodes.ARG_STAR2)
11931169
else:
1194-
raise Failure
1170+
raise AssertionError
11951171
else:
11961172
arg_types = [anytype(), anytype()]
11971173
arg_kinds = [nodes.ARG_STAR, nodes.ARG_STAR2]
@@ -1286,20 +1262,9 @@ def build_stubs(modules: List[str], options: Options, find_submodules: bool = Fa
12861262
try:
12871263
res = mypy.build.build(sources=sources, options=options)
12881264
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
12961266
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))
13031268

13041269
global _all_stubs
13051270
_all_stubs = res.files
@@ -1309,10 +1274,7 @@ def build_stubs(modules: List[str], options: Options, find_submodules: bool = Fa
13091274

13101275
def get_stub(module: str) -> Optional[nodes.MypyFile]:
13111276
"""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)
13161278

13171279

13181280
def get_typeshed_stdlib_modules(
@@ -1367,7 +1329,7 @@ def strip_comments(s: str) -> str:
13671329
yield entry
13681330

13691331

1370-
class Arguments:
1332+
class _Arguments:
13711333
modules: List[str]
13721334
concise: bool
13731335
ignore_missing_stub: bool
@@ -1379,10 +1341,9 @@ class Arguments:
13791341
custom_typeshed_dir: str
13801342
check_typeshed: bool
13811343
version: str
1382-
error_summary: bool
13831344

13841345

1385-
def test_stubs(args: Arguments, use_builtins_fixtures: bool = False) -> int:
1346+
def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
13861347
"""This is stubtest! It's time to test the stubs!"""
13871348
# Load the allowlist. This is a series of strings corresponding to Error.object_desc
13881349
# 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:
13991360
modules = args.modules
14001361
if args.check_typeshed:
14011362
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
14031368
modules = get_typeshed_stdlib_modules(args.custom_typeshed_dir)
14041369
# typeshed added a stub for __main__, but that causes stubtest to check itself
14051370
annoying_modules = {"antigravity", "this", "__main__"}
14061371
modules = [m for m in modules if m not in annoying_modules]
14071372

14081373
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
14101379

14111380
options = Options()
14121381
options.incremental = False
@@ -1421,14 +1390,17 @@ def set_strict_flags() -> None: # not needed yet
14211390

14221391
try:
14231392
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+
)
14251398
return 1
14261399

14271400
exit_code = 0
14281401
error_count = 0
1429-
error_modules: Set[str] = set()
14301402
for module in modules:
1431-
for error in test_module(module, args.concise):
1403+
for error in test_module(module):
14321404
# Filter errors
14331405
if args.ignore_missing_stub and error.is_missing_stub():
14341406
continue
@@ -1453,7 +1425,6 @@ def set_strict_flags() -> None: # not needed yet
14531425
continue
14541426
print(error.get_description(concise=args.concise))
14551427
error_count += 1
1456-
error_modules.add(module)
14571428

14581429
# Print unused allowlist entries
14591430
if not args.ignore_unused_allowlist:
@@ -1462,33 +1433,35 @@ def set_strict_flags() -> None: # not needed yet
14621433
# This lets us allowlist errors that don't manifest at all on some systems
14631434
if not allowlist[w] and not allowlist_regexes[w].fullmatch(""):
14641435
exit_code = 1
1436+
error_count += 1
14651437
print(f"note: unused allowlist entry {w}")
14661438

14671439
# Print the generated allowlist
14681440
if args.generate_allowlist:
14691441
for e in sorted(generated_allowlist):
14701442
print(e)
14711443
exit_code = 0
1472-
1473-
if args.error_summary:
1474-
if not error_count:
1444+
elif not args.concise:
1445+
if error_count:
14751446
print(
14761447
_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
14791451
)
14801452
)
14811453
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+
)
14861459
)
14871460

14881461
return exit_code
14891462

14901463

1491-
def parse_options(args: List[str]) -> Arguments:
1464+
def parse_options(args: List[str]) -> _Arguments:
14921465
parser = argparse.ArgumentParser(
14931466
description="Compares stubs to objects introspected from the runtime."
14941467
)
@@ -1549,24 +1522,13 @@ def parse_options(args: List[str]) -> Arguments:
15491522
parser.add_argument(
15501523
"--version", action="version", version="%(prog)s " + mypy.version.__version__
15511524
)
1552-
parser.add_argument(
1553-
"--no-error-summary", action="store_false", dest="error_summary",
1554-
help="Don't output an error summary"
1555-
)
15561525

1557-
return parser.parse_args(args, namespace=Arguments())
1526+
return parser.parse_args(args, namespace=_Arguments())
15581527

15591528

15601529
def main() -> int:
15611530
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:]))
15701532

15711533

15721534
if __name__ == "__main__":

0 commit comments

Comments
 (0)