##// END OF EJS Templates
Pytest ipdoctest plugin...
Nikita Kniazev -
Show More
@@ -584,7 +584,7 b' class TestXdel(tt.TempFileMixin):'
584 def doctest_who():
584 def doctest_who():
585 """doctest for %who
585 """doctest for %who
586
586
587 In [1]: %reset -f
587 In [1]: %reset -sf
588
588
589 In [2]: alpha = 123
589 In [2]: alpha = 123
590
590
@@ -1,6 +1,13 b''
1 """Discover and run doctests in modules and test files."""
1 # Based on Pytest doctest.py
2 # Original license:
3 # The MIT License (MIT)
4 #
5 # Copyright (c) 2004-2021 Holger Krekel and others
6 """Discover and run ipdoctests in modules and test files."""
7 import builtins
2 import bdb
8 import bdb
3 import inspect
9 import inspect
10 import os
4 import platform
11 import platform
5 import sys
12 import sys
6 import traceback
13 import traceback
@@ -59,56 +66,56 b' DOCTEST_REPORT_CHOICES = ('
59 # Lazy definition of runner class
66 # Lazy definition of runner class
60 RUNNER_CLASS = None
67 RUNNER_CLASS = None
61 # Lazy definition of output checker class
68 # Lazy definition of output checker class
62 CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None
69 CHECKER_CLASS: Optional[Type["IPDoctestOutputChecker"]] = None
63
70
64
71
65 def pytest_addoption(parser: Parser) -> None:
72 def pytest_addoption(parser: Parser) -> None:
66 parser.addini(
73 parser.addini(
67 "doctest_optionflags",
74 "ipdoctest_optionflags",
68 "option flags for doctests",
75 "option flags for ipdoctests",
69 type="args",
76 type="args",
70 default=["ELLIPSIS"],
77 default=["ELLIPSIS"],
71 )
78 )
72 parser.addini(
79 parser.addini(
73 "doctest_encoding", "encoding used for doctest files", default="utf-8"
80 "ipdoctest_encoding", "encoding used for ipdoctest files", default="utf-8"
74 )
81 )
75 group = parser.getgroup("collect")
82 group = parser.getgroup("collect")
76 group.addoption(
83 group.addoption(
77 "--doctest-modules",
84 "--ipdoctest-modules",
78 action="store_true",
85 action="store_true",
79 default=False,
86 default=False,
80 help="run doctests in all .py modules",
87 help="run ipdoctests in all .py modules",
81 dest="doctestmodules",
88 dest="ipdoctestmodules",
82 )
89 )
83 group.addoption(
90 group.addoption(
84 "--doctest-report",
91 "--ipdoctest-report",
85 type=str.lower,
92 type=str.lower,
86 default="udiff",
93 default="udiff",
87 help="choose another output format for diffs on doctest failure",
94 help="choose another output format for diffs on ipdoctest failure",
88 choices=DOCTEST_REPORT_CHOICES,
95 choices=DOCTEST_REPORT_CHOICES,
89 dest="doctestreport",
96 dest="ipdoctestreport",
90 )
97 )
91 group.addoption(
98 group.addoption(
92 "--doctest-glob",
99 "--ipdoctest-glob",
93 action="append",
100 action="append",
94 default=[],
101 default=[],
95 metavar="pat",
102 metavar="pat",
96 help="doctests file matching pattern, default: test*.txt",
103 help="ipdoctests file matching pattern, default: test*.txt",
97 dest="doctestglob",
104 dest="ipdoctestglob",
98 )
105 )
99 group.addoption(
106 group.addoption(
100 "--doctest-ignore-import-errors",
107 "--ipdoctest-ignore-import-errors",
101 action="store_true",
108 action="store_true",
102 default=False,
109 default=False,
103 help="ignore doctest ImportErrors",
110 help="ignore ipdoctest ImportErrors",
104 dest="doctest_ignore_import_errors",
111 dest="ipdoctest_ignore_import_errors",
105 )
112 )
106 group.addoption(
113 group.addoption(
107 "--doctest-continue-on-failure",
114 "--ipdoctest-continue-on-failure",
108 action="store_true",
115 action="store_true",
109 default=False,
116 default=False,
110 help="for a given doctest, continue to run after the first failure",
117 help="for a given ipdoctest, continue to run after the first failure",
111 dest="doctest_continue_on_failure",
118 dest="ipdoctest_continue_on_failure",
112 )
119 )
113
120
114
121
@@ -119,15 +126,16 b' def pytest_unconfigure() -> None:'
119
126
120
127
121 def pytest_collect_file(
128 def pytest_collect_file(
122 path: py.path.local, parent: Collector,
129 path: py.path.local,
123 ) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
130 parent: Collector,
131 ) -> Optional[Union["IPDoctestModule", "IPDoctestTextfile"]]:
124 config = parent.config
132 config = parent.config
125 if path.ext == ".py":
133 if path.ext == ".py":
126 if config.option.doctestmodules and not _is_setup_py(path):
134 if config.option.ipdoctestmodules and not _is_setup_py(path):
127 mod: DoctestModule = DoctestModule.from_parent(parent, fspath=path)
135 mod: IPDoctestModule = IPDoctestModule.from_parent(parent, fspath=path)
128 return mod
136 return mod
129 elif _is_doctest(config, path, parent):
137 elif _is_ipdoctest(config, path, parent):
130 txt: DoctestTextfile = DoctestTextfile.from_parent(parent, fspath=path)
138 txt: IPDoctestTextfile = IPDoctestTextfile.from_parent(parent, fspath=path)
131 return txt
139 return txt
132 return None
140 return None
133
141
@@ -139,10 +147,10 b' def _is_setup_py(path: py.path.local) -> bool:'
139 return b"setuptools" in contents or b"distutils" in contents
147 return b"setuptools" in contents or b"distutils" in contents
140
148
141
149
142 def _is_doctest(config: Config, path: py.path.local, parent) -> bool:
150 def _is_ipdoctest(config: Config, path: py.path.local, parent) -> bool:
143 if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
151 if path.ext in (".txt", ".rst") and parent.session.isinitpath(path):
144 return True
152 return True
145 globs = config.getoption("doctestglob") or ["test*.txt"]
153 globs = config.getoption("ipdoctestglob") or ["test*.txt"]
146 for glob in globs:
154 for glob in globs:
147 if path.check(fnmatch=glob):
155 if path.check(fnmatch=glob):
148 return True
156 return True
@@ -168,10 +176,11 b' class MultipleDoctestFailures(Exception):'
168 self.failures = failures
176 self.failures = failures
169
177
170
178
171 def _init_runner_class() -> Type["doctest.DocTestRunner"]:
179 def _init_runner_class() -> Type["IPDocTestRunner"]:
172 import doctest
180 import doctest
181 from .ipdoctest import IPDocTestRunner
173
182
174 class PytestDoctestRunner(doctest.DebugRunner):
183 class PytestDoctestRunner(IPDocTestRunner):
175 """Runner to collect failures.
184 """Runner to collect failures.
176
185
177 Note that the out variable in this case is a list instead of a
186 Note that the out variable in this case is a list instead of a
@@ -180,18 +189,20 b' def _init_runner_class() -> Type["doctest.DocTestRunner"]:'
180
189
181 def __init__(
190 def __init__(
182 self,
191 self,
183 checker: Optional["doctest.OutputChecker"] = None,
192 checker: Optional["IPDoctestOutputChecker"] = None,
184 verbose: Optional[bool] = None,
193 verbose: Optional[bool] = None,
185 optionflags: int = 0,
194 optionflags: int = 0,
186 continue_on_failure: bool = True,
195 continue_on_failure: bool = True,
187 ) -> None:
196 ) -> None:
188 doctest.DebugRunner.__init__(
197 super().__init__(checker=checker, verbose=verbose, optionflags=optionflags)
189 self, checker=checker, verbose=verbose, optionflags=optionflags
190 )
191 self.continue_on_failure = continue_on_failure
198 self.continue_on_failure = continue_on_failure
192
199
193 def report_failure(
200 def report_failure(
194 self, out, test: "doctest.DocTest", example: "doctest.Example", got: str,
201 self,
202 out,
203 test: "doctest.DocTest",
204 example: "doctest.Example",
205 got: str,
195 ) -> None:
206 ) -> None:
196 failure = doctest.DocTestFailure(test, example, got)
207 failure = doctest.DocTestFailure(test, example, got)
197 if self.continue_on_failure:
208 if self.continue_on_failure:
@@ -220,11 +231,11 b' def _init_runner_class() -> Type["doctest.DocTestRunner"]:'
220
231
221
232
222 def _get_runner(
233 def _get_runner(
223 checker: Optional["doctest.OutputChecker"] = None,
234 checker: Optional["IPDoctestOutputChecker"] = None,
224 verbose: Optional[bool] = None,
235 verbose: Optional[bool] = None,
225 optionflags: int = 0,
236 optionflags: int = 0,
226 continue_on_failure: bool = True,
237 continue_on_failure: bool = True,
227 ) -> "doctest.DocTestRunner":
238 ) -> "IPDocTestRunner":
228 # We need this in order to do a lazy import on doctest
239 # We need this in order to do a lazy import on doctest
229 global RUNNER_CLASS
240 global RUNNER_CLASS
230 if RUNNER_CLASS is None:
241 if RUNNER_CLASS is None:
@@ -239,12 +250,12 b' def _get_runner('
239 )
250 )
240
251
241
252
242 class DoctestItem(pytest.Item):
253 class IPDoctestItem(pytest.Item):
243 def __init__(
254 def __init__(
244 self,
255 self,
245 name: str,
256 name: str,
246 parent: "Union[DoctestTextfile, DoctestModule]",
257 parent: "Union[IPDoctestTextfile, IPDoctestModule]",
247 runner: Optional["doctest.DocTestRunner"] = None,
258 runner: Optional["IPDocTestRunner"] = None,
248 dtest: Optional["doctest.DocTest"] = None,
259 dtest: Optional["doctest.DocTest"] = None,
249 ) -> None:
260 ) -> None:
250 super().__init__(name, parent)
261 super().__init__(name, parent)
@@ -256,10 +267,10 b' class DoctestItem(pytest.Item):'
256 @classmethod
267 @classmethod
257 def from_parent( # type: ignore
268 def from_parent( # type: ignore
258 cls,
269 cls,
259 parent: "Union[DoctestTextfile, DoctestModule]",
270 parent: "Union[IPDoctestTextfile, IPDoctestModule]",
260 *,
271 *,
261 name: str,
272 name: str,
262 runner: "doctest.DocTestRunner",
273 runner: "IPDocTestRunner",
263 dtest: "doctest.DocTest",
274 dtest: "doctest.DocTest",
264 ):
275 ):
265 # incompatible signature due to to imposed limits on sublcass
276 # incompatible signature due to to imposed limits on sublcass
@@ -271,25 +282,70 b' class DoctestItem(pytest.Item):'
271 self.fixture_request = _setup_fixtures(self)
282 self.fixture_request = _setup_fixtures(self)
272 globs = dict(getfixture=self.fixture_request.getfixturevalue)
283 globs = dict(getfixture=self.fixture_request.getfixturevalue)
273 for name, value in self.fixture_request.getfixturevalue(
284 for name, value in self.fixture_request.getfixturevalue(
274 "doctest_namespace"
285 "ipdoctest_namespace"
275 ).items():
286 ).items():
276 globs[name] = value
287 globs[name] = value
277 self.dtest.globs.update(globs)
288 self.dtest.globs.update(globs)
278
289
290 from .ipdoctest import IPExample
291
292 if isinstance(self.dtest.examples[0], IPExample):
293 # for IPython examples *only*, we swap the globals with the ipython
294 # namespace, after updating it with the globals (which doctest
295 # fills with the necessary info from the module being tested).
296 self._user_ns_orig = {}
297 self._user_ns_orig.update(_ip.user_ns)
298 _ip.user_ns.update(self.dtest.globs)
299 # We must remove the _ key in the namespace, so that Python's
300 # doctest code sets it naturally
301 _ip.user_ns.pop("_", None)
302 _ip.user_ns["__builtins__"] = builtins
303 self.dtest.globs = _ip.user_ns
304
305 def teardown(self) -> None:
306 from .ipdoctest import IPExample
307
308 # Undo the test.globs reassignment we made
309 if isinstance(self.dtest.examples[0], IPExample):
310 self.dtest.globs = {}
311 _ip.user_ns.clear()
312 _ip.user_ns.update(self._user_ns_orig)
313 del self._user_ns_orig
314
315 self.dtest.globs.clear()
316
279 def runtest(self) -> None:
317 def runtest(self) -> None:
280 assert self.dtest is not None
318 assert self.dtest is not None
281 assert self.runner is not None
319 assert self.runner is not None
282 _check_all_skipped(self.dtest)
320 _check_all_skipped(self.dtest)
283 self._disable_output_capturing_for_darwin()
321 self._disable_output_capturing_for_darwin()
284 failures: List["doctest.DocTestFailure"] = []
322 failures: List["doctest.DocTestFailure"] = []
285 # Type ignored because we change the type of `out` from what
323
286 # doctest expects.
324 # exec(compile(..., "single", ...), ...) puts result in builtins._
287 self.runner.run(self.dtest, out=failures) # type: ignore[arg-type]
325 had_underscore_value = hasattr(builtins, "_")
326 underscore_original_value = getattr(builtins, "_", None)
327
328 # Save our current directory and switch out to the one where the
329 # test was originally created, in case another doctest did a
330 # directory change. We'll restore this in the finally clause.
331 curdir = os.getcwd()
332 os.chdir(self.fspath.dirname)
333 try:
334 # Type ignored because we change the type of `out` from what
335 # ipdoctest expects.
336 self.runner.run(self.dtest, out=failures, clear_globs=False) # type: ignore[arg-type]
337 finally:
338 os.chdir(curdir)
339 if had_underscore_value:
340 setattr(builtins, "_", underscore_original_value)
341 elif hasattr(builtins, "_"):
342 delattr(builtins, "_")
343
288 if failures:
344 if failures:
289 raise MultipleDoctestFailures(failures)
345 raise MultipleDoctestFailures(failures)
290
346
291 def _disable_output_capturing_for_darwin(self) -> None:
347 def _disable_output_capturing_for_darwin(self) -> None:
292 """Disable output capturing. Otherwise, stdout is lost to doctest (#985)."""
348 """Disable output capturing. Otherwise, stdout is lost to ipdoctest (pytest#985)."""
293 if platform.system() != "Darwin":
349 if platform.system() != "Darwin":
294 return
350 return
295 capman = self.config.pluginmanager.getplugin("capturemanager")
351 capman = self.config.pluginmanager.getplugin("capturemanager")
@@ -301,13 +357,14 b' class DoctestItem(pytest.Item):'
301
357
302 # TODO: Type ignored -- breaks Liskov Substitution.
358 # TODO: Type ignored -- breaks Liskov Substitution.
303 def repr_failure( # type: ignore[override]
359 def repr_failure( # type: ignore[override]
304 self, excinfo: ExceptionInfo[BaseException],
360 self,
361 excinfo: ExceptionInfo[BaseException],
305 ) -> Union[str, TerminalRepr]:
362 ) -> Union[str, TerminalRepr]:
306 import doctest
363 import doctest
307
364
308 failures: Optional[
365 failures: Optional[
309 Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
366 Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
310 ] = (None)
367 ] = None
311 if isinstance(
368 if isinstance(
312 excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
369 excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
313 ):
370 ):
@@ -330,7 +387,7 b' class DoctestItem(pytest.Item):'
330 reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type]
387 reprlocation = ReprFileLocation(filename, lineno, message) # type: ignore[arg-type]
331 checker = _get_checker()
388 checker = _get_checker()
332 report_choice = _get_report_choice(
389 report_choice = _get_report_choice(
333 self.config.getoption("doctestreport")
390 self.config.getoption("ipdoctestreport")
334 )
391 )
335 if lineno is not None:
392 if lineno is not None:
336 assert failure.test.docstring is not None
393 assert failure.test.docstring is not None
@@ -369,7 +426,7 b' class DoctestItem(pytest.Item):'
369
426
370 def reportinfo(self):
427 def reportinfo(self):
371 assert self.dtest is not None
428 assert self.dtest is not None
372 return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
429 return self.fspath, self.dtest.lineno, "[ipdoctest] %s" % self.name
373
430
374
431
375 def _get_flag_lookup() -> Dict[str, int]:
432 def _get_flag_lookup() -> Dict[str, int]:
@@ -389,7 +446,7 b' def _get_flag_lookup() -> Dict[str, int]:'
389
446
390
447
391 def get_optionflags(parent):
448 def get_optionflags(parent):
392 optionflags_str = parent.config.getini("doctest_optionflags")
449 optionflags_str = parent.config.getini("ipdoctest_optionflags")
393 flag_lookup_table = _get_flag_lookup()
450 flag_lookup_table = _get_flag_lookup()
394 flag_acc = 0
451 flag_acc = 0
395 for flag in optionflags_str:
452 for flag in optionflags_str:
@@ -398,7 +455,7 b' def get_optionflags(parent):'
398
455
399
456
400 def _get_continue_on_failure(config):
457 def _get_continue_on_failure(config):
401 continue_on_failure = config.getvalue("doctest_continue_on_failure")
458 continue_on_failure = config.getvalue("ipdoctest_continue_on_failure")
402 if continue_on_failure:
459 if continue_on_failure:
403 # We need to turn off this if we use pdb since we should stop at
460 # We need to turn off this if we use pdb since we should stop at
404 # the first failure.
461 # the first failure.
@@ -407,15 +464,16 b' def _get_continue_on_failure(config):'
407 return continue_on_failure
464 return continue_on_failure
408
465
409
466
410 class DoctestTextfile(pytest.Module):
467 class IPDoctestTextfile(pytest.Module):
411 obj = None
468 obj = None
412
469
413 def collect(self) -> Iterable[DoctestItem]:
470 def collect(self) -> Iterable[IPDoctestItem]:
414 import doctest
471 import doctest
472 from .ipdoctest import IPDocTestParser
415
473
416 # Inspired by doctest.testfile; ideally we would use it directly,
474 # Inspired by doctest.testfile; ideally we would use it directly,
417 # but it doesn't support passing a custom checker.
475 # but it doesn't support passing a custom checker.
418 encoding = self.config.getini("doctest_encoding")
476 encoding = self.config.getini("ipdoctest_encoding")
419 text = self.fspath.read_text(encoding)
477 text = self.fspath.read_text(encoding)
420 filename = str(self.fspath)
478 filename = str(self.fspath)
421 name = self.fspath.basename
479 name = self.fspath.basename
@@ -430,10 +488,10 b' class DoctestTextfile(pytest.Module):'
430 continue_on_failure=_get_continue_on_failure(self.config),
488 continue_on_failure=_get_continue_on_failure(self.config),
431 )
489 )
432
490
433 parser = doctest.DocTestParser()
491 parser = IPDocTestParser()
434 test = parser.get_doctest(text, globs, name, filename, 0)
492 test = parser.get_doctest(text, globs, name, filename, 0)
435 if test.examples:
493 if test.examples:
436 yield DoctestItem.from_parent(
494 yield IPDoctestItem.from_parent(
437 self, name=test.name, runner=runner, dtest=test
495 self, name=test.name, runner=runner, dtest=test
438 )
496 )
439
497
@@ -487,12 +545,13 b' def _patch_unwrap_mock_aware() -> Generator[None, None, None]:'
487 inspect.unwrap = real_unwrap
545 inspect.unwrap = real_unwrap
488
546
489
547
490 class DoctestModule(pytest.Module):
548 class IPDoctestModule(pytest.Module):
491 def collect(self) -> Iterable[DoctestItem]:
549 def collect(self) -> Iterable[IPDoctestItem]:
492 import doctest
550 import doctest
551 from .ipdoctest import DocTestFinder, IPDocTestParser
493
552
494 class MockAwareDocTestFinder(doctest.DocTestFinder):
553 class MockAwareDocTestFinder(DocTestFinder):
495 """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug.
554 """A hackish ipdoctest finder that overrides stdlib internals to fix a stdlib bug.
496
555
497 https://github.com/pytest-dev/pytest/issues/3456
556 https://github.com/pytest-dev/pytest/issues/3456
498 https://bugs.python.org/issue25532
557 https://bugs.python.org/issue25532
@@ -507,8 +566,10 b' class DoctestModule(pytest.Module):'
507 if isinstance(obj, property):
566 if isinstance(obj, property):
508 obj = getattr(obj, "fget", obj)
567 obj = getattr(obj, "fget", obj)
509 # Type ignored because this is a private function.
568 # Type ignored because this is a private function.
510 return doctest.DocTestFinder._find_lineno( # type: ignore
569 return DocTestFinder._find_lineno( # type: ignore
511 self, obj, source_lines,
570 self,
571 obj,
572 source_lines,
512 )
573 )
513
574
514 def _find(
575 def _find(
@@ -519,7 +580,7 b' class DoctestModule(pytest.Module):'
519 with _patch_unwrap_mock_aware():
580 with _patch_unwrap_mock_aware():
520
581
521 # Type ignored because this is a private function.
582 # Type ignored because this is a private function.
522 doctest.DocTestFinder._find( # type: ignore
583 DocTestFinder._find( # type: ignore
523 self, tests, obj, name, module, source_lines, globs, seen
584 self, tests, obj, name, module, source_lines, globs, seen
524 )
585 )
525
586
@@ -531,12 +592,12 b' class DoctestModule(pytest.Module):'
531 try:
592 try:
532 module = import_path(self.fspath)
593 module = import_path(self.fspath)
533 except ImportError:
594 except ImportError:
534 if self.config.getvalue("doctest_ignore_import_errors"):
595 if self.config.getvalue("ipdoctest_ignore_import_errors"):
535 pytest.skip("unable to import module %r" % self.fspath)
596 pytest.skip("unable to import module %r" % self.fspath)
536 else:
597 else:
537 raise
598 raise
538 # Uses internal doctest module parsing mechanism.
599 # Uses internal doctest module parsing mechanism.
539 finder = MockAwareDocTestFinder()
600 finder = MockAwareDocTestFinder(parser=IPDocTestParser())
540 optionflags = get_optionflags(self)
601 optionflags = get_optionflags(self)
541 runner = _get_runner(
602 runner = _get_runner(
542 verbose=False,
603 verbose=False,
@@ -546,14 +607,14 b' class DoctestModule(pytest.Module):'
546 )
607 )
547
608
548 for test in finder.find(module, module.__name__):
609 for test in finder.find(module, module.__name__):
549 if test.examples: # skip empty doctests
610 if test.examples: # skip empty ipdoctests
550 yield DoctestItem.from_parent(
611 yield IPDoctestItem.from_parent(
551 self, name=test.name, runner=runner, dtest=test
612 self, name=test.name, runner=runner, dtest=test
552 )
613 )
553
614
554
615
555 def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:
616 def _setup_fixtures(doctest_item: IPDoctestItem) -> FixtureRequest:
556 """Used by DoctestTextfile and DoctestItem to setup fixture information."""
617 """Used by IPDoctestTextfile and IPDoctestItem to setup fixture information."""
557
618
558 def func() -> None:
619 def func() -> None:
559 pass
620 pass
@@ -568,11 +629,12 b' def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:'
568 return fixture_request
629 return fixture_request
569
630
570
631
571 def _init_checker_class() -> Type["doctest.OutputChecker"]:
632 def _init_checker_class() -> Type["IPDoctestOutputChecker"]:
572 import doctest
633 import doctest
573 import re
634 import re
635 from .ipdoctest import IPDoctestOutputChecker
574
636
575 class LiteralsOutputChecker(doctest.OutputChecker):
637 class LiteralsOutputChecker(IPDoctestOutputChecker):
576 # Based on doctest_nose_plugin.py from the nltk project
638 # Based on doctest_nose_plugin.py from the nltk project
577 # (https://github.com/nltk/nltk) and on the "numtest" doctest extension
639 # (https://github.com/nltk/nltk) and on the "numtest" doctest extension
578 # by Sebastien Boisgerault (https://github.com/boisgera/numtest).
640 # by Sebastien Boisgerault (https://github.com/boisgera/numtest).
@@ -603,7 +665,7 b' def _init_checker_class() -> Type["doctest.OutputChecker"]:'
603 )
665 )
604
666
605 def check_output(self, want: str, got: str, optionflags: int) -> bool:
667 def check_output(self, want: str, got: str, optionflags: int) -> bool:
606 if doctest.OutputChecker.check_output(self, want, got, optionflags):
668 if IPDoctestOutputChecker.check_output(self, want, got, optionflags):
607 return True
669 return True
608
670
609 allow_unicode = optionflags & _get_allow_unicode_flag()
671 allow_unicode = optionflags & _get_allow_unicode_flag()
@@ -627,7 +689,7 b' def _init_checker_class() -> Type["doctest.OutputChecker"]:'
627 if allow_number:
689 if allow_number:
628 got = self._remove_unwanted_precision(want, got)
690 got = self._remove_unwanted_precision(want, got)
629
691
630 return doctest.OutputChecker.check_output(self, want, got, optionflags)
692 return IPDoctestOutputChecker.check_output(self, want, got, optionflags)
631
693
632 def _remove_unwanted_precision(self, want: str, got: str) -> str:
694 def _remove_unwanted_precision(self, want: str, got: str) -> str:
633 wants = list(self._number_re.finditer(want))
695 wants = list(self._number_re.finditer(want))
@@ -659,18 +721,18 b' def _init_checker_class() -> Type["doctest.OutputChecker"]:'
659 return LiteralsOutputChecker
721 return LiteralsOutputChecker
660
722
661
723
662 def _get_checker() -> "doctest.OutputChecker":
724 def _get_checker() -> "IPDoctestOutputChecker":
663 """Return a doctest.OutputChecker subclass that supports some
725 """Return a IPDoctestOutputChecker subclass that supports some
664 additional options:
726 additional options:
665
727
666 * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
728 * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
667 prefixes (respectively) in string literals. Useful when the same
729 prefixes (respectively) in string literals. Useful when the same
668 doctest should run in Python 2 and Python 3.
730 ipdoctest should run in Python 2 and Python 3.
669
731
670 * NUMBER to ignore floating-point differences smaller than the
732 * NUMBER to ignore floating-point differences smaller than the
671 precision of the literal number in the doctest.
733 precision of the literal number in the ipdoctest.
672
734
673 An inner class is used to avoid importing "doctest" at the module
735 An inner class is used to avoid importing "ipdoctest" at the module
674 level.
736 level.
675 """
737 """
676 global CHECKER_CLASS
738 global CHECKER_CLASS
@@ -701,9 +763,9 b' def _get_number_flag() -> int:'
701
763
702
764
703 def _get_report_choice(key: str) -> int:
765 def _get_report_choice(key: str) -> int:
704 """Return the actual `doctest` module flag value.
766 """Return the actual `ipdoctest` module flag value.
705
767
706 We want to do it as late as possible to avoid importing `doctest` and all
768 We want to do it as late as possible to avoid importing `ipdoctest` and all
707 its dependencies when parsing options, as it adds overhead and breaks tests.
769 its dependencies when parsing options, as it adds overhead and breaks tests.
708 """
770 """
709 import doctest
771 import doctest
@@ -718,7 +780,7 b' def _get_report_choice(key: str) -> int:'
718
780
719
781
720 @pytest.fixture(scope="session")
782 @pytest.fixture(scope="session")
721 def doctest_namespace() -> Dict[str, Any]:
783 def ipdoctest_namespace() -> Dict[str, Any]:
722 """Fixture that returns a :py:class:`dict` that will be injected into the
784 """Fixture that returns a :py:class:`dict` that will be injected into the
723 namespace of doctests."""
785 namespace of ipdoctests."""
724 return dict()
786 return dict()
@@ -27,7 +27,7 b' install:'
27 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
27 - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
28 - python -m pip install --upgrade setuptools pip
28 - python -m pip install --upgrade setuptools pip
29 - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib pandas
29 - pip install nose coverage pytest pytest-cov pytest-trio pywin32 matplotlib pandas
30 - pip install .[test]
30 - pip install -e .[test]
31 - mkdir results
31 - mkdir results
32 - cd results
32 - cd results
33 test_script:
33 test_script:
@@ -1,2 +1,43 b''
1 [pytest]
1 [pytest]
2 addopts = --durations=10
2 addopts = --durations=10
3 -p IPython.testing.plugin.pytest_ipdoctest --ipdoctest-modules
4 --ignore=docs
5 --ignore=examples
6 --ignore=htmlcov
7 --ignore=ipython_kernel
8 --ignore=ipython_parallel
9 --ignore=results
10 --ignore=tmp
11 --ignore=tools
12 --ignore=traitlets
13 --ignore=IPython/core/tests/daft_extension
14 --ignore=IPython/sphinxext
15 --ignore=IPython/terminal/pt_inputhooks
16 --ignore=IPython/__main__.py
17 --ignore=IPython/config.py
18 --ignore=IPython/frontend.py
19 --ignore=IPython/html.py
20 --ignore=IPython/nbconvert.py
21 --ignore=IPython/nbformat.py
22 --ignore=IPython/parallel.py
23 --ignore=IPython/qt.py
24 --ignore=IPython/external/qt_for_kernel.py
25 --ignore=IPython/html/widgets/widget_link.py
26 --ignore=IPython/html/widgets/widget_output.py
27 --ignore=IPython/lib/inputhookglut.py
28 --ignore=IPython/lib/inputhookgtk.py
29 --ignore=IPython/lib/inputhookgtk3.py
30 --ignore=IPython/lib/inputhookgtk4.py
31 --ignore=IPython/lib/inputhookpyglet.py
32 --ignore=IPython/lib/inputhookqt4.py
33 --ignore=IPython/lib/inputhookwx.py
34 --ignore=IPython/terminal/console.py
35 --ignore=IPython/terminal/ptshell.py
36 --ignore=IPython/utils/_process_cli.py
37 --ignore=IPython/utils/_process_posix.py
38 --ignore=IPython/utils/_process_win32.py
39 --ignore=IPython/utils/_process_win32_controller.py
40 --ignore=IPython/utils/daemonize.py
41 --ignore=IPython/utils/eventful.py
42 doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
43 ipdoctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS
General Comments 0
You need to be logged in to leave comments. Login now