scipy-doctest 1.5.1__py3-none-any.whl → 1.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scipy_doctest/.ruff_cache/.gitignore +2 -0
- scipy_doctest/.ruff_cache/0.9.4/12495833369255784944 +0 -0
- scipy_doctest/.ruff_cache/CACHEDIR.TAG +1 -0
- scipy_doctest/__init__.py +1 -2
- scipy_doctest/conftest.py +0 -1
- scipy_doctest/frontend.py +18 -9
- scipy_doctest/impl.py +102 -35
- scipy_doctest/plugin.py +9 -9
- scipy_doctest/tests/failure_cases_2.py +0 -1
- scipy_doctest/tests/finder_cases.py +0 -2
- scipy_doctest/tests/finder_cases_2.py +22 -0
- scipy_doctest/tests/local_file_cases.py +0 -2
- scipy_doctest/tests/module_cases.py +0 -2
- scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst +2 -2
- scipy_doctest/tests/test_finder.py +62 -13
- scipy_doctest/tests/test_parser.py +0 -3
- scipy_doctest/tests/test_pytest_configuration.py +1 -2
- scipy_doctest/tests/test_runner.py +0 -1
- scipy_doctest/tests/test_skipmarkers.py +12 -7
- scipy_doctest/tests/test_testmod.py +1 -1
- scipy_doctest/util.py +2 -3
- {scipy_doctest-1.5.1.dist-info → scipy_doctest-1.7.dist-info}/METADATA +59 -45
- scipy_doctest-1.7.dist-info/RECORD +33 -0
- {scipy_doctest-1.5.1.dist-info → scipy_doctest-1.7.dist-info}/WHEEL +1 -1
- scipy_doctest-1.5.1.dist-info/RECORD +0 -29
- {scipy_doctest-1.5.1.dist-info → scipy_doctest-1.7.dist-info}/entry_points.txt +0 -0
- {scipy_doctest-1.5.1.dist-info → scipy_doctest-1.7.dist-info/licenses}/LICENSE +0 -0
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Signature: 8a477f597d28d172789f06886806bc55
|
scipy_doctest/__init__.py
CHANGED
|
@@ -3,7 +3,7 @@ Configurable, whitespace-insensitive, floating-point-aware doctest helpers.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
__version__ = "1.
|
|
6
|
+
__version__ = "1.7"
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
9
|
# register internal modules with pytest; obscure errors galore otherwise
|
|
@@ -19,4 +19,3 @@ except ModuleNotFoundError:
|
|
|
19
19
|
|
|
20
20
|
from .impl import DTChecker, DTFinder, DTParser, DTRunner, DebugDTRunner, DTConfig # noqa
|
|
21
21
|
from .frontend import testmod, testfile, find_doctests, run_docstring_examples # noqa
|
|
22
|
-
|
scipy_doctest/conftest.py
CHANGED
scipy_doctest/frontend.py
CHANGED
|
@@ -46,7 +46,7 @@ def find_doctests(module, strategy=None,
|
|
|
46
46
|
Returns
|
|
47
47
|
-------
|
|
48
48
|
tests : list
|
|
49
|
-
A list of `doctest.DocTest`s that are defined by the module docstring,
|
|
49
|
+
A list of `doctest.DocTest` s that are defined by the module docstring,
|
|
50
50
|
and by its contained objects’ docstrings. The selection is controlled
|
|
51
51
|
by the `strategy` argument.
|
|
52
52
|
|
|
@@ -90,14 +90,24 @@ def find_doctests(module, strategy=None,
|
|
|
90
90
|
# Having collected the list of objects, extract doctests
|
|
91
91
|
tests = []
|
|
92
92
|
for item, name in zip(items, names):
|
|
93
|
-
full_name = module.__name__ + '.' + name
|
|
94
93
|
if inspect.ismodule(item):
|
|
95
94
|
# do not recurse, only inspect the module docstring
|
|
96
95
|
_finder = DTFinder(recurse=False, config=config)
|
|
97
96
|
t = _finder.find(item, name, globs=globs, extraglobs=extraglobs)
|
|
97
|
+
unique_t = set(t)
|
|
98
98
|
else:
|
|
99
|
+
full_name = module.__name__ + '.' + name
|
|
99
100
|
t = finder.find(item, full_name, globs=globs, extraglobs=extraglobs)
|
|
100
|
-
|
|
101
|
+
|
|
102
|
+
unique_t = set(t)
|
|
103
|
+
if hasattr(item, '__mro__'):
|
|
104
|
+
# is a class, inspect superclasses
|
|
105
|
+
# cf https://github.com/scipy/scipy_doctest/issues/177
|
|
106
|
+
# item.__mro__ starts with itself, ends with `object`
|
|
107
|
+
for item_ in item.__mro__[1:-1]:
|
|
108
|
+
t_ = finder.find(item_, full_name, globs=globs, extraglobs=extraglobs)
|
|
109
|
+
unique_t.update(set(t_))
|
|
110
|
+
tests += list(unique_t)
|
|
101
111
|
|
|
102
112
|
# If the skiplist contains methods of objects, their doctests may have been
|
|
103
113
|
# left in the `tests` list. Remove them.
|
|
@@ -131,7 +141,7 @@ def testmod(m=None, name=None, globs=None, verbose=None,
|
|
|
131
141
|
In verbose mode, the summary is detailed, else very brief (in fact,
|
|
132
142
|
empty if all tests passed)
|
|
133
143
|
Default is True.
|
|
134
|
-
|
|
144
|
+
verbose : int
|
|
135
145
|
Control the run verbosity:
|
|
136
146
|
0 means only report failures,
|
|
137
147
|
1 means emit object names,
|
|
@@ -165,7 +175,7 @@ def testmod(m=None, name=None, globs=None, verbose=None,
|
|
|
165
175
|
(result, history)
|
|
166
176
|
`result` is a namedtuple ``TestResult(failed, attempted)``
|
|
167
177
|
`history` is a dict with details of which objects were examined (the
|
|
168
|
-
keys are object names and values are individual objects' ``TestResult``s)
|
|
178
|
+
keys are object names and values are individual objects' ``TestResult`` s)
|
|
169
179
|
|
|
170
180
|
Examples
|
|
171
181
|
--------
|
|
@@ -194,7 +204,7 @@ def testmod(m=None, name=None, globs=None, verbose=None,
|
|
|
194
204
|
For complex packages, prefer `strategy='api'`, which works as follows:
|
|
195
205
|
- take the names of public objects from the `__all__` attribute of the package.
|
|
196
206
|
- if `__all__` is not defined, take `dir(module)` and filter out names
|
|
197
|
-
|
|
207
|
+
which start with a leading underscore and dunders.
|
|
198
208
|
- filter out deprecated items, i.e. those which raise `DeprecationWarning`.
|
|
199
209
|
|
|
200
210
|
"""
|
|
@@ -296,7 +306,7 @@ def testfile(filename, module_relative=True, name=None, package=None,
|
|
|
296
306
|
In verbose mode, the summary is detailed, else very brief (in fact,
|
|
297
307
|
empty if all tests passed)
|
|
298
308
|
Default is True.
|
|
299
|
-
|
|
309
|
+
verbose : int
|
|
300
310
|
Control the run verbosity:
|
|
301
311
|
0 means only report failures,
|
|
302
312
|
1 means emit object names,
|
|
@@ -325,7 +335,7 @@ def testfile(filename, module_relative=True, name=None, package=None,
|
|
|
325
335
|
(result, history)
|
|
326
336
|
`result` is a namedtuple ``TestResult(failed, attempted)``
|
|
327
337
|
`history` is a dict with details of which objects were examined (the
|
|
328
|
-
keys are object names and values are individual objects' ``TestResult``s)
|
|
338
|
+
keys are object names and values are individual objects' ``TestResult`` s)
|
|
329
339
|
"""
|
|
330
340
|
# initial configuration
|
|
331
341
|
if config is None:
|
|
@@ -468,4 +478,3 @@ def _main():
|
|
|
468
478
|
if result.failed:
|
|
469
479
|
return 1
|
|
470
480
|
return 0
|
|
471
|
-
|
scipy_doctest/impl.py
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import warnings
|
|
3
|
+
import inspect
|
|
3
4
|
import doctest
|
|
4
5
|
from doctest import NORMALIZE_WHITESPACE, ELLIPSIS, IGNORE_EXCEPTION_DETAIL
|
|
5
6
|
from itertools import zip_longest
|
|
7
|
+
from sys import version_info
|
|
6
8
|
|
|
7
9
|
import numpy as np
|
|
8
10
|
|
|
9
11
|
from . import util
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
## shim numpy 1.x vs 2.0
|
|
13
|
-
if np.__version__ < "2":
|
|
14
|
-
VisibleDeprecationWarning = np.VisibleDeprecationWarning
|
|
15
|
-
else:
|
|
16
|
-
VisibleDeprecationWarning = np.exceptions.VisibleDeprecationWarning
|
|
17
|
-
|
|
18
|
-
|
|
19
13
|
# Register the optionflag to skip whole blocks, i.e.
|
|
20
14
|
# sequences of Examples without an intervening text.
|
|
21
15
|
SKIPBLOCK = doctest.register_optionflag('SKIPBLOCK')
|
|
@@ -67,6 +61,7 @@ class DTConfig:
|
|
|
67
61
|
>>> for test in tests:
|
|
68
62
|
... with user_context(test):
|
|
69
63
|
... runner.run(test)
|
|
64
|
+
|
|
70
65
|
Default is a noop.
|
|
71
66
|
local_resources: dict
|
|
72
67
|
If a test needs some local files, list them here. The format is
|
|
@@ -189,7 +184,8 @@ class DTConfig:
|
|
|
189
184
|
'.bar(', '.title', '.ylabel', '.xlabel', 'set_ylim', 'set_xlim',
|
|
190
185
|
'# reformatted', '.set_xlabel(', '.set_ylabel(', '.set_zlabel(',
|
|
191
186
|
'.set(xlim=', '.set(ylim=', '.set(xlabel=', '.set(ylabel=', '.xlim(',
|
|
192
|
-
'ax.set('
|
|
187
|
+
'ax.set(', '.text(',
|
|
188
|
+
}
|
|
193
189
|
self.stopwords = stopwords
|
|
194
190
|
|
|
195
191
|
if pseudocode is None:
|
|
@@ -223,8 +219,13 @@ class DTConfig:
|
|
|
223
219
|
|
|
224
220
|
|
|
225
221
|
def try_convert_namedtuple(got):
|
|
226
|
-
|
|
227
|
-
|
|
222
|
+
"""
|
|
223
|
+
Converts a string representation of a named tuple into a plain tuple.
|
|
224
|
+
|
|
225
|
+
Suppose that "got" is smth like MoodResult(statistic=10, pvalue=0.1).
|
|
226
|
+
Then convert it to the tuple (10, 0.1), so that can later compare tuples.
|
|
227
|
+
"""
|
|
228
|
+
|
|
228
229
|
num = got.count('=')
|
|
229
230
|
if num == 0:
|
|
230
231
|
# not a nameduple, bail out
|
|
@@ -263,6 +264,8 @@ def try_convert_printed_array(got):
|
|
|
263
264
|
|
|
264
265
|
|
|
265
266
|
def has_masked(got):
|
|
267
|
+
"""Check if a given string represents a NumPy masked array.
|
|
268
|
+
"""
|
|
266
269
|
return 'masked_array' in got and '--' in got
|
|
267
270
|
|
|
268
271
|
|
|
@@ -290,6 +293,20 @@ def try_split_shape_from_abbrv(s_got):
|
|
|
290
293
|
|
|
291
294
|
|
|
292
295
|
class DTChecker(doctest.OutputChecker):
|
|
296
|
+
"""
|
|
297
|
+
A drop-in replacement for `doctest.OutputChecker`.
|
|
298
|
+
|
|
299
|
+
Allows robust output comparison for numerical values and special
|
|
300
|
+
cases involving NumPy arrays, masked arrays, namedtuples, and object
|
|
301
|
+
memory addresses. It is configurable via a `DTConfig` object.
|
|
302
|
+
|
|
303
|
+
Parameters:
|
|
304
|
+
-----------
|
|
305
|
+
config : DTConfig, optional
|
|
306
|
+
Configuration object that controls various aspects of output checking.
|
|
307
|
+
If not provided, a default `DTConfig` instance is used.
|
|
308
|
+
|
|
309
|
+
"""
|
|
293
310
|
obj_pattern = re.compile(r'at 0x[0-9a-fA-F]+>')
|
|
294
311
|
vanilla = doctest.OutputChecker()
|
|
295
312
|
|
|
@@ -330,13 +347,8 @@ class DTChecker(doctest.OutputChecker):
|
|
|
330
347
|
# OK then, convert strings to objects
|
|
331
348
|
ns = dict(self.config.check_namespace)
|
|
332
349
|
try:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
# also array abbreviations: try `np.diag(np.arange(1000))`
|
|
336
|
-
warnings.simplefilter('ignore', VisibleDeprecationWarning)
|
|
337
|
-
|
|
338
|
-
a_want = eval(want, dict(ns))
|
|
339
|
-
a_got = eval(got, dict(ns))
|
|
350
|
+
a_want = eval(want, dict(ns))
|
|
351
|
+
a_got = eval(got, dict(ns))
|
|
340
352
|
except Exception:
|
|
341
353
|
# Maybe we're printing a numpy array? This produces invalid python
|
|
342
354
|
# code: `print(np.arange(3))` produces "[0 1 2]" w/o commas between
|
|
@@ -350,9 +362,9 @@ class DTChecker(doctest.OutputChecker):
|
|
|
350
362
|
s_got = try_convert_printed_array(s_got)
|
|
351
363
|
|
|
352
364
|
return self.check_output(s_want, s_got, optionflags)
|
|
353
|
-
|
|
365
|
+
|
|
354
366
|
#handle array abbreviation for n-dimensional arrays, n >= 1
|
|
355
|
-
ndim_array = (s_want.startswith("array([") and "..." in s_want and
|
|
367
|
+
ndim_array = (s_want.startswith("array([") and "..." in s_want and
|
|
356
368
|
s_got.startswith("array([") and "..." in s_got)
|
|
357
369
|
if ndim_array:
|
|
358
370
|
s_want, want_shape = try_split_shape_from_abbrv(s_want)
|
|
@@ -431,16 +443,31 @@ class DTChecker(doctest.OutputChecker):
|
|
|
431
443
|
except Exception:
|
|
432
444
|
pass
|
|
433
445
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
warnings.simplefilter('ignore', VisibleDeprecationWarning)
|
|
437
|
-
|
|
438
|
-
# This line is the crux of the whole thing. The rest is mostly scaffolding.
|
|
439
|
-
result = np.allclose(want, got, atol=self.atol, rtol=self.rtol, equal_nan=True)
|
|
446
|
+
# This line is the crux of the whole thing. The rest is mostly scaffolding.
|
|
447
|
+
result = np.allclose(want, got, atol=self.atol, rtol=self.rtol, equal_nan=True)
|
|
440
448
|
return result
|
|
441
449
|
|
|
442
450
|
|
|
443
451
|
class DTRunner(doctest.DocTestRunner):
|
|
452
|
+
"""
|
|
453
|
+
A drop-in replacement for `doctest.DocTestRunner`.
|
|
454
|
+
|
|
455
|
+
Improves how test names are reported and allows better control over exception handling.
|
|
456
|
+
It integrates with `DTConfig` to apply customized settings for doctest execution.
|
|
457
|
+
|
|
458
|
+
Parameters:
|
|
459
|
+
-----------
|
|
460
|
+
checker : doctest.OutputChecker, optional
|
|
461
|
+
A custom output checker, defaults to `DTConfig.CheckerKlass(config)`.
|
|
462
|
+
verbose : bool, optional
|
|
463
|
+
If `True`, enables verbose output.
|
|
464
|
+
optionflags : int, optional
|
|
465
|
+
Bitwise OR of `doctest` option flags; defaults to `DTConfig.optionflags`.
|
|
466
|
+
config : DTConfig, optional
|
|
467
|
+
A configuration object controlling doctest behavior; a default instance is used if not provided.
|
|
468
|
+
|
|
469
|
+
"""
|
|
470
|
+
|
|
444
471
|
DIVIDER = "\n"
|
|
445
472
|
|
|
446
473
|
def __init__(self, checker=None, verbose=None, optionflags=None, config=None):
|
|
@@ -488,15 +515,20 @@ class DTRunner(doctest.DocTestRunner):
|
|
|
488
515
|
def get_history(self):
|
|
489
516
|
"""Return a dict with names of items which were run.
|
|
490
517
|
|
|
491
|
-
Actually the dict is `{name : (
|
|
492
|
-
|
|
493
|
-
|
|
518
|
+
Actually the dict is `{name : (failures, tries, skips)}` (before Python
|
|
519
|
+
3.13, just `{name : (failures, tries, skips)}`) where `name` is the
|
|
520
|
+
name of an object, and the value is a tuple of the numbers of examples
|
|
521
|
+
which failed, were tried, and were skipped, respectively.
|
|
494
522
|
"""
|
|
495
|
-
|
|
523
|
+
if version_info >= (3, 13):
|
|
524
|
+
return self._stats
|
|
525
|
+
else:
|
|
526
|
+
return self._name2ft
|
|
496
527
|
|
|
497
528
|
|
|
498
529
|
class DebugDTRunner(DTRunner):
|
|
499
|
-
"""
|
|
530
|
+
"""
|
|
531
|
+
Doctest runner which raises an exception on the first error.
|
|
500
532
|
|
|
501
533
|
Almost verbatim copy of `doctest.DebugRunner`.
|
|
502
534
|
"""
|
|
@@ -520,8 +552,24 @@ class DebugDTRunner(DTRunner):
|
|
|
520
552
|
|
|
521
553
|
|
|
522
554
|
class DTFinder(doctest.DocTestFinder):
|
|
523
|
-
"""A Finder with helpful defaults.
|
|
524
555
|
"""
|
|
556
|
+
A drop-in replacement for `doctest.DocTestFinder` with helpful defaults.
|
|
557
|
+
|
|
558
|
+
Parameters:
|
|
559
|
+
-----------
|
|
560
|
+
verbose : bool, optional
|
|
561
|
+
If `True`, enables verbose output during doctest discovery.
|
|
562
|
+
parser : doctest.DocTestParser, optional
|
|
563
|
+
A custom parser for extracting doctests; defaults to `DTParser(config)`.
|
|
564
|
+
recurse : bool, default=True
|
|
565
|
+
Whether to recursively search for doctests in nested objects.
|
|
566
|
+
exclude_empty : bool, default=True
|
|
567
|
+
Whether to exclude objects that have no doctests.
|
|
568
|
+
config : DTConfig, optional
|
|
569
|
+
A configuration object controlling doctest behavior; a default instance is used if not provided.
|
|
570
|
+
|
|
571
|
+
"""
|
|
572
|
+
|
|
525
573
|
def __init__(self, verbose=None, parser=None, recurse=True,
|
|
526
574
|
exclude_empty=True, config=None):
|
|
527
575
|
if config is None:
|
|
@@ -536,14 +584,34 @@ class DTFinder(doctest.DocTestFinder):
|
|
|
536
584
|
if globs is None:
|
|
537
585
|
globs = dict(self.config.default_namespace)
|
|
538
586
|
# XXX: does this make similar checks in testmod/testfile duplicate?
|
|
539
|
-
if module not in self.config.skiplist:
|
|
587
|
+
if module not in self.config.skiplist:
|
|
540
588
|
tests = super().find(obj, name, module, globs, extraglobs)
|
|
589
|
+
|
|
590
|
+
if inspect.isclass(obj):
|
|
591
|
+
for name_, method in inspect.getmembers(obj):
|
|
592
|
+
if inspect.isdatadescriptor(method):
|
|
593
|
+
tests += super().find(
|
|
594
|
+
method, f'{name}.{name_}', module, globs, extraglobs
|
|
595
|
+
)
|
|
541
596
|
return tests
|
|
542
597
|
|
|
543
598
|
|
|
544
599
|
class DTParser(doctest.DocTestParser):
|
|
545
|
-
"""A Parser with a stopword list.
|
|
546
600
|
"""
|
|
601
|
+
A drop-in replacement for `doctest.DocTestParser` with a stopword list.
|
|
602
|
+
|
|
603
|
+
It filters out stopwords, pseudocode, and random markers from doctests.
|
|
604
|
+
|
|
605
|
+
Parameters:
|
|
606
|
+
-----------
|
|
607
|
+
config : DTConfig, optional
|
|
608
|
+
A configuration object containing:
|
|
609
|
+
- `stopwords`: A list of words that signal a doctest should be ignored.
|
|
610
|
+
- `pseudocode`: A list of markers indicating non-executable code.
|
|
611
|
+
- `rndm_markers`: A list of markers indicating results may vary.
|
|
612
|
+
|
|
613
|
+
"""
|
|
614
|
+
|
|
547
615
|
def __init__(self, config=None):
|
|
548
616
|
if config is None:
|
|
549
617
|
config = DTConfig()
|
|
@@ -600,4 +668,3 @@ class DTParser(doctest.DocTestParser):
|
|
|
600
668
|
example.want += " # _ignore\n"
|
|
601
669
|
examples.append(example)
|
|
602
670
|
return examples
|
|
603
|
-
|
scipy_doctest/plugin.py
CHANGED
|
@@ -47,7 +47,7 @@ def pytest_ignore_collect(collection_path, config):
|
|
|
47
47
|
"""
|
|
48
48
|
Determine whether to ignore the specified collection path.
|
|
49
49
|
This function is used to exclude the 'tests' directory and test modules when
|
|
50
|
-
the
|
|
50
|
+
the `--doctest-modules` option is used.
|
|
51
51
|
"""
|
|
52
52
|
if config.getoption("--doctest-modules"):
|
|
53
53
|
path_str = str(collection_path)
|
|
@@ -62,7 +62,7 @@ def pytest_ignore_collect(collection_path, config):
|
|
|
62
62
|
def is_private(item):
|
|
63
63
|
"""Decide if an DocTestItem `item` is private.
|
|
64
64
|
|
|
65
|
-
Private items are ignored in pytest_collect_modifyitem`.
|
|
65
|
+
Private items are ignored in `pytest_collect_modifyitem`.
|
|
66
66
|
"""
|
|
67
67
|
# Here we look at the name of a test module/object. A seemingly less
|
|
68
68
|
# hacky alternative is to populate a set of seen `item.dtest` attributes
|
|
@@ -174,7 +174,7 @@ def _is_deprecated(module):
|
|
|
174
174
|
class DTModule(DoctestModule):
|
|
175
175
|
"""
|
|
176
176
|
This class extends the DoctestModule class provided by pytest.
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
DTModule is responsible for overriding the behavior of the collect method.
|
|
179
179
|
The collect method is called by pytest to collect and generate test items for doctests
|
|
180
180
|
in the specified module or file.
|
|
@@ -222,7 +222,7 @@ class DTModule(DoctestModule):
|
|
|
222
222
|
|
|
223
223
|
optionflags = dt_config.optionflags
|
|
224
224
|
|
|
225
|
-
# Plug in the custom runner: `PytestDTRunner`
|
|
225
|
+
# Plug in the custom runner: `PytestDTRunner`
|
|
226
226
|
runner = _get_runner(self.config,
|
|
227
227
|
verbose=False,
|
|
228
228
|
optionflags=optionflags,
|
|
@@ -245,7 +245,7 @@ class DTModule(DoctestModule):
|
|
|
245
245
|
class DTTextfile(DoctestTextfile):
|
|
246
246
|
"""
|
|
247
247
|
This class extends the DoctestTextfile class provided by pytest.
|
|
248
|
-
|
|
248
|
+
|
|
249
249
|
DTTextfile is responsible for overriding the behavior of the collect method.
|
|
250
250
|
The collect method is called by pytest to collect and generate test items for doctests
|
|
251
251
|
in the specified text files.
|
|
@@ -282,7 +282,7 @@ class DTTextfile(DoctestTextfile):
|
|
|
282
282
|
def _get_runner(config, verbose, optionflags):
|
|
283
283
|
"""
|
|
284
284
|
Override function to return an instance of PytestDTRunner.
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
This function creates and returns an instance of PytestDTRunner, a custom runner class
|
|
287
287
|
that extends the behavior of DebugDTRunner for running doctests in pytest.
|
|
288
288
|
"""
|
|
@@ -290,13 +290,13 @@ def _get_runner(config, verbose, optionflags):
|
|
|
290
290
|
def run(self, test, compileflags=None, out=None, clear_globs=False):
|
|
291
291
|
"""
|
|
292
292
|
Run tests in context managers.
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
Restore the errstate/print state after each docstring.
|
|
295
295
|
Also, make MPL backend non-GUI and close the figures.
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
The order of context managers is actually relevant. Consider
|
|
298
298
|
user_context_mgr that turns warnings into errors.
|
|
299
|
-
|
|
299
|
+
|
|
300
300
|
Additionally, suppose that MPL deprecates something and plt.something
|
|
301
301
|
starts issuing warnings. Now all of those become errors
|
|
302
302
|
*unless* the `mpl()` context mgr has a chance to filter them out
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Private method in subclasses
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__all__ = ["Klass"]
|
|
6
|
+
|
|
7
|
+
class _PrivateKlass:
|
|
8
|
+
def private_method(self):
|
|
9
|
+
"""
|
|
10
|
+
>>> 2 / 3
|
|
11
|
+
0.667
|
|
12
|
+
"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Klass(_PrivateKlass):
|
|
17
|
+
def public_method(self):
|
|
18
|
+
"""
|
|
19
|
+
>>> 3 / 4
|
|
20
|
+
0.74
|
|
21
|
+
"""
|
|
22
|
+
pass
|
|
@@ -215,7 +215,7 @@ Smoothing filters
|
|
|
215
215
|
corresponds to convolution with a Gaussian kernel. An order of 1, 2,
|
|
216
216
|
or 3 corresponds to convolution with the first, second, or third
|
|
217
217
|
derivatives of a Gaussian. Higher-order derivatives are not
|
|
218
|
-
implemented.
|
|
218
|
+
implemented.
|
|
219
219
|
|
|
220
220
|
|
|
221
221
|
|
|
@@ -232,7 +232,7 @@ Smoothing filters
|
|
|
232
232
|
number, to specify the same order for all axes, or a sequence of
|
|
233
233
|
numbers to specify a different order for each axis. The example below
|
|
234
234
|
shows the filter applied on test data with different values of *sigma*.
|
|
235
|
-
The *order* parameter is kept at 0.
|
|
235
|
+
The *order* parameter is kept at 0.
|
|
236
236
|
|
|
237
237
|
.. plot:: tutorial/examples/gaussian_filter_plot1.py
|
|
238
238
|
:align: center
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
3
5
|
from . import finder_cases
|
|
6
|
+
from . import finder_cases_2
|
|
4
7
|
from ..util import get_all_list, get_public_objects
|
|
5
8
|
from ..impl import DTFinder, DTConfig
|
|
6
9
|
from ..frontend import find_doctests
|
|
7
10
|
|
|
11
|
+
|
|
8
12
|
def test_get_all_list():
|
|
9
13
|
items, depr, other = get_all_list(finder_cases)
|
|
10
14
|
assert sorted(items) == ['Klass', 'func']
|
|
@@ -82,8 +86,8 @@ class TestSkiplist:
|
|
|
82
86
|
assert sorted(names) == sorted(wanted_names)
|
|
83
87
|
|
|
84
88
|
def test_get_doctests_strategy_None(self):
|
|
85
|
-
# Add a skiplist: strategy=None skips listed items
|
|
86
|
-
base = finder_cases.__name__
|
|
89
|
+
# Add a skiplist: strategy=None skips listed items
|
|
90
|
+
base = finder_cases.__name__
|
|
87
91
|
skips = [base + '.func', base + '.Klass.meth_2']
|
|
88
92
|
config = DTConfig(skiplist=skips)
|
|
89
93
|
|
|
@@ -98,8 +102,8 @@ class TestSkiplist:
|
|
|
98
102
|
assert sorted(names) == sorted(wanted_names)
|
|
99
103
|
|
|
100
104
|
def test_get_doctests_strategy_api(self):
|
|
101
|
-
# Add a skiplist: strategy='api' skips listed items
|
|
102
|
-
base = finder_cases.__name__
|
|
105
|
+
# Add a skiplist: strategy='api' skips listed items
|
|
106
|
+
base = finder_cases.__name__
|
|
103
107
|
skips = [base + '.func', base + '.Klass.meth_2']
|
|
104
108
|
config = DTConfig(skiplist=skips)
|
|
105
109
|
|
|
@@ -111,12 +115,13 @@ class TestSkiplist:
|
|
|
111
115
|
# - *private* stuff, which is not in `__all__`
|
|
112
116
|
wanted_names = ['Klass', 'Klass.meth']
|
|
113
117
|
wanted_names = [base] + [base + '.' + n for n in wanted_names]
|
|
118
|
+
wanted_names += [f'{base}.Klass.__weakref__']
|
|
114
119
|
|
|
115
120
|
assert sorted(names) == sorted(wanted_names)
|
|
116
121
|
|
|
117
122
|
def test_get_doctests_strategy_list(self):
|
|
118
|
-
# Add a skiplist: strategy=<list> skips listed items
|
|
119
|
-
base = finder_cases.__name__
|
|
123
|
+
# Add a skiplist: strategy=<list> skips listed items
|
|
124
|
+
base = finder_cases.__name__
|
|
120
125
|
skips = [base + '.func', base + '.Klass.meth_2']
|
|
121
126
|
config = DTConfig(skiplist=skips)
|
|
122
127
|
|
|
@@ -129,6 +134,7 @@ class TestSkiplist:
|
|
|
129
134
|
# - the 'base' module (via the strategy=<list>)
|
|
130
135
|
wanted_names = ['Klass', 'Klass.meth']
|
|
131
136
|
wanted_names = [base + '.' + n for n in wanted_names]
|
|
137
|
+
wanted_names += [f'{base}.Klass.__weakref__']
|
|
132
138
|
|
|
133
139
|
assert sorted(names) == sorted(wanted_names)
|
|
134
140
|
|
|
@@ -137,9 +143,13 @@ def test_explicit_object_list():
|
|
|
137
143
|
objs = [finder_cases.Klass]
|
|
138
144
|
tests = find_doctests(finder_cases, strategy=objs)
|
|
139
145
|
|
|
146
|
+
names = sorted([test.name for test in tests])
|
|
147
|
+
|
|
140
148
|
base = 'scipy_doctest.tests.finder_cases'
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
expected = sorted([f'{base}.Klass', f'{base}.Klass.__weakref__',
|
|
150
|
+
f'{base}.Klass.meth', f'{base}.Klass.meth_2',])
|
|
151
|
+
|
|
152
|
+
assert names == expected
|
|
143
153
|
|
|
144
154
|
|
|
145
155
|
def test_explicit_object_list_with_module():
|
|
@@ -149,22 +159,61 @@ def test_explicit_object_list_with_module():
|
|
|
149
159
|
objs = [finder_cases, finder_cases.Klass]
|
|
150
160
|
tests = find_doctests(finder_cases, strategy=objs)
|
|
151
161
|
|
|
162
|
+
names = sorted([test.name for test in tests])
|
|
163
|
+
|
|
152
164
|
base = 'scipy_doctest.tests.finder_cases'
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
expected = sorted([base, f'{base}.Klass', f'{base}.Klass.__weakref__',
|
|
166
|
+
f'{base}.Klass.meth', f'{base}.Klass.meth_2'])
|
|
167
|
+
|
|
168
|
+
assert names == expected
|
|
155
169
|
|
|
156
170
|
|
|
157
171
|
def test_find_doctests_api():
|
|
158
172
|
# Test that the module itself is included with strategy='api'
|
|
159
173
|
tests = find_doctests(finder_cases, strategy='api')
|
|
160
174
|
|
|
175
|
+
names = sorted([test.name for test in tests])
|
|
176
|
+
|
|
161
177
|
base = 'scipy_doctest.tests.finder_cases'
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
178
|
+
expected = sorted([base + '.func', base + '.Klass', base + '.Klass.meth',
|
|
179
|
+
base + '.Klass.meth_2', base + '.Klass.__weakref__', base])
|
|
180
|
+
|
|
181
|
+
assert names == expected
|
|
165
182
|
|
|
166
183
|
|
|
167
184
|
def test_dtfinder_config():
|
|
168
185
|
config = DTConfig()
|
|
169
186
|
finder = DTFinder(config=config)
|
|
170
187
|
assert finder.config is config
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.mark.skipif(np.__version__ < '2', reason="XXX check if works on numpy 1.x")
|
|
191
|
+
def test_descriptors_get_collected():
|
|
192
|
+
tests = find_doctests(np, strategy=[np.dtype])
|
|
193
|
+
names = [test.name for test in tests]
|
|
194
|
+
assert 'numpy.dtype.kind' in names # was previously missing
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.parametrize('strategy', [None, 'api'])
|
|
198
|
+
def test_private_superclasses(strategy):
|
|
199
|
+
# Test that methods from inherited private superclasses get collected
|
|
200
|
+
tests = find_doctests(finder_cases_2, strategy=strategy)
|
|
201
|
+
|
|
202
|
+
names = set(test.name.split('.')[-1] for test in tests)
|
|
203
|
+
expected_names = ['finder_cases_2', 'public_method', 'private_method']
|
|
204
|
+
if strategy == 'api':
|
|
205
|
+
expected_names += ['__weakref__']
|
|
206
|
+
|
|
207
|
+
assert len(tests) == len(expected_names)
|
|
208
|
+
assert names == set(expected_names)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_private_superclasses_2():
|
|
212
|
+
# similar to test_private_superclass, only with an explicit strategy=list
|
|
213
|
+
tests = find_doctests(finder_cases_2, strategy=[finder_cases_2.Klass])
|
|
214
|
+
|
|
215
|
+
names = set(test.name.split('.')[-1] for test in tests)
|
|
216
|
+
expected_names = ['public_method', 'private_method', '__weakref__']
|
|
217
|
+
|
|
218
|
+
assert len(tests) == len(expected_names)
|
|
219
|
+
assert names == set(expected_names)
|
|
@@ -39,7 +39,7 @@ def test_failure_cases(pytester):
|
|
|
39
39
|
result = pytester.inline_run(python_file, "--doctest-modules")
|
|
40
40
|
assert result.ret == pytest.ExitCode.TESTS_FAILED
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
@pytest.mark.skipif(not HAVE_MATPLOTLIB, reason='need matplotlib')
|
|
44
44
|
def test_stopword_cases(pytester):
|
|
45
45
|
"""Test that pytest uses the DTParser for doctests."""
|
|
@@ -90,4 +90,3 @@ def test_alt_checker(pytester):
|
|
|
90
90
|
# run all tests with pytest
|
|
91
91
|
result = pytester.inline_run(f, '--doctest-modules')
|
|
92
92
|
assert result.ret == pytest.ExitCode.TESTS_FAILED
|
|
93
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import doctest
|
|
2
|
+
from sys import version_info
|
|
2
3
|
|
|
3
4
|
try:
|
|
4
5
|
import matplotlib.pyplot as plt # noqa
|
|
@@ -57,7 +58,8 @@ class TestSyntaxErrors:
|
|
|
57
58
|
lineno=0)
|
|
58
59
|
runner = DebugDTRunner()
|
|
59
60
|
runner.run(test)
|
|
60
|
-
|
|
61
|
+
stats = (0, 1, 1) if version_info >= (3, 13) else (0, 0)
|
|
62
|
+
assert runner.get_history() == {'none : +SKIP': stats}
|
|
61
63
|
|
|
62
64
|
def test_invalid_python_pseudocode(self):
|
|
63
65
|
# Marking a test as pseudocode is equivalent to a +SKIP:
|
|
@@ -74,7 +76,8 @@ class TestSyntaxErrors:
|
|
|
74
76
|
lineno=0)
|
|
75
77
|
runner = DebugDTRunner()
|
|
76
78
|
runner.run(test)
|
|
77
|
-
|
|
79
|
+
stats = (0, 1, 1) if version_info >= (3, 13) else (0, 0)
|
|
80
|
+
assert runner.get_history() == {'none : pseudocode': stats}
|
|
78
81
|
|
|
79
82
|
|
|
80
83
|
class TestPseudocodeMarkers:
|
|
@@ -125,7 +128,8 @@ class TestStopwords:
|
|
|
125
128
|
runner.run(test)
|
|
126
129
|
|
|
127
130
|
# one example tried, of which zero failed
|
|
128
|
-
|
|
131
|
+
stats = (0, 1, 0) if version_info >= (3, 13) else (0, 1)
|
|
132
|
+
assert runner.get_history() == {'stopwords_bogus_output': stats}
|
|
129
133
|
|
|
130
134
|
|
|
131
135
|
class TestMayVary:
|
|
@@ -148,7 +152,8 @@ class TestMayVary:
|
|
|
148
152
|
runner.run(test)
|
|
149
153
|
|
|
150
154
|
# one example tried, of which zero failed
|
|
151
|
-
|
|
155
|
+
stats = (0, 1, 0) if version_info >= (3, 13) else (0, 1)
|
|
156
|
+
assert runner.get_history() == {'may_vary_markers': stats}
|
|
152
157
|
|
|
153
158
|
def test_may_vary_source(self):
|
|
154
159
|
# The marker needs to be added to the example output, not source.
|
|
@@ -164,7 +169,8 @@ class TestMayVary:
|
|
|
164
169
|
runner.run(test)
|
|
165
170
|
|
|
166
171
|
# one example tried, of which zero failed
|
|
167
|
-
|
|
172
|
+
stats = (0, 1, 0) if version_info >= (3, 13) else (0, 1)
|
|
173
|
+
assert runner.get_history() == {'may_vary_source': stats}
|
|
168
174
|
|
|
169
175
|
def test_may_vary_syntax_error(self):
|
|
170
176
|
# `# may vary` markers do not mask syntax errors, unlike `# doctest: +SKIP`
|
|
@@ -193,7 +199,7 @@ below
|
|
|
193
199
|
|
|
194
200
|
Note how the block above will fail doctesting unless the second line is
|
|
195
201
|
skipped. A standard solution is to add a +SKIP marker to every line, but this
|
|
196
|
-
is ugly and we skip the whole block instead.
|
|
202
|
+
is ugly and we skip the whole block instead.
|
|
197
203
|
|
|
198
204
|
Once the block is over, we get back to usual doctests, which are not skipped
|
|
199
205
|
|
|
@@ -216,4 +222,3 @@ def test_SKIPBLOCK():
|
|
|
216
222
|
assert test.examples[0].options[SKIP] is True
|
|
217
223
|
assert test.examples[1].options[SKIP] is True
|
|
218
224
|
assert test.examples[2].options == {} # not skipped
|
|
219
|
-
|
scipy_doctest/util.py
CHANGED
|
@@ -260,9 +260,9 @@ modules = []
|
|
|
260
260
|
def generate_log(module, test):
|
|
261
261
|
"""
|
|
262
262
|
Generate a log of the doctested items.
|
|
263
|
-
|
|
263
|
+
|
|
264
264
|
This function logs the items being doctested to a file named 'doctest.log'.
|
|
265
|
-
|
|
265
|
+
|
|
266
266
|
Args:
|
|
267
267
|
module (module): The module being doctested.
|
|
268
268
|
test (str): The name of the doctest item.
|
|
@@ -276,4 +276,3 @@ def generate_log(module, test):
|
|
|
276
276
|
LOGFILE.write(f"{test}\n")
|
|
277
277
|
except AttributeError:
|
|
278
278
|
LOGFILE.write(f"{test}\n")
|
|
279
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: scipy_doctest
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7
|
|
4
4
|
Summary: Configurable, whitespace-insensitive, floating-point-aware doctest helpers.
|
|
5
5
|
Maintainer-email: SciPy developers <scipy-dev@python.org>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -10,20 +10,36 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
10
10
|
Classifier: Intended Audience :: Developers
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Framework :: Pytest
|
|
13
|
+
License-File: LICENSE
|
|
13
14
|
Requires-Dist: numpy>=1.19.5
|
|
14
15
|
Requires-Dist: pytest
|
|
15
|
-
Requires-Dist:
|
|
16
|
+
Requires-Dist: furo==2024.8.6 ; extra == "doc"
|
|
17
|
+
Requires-Dist: myst-parser==4.0.0 ; extra == "doc"
|
|
18
|
+
Requires-Dist: sphinx==8.1.3 ; extra == "doc"
|
|
19
|
+
Requires-Dist: sphinx-copybutton==0.5.2 ; extra == "doc"
|
|
20
|
+
Requires-Dist: scipy <= 1.14.1 ; extra == "test"
|
|
16
21
|
Requires-Dist: matplotlib ; extra == "test"
|
|
17
22
|
Project-URL: Home, https://github.com/scipy/scipy_doctest
|
|
23
|
+
Provides-Extra: doc
|
|
18
24
|
Provides-Extra: test
|
|
19
25
|
|
|
20
26
|
# Floating-point aware, human readable, numpy-compatible doctesting.
|
|
21
27
|
|
|
28
|
+
[![PyPI version][pypi-version]][pypi-link]
|
|
29
|
+
[![Conda-Forge][conda-badge]][conda-link]
|
|
30
|
+
|
|
31
|
+
<!-- prettier-ignore-start -->
|
|
32
|
+
[conda-badge]: https://img.shields.io/conda/vn/conda-forge/scipy-doctest
|
|
33
|
+
[conda-link]: https://anaconda.org/conda-forge/scipy-doctest
|
|
34
|
+
[pypi-link]: https://pypi.org/project/scipy-doctest/
|
|
35
|
+
[pypi-version]: https://img.shields.io/pypi/v/scipy-doctest
|
|
36
|
+
<!-- prettier-ignore-end -->
|
|
37
|
+
|
|
22
38
|
## TL;DR
|
|
23
39
|
|
|
24
40
|
This project extends the standard library `doctest` module to allow flexibility
|
|
25
41
|
and easy customization of finding, parsing and checking code examples in
|
|
26
|
-
documentation.
|
|
42
|
+
documentation.
|
|
27
43
|
|
|
28
44
|
Can be used either as drop-in `doctest` replacement or through the `pytest`
|
|
29
45
|
integration. Uses a floating-point aware doctest checker by default.
|
|
@@ -31,7 +47,7 @@ integration. Uses a floating-point aware doctest checker by default.
|
|
|
31
47
|
## Motivation and scope
|
|
32
48
|
|
|
33
49
|
Having examples in the documentation is great. Having wrong examples in the
|
|
34
|
-
documentation is not that great however.
|
|
50
|
+
documentation is not that great however.
|
|
35
51
|
|
|
36
52
|
The standard library `doctest` module is great for making sure that docstring
|
|
37
53
|
examples are correct. However, the `doctest` module is limited in several
|
|
@@ -46,7 +62,7 @@ This looks reasonably clear but does not work, in three different ways.
|
|
|
46
62
|
_First_, `1/3` is not equal to 0.333 because floating-point arithmetic.
|
|
47
63
|
_Second_, `numpy` adds whitespace to its output, this whitespace confuses the
|
|
48
64
|
`doctest`, which is whitespace-sensitive. Therefore, we added a magic directive,
|
|
49
|
-
|
|
65
|
+
`+SKIP` to avoid a doctest error. _Third_, the example is actually
|
|
50
66
|
wrong---notice `0.669` which is not equal to `2/3` to three sig figs. The error
|
|
51
67
|
went unnoticed by the doctester also because of the `+SKIP` directive.
|
|
52
68
|
|
|
@@ -56,44 +72,44 @@ a human reader, and should not be present in the documentation.
|
|
|
56
72
|
This package defines modified doctesting routines which fix these deficiencies.
|
|
57
73
|
Its main features are
|
|
58
74
|
|
|
59
|
-
-
|
|
75
|
+
- _Doctesting is floating-point aware._ In a nutshell, the core check is
|
|
60
76
|
`np.allclose(want, got, atol=..., rtol=...)`, with user-controllable abs
|
|
61
77
|
and relative tolerances. In the example above (_sans_ `# doctest: +SKIP`),
|
|
62
78
|
`want` is the desired output, `array([0.333, 0.669, 1])` and `got` is the
|
|
63
79
|
actual output from numpy: `array([0.33333333, 0.66666667, 1. ])`.
|
|
64
80
|
|
|
65
|
-
-
|
|
81
|
+
- _Human-readable skip markers._ Consider
|
|
66
82
|
```
|
|
67
83
|
>>> np.random.randint(100)
|
|
68
84
|
42 # may vary
|
|
69
85
|
```
|
|
70
|
-
Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
|
|
71
|
-
to either an example's output, or its source.
|
|
86
|
+
Note that the markers (by default, `"# may vary"` and `"# random"`) can be applied
|
|
87
|
+
to either an example's output, or its source.
|
|
72
88
|
|
|
73
89
|
Also note a difference with respect to the standard `# doctest: +SKIP`: the latter
|
|
74
90
|
skips the example entirely, while these additional markers only skip checking
|
|
75
91
|
the output. Thus the example source needs to be valid python code still.
|
|
76
92
|
|
|
77
|
-
- A user-configurable list of
|
|
93
|
+
- A user-configurable list of _stopwords_. If an example contains a stopword,
|
|
78
94
|
it is checked to be valid python, but the output is not checked. This can
|
|
79
95
|
be useful e.g. for not littering the documentation with the output of
|
|
80
96
|
`import matplotlib.pyplot as plt; plt.xlim([2.3, 4.5])`.
|
|
81
97
|
|
|
82
|
-
- A user-configurable list of
|
|
98
|
+
- A user-configurable list of _pseudocode_ markers. If an example contains one
|
|
83
99
|
of these markers, it is considered pseudocode and is not checked.
|
|
84
100
|
This is useful for `from example import some_functions` and similar stanzas.
|
|
85
101
|
|
|
86
102
|
- A `# doctest: +SKIPBLOCK` option flag to skip whole blocks of pseudocode. Here
|
|
87
103
|
a 'block' is a sequence of doctest examples without any intervening text.
|
|
88
104
|
|
|
89
|
-
-
|
|
105
|
+
- _Doctest discovery_ is somewhat more flexible then the standard library
|
|
90
106
|
`doctest` module. Specifically, one can use `testmod(module, strategy='api')`
|
|
91
107
|
to only examine public objects of a module. This is helpful for complex
|
|
92
108
|
packages, with non-trivial internal file structure. Alternatively, the default
|
|
93
109
|
value of `strategy=None` is equivalent to the standard `doctest` module
|
|
94
110
|
behavior.
|
|
95
111
|
|
|
96
|
-
-
|
|
112
|
+
- _User configuration_. Essentially all aspects of the behavior are user
|
|
97
113
|
configurable via a `DTConfig` instance attributes. See the `DTConfig`
|
|
98
114
|
docstring for details.
|
|
99
115
|
|
|
@@ -124,7 +140,7 @@ pip install scipy-doctest
|
|
|
124
140
|
|
|
125
141
|
2. **Register or load the plugin**
|
|
126
142
|
|
|
127
|
-
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
|
|
143
|
+
Next, you need to register or load the pytest plugin within your test module or `conftest.py` file.
|
|
128
144
|
|
|
129
145
|
To do this, add the following line of code:
|
|
130
146
|
|
|
@@ -136,14 +152,16 @@ pytest_plugins = "scipy_doctest"
|
|
|
136
152
|
|
|
137
153
|
Check out the [pytest documentation](https://docs.pytest.org/en/stable/how-to/writing_plugins.html#requiring-loading-plugins-in-a-test-module-or-conftest-file) for more information on requiring/loading plugins in a test module or `conftest.py` file.
|
|
138
154
|
|
|
139
|
-
3. **Run doctests**
|
|
155
|
+
3. **Run doctests**
|
|
140
156
|
|
|
141
157
|
Once the plugin is registered, run the doctests by executing the following command:
|
|
142
158
|
|
|
143
159
|
```bash
|
|
144
160
|
$ python -m pytest --doctest-modules
|
|
145
161
|
```
|
|
162
|
+
|
|
146
163
|
or
|
|
164
|
+
|
|
147
165
|
```bash
|
|
148
166
|
$ pytest --pyargs <your-package> --doctest-modules
|
|
149
167
|
```
|
|
@@ -155,10 +173,9 @@ use the command flag
|
|
|
155
173
|
$ pytest --pyargs <your-package> --doctest-modules --doctest-collect=api
|
|
156
174
|
```
|
|
157
175
|
|
|
158
|
-
See [More fine-grained control](
|
|
176
|
+
See [More fine-grained control](#more-fine-grained-control) section
|
|
159
177
|
for details on how to customize the behavior.
|
|
160
178
|
|
|
161
|
-
|
|
162
179
|
### Basic usage
|
|
163
180
|
|
|
164
181
|
The use of `pytest` is optional, and you can use the `doctest` layer API.
|
|
@@ -171,6 +188,7 @@ For example,
|
|
|
171
188
|
>>> res
|
|
172
189
|
TestResults(failed=0, attempted=764)
|
|
173
190
|
```
|
|
191
|
+
|
|
174
192
|
The second return value, `hist` is a dict which maps the names of the objects
|
|
175
193
|
to the numbers of failures and attempts for individual examples.
|
|
176
194
|
|
|
@@ -178,10 +196,10 @@ For more details, see the `testmod` docstring. Other useful functions are
|
|
|
178
196
|
`find_doctests`, `run_docstring_examples` and `testfile` (the latter two mimic
|
|
179
197
|
the behavior of the eponymous functions of the `doctest` module).
|
|
180
198
|
|
|
181
|
-
|
|
182
199
|
### Command-line interface
|
|
183
200
|
|
|
184
201
|
There is a basic CLI, which also mimics that of the `doctest` module:
|
|
202
|
+
|
|
185
203
|
```
|
|
186
204
|
$ python -m scipy_doctest foo.py
|
|
187
205
|
```
|
|
@@ -190,28 +208,30 @@ Note that, just like `$ python -m doctest foo.py`, this may
|
|
|
190
208
|
fail if `foo.py` is a part of a package due to package imports.
|
|
191
209
|
|
|
192
210
|
Text files can also be CLI-checked:
|
|
211
|
+
|
|
193
212
|
```
|
|
194
213
|
$ python -m scipy_doctest bar.rst
|
|
195
214
|
```
|
|
196
215
|
|
|
197
216
|
Notice that the command-line usage only uses the default `DTConfig` settings.
|
|
198
217
|
|
|
218
|
+
(more-fine-grained-control)=
|
|
199
219
|
|
|
200
220
|
## More fine-grained control
|
|
201
221
|
|
|
202
222
|
More fine-grained control of the functionality is available via the following
|
|
203
223
|
classes
|
|
204
224
|
|
|
205
|
-
|
|
|
206
|
-
|
|
207
|
-
| `DTChecker` | `DocTestChecker`
|
|
208
|
-
| `DTParser` | `DocTestParser`
|
|
209
|
-
| `DTRunner` | `DocTestRunner`
|
|
210
|
-
| `DTFinder` | `DocTestFinder`
|
|
211
|
-
| `DTContext` |
|
|
225
|
+
| Class | `doctest` analog |
|
|
226
|
+
| ----------- | ---------------- |
|
|
227
|
+
| `DTChecker` | `DocTestChecker` |
|
|
228
|
+
| `DTParser` | `DocTestParser` |
|
|
229
|
+
| `DTRunner` | `DocTestRunner` |
|
|
230
|
+
| `DTFinder` | `DocTestFinder` |
|
|
231
|
+
| `DTContext` | -- |
|
|
212
232
|
|
|
213
233
|
The `DTContext` class is just a bag class which holds various configuration
|
|
214
|
-
settings as attributes.
|
|
234
|
+
settings as attributes. An instance of this class is passed around, so user
|
|
215
235
|
configuration is simply creating an instance, overriding an attribute and
|
|
216
236
|
passing the instance to `testmod` or constructors of `DT*` objects. Defaults
|
|
217
237
|
are provided, based on a long-term usage in SciPy.
|
|
@@ -220,7 +240,7 @@ See the [DTConfig docstring](https://github.com/scipy/scipy_doctest/blob/main/sc
|
|
|
220
240
|
for the full set of attributes that allow you to fine-tune your doctesting experience.
|
|
221
241
|
|
|
222
242
|
To set any of these attributes, create an instance of `DTConfig` and assign the attributes
|
|
223
|
-
in a usual way.
|
|
243
|
+
in a usual way.
|
|
224
244
|
|
|
225
245
|
If using the pytest plugin, it is convenient to use the default instance, which
|
|
226
246
|
is predefined in `scipy_doctest/conftest.py`. This instance will be automatically
|
|
@@ -260,14 +280,12 @@ dt_config.skiplist = {
|
|
|
260
280
|
|
|
261
281
|
If you don't set these attributes, the [default settings](https://github.com/scipy/scipy_doctest/blob/58ff06a837b7bff1dbac6560013fc6fd07952ae2/scipy_doctest/impl.py#L94) of the attributes are used.
|
|
262
282
|
|
|
263
|
-
|
|
264
283
|
#### Alternative Checkers
|
|
265
284
|
|
|
266
285
|
By default, we use the floating-point aware `DTChecker`. If you want to use an
|
|
267
286
|
alternative checker, all you need to do is to define the corresponding class,
|
|
268
287
|
and add an attribute to the `DTConfig` instance. For example,
|
|
269
288
|
|
|
270
|
-
|
|
271
289
|
```
|
|
272
290
|
class VanillaOutputChecker(doctest.OutputChecker):
|
|
273
291
|
"""doctest.OutputChecker to drop in for DTChecker.
|
|
@@ -290,10 +308,8 @@ See [a pytest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_do
|
|
|
290
308
|
and [a doctest example](https://github.com/scipy/scipy_doctest/blob/main/scipy_doctest/tests/test_runner.py#L94)
|
|
291
309
|
for more details.
|
|
292
310
|
|
|
293
|
-
|
|
294
311
|
### NumPy and SciPy wrappers
|
|
295
312
|
|
|
296
|
-
|
|
297
313
|
NumPy wraps `scipy-doctest` with the `spin` command
|
|
298
314
|
|
|
299
315
|
```
|
|
@@ -307,13 +323,11 @@ $ python dev.py smoke-docs # check docstrings
|
|
|
307
323
|
$ python dev.py smoke-tutorials # ReST user guide tutorials
|
|
308
324
|
```
|
|
309
325
|
|
|
310
|
-
|
|
311
|
-
|
|
312
326
|
## Rough edges and sharp bits
|
|
313
327
|
|
|
314
328
|
Here is a (non-exhaustive) list of possible gotchas:
|
|
315
329
|
|
|
316
|
-
-
|
|
330
|
+
- _In-place development builds_.
|
|
317
331
|
|
|
318
332
|
Some tools (looking at you `meson-python`) simulate in-place builds with a
|
|
319
333
|
`build-install` directory. If this directory is located under the project root,
|
|
@@ -329,12 +343,15 @@ $ pytest build-install/lib/python3.10/site-packages/scipy/ --doctest-modules
|
|
|
329
343
|
|
|
330
344
|
instead of `$ pytest --pyargs scipy`.
|
|
331
345
|
|
|
332
|
-
If
|
|
346
|
+
If you use actual editable installs, of the `pip install --no-build-isolation -e .` variety, you may
|
|
347
|
+
need to add `--import-mode=importlib` to the `pytest` invocation.
|
|
348
|
+
|
|
349
|
+
If push really comes to shove, you may try using the magic env variable:
|
|
333
350
|
` PY_IGNORE_IMPORTMISMATCH=1 pytest ...`,
|
|
334
351
|
however the need usually indicates an issue with the package itself.
|
|
335
352
|
(see [gh-107](https://github.com/scipy/scipy_doctest/pull/107) for an example).
|
|
336
353
|
|
|
337
|
-
-
|
|
354
|
+
- _Optional dependencies are not that optional_
|
|
338
355
|
|
|
339
356
|
If your package contains optional dependencies, doctests do not know about them
|
|
340
357
|
being optional. So you either guard the imports in doctests (yikes!), or
|
|
@@ -353,11 +370,11 @@ Note that installed packages are no different:
|
|
|
353
370
|
$ pytest --pyargs scipy --doctest-modules --ignore=/path/to/installed/scipy/_lib
|
|
354
371
|
```
|
|
355
372
|
|
|
356
|
-
-
|
|
373
|
+
- _Doctest collection strategies_
|
|
357
374
|
|
|
358
375
|
The default collection strategy follows `doctest` module and `pytest`. This leads
|
|
359
376
|
to duplicates if your package has the split between public and \_private modules,
|
|
360
|
-
where
|
|
377
|
+
where public modules re-export things from private ones. The solution is to
|
|
361
378
|
use `$ pytest --doctest-collect=api` CLI switch: with this, only public
|
|
362
379
|
objects will be collected.
|
|
363
380
|
|
|
@@ -375,8 +392,7 @@ leads to
|
|
|
375
392
|
- `scipy.linalg._basic.det`, collected from `scipy/linalg/_basic.py`, is private.
|
|
376
393
|
- `scipy.linalg.det`, collected from `scipy/linalg/__init__.py`, is public.
|
|
377
394
|
|
|
378
|
-
|
|
379
|
-
- *`pytest`'s assertion rewriting*
|
|
395
|
+
- _`pytest`'s assertion rewriting_
|
|
380
396
|
|
|
381
397
|
In some rare cases you may need to either explicitly register the `scipy_doctest`
|
|
382
398
|
package with the `pytest` assertion rewriting machinery, or ask it to avoid rewriting
|
|
@@ -388,7 +404,6 @@ In general, rewriting assertions is not very useful for doctests, as the
|
|
|
388
404
|
output on error is fixed by the doctest machinery anyway. Therefore, we believe
|
|
389
405
|
adding `--assert=plain` is reasonable.
|
|
390
406
|
|
|
391
|
-
|
|
392
407
|
## Prior art and related work
|
|
393
408
|
|
|
394
409
|
- `pytest` provides some limited floating-point aware `NumericLiteralChecker`.
|
|
@@ -409,15 +424,14 @@ adding `--assert=plain` is reasonable.
|
|
|
409
424
|
to be not easy to reason about, work with, and extend to other projects.
|
|
410
425
|
|
|
411
426
|
This project is mainly the core functionality of the modified
|
|
412
|
-
`refguide-check` doctesting, extracted to a separate package.
|
|
427
|
+
`refguide-check` doctesting, extracted to a separate package.
|
|
413
428
|
We believe having it separate simplifies both addressing the needs of these
|
|
414
429
|
two packages, and potential adoption by other projects.
|
|
415
430
|
|
|
416
|
-
|
|
417
431
|
### Bug reports, feature requests and contributions
|
|
418
432
|
|
|
419
433
|
This package is work in progress. Contributions are most welcome!
|
|
420
434
|
Please don't hesitate to open an issue in the tracker or send a pull request.
|
|
421
435
|
|
|
422
|
-
The current location of the issue tracker is https://github.com/scipy/scipy_doctest
|
|
436
|
+
The current location of the issue tracker is <https://github.com/scipy/scipy_doctest>.
|
|
423
437
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
scipy_doctest/__init__.py,sha256=KwjnUgFMzyn5AXQBP39C0zuq2JQOiT1aNDXajOu3APs,648
|
|
2
|
+
scipy_doctest/__main__.py,sha256=H8jTO13GlOLzexbgu7lHMJW1y3_NhLOSArARFZ5WS7o,90
|
|
3
|
+
scipy_doctest/conftest.py,sha256=rOkdrpmq95vzvMGMqm88c4HTrbpxLOvANtEuiD6suCg,52
|
|
4
|
+
scipy_doctest/frontend.py,sha256=wl0-8I6epZ49TwxKVSuZbizS6J_pxea99z0vAv2EdbY,19126
|
|
5
|
+
scipy_doctest/impl.py,sha256=kp8W2p7mTvQR-TwYc2EXA3JiXo1Z9t98XDzFsoOUVSc,26480
|
|
6
|
+
scipy_doctest/plugin.py,sha256=8g2Jyoa69ezQ22RrVBcD42HGNZ6z3oqaPITBDHzJSiY,12852
|
|
7
|
+
scipy_doctest/util.py,sha256=NQaJWfpUCqB8khIRJnTZz8ktKFqd0xT2sipFlv4RIhA,8048
|
|
8
|
+
scipy_doctest/.ruff_cache/.gitignore,sha256=njpg8ebsSuYCFcEdVLFxOSdF7CXp3e1DPVvZITY68xY,35
|
|
9
|
+
scipy_doctest/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
|
|
10
|
+
scipy_doctest/.ruff_cache/0.9.4/12495833369255784944,sha256=w5fjZj_--UXMu8x8t41NSb7Od5tJMg9juUSF1Po7VV0,1442
|
|
11
|
+
scipy_doctest/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
scipy_doctest/tests/failure_cases.py,sha256=ReSRSugjKDNoWO5M4h-vwCbX_7BwkktlwEjFiGWI3F4,732
|
|
13
|
+
scipy_doctest/tests/failure_cases_2.py,sha256=p2Qg_eKh35-gavFS25hRNkYW22w8kwNRlAg0Vrq4MtM,783
|
|
14
|
+
scipy_doctest/tests/finder_cases.py,sha256=-aWcIyKsW1GAOEhuqEYUVYGLYsVi2FU8HgZG2x7Nmyc,870
|
|
15
|
+
scipy_doctest/tests/finder_cases_2.py,sha256=Z5pnvVQKk3Z8bWpnQyBSnM02qk-80R0doi7DYUbMBaU,306
|
|
16
|
+
scipy_doctest/tests/local_file.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
scipy_doctest/tests/local_file_cases.py,sha256=8gIEwIJOwwWo0ii2I2gMbcg_8ZhH5XoWqqRFWxnVpd8,912
|
|
18
|
+
scipy_doctest/tests/module_cases.py,sha256=kApmKDYImEHbgm-hiCSG7HpDzHhvwqOM8d1J9Dq7Gk4,5827
|
|
19
|
+
scipy_doctest/tests/octave_a.mat,sha256=lOfXBSOdMG7_kruTnOHjixXkPy3zSUt10A1FSVjfngI,288
|
|
20
|
+
scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst,sha256=gw5YfCNuq1I-Pwu2XUYlOFLg4Qk8E0n9A8APXmcBh1w,81592
|
|
21
|
+
scipy_doctest/tests/stopwords_cases.py,sha256=OEZkoFW3B9nHUCG_5adSkLI91avSwNjw-NeUS0D6Yz0,156
|
|
22
|
+
scipy_doctest/tests/test_finder.py,sha256=8upZaULq8LhhQQyM1eriKhg2I48eGXm3cSJ0s7SkXzw,7769
|
|
23
|
+
scipy_doctest/tests/test_parser.py,sha256=jF32p7PDrxKrdMdX91VTkkjiMhYrjCbPkxWq1RKMryA,922
|
|
24
|
+
scipy_doctest/tests/test_pytest_configuration.py,sha256=Fou_WthuWwh0q-o2zZD92HLPwbWOjxcJAV_s_ymbP20,2568
|
|
25
|
+
scipy_doctest/tests/test_runner.py,sha256=cXDY51lRP7fG-5C_6Y69e8fFsB6mLr0NWauH4sCOWHs,2935
|
|
26
|
+
scipy_doctest/tests/test_skipmarkers.py,sha256=dLcoy1F0e1GrLLAi2CqJQLapFB8uUBoqkTT-n5oFWbY,8182
|
|
27
|
+
scipy_doctest/tests/test_testfile.py,sha256=66ZHUpEGGg8MfQT8EKSZ8Zx5pV55gP__TZejGMYwBHA,733
|
|
28
|
+
scipy_doctest/tests/test_testmod.py,sha256=We1LBz3rKD5VDkcB8ssQehEr5aQRy2riBJwGwJWGgzs,5870
|
|
29
|
+
scipy_doctest-1.7.dist-info/entry_points.txt,sha256=dFda3z6PjFL7pEWokv_QmoLwE8X1HETCY1H60xopQ-s,47
|
|
30
|
+
scipy_doctest-1.7.dist-info/licenses/LICENSE,sha256=xH5PVX8bm8e1JxkmJ-e5FsZsOa7FsNOMfepmCvMoR9g,1523
|
|
31
|
+
scipy_doctest-1.7.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
32
|
+
scipy_doctest-1.7.dist-info/METADATA,sha256=72XdHKgkfz2s6ewsg8sn-eibj25d4vktcz-fLrzvLHo,15711
|
|
33
|
+
scipy_doctest-1.7.dist-info/RECORD,,
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
scipy_doctest/__init__.py,sha256=-RFBEAnfRqgEUJmvLL5MBc6HfuyqetcTQgPsRcVFREU,651
|
|
2
|
-
scipy_doctest/__main__.py,sha256=H8jTO13GlOLzexbgu7lHMJW1y3_NhLOSArARFZ5WS7o,90
|
|
3
|
-
scipy_doctest/conftest.py,sha256=5vZxzuH042urYIToiKWANDJHQPoGkfQI-ppQ_cuHgM0,53
|
|
4
|
-
scipy_doctest/frontend.py,sha256=7Vz9VIRmzdkmwPD1OtI9sJSxCXIF7jmSxXyt6asjaI0,18628
|
|
5
|
-
scipy_doctest/impl.py,sha256=NC61Qy-4VXd3Vjk9WXyTf0oPcm6ZdIiv7WxeVSffTrw,24083
|
|
6
|
-
scipy_doctest/plugin.py,sha256=DzTPBCDIre5b2e8JLiTGw7d9zhCP9hYqXcxeKpFTtys,12900
|
|
7
|
-
scipy_doctest/util.py,sha256=R-pS9FSL5hQNmOA0nhRDLOL1riFVAoK-OhG70ilaKhw,8057
|
|
8
|
-
scipy_doctest/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
scipy_doctest/tests/failure_cases.py,sha256=ReSRSugjKDNoWO5M4h-vwCbX_7BwkktlwEjFiGWI3F4,732
|
|
10
|
-
scipy_doctest/tests/failure_cases_2.py,sha256=gupqwSICvzurGIiKVNJRJX9jkmtFR7_Rf3snWU-4Nac,784
|
|
11
|
-
scipy_doctest/tests/finder_cases.py,sha256=s4sq5HZ7m5mXEi1N8dbkgCRY6AxEGcirFRYhEyEG7rw,872
|
|
12
|
-
scipy_doctest/tests/local_file.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
scipy_doctest/tests/local_file_cases.py,sha256=xQeD809EoLxY-QIb0X7ZPl6Ecyme3zjnf95AHJ_8gp0,914
|
|
14
|
-
scipy_doctest/tests/module_cases.py,sha256=W3u97oMJ8yf0rqrsiQbB1yMMTkKAVz4gNpBLDhijHek,5860
|
|
15
|
-
scipy_doctest/tests/octave_a.mat,sha256=lOfXBSOdMG7_kruTnOHjixXkPy3zSUt10A1FSVjfngI,288
|
|
16
|
-
scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst,sha256=uCtH99RQM0hvDK2_5lgBO2hqtMUOUQhPKxWn4zwHjSs,81594
|
|
17
|
-
scipy_doctest/tests/stopwords_cases.py,sha256=OEZkoFW3B9nHUCG_5adSkLI91avSwNjw-NeUS0D6Yz0,156
|
|
18
|
-
scipy_doctest/tests/test_finder.py,sha256=9bBa3OvB8aTPixMkAFAPlCul7Bm7J95GKri0npOMp9M,6136
|
|
19
|
-
scipy_doctest/tests/test_parser.py,sha256=cmK5kXqTWPUdSVor4bPu6yoikIukDIkVXjIjk1TTPI8,925
|
|
20
|
-
scipy_doctest/tests/test_pytest_configuration.py,sha256=2aesrRyHFdMcRMjARXJ5g8nMtaYC0gb_48Mh8U58RzE,2573
|
|
21
|
-
scipy_doctest/tests/test_runner.py,sha256=qP4u8ngbUK946HhMM6Py70hi0W0DcZGcCN258phhM7g,2936
|
|
22
|
-
scipy_doctest/tests/test_skipmarkers.py,sha256=C5U8BKF3Ti-nPWekt2yK6a3j3Tcg_pq-Z5IuiR-F9eU,7835
|
|
23
|
-
scipy_doctest/tests/test_testfile.py,sha256=66ZHUpEGGg8MfQT8EKSZ8Zx5pV55gP__TZejGMYwBHA,733
|
|
24
|
-
scipy_doctest/tests/test_testmod.py,sha256=PoOI0o2_dXjWDmDkUUKqcJiyZvh46YpjlmMxyW6PXyI,5874
|
|
25
|
-
scipy_doctest-1.5.1.dist-info/entry_points.txt,sha256=dFda3z6PjFL7pEWokv_QmoLwE8X1HETCY1H60xopQ-s,47
|
|
26
|
-
scipy_doctest-1.5.1.dist-info/LICENSE,sha256=xH5PVX8bm8e1JxkmJ-e5FsZsOa7FsNOMfepmCvMoR9g,1523
|
|
27
|
-
scipy_doctest-1.5.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
28
|
-
scipy_doctest-1.5.1.dist-info/METADATA,sha256=r7zprKH8IAsQde_mAkhp8SOYdlpV6iUc9pttsYu7wu4,14869
|
|
29
|
-
scipy_doctest-1.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|