Skip to content

Commit c54a86b

Browse files
author
hauntsaninja
committed
making changes directly will be easier than code review
1 parent 0f92391 commit c54a86b

File tree

3 files changed

+111
-44
lines changed

3 files changed

+111
-44
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: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import enum
1010
import importlib
1111
import inspect
12+
import os
1213
import re
1314
import sys
1415
import types
1516
import warnings
17+
from contextlib import redirect_stdout, redirect_stderr
1618
from functools import singledispatch
1719
from pathlib import Path
1820
from typing import Any, Dict, Generic, Iterator, List, Optional, Tuple, TypeVar, Union, cast
@@ -27,7 +29,7 @@
2729
from mypy import nodes
2830
from mypy.config_parser import parse_config_file
2931
from mypy.options import Options
30-
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
3133

3234

3335
class Missing:
@@ -51,6 +53,10 @@ def _style(message: str, **kwargs: Any) -> str:
5153
return _formatter.style(message, **kwargs)
5254

5355

56+
class StubtestFailure(Exception):
57+
pass
58+
59+
5460
class Error:
5561
def __init__(
5662
self,
@@ -161,19 +167,20 @@ def test_module(module_name: str) -> Iterator[Error]:
161167
"""
162168
stub = get_stub(module_name)
163169
if stub is None:
164-
yield Error([module_name], "failed to find stubs", MISSING, None)
170+
yield Error([module_name], "failed to find stubs", MISSING, None, runtime_desc="N/A")
165171
return
166172

167173
try:
168-
with warnings.catch_warnings():
169-
warnings.simplefilter("ignore")
170-
runtime = importlib.import_module(module_name)
171-
# Also run the equivalent of `from module import *`
172-
# This could have the additional effect of loading not-yet-loaded submodules
173-
# mentioned in __all__
174-
__import__(module_name, fromlist=["*"])
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=["*"])
175182
except Exception as e:
176-
yield Error([module_name], f"failed to import: {e}", stub, MISSING)
183+
yield Error([module_name], f"failed to import, {type(e).__name__}: {e}", stub, MISSING)
177184
return
178185

179186
with warnings.catch_warnings():
@@ -918,7 +925,11 @@ def apply_decorator_to_funcitem(
918925
) or decorator.fullname in mypy.types.OVERLOAD_NAMES:
919926
return func
920927
if decorator.fullname == "builtins.classmethod":
921-
assert func.arguments[0].variable.name in ("cls", "metacls")
928+
if func.arguments[0].variable.name not in ("cls", "mcs", "metacls"):
929+
raise StubtestFailure(
930+
f"unexpected class argument name {func.arguments[0].variable.name!r} "
931+
f"in {dec.fullname}"
932+
)
922933
# FuncItem is written so that copy.copy() actually works, even when compiled
923934
ret = copy.copy(func)
924935
# Remove the cls argument, since it's not present in inspect.signature of classmethods
@@ -1251,20 +1262,9 @@ def build_stubs(modules: List[str], options: Options, find_submodules: bool = Fa
12511262
try:
12521263
res = mypy.build.build(sources=sources, options=options)
12531264
except mypy.errors.CompileError as e:
1254-
output = [
1255-
_style("error: ", color="red", bold=True),
1256-
"not checking stubs due to failed mypy compile:\n",
1257-
str(e),
1258-
]
1259-
print("".join(output))
1260-
raise RuntimeError from e
1265+
raise StubtestFailure(f"failed mypy compile:\n{e}") from e
12611266
if res.errors:
1262-
output = [
1263-
_style("error: ", color="red", bold=True),
1264-
"not checking stubs due to mypy build errors:\n",
1265-
]
1266-
print("".join(output) + "\n".join(res.errors))
1267-
raise RuntimeError
1267+
raise StubtestFailure("mypy build errors:\n" + "\n".join(res.errors))
12681268

12691269
global _all_stubs
12701270
_all_stubs = res.files
@@ -1329,7 +1329,21 @@ def strip_comments(s: str) -> str:
13291329
yield entry
13301330

13311331

1332-
def test_stubs(args: argparse.Namespace, use_builtins_fixtures: bool = False) -> int:
1332+
class _Arguments:
1333+
modules: List[str]
1334+
concise: bool
1335+
ignore_missing_stub: bool
1336+
ignore_positional_only: bool
1337+
allowlist: List[str]
1338+
generate_allowlist: bool
1339+
ignore_unused_allowlist: bool
1340+
mypy_config_file: str
1341+
custom_typeshed_dir: str
1342+
check_typeshed: bool
1343+
version: str
1344+
1345+
1346+
def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
13331347
"""This is stubtest! It's time to test the stubs!"""
13341348
# Load the allowlist. This is a series of strings corresponding to Error.object_desc
13351349
# Values in the dict will store whether we used the allowlist entry or not.
@@ -1345,13 +1359,23 @@ def test_stubs(args: argparse.Namespace, use_builtins_fixtures: bool = False) ->
13451359

13461360
modules = args.modules
13471361
if args.check_typeshed:
1348-
assert not args.modules, "Cannot pass both --check-typeshed and a list of modules"
1362+
if args.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
13491368
modules = get_typeshed_stdlib_modules(args.custom_typeshed_dir)
13501369
# typeshed added a stub for __main__, but that causes stubtest to check itself
13511370
annoying_modules = {"antigravity", "this", "__main__"}
13521371
modules = [m for m in modules if m not in annoying_modules]
13531372

1354-
assert modules, "No modules to check"
1373+
if not modules:
1374+
print(
1375+
_style("error:", color="red", bold=True),
1376+
"no modules to check",
1377+
)
1378+
return 1
13551379

13561380
options = Options()
13571381
options.incremental = False
@@ -1366,10 +1390,15 @@ def set_strict_flags() -> None: # not needed yet
13661390

13671391
try:
13681392
modules = build_stubs(modules, options, find_submodules=not args.check_typeshed)
1369-
except RuntimeError:
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+
)
13701398
return 1
13711399

13721400
exit_code = 0
1401+
error_count = 0
13731402
for module in modules:
13741403
for error in test_module(module):
13751404
# Filter errors
@@ -1395,6 +1424,7 @@ def set_strict_flags() -> None: # not needed yet
13951424
generated_allowlist.add(error.object_desc)
13961425
continue
13971426
print(error.get_description(concise=args.concise))
1427+
error_count += 1
13981428

13991429
# Print unused allowlist entries
14001430
if not args.ignore_unused_allowlist:
@@ -1403,18 +1433,35 @@ def set_strict_flags() -> None: # not needed yet
14031433
# This lets us allowlist errors that don't manifest at all on some systems
14041434
if not allowlist[w] and not allowlist_regexes[w].fullmatch(""):
14051435
exit_code = 1
1436+
error_count += 1
14061437
print(f"note: unused allowlist entry {w}")
14071438

14081439
# Print the generated allowlist
14091440
if args.generate_allowlist:
14101441
for e in sorted(generated_allowlist):
14111442
print(e)
14121443
exit_code = 0
1444+
elif not args.concise:
1445+
if error_count:
1446+
print(
1447+
_style(
1448+
f"Found {error_count} error{plural_s(error_count)}"
1449+
f" (checked {len(modules)} module{plural_s(modules)})",
1450+
color="red", bold=True
1451+
)
1452+
)
1453+
else:
1454+
print(
1455+
_style(
1456+
f"Success: no issues found in {len(modules)} module{plural_s(modules)}",
1457+
color="green", bold=True
1458+
)
1459+
)
14131460

14141461
return exit_code
14151462

14161463

1417-
def parse_options(args: List[str]) -> argparse.Namespace:
1464+
def parse_options(args: List[str]) -> _Arguments:
14181465
parser = argparse.ArgumentParser(
14191466
description="Compares stubs to objects introspected from the runtime."
14201467
)
@@ -1476,7 +1523,7 @@ def parse_options(args: List[str]) -> argparse.Namespace:
14761523
"--version", action="version", version="%(prog)s " + mypy.version.__version__
14771524
)
14781525

1479-
return parser.parse_args(args)
1526+
return parser.parse_args(args, namespace=_Arguments())
14801527

14811528

14821529
def main() -> int:

mypy/test/teststubtest.py

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ def test_missing_no_runtime_all(self) -> Iterator[Case]:
806806
@collect_cases
807807
def test_non_public_1(self) -> Iterator[Case]:
808808
yield Case(
809-
stub="__all__: list[str]", runtime="", error="test_module.__all__"
809+
stub="__all__: list[str]", runtime="", error=f"{TEST_MODULE_NAME}.__all__"
810810
) # dummy case
811811
yield Case(stub="_f: int", runtime="def _f(): ...", error="_f")
812812

@@ -1055,9 +1055,10 @@ def test_output(self) -> None:
10551055
options=[],
10561056
)
10571057
expected = (
1058-
'error: {0}.bad is inconsistent, stub argument "number" differs from runtime '
1058+
f'error: {TEST_MODULE_NAME}.bad is inconsistent, stub argument "number" differs from runtime '
10591059
'argument "num"\nStub: at line 1\ndef (number: builtins.int, text: builtins.str)\n'
1060-
"Runtime: at line 1 in file {0}.py\ndef (num, text)\n\n".format(TEST_MODULE_NAME)
1060+
f"Runtime: at line 1 in file {TEST_MODULE_NAME}.py\ndef (num, text)\n\n"
1061+
'Found 1 error (checked 1 module)\n'
10611062
)
10621063
assert remove_color_code(output) == expected
10631064

@@ -1076,17 +1077,17 @@ def test_ignore_flags(self) -> None:
10761077
output = run_stubtest(
10771078
stub="", runtime="__all__ = ['f']\ndef f(): pass", options=["--ignore-missing-stub"]
10781079
)
1079-
assert not output
1080+
assert output == 'Success: no issues found in 1 module\n'
10801081

10811082
output = run_stubtest(
10821083
stub="", runtime="def f(): pass", options=["--ignore-missing-stub"]
10831084
)
1084-
assert not output
1085+
assert output == 'Success: no issues found in 1 module\n'
10851086

10861087
output = run_stubtest(
10871088
stub="def f(__a): ...", runtime="def f(a): pass", options=["--ignore-positional-only"]
10881089
)
1089-
assert not output
1090+
assert output == 'Success: no issues found in 1 module\n'
10901091

10911092
def test_allowlist(self) -> None:
10921093
# Can't use this as a context because Windows
@@ -1100,18 +1101,21 @@ def test_allowlist(self) -> None:
11001101
runtime="def bad(asdf, text): pass",
11011102
options=["--allowlist", allowlist.name],
11021103
)
1103-
assert not output
1104+
assert output == 'Success: no issues found in 1 module\n'
11041105

11051106
# test unused entry detection
11061107
output = run_stubtest(stub="", runtime="", options=["--allowlist", allowlist.name])
1107-
assert output == f"note: unused allowlist entry {TEST_MODULE_NAME}.bad\n"
1108+
assert output == (
1109+
f"note: unused allowlist entry {TEST_MODULE_NAME}.bad\n"
1110+
"Found 1 error (checked 1 module)\n"
1111+
)
11081112

11091113
output = run_stubtest(
11101114
stub="",
11111115
runtime="",
11121116
options=["--allowlist", allowlist.name, "--ignore-unused-allowlist"],
11131117
)
1114-
assert not output
1118+
assert output == 'Success: no issues found in 1 module\n'
11151119

11161120
# test regex matching
11171121
with open(allowlist.name, mode="w+") as f:
@@ -1136,8 +1140,9 @@ def also_bad(asdf): pass
11361140
),
11371141
options=["--allowlist", allowlist.name, "--generate-allowlist"],
11381142
)
1139-
assert output == "note: unused allowlist entry unused.*\n{}.also_bad\n".format(
1140-
TEST_MODULE_NAME
1143+
assert output == (
1144+
f"note: unused allowlist entry unused.*\n"
1145+
f"{TEST_MODULE_NAME}.also_bad\n"
11411146
)
11421147
finally:
11431148
os.unlink(allowlist.name)
@@ -1193,8 +1198,23 @@ def test_config_file(self) -> None:
11931198
)
11941199
output = run_stubtest(stub=stub, runtime=runtime, options=[])
11951200
assert remove_color_code(output) == (
1196-
"error: test_module.temp variable differs from runtime type Literal[5]\n"
1201+
f"error: {TEST_MODULE_NAME}.temp variable differs from runtime type Literal[5]\n"
11971202
"Stub: at line 2\n_decimal.Decimal\nRuntime:\n5\n\n"
1203+
"Found 1 error (checked 1 module)\n"
11981204
)
11991205
output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file)
1200-
assert output == ""
1206+
assert output == "Success: no issues found in 1 module\n"
1207+
1208+
def test_no_modules(self) -> None:
1209+
output = io.StringIO()
1210+
with contextlib.redirect_stdout(output):
1211+
test_stubs(parse_options([]))
1212+
assert remove_color_code(output.getvalue()) == "error: no modules to check\n"
1213+
1214+
def test_module_and_typeshed(self) -> None:
1215+
output = io.StringIO()
1216+
with contextlib.redirect_stdout(output):
1217+
test_stubs(parse_options(["--check-typeshed", "some_module"]))
1218+
assert remove_color_code(output.getvalue()) == (
1219+
"error: cannot pass both --check-typeshed and a list of modules\n"
1220+
)

0 commit comments

Comments
 (0)