Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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.outcomes import OutcomeException 

36from _pytest.pathlib import import_path 

37from _pytest.python_api import approx 

38from _pytest.warning_types import PytestWarning 

39 

40if TYPE_CHECKING: 

41 import doctest 

42 from typing import Type 

43 

44DOCTEST_REPORT_CHOICE_NONE = "none" 

45DOCTEST_REPORT_CHOICE_CDIFF = "cdiff" 

46DOCTEST_REPORT_CHOICE_NDIFF = "ndiff" 

47DOCTEST_REPORT_CHOICE_UDIFF = "udiff" 

48DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = "only_first_failure" 

49 

50DOCTEST_REPORT_CHOICES = ( 

51 DOCTEST_REPORT_CHOICE_NONE, 

52 DOCTEST_REPORT_CHOICE_CDIFF, 

53 DOCTEST_REPORT_CHOICE_NDIFF, 

54 DOCTEST_REPORT_CHOICE_UDIFF, 

55 DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, 

56) 

57 

58# Lazy definition of runner class 

59RUNNER_CLASS = None 

60# Lazy definition of output checker class 

61CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]] 

62 

63 

64def pytest_addoption(parser: Parser) -> None: 

65 parser.addini( 

66 "doctest_optionflags", 

67 "option flags for doctests", 

68 type="args", 

69 default=["ELLIPSIS"], 

70 ) 

71 parser.addini( 

72 "doctest_encoding", "encoding used for doctest files", default="utf-8" 

73 ) 

74 group = parser.getgroup("collect") 

75 group.addoption( 

76 "--doctest-modules", 

77 action="store_true", 

78 default=False, 

79 help="run doctests in all .py modules", 

80 dest="doctestmodules", 

81 ) 

82 group.addoption( 

83 "--doctest-report", 

84 type=str.lower, 

85 default="udiff", 

86 help="choose another output format for diffs on doctest failure", 

87 choices=DOCTEST_REPORT_CHOICES, 

88 dest="doctestreport", 

89 ) 

90 group.addoption( 

91 "--doctest-glob", 

92 action="append", 

93 default=[], 

94 metavar="pat", 

95 help="doctests file matching pattern, default: test*.txt", 

96 dest="doctestglob", 

97 ) 

98 group.addoption( 

99 "--doctest-ignore-import-errors", 

100 action="store_true", 

101 default=False, 

102 help="ignore doctest ImportErrors", 

103 dest="doctest_ignore_import_errors", 

104 ) 

105 group.addoption( 

106 "--doctest-continue-on-failure", 

107 action="store_true", 

108 default=False, 

109 help="for a given doctest, continue to run after the first failure", 

110 dest="doctest_continue_on_failure", 

111 ) 

112 

113 

114def pytest_unconfigure() -> None: 

115 global RUNNER_CLASS 

116 

117 RUNNER_CLASS = None 

118 

119 

120def pytest_collect_file( 

121 path: py.path.local, parent 

122) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: 

123 config = parent.config 

124 if path.ext == ".py": 

125 if config.option.doctestmodules and not _is_setup_py(path): 

126 mod = DoctestModule.from_parent(parent, fspath=path) # type: DoctestModule 

127 return mod 

128 elif _is_doctest(config, path, parent): 

129 txt = DoctestTextfile.from_parent(parent, fspath=path) # type: DoctestTextfile 

130 return txt 

131 return None 

132 

133 

134def _is_setup_py(path: py.path.local) -> bool: 

135 if path.basename != "setup.py": 

136 return False 

137 contents = path.read_binary() 

138 return b"setuptools" in contents or b"distutils" in contents 

139 

140 

141def _is_doctest(config: Config, path: py.path.local, parent) -> bool: 

142 if path.ext in (".txt", ".rst") and parent.session.isinitpath(path): 

143 return True 

144 globs = config.getoption("doctestglob") or ["test*.txt"] 

145 for glob in globs: 

146 if path.check(fnmatch=glob): 

147 return True 

148 return False 

149 

150 

151class ReprFailDoctest(TerminalRepr): 

152 def __init__( 

153 self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] 

154 ) -> None: 

155 self.reprlocation_lines = reprlocation_lines 

156 

157 def toterminal(self, tw: TerminalWriter) -> None: 

158 for reprlocation, lines in self.reprlocation_lines: 

159 for line in lines: 

160 tw.line(line) 

161 reprlocation.toterminal(tw) 

162 

163 

164class MultipleDoctestFailures(Exception): 

165 def __init__(self, failures: "Sequence[doctest.DocTestFailure]") -> None: 

166 super().__init__() 

167 self.failures = failures 

168 

169 

170def _init_runner_class() -> "Type[doctest.DocTestRunner]": 

171 import doctest 

172 

173 class PytestDoctestRunner(doctest.DebugRunner): 

174 """ 

175 Runner to collect failures. Note that the out variable in this case is 

176 a list instead of a stdout-like object 

177 """ 

178 

179 def __init__( 

180 self, 

181 checker: Optional[doctest.OutputChecker] = None, 

182 verbose: Optional[bool] = None, 

183 optionflags: int = 0, 

184 continue_on_failure: bool = True, 

185 ) -> None: 

186 doctest.DebugRunner.__init__( 

187 self, checker=checker, verbose=verbose, optionflags=optionflags 

188 ) 

189 self.continue_on_failure = continue_on_failure 

190 

191 def report_failure( 

192 self, out, test: "doctest.DocTest", example: "doctest.Example", got: str, 

193 ) -> None: 

194 failure = doctest.DocTestFailure(test, example, got) 

195 if self.continue_on_failure: 

196 out.append(failure) 

197 else: 

198 raise failure 

199 

200 def report_unexpected_exception( 

201 self, 

202 out, 

203 test: "doctest.DocTest", 

204 example: "doctest.Example", 

205 exc_info: "Tuple[Type[BaseException], BaseException, types.TracebackType]", 

206 ) -> None: 

207 if isinstance(exc_info[1], OutcomeException): 

208 raise exc_info[1] 

209 if isinstance(exc_info[1], bdb.BdbQuit): 

210 outcomes.exit("Quitting debugger") 

211 failure = doctest.UnexpectedException(test, example, exc_info) 

212 if self.continue_on_failure: 

213 out.append(failure) 

214 else: 

215 raise failure 

216 

217 return PytestDoctestRunner 

218 

219 

220def _get_runner( 

221 checker: Optional["doctest.OutputChecker"] = None, 

222 verbose: Optional[bool] = None, 

223 optionflags: int = 0, 

224 continue_on_failure: bool = True, 

225) -> "doctest.DocTestRunner": 

226 # We need this in order to do a lazy import on doctest 

227 global RUNNER_CLASS 

228 if RUNNER_CLASS is None: 

229 RUNNER_CLASS = _init_runner_class() 

230 # Type ignored because the continue_on_failure argument is only defined on 

231 # PytestDoctestRunner, which is lazily defined so can't be used as a type. 

232 return RUNNER_CLASS( # type: ignore 

233 checker=checker, 

234 verbose=verbose, 

235 optionflags=optionflags, 

236 continue_on_failure=continue_on_failure, 

237 ) 

238 

239 

240class DoctestItem(pytest.Item): 

241 def __init__( 

242 self, 

243 name: str, 

244 parent: "Union[DoctestTextfile, DoctestModule]", 

245 runner: Optional["doctest.DocTestRunner"] = None, 

246 dtest: Optional["doctest.DocTest"] = None, 

247 ) -> None: 

248 super().__init__(name, parent) 

249 self.runner = runner 

250 self.dtest = dtest 

251 self.obj = None 

252 self.fixture_request = None # type: Optional[FixtureRequest] 

253 

254 @classmethod 

255 def from_parent( # type: ignore 

256 cls, 

257 parent: "Union[DoctestTextfile, DoctestModule]", 

258 *, 

259 name: str, 

260 runner: "doctest.DocTestRunner", 

261 dtest: "doctest.DocTest" 

262 ): 

263 # incompatible signature due to to imposed limits on sublcass 

264 """ 

265 the public named constructor 

266 """ 

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 """ 

293 Disable output capturing. Otherwise, stdout is lost to doctest (#985) 

294 """ 

295 if platform.system() != "Darwin": 

296 return 

297 capman = self.config.pluginmanager.getplugin("capturemanager") 

298 if capman: 

299 capman.suspend_global_capture(in_=True) 

300 out, err = capman.read_global_capture() 

301 sys.stdout.write(out) 

302 sys.stderr.write(err) 

303 

304 # TODO: Type ignored -- breaks Liskov Substitution. 

305 def repr_failure( # type: ignore[override] 

306 self, excinfo: ExceptionInfo[BaseException], 

307 ) -> Union[str, TerminalRepr]: 

308 import doctest 

309 

310 failures = ( 

311 None 

312 ) # type: Optional[Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]] 

313 if isinstance( 

314 excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) 

315 ): 

316 failures = [excinfo.value] 

317 elif isinstance(excinfo.value, MultipleDoctestFailures): 

318 failures = excinfo.value.failures 

319 

320 if failures is not None: 

321 reprlocation_lines = [] 

322 for failure in failures: 

323 example = failure.example 

324 test = failure.test 

325 filename = test.filename 

326 if test.lineno is None: 

327 lineno = None 

328 else: 

329 lineno = test.lineno + example.lineno + 1 

330 message = type(failure).__name__ 

331 # TODO: ReprFileLocation doesn't expect a None lineno. 

332 reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type] 

333 checker = _get_checker() 

334 report_choice = _get_report_choice( 

335 self.config.getoption("doctestreport") 

336 ) 

337 if lineno is not None: 

338 assert failure.test.docstring is not None 

339 lines = failure.test.docstring.splitlines(False) 

340 # add line numbers to the left of the error message 

341 assert test.lineno is not None 

342 lines = [ 

343 "%03d %s" % (i + test.lineno + 1, x) 

344 for (i, x) in enumerate(lines) 

345 ] 

346 # trim docstring error lines to 10 

347 lines = lines[max(example.lineno - 9, 0) : example.lineno + 1] 

348 else: 

349 lines = [ 

350 "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example" 

351 ] 

352 indent = ">>>" 

353 for line in example.source.splitlines(): 

354 lines.append("??? {} {}".format(indent, line)) 

355 indent = "..." 

356 if isinstance(failure, doctest.DocTestFailure): 

357 lines += checker.output_difference( 

358 example, failure.got, report_choice 

359 ).split("\n") 

360 else: 

361 inner_excinfo = ExceptionInfo(failure.exc_info) 

362 lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] 

363 lines += [ 

364 x.strip("\n") 

365 for x in traceback.format_exception(*failure.exc_info) 

366 ] 

367 reprlocation_lines.append((reprlocation, lines)) 

368 return ReprFailDoctest(reprlocation_lines) 

369 else: 

370 return super().repr_failure(excinfo) 

371 

372 def reportinfo(self): 

373 assert self.dtest is not None 

374 return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name 

375 

376 

377def _get_flag_lookup() -> Dict[str, int]: 

378 import doctest 

379 

380 return dict( 

381 DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1, 

382 DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE, 

383 NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE, 

384 ELLIPSIS=doctest.ELLIPSIS, 

385 IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL, 

386 COMPARISON_FLAGS=doctest.COMPARISON_FLAGS, 

387 ALLOW_UNICODE=_get_allow_unicode_flag(), 

388 ALLOW_BYTES=_get_allow_bytes_flag(), 

389 NUMBER=_get_number_flag(), 

390 ) 

391 

392 

393def get_optionflags(parent): 

394 optionflags_str = parent.config.getini("doctest_optionflags") 

395 flag_lookup_table = _get_flag_lookup() 

396 flag_acc = 0 

397 for flag in optionflags_str: 

398 flag_acc |= flag_lookup_table[flag] 

399 return flag_acc 

400 

401 

402def _get_continue_on_failure(config): 

403 continue_on_failure = config.getvalue("doctest_continue_on_failure") 

404 if continue_on_failure: 

405 # We need to turn off this if we use pdb since we should stop at 

406 # the first failure 

407 if config.getvalue("usepdb"): 

408 continue_on_failure = False 

409 return continue_on_failure 

410 

411 

412class DoctestTextfile(pytest.Module): 

413 obj = None 

414 

415 def collect(self) -> Iterable[DoctestItem]: 

416 import doctest 

417 

418 # inspired by doctest.testfile; ideally we would use it directly, 

419 # but it doesn't support passing a custom checker 

420 encoding = self.config.getini("doctest_encoding") 

421 text = self.fspath.read_text(encoding) 

422 filename = str(self.fspath) 

423 name = self.fspath.basename 

424 globs = {"__name__": "__main__"} 

425 

426 optionflags = get_optionflags(self) 

427 

428 runner = _get_runner( 

429 verbose=False, 

430 optionflags=optionflags, 

431 checker=_get_checker(), 

432 continue_on_failure=_get_continue_on_failure(self.config), 

433 ) 

434 

435 parser = doctest.DocTestParser() 

436 test = parser.get_doctest(text, globs, name, filename, 0) 

437 if test.examples: 

438 yield DoctestItem.from_parent( 

439 self, name=test.name, runner=runner, dtest=test 

440 ) 

441 

442 

443def _check_all_skipped(test: "doctest.DocTest") -> None: 

444 """raises pytest.skip() if all examples in the given DocTest have the SKIP 

445 option set. 

446 """ 

447 import doctest 

448 

449 all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples) 

450 if all_skipped: 

451 pytest.skip("all tests skipped by +SKIP option") 

452 

453 

454def _is_mocked(obj: object) -> bool: 

455 """ 

456 returns if a object is possibly a mock object by checking the existence of a highly improbable attribute 

457 """ 

458 return ( 

459 safe_getattr(obj, "pytest_mock_example_attribute_that_shouldnt_exist", None) 

460 is not None 

461 ) 

462 

463 

464@contextmanager 

465def _patch_unwrap_mock_aware() -> Generator[None, None, None]: 

466 """ 

467 contextmanager which replaces ``inspect.unwrap`` with a version 

468 that's aware of mock objects and doesn't recurse on them 

469 """ 

470 real_unwrap = inspect.unwrap 

471 

472 def _mock_aware_unwrap( 

473 func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None 

474 ) -> Any: 

475 try: 

476 if stop is None or stop is _is_mocked: 

477 return real_unwrap(func, stop=_is_mocked) 

478 _stop = stop 

479 return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func)) 

480 except Exception as e: 

481 warnings.warn( 

482 "Got %r when unwrapping %r. This is usually caused " 

483 "by a violation of Python's object protocol; see e.g. " 

484 "https://github.com/pytest-dev/pytest/issues/5080" % (e, func), 

485 PytestWarning, 

486 ) 

487 raise 

488 

489 inspect.unwrap = _mock_aware_unwrap 

490 try: 

491 yield 

492 finally: 

493 inspect.unwrap = real_unwrap 

494 

495 

496class DoctestModule(pytest.Module): 

497 def collect(self) -> Iterable[DoctestItem]: 

498 import doctest 

499 

500 class MockAwareDocTestFinder(doctest.DocTestFinder): 

501 """ 

502 a hackish doctest finder that overrides stdlib internals to fix a stdlib bug 

503 

504 https://github.com/pytest-dev/pytest/issues/3456 

505 https://bugs.python.org/issue25532 

506 """ 

507 

508 def _find_lineno(self, obj, source_lines): 

509 """ 

510 Doctest code does not take into account `@property`, this is a hackish way to fix it. 

511 

512 https://bugs.python.org/issue17446 

513 """ 

514 if isinstance(obj, property): 

515 obj = getattr(obj, "fget", obj) 

516 # Type ignored because this is a private function. 

517 return doctest.DocTestFinder._find_lineno( # type: ignore 

518 self, obj, source_lines, 

519 ) 

520 

521 def _find( 

522 self, tests, obj, name, module, source_lines, globs, seen 

523 ) -> None: 

524 if _is_mocked(obj): 

525 return 

526 with _patch_unwrap_mock_aware(): 

527 

528 # Type ignored because this is a private function. 

529 doctest.DocTestFinder._find( # type: ignore 

530 self, tests, obj, name, module, source_lines, globs, seen 

531 ) 

532 

533 if self.fspath.basename == "conftest.py": 

534 module = self.config.pluginmanager._importconftest( 

535 self.fspath, self.config.getoption("importmode") 

536 ) 

537 else: 

538 try: 

539 module = import_path(self.fspath) 

540 except ImportError: 

541 if self.config.getvalue("doctest_ignore_import_errors"): 

542 pytest.skip("unable to import module %r" % self.fspath) 

543 else: 

544 raise 

545 # uses internal doctest module parsing mechanism 

546 finder = MockAwareDocTestFinder() 

547 optionflags = get_optionflags(self) 

548 runner = _get_runner( 

549 verbose=False, 

550 optionflags=optionflags, 

551 checker=_get_checker(), 

552 continue_on_failure=_get_continue_on_failure(self.config), 

553 ) 

554 

555 for test in finder.find(module, module.__name__): 

556 if test.examples: # skip empty doctests 

557 yield DoctestItem.from_parent( 

558 self, name=test.name, runner=runner, dtest=test 

559 ) 

560 

561 

562def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest: 

563 """ 

564 Used by DoctestTextfile and DoctestItem to setup fixture information. 

565 """ 

566 

567 def func() -> None: 

568 pass 

569 

570 doctest_item.funcargs = {} # type: ignore[attr-defined] 

571 fm = doctest_item.session._fixturemanager 

572 doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined] 

573 node=doctest_item, func=func, cls=None, funcargs=False 

574 ) 

575 fixture_request = FixtureRequest(doctest_item) 

576 fixture_request._fillfixtures() 

577 return fixture_request 

578 

579 

580def _init_checker_class() -> "Type[doctest.OutputChecker]": 

581 import doctest 

582 import re 

583 

584 class LiteralsOutputChecker(doctest.OutputChecker): 

585 """ 

586 Based on doctest_nose_plugin.py from the nltk project 

587 (https://github.com/nltk/nltk) and on the "numtest" doctest extension 

588 by Sebastien Boisgerault (https://github.com/boisgera/numtest). 

589 """ 

590 

591 _unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE) 

592 _bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE) 

593 _number_re = re.compile( 

594 r""" 

595 (?P<number> 

596 (?P<mantissa> 

597 (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+) 

598 | 

599 (?P<integer2> [+-]?\d+)\. 

600 ) 

601 (?: 

602 [Ee] 

603 (?P<exponent1> [+-]?\d+) 

604 )? 

605 | 

606 (?P<integer3> [+-]?\d+) 

607 (?: 

608 [Ee] 

609 (?P<exponent2> [+-]?\d+) 

610 ) 

611 ) 

612 """, 

613 re.VERBOSE, 

614 ) 

615 

616 def check_output(self, want: str, got: str, optionflags: int) -> bool: 

617 if doctest.OutputChecker.check_output(self, want, got, optionflags): 

618 return True 

619 

620 allow_unicode = optionflags & _get_allow_unicode_flag() 

621 allow_bytes = optionflags & _get_allow_bytes_flag() 

622 allow_number = optionflags & _get_number_flag() 

623 

624 if not allow_unicode and not allow_bytes and not allow_number: 

625 return False 

626 

627 def remove_prefixes(regex: Pattern[str], txt: str) -> str: 

628 return re.sub(regex, r"\1\2", txt) 

629 

630 if allow_unicode: 

631 want = remove_prefixes(self._unicode_literal_re, want) 

632 got = remove_prefixes(self._unicode_literal_re, got) 

633 

634 if allow_bytes: 

635 want = remove_prefixes(self._bytes_literal_re, want) 

636 got = remove_prefixes(self._bytes_literal_re, got) 

637 

638 if allow_number: 

639 got = self._remove_unwanted_precision(want, got) 

640 

641 return doctest.OutputChecker.check_output(self, want, got, optionflags) 

642 

643 def _remove_unwanted_precision(self, want: str, got: str) -> str: 

644 wants = list(self._number_re.finditer(want)) 

645 gots = list(self._number_re.finditer(got)) 

646 if len(wants) != len(gots): 

647 return got 

648 offset = 0 

649 for w, g in zip(wants, gots): 

650 fraction = w.group("fraction") 

651 exponent = w.group("exponent1") 

652 if exponent is None: 

653 exponent = w.group("exponent2") 

654 if fraction is None: 

655 precision = 0 

656 else: 

657 precision = len(fraction) 

658 if exponent is not None: 

659 precision -= int(exponent) 

660 if float(w.group()) == approx(float(g.group()), abs=10 ** -precision): 

661 # They're close enough. Replace the text we actually 

662 # got with the text we want, so that it will match when we 

663 # check the string literally. 

664 got = ( 

665 got[: g.start() + offset] + w.group() + got[g.end() + offset :] 

666 ) 

667 offset += w.end() - w.start() - (g.end() - g.start()) 

668 return got 

669 

670 return LiteralsOutputChecker 

671 

672 

673def _get_checker() -> "doctest.OutputChecker": 

674 """ 

675 Returns a doctest.OutputChecker subclass that supports some 

676 additional options: 

677 

678 * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b'' 

679 prefixes (respectively) in string literals. Useful when the same 

680 doctest should run in Python 2 and Python 3. 

681 

682 * NUMBER to ignore floating-point differences smaller than the 

683 precision of the literal number in the doctest. 

684 

685 An inner class is used to avoid importing "doctest" at the module 

686 level. 

687 """ 

688 global CHECKER_CLASS 

689 if CHECKER_CLASS is None: 

690 CHECKER_CLASS = _init_checker_class() 

691 return CHECKER_CLASS() 

692 

693 

694def _get_allow_unicode_flag() -> int: 

695 """ 

696 Registers and returns the ALLOW_UNICODE flag. 

697 """ 

698 import doctest 

699 

700 return doctest.register_optionflag("ALLOW_UNICODE") 

701 

702 

703def _get_allow_bytes_flag() -> int: 

704 """ 

705 Registers and returns the ALLOW_BYTES flag. 

706 """ 

707 import doctest 

708 

709 return doctest.register_optionflag("ALLOW_BYTES") 

710 

711 

712def _get_number_flag() -> int: 

713 """ 

714 Registers and returns the NUMBER flag. 

715 """ 

716 import doctest 

717 

718 return doctest.register_optionflag("NUMBER") 

719 

720 

721def _get_report_choice(key: str) -> int: 

722 """ 

723 This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid 

724 importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests. 

725 """ 

726 import doctest 

727 

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] 

735 

736 

737@pytest.fixture(scope="session") 

738def doctest_namespace() -> Dict[str, Any]: 

739 """ 

740 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. 

741 """ 

742 return dict()