Show More
@@ -14,6 +14,7 b' import traceback' | |||
|
14 | 14 | import types |
|
15 | 15 | import warnings |
|
16 | 16 | from contextlib import contextmanager |
|
17 | from pathlib import Path | |
|
17 | 18 | from typing import Any |
|
18 | 19 | from typing import Callable |
|
19 | 20 | from typing import Dict |
@@ -28,8 +29,6 b' from typing import Type' | |||
|
28 | 29 | from typing import TYPE_CHECKING |
|
29 | 30 | from typing import Union |
|
30 | 31 | |
|
31 | import py.path | |
|
32 | ||
|
33 | 32 | import pytest |
|
34 | 33 | from _pytest import outcomes |
|
35 | 34 | from _pytest._code.code import ExceptionInfo |
@@ -42,6 +41,7 b' from _pytest.config.argparsing import Parser' | |||
|
42 | 41 | from _pytest.fixtures import FixtureRequest |
|
43 | 42 | from _pytest.nodes import Collector |
|
44 | 43 | from _pytest.outcomes import OutcomeException |
|
44 | from _pytest.pathlib import fnmatch_ex | |
|
45 | 45 | from _pytest.pathlib import import_path |
|
46 | 46 | from _pytest.python_api import approx |
|
47 | 47 | from _pytest.warning_types import PytestWarning |
@@ -126,35 +126,38 b' def pytest_unconfigure() -> None:' | |||
|
126 | 126 | |
|
127 | 127 | |
|
128 | 128 | def pytest_collect_file( |
|
129 |
path: |
|
|
129 | file_path: Path, | |
|
130 | 130 | parent: Collector, |
|
131 | 131 | ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]: |
|
132 | 132 | config = parent.config |
|
133 |
if path. |
|
|
134 |
if config.option.ipdoctestmodules and not |
|
|
135 | mod: IPDoctestModule = IPDoctestModule.from_parent(parent, fspath=path) | |
|
133 | if file_path.suffix == ".py": | |
|
134 | if config.option.ipdoctestmodules and not any( | |
|
135 | (_is_setup_py(file_path), _is_main_py(file_path)) | |
|
136 | ): | |
|
137 | mod: IPDoctestModule = IPDoctestModule.from_parent(parent, path=file_path) | |
|
136 | 138 | return mod |
|
137 | elif _is_ipdoctest(config, path, parent): | |
|
138 |
txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, |
|
|
139 | elif _is_ipdoctest(config, file_path, parent): | |
|
140 | txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, path=file_path) | |
|
139 | 141 | return txt |
|
140 | 142 | return None |
|
141 | 143 | |
|
142 | 144 | |
|
143 |
def _is_setup_py(path: |
|
|
144 |
if path. |
|
|
145 | def _is_setup_py(path: Path) -> bool: | |
|
146 | if path.name != "setup.py": | |
|
145 | 147 | return False |
|
146 |
contents = path.read_b |
|
|
148 | contents = path.read_bytes() | |
|
147 | 149 | return b"setuptools" in contents or b"distutils" in contents |
|
148 | 150 | |
|
149 | 151 | |
|
150 |
def _is_ipdoctest(config: Config, path: |
|
|
151 |
if path. |
|
|
152 | def _is_ipdoctest(config: Config, path: Path, parent: Collector) -> bool: | |
|
153 | if path.suffix in (".txt", ".rst") and parent.session.isinitpath(path): | |
|
152 | 154 | return True |
|
153 | 155 | globs = config.getoption("ipdoctestglob") or ["test*.txt"] |
|
154 | for glob in globs: | |
|
155 | if path.check(fnmatch=glob): | |
|
156 | return True | |
|
157 | return False | |
|
156 | return any(fnmatch_ex(glob, path) for glob in globs) | |
|
157 | ||
|
158 | ||
|
159 | def _is_main_py(path: Path) -> bool: | |
|
160 | return path.name == "__main__.py" | |
|
158 | 161 | |
|
159 | 162 | |
|
160 | 163 | class ReprFailDoctest(TerminalRepr): |
@@ -273,7 +276,7 b' class IPDoctestItem(pytest.Item):' | |||
|
273 | 276 | runner: "IPDocTestRunner", |
|
274 | 277 | dtest: "doctest.DocTest", |
|
275 | 278 | ): |
|
276 |
# incompatible signature due to |
|
|
279 | # incompatible signature due to imposed limits on subclass | |
|
277 | 280 | """The public named constructor.""" |
|
278 | 281 | return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) |
|
279 | 282 | |
@@ -372,7 +375,9 b' class IPDoctestItem(pytest.Item):' | |||
|
372 | 375 | elif isinstance(excinfo.value, MultipleDoctestFailures): |
|
373 | 376 | failures = excinfo.value.failures |
|
374 | 377 | |
|
375 |
if failures is |
|
|
378 | if failures is None: | |
|
379 | return super().repr_failure(excinfo) | |
|
380 | ||
|
376 | 381 |
|
|
377 | 382 |
|
|
378 | 383 |
|
@@ -386,17 +391,14 b' class IPDoctestItem(pytest.Item):' | |||
|
386 | 391 |
|
|
387 | 392 |
|
|
388 | 393 |
|
|
389 |
|
|
|
390 | self.config.getoption("ipdoctestreport") | |
|
391 | ) | |
|
394 | report_choice = _get_report_choice(self.config.getoption("ipdoctestreport")) | |
|
392 | 395 |
|
|
393 | 396 |
|
|
394 | 397 |
|
|
395 | 398 |
|
|
396 | 399 |
|
|
397 | 400 |
|
|
398 |
|
|
|
399 | for (i, x) in enumerate(lines) | |
|
401 | "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines) | |
|
400 | 402 |
|
|
401 | 403 |
|
|
402 | 404 |
|
@@ -413,20 +415,17 b' class IPDoctestItem(pytest.Item):' | |||
|
413 | 415 |
|
|
414 | 416 |
|
|
415 | 417 |
|
|
416 |
|
|
|
418 | inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) | |
|
417 | 419 |
|
|
418 | 420 |
|
|
419 | x.strip("\n") | |
|
420 | for x in traceback.format_exception(*failure.exc_info) | |
|
421 | x.strip("\n") for x in traceback.format_exception(*failure.exc_info) | |
|
421 | 422 |
|
|
422 | 423 |
|
|
423 | 424 |
|
|
424 | else: | |
|
425 | return super().repr_failure(excinfo) | |
|
426 | 425 | |
|
427 | def reportinfo(self): | |
|
426 | def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: | |
|
428 | 427 | assert self.dtest is not None |
|
429 |
return self. |
|
|
428 | return self.path, self.dtest.lineno, "[ipdoctest] %s" % self.name | |
|
430 | 429 | |
|
431 | 430 | |
|
432 | 431 | def _get_flag_lookup() -> Dict[str, int]: |
@@ -474,9 +473,9 b' class IPDoctestTextfile(pytest.Module):' | |||
|
474 | 473 | # Inspired by doctest.testfile; ideally we would use it directly, |
|
475 | 474 | # but it doesn't support passing a custom checker. |
|
476 | 475 | encoding = self.config.getini("ipdoctest_encoding") |
|
477 |
text = self. |
|
|
478 |
filename = str(self. |
|
|
479 |
name = self. |
|
|
476 | text = self.path.read_text(encoding) | |
|
477 | filename = str(self.path) | |
|
478 | name = self.path.name | |
|
480 | 479 | globs = {"__name__": "__main__"} |
|
481 | 480 | |
|
482 | 481 | optionflags = get_optionflags(self) |
@@ -559,15 +558,20 b' class IPDoctestModule(pytest.Module):' | |||
|
559 | 558 | |
|
560 | 559 | def _find_lineno(self, obj, source_lines): |
|
561 | 560 | """Doctest code does not take into account `@property`, this |
|
562 | is a hackish way to fix it. | |
|
561 | is a hackish way to fix it. https://bugs.python.org/issue17446 | |
|
563 | 562 | |
|
564 | https://bugs.python.org/issue17446 | |
|
563 | Wrapped Doctests will need to be unwrapped so the correct | |
|
564 | line number is returned. This will be reported upstream. #8796 | |
|
565 | 565 | """ |
|
566 | 566 | if isinstance(obj, property): |
|
567 | 567 | obj = getattr(obj, "fget", obj) |
|
568 | ||
|
569 | if hasattr(obj, "__wrapped__"): | |
|
570 | # Get the main obj in case of it being wrapped | |
|
571 | obj = inspect.unwrap(obj) | |
|
572 | ||
|
568 | 573 | # Type ignored because this is a private function. |
|
569 |
return |
|
|
570 | self, | |
|
574 | return super()._find_lineno( # type:ignore[misc] | |
|
571 | 575 | obj, |
|
572 | 576 | source_lines, |
|
573 | 577 | ) |
@@ -580,20 +584,22 b' class IPDoctestModule(pytest.Module):' | |||
|
580 | 584 | with _patch_unwrap_mock_aware(): |
|
581 | 585 | |
|
582 | 586 | # Type ignored because this is a private function. |
|
583 |
|
|
|
584 |
|
|
|
587 | super()._find( # type:ignore[misc] | |
|
588 | tests, obj, name, module, source_lines, globs, seen | |
|
585 | 589 | ) |
|
586 | 590 | |
|
587 |
if self. |
|
|
591 | if self.path.name == "conftest.py": | |
|
588 | 592 | module = self.config.pluginmanager._importconftest( |
|
589 | self.fspath, self.config.getoption("importmode") | |
|
593 | self.path, | |
|
594 | self.config.getoption("importmode"), | |
|
595 | rootpath=self.config.rootpath, | |
|
590 | 596 | ) |
|
591 | 597 | else: |
|
592 | 598 | try: |
|
593 |
module = import_path(self. |
|
|
599 | module = import_path(self.path, root=self.config.rootpath) | |
|
594 | 600 | except ImportError: |
|
595 | 601 | if self.config.getvalue("ipdoctest_ignore_import_errors"): |
|
596 |
pytest.skip("unable to import module %r" % self. |
|
|
602 | pytest.skip("unable to import module %r" % self.path) | |
|
597 | 603 | else: |
|
598 | 604 | raise |
|
599 | 605 | # Uses internal doctest module parsing mechanism. |
@@ -665,7 +671,7 b' def _init_checker_class() -> Type["IPDoctestOutputChecker"]:' | |||
|
665 | 671 | ) |
|
666 | 672 | |
|
667 | 673 | def check_output(self, want: str, got: str, optionflags: int) -> bool: |
|
668 |
if |
|
|
674 | if super().check_output(want, got, optionflags): | |
|
669 | 675 | return True |
|
670 | 676 | |
|
671 | 677 | allow_unicode = optionflags & _get_allow_unicode_flag() |
@@ -689,7 +695,7 b' def _init_checker_class() -> Type["IPDoctestOutputChecker"]:' | |||
|
689 | 695 | if allow_number: |
|
690 | 696 | got = self._remove_unwanted_precision(want, got) |
|
691 | 697 | |
|
692 |
return |
|
|
698 | return super().check_output(want, got, optionflags) | |
|
693 | 699 | |
|
694 | 700 | def _remove_unwanted_precision(self, want: str, got: str) -> str: |
|
695 | 701 | wants = list(self._number_re.finditer(want)) |
@@ -702,10 +708,7 b' def _init_checker_class() -> Type["IPDoctestOutputChecker"]:' | |||
|
702 | 708 | exponent: Optional[str] = w.group("exponent1") |
|
703 | 709 | if exponent is None: |
|
704 | 710 | exponent = w.group("exponent2") |
|
705 |
if fraction is None |
|
|
706 | precision = 0 | |
|
707 | else: | |
|
708 | precision = len(fraction) | |
|
711 | precision = 0 if fraction is None else len(fraction) | |
|
709 | 712 | if exponent is not None: |
|
710 | 713 | precision -= int(exponent) |
|
711 | 714 |
if float(w.group()) == approx(float(g.group()), abs=10 |
General Comments 0
You need to be logged in to leave comments.
Login now