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

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 

25 

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 

45 

46if TYPE_CHECKING: 

47 import doctest 

48 

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" 

54 

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) 

62 

63# Lazy definition of runner class 

64RUNNER_CLASS = None 

65# Lazy definition of output checker class 

66CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None 

67 

68 

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 ) 

117 

118 

119def pytest_unconfigure() -> None: 

120 global RUNNER_CLASS 

121 

122 RUNNER_CLASS = None 

123 

124 

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 

140 

141 

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 

147 

148 

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) 

154 

155 

156def _is_main_py(path: Path) -> bool: 

157 return path.name == "__main__.py" 

158 

159 

160class ReprFailDoctest(TerminalRepr): 

161 def __init__( 

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

163 ) -> None: 

164 self.reprlocation_lines = reprlocation_lines 

165 

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) 

171 

172 

173class MultipleDoctestFailures(Exception): 

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

175 super().__init__() 

176 self.failures = failures 

177 

178 

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

180 import doctest 

181 

182 class PytestDoctestRunner(doctest.DebugRunner): 

183 """Runner to collect failures. 

184 

185 Note that the out variable in this case is a list instead of a 

186 stdout-like object. 

187 """ 

188 

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 

198 

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 

211 

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 

228 

229 return PytestDoctestRunner 

230 

231 

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 ) 

250 

251 

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 

265 

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) 

278 

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) 

288 

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) 

300 

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) 

311 

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 

318 

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 

328 

329 if failures is None: 

330 return super().repr_failure(excinfo) 

331 

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) 

376 

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 

380 

381 

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

383 import doctest 

384 

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 ) 

396 

397 

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 

405 

406 

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 

415 

416 

417class DoctestTextfile(Module): 

418 obj = None 

419 

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

421 import doctest 

422 

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

430 

431 optionflags = get_optionflags(self) 

432 

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 ) 

439 

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 ) 

446 

447 

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 

452 

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

456 

457 

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 ) 

465 

466 

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 

472 

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 

489 

490 inspect.unwrap = _mock_aware_unwrap 

491 try: 

492 yield 

493 finally: 

494 inspect.unwrap = real_unwrap 

495 

496 

497class DoctestModule(Module): 

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

499 import doctest 

500 

501 class MockAwareDocTestFinder(doctest.DocTestFinder): 

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 """Doctest code does not take into account `@property`, this 

510 is a hackish way to fix it. https://bugs.python.org/issue17446 

511 

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) 

517 

518 if hasattr(obj, "__wrapped__"): 

519 # Get the main obj in case of it being wrapped 

520 obj = inspect.unwrap(obj) 

521 

522 # Type ignored because this is a private function. 

523 return super()._find_lineno( # type:ignore[misc] 

524 obj, 

525 source_lines, 

526 ) 

527 

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(): 

534 

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 ) 

539 

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 ) 

567 

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 ) 

573 

574 

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

576 """Used by DoctestTextfile and DoctestItem to setup fixture information.""" 

577 

578 def func() -> None: 

579 pass 

580 

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 

589 

590 

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

592 import doctest 

593 import re 

594 

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). 

599 

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 ) 

624 

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

626 if super().check_output(want, got, optionflags): 

627 return True 

628 

629 allow_unicode = optionflags & _get_allow_unicode_flag() 

630 allow_bytes = optionflags & _get_allow_bytes_flag() 

631 allow_number = optionflags & _get_number_flag() 

632 

633 if not allow_unicode and not allow_bytes and not allow_number: 

634 return False 

635 

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

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

638 

639 if allow_unicode: 

640 want = remove_prefixes(self._unicode_literal_re, want) 

641 got = remove_prefixes(self._unicode_literal_re, got) 

642 

643 if allow_bytes: 

644 want = remove_prefixes(self._bytes_literal_re, want) 

645 got = remove_prefixes(self._bytes_literal_re, got) 

646 

647 if allow_number: 

648 got = self._remove_unwanted_precision(want, got) 

649 

650 return super().check_output(want, got, optionflags) 

651 

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 

675 

676 return LiteralsOutputChecker 

677 

678 

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

680 """Return a doctest.OutputChecker subclass that supports some 

681 additional options: 

682 

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. 

686 

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

688 precision of the literal number in the doctest. 

689 

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() 

697 

698 

699def _get_allow_unicode_flag() -> int: 

700 """Register and return the ALLOW_UNICODE flag.""" 

701 import doctest 

702 

703 return doctest.register_optionflag("ALLOW_UNICODE") 

704 

705 

706def _get_allow_bytes_flag() -> int: 

707 """Register and return the ALLOW_BYTES flag.""" 

708 import doctest 

709 

710 return doctest.register_optionflag("ALLOW_BYTES") 

711 

712 

713def _get_number_flag() -> int: 

714 """Register and return the NUMBER flag.""" 

715 import doctest 

716 

717 return doctest.register_optionflag("NUMBER") 

718 

719 

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

721 """Return the actual `doctest` module flag value. 

722 

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 

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@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. 

741 

742 Usually this fixture is used in conjunction with another ``autouse`` fixture: 

743 

744 .. code-block:: python 

745 

746 @pytest.fixture(autouse=True) 

747 def add_np(doctest_namespace): 

748 doctest_namespace["np"] = numpy 

749 

750 For more details: :ref:`doctest_namespace`. 

751 """ 

752 return dict()