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