1"""Discover and run doctests in modules and test files.""" 2import bdb 3import inspect 4import platform 5import sys 6import traceback 7import types 8import warnings 9from contextlib import contextmanager 10from typing import Any 11from typing import Callable 12from typing import Dict 13from typing import Generator 14from typing import Iterable 15from typing import List 16from typing import Optional 17from typing import Pattern 18from typing import Sequence 19from typing import Tuple 20from typing import Union 21 22import py.path 23 24import pytest 25from _pytest import outcomes 26from _pytest._code.code import ExceptionInfo 27from _pytest._code.code import ReprFileLocation 28from _pytest._code.code import TerminalRepr 29from _pytest._io import TerminalWriter 30from _pytest.compat import safe_getattr 31from _pytest.compat import TYPE_CHECKING 32from _pytest.config import Config 33from _pytest.config.argparsing import Parser 34from _pytest.fixtures import FixtureRequest 35from _pytest.nodes import Collector 36from _pytest.outcomes import OutcomeException 37from _pytest.pathlib import import_path 38from _pytest.python_api import approx 39from _pytest.warning_types import PytestWarning 40 41if TYPE_CHECKING: 42 import doctest 43 from typing import Type 44 45DOCTEST_REPORT_CHOICE_NONE = "none" 46DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" 47DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" 48DOCTEST_REPORT_CHOICE_UDIFF = "udiff" 49DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure" 50 51DOCTEST_REPORT_CHOICES = ( 52 DOCTEST_REPORT_CHOICE_NONE, 53 DOCTEST_REPORT_CHOICE_CDIFF, 54 DOCTEST_REPORT_CHOICE_NDIFF, 55 DOCTEST_REPORT_CHOICE_UDIFF, 56 DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, 57) 58 59# Lazy definition of runner class 60RUNNER_CLASS = None 61# Lazy definition of output checker class 62CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]] 63 64 65def pytest_addoption(parser: Parser) -> None: 66 parser.addini( 67 "doctest_optionflags", 68 "option flags for doctests", 69 type="args", 70 default=["ELLIPSIS"], 71 ) 72 parser.addini( 73 "doctest_encoding", "encoding used for doctest files", default="utf-8" 74 ) 75 group = parser.getgroup("collect") 76 group.addoption( 77 "--doctest-modules", 78 action="store_true", 79 default=False, 80 help="run doctests in all .py modules", 81 dest="doctestmodules", 82 ) 83 group.addoption( 84 "--doctest-report", 85 type=str.lower, 86 default="udiff", 87 help="choose another output format for diffs on doctest failure", 88 choices=DOCTEST_REPORT_CHOICES, 89 dest="doctestreport", 90 ) 91 group.addoption( 92 "--doctest-glob", 93 action="append", 94 default=[], 95 metavar="pat", 96 help="doctests file matching pattern, default: test*.txt", 97 dest="doctestglob", 98 ) 99 group.addoption( 100 "--doctest-ignore-import-errors", 101 action="store_true", 102 default=False, 103 help="ignore doctest ImportErrors", 104 dest="doctest_ignore_import_errors", 105 ) 106 group.addoption( 107 "--doctest-continue-on-failure", 108 action="store_true", 109 default=False, 110 help="for a given doctest, continue to run after the first failure", 111 dest="doctest_continue_on_failure", 112 ) 113 114 115def pytest_unconfigure() -> None: 116 global RUNNER_CLASS 117 118 RUNNER_CLASS = None 119 120 121def pytest_collect_file( 122 path: py.path.local, parent: Collector, 123) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: 124 config = parent.config 125 if path.ext == ".py": 126 if config.option.doctestmodules and not _is_setup_py(path): 127 mod = DoctestModule.from_parent(parent, fspath=path) # type: DoctestModule 128 return mod 129 elif _is_doctest(config, path, parent): 130 txt = DoctestTextfile.from_parent(parent, fspath=path) # type: DoctestTextfile 131 return txt 132 return None 133 134 135def _is_setup_py(path: py.path.local) -> bool: 136 if path.basename != "setup.py": 137 return False 138 contents = path.read_binary() 139 return b"setuptools" in contents or b"distutils" in contents 140 141 142def _is_doctest(config: Config, path: py.path.local, parent) -> bool: 143 if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): 144 return True 145 globs = config.getoption("doctestglob") or ["test*.txt"] 146 for glob in globs: 147 if path.check(fnmatch=glob): 148 return True 149 return False 150 151 152class ReprFailDoctest(TerminalRepr): 153 def __init__( 154 self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] 155 ) -> None: 156 self.reprlocation_lines = reprlocation_lines 157 158 def toterminal(self, tw: TerminalWriter) -> None: 159 for reprlocation, lines in self.reprlocation_lines: 160 for line in lines: 161 tw.line(line) 162 reprlocation.toterminal(tw) 163 164 165class MultipleDoctestFailures(Exception): 166 def __init__(self, failures: "Sequence[doctest.DocTestFailure]") -> None: 167 super().__init__() 168 self.failures = failures 169 170 171def _init_runner_class() -> "Type[doctest.DocTestRunner]": 172 import doctest 173 174 class PytestDoctestRunner(doctest.DebugRunner): 175 """Runner to collect failures. 176 177 Note that the out variable in this case is a list instead of a 178 stdout-like object. 179 """ 180 181 def __init__( 182 self, 183 checker: Optional[doctest.OutputChecker] = None, 184 verbose: Optional[bool] = None, 185 optionflags: int = 0, 186 continue_on_failure: bool = True, 187 ) -> None: 188 doctest.DebugRunner.__init__( 189 self, checker=checker, verbose=verbose, optionflags=optionflags 190 ) 191 self.continue_on_failure = continue_on_failure 192 193 def report_failure( 194 self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, 195 ) -> None: 196 failure = doctest.DocTestFailure(test, example, got) 197 if self.continue_on_failure: 198 out.append(failure) 199 else: 200 raise failure 201 202 def report_unexpected_exception( 203 self, 204 out, 205 test: "doctest.DocTest", 206 example: "doctest.Example", 207 exc_info: "Tuple[Type[BaseException], BaseException, types.TracebackType]", 208 ) -> None: 209 if isinstance(exc_info[1], OutcomeException): 210 raise exc_info[1] 211 if isinstance(exc_info[1], bdb.BdbQuit): 212 outcomes.exit("Quitting debugger") 213 failure = doctest.UnexpectedException(test, example, exc_info) 214 if self.continue_on_failure: 215 out.append(failure) 216 else: 217 raise failure 218 219 return PytestDoctestRunner 220 221 222def _get_runner( 223 checker: Optional["doctest.OutputChecker"] = None, 224 verbose: Optional[bool] = None, 225 optionflags: int = 0, 226 continue_on_failure: bool = True, 227) -> "doctest.DocTestRunner": 228 # We need this in order to do a lazy import on doctest 229 global RUNNER_CLASS 230 if RUNNER_CLASS is None: 231 RUNNER_CLASS = _init_runner_class() 232 # Type ignored because the continue_on_failure argument is only defined on 233 # PytestDoctestRunner, which is lazily defined so can't be used as a type. 234 return RUNNER_CLASS( # type: ignore 235 checker=checker, 236 verbose=verbose, 237 optionflags=optionflags, 238 continue_on_failure=continue_on_failure, 239 ) 240 241 242class DoctestItem(pytest.Item): 243 def __init__( 244 self, 245 name: str, 246 parent: "Union[DoctestTextfile, DoctestModule]", 247 runner: Optional["doctest.DocTestRunner"] = None, 248 dtest: Optional["doctest.DocTest"] = None, 249 ) -> None: 250 super().__init__(name, parent) 251 self.runner = runner 252 self.dtest = dtest 253 self.obj = None 254 self.fixture_request = None # type: Optional[FixtureRequest] 255 256 @classmethod 257 def from_parent( # type: ignore 258 cls, 259 parent: "Union[DoctestTextfile, DoctestModule]", 260 *, 261 name: str, 262 runner: "doctest.DocTestRunner", 263 dtest: "doctest.DocTest" 264 ): 265 # incompatible signature due to to imposed limits on sublcass 266 """The public named constructor.""" 267 return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) 268 269 def setup(self) -> None: 270 if self.dtest is not None: 271 self.fixture_request = _setup_fixtures(self) 272 globs = dict(getfixture=self.fixture_request.getfixturevalue) 273 for name, value in self.fixture_request.getfixturevalue( 274 "doctest_namespace" 275 ).items(): 276 globs[name] = value 277 self.dtest.globs.update(globs) 278 279 def runtest(self) -> None: 280 assert self.dtest is not None 281 assert self.runner is not None 282 _check_all_skipped(self.dtest) 283 self._disable_output_capturing_for_darwin() 284 failures = [] # type: List[doctest.DocTestFailure] 285 # Type ignored because we change the type of `out` from what 286 # doctest expects. 287 self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] 288 if failures: 289 raise MultipleDoctestFailures(failures) 290 291 def _disable_output_capturing_for_darwin(self) -> None: 292 """Disable output capturing. Otherwise, stdout is lost to doctest (#985).""" 293 if platform.system() != "Darwin": 294 return 295 capman = self.config.pluginmanager.getplugin("capturemanager") 296 if capman: 297 capman.suspend_global_capture(in_=True) 298 out, err = capman.read_global_capture() 299 sys.stdout.write(out) 300 sys.stderr.write(err) 301 302 # TODO: Type ignored -- breaks Liskov Substitution. 303 def repr_failure( # type: ignore[override] 304 self, excinfo: ExceptionInfo[BaseException], 305 ) -> Union[str, TerminalRepr]: 306 import doctest 307 308 failures = ( 309 None 310 ) # type: Optional[Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]] 311 if isinstance( 312 excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) 313 ): 314 failures = [excinfo.value] 315 elif isinstance(excinfo.value, MultipleDoctestFailures): 316 failures = excinfo.value.failures 317 318 if failures is not None: 319 reprlocation_lines = [] 320 for failure in failures: 321 example = failure.example 322 test = failure.test 323 filename = test.filename 324 if test.lineno is None: 325 lineno = None 326 else: 327 lineno = test.lineno + example.lineno + 1 328 message = type(failure).__name__ 329 # TODO: ReprFileLocation doesn't expect a None lineno. 330 reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] 331 checker = _get_checker() 332 report_choice = _get_report_choice( 333 self.config.getoption("doctestreport") 334 ) 335 if lineno is not None: 336 assert failure.test.docstring is not None 337 lines = failure.test.docstring.splitlines(False) 338 # add line numbers to the left of the error message 339 assert test.lineno is not None 340 lines = [ 341 "%03d %s" % (i + test.lineno + 1, x) 342 for (i, x) in enumerate(lines) 343 ] 344 # trim docstring error lines to 10 345 lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] 346 else: 347 lines = [ 348 "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example" 349 ] 350 indent = ">>>" 351 for line in example.source.splitlines(): 352 lines.append("??? {} {}".format(indent, line)) 353 indent = "..." 354 if isinstance(failure, doctest.DocTestFailure): 355 lines += checker.output_difference( 356 example, failure.got, report_choice 357 ).split("\n") 358 else: 359 inner_excinfo = ExceptionInfo(failure.exc_info) 360 lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] 361 lines += [ 362 x.strip("\n") 363 for x in traceback.format_exception(*failure.exc_info) 364 ] 365 reprlocation_lines.append((reprlocation, lines)) 366 return ReprFailDoctest(reprlocation_lines) 367 else: 368 return super().repr_failure(excinfo) 369 370 def reportinfo(self): 371 assert self.dtest is not None 372 return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name 373 374 375def _get_flag_lookup() -> Dict[str, int]: 376 import doctest 377 378 return dict( 379 DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1, 380 DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE, 381 NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE, 382 ELLIPSIS=doctest.ELLIPSIS, 383 IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, 384 COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, 385 ALLOW_UNICODE=_get_allow_unicode_flag(), 386 ALLOW_BYTES=_get_allow_bytes_flag(), 387 NUMBER=_get_number_flag(), 388 ) 389 390 391def get_optionflags(parent): 392 optionflags_str = parent.config.getini("doctest_optionflags") 393 flag_lookup_table = _get_flag_lookup() 394 flag_acc = 0 395 for flag in optionflags_str: 396 flag_acc |= flag_lookup_table[flag] 397 return flag_acc 398 399 400def _get_continue_on_failure(config): 401 continue_on_failure = config.getvalue("doctest_continue_on_failure") 402 if continue_on_failure: 403 # We need to turn off this if we use pdb since we should stop at 404 # the first failure. 405 if config.getvalue("usepdb"): 406 continue_on_failure = False 407 return continue_on_failure 408 409 410class DoctestTextfile(pytest.Module): 411 obj = None 412 413 def collect(self) -> Iterable[DoctestItem]: 414 import doctest 415 416 # Inspired by doctest.testfile; ideally we would use it directly, 417 # but it doesn't support passing a custom checker. 418 encoding = self.config.getini("doctest_encoding") 419 text = self.fspath.read_text(encoding) 420 filename = str(self.fspath) 421 name = self.fspath.basename 422 globs = {"__name__": "__main__"} 423 424 optionflags = get_optionflags(self) 425 426 runner = _get_runner( 427 verbose=False, 428 optionflags=optionflags, 429 checker=_get_checker(), 430 continue_on_failure=_get_continue_on_failure(self.config), 431 ) 432 433 parser = doctest.DocTestParser() 434 test = parser.get_doctest(text, globs, name, filename, 0) 435 if test.examples: 436 yield DoctestItem.from_parent( 437 self, name=test.name, runner=runner, dtest=test 438 ) 439 440 441def _check_all_skipped(test: "doctest.DocTest") -> None: 442 """Raise pytest.skip() if all examples in the given DocTest have the SKIP 443 option set.""" 444 import doctest 445 446 all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) 447 if all_skipped: 448 pytest.skip("all tests skipped by +SKIP option") 449 450 451def _is_mocked(obj: object) -> bool: 452 """Return if an object is possibly a mock object by checking the 453 existence of a highly improbable attribute.""" 454 return ( 455 safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) 456 is not None 457 ) 458 459 460@contextmanager 461def _patch_unwrap_mock_aware() -> Generator[None, None, None]: 462 """Context manager which replaces ``inspect.unwrap`` with a version 463 that's aware of mock objects and doesn't recurse into them.""" 464 real_unwrap = inspect.unwrap 465 466 def _mock_aware_unwrap( 467 func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None 468 ) -> Any: 469 try: 470 if stop is None or stop is _is_mocked: 471 return real_unwrap(func, stop=_is_mocked) 472 _stop = stop 473 return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) 474 except Exception as e: 475 warnings.warn( 476 "Got %r when unwrapping %r. This is usually caused " 477 "by a violation of Python's object protocol; see e.g. " 478 "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), 479 PytestWarning, 480 ) 481 raise 482 483 inspect.unwrap = _mock_aware_unwrap 484 try: 485 yield 486 finally: 487 inspect.unwrap = real_unwrap 488 489 490class DoctestModule(pytest.Module): 491 def collect(self) -> Iterable[DoctestItem]: 492 import doctest 493 494 class MockAwareDocTestFinder(doctest.DocTestFinder): 495 """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. 496 497 https://github.com/pytest-dev/pytest/issues/3456 498 https://bugs.python.org/issue25532 499 """ 500 501 def _find_lineno(self, obj, source_lines): 502 """Doctest code does not take into account `@property`, this 503 is a hackish way to fix it. 504 505 https://bugs.python.org/issue17446 506 """ 507 if isinstance(obj, property): 508 obj = getattr(obj, "fget", obj) 509 # Type ignored because this is a private function. 510 return doctest.DocTestFinder._find_lineno( # type: ignore 511 self, obj, source_lines, 512 ) 513 514 def _find( 515 self, tests, obj, name, module, source_lines, globs, seen 516 ) -> None: 517 if _is_mocked(obj): 518 return 519 with _patch_unwrap_mock_aware(): 520 521 # Type ignored because this is a private function. 522 doctest.DocTestFinder._find( # type: ignore 523 self, tests, obj, name, module, source_lines, globs, seen 524 ) 525 526 if self.fspath.basename == "conftest.py": 527 module = self.config.pluginmanager._importconftest( 528 self.fspath, self.config.getoption("importmode") 529 ) 530 else: 531 try: 532 module = import_path(self.fspath) 533 except ImportError: 534 if self.config.getvalue("doctest_ignore_import_errors"): 535 pytest.skip("unable to import module %r" % self.fspath) 536 else: 537 raise 538 # Uses internal doctest module parsing mechanism. 539 finder = MockAwareDocTestFinder() 540 optionflags = get_optionflags(self) 541 runner = _get_runner( 542 verbose=False, 543 optionflags=optionflags, 544 checker=_get_checker(), 545 continue_on_failure=_get_continue_on_failure(self.config), 546 ) 547 548 for test in finder.find(module, module.__name__): 549 if test.examples: # skip empty doctests 550 yield DoctestItem.from_parent( 551 self, name=test.name, runner=runner, dtest=test 552 ) 553 554 555def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: 556 """Used by DoctestTextfile and DoctestItem to setup fixture information.""" 557 558 def func() -> None: 559 pass 560 561 doctest_item.funcargs = {} # type: ignore[attr-defined] 562 fm = doctest_item.session._fixturemanager 563 doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] 564 node=doctest_item, func=func, cls=None, funcargs=False 565 ) 566 fixture_request = FixtureRequest(doctest_item) 567 fixture_request._fillfixtures() 568 return fixture_request 569 570 571def _init_checker_class() -> "Type[doctest.OutputChecker]": 572 import doctest 573 import re 574 575 class LiteralsOutputChecker(doctest.OutputChecker): 576 # Based on doctest_nose_plugin.py from the nltk project 577 # (https://github.com/nltk/nltk) and on the "numtest" doctest extension 578 # by Sebastien Boisgerault (https://github.com/boisgera/numtest). 579 580 _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) 581 _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) 582 _number_re = re.compile( 583 r""" 584 (?P<number> 585 (?P<mantissa> 586 (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+) 587 | 588 (?P<integer2> [+-]?\d+)\. 589 ) 590 (?: 591 [Ee] 592 (?P<exponent1> [+-]?\d+) 593 )? 594 | 595 (?P<integer3> [+-]?\d+) 596 (?: 597 [Ee] 598 (?P<exponent2> [+-]?\d+) 599 ) 600 ) 601 """, 602 re.VERBOSE, 603 ) 604 605 def check_output(self, want: str, got: str, optionflags: int) -> bool: 606 if doctest.OutputChecker.check_output(self, want, got, optionflags): 607 return True 608 609 allow_unicode = optionflags & _get_allow_unicode_flag() 610 allow_bytes = optionflags & _get_allow_bytes_flag() 611 allow_number = optionflags & _get_number_flag() 612 613 if not allow_unicode and not allow_bytes and not allow_number: 614 return False 615 616 def remove_prefixes(regex: Pattern[str], txt: str) -> str: 617 return re.sub(regex, r"\1\2", txt) 618 619 if allow_unicode: 620 want = remove_prefixes(self._unicode_literal_re, want) 621 got = remove_prefixes(self._unicode_literal_re, got) 622 623 if allow_bytes: 624 want = remove_prefixes(self._bytes_literal_re, want) 625 got = remove_prefixes(self._bytes_literal_re, got) 626 627 if allow_number: 628 got = self._remove_unwanted_precision(want, got) 629 630 return doctest.OutputChecker.check_output(self, want, got, optionflags) 631 632 def _remove_unwanted_precision(self, want: str, got: str) -> str: 633 wants = list(self._number_re.finditer(want)) 634 gots = list(self._number_re.finditer(got)) 635 if len(wants) != len(gots): 636 return got 637 offset = 0 638 for w, g in zip(wants, gots): 639 fraction = w.group("fraction") # type: Optional[str] 640 exponent = w.group("exponent1") # type: Optional[str] 641 if exponent is None: 642 exponent = w.group("exponent2") 643 if fraction is None: 644 precision = 0 645 else: 646 precision = len(fraction) 647 if exponent is not None: 648 precision -= int(exponent) 649 if float(w.group()) == approx(float(g.group()), abs=10 ** -precision): 650 # They're close enough. Replace the text we actually 651 # got with the text we want, so that it will match when we 652 # check the string literally. 653 got = ( 654 got[: g.start() + offset] + w.group() + got[g.end() + offset :] 655 ) 656 offset += w.end() - w.start() - (g.end() - g.start()) 657 return got 658 659 return LiteralsOutputChecker 660 661 662def _get_checker() -> "doctest.OutputChecker": 663 """Return a doctest.OutputChecker subclass that supports some 664 additional options: 665 666 * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' 667 prefixes (respectively) in string literals. Useful when the same 668 doctest should run in Python 2 and Python 3. 669 670 * NUMBER to ignore floating-point differences smaller than the 671 precision of the literal number in the doctest. 672 673 An inner class is used to avoid importing "doctest" at the module 674 level. 675 """ 676 global CHECKER_CLASS 677 if CHECKER_CLASS is None: 678 CHECKER_CLASS = _init_checker_class() 679 return CHECKER_CLASS() 680 681 682def _get_allow_unicode_flag() -> int: 683 """Register and return the ALLOW_UNICODE flag.""" 684 import doctest 685 686 return doctest.register_optionflag("ALLOW_UNICODE") 687 688 689def _get_allow_bytes_flag() -> int: 690 """Register and return the ALLOW_BYTES flag.""" 691 import doctest 692 693 return doctest.register_optionflag("ALLOW_BYTES") 694 695 696def _get_number_flag() -> int: 697 """Register and return the NUMBER flag.""" 698 import doctest 699 700 return doctest.register_optionflag("NUMBER") 701 702 703def _get_report_choice(key: str) -> int: 704 """Return the actual `doctest` module flag value. 705 706 We want to do it as late as possible to avoid importing `doctest` and all 707 its dependencies when parsing options, as it adds overhead and breaks tests. 708 """ 709 import doctest 710 711 return { 712 DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF, 713 DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF, 714 DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF, 715 DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE, 716 DOCTEST_REPORT_CHOICE_NONE: 0, 717 }[key] 718 719 720@pytest.fixture(scope="session") 721def doctest_namespace() -> Dict[str, Any]: 722 """Fixture that returns a :py:class:`dict` that will be injected into the 723 namespace of doctests.""" 724 return dict() 725