moving to scripts
This commit is contained in:
156
asq-env/lib/python3.9/site-packages/trio/tests/test_exports.py
Normal file
156
asq-env/lib/python3.9/site-packages/trio/tests/test_exports.py
Normal file
@@ -0,0 +1,156 @@
|
||||
import re
|
||||
import sys
|
||||
import importlib
|
||||
import types
|
||||
import inspect
|
||||
import enum
|
||||
|
||||
import pytest
|
||||
|
||||
import trio
|
||||
import trio.testing
|
||||
|
||||
from .. import _core
|
||||
from .. import _util
|
||||
|
||||
|
||||
def test_core_is_properly_reexported():
|
||||
# Each export from _core should be re-exported by exactly one of these
|
||||
# three modules:
|
||||
sources = [trio, trio.lowlevel, trio.testing]
|
||||
for symbol in dir(_core):
|
||||
if symbol.startswith("_") or symbol == "tests":
|
||||
continue
|
||||
found = 0
|
||||
for source in sources:
|
||||
if symbol in dir(source) and getattr(source, symbol) is getattr(
|
||||
_core, symbol
|
||||
):
|
||||
found += 1
|
||||
print(symbol, found)
|
||||
assert found == 1
|
||||
|
||||
|
||||
def public_modules(module):
|
||||
yield module
|
||||
for name, class_ in module.__dict__.items():
|
||||
if name.startswith("_"): # pragma: no cover
|
||||
continue
|
||||
if not isinstance(class_, types.ModuleType):
|
||||
continue
|
||||
if not class_.__name__.startswith(module.__name__): # pragma: no cover
|
||||
continue
|
||||
if class_ is module:
|
||||
continue
|
||||
# We should rename the trio.tests module (#274), but until then we use
|
||||
# a special-case hack:
|
||||
if class_.__name__ == "trio.tests":
|
||||
continue
|
||||
yield from public_modules(class_)
|
||||
|
||||
|
||||
PUBLIC_MODULES = list(public_modules(trio))
|
||||
PUBLIC_MODULE_NAMES = [m.__name__ for m in PUBLIC_MODULES]
|
||||
|
||||
|
||||
# It doesn't make sense for downstream redistributors to run this test, since
|
||||
# they might be using a newer version of Python with additional symbols which
|
||||
# won't be reflected in trio.socket, and this shouldn't cause downstream test
|
||||
# runs to start failing.
|
||||
@pytest.mark.redistributors_should_skip
|
||||
# pylint/jedi often have trouble with alpha releases, where Python's internals
|
||||
# are in flux, grammar may not have settled down, etc.
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info.releaselevel == "alpha",
|
||||
reason="skip static introspection tools on Python dev/alpha releases",
|
||||
)
|
||||
@pytest.mark.filterwarnings(
|
||||
# https://github.com/PyCQA/astroid/issues/681
|
||||
"ignore:the imp module is deprecated.*:DeprecationWarning"
|
||||
)
|
||||
@pytest.mark.parametrize("modname", PUBLIC_MODULE_NAMES)
|
||||
@pytest.mark.parametrize("tool", ["pylint", "jedi"])
|
||||
@pytest.mark.filterwarnings(
|
||||
"ignore:"
|
||||
+ re.escape(
|
||||
"The distutils package is deprecated and slated for removal in Python 3.12. "
|
||||
"Use setuptools or check PEP 632 for potential alternatives"
|
||||
)
|
||||
+ ":DeprecationWarning",
|
||||
"ignore:"
|
||||
+ re.escape("The distutils.sysconfig module is deprecated, use sysconfig instead")
|
||||
+ ":DeprecationWarning",
|
||||
)
|
||||
def test_static_tool_sees_all_symbols(tool, modname):
|
||||
module = importlib.import_module(modname)
|
||||
|
||||
def no_underscores(symbols):
|
||||
return {symbol for symbol in symbols if not symbol.startswith("_")}
|
||||
|
||||
runtime_names = no_underscores(dir(module))
|
||||
|
||||
# We should rename the trio.tests module (#274), but until then we use a
|
||||
# special-case hack:
|
||||
if modname == "trio":
|
||||
runtime_names.remove("tests")
|
||||
|
||||
if tool == "pylint":
|
||||
from pylint.lint import PyLinter
|
||||
|
||||
linter = PyLinter()
|
||||
ast = linter.get_ast(module.__file__, modname)
|
||||
static_names = no_underscores(ast)
|
||||
elif tool == "jedi":
|
||||
import jedi
|
||||
|
||||
# Simulate typing "import trio; trio.<TAB>"
|
||||
script = jedi.Script("import {}; {}.".format(modname, modname))
|
||||
completions = script.complete()
|
||||
static_names = no_underscores(c.name for c in completions)
|
||||
else: # pragma: no cover
|
||||
assert False
|
||||
|
||||
# It's expected that the static set will contain more names than the
|
||||
# runtime set:
|
||||
# - static tools are sometimes sloppy and include deleted names
|
||||
# - some symbols are platform-specific at runtime, but always show up in
|
||||
# static analysis (e.g. in trio.socket or trio.lowlevel)
|
||||
# So we check that the runtime names are a subset of the static names.
|
||||
missing_names = runtime_names - static_names
|
||||
if missing_names: # pragma: no cover
|
||||
print("{} can't see the following names in {}:".format(tool, modname))
|
||||
print()
|
||||
for name in sorted(missing_names):
|
||||
print(" {}".format(name))
|
||||
assert False
|
||||
|
||||
|
||||
def test_classes_are_final():
|
||||
for module in PUBLIC_MODULES:
|
||||
for name, class_ in module.__dict__.items():
|
||||
if not isinstance(class_, type):
|
||||
continue
|
||||
# Deprecated classes are exported with a leading underscore
|
||||
if name.startswith("_"): # pragma: no cover
|
||||
continue
|
||||
|
||||
# Abstract classes can be subclassed, because that's the whole
|
||||
# point of ABCs
|
||||
if inspect.isabstract(class_):
|
||||
continue
|
||||
# Exceptions are allowed to be subclassed, because exception
|
||||
# subclassing isn't used to inherit behavior.
|
||||
if issubclass(class_, BaseException):
|
||||
continue
|
||||
# These are classes that are conceptually abstract, but
|
||||
# inspect.isabstract returns False for boring reasons.
|
||||
if class_ in {trio.abc.Instrument, trio.socket.SocketType}:
|
||||
continue
|
||||
# Enums have their own metaclass, so we can't use our metaclasses.
|
||||
# And I don't think there's a lot of risk from people subclassing
|
||||
# enums...
|
||||
if issubclass(class_, enum.Enum):
|
||||
continue
|
||||
# ... insert other special cases here ...
|
||||
|
||||
assert isinstance(class_, _util.Final)
|
||||
Reference in New Issue
Block a user