From 7a2aff8e1b917dad6d847342c64646934075ddd2 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Wed, 1 Dec 2021 14:37:20 -0800 Subject: [PATCH] stubtest: increase coverage (#11634) Co-authored-by: hauntsaninja <> --- mypy/stubtest.py | 37 ++++++++++++++++++++++++------------- mypy/test/teststubtest.py | 18 +++++++++++++++++- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 7ad77086e7ab..7228afaed446 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -200,23 +200,34 @@ def verify_mypyfile( yield Error(object_path, "is not a module", stub, runtime) return - # Check things in the stub that are public + # Check things in the stub to_check = set( m for m, o in stub.names.items() - # TODO: change `o.module_public` to `not o.module_hidden` - if o.module_public and (not m.startswith("_") or hasattr(runtime, m)) + if not o.module_hidden and (not m.startswith("_") or hasattr(runtime, m)) ) - runtime_public_contents = [ - m - for m in dir(runtime) - if not m.startswith("_") - # Ensure that the object's module is `runtime`, since in the absence of __all__ we don't - # have a good way to detect re-exports at runtime. - and getattr(getattr(runtime, m), "__module__", None) == runtime.__name__ - ] - # Check all things declared in module's __all__, falling back to runtime_public_contents - to_check.update(getattr(runtime, "__all__", runtime_public_contents)) + + def _belongs_to_runtime(r: types.ModuleType, attr: str) -> bool: + obj = getattr(r, attr) + obj_mod = getattr(obj, "__module__", None) + if obj_mod is not None: + return obj_mod == r.__name__ + return not isinstance(obj, types.ModuleType) + + runtime_public_contents = ( + runtime.__all__ + if hasattr(runtime, "__all__") + else [ + m + for m in dir(runtime) + if not m.startswith("_") + # Ensure that the object's module is `runtime`, since in the absence of __all__ we + # don't have a good way to detect re-exports at runtime. + and _belongs_to_runtime(runtime, m) + ] + ) + # Check all things declared in module's __all__, falling back to our best guess + to_check.update(runtime_public_contents) to_check.difference_update({"__file__", "__doc__", "__name__", "__builtins__", "__package__"}) for entry in sorted(to_check): diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 7275037a9246..e6eb8465c665 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -52,6 +52,8 @@ 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: ... @@ -648,7 +650,7 @@ def h(x: str): ... runtime="", error="h", ) - yield Case("", "__all__ = []", None) # dummy case + 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 @@ -660,6 +662,20 @@ def h(x: str): ... 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") + + @collect_cases + def test_non_public_1(self) -> Iterator[Case]: + yield Case(stub="__all__: list[str]", runtime="", error=None) # 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_special_dunders(self) -> Iterator[Case]: