diff options
author | arcadia-devtools <arcadia-devtools@yandex-team.ru> | 2022-02-09 12:00:52 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 15:58:17 +0300 |
commit | 8e1413fed79d1e8036e65228af6c93399ccf5502 (patch) | |
tree | 502c9df7b2614d20541c7a2d39d390e9a51877cc /contrib/python/pytest/py3/_pytest/doctest.py | |
parent | 6b813c17d56d1d05f92c61ddc347d0e4d358fe85 (diff) | |
download | ydb-8e1413fed79d1e8036e65228af6c93399ccf5502.tar.gz |
intermediate changes
ref:614ed510ddd3cdf86a8c5dbf19afd113397e0172
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/doctest.py')
-rw-r--r-- | contrib/python/pytest/py3/_pytest/doctest.py | 256 |
1 files changed, 147 insertions, 109 deletions
diff --git a/contrib/python/pytest/py3/_pytest/doctest.py b/contrib/python/pytest/py3/_pytest/doctest.py index e1dd9691cc..64e8f0e0ee 100644 --- a/contrib/python/pytest/py3/_pytest/doctest.py +++ b/contrib/python/pytest/py3/_pytest/doctest.py @@ -1,16 +1,24 @@ -""" discover and run doctests in modules and test files.""" +"""Discover and run doctests in modules and test files.""" import bdb import inspect import platform import sys import traceback +import types import warnings from contextlib import contextmanager +from typing import Any +from typing import Callable from typing import Dict +from typing import Generator +from typing import Iterable from typing import List from typing import Optional +from typing import Pattern from typing import Sequence from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING from typing import Union import py.path @@ -22,15 +30,17 @@ from _pytest._code.code import ReprFileLocation from _pytest._code.code import TerminalRepr from _pytest._io import TerminalWriter from _pytest.compat import safe_getattr -from _pytest.compat import TYPE_CHECKING +from _pytest.config import Config +from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest +from _pytest.nodes import Collector from _pytest.outcomes import OutcomeException +from _pytest.pathlib import import_path from _pytest.python_api import approx from _pytest.warning_types import PytestWarning if TYPE_CHECKING: import doctest - from typing import Type DOCTEST_REPORT_CHOICE_NONE = "none" DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" @@ -49,10 +59,10 @@ DOCTEST_REPORT_CHOICES = ( # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]] +CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None -def pytest_addoption(parser): +def pytest_addoption(parser: Parser) -> None: parser.addini( "doctest_optionflags", "option flags for doctests", @@ -102,19 +112,24 @@ def pytest_addoption(parser): ) -def pytest_unconfigure(): +def pytest_unconfigure() -> None: global RUNNER_CLASS RUNNER_CLASS = None -def pytest_collect_file(path: py.path.local, parent): +def pytest_collect_file( + path: py.path.local, parent: Collector, +) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: config = parent.config if path.ext == ".py": if config.option.doctestmodules and not _is_setup_py(path): - return DoctestModule.from_parent(parent, fspath=path) + mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path) + return mod elif _is_doctest(config, path, parent): - return DoctestTextfile.from_parent(parent, fspath=path) + txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path) + return txt + return None def _is_setup_py(path: py.path.local) -> bool: @@ -124,7 +139,7 @@ def _is_setup_py(path: py.path.local) -> bool: return b"setuptools" in contents or b"distutils" in contents -def _is_doctest(config, path, parent): +def _is_doctest(config: Config, path: py.path.local, parent) -> bool: if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): return True globs = config.getoption("doctestglob") or ["test*.txt"] @@ -137,7 +152,7 @@ def _is_doctest(config, path, parent): class ReprFailDoctest(TerminalRepr): def __init__( self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] - ): + ) -> None: self.reprlocation_lines = reprlocation_lines def toterminal(self, tw: TerminalWriter) -> None: @@ -148,36 +163,49 @@ class ReprFailDoctest(TerminalRepr): class MultipleDoctestFailures(Exception): - def __init__(self, failures): + def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: super().__init__() self.failures = failures -def _init_runner_class() -> "Type[doctest.DocTestRunner]": +def _init_runner_class() -> Type["doctest.DocTestRunner"]: import doctest class PytestDoctestRunner(doctest.DebugRunner): - """ - Runner to collect failures. Note that the out variable in this case is - a list instead of a stdout-like object + """Runner to collect failures. + + Note that the out variable in this case is a list instead of a + stdout-like object. """ def __init__( - self, checker=None, verbose=None, optionflags=0, continue_on_failure=True - ): + self, + checker: Optional["doctest.OutputChecker"] = None, + verbose: Optional[bool] = None, + optionflags: int = 0, + continue_on_failure: bool = True, + ) -> None: doctest.DebugRunner.__init__( self, checker=checker, verbose=verbose, optionflags=optionflags ) self.continue_on_failure = continue_on_failure - def report_failure(self, out, test, example, got): + def report_failure( + self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, + ) -> None: failure = doctest.DocTestFailure(test, example, got) if self.continue_on_failure: out.append(failure) else: raise failure - def report_unexpected_exception(self, out, test, example, exc_info): + def report_unexpected_exception( + self, + out, + test: "doctest.DocTest", + example: "doctest.Example", + exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + ) -> None: if isinstance(exc_info[1], OutcomeException): raise exc_info[1] if isinstance(exc_info[1], bdb.BdbQuit): @@ -212,24 +240,33 @@ def _get_runner( class DoctestItem(pytest.Item): - def __init__(self, name, parent, runner=None, dtest=None): + def __init__( + self, + name: str, + parent: "Union[DoctestTextfile, DoctestModule]", + runner: Optional["doctest.DocTestRunner"] = None, + dtest: Optional["doctest.DocTest"] = None, + ) -> None: super().__init__(name, parent) self.runner = runner self.dtest = dtest self.obj = None - self.fixture_request = None + self.fixture_request: Optional[FixtureRequest] = None @classmethod def from_parent( # type: ignore - cls, parent: "Union[DoctestTextfile, DoctestModule]", *, name, runner, dtest + cls, + parent: "Union[DoctestTextfile, DoctestModule]", + *, + name: str, + runner: "doctest.DocTestRunner", + dtest: "doctest.DocTest", ): # incompatible signature due to to imposed limits on sublcass - """ - the public named constructor - """ + """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) - def setup(self): + def setup(self) -> None: if self.dtest is not None: self.fixture_request = _setup_fixtures(self) globs = dict(getfixture=self.fixture_request.getfixturevalue) @@ -240,17 +277,19 @@ class DoctestItem(pytest.Item): self.dtest.globs.update(globs) def runtest(self) -> None: + assert self.dtest is not None + assert self.runner is not None _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() - failures = [] # type: List[doctest.DocTestFailure] - self.runner.run(self.dtest, out=failures) + failures: List["doctest.DocTestFailure"] = [] + # Type ignored because we change the type of `out` from what + # doctest expects. + self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] if failures: raise MultipleDoctestFailures(failures) - def _disable_output_capturing_for_darwin(self): - """ - Disable output capturing. Otherwise, stdout is lost to doctest (#985) - """ + def _disable_output_capturing_for_darwin(self) -> None: + """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" if platform.system() != "Darwin": return capman = self.config.pluginmanager.getplugin("capturemanager") @@ -260,15 +299,20 @@ class DoctestItem(pytest.Item): sys.stdout.write(out) sys.stderr.write(err) - def repr_failure(self, excinfo): + # TODO: Type ignored -- breaks Liskov Substitution. + def repr_failure( # type: ignore[override] + self, excinfo: ExceptionInfo[BaseException], + ) -> Union[str, TerminalRepr]: import doctest - failures = ( - None - ) # type: Optional[List[Union[doctest.DocTestFailure, doctest.UnexpectedException]]] - if excinfo.errisinstance((doctest.DocTestFailure, doctest.UnexpectedException)): + failures: Optional[ + Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] + ] = (None) + if isinstance( + excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) + ): failures = [excinfo.value] - elif excinfo.errisinstance(MultipleDoctestFailures): + elif isinstance(excinfo.value, MultipleDoctestFailures): failures = excinfo.value.failures if failures is not None: @@ -282,7 +326,8 @@ class DoctestItem(pytest.Item): else: lineno = test.lineno + example.lineno + 1 message = type(failure).__name__ - reprlocation = ReprFileLocation(filename, lineno, message) + # TODO: ReprFileLocation doesn't expect a None lineno. + reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] checker = _get_checker() report_choice = _get_report_choice( self.config.getoption("doctestreport") @@ -304,7 +349,7 @@ class DoctestItem(pytest.Item): ] indent = ">>>" for line in example.source.splitlines(): - lines.append("??? {} {}".format(indent, line)) + lines.append(f"??? {indent} {line}") indent = "..." if isinstance(failure, doctest.DocTestFailure): lines += checker.output_difference( @@ -322,7 +367,8 @@ class DoctestItem(pytest.Item): else: return super().repr_failure(excinfo) - def reportinfo(self) -> Tuple[py.path.local, int, str]: + def reportinfo(self): + assert self.dtest is not None return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name @@ -355,7 +401,7 @@ def _get_continue_on_failure(config): continue_on_failure = config.getvalue("doctest_continue_on_failure") if continue_on_failure: # We need to turn off this if we use pdb since we should stop at - # the first failure + # the first failure. if config.getvalue("usepdb"): continue_on_failure = False return continue_on_failure @@ -364,11 +410,11 @@ def _get_continue_on_failure(config): class DoctestTextfile(pytest.Module): obj = None - def collect(self): + def collect(self) -> Iterable[DoctestItem]: import doctest - # inspired by doctest.testfile; ideally we would use it directly, - # but it doesn't support passing a custom checker + # Inspired by doctest.testfile; ideally we would use it directly, + # but it doesn't support passing a custom checker. encoding = self.config.getini("doctest_encoding") text = self.fspath.read_text(encoding) filename = str(self.fspath) @@ -392,10 +438,9 @@ class DoctestTextfile(pytest.Module): ) -def _check_all_skipped(test): - """raises pytest.skip() if all examples in the given DocTest have the SKIP - option set. - """ +def _check_all_skipped(test: "doctest.DocTest") -> None: + """Raise pytest.skip() if all examples in the given DocTest have the SKIP + option set.""" import doctest all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) @@ -403,10 +448,9 @@ def _check_all_skipped(test): pytest.skip("all tests skipped by +SKIP option") -def _is_mocked(obj): - """ - returns if a object is possibly a mock object by checking the existence of a highly improbable attribute - """ +def _is_mocked(obj: object) -> bool: + """Return if an object is possibly a mock object by checking the + existence of a highly improbable attribute.""" return ( safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) is not None @@ -414,23 +458,24 @@ def _is_mocked(obj): @contextmanager -def _patch_unwrap_mock_aware(): - """ - contextmanager which replaces ``inspect.unwrap`` with a version - that's aware of mock objects and doesn't recurse on them - """ +def _patch_unwrap_mock_aware() -> Generator[None, None, None]: + """Context manager which replaces ``inspect.unwrap`` with a version + that's aware of mock objects and doesn't recurse into them.""" real_unwrap = inspect.unwrap - def _mock_aware_unwrap(obj, stop=None): + def _mock_aware_unwrap( + func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + ) -> Any: try: if stop is None or stop is _is_mocked: - return real_unwrap(obj, stop=_is_mocked) - return real_unwrap(obj, stop=lambda obj: _is_mocked(obj) or stop(obj)) + return real_unwrap(func, stop=_is_mocked) + _stop = stop + return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) except Exception as e: warnings.warn( "Got %r when unwrapping %r. This is usually caused " "by a violation of Python's object protocol; see e.g. " - "https://github.com/pytest-dev/pytest/issues/5080" % (e, obj), + "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), PytestWarning, ) raise @@ -443,26 +488,28 @@ def _patch_unwrap_mock_aware(): class DoctestModule(pytest.Module): - def collect(self): + def collect(self) -> Iterable[DoctestItem]: import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): - """ - a hackish doctest finder that overrides stdlib internals to fix a stdlib bug + """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. https://github.com/pytest-dev/pytest/issues/3456 https://bugs.python.org/issue25532 """ def _find_lineno(self, obj, source_lines): - """ - Doctest code does not take into account `@property`, this is a hackish way to fix it. + """Doctest code does not take into account `@property`, this + is a hackish way to fix it. https://bugs.python.org/issue17446 """ if isinstance(obj, property): obj = getattr(obj, "fget", obj) - return doctest.DocTestFinder._find_lineno(self, obj, source_lines) + # Type ignored because this is a private function. + return doctest.DocTestFinder._find_lineno( # type: ignore + self, obj, source_lines, + ) def _find( self, tests, obj, name, module, source_lines, globs, seen @@ -477,16 +524,18 @@ class DoctestModule(pytest.Module): ) if self.fspath.basename == "conftest.py": - module = self.config.pluginmanager._importconftest(self.fspath) + module = self.config.pluginmanager._importconftest( + self.fspath, self.config.getoption("importmode") + ) else: try: - module = self.fspath.pyimport() + module = import_path(self.fspath) except ImportError: if self.config.getvalue("doctest_ignore_import_errors"): pytest.skip("unable to import module %r" % self.fspath) else: raise - # uses internal doctest module parsing mechanism + # Uses internal doctest module parsing mechanism. finder = MockAwareDocTestFinder() optionflags = get_optionflags(self) runner = _get_runner( @@ -503,34 +552,30 @@ class DoctestModule(pytest.Module): ) -def _setup_fixtures(doctest_item): - """ - Used by DoctestTextfile and DoctestItem to setup fixture information. - """ +def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: + """Used by DoctestTextfile and DoctestItem to setup fixture information.""" - def func(): + def func() -> None: pass - doctest_item.funcargs = {} + doctest_item.funcargs = {} # type: ignore[attr-defined] fm = doctest_item.session._fixturemanager - doctest_item._fixtureinfo = fm.getfixtureinfo( + doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] node=doctest_item, func=func, cls=None, funcargs=False ) - fixture_request = FixtureRequest(doctest_item) + fixture_request = FixtureRequest(doctest_item, _ispytest=True) fixture_request._fillfixtures() return fixture_request -def _init_checker_class() -> "Type[doctest.OutputChecker]": +def _init_checker_class() -> Type["doctest.OutputChecker"]: import doctest import re class LiteralsOutputChecker(doctest.OutputChecker): - """ - Based on doctest_nose_plugin.py from the nltk project - (https://github.com/nltk/nltk) and on the "numtest" doctest extension - by Sebastien Boisgerault (https://github.com/boisgera/numtest). - """ + # Based on doctest_nose_plugin.py from the nltk project + # (https://github.com/nltk/nltk) and on the "numtest" doctest extension + # by Sebastien Boisgerault (https://github.com/boisgera/numtest). _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) @@ -557,7 +602,7 @@ def _init_checker_class() -> "Type[doctest.OutputChecker]": re.VERBOSE, ) - def check_output(self, want, got, optionflags): + def check_output(self, want: str, got: str, optionflags: int) -> bool: if doctest.OutputChecker.check_output(self, want, got, optionflags): return True @@ -568,7 +613,7 @@ def _init_checker_class() -> "Type[doctest.OutputChecker]": if not allow_unicode and not allow_bytes and not allow_number: return False - def remove_prefixes(regex, txt): + def remove_prefixes(regex: Pattern[str], txt: str) -> str: return re.sub(regex, r"\1\2", txt) if allow_unicode: @@ -584,15 +629,15 @@ def _init_checker_class() -> "Type[doctest.OutputChecker]": return doctest.OutputChecker.check_output(self, want, got, optionflags) - def _remove_unwanted_precision(self, want, got): + def _remove_unwanted_precision(self, want: str, got: str) -> str: wants = list(self._number_re.finditer(want)) gots = list(self._number_re.finditer(got)) if len(wants) != len(gots): return got offset = 0 for w, g in zip(wants, gots): - fraction = w.group("fraction") - exponent = w.group("exponent1") + fraction: Optional[str] = w.group("fraction") + exponent: Optional[str] = w.group("exponent1") if exponent is None: exponent = w.group("exponent2") if fraction is None: @@ -615,8 +660,7 @@ def _init_checker_class() -> "Type[doctest.OutputChecker]": def _get_checker() -> "doctest.OutputChecker": - """ - Returns a doctest.OutputChecker subclass that supports some + """Return a doctest.OutputChecker subclass that supports some additional options: * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' @@ -636,36 +680,31 @@ def _get_checker() -> "doctest.OutputChecker": def _get_allow_unicode_flag() -> int: - """ - Registers and returns the ALLOW_UNICODE flag. - """ + """Register and return the ALLOW_UNICODE flag.""" import doctest return doctest.register_optionflag("ALLOW_UNICODE") def _get_allow_bytes_flag() -> int: - """ - Registers and returns the ALLOW_BYTES flag. - """ + """Register and return the ALLOW_BYTES flag.""" import doctest return doctest.register_optionflag("ALLOW_BYTES") def _get_number_flag() -> int: - """ - Registers and returns the NUMBER flag. - """ + """Register and return the NUMBER flag.""" import doctest return doctest.register_optionflag("NUMBER") def _get_report_choice(key: str) -> int: - """ - This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid - importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests. + """Return the actual `doctest` module flag value. + + We want to do it as late as possible to avoid importing `doctest` and all + its dependencies when parsing options, as it adds overhead and breaks tests. """ import doctest @@ -679,8 +718,7 @@ def _get_report_choice(key: str) -> int: @pytest.fixture(scope="session") -def doctest_namespace(): - """ - Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. - """ +def doctest_namespace() -> Dict[str, Any]: + """Fixture that returns a :py:class:`dict` that will be injected into the + namespace of doctests.""" return dict() |