aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/doctest.py
diff options
context:
space:
mode:
authorarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-09 12:00:52 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 15:58:17 +0300
commit8e1413fed79d1e8036e65228af6c93399ccf5502 (patch)
tree502c9df7b2614d20541c7a2d39d390e9a51877cc /contrib/python/pytest/py3/_pytest/doctest.py
parent6b813c17d56d1d05f92c61ddc347d0e4d358fe85 (diff)
downloadydb-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.py256
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()