aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/doctest.py
diff options
context:
space:
mode:
authorarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-14 00:49:36 +0300
committerarcadia-devtools <arcadia-devtools@yandex-team.ru>2022-02-14 00:49:36 +0300
commit82cfd1b7cab2d843cdf5467d9737f72597a493bd (patch)
tree1dfdcfe81a1a6b193ceacc2a828c521b657a339b /contrib/python/pytest/py3/_pytest/doctest.py
parent3df7211d3e3691f8e33b0a1fb1764fe810d59302 (diff)
downloadydb-82cfd1b7cab2d843cdf5467d9737f72597a493bd.tar.gz
intermediate changes
ref:68b1302de4b5da30b6bdf02193f7a2604d8b5cf8
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/doctest.py')
-rw-r--r--contrib/python/pytest/py3/_pytest/doctest.py200
1 files changed, 105 insertions, 95 deletions
diff --git a/contrib/python/pytest/py3/_pytest/doctest.py b/contrib/python/pytest/py3/_pytest/doctest.py
index 64e8f0e0ee..0784f431b8 100644
--- a/contrib/python/pytest/py3/_pytest/doctest.py
+++ b/contrib/python/pytest/py3/_pytest/doctest.py
@@ -1,12 +1,14 @@
"""Discover and run doctests in modules and test files."""
import bdb
import inspect
+import os
import platform
import sys
import traceback
import types
import warnings
from contextlib import contextmanager
+from pathlib import Path
from typing import Any
from typing import Callable
from typing import Dict
@@ -21,8 +23,6 @@ from typing import Type
from typing import TYPE_CHECKING
from typing import Union
-import py.path
-
import pytest
from _pytest import outcomes
from _pytest._code.code import ExceptionInfo
@@ -35,6 +35,7 @@ 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 fnmatch_ex
from _pytest.pathlib import import_path
from _pytest.python_api import approx
from _pytest.warning_types import PytestWarning
@@ -119,34 +120,38 @@ def pytest_unconfigure() -> None:
def pytest_collect_file(
- path: py.path.local, parent: Collector,
+ file_path: Path,
+ parent: Collector,
) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
config = parent.config
- if path.ext == ".py":
- if config.option.doctestmodules and not _is_setup_py(path):
- mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path)
+ if file_path.suffix == ".py":
+ if config.option.doctestmodules and not any(
+ (_is_setup_py(file_path), _is_main_py(file_path))
+ ):
+ mod: DoctestModule = DoctestModule.from_parent(parent, path=file_path)
return mod
- elif _is_doctest(config, path, parent):
- txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path)
+ elif _is_doctest(config, file_path, parent):
+ txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=file_path)
return txt
return None
-def _is_setup_py(path: py.path.local) -> bool:
- if path.basename != "setup.py":
+def _is_setup_py(path: Path) -> bool:
+ if path.name != "setup.py":
return False
- contents = path.read_binary()
+ contents = path.read_bytes()
return b"setuptools" in contents or b"distutils" in contents
-def _is_doctest(config: Config, path: py.path.local, parent) -> bool:
- if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
+def _is_doctest(config: Config, path: Path, parent: Collector) -> bool:
+ if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path):
return True
globs = config.getoption("doctestglob") or ["test*.txt"]
- for glob in globs:
- if path.check(fnmatch=glob):
- return True
- return False
+ return any(fnmatch_ex(glob, path) for glob in globs)
+
+
+def _is_main_py(path: Path) -> bool:
+ return path.name == "__main__.py"
class ReprFailDoctest(TerminalRepr):
@@ -185,13 +190,15 @@ def _init_runner_class() -> Type["doctest.DocTestRunner"]:
optionflags: int = 0,
continue_on_failure: bool = True,
) -> None:
- doctest.DebugRunner.__init__(
- self, checker=checker, verbose=verbose, optionflags=optionflags
- )
+ super().__init__(checker=checker, verbose=verbose, optionflags=optionflags)
self.continue_on_failure = continue_on_failure
def report_failure(
- self, out, test: "doctest.DocTest", example: "doctest.Example", got: str,
+ self,
+ out,
+ test: "doctest.DocTest",
+ example: "doctest.Example",
+ got: str,
) -> None:
failure = doctest.DocTestFailure(test, example, got)
if self.continue_on_failure:
@@ -262,7 +269,7 @@ class DoctestItem(pytest.Item):
runner: "doctest.DocTestRunner",
dtest: "doctest.DocTest",
):
- # incompatible signature due to to imposed limits on sublcass
+ # incompatible signature due to imposed limits on subclass
"""The public named constructor."""
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
@@ -301,13 +308,14 @@ class DoctestItem(pytest.Item):
# TODO: Type ignored -- breaks Liskov Substitution.
def repr_failure( # type: ignore[override]
- self, excinfo: ExceptionInfo[BaseException],
+ self,
+ excinfo: ExceptionInfo[BaseException],
) -> Union[str, TerminalRepr]:
import doctest
failures: Optional[
Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
- ] = (None)
+ ] = None
if isinstance(
excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
):
@@ -315,61 +323,57 @@ class DoctestItem(pytest.Item):
elif isinstance(excinfo.value, MultipleDoctestFailures):
failures = excinfo.value.failures
- if failures is not None:
- reprlocation_lines = []
- for failure in failures:
- example = failure.example
- test = failure.test
- filename = test.filename
- if test.lineno is None:
- lineno = None
- else:
- lineno = test.lineno + example.lineno + 1
- message = type(failure).__name__
- # 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")
- )
- if lineno is not None:
- assert failure.test.docstring is not None
- lines = failure.test.docstring.splitlines(False)
- # add line numbers to the left of the error message
- assert test.lineno is not None
- lines = [
- "%03d %s" % (i + test.lineno + 1, x)
- for (i, x) in enumerate(lines)
- ]
- # trim docstring error lines to 10
- lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
- else:
- lines = [
- "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
- ]
- indent = ">>>"
- for line in example.source.splitlines():
- lines.append(f"??? {indent} {line}")
- indent = "..."
- if isinstance(failure, doctest.DocTestFailure):
- lines += checker.output_difference(
- example, failure.got, report_choice
- ).split("\n")
- else:
- inner_excinfo = ExceptionInfo(failure.exc_info)
- lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
- lines += [
- x.strip("\n")
- for x in traceback.format_exception(*failure.exc_info)
- ]
- reprlocation_lines.append((reprlocation, lines))
- return ReprFailDoctest(reprlocation_lines)
- else:
+ if failures is None:
return super().repr_failure(excinfo)
- def reportinfo(self):
+ reprlocation_lines = []
+ for failure in failures:
+ example = failure.example
+ test = failure.test
+ filename = test.filename
+ if test.lineno is None:
+ lineno = None
+ else:
+ lineno = test.lineno + example.lineno + 1
+ message = type(failure).__name__
+ # 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"))
+ if lineno is not None:
+ assert failure.test.docstring is not None
+ lines = failure.test.docstring.splitlines(False)
+ # add line numbers to the left of the error message
+ assert test.lineno is not None
+ lines = [
+ "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines)
+ ]
+ # trim docstring error lines to 10
+ lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
+ else:
+ lines = [
+ "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example"
+ ]
+ indent = ">>>"
+ for line in example.source.splitlines():
+ lines.append(f"??? {indent} {line}")
+ indent = "..."
+ if isinstance(failure, doctest.DocTestFailure):
+ lines += checker.output_difference(
+ example, failure.got, report_choice
+ ).split("\n")
+ else:
+ inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
+ lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
+ lines += [
+ x.strip("\n") for x in traceback.format_exception(*failure.exc_info)
+ ]
+ reprlocation_lines.append((reprlocation, lines))
+ return ReprFailDoctest(reprlocation_lines)
+
+ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
assert self.dtest is not None
- return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
+ return self.path, self.dtest.lineno, "[doctest] %s" % self.name
def _get_flag_lookup() -> Dict[str, int]:
@@ -416,9 +420,9 @@ class DoctestTextfile(pytest.Module):
# 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)
- name = self.fspath.basename
+ text = self.path.read_text(encoding)
+ filename = str(self.path)
+ name = self.path.name
globs = {"__name__": "__main__"}
optionflags = get_optionflags(self)
@@ -500,15 +504,22 @@ class DoctestModule(pytest.Module):
def _find_lineno(self, obj, source_lines):
"""Doctest code does not take into account `@property`, this
- is a hackish way to fix it.
+ is a hackish way to fix it. https://bugs.python.org/issue17446
- https://bugs.python.org/issue17446
+ Wrapped Doctests will need to be unwrapped so the correct
+ line number is returned. This will be reported upstream. #8796
"""
if isinstance(obj, property):
obj = getattr(obj, "fget", obj)
+
+ if hasattr(obj, "__wrapped__"):
+ # Get the main obj in case of it being wrapped
+ obj = inspect.unwrap(obj)
+
# Type ignored because this is a private function.
- return doctest.DocTestFinder._find_lineno( # type: ignore
- self, obj, source_lines,
+ return super()._find_lineno( # type:ignore[misc]
+ obj,
+ source_lines,
)
def _find(
@@ -519,20 +530,22 @@ class DoctestModule(pytest.Module):
with _patch_unwrap_mock_aware():
# Type ignored because this is a private function.
- doctest.DocTestFinder._find( # type: ignore
- self, tests, obj, name, module, source_lines, globs, seen
+ super()._find( # type:ignore[misc]
+ tests, obj, name, module, source_lines, globs, seen
)
- if self.fspath.basename == "conftest.py":
+ if self.path.name == "conftest.py":
module = self.config.pluginmanager._importconftest(
- self.fspath, self.config.getoption("importmode")
+ self.path,
+ self.config.getoption("importmode"),
+ rootpath=self.config.rootpath,
)
else:
try:
- module = import_path(self.fspath)
+ module = import_path(self.path, root=self.config.rootpath)
except ImportError:
if self.config.getvalue("doctest_ignore_import_errors"):
- pytest.skip("unable to import module %r" % self.fspath)
+ pytest.skip("unable to import module %r" % self.path)
else:
raise
# Uses internal doctest module parsing mechanism.
@@ -603,7 +616,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
)
def check_output(self, want: str, got: str, optionflags: int) -> bool:
- if doctest.OutputChecker.check_output(self, want, got, optionflags):
+ if super().check_output(want, got, optionflags):
return True
allow_unicode = optionflags & _get_allow_unicode_flag()
@@ -627,7 +640,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
if allow_number:
got = self._remove_unwanted_precision(want, got)
- return doctest.OutputChecker.check_output(self, want, got, optionflags)
+ return super().check_output(want, got, optionflags)
def _remove_unwanted_precision(self, want: str, got: str) -> str:
wants = list(self._number_re.finditer(want))
@@ -640,10 +653,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
exponent: Optional[str] = w.group("exponent1")
if exponent is None:
exponent = w.group("exponent2")
- if fraction is None:
- precision = 0
- else:
- precision = len(fraction)
+ precision = 0 if fraction is None else len(fraction)
if exponent is not None:
precision -= int(exponent)
if float(w.group()) == approx(float(g.group()), abs=10 ** -precision):