| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038 |
- from __future__ import annotations
- import contextlib
- import inspect
- import io
- import os
- import re
- import sys
- import tempfile
- import textwrap
- import unittest
- from typing import Any, Callable, Iterator
- import mypy.stubtest
- from mypy.stubtest import parse_options, test_stubs
- from mypy.test.data import root_dir
- @contextlib.contextmanager
- def use_tmp_dir(mod_name: str) -> Iterator[str]:
- current = os.getcwd()
- current_syspath = sys.path.copy()
- with tempfile.TemporaryDirectory() as tmp:
- try:
- os.chdir(tmp)
- if sys.path[0] != tmp:
- sys.path.insert(0, tmp)
- yield tmp
- finally:
- sys.path = current_syspath.copy()
- if mod_name in sys.modules:
- del sys.modules[mod_name]
- os.chdir(current)
- TEST_MODULE_NAME = "test_module"
- stubtest_typing_stub = """
- Any = object()
- class _SpecialForm:
- def __getitem__(self, typeargs: Any) -> object: ...
- Callable: _SpecialForm = ...
- Generic: _SpecialForm = ...
- Protocol: _SpecialForm = ...
- Union: _SpecialForm = ...
- class TypeVar:
- def __init__(self, name, covariant: bool = ..., contravariant: bool = ...) -> None: ...
- class ParamSpec:
- def __init__(self, name: str) -> None: ...
- AnyStr = TypeVar("AnyStr", str, bytes)
- _T = TypeVar("_T")
- _T_co = TypeVar("_T_co", covariant=True)
- _K = TypeVar("_K")
- _V = TypeVar("_V")
- _S = TypeVar("_S", contravariant=True)
- _R = TypeVar("_R", covariant=True)
- class Coroutine(Generic[_T_co, _S, _R]): ...
- class Iterable(Generic[_T_co]): ...
- class Mapping(Generic[_K, _V]): ...
- class Match(Generic[AnyStr]): ...
- class Sequence(Iterable[_T_co]): ...
- class Tuple(Sequence[_T_co]): ...
- def overload(func: _T) -> _T: ...
- """
- stubtest_builtins_stub = """
- from typing import Generic, Mapping, Sequence, TypeVar, overload
- T = TypeVar('T')
- T_co = TypeVar('T_co', covariant=True)
- KT = TypeVar('KT')
- VT = TypeVar('VT')
- class object:
- __module__: str
- def __init__(self) -> None: pass
- class type: ...
- class tuple(Sequence[T_co], Generic[T_co]): ...
- class dict(Mapping[KT, VT]): ...
- class function: pass
- class ellipsis: pass
- class int: ...
- class float: ...
- class bool(int): ...
- class str: ...
- class bytes: ...
- class list(Sequence[T]): ...
- def property(f: T) -> T: ...
- def classmethod(f: T) -> T: ...
- def staticmethod(f: T) -> T: ...
- """
- def run_stubtest(
- stub: str, runtime: str, options: list[str], config_file: str | None = None
- ) -> str:
- with use_tmp_dir(TEST_MODULE_NAME) as tmp_dir:
- with open("builtins.pyi", "w") as f:
- f.write(stubtest_builtins_stub)
- with open("typing.pyi", "w") as f:
- f.write(stubtest_typing_stub)
- with open(f"{TEST_MODULE_NAME}.pyi", "w") as f:
- f.write(stub)
- with open(f"{TEST_MODULE_NAME}.py", "w") as f:
- f.write(runtime)
- if config_file:
- with open(f"{TEST_MODULE_NAME}_config.ini", "w") as f:
- f.write(config_file)
- options = options + ["--mypy-config-file", f"{TEST_MODULE_NAME}_config.ini"]
- output = io.StringIO()
- with contextlib.redirect_stdout(output):
- test_stubs(parse_options([TEST_MODULE_NAME] + options), use_builtins_fixtures=True)
- return remove_color_code(
- output.getvalue()
- # remove cwd as it's not available from outside
- .replace(os.path.realpath(tmp_dir) + os.sep, "").replace(tmp_dir + os.sep, "")
- )
- class Case:
- def __init__(self, stub: str, runtime: str, error: str | None):
- self.stub = stub
- self.runtime = runtime
- self.error = error
- def collect_cases(fn: Callable[..., Iterator[Case]]) -> Callable[..., None]:
- """run_stubtest used to be slow, so we used this decorator to combine cases.
- If you're reading this and bored, feel free to refactor this and make it more like
- other mypy tests.
- """
- def test(*args: Any, **kwargs: Any) -> None:
- cases = list(fn(*args, **kwargs))
- expected_errors = set()
- for c in cases:
- if c.error is None:
- continue
- expected_error = c.error
- if expected_error == "":
- expected_error = TEST_MODULE_NAME
- elif not expected_error.startswith(f"{TEST_MODULE_NAME}."):
- expected_error = f"{TEST_MODULE_NAME}.{expected_error}"
- assert expected_error not in expected_errors, (
- "collect_cases merges cases into a single stubtest invocation; we already "
- "expect an error for {}".format(expected_error)
- )
- expected_errors.add(expected_error)
- output = run_stubtest(
- stub="\n\n".join(textwrap.dedent(c.stub.lstrip("\n")) for c in cases),
- runtime="\n\n".join(textwrap.dedent(c.runtime.lstrip("\n")) for c in cases),
- options=["--generate-allowlist"],
- )
- actual_errors = set(output.splitlines())
- assert actual_errors == expected_errors, output
- return test
- class StubtestUnit(unittest.TestCase):
- @collect_cases
- def test_basic_good(self) -> Iterator[Case]:
- yield Case(
- stub="def f(number: int, text: str) -> None: ...",
- runtime="def f(number, text): pass",
- error=None,
- )
- yield Case(
- stub="""
- class X:
- def f(self, number: int, text: str) -> None: ...
- """,
- runtime="""
- class X:
- def f(self, number, text): pass
- """,
- error=None,
- )
- @collect_cases
- def test_types(self) -> Iterator[Case]:
- yield Case(
- stub="def mistyped_class() -> None: ...",
- runtime="class mistyped_class: pass",
- error="mistyped_class",
- )
- yield Case(
- stub="class mistyped_fn: ...", runtime="def mistyped_fn(): pass", error="mistyped_fn"
- )
- yield Case(
- stub="""
- class X:
- def mistyped_var(self) -> int: ...
- """,
- runtime="""
- class X:
- mistyped_var = 1
- """,
- error="X.mistyped_var",
- )
- @collect_cases
- def test_coroutines(self) -> Iterator[Case]:
- yield Case(stub="def bar() -> int: ...", runtime="async def bar(): return 5", error="bar")
- # Don't error for this one -- we get false positives otherwise
- yield Case(stub="async def foo() -> int: ...", runtime="def foo(): return 5", error=None)
- yield Case(stub="def baz() -> int: ...", runtime="def baz(): return 5", error=None)
- yield Case(
- stub="async def bingo() -> int: ...", runtime="async def bingo(): return 5", error=None
- )
- @collect_cases
- def test_arg_name(self) -> Iterator[Case]:
- yield Case(
- stub="def bad(number: int, text: str) -> None: ...",
- runtime="def bad(num, text) -> None: pass",
- error="bad",
- )
- yield Case(
- stub="def good_posonly(__number: int, text: str) -> None: ...",
- runtime="def good_posonly(num, /, text): pass",
- error=None,
- )
- yield Case(
- stub="def bad_posonly(__number: int, text: str) -> None: ...",
- runtime="def bad_posonly(flag, /, text): pass",
- error="bad_posonly",
- )
- yield Case(
- stub="""
- class BadMethod:
- def f(self, number: int, text: str) -> None: ...
- """,
- runtime="""
- class BadMethod:
- def f(self, n, text): pass
- """,
- error="BadMethod.f",
- )
- yield Case(
- stub="""
- class GoodDunder:
- def __exit__(self, t, v, tb) -> None: ...
- """,
- runtime="""
- class GoodDunder:
- def __exit__(self, exc_type, exc_val, exc_tb): pass
- """,
- error=None,
- )
- @collect_cases
- def test_arg_kind(self) -> Iterator[Case]:
- yield Case(
- stub="def runtime_kwonly(number: int, text: str) -> None: ...",
- runtime="def runtime_kwonly(number, *, text): pass",
- error="runtime_kwonly",
- )
- yield Case(
- stub="def stub_kwonly(number: int, *, text: str) -> None: ...",
- runtime="def stub_kwonly(number, text): pass",
- error="stub_kwonly",
- )
- yield Case(
- stub="def stub_posonly(__number: int, text: str) -> None: ...",
- runtime="def stub_posonly(number, text): pass",
- error="stub_posonly",
- )
- yield Case(
- stub="def good_posonly(__number: int, text: str) -> None: ...",
- runtime="def good_posonly(number, /, text): pass",
- error=None,
- )
- yield Case(
- stub="def runtime_posonly(number: int, text: str) -> None: ...",
- runtime="def runtime_posonly(number, /, text): pass",
- error="runtime_posonly",
- )
- yield Case(
- stub="def stub_posonly_570(number: int, /, text: str) -> None: ...",
- runtime="def stub_posonly_570(number, text): pass",
- error="stub_posonly_570",
- )
- @collect_cases
- def test_default_presence(self) -> Iterator[Case]:
- yield Case(
- stub="def f1(text: str = ...) -> None: ...",
- runtime="def f1(text = 'asdf'): pass",
- error=None,
- )
- yield Case(
- stub="def f2(text: str = ...) -> None: ...", runtime="def f2(text): pass", error="f2"
- )
- yield Case(
- stub="def f3(text: str) -> None: ...",
- runtime="def f3(text = 'asdf'): pass",
- error="f3",
- )
- yield Case(
- stub="def f4(text: str = ...) -> None: ...",
- runtime="def f4(text = None): pass",
- error="f4",
- )
- yield Case(
- stub="def f5(data: bytes = ...) -> None: ...",
- runtime="def f5(data = 'asdf'): pass",
- error="f5",
- )
- yield Case(
- stub="""
- from typing import TypeVar
- _T = TypeVar("_T", bound=str)
- def f6(text: _T = ...) -> None: ...
- """,
- runtime="def f6(text = None): pass",
- error="f6",
- )
- @collect_cases
- def test_default_value(self) -> Iterator[Case]:
- yield Case(
- stub="def f1(text: str = 'x') -> None: ...",
- runtime="def f1(text = 'y'): pass",
- error="f1",
- )
- yield Case(
- stub='def f2(text: bytes = b"x\'") -> None: ...',
- runtime='def f2(text = b"x\'"): pass',
- error=None,
- )
- yield Case(
- stub='def f3(text: bytes = b"y\'") -> None: ...',
- runtime='def f3(text = b"x\'"): pass',
- error="f3",
- )
- yield Case(
- stub="def f4(text: object = 1) -> None: ...",
- runtime="def f4(text = 1.0): pass",
- error="f4",
- )
- yield Case(
- stub="def f5(text: object = True) -> None: ...",
- runtime="def f5(text = 1): pass",
- error="f5",
- )
- yield Case(
- stub="def f6(text: object = True) -> None: ...",
- runtime="def f6(text = True): pass",
- error=None,
- )
- yield Case(
- stub="def f7(text: object = not True) -> None: ...",
- runtime="def f7(text = False): pass",
- error=None,
- )
- yield Case(
- stub="def f8(text: object = not True) -> None: ...",
- runtime="def f8(text = True): pass",
- error="f8",
- )
- yield Case(
- stub="def f9(text: object = {1: 2}) -> None: ...",
- runtime="def f9(text = {1: 3}): pass",
- error="f9",
- )
- yield Case(
- stub="def f10(text: object = [1, 2]) -> None: ...",
- runtime="def f10(text = [1, 2]): pass",
- error=None,
- )
- @collect_cases
- def test_static_class_method(self) -> Iterator[Case]:
- yield Case(
- stub="""
- class Good:
- @classmethod
- def f(cls, number: int, text: str) -> None: ...
- """,
- runtime="""
- class Good:
- @classmethod
- def f(cls, number, text): pass
- """,
- error=None,
- )
- yield Case(
- stub="""
- class Bad1:
- def f(cls, number: int, text: str) -> None: ...
- """,
- runtime="""
- class Bad1:
- @classmethod
- def f(cls, number, text): pass
- """,
- error="Bad1.f",
- )
- yield Case(
- stub="""
- class Bad2:
- @classmethod
- def f(cls, number: int, text: str) -> None: ...
- """,
- runtime="""
- class Bad2:
- @staticmethod
- def f(self, number, text): pass
- """,
- error="Bad2.f",
- )
- yield Case(
- stub="""
- class Bad3:
- @staticmethod
- def f(cls, number: int, text: str) -> None: ...
- """,
- runtime="""
- class Bad3:
- @classmethod
- def f(self, number, text): pass
- """,
- error="Bad3.f",
- )
- yield Case(
- stub="""
- class GoodNew:
- def __new__(cls, *args, **kwargs): ...
- """,
- runtime="""
- class GoodNew:
- def __new__(cls, *args, **kwargs): pass
- """,
- error=None,
- )
- @collect_cases
- def test_arg_mismatch(self) -> Iterator[Case]:
- yield Case(
- stub="def f1(a, *, b, c) -> None: ...", runtime="def f1(a, *, b, c): pass", error=None
- )
- yield Case(
- stub="def f2(a, *, b) -> None: ...", runtime="def f2(a, *, b, c): pass", error="f2"
- )
- yield Case(
- stub="def f3(a, *, b, c) -> None: ...", runtime="def f3(a, *, b): pass", error="f3"
- )
- yield Case(
- stub="def f4(a, *, b, c) -> None: ...", runtime="def f4(a, b, *, c): pass", error="f4"
- )
- yield Case(
- stub="def f5(a, b, *, c) -> None: ...", runtime="def f5(a, *, b, c): pass", error="f5"
- )
- @collect_cases
- def test_varargs_varkwargs(self) -> Iterator[Case]:
- yield Case(
- stub="def f1(*args, **kwargs) -> None: ...",
- runtime="def f1(*args, **kwargs): pass",
- error=None,
- )
- yield Case(
- stub="def f2(*args, **kwargs) -> None: ...",
- runtime="def f2(**kwargs): pass",
- error="f2",
- )
- yield Case(
- stub="def g1(a, b, c, d) -> None: ...", runtime="def g1(a, *args): pass", error=None
- )
- yield Case(
- stub="def g2(a, b, c, d, *args) -> None: ...", runtime="def g2(a): pass", error="g2"
- )
- yield Case(
- stub="def g3(a, b, c, d, *args) -> None: ...",
- runtime="def g3(a, *args): pass",
- error=None,
- )
- yield Case(
- stub="def h1(a) -> None: ...", runtime="def h1(a, b, c, d, *args): pass", error="h1"
- )
- yield Case(
- stub="def h2(a, *args) -> None: ...", runtime="def h2(a, b, c, d): pass", error="h2"
- )
- yield Case(
- stub="def h3(a, *args) -> None: ...",
- runtime="def h3(a, b, c, d, *args): pass",
- error="h3",
- )
- yield Case(
- stub="def j1(a: int, *args) -> None: ...", runtime="def j1(a): pass", error="j1"
- )
- yield Case(
- stub="def j2(a: int) -> None: ...", runtime="def j2(a, *args): pass", error="j2"
- )
- yield Case(
- stub="def j3(a, b, c) -> None: ...", runtime="def j3(a, *args, c): pass", error="j3"
- )
- yield Case(stub="def k1(a, **kwargs) -> None: ...", runtime="def k1(a): pass", error="k1")
- yield Case(
- # In theory an error, but led to worse results in practice
- stub="def k2(a) -> None: ...",
- runtime="def k2(a, **kwargs): pass",
- error=None,
- )
- yield Case(
- stub="def k3(a, b) -> None: ...", runtime="def k3(a, **kwargs): pass", error="k3"
- )
- yield Case(
- stub="def k4(a, *, b) -> None: ...", runtime="def k4(a, **kwargs): pass", error=None
- )
- yield Case(
- stub="def k5(a, *, b) -> None: ...",
- runtime="def k5(a, *, b, c, **kwargs): pass",
- error="k5",
- )
- yield Case(
- stub="def k6(a, *, b, **kwargs) -> None: ...",
- runtime="def k6(a, *, b, c, **kwargs): pass",
- error="k6",
- )
- @collect_cases
- def test_overload(self) -> Iterator[Case]:
- yield Case(
- stub="""
- from typing import overload
- @overload
- def f1(a: int, *, c: int = ...) -> int: ...
- @overload
- def f1(a: int, b: int, c: int = ...) -> str: ...
- """,
- runtime="def f1(a, b = 0, c = 0): pass",
- error=None,
- )
- yield Case(
- stub="""
- @overload
- def f2(a: int, *, c: int = ...) -> int: ...
- @overload
- def f2(a: int, b: int, c: int = ...) -> str: ...
- """,
- runtime="def f2(a, b, c = 0): pass",
- error="f2",
- )
- yield Case(
- stub="""
- @overload
- def f3(a: int) -> int: ...
- @overload
- def f3(a: int, b: str) -> str: ...
- """,
- runtime="def f3(a, b = None): pass",
- error="f3",
- )
- yield Case(
- stub="""
- @overload
- def f4(a: int, *args, b: int, **kwargs) -> int: ...
- @overload
- def f4(a: str, *args, b: int, **kwargs) -> str: ...
- """,
- runtime="def f4(a, *args, b, **kwargs): pass",
- error=None,
- )
- yield Case(
- stub="""
- @overload
- def f5(__a: int) -> int: ...
- @overload
- def f5(__b: str) -> str: ...
- """,
- runtime="def f5(x, /): pass",
- error=None,
- )
- @collect_cases
- def test_property(self) -> Iterator[Case]:
- yield Case(
- stub="""
- class Good:
- @property
- def read_only_attr(self) -> int: ...
- """,
- runtime="""
- class Good:
- @property
- def read_only_attr(self): return 1
- """,
- error=None,
- )
- yield Case(
- stub="""
- class Bad:
- @property
- def f(self) -> int: ...
- """,
- runtime="""
- class Bad:
- def f(self) -> int: return 1
- """,
- error="Bad.f",
- )
- yield Case(
- stub="""
- class GoodReadOnly:
- @property
- def f(self) -> int: ...
- """,
- runtime="""
- class GoodReadOnly:
- f = 1
- """,
- error=None,
- )
- yield Case(
- stub="""
- class BadReadOnly:
- @property
- def f(self) -> str: ...
- """,
- runtime="""
- class BadReadOnly:
- f = 1
- """,
- error="BadReadOnly.f",
- )
- yield Case(
- stub="""
- class Y:
- @property
- def read_only_attr(self) -> int: ...
- @read_only_attr.setter
- def read_only_attr(self, val: int) -> None: ...
- """,
- runtime="""
- class Y:
- @property
- def read_only_attr(self): return 5
- """,
- error="Y.read_only_attr",
- )
- yield Case(
- stub="""
- class Z:
- @property
- def read_write_attr(self) -> int: ...
- @read_write_attr.setter
- def read_write_attr(self, val: int) -> None: ...
- """,
- runtime="""
- class Z:
- @property
- def read_write_attr(self): return self._val
- @read_write_attr.setter
- def read_write_attr(self, val): self._val = val
- """,
- error=None,
- )
- yield Case(
- stub="""
- class FineAndDandy:
- @property
- def attr(self) -> int: ...
- """,
- runtime="""
- class _EvilDescriptor:
- def __get__(self, instance, ownerclass=None):
- if instance is None:
- raise AttributeError('no')
- return 42
- def __set__(self, instance, value):
- raise AttributeError('no')
- class FineAndDandy:
- attr = _EvilDescriptor()
- """,
- error=None,
- )
- @collect_cases
- def test_var(self) -> Iterator[Case]:
- yield Case(stub="x1: int", runtime="x1 = 5", error=None)
- yield Case(stub="x2: str", runtime="x2 = 5", error="x2")
- yield Case("from typing import Tuple", "", None) # dummy case
- yield Case(
- stub="""
- x3: Tuple[int, int]
- """,
- runtime="x3 = (1, 3)",
- error=None,
- )
- yield Case(
- stub="""
- x4: Tuple[int, int]
- """,
- runtime="x4 = (1, 3, 5)",
- error="x4",
- )
- yield Case(stub="x5: int", runtime="def x5(a, b): pass", error="x5")
- yield Case(
- stub="def foo(a: int, b: int) -> None: ...\nx6 = foo",
- runtime="def foo(a, b): pass\ndef x6(c, d): pass",
- error="x6",
- )
- yield Case(
- stub="""
- class X:
- f: int
- """,
- runtime="""
- class X:
- def __init__(self):
- self.f = "asdf"
- """,
- error=None,
- )
- yield Case(
- stub="""
- class Y:
- read_only_attr: int
- """,
- runtime="""
- class Y:
- @property
- def read_only_attr(self): return 5
- """,
- error="Y.read_only_attr",
- )
- yield Case(
- stub="""
- class Z:
- read_write_attr: int
- """,
- runtime="""
- class Z:
- @property
- def read_write_attr(self): return self._val
- @read_write_attr.setter
- def read_write_attr(self, val): self._val = val
- """,
- error=None,
- )
- @collect_cases
- def test_type_alias(self) -> Iterator[Case]:
- yield Case(
- stub="""
- import collections.abc
- import re
- import typing
- from typing import Callable, Dict, Generic, Iterable, List, Match, Tuple, TypeVar, Union
- """,
- runtime="""
- import collections.abc
- import re
- from typing import Callable, Dict, Generic, Iterable, List, Match, Tuple, TypeVar, Union
- """,
- error=None,
- )
- yield Case(
- stub="""
- class X:
- def f(self) -> None: ...
- Y = X
- """,
- runtime="""
- class X:
- def f(self) -> None: ...
- class Y: ...
- """,
- error="Y.f",
- )
- yield Case(stub="A = Tuple[int, str]", runtime="A = (int, str)", error="A")
- # Error if an alias isn't present at runtime...
- yield Case(stub="B = str", runtime="", error="B")
- # ... but only if the alias isn't private
- yield Case(stub="_C = int", runtime="", error=None)
- yield Case(
- stub="""
- D = tuple[str, str]
- E = Tuple[int, int, int]
- F = Tuple[str, int]
- """,
- runtime="""
- D = Tuple[str, str]
- E = Tuple[int, int, int]
- F = List[str]
- """,
- error="F",
- )
- yield Case(
- stub="""
- G = str | int
- H = Union[str, bool]
- I = str | int
- """,
- runtime="""
- G = Union[str, int]
- H = Union[str, bool]
- I = str
- """,
- error="I",
- )
- yield Case(
- stub="""
- K = dict[str, str]
- L = Dict[int, int]
- KK = collections.abc.Iterable[str]
- LL = typing.Iterable[str]
- """,
- runtime="""
- K = Dict[str, str]
- L = Dict[int, int]
- KK = Iterable[str]
- LL = Iterable[str]
- """,
- error=None,
- )
- yield Case(
- stub="""
- _T = TypeVar("_T")
- class _Spam(Generic[_T]):
- def foo(self) -> None: ...
- IntFood = _Spam[int]
- """,
- runtime="""
- _T = TypeVar("_T")
- class _Bacon(Generic[_T]):
- def foo(self, arg): pass
- IntFood = _Bacon[int]
- """,
- error="IntFood.foo",
- )
- yield Case(stub="StrList = list[str]", runtime="StrList = ['foo', 'bar']", error="StrList")
- yield Case(
- stub="""
- N = typing.Callable[[str], bool]
- O = collections.abc.Callable[[int], str]
- P = typing.Callable[[str], bool]
- """,
- runtime="""
- N = Callable[[str], bool]
- O = Callable[[int], str]
- P = int
- """,
- error="P",
- )
- yield Case(
- stub="""
- class Foo:
- class Bar: ...
- BarAlias = Foo.Bar
- """,
- runtime="""
- class Foo:
- class Bar: pass
- BarAlias = Foo.Bar
- """,
- error=None,
- )
- yield Case(
- stub="""
- from io import StringIO
- StringIOAlias = StringIO
- """,
- runtime="""
- from _io import StringIO
- StringIOAlias = StringIO
- """,
- error=None,
- )
- yield Case(stub="M = Match[str]", runtime="M = Match[str]", error=None)
- yield Case(
- stub="""
- class Baz:
- def fizz(self) -> None: ...
- BazAlias = Baz
- """,
- runtime="""
- class Baz:
- def fizz(self): pass
- BazAlias = Baz
- Baz.__name__ = Baz.__qualname__ = Baz.__module__ = "New"
- """,
- error=None,
- )
- yield Case(
- stub="""
- class FooBar:
- __module__: None # type: ignore
- def fizz(self) -> None: ...
- FooBarAlias = FooBar
- """,
- runtime="""
- class FooBar:
- def fizz(self): pass
- FooBarAlias = FooBar
- FooBar.__module__ = None
- """,
- error=None,
- )
- if sys.version_info >= (3, 10):
- yield Case(
- stub="""
- Q = Dict[str, str]
- R = dict[int, int]
- S = Tuple[int, int]
- T = tuple[str, str]
- U = int | str
- V = Union[int, str]
- W = typing.Callable[[str], bool]
- Z = collections.abc.Callable[[str], bool]
- QQ = typing.Iterable[str]
- RR = collections.abc.Iterable[str]
- MM = typing.Match[str]
- MMM = re.Match[str]
- """,
- runtime="""
- Q = dict[str, str]
- R = dict[int, int]
- S = tuple[int, int]
- T = tuple[str, str]
- U = int | str
- V = int | str
- W = collections.abc.Callable[[str], bool]
- Z = collections.abc.Callable[[str], bool]
- QQ = collections.abc.Iterable[str]
- RR = collections.abc.Iterable[str]
- MM = re.Match[str]
- MMM = re.Match[str]
- """,
- error=None,
- )
- @collect_cases
- def test_enum(self) -> Iterator[Case]:
- yield Case(
- stub="""
- import enum
- class X(enum.Enum):
- a: int
- b: str
- c: str
- """,
- runtime="""
- import enum
- class X(enum.Enum):
- a = 1
- b = "asdf"
- c = 2
- """,
- error="X.c",
- )
- @collect_cases
- def test_decorator(self) -> Iterator[Case]:
- yield Case(
- stub="""
- from typing import Any, Callable
- def decorator(f: Callable[[], int]) -> Callable[..., Any]: ...
- @decorator
- def f() -> Any: ...
- """,
- runtime="""
- def decorator(f): return f
- @decorator
- def f(): return 3
- """,
- error=None,
- )
- @collect_cases
- def test_all_at_runtime_not_stub(self) -> Iterator[Case]:
- yield Case(
- stub="Z: int",
- runtime="""
- __all__ = []
- Z = 5""",
- error=None,
- )
- @collect_cases
- def test_all_in_stub_not_at_runtime(self) -> Iterator[Case]:
- yield Case(stub="__all__ = ()", runtime="", error="__all__")
- @collect_cases
- def test_all_in_stub_different_to_all_at_runtime(self) -> Iterator[Case]:
- # We *should* emit an error with the module name itself + __all__,
- # if the stub *does* define __all__,
- # but the stub's __all__ is inconsistent with the runtime's __all__
- yield Case(
- stub="""
- __all__ = ['foo']
- foo: str
- """,
- runtime="""
- __all__ = []
- foo = 'foo'
- """,
- error="__all__",
- )
- @collect_cases
- def test_missing(self) -> Iterator[Case]:
- yield Case(stub="x = 5", runtime="", error="x")
- yield Case(stub="def f(): ...", runtime="", error="f")
- yield Case(stub="class X: ...", runtime="", error="X")
- yield Case(
- stub="""
- from typing import overload
- @overload
- def h(x: int): ...
- @overload
- def h(x: str): ...
- """,
- runtime="",
- error="h",
- )
- yield Case(stub="", runtime="__all__ = []", error=None) # dummy case
- yield Case(stub="", runtime="__all__ += ['y']\ny = 5", error="y")
- yield Case(stub="", runtime="__all__ += ['g']\ndef g(): pass", error="g")
- # Here we should only check that runtime has B, since the stub explicitly re-exports it
- yield Case(
- stub="from mystery import A, B as B, C as D # type: ignore", runtime="", error="B"
- )
- yield Case(
- stub="class Y: ...",
- runtime="__all__ += ['Y']\nclass Y:\n def __or__(self, other): return self|other",
- error="Y.__or__",
- )
- yield Case(
- stub="class Z: ...",
- runtime="__all__ += ['Z']\nclass Z:\n def __reduce__(self): return (Z,)",
- error=None,
- )
- @collect_cases
- def test_missing_no_runtime_all(self) -> Iterator[Case]:
- yield Case(stub="", runtime="import sys", error=None)
- yield Case(stub="", runtime="def g(): ...", error="g")
- yield Case(stub="", runtime="CONSTANT = 0", error="CONSTANT")
- yield Case(stub="", runtime="import re; constant = re.compile('foo')", error="constant")
- yield Case(stub="", runtime="from json.scanner import NUMBER_RE", error=None)
- yield Case(stub="", runtime="from string import ascii_letters", error=None)
- @collect_cases
- def test_non_public_1(self) -> Iterator[Case]:
- yield Case(
- stub="__all__: list[str]", runtime="", error=f"{TEST_MODULE_NAME}.__all__"
- ) # dummy case
- yield Case(stub="_f: int", runtime="def _f(): ...", error="_f")
- @collect_cases
- def test_non_public_2(self) -> Iterator[Case]:
- yield Case(stub="__all__: list[str] = ['f']", runtime="__all__ = ['f']", error=None)
- yield Case(stub="f: int", runtime="def f(): ...", error="f")
- yield Case(stub="g: int", runtime="def g(): ...", error="g")
- @collect_cases
- def test_dunders(self) -> Iterator[Case]:
- yield Case(
- stub="class A:\n def __init__(self, a: int, b: int) -> None: ...",
- runtime="class A:\n def __init__(self, a, bx): pass",
- error="A.__init__",
- )
- yield Case(
- stub="class B:\n def __call__(self, c: int, d: int) -> None: ...",
- runtime="class B:\n def __call__(self, c, dx): pass",
- error="B.__call__",
- )
- yield Case(
- stub=(
- "class C:\n"
- " def __init_subclass__(\n"
- " cls, e: int = ..., **kwargs: int\n"
- " ) -> None: ...\n"
- ),
- runtime="class C:\n def __init_subclass__(cls, e=1, **kwargs): pass",
- error=None,
- )
- if sys.version_info >= (3, 9):
- yield Case(
- stub="class D:\n def __class_getitem__(cls, type: type) -> type: ...",
- runtime="class D:\n def __class_getitem__(cls, type): ...",
- error=None,
- )
- @collect_cases
- def test_not_subclassable(self) -> Iterator[Case]:
- yield Case(
- stub="class CanBeSubclassed: ...", runtime="class CanBeSubclassed: ...", error=None
- )
- yield Case(
- stub="class CannotBeSubclassed:\n def __init_subclass__(cls) -> None: ...",
- runtime="class CannotBeSubclassed:\n def __init_subclass__(cls): raise TypeError",
- error="CannotBeSubclassed",
- )
- @collect_cases
- def test_has_runtime_final_decorator(self) -> Iterator[Case]:
- yield Case(
- stub="from typing_extensions import final",
- runtime="""
- import functools
- from typing_extensions import final
- """,
- error=None,
- )
- yield Case(
- stub="""
- @final
- class A: ...
- """,
- runtime="""
- @final
- class A: ...
- """,
- error=None,
- )
- yield Case( # Runtime can miss `@final` decorator
- stub="""
- @final
- class B: ...
- """,
- runtime="""
- class B: ...
- """,
- error=None,
- )
- yield Case( # Stub cannot miss `@final` decorator
- stub="""
- class C: ...
- """,
- runtime="""
- @final
- class C: ...
- """,
- error="C",
- )
- yield Case(
- stub="""
- class D:
- @final
- def foo(self) -> None: ...
- @final
- @staticmethod
- def bar() -> None: ...
- @staticmethod
- @final
- def bar2() -> None: ...
- @final
- @classmethod
- def baz(cls) -> None: ...
- @classmethod
- @final
- def baz2(cls) -> None: ...
- @property
- @final
- def eggs(self) -> int: ...
- @final
- @property
- def eggs2(self) -> int: ...
- @final
- def ham(self, obj: int) -> int: ...
- """,
- runtime="""
- class D:
- @final
- def foo(self): pass
- @final
- @staticmethod
- def bar(): pass
- @staticmethod
- @final
- def bar2(): pass
- @final
- @classmethod
- def baz(cls): pass
- @classmethod
- @final
- def baz2(cls): pass
- @property
- @final
- def eggs(self): return 42
- @final
- @property
- def eggs2(self): pass
- @final
- @functools.lru_cache()
- def ham(self, obj): return obj * 2
- """,
- error=None,
- )
- # Stub methods are allowed to have @final even if the runtime doesn't...
- yield Case(
- stub="""
- class E:
- @final
- def foo(self) -> None: ...
- @final
- @staticmethod
- def bar() -> None: ...
- @staticmethod
- @final
- def bar2() -> None: ...
- @final
- @classmethod
- def baz(cls) -> None: ...
- @classmethod
- @final
- def baz2(cls) -> None: ...
- @property
- @final
- def eggs(self) -> int: ...
- @final
- @property
- def eggs2(self) -> int: ...
- @final
- def ham(self, obj: int) -> int: ...
- """,
- runtime="""
- class E:
- def foo(self): pass
- @staticmethod
- def bar(): pass
- @staticmethod
- def bar2(): pass
- @classmethod
- def baz(cls): pass
- @classmethod
- def baz2(cls): pass
- @property
- def eggs(self): return 42
- @property
- def eggs2(self): return 42
- @functools.lru_cache()
- def ham(self, obj): return obj * 2
- """,
- error=None,
- )
- # ...But if the runtime has @final, the stub must have it as well
- yield Case(
- stub="""
- class F:
- def foo(self) -> None: ...
- """,
- runtime="""
- class F:
- @final
- def foo(self): pass
- """,
- error="F.foo",
- )
- yield Case(
- stub="""
- class G:
- @staticmethod
- def foo() -> None: ...
- """,
- runtime="""
- class G:
- @final
- @staticmethod
- def foo(): pass
- """,
- error="G.foo",
- )
- yield Case(
- stub="""
- class H:
- @staticmethod
- def foo() -> None: ...
- """,
- runtime="""
- class H:
- @staticmethod
- @final
- def foo(): pass
- """,
- error="H.foo",
- )
- yield Case(
- stub="""
- class I:
- @classmethod
- def foo(cls) -> None: ...
- """,
- runtime="""
- class I:
- @final
- @classmethod
- def foo(cls): pass
- """,
- error="I.foo",
- )
- yield Case(
- stub="""
- class J:
- @classmethod
- def foo(cls) -> None: ...
- """,
- runtime="""
- class J:
- @classmethod
- @final
- def foo(cls): pass
- """,
- error="J.foo",
- )
- yield Case(
- stub="""
- class K:
- @property
- def foo(self) -> int: ...
- """,
- runtime="""
- class K:
- @property
- @final
- def foo(self): return 42
- """,
- error="K.foo",
- )
- # This test wouldn't pass,
- # because the runtime can't set __final__ on instances of builtins.property,
- # so stubtest has non way of knowing that the runtime was decorated with @final:
- #
- # yield Case(
- # stub="""
- # class K2:
- # @property
- # def foo(self) -> int: ...
- # """,
- # runtime="""
- # class K2:
- # @final
- # @property
- # def foo(self): return 42
- # """,
- # error="K2.foo",
- # )
- yield Case(
- stub="""
- class L:
- def foo(self, obj: int) -> int: ...
- """,
- runtime="""
- class L:
- @final
- @functools.lru_cache()
- def foo(self, obj): return obj * 2
- """,
- error="L.foo",
- )
- @collect_cases
- def test_name_mangling(self) -> Iterator[Case]:
- yield Case(
- stub="""
- class X:
- def __mangle_good(self, text: str) -> None: ...
- def __mangle_bad(self, number: int) -> None: ...
- """,
- runtime="""
- class X:
- def __mangle_good(self, text): pass
- def __mangle_bad(self, text): pass
- """,
- error="X.__mangle_bad",
- )
- yield Case(
- stub="""
- class Klass:
- class __Mangled1:
- class __Mangled2:
- def __mangle_good(self, text: str) -> None: ...
- def __mangle_bad(self, number: int) -> None: ...
- """,
- runtime="""
- class Klass:
- class __Mangled1:
- class __Mangled2:
- def __mangle_good(self, text): pass
- def __mangle_bad(self, text): pass
- """,
- error="Klass.__Mangled1.__Mangled2.__mangle_bad",
- )
- yield Case(
- stub="""
- class __Dunder__:
- def __mangle_good(self, text: str) -> None: ...
- def __mangle_bad(self, number: int) -> None: ...
- """,
- runtime="""
- class __Dunder__:
- def __mangle_good(self, text): pass
- def __mangle_bad(self, text): pass
- """,
- error="__Dunder__.__mangle_bad",
- )
- yield Case(
- stub="""
- class _Private:
- def __mangle_good(self, text: str) -> None: ...
- def __mangle_bad(self, number: int) -> None: ...
- """,
- runtime="""
- class _Private:
- def __mangle_good(self, text): pass
- def __mangle_bad(self, text): pass
- """,
- error="_Private.__mangle_bad",
- )
- @collect_cases
- def test_mro(self) -> Iterator[Case]:
- yield Case(
- stub="""
- class A:
- def foo(self, x: int) -> None: ...
- class B(A):
- pass
- class C(A):
- pass
- """,
- runtime="""
- class A:
- def foo(self, x: int) -> None: ...
- class B(A):
- def foo(self, x: int) -> None: ...
- class C(A):
- def foo(self, y: int) -> None: ...
- """,
- error="C.foo",
- )
- yield Case(
- stub="""
- class X: ...
- """,
- runtime="""
- class X:
- def __init__(self, x): pass
- """,
- error="X.__init__",
- )
- @collect_cases
- def test_good_literal(self) -> Iterator[Case]:
- yield Case(
- stub=r"""
- from typing_extensions import Literal
- import enum
- class Color(enum.Enum):
- RED: int
- NUM: Literal[1]
- CHAR: Literal['a']
- FLAG: Literal[True]
- NON: Literal[None]
- BYT1: Literal[b'abc']
- BYT2: Literal[b'\x90']
- ENUM: Literal[Color.RED]
- """,
- runtime=r"""
- import enum
- class Color(enum.Enum):
- RED = 3
- NUM = 1
- CHAR = 'a'
- NON = None
- FLAG = True
- BYT1 = b"abc"
- BYT2 = b'\x90'
- ENUM = Color.RED
- """,
- error=None,
- )
- @collect_cases
- def test_bad_literal(self) -> Iterator[Case]:
- yield Case("from typing_extensions import Literal", "", None) # dummy case
- yield Case(
- stub="INT_FLOAT_MISMATCH: Literal[1]",
- runtime="INT_FLOAT_MISMATCH = 1.0",
- error="INT_FLOAT_MISMATCH",
- )
- yield Case(stub="WRONG_INT: Literal[1]", runtime="WRONG_INT = 2", error="WRONG_INT")
- yield Case(stub="WRONG_STR: Literal['a']", runtime="WRONG_STR = 'b'", error="WRONG_STR")
- yield Case(
- stub="BYTES_STR_MISMATCH: Literal[b'value']",
- runtime="BYTES_STR_MISMATCH = 'value'",
- error="BYTES_STR_MISMATCH",
- )
- yield Case(
- stub="STR_BYTES_MISMATCH: Literal['value']",
- runtime="STR_BYTES_MISMATCH = b'value'",
- error="STR_BYTES_MISMATCH",
- )
- yield Case(
- stub="WRONG_BYTES: Literal[b'abc']",
- runtime="WRONG_BYTES = b'xyz'",
- error="WRONG_BYTES",
- )
- yield Case(
- stub="WRONG_BOOL_1: Literal[True]",
- runtime="WRONG_BOOL_1 = False",
- error="WRONG_BOOL_1",
- )
- yield Case(
- stub="WRONG_BOOL_2: Literal[False]",
- runtime="WRONG_BOOL_2 = True",
- error="WRONG_BOOL_2",
- )
- @collect_cases
- def test_special_subtype(self) -> Iterator[Case]:
- yield Case(
- stub="""
- b1: bool
- b2: bool
- b3: bool
- """,
- runtime="""
- b1 = 0
- b2 = 1
- b3 = 2
- """,
- error="b3",
- )
- yield Case(
- stub="""
- from typing_extensions import TypedDict
- class _Options(TypedDict):
- a: str
- b: int
- opt1: _Options
- opt2: _Options
- opt3: _Options
- """,
- runtime="""
- opt1 = {"a": "3.", "b": 14}
- opt2 = {"some": "stuff"} # false negative
- opt3 = 0
- """,
- error="opt3",
- )
- @collect_cases
- def test_runtime_typing_objects(self) -> Iterator[Case]:
- yield Case(
- stub="from typing_extensions import Protocol, TypedDict",
- runtime="from typing_extensions import Protocol, TypedDict",
- error=None,
- )
- yield Case(
- stub="""
- class X(Protocol):
- bar: int
- def foo(self, x: int, y: bytes = ...) -> str: ...
- """,
- runtime="""
- class X(Protocol):
- bar: int
- def foo(self, x: int, y: bytes = ...) -> str: ...
- """,
- error=None,
- )
- yield Case(
- stub="""
- class Y(TypedDict):
- a: int
- """,
- runtime="""
- class Y(TypedDict):
- a: int
- """,
- error=None,
- )
- @collect_cases
- def test_type_var(self) -> Iterator[Case]:
- yield Case(
- stub="from typing import TypeVar", runtime="from typing import TypeVar", error=None
- )
- yield Case(stub="A = TypeVar('A')", runtime="A = TypeVar('A')", error=None)
- yield Case(stub="B = TypeVar('B')", runtime="B = 5", error="B")
- if sys.version_info >= (3, 10):
- yield Case(
- stub="from typing import ParamSpec",
- runtime="from typing import ParamSpec",
- error=None,
- )
- yield Case(stub="C = ParamSpec('C')", runtime="C = ParamSpec('C')", error=None)
- @collect_cases
- def test_metaclass_match(self) -> Iterator[Case]:
- yield Case(stub="class Meta(type): ...", runtime="class Meta(type): ...", error=None)
- yield Case(stub="class A0: ...", runtime="class A0: ...", error=None)
- yield Case(
- stub="class A1(metaclass=Meta): ...",
- runtime="class A1(metaclass=Meta): ...",
- error=None,
- )
- yield Case(stub="class A2: ...", runtime="class A2(metaclass=Meta): ...", error="A2")
- yield Case(stub="class A3(metaclass=Meta): ...", runtime="class A3: ...", error="A3")
- # Explicit `type` metaclass can always be added in any part:
- yield Case(
- stub="class T1(metaclass=type): ...",
- runtime="class T1(metaclass=type): ...",
- error=None,
- )
- yield Case(stub="class T2: ...", runtime="class T2(metaclass=type): ...", error=None)
- yield Case(stub="class T3(metaclass=type): ...", runtime="class T3: ...", error=None)
- # Explicit check that `_protected` names are also supported:
- yield Case(stub="class _P1(type): ...", runtime="class _P1(type): ...", error=None)
- yield Case(stub="class P2: ...", runtime="class P2(metaclass=_P1): ...", error="P2")
- # With inheritance:
- yield Case(
- stub="""
- class I1(metaclass=Meta): ...
- class S1(I1): ...
- """,
- runtime="""
- class I1(metaclass=Meta): ...
- class S1(I1): ...
- """,
- error=None,
- )
- yield Case(
- stub="""
- class I2(metaclass=Meta): ...
- class S2: ... # missing inheritance
- """,
- runtime="""
- class I2(metaclass=Meta): ...
- class S2(I2): ...
- """,
- error="S2",
- )
- @collect_cases
- def test_metaclass_abcmeta(self) -> Iterator[Case]:
- # Handling abstract metaclasses is special:
- yield Case(stub="from abc import ABCMeta", runtime="from abc import ABCMeta", error=None)
- yield Case(
- stub="class A1(metaclass=ABCMeta): ...",
- runtime="class A1(metaclass=ABCMeta): ...",
- error=None,
- )
- # Stubs cannot miss abstract metaclass:
- yield Case(stub="class A2: ...", runtime="class A2(metaclass=ABCMeta): ...", error="A2")
- # But, stubs can add extra abstract metaclass, this might be a typing hack:
- yield Case(stub="class A3(metaclass=ABCMeta): ...", runtime="class A3: ...", error=None)
- @collect_cases
- def test_abstract_methods(self) -> Iterator[Case]:
- yield Case(
- stub="""
- from abc import abstractmethod
- from typing import overload
- """,
- runtime="from abc import abstractmethod",
- error=None,
- )
- yield Case(
- stub="""
- class A1:
- def some(self) -> None: ...
- """,
- runtime="""
- class A1:
- @abstractmethod
- def some(self) -> None: ...
- """,
- error="A1.some",
- )
- yield Case(
- stub="""
- class A2:
- @abstractmethod
- def some(self) -> None: ...
- """,
- runtime="""
- class A2:
- @abstractmethod
- def some(self) -> None: ...
- """,
- error=None,
- )
- yield Case(
- stub="""
- class A3:
- @overload
- def some(self, other: int) -> str: ...
- @overload
- def some(self, other: str) -> int: ...
- """,
- runtime="""
- class A3:
- @abstractmethod
- def some(self, other) -> None: ...
- """,
- error="A3.some",
- )
- yield Case(
- stub="""
- class A4:
- @overload
- @abstractmethod
- def some(self, other: int) -> str: ...
- @overload
- @abstractmethod
- def some(self, other: str) -> int: ...
- """,
- runtime="""
- class A4:
- @abstractmethod
- def some(self, other) -> None: ...
- """,
- error=None,
- )
- yield Case(
- stub="""
- class A5:
- @abstractmethod
- @overload
- def some(self, other: int) -> str: ...
- @abstractmethod
- @overload
- def some(self, other: str) -> int: ...
- """,
- runtime="""
- class A5:
- @abstractmethod
- def some(self, other) -> None: ...
- """,
- error=None,
- )
- # Runtime can miss `@abstractmethod`:
- yield Case(
- stub="""
- class A6:
- @abstractmethod
- def some(self) -> None: ...
- """,
- runtime="""
- class A6:
- def some(self) -> None: ...
- """,
- error=None,
- )
- @collect_cases
- def test_abstract_properties(self) -> Iterator[Case]:
- # TODO: test abstract properties with setters
- yield Case(
- stub="from abc import abstractmethod",
- runtime="from abc import abstractmethod",
- error=None,
- )
- # Ensure that `@property` also can be abstract:
- yield Case(
- stub="""
- class AP1:
- @property
- def some(self) -> int: ...
- """,
- runtime="""
- class AP1:
- @property
- @abstractmethod
- def some(self) -> int: ...
- """,
- error="AP1.some",
- )
- yield Case(
- stub="""
- class AP1_2:
- def some(self) -> int: ... # missing `@property` decorator
- """,
- runtime="""
- class AP1_2:
- @property
- @abstractmethod
- def some(self) -> int: ...
- """,
- error="AP1_2.some",
- )
- yield Case(
- stub="""
- class AP2:
- @property
- @abstractmethod
- def some(self) -> int: ...
- """,
- runtime="""
- class AP2:
- @property
- @abstractmethod
- def some(self) -> int: ...
- """,
- error=None,
- )
- # Runtime can miss `@abstractmethod`:
- yield Case(
- stub="""
- class AP3:
- @property
- @abstractmethod
- def some(self) -> int: ...
- """,
- runtime="""
- class AP3:
- @property
- def some(self) -> int: ...
- """,
- error=None,
- )
- def remove_color_code(s: str) -> str:
- return re.sub("\\x1b.*?m", "", s) # this works!
- class StubtestMiscUnit(unittest.TestCase):
- def test_output(self) -> None:
- output = run_stubtest(
- stub="def bad(number: int, text: str) -> None: ...",
- runtime="def bad(num, text): pass",
- options=[],
- )
- expected = (
- f'error: {TEST_MODULE_NAME}.bad is inconsistent, stub argument "number" differs '
- 'from runtime argument "num"\n'
- f"Stub: in file {TEST_MODULE_NAME}.pyi:1\n"
- "def (number: builtins.int, text: builtins.str)\n"
- f"Runtime: in file {TEST_MODULE_NAME}.py:1\ndef (num, text)\n\n"
- "Found 1 error (checked 1 module)\n"
- )
- assert output == expected
- output = run_stubtest(
- stub="def bad(number: int, text: str) -> None: ...",
- runtime="def bad(num, text): pass",
- options=["--concise"],
- )
- expected = (
- "{}.bad is inconsistent, "
- 'stub argument "number" differs from runtime argument "num"\n'.format(TEST_MODULE_NAME)
- )
- assert output == expected
- def test_ignore_flags(self) -> None:
- output = run_stubtest(
- stub="", runtime="__all__ = ['f']\ndef f(): pass", options=["--ignore-missing-stub"]
- )
- assert output == "Success: no issues found in 1 module\n"
- output = run_stubtest(stub="", runtime="def f(): pass", options=["--ignore-missing-stub"])
- assert output == "Success: no issues found in 1 module\n"
- output = run_stubtest(
- stub="def f(__a): ...", runtime="def f(a): pass", options=["--ignore-positional-only"]
- )
- assert output == "Success: no issues found in 1 module\n"
- def test_allowlist(self) -> None:
- # Can't use this as a context because Windows
- allowlist = tempfile.NamedTemporaryFile(mode="w+", delete=False)
- try:
- with allowlist:
- allowlist.write(f"{TEST_MODULE_NAME}.bad # comment\n# comment")
- output = run_stubtest(
- stub="def bad(number: int, text: str) -> None: ...",
- runtime="def bad(asdf, text): pass",
- options=["--allowlist", allowlist.name],
- )
- assert output == "Success: no issues found in 1 module\n"
- # test unused entry detection
- output = run_stubtest(stub="", runtime="", options=["--allowlist", allowlist.name])
- assert output == (
- f"note: unused allowlist entry {TEST_MODULE_NAME}.bad\n"
- "Found 1 error (checked 1 module)\n"
- )
- output = run_stubtest(
- stub="",
- runtime="",
- options=["--allowlist", allowlist.name, "--ignore-unused-allowlist"],
- )
- assert output == "Success: no issues found in 1 module\n"
- # test regex matching
- with open(allowlist.name, mode="w+") as f:
- f.write(f"{TEST_MODULE_NAME}.b.*\n")
- f.write("(unused_missing)?\n")
- f.write("unused.*\n")
- output = run_stubtest(
- stub=textwrap.dedent(
- """
- def good() -> None: ...
- def bad(number: int) -> None: ...
- def also_bad(number: int) -> None: ...
- """.lstrip(
- "\n"
- )
- ),
- runtime=textwrap.dedent(
- """
- def good(): pass
- def bad(asdf): pass
- def also_bad(asdf): pass
- """.lstrip(
- "\n"
- )
- ),
- options=["--allowlist", allowlist.name, "--generate-allowlist"],
- )
- assert output == (
- f"note: unused allowlist entry unused.*\n" f"{TEST_MODULE_NAME}.also_bad\n"
- )
- finally:
- os.unlink(allowlist.name)
- def test_mypy_build(self) -> None:
- output = run_stubtest(stub="+", runtime="", options=[])
- assert output == (
- "error: not checking stubs due to failed mypy compile:\n{}.pyi:1: "
- "error: invalid syntax [syntax]\n".format(TEST_MODULE_NAME)
- )
- output = run_stubtest(stub="def f(): ...\ndef f(): ...", runtime="", options=[])
- assert output == (
- "error: not checking stubs due to mypy build errors:\n{}.pyi:2: "
- 'error: Name "f" already defined on line 1 [no-redef]\n'.format(TEST_MODULE_NAME)
- )
- def test_missing_stubs(self) -> None:
- output = io.StringIO()
- with contextlib.redirect_stdout(output):
- test_stubs(parse_options(["not_a_module"]))
- assert remove_color_code(output.getvalue()) == (
- "error: not_a_module failed to find stubs\n"
- "Stub:\nMISSING\nRuntime:\nN/A\n\n"
- "Found 1 error (checked 1 module)\n"
- )
- def test_only_py(self) -> None:
- # in this case, stubtest will check the py against itself
- # this is useful to support packages with a mix of stubs and inline types
- with use_tmp_dir(TEST_MODULE_NAME):
- with open(f"{TEST_MODULE_NAME}.py", "w") as f:
- f.write("a = 1")
- output = io.StringIO()
- with contextlib.redirect_stdout(output):
- test_stubs(parse_options([TEST_MODULE_NAME]))
- output_str = remove_color_code(output.getvalue())
- assert output_str == "Success: no issues found in 1 module\n"
- def test_get_typeshed_stdlib_modules(self) -> None:
- stdlib = mypy.stubtest.get_typeshed_stdlib_modules(None, (3, 7))
- assert "builtins" in stdlib
- assert "os" in stdlib
- assert "os.path" in stdlib
- assert "asyncio" in stdlib
- assert "graphlib" not in stdlib
- assert "formatter" in stdlib
- assert "contextvars" in stdlib # 3.7+
- assert "importlib.metadata" not in stdlib
- stdlib = mypy.stubtest.get_typeshed_stdlib_modules(None, (3, 10))
- assert "graphlib" in stdlib
- assert "formatter" not in stdlib
- assert "importlib.metadata" in stdlib
- def test_signature(self) -> None:
- def f(a: int, b: int, *, c: int, d: int = 0, **kwargs: Any) -> None:
- pass
- assert (
- str(mypy.stubtest.Signature.from_inspect_signature(inspect.signature(f)))
- == "def (a, b, *, c, d = ..., **kwargs)"
- )
- def test_config_file(self) -> None:
- runtime = "temp = 5\n"
- stub = "from decimal import Decimal\ntemp: Decimal\n"
- config_file = f"[mypy]\nplugins={root_dir}/test-data/unit/plugins/decimal_to_int.py\n"
- output = run_stubtest(stub=stub, runtime=runtime, options=[])
- assert output == (
- f"error: {TEST_MODULE_NAME}.temp variable differs from runtime type Literal[5]\n"
- f"Stub: in file {TEST_MODULE_NAME}.pyi:2\n_decimal.Decimal\nRuntime:\n5\n\n"
- "Found 1 error (checked 1 module)\n"
- )
- output = run_stubtest(stub=stub, runtime=runtime, options=[], config_file=config_file)
- assert output == "Success: no issues found in 1 module\n"
- def test_no_modules(self) -> None:
- output = io.StringIO()
- with contextlib.redirect_stdout(output):
- test_stubs(parse_options([]))
- assert remove_color_code(output.getvalue()) == "error: no modules to check\n"
- def test_module_and_typeshed(self) -> None:
- output = io.StringIO()
- with contextlib.redirect_stdout(output):
- test_stubs(parse_options(["--check-typeshed", "some_module"]))
- assert remove_color_code(output.getvalue()) == (
- "error: cannot pass both --check-typeshed and a list of modules\n"
- )
|