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