scipy-doctest 1.1__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/__init__.py +12 -0
- scipy_doctest/__main__.py +6 -0
- scipy_doctest/conftest.py +5 -0
- scipy_doctest/frontend.py +468 -0
- scipy_doctest/impl.py +506 -0
- scipy_doctest/plugin.py +336 -0
- scipy_doctest/tests/__init__.py +0 -0
- scipy_doctest/tests/failure_cases.py +17 -0
- scipy_doctest/tests/failure_cases_2.py +27 -0
- scipy_doctest/tests/finder_cases.py +65 -0
- scipy_doctest/tests/local_file.txt +0 -0
- scipy_doctest/tests/local_file_cases.py +37 -0
- scipy_doctest/tests/module_cases.py +192 -0
- scipy_doctest/tests/octave_a.mat +0 -0
- scipy_doctest/tests/scipy_ndimage_tutorial_clone.rst +2018 -0
- scipy_doctest/tests/stopwords_cases.py +9 -0
- scipy_doctest/tests/test_finder.py +170 -0
- scipy_doctest/tests/test_parser.py +36 -0
- scipy_doctest/tests/test_pytest_configuration.py +95 -0
- scipy_doctest/tests/test_runner.py +105 -0
- scipy_doctest/tests/test_skipmarkers.py +202 -0
- scipy_doctest/tests/test_testfile.py +24 -0
- scipy_doctest/tests/test_testmod.py +158 -0
- scipy_doctest/util.py +279 -0
- scipy_doctest-1.1.dist-info/LICENSE +29 -0
- scipy_doctest-1.1.dist-info/METADATA +390 -0
- scipy_doctest-1.1.dist-info/RECORD +29 -0
- scipy_doctest-1.1.dist-info/WHEEL +4 -0
- scipy_doctest-1.1.dist-info/entry_points.txt +3 -0
scipy_doctest/plugin.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A pytest plugin that provides enhanced doctesting for Pydata libraries
|
|
3
|
+
"""
|
|
4
|
+
import bdb
|
|
5
|
+
import warnings
|
|
6
|
+
import doctest
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import _pytest
|
|
10
|
+
from _pytest import doctest as pydoctest, outcomes
|
|
11
|
+
from _pytest.doctest import DoctestModule, DoctestTextfile
|
|
12
|
+
from _pytest.pathlib import import_path
|
|
13
|
+
|
|
14
|
+
from .impl import DTParser, DebugDTRunner
|
|
15
|
+
from .conftest import dt_config
|
|
16
|
+
from .util import np_errstate, matplotlib_make_nongui, temp_cwd
|
|
17
|
+
from .frontend import find_doctests
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def pytest_addoption(parser):
|
|
21
|
+
group = parser.getgroup("collect")
|
|
22
|
+
|
|
23
|
+
group.addoption(
|
|
24
|
+
"--doctest-collect",
|
|
25
|
+
action="store",
|
|
26
|
+
default="None",
|
|
27
|
+
help="Doctest collection strategy: vanilla pytest ('None', default), or 'api'",
|
|
28
|
+
choices=("None", "api"),
|
|
29
|
+
dest="collection_strategy"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def pytest_configure(config):
|
|
34
|
+
"""
|
|
35
|
+
Perform initial configuration for the pytest plugin.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Create a dt config attribute within pytest's config object for easy access.
|
|
39
|
+
config.dt_config = dt_config
|
|
40
|
+
|
|
41
|
+
# Override doctest's objects with the plugin's alternative implementation.
|
|
42
|
+
pydoctest.DoctestModule = DTModule
|
|
43
|
+
pydoctest.DoctestTextfile = DTTextfile
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def pytest_ignore_collect(collection_path, config):
|
|
47
|
+
"""
|
|
48
|
+
Determine whether to ignore the specified collection path.
|
|
49
|
+
This function is used to exclude the 'tests' directory and test modules when
|
|
50
|
+
the '--doctest-modules' option is used.
|
|
51
|
+
"""
|
|
52
|
+
if config.getoption("--doctest-modules"):
|
|
53
|
+
path_str = str(collection_path)
|
|
54
|
+
if "tests" in path_str or "test_" in path_str:
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
for entry in config.dt_config.pytest_extra_ignore:
|
|
58
|
+
if entry in str(collection_path):
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_private(item):
|
|
63
|
+
"""Decide if an DocTestItem `item` is private.
|
|
64
|
+
|
|
65
|
+
Private items are ignored in pytest_collect_modifyitem`.
|
|
66
|
+
"""
|
|
67
|
+
# Here we look at the name of a test module/object. A seemingly less
|
|
68
|
+
# hacky alternative is to populate a set of seen `item.dtest` attributes
|
|
69
|
+
# (which are actual DocTest objects). The issue with that is it's tricky
|
|
70
|
+
# for explicit skips/ignores. Do we skip linalg.det or linalg._basic.det?
|
|
71
|
+
# (collection order is not guaranteed)
|
|
72
|
+
parent_full_name = item.parent.module.__name__
|
|
73
|
+
is_private = "._" in parent_full_name
|
|
74
|
+
return is_private
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _maybe_add_markers(item, config):
|
|
78
|
+
"""Add xfail/skip markers to `item` if DTConfig says so.
|
|
79
|
+
|
|
80
|
+
Modifies the item in-place.
|
|
81
|
+
"""
|
|
82
|
+
dt_config = config.dt_config
|
|
83
|
+
|
|
84
|
+
extra_skip = dt_config.pytest_extra_skip
|
|
85
|
+
skip_it = item.name in extra_skip
|
|
86
|
+
if skip_it:
|
|
87
|
+
reason = extra_skip[item.name] or ''
|
|
88
|
+
item.add_marker(
|
|
89
|
+
pytest.mark.skip(reason=reason)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
extra_xfail = dt_config.pytest_extra_xfail
|
|
93
|
+
fail_it = item.name in extra_xfail
|
|
94
|
+
if fail_it:
|
|
95
|
+
reason = extra_xfail[item.name] or ''
|
|
96
|
+
item.add_marker(
|
|
97
|
+
pytest.mark.xfail(reason=reason)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def pytest_collection_modifyitems(config, items):
|
|
102
|
+
"""
|
|
103
|
+
This hook is executed after test collection and allows you to modify the list of collected items.
|
|
104
|
+
|
|
105
|
+
The function removes
|
|
106
|
+
- duplicate Doctest items (e.g., scipy.stats.norm and scipy.stats.distributions.norm)
|
|
107
|
+
- Doctest items from underscored or otherwise private modules (e.g., scipy.special._precompute)
|
|
108
|
+
|
|
109
|
+
Note that this functions cooperates with and cleans up after `DTModule.collect`, which does the
|
|
110
|
+
bulk of the collection work.
|
|
111
|
+
"""
|
|
112
|
+
# XXX: The logic in this function can probably be folded into DTModule.collect.
|
|
113
|
+
# I (E.B.) quickly tried it and it does not seem to just work. Apparently something
|
|
114
|
+
# pytest-y runs in between DTModule.collect and this hook (should that something
|
|
115
|
+
# be the proper home for all collection?).
|
|
116
|
+
# Also note that DTTextfile needs _maybe_add_markers, too.
|
|
117
|
+
|
|
118
|
+
need_filter_unique = (
|
|
119
|
+
config.getoption("--doctest-modules") and
|
|
120
|
+
config.getvalue("collection_strategy") == 'api'
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
unique_items = []
|
|
124
|
+
|
|
125
|
+
for item in items:
|
|
126
|
+
if isinstance(item.parent, DTModule) and need_filter_unique:
|
|
127
|
+
# objects are collected twice: from their public module + from the impl module
|
|
128
|
+
# e.g. for `levy_stable` we have
|
|
129
|
+
# (Pdb) p item.name, item.parent.name
|
|
130
|
+
# ('scipy.stats.levy_stable', 'build-install/lib/python3.10/site-packages/scipy/stats/__init__.py')
|
|
131
|
+
# and
|
|
132
|
+
# ('scipy.stats.distributions.levy_stable', 'distributions.py')
|
|
133
|
+
# so we filter out the second occurence
|
|
134
|
+
#
|
|
135
|
+
# There are two options:
|
|
136
|
+
# - either the impl module has a leading underscore (scipy.linalg._basic), or
|
|
137
|
+
# - it needs to be explicitly listed in the 'extra_ignore' config key (distributions.py)
|
|
138
|
+
#
|
|
139
|
+
# Note that the last part cannot be automated: scipy.cluster.vq is public, but
|
|
140
|
+
# scipy.stats.distributions is not
|
|
141
|
+
extra_ignore = config.dt_config.pytest_extra_ignore
|
|
142
|
+
parent_full_name = item.parent.module.__name__
|
|
143
|
+
is_duplicate = parent_full_name in extra_ignore or item.name in extra_ignore
|
|
144
|
+
|
|
145
|
+
if is_duplicate or is_private(item):
|
|
146
|
+
# ignore it
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
_maybe_add_markers(item, config)
|
|
150
|
+
unique_items.append(item)
|
|
151
|
+
|
|
152
|
+
# Replace the original list of test items with the unique ones
|
|
153
|
+
items[:] = unique_items
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _is_deprecated(module):
|
|
157
|
+
"""Detect if a module is deprecated (i.e., raises or warns on getattr)."""
|
|
158
|
+
names = dir(module)
|
|
159
|
+
if not names:
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
res = False
|
|
163
|
+
try:
|
|
164
|
+
with warnings.catch_warnings():
|
|
165
|
+
warnings.simplefilter('error', DeprecationWarning)
|
|
166
|
+
getattr(module, names[0])
|
|
167
|
+
res = False
|
|
168
|
+
except DeprecationWarning:
|
|
169
|
+
res = True
|
|
170
|
+
|
|
171
|
+
return res
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class DTModule(DoctestModule):
|
|
175
|
+
"""
|
|
176
|
+
This class extends the DoctestModule class provided by pytest.
|
|
177
|
+
|
|
178
|
+
DTModule is responsible for overriding the behavior of the collect method.
|
|
179
|
+
The collect method is called by pytest to collect and generate test items for doctests
|
|
180
|
+
in the specified module or file.
|
|
181
|
+
"""
|
|
182
|
+
def collect(self):
|
|
183
|
+
if pytest.__version__ < '8':
|
|
184
|
+
# Part of this code is copy-pasted from the `_pytest.doctest` module(pytest 7.4.0):
|
|
185
|
+
# https://github.com/pytest-dev/pytest/blob/448563caaac559b8a3195edc58e8806aca8d2c71/src/_pytest/doctest.py#L497
|
|
186
|
+
if self.path.name == "setup.py":
|
|
187
|
+
return
|
|
188
|
+
if self.path.name == "conftest.py":
|
|
189
|
+
module = self.config.pluginmanager._importconftest(
|
|
190
|
+
self.path,
|
|
191
|
+
self.config.getoption("importmode"),
|
|
192
|
+
rootpath=self.config.rootpath
|
|
193
|
+
)
|
|
194
|
+
else:
|
|
195
|
+
try:
|
|
196
|
+
module = import_path(
|
|
197
|
+
self.path,
|
|
198
|
+
root=self.config.rootpath,
|
|
199
|
+
mode=self.config.getoption("importmode"),
|
|
200
|
+
)
|
|
201
|
+
except ImportError:
|
|
202
|
+
if self.config.getvalue("doctest_ignore_import_errors"):
|
|
203
|
+
outcomes.skip("unable to import module %r" % self.path)
|
|
204
|
+
else:
|
|
205
|
+
raise
|
|
206
|
+
|
|
207
|
+
# XXX: `assert module == self.obj` seems to work (so is it all automatic?)
|
|
208
|
+
# but what are failure modes
|
|
209
|
+
else:
|
|
210
|
+
# https://github.com/pytest-dev/pytest/blob/8.1.0/src/_pytest/doctest.py#L561
|
|
211
|
+
try:
|
|
212
|
+
module = self.obj
|
|
213
|
+
except _pytest.nodes.Collector.CollectError:
|
|
214
|
+
if self.config.getvalue("doctest_ignore_import_errors"):
|
|
215
|
+
outcomes.skip("unable to import module %r" % self.path)
|
|
216
|
+
else:
|
|
217
|
+
raise
|
|
218
|
+
|
|
219
|
+
if _is_deprecated(module):
|
|
220
|
+
# bail out early
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
optionflags = dt_config.optionflags
|
|
224
|
+
|
|
225
|
+
# Plug in the custom runner: `PytestDTRunner`
|
|
226
|
+
runner = _get_runner(self.config,
|
|
227
|
+
verbose=False,
|
|
228
|
+
optionflags=optionflags,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# strategy='api': discover doctests in public, non-deprecated objects in module
|
|
232
|
+
# strategy=None : use vanilla stdlib doctest discovery
|
|
233
|
+
strategy = self.config.getvalue("collection_strategy")
|
|
234
|
+
if strategy == 'None':
|
|
235
|
+
strategy = None
|
|
236
|
+
|
|
237
|
+
# NB: additional postprocessing in pytest_collection_modifyitems
|
|
238
|
+
for test in find_doctests(module, strategy=strategy, name=module.__name__, config=dt_config):
|
|
239
|
+
if test.examples: # skip empty doctests
|
|
240
|
+
yield pydoctest.DoctestItem.from_parent(
|
|
241
|
+
self, name=test.name, runner=runner, dtest=test
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class DTTextfile(DoctestTextfile):
|
|
246
|
+
"""
|
|
247
|
+
This class extends the DoctestTextfile class provided by pytest.
|
|
248
|
+
|
|
249
|
+
DTTextfile is responsible for overriding the behavior of the collect method.
|
|
250
|
+
The collect method is called by pytest to collect and generate test items for doctests
|
|
251
|
+
in the specified text files.
|
|
252
|
+
"""
|
|
253
|
+
def collect(self):
|
|
254
|
+
# Part of this code is copy-pasted from `_pytest.doctest` module(pytest 7.4.0):
|
|
255
|
+
# https://github.com/pytest-dev/pytest/blob/448563caaac559b8a3195edc58e8806aca8d2c71/src/_pytest/doctest.py#L417
|
|
256
|
+
encoding = self.config.getini("doctest_encoding")
|
|
257
|
+
text = self.path.read_text(encoding)
|
|
258
|
+
filename = str(self.path)
|
|
259
|
+
name = self.path.name
|
|
260
|
+
globs = {"__name__": "__main__"}
|
|
261
|
+
|
|
262
|
+
optionflags = dt_config.optionflags
|
|
263
|
+
|
|
264
|
+
# Plug in the custom runner: `PytestDTRunner`
|
|
265
|
+
runner = _get_runner(self.config,
|
|
266
|
+
verbose=False,
|
|
267
|
+
optionflags=optionflags,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Plug in an instance of `DTParser` which parses the doctest examples from the text file and
|
|
271
|
+
# filters out stopwords and pseudocode.
|
|
272
|
+
parser = DTParser(config=self.config.dt_config)
|
|
273
|
+
|
|
274
|
+
# This part of the code is unchanged
|
|
275
|
+
test = parser.get_doctest(text, globs, name, filename, 0)
|
|
276
|
+
if test.examples:
|
|
277
|
+
yield pydoctest.DoctestItem.from_parent(
|
|
278
|
+
self, name=test.name, runner=runner, dtest=test
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _get_runner(config, verbose, optionflags):
|
|
283
|
+
"""
|
|
284
|
+
Override function to return an instance of PytestDTRunner.
|
|
285
|
+
|
|
286
|
+
This function creates and returns an instance of PytestDTRunner, a custom runner class
|
|
287
|
+
that extends the behavior of DebugDTRunner for running doctests in pytest.
|
|
288
|
+
"""
|
|
289
|
+
class PytestDTRunner(DebugDTRunner):
|
|
290
|
+
def run(self, test, compileflags=None, out=None, clear_globs=False):
|
|
291
|
+
"""
|
|
292
|
+
Run tests in context managers.
|
|
293
|
+
|
|
294
|
+
Restore the errstate/print state after each docstring.
|
|
295
|
+
Also, make MPL backend non-GUI and close the figures.
|
|
296
|
+
|
|
297
|
+
The order of context managers is actually relevant. Consider
|
|
298
|
+
user_context_mgr that turns warnings into errors.
|
|
299
|
+
|
|
300
|
+
Additionally, suppose that MPL deprecates something and plt.something
|
|
301
|
+
starts issuing warnings. Now all of those become errors
|
|
302
|
+
*unless* the `mpl()` context mgr has a chance to filter them out
|
|
303
|
+
*before* they become errors in `config.user_context_mgr()`.
|
|
304
|
+
"""
|
|
305
|
+
dt_config = config.dt_config
|
|
306
|
+
|
|
307
|
+
with np_errstate():
|
|
308
|
+
with dt_config.user_context_mgr(test):
|
|
309
|
+
with matplotlib_make_nongui():
|
|
310
|
+
# XXX: local_resourses needed? they seem to be, w/o pytest
|
|
311
|
+
with temp_cwd(test, dt_config.local_resources):
|
|
312
|
+
super().run(test, compileflags=compileflags, out=out, clear_globs=clear_globs)
|
|
313
|
+
|
|
314
|
+
"""
|
|
315
|
+
Almost verbatim copy of `_pytest.doctest.PytestDoctestRunner` except we utilize
|
|
316
|
+
DTConfig's `nameerror_after_exception` attribute in place of doctest's `continue_on_failure`.
|
|
317
|
+
"""
|
|
318
|
+
def report_failure(self, out, test, example, got):
|
|
319
|
+
failure = doctest.DocTestFailure(test, example, got)
|
|
320
|
+
if config.dt_config.nameerror_after_exception:
|
|
321
|
+
out.append(failure)
|
|
322
|
+
else:
|
|
323
|
+
raise failure
|
|
324
|
+
|
|
325
|
+
def report_unexpected_exception(self, out, test, example, exc_info):
|
|
326
|
+
if isinstance(exc_info[1], outcomes.OutcomeException):
|
|
327
|
+
raise exc_info[1]
|
|
328
|
+
if isinstance(exc_info[1], bdb.BdbQuit):
|
|
329
|
+
outcomes.exit("Quitting debugger")
|
|
330
|
+
failure = doctest.UnexpectedException(test, example, exc_info)
|
|
331
|
+
if config.dt_config.nameerror_after_exception:
|
|
332
|
+
out.append(failure)
|
|
333
|
+
else:
|
|
334
|
+
raise failure
|
|
335
|
+
|
|
336
|
+
return PytestDTRunner(verbose=verbose, optionflags=optionflags, config=config.dt_config)
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__all__ = ['func9', 'func10']
|
|
2
|
+
|
|
3
|
+
def func9():
|
|
4
|
+
"""
|
|
5
|
+
Wrong output.
|
|
6
|
+
>>> import numpy as np
|
|
7
|
+
>>> np.array([1, 2, 3])
|
|
8
|
+
array([2, 3, 4])
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def func10():
|
|
13
|
+
"""
|
|
14
|
+
NameError
|
|
15
|
+
>>> import numpy as np
|
|
16
|
+
>>> np.arraY([1, 2, 3])
|
|
17
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__all__ = ['func_depr', 'func_name_error']
|
|
2
|
+
|
|
3
|
+
def func_depr():
|
|
4
|
+
"""
|
|
5
|
+
A test case for the user context mgr to turn warnings to errors.
|
|
6
|
+
|
|
7
|
+
>>> import warnings; warnings.warn('Sample deprecation warning', DeprecationWarning)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def func_name_error():
|
|
12
|
+
"""After an example fails, next examples may emit NameErrors. Suppress them.
|
|
13
|
+
|
|
14
|
+
Note that the suppresion is in effect for the duration of the DocTest, i.e.
|
|
15
|
+
for the whole docstring. Maybe this can be fixed at some point.
|
|
16
|
+
|
|
17
|
+
>>> def func():
|
|
18
|
+
... raise ValueError("oops")
|
|
19
|
+
... return 42
|
|
20
|
+
>>> res = func()
|
|
21
|
+
>>> res
|
|
22
|
+
>>> raise NameError('This is legitimate, but is suppressed')
|
|
23
|
+
|
|
24
|
+
Further name errors are also suppressed (which is a bug, too):
|
|
25
|
+
>>> raise NameError('Also legit')
|
|
26
|
+
"""
|
|
27
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A set of simple cases for DocTestFinder / DTFinder and its helpers.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
1. There is a doctest in the module docstring
|
|
7
|
+
|
|
8
|
+
>>> 1 + 2
|
|
9
|
+
3
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__all__ = ['func', 'Klass']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def func():
|
|
16
|
+
"""Two doctests in a module-level function.
|
|
17
|
+
|
|
18
|
+
>>> 1 + 3
|
|
19
|
+
4
|
|
20
|
+
|
|
21
|
+
>>> 5 + 6
|
|
22
|
+
11
|
|
23
|
+
"""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Klass:
|
|
28
|
+
"""A class has doctests in a class docstring.
|
|
29
|
+
|
|
30
|
+
>>> 1 + 8
|
|
31
|
+
9
|
|
32
|
+
"""
|
|
33
|
+
def meth(self):
|
|
34
|
+
"""And a method has its doctests.
|
|
35
|
+
|
|
36
|
+
>>> 2 + 11
|
|
37
|
+
13
|
|
38
|
+
"""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def meth_2(self):
|
|
42
|
+
"""
|
|
43
|
+
One other method.
|
|
44
|
+
|
|
45
|
+
>>> 111 + 1
|
|
46
|
+
112
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def private_func():
|
|
51
|
+
"""A non-public function (not listed in __all__) also has examples.
|
|
52
|
+
|
|
53
|
+
>>> 9 + 11
|
|
54
|
+
20
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _underscored_private_func():
|
|
59
|
+
"""A private function (starts with an undescore) also has examples.
|
|
60
|
+
|
|
61
|
+
>>> 9 + 12
|
|
62
|
+
21
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from ..conftest import dt_config
|
|
2
|
+
|
|
3
|
+
# Specify local files required by doctests
|
|
4
|
+
dt_config.local_resources = {
|
|
5
|
+
'scipy_doctest.tests.local_file_cases.local_files': ['local_file.txt'],
|
|
6
|
+
'scipy_doctest.local_file_cases.sio': ['octave_a.mat']
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
__all__ = ['local_files', 'sio']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def local_files():
|
|
14
|
+
"""
|
|
15
|
+
A doctest that tries to read a local file
|
|
16
|
+
|
|
17
|
+
>>> with open('local_file.txt', 'r'):
|
|
18
|
+
... pass
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def sio():
|
|
23
|
+
"""
|
|
24
|
+
The .mat file is from scipy/tutorial/io.rst; The test checks that a want/got
|
|
25
|
+
being a dict is handled correctly.
|
|
26
|
+
|
|
27
|
+
>>> import scipy.io as sio
|
|
28
|
+
>>> sio.loadmat('octave_a.mat')
|
|
29
|
+
{'__header__': b'MATLAB 5.0 MAT-file, written by Octave 3.2.3, 2010-05-30 02:13:40 UTC',
|
|
30
|
+
'__version__': '1.0',
|
|
31
|
+
'__globals__': [],
|
|
32
|
+
'a': array([[[ 1., 4., 7., 10.],
|
|
33
|
+
[ 2., 5., 8., 11.],
|
|
34
|
+
[ 3., 6., 9., 12.]]])}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'func', 'func2', 'func3', 'func4', 'func5', 'func6', 'func8',
|
|
3
|
+
'func7', 'manip_printoptions', 'array_abbreviation'
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
def func():
|
|
10
|
+
"""
|
|
11
|
+
>>> 2 / 3
|
|
12
|
+
0.667
|
|
13
|
+
"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def func2():
|
|
18
|
+
"""
|
|
19
|
+
Check that `np.` is imported and the array repr is recognized. Also check
|
|
20
|
+
that whitespace is irrelevant for the checker.
|
|
21
|
+
>>> import numpy as np
|
|
22
|
+
>>> np.array([1, 2, 3.0])
|
|
23
|
+
array([1, 2, 3])
|
|
24
|
+
|
|
25
|
+
Check that the comparison is with atol and rtol: give less digits than
|
|
26
|
+
what numpy by default prints
|
|
27
|
+
>>> np.sin([1., 2, 3])
|
|
28
|
+
array([0.8414, 0.9092, 0.1411])
|
|
29
|
+
|
|
30
|
+
Also check that numpy repr for e.g. dtypes is recognized
|
|
31
|
+
>>> np.array([1, 2, 3], dtype=np.float32)
|
|
32
|
+
array([1., 2., 3.], dtype=float32)
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def func3():
|
|
38
|
+
"""
|
|
39
|
+
Check that printed arrays are checked with atol/rtol
|
|
40
|
+
>>> import numpy as np
|
|
41
|
+
>>> a = np.array([1, 2, 3, 4]) / 3
|
|
42
|
+
>>> print(a)
|
|
43
|
+
[0.33 0.66 1 1.33]
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def func4():
|
|
48
|
+
"""
|
|
49
|
+
Test `# may vary` markers : these should not break doctests (but the code
|
|
50
|
+
should still be valid, otherwise it's an error).
|
|
51
|
+
>>> import numpy as np
|
|
52
|
+
>>> np.random.randint(50)
|
|
53
|
+
42 # may vary
|
|
54
|
+
|
|
55
|
+
>>> np.random.randint(50)
|
|
56
|
+
42 # Random
|
|
57
|
+
|
|
58
|
+
>>> np.random.randint(50)
|
|
59
|
+
42 # random
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def func5():
|
|
64
|
+
"""
|
|
65
|
+
Object addresses are ignored:
|
|
66
|
+
>>> import numpy as np
|
|
67
|
+
>>> np.array([1, 2, 3]).data
|
|
68
|
+
<memory at 0x7f119b952400>
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def func6():
|
|
73
|
+
"""
|
|
74
|
+
Masked arrays
|
|
75
|
+
|
|
76
|
+
>>> import numpy.ma as ma
|
|
77
|
+
>>> y = ma.array([1, 2, 3], mask = [0, 1, 0])
|
|
78
|
+
>>> y
|
|
79
|
+
masked_array(data=[1, --, 3],
|
|
80
|
+
mask=[False, True, False],
|
|
81
|
+
fill_value=999999)
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def func8():
|
|
87
|
+
"""
|
|
88
|
+
Namedtuples (they are *not* in the namespace)
|
|
89
|
+
|
|
90
|
+
>>> from scipy.stats import levene
|
|
91
|
+
>>> a = [8.88, 9.12, 9.04, 8.98, 9.00, 9.08, 9.01, 8.85, 9.06, 8.99]
|
|
92
|
+
>>> b = [8.88, 8.95, 9.29, 9.44, 9.15, 9.58, 8.36, 9.18, 8.67, 9.05]
|
|
93
|
+
>>> c = [8.95, 9.12, 8.95, 8.85, 9.03, 8.84, 9.07, 8.98, 8.86, 8.98]
|
|
94
|
+
|
|
95
|
+
# namedtuples are recognized...
|
|
96
|
+
>>> levene(a, b, c)
|
|
97
|
+
LeveneResult(statistic=7.58495, pvalue=0.00243)
|
|
98
|
+
|
|
99
|
+
# can be reformatted
|
|
100
|
+
>>> levene(a, b, c)
|
|
101
|
+
LeveneResult(statistic=7.58495,
|
|
102
|
+
pvalue=0.00243)
|
|
103
|
+
|
|
104
|
+
# ... or can be given just as tuples
|
|
105
|
+
>>> levene(a, b, c)
|
|
106
|
+
(7.58495, 0.00243)
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def func7():
|
|
112
|
+
"""
|
|
113
|
+
Multiline namedtuples + nested tuples, see scipy/gh-16082
|
|
114
|
+
|
|
115
|
+
>>> from scipy import stats
|
|
116
|
+
>>> import numpy as np
|
|
117
|
+
>>> a = np.arange(10)
|
|
118
|
+
>>> stats.describe(a)
|
|
119
|
+
DescribeResult(nobs=10, minmax=(0, 9), mean=4.5,
|
|
120
|
+
variance=9.16666666666, skewness=0.0,
|
|
121
|
+
kurtosis=-1.224242424)
|
|
122
|
+
|
|
123
|
+
A single-line form of a namedtuple
|
|
124
|
+
>>> stats.describe(a)
|
|
125
|
+
DescribeResult(nobs=10, minmax=(0, 9), mean=4.5, variance=9.16666, skewness=0.0, kurtosis=-1.2242424)
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def manip_printoptions():
|
|
131
|
+
"""Manipulate np.printoptions.
|
|
132
|
+
>>> import numpy as np
|
|
133
|
+
>>> np.set_printoptions(linewidth=146)
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def array_abbreviation():
|
|
138
|
+
"""
|
|
139
|
+
Numpy abbreviates arrays, check that it works.
|
|
140
|
+
|
|
141
|
+
NB: the implementation might need to change when
|
|
142
|
+
numpy finally disallows default-creating ragged arrays.
|
|
143
|
+
Currently, `...` gets interpreted as an Ellipsis,
|
|
144
|
+
thus the `a_want/a_got` variables in DTChecker are in fact
|
|
145
|
+
object arrays.
|
|
146
|
+
>>> import numpy as np
|
|
147
|
+
>>> np.arange(10000)
|
|
148
|
+
array([0, 1, 2, ..., 9997, 9998, 9999])
|
|
149
|
+
|
|
150
|
+
>>> np.diag(np.arange(33)) / 30
|
|
151
|
+
array([[0., 0., 0., ..., 0., 0.,0.],
|
|
152
|
+
[0., 0.03333333, 0., ..., 0., 0., 0.],
|
|
153
|
+
[0., 0., 0.06666667, ..., 0., 0., 0.],
|
|
154
|
+
...,
|
|
155
|
+
[0., 0., 0., ..., 1., 0., 0.],
|
|
156
|
+
[0., 0., 0., ..., 0., 1.03333333, 0.],
|
|
157
|
+
[0., 0., 0., ..., 0., 0., 1.06666667]])
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
>>> np.diag(np.arange(1, 1001, dtype=float))
|
|
161
|
+
array([[1, 0, 0, ..., 0, 0, 0],
|
|
162
|
+
[0, 2, 0, ..., 0, 0, 0],
|
|
163
|
+
[0, 0, 3, ..., 0, 0, 0],
|
|
164
|
+
...,
|
|
165
|
+
[0, 0, 0, ..., 998, 0, 0],
|
|
166
|
+
[0, 0, 0, ..., 0, 999, 0],
|
|
167
|
+
[0, 0, 0, ..., 0, 0, 1000]])
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def nan_equal():
|
|
171
|
+
"""
|
|
172
|
+
Test that nans are treated as equal.
|
|
173
|
+
|
|
174
|
+
>>> import numpy as np
|
|
175
|
+
>>> np.nan
|
|
176
|
+
np.float64(nan)
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_cmplx_nan():
|
|
181
|
+
"""
|
|
182
|
+
Complex nans
|
|
183
|
+
>>> import numpy as np
|
|
184
|
+
>>> np.nan - 1j*np.nan
|
|
185
|
+
nan + nanj
|
|
186
|
+
|
|
187
|
+
>>> np.nan + 1j*np.nan
|
|
188
|
+
np.complex128(nan+nanj)
|
|
189
|
+
|
|
190
|
+
>>> 1j*np.complex128(np.nan)
|
|
191
|
+
np.complex128(nan+nanj)
|
|
192
|
+
"""
|
|
Binary file
|