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.
@@ -0,0 +1,158 @@
1
+ import io
2
+ import doctest
3
+
4
+ from contextlib import redirect_stderr
5
+
6
+ import numpy as np
7
+
8
+ try:
9
+ import matplotlib.pyplot as plt # noqa
10
+ HAVE_MATPLOTLIB = True
11
+ except Exception:
12
+ HAVE_MATPLOTLIB = False
13
+
14
+ import pytest
15
+
16
+ try:
17
+ import scipy # noqa
18
+ HAVE_SCIPY = True
19
+ except Exception:
20
+ HAVE_SCIPY = False
21
+
22
+ from . import (module_cases as module,
23
+ stopwords_cases as stopwords,
24
+ finder_cases,
25
+ failure_cases,
26
+ failure_cases_2,
27
+ local_file_cases)
28
+ from ..frontend import testmod as _testmod, run_docstring_examples
29
+ from ..util import warnings_errors
30
+ from ..impl import DTConfig
31
+
32
+ _VERBOSE = 2
33
+
34
+
35
+ @pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
36
+ def test_module():
37
+ res, _ = _testmod(module, verbose=_VERBOSE)
38
+ assert res.failed == 0
39
+ assert res.attempted != 0
40
+
41
+
42
+ @pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
43
+ def test_module_vanilla_dtfinder():
44
+ config = DTConfig()
45
+ config.stopwords = []
46
+ res, _ = _testmod(module, verbose=_VERBOSE, config=config)
47
+ assert res.failed == 0
48
+ assert res.attempted != 0
49
+
50
+
51
+ @pytest.mark.skipif(not HAVE_MATPLOTLIB, reason='need matplotlib')
52
+ def test_stopwords():
53
+ res, _ = _testmod(stopwords, verbose=_VERBOSE)
54
+ assert res.failed == 0
55
+ assert res.attempted != 0
56
+
57
+
58
+ @pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
59
+ def test_public_obj_discovery():
60
+ res, _ = _testmod(module, verbose=_VERBOSE, strategy='api')
61
+ assert res.failed == 0
62
+ assert res.attempted != 0
63
+
64
+
65
+ def test_run_docstring_examples():
66
+ f = finder_cases.Klass
67
+ res1 = run_docstring_examples(f)
68
+ res2 = _testmod(finder_cases, strategy=[finder_cases.Klass])
69
+ assert res1 == res2
70
+
71
+
72
+ def test_global_state():
73
+ # Make sure doctesting does not alter the global state, as much as reasonable
74
+ opts = np.get_printoptions()
75
+ _testmod(module)
76
+ new_opts = np.get_printoptions()
77
+ assert new_opts == opts
78
+
79
+
80
+ def test_module_debugrunner():
81
+ with pytest.raises((doctest.UnexpectedException, doctest.DocTestFailure)):
82
+ _testmod(failure_cases, raise_on_error=True)
83
+
84
+
85
+ def test_verbosity_1():
86
+ # smoke test that verbose=1 works
87
+ stream = io.StringIO()
88
+ with redirect_stderr(stream):
89
+ _testmod(failure_cases, verbose=1, report=False)
90
+
91
+
92
+ def test_user_context():
93
+ # use a user context to turn warnings to errors : test that it raises
94
+ config = DTConfig()
95
+ config.user_context_mgr = warnings_errors
96
+ with pytest.raises(doctest.UnexpectedException):
97
+ _testmod(failure_cases_2,
98
+ raise_on_error=True, strategy=[failure_cases_2.func_depr],
99
+ config=config)
100
+
101
+
102
+ class TestLocalFiles:
103
+ def test_local_files(self):
104
+ # A doctest tries to open a local file. Test that it works
105
+ # (internally, the file will need to be copied).
106
+ config = DTConfig()
107
+ config.local_resources = {'scipy_doctest.tests.local_file_cases.local_files':
108
+ ['local_file.txt']}
109
+ res, _ = _testmod(local_file_cases, config=config,
110
+ strategy=[local_file_cases.local_files],
111
+ verbose=_VERBOSE)
112
+
113
+ assert res.failed == 0
114
+ assert res.attempted != 0
115
+
116
+ @pytest.mark.skipif(not HAVE_SCIPY, reason='need scipy')
117
+ def test_sio_octave(self):
118
+ # scipy/tutorial/io.rst : octave_a.mat file
119
+ config = DTConfig()
120
+ config.local_resources = {'scipy_doctest.tests.local_file_cases.sio':
121
+ ['octave_a.mat']}
122
+ res, _ = _testmod(local_file_cases, config=config,
123
+ strategy=[local_file_cases.sio],
124
+ verbose=_VERBOSE)
125
+
126
+ assert res.failed == 0
127
+ assert res.attempted != 0
128
+
129
+
130
+ class TestNameErrorAfterException:
131
+ def test_name_error_after_exception(self):
132
+ # After an example fails, subsequent examples may emit NameErrors.
133
+ # Check that they are suppressed.
134
+ # This first came in in https://github.com/scipy/scipy/pull/13116
135
+ stream = io.StringIO()
136
+ with redirect_stderr(stream):
137
+ _testmod(failure_cases_2,
138
+ strategy=[failure_cases_2.func_name_error])
139
+
140
+ stream.seek(0)
141
+ output = stream.read()
142
+
143
+ assert "ValueError:" in output # the original exception
144
+ assert "NameError:" not in output # the follow-up NameError
145
+
146
+ def test_name_error_after_exception_off(self):
147
+ # show NameErrors
148
+ config = DTConfig(nameerror_after_exception=True)
149
+ stream = io.StringIO()
150
+ with redirect_stderr(stream):
151
+ _testmod(failure_cases_2,
152
+ strategy=[failure_cases_2.func_name_error], config=config)
153
+
154
+ stream.seek(0)
155
+ output = stream.read()
156
+
157
+ assert "ValueError:" in output # the original exception
158
+ assert "NameError:" in output # the follow-up NameError
scipy_doctest/util.py ADDED
@@ -0,0 +1,279 @@
1
+ """
2
+ Assorted utilities.
3
+ """
4
+ import os
5
+ import warnings
6
+ import operator
7
+ import shutil
8
+ import copy
9
+ import tempfile
10
+ import inspect
11
+ from contextlib import contextmanager
12
+
13
+
14
+ @contextmanager
15
+ def matplotlib_make_nongui():
16
+ """ Temporarily make the matplotlib backend non-GUI; close all figures on exit.
17
+ """
18
+ try:
19
+ import matplotlib
20
+ import matplotlib.pyplot as plt
21
+ backend = matplotlib.get_backend()
22
+ plt.close('all')
23
+ matplotlib.use('Agg')
24
+ except ImportError:
25
+ backend = None
26
+
27
+ try:
28
+ # Matplotlib issues UserWarnings on plt.show() with a non-GUI backend,
29
+ # Filter them out.
30
+ # UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
31
+ with warnings.catch_warnings():
32
+ warnings.filterwarnings("ignore", "FigureCanvasAgg", UserWarning) # MPL >= 3.8.x
33
+ warnings.filterwarnings("ignore", "Matplotlib", UserWarning) # MPL <= 3.7.x
34
+ yield backend
35
+ finally:
36
+ if backend:
37
+ import matplotlib.pyplot as plt
38
+ plt.close('all')
39
+ matplotlib.use(backend)
40
+
41
+
42
+ @contextmanager
43
+ def temp_cwd(test, local_resources=None):
44
+ """Switch to a temp directory, clean up when done.
45
+
46
+ Copy local files, if requested.
47
+
48
+ Parameters
49
+ ----------
50
+ test : doctest.DocTest instance
51
+ The current test instance
52
+ local_resources : dict, optional
53
+ If provided, maps the name of the test (`test.name` attribute) to
54
+ a list of filenames to copy to the tempdir. File names are relative
55
+ to the `test.filename`, which is, in most cases, the name of the
56
+ file the doctest has been extracted from.
57
+ """
58
+ cwd = os.getcwd()
59
+ tmpdir = tempfile.mkdtemp()
60
+
61
+ if local_resources and test.name in local_resources:
62
+ # local files requested; copy the files
63
+ path, _ = os.path.split(test.filename)
64
+ for fname in local_resources[test.name]:
65
+ shutil.copy(os.path.join(path, fname), tmpdir)
66
+ try:
67
+ os.chdir(tmpdir)
68
+ yield tmpdir
69
+ finally:
70
+ os.chdir(cwd)
71
+ shutil.rmtree(tmpdir)
72
+
73
+
74
+ # Options for the usr_context_mgr : do nothing (default), and control the random
75
+ # state, in two flavors.
76
+
77
+ @contextmanager
78
+ def scipy_rndm_state():
79
+ """Restore the `np.random` state when done."""
80
+ # FIXME: this matches the refguide-check behavior, but is a tad strange:
81
+ # makes sure that the seed the old-fashioned np.random* methods is *NOT* reproducible
82
+ # but the new-style `default_rng()` *IS*.
83
+ # Should these two be either both repro or both not repro?
84
+ from scipy._lib._util import _fixed_default_rng
85
+ import numpy as np
86
+ with _fixed_default_rng():
87
+ np.random.seed(None)
88
+ yield
89
+
90
+
91
+ @contextmanager
92
+ def numpy_rndm_state():
93
+ """Restore the `np.random` state when done."""
94
+ # Make sure that the seed the old-fashioned np.random* methods is *NOT* reproducible
95
+ import numpy as np
96
+ np.random.seed(None)
97
+ yield
98
+
99
+
100
+ @contextmanager
101
+ def noop_context_mgr(test):
102
+ """Do nothing.
103
+
104
+ This is a stub context manager to serve as a default for
105
+ ``DTConfig().user_context_mgr``, for users to override.
106
+ """
107
+ yield
108
+
109
+
110
+ @contextmanager
111
+ def np_errstate():
112
+ """A context manager to restore the numpy errstate and printoptions when done."""
113
+ import numpy as np
114
+ with np.errstate():
115
+ with np.printoptions():
116
+ yield
117
+
118
+
119
+ @contextmanager
120
+ def warnings_errors(test):
121
+ """Temporarily turn all warnings to errors."""
122
+ with warnings.catch_warnings():
123
+ warnings.simplefilter('error', Warning)
124
+ yield
125
+
126
+
127
+ def _map_verbosity(level):
128
+ """A helper for validating/constructing the verbosity level.
129
+
130
+ Validate that $ 0 <= level <= 2 $ and map the boolean flag for the
131
+ `doctest.DocTestFinder` et al:
132
+ 0, 1 -> False, 2 -> True
133
+ See the `testmod` docstring for details.
134
+
135
+
136
+ Parameters
137
+ ----------
138
+ level : int or None
139
+ Allowed values are 0, 1 or 2. None mean 0.
140
+
141
+ Returns
142
+ -------
143
+ level : int
144
+ dtverbose : bool
145
+
146
+ """
147
+ if level is None:
148
+ level = 0
149
+ level = operator.index(level)
150
+ if level not in [0, 1, 2]:
151
+ raise ValueError("Unknown verbosity setting : level = %s " % level)
152
+ dtverbose = True if level == 2 else False
153
+ return level, dtverbose
154
+
155
+
156
+ ### Object / Doctest selection helpers ###
157
+
158
+
159
+ def is_deprecated(f):
160
+ """ Check if an item is deprecated.
161
+ """
162
+ with warnings.catch_warnings(record=True):
163
+ warnings.simplefilter("error")
164
+ try:
165
+ f(**{"not a kwarg": None})
166
+ except DeprecationWarning:
167
+ return True
168
+ except Exception:
169
+ pass
170
+ return False
171
+
172
+
173
+ def get_all_list(module):
174
+ """Return a copy of the __all__ list with irrelevant items removed.
175
+ The __all__list explicitly specifies which objects should be considered public.
176
+ - If strategy="api" and __all__ is missing, return an empty list.
177
+ - Also return a list of deprecated items and "other" items, which failed
178
+ to classify.
179
+ """
180
+ if hasattr(module, "__all__"):
181
+ all_list = copy.deepcopy(module.__all__)
182
+ else:
183
+ all_list = []
184
+ for name in ['absolute_import', 'division', 'print_function']:
185
+ try:
186
+ all_list.remove(name)
187
+ except ValueError:
188
+ pass
189
+
190
+ # Modules are almost always private; real submodules need a separate
191
+ # run of refguide_check.
192
+ all_list = [name for name in all_list
193
+ if not inspect.ismodule(getattr(module, name, None))]
194
+
195
+ deprecated = []
196
+ not_deprecated = []
197
+ for name in all_list:
198
+ f = getattr(module, name, None)
199
+ if callable(f) and is_deprecated(f):
200
+ deprecated.append(name)
201
+ else:
202
+ not_deprecated.append(name)
203
+
204
+ others = set(dir(module)).difference(set(deprecated)).difference(set(not_deprecated))
205
+
206
+ return not_deprecated, deprecated, others
207
+
208
+
209
+ def get_public_objects(module, skiplist=None):
210
+ """Return a list of public objects in a module.
211
+
212
+ Parameters
213
+ ----------
214
+ module :
215
+ A module look for objects
216
+ skiplist : list
217
+ The list of names to skip
218
+
219
+ Returns
220
+ -------
221
+ (items, names) : a tuple of two lists
222
+ `items` is a list of public objects in the module
223
+ `names` is a list of names of these objects. Each entry of this list
224
+ is nothing but `item.__name__` *if the latter exists* :
225
+ `name == item.__name__ if item.__name__`.
226
+ Otherwise, the name is taken from the `__all__` list of the module.
227
+ failures : list
228
+ a list of names which failed to be found in the module
229
+
230
+ """
231
+ if skiplist is None:
232
+ skiplist = set()
233
+
234
+ all_list, _, _ = get_all_list(module)
235
+
236
+ items, names, failures = [], [], []
237
+
238
+ for name in all_list:
239
+ full_name = module.__name__ + '.' + name
240
+
241
+ if full_name in skiplist:
242
+ continue
243
+
244
+ try:
245
+ obj = getattr(module, name)
246
+ items.append(obj)
247
+ names.append(name)
248
+ except AttributeError:
249
+ import traceback
250
+ failures.append((full_name, False,
251
+ "Missing item!\n" +
252
+ traceback.format_exc()))
253
+ continue
254
+
255
+ return (items, names), failures
256
+
257
+
258
+ # XXX: not used ATM
259
+ modules = []
260
+ def generate_log(module, test):
261
+ """
262
+ Generate a log of the doctested items.
263
+
264
+ This function logs the items being doctested to a file named 'doctest.log'.
265
+
266
+ Args:
267
+ module (module): The module being doctested.
268
+ test (str): The name of the doctest item.
269
+ """
270
+ with open('doctest.log', 'a') as LOGFILE:
271
+ try:
272
+ if module.__name__ not in modules:
273
+ LOGFILE.write("\n" + module.__name__ + "\n")
274
+ LOGFILE.write("="*len(module.__name__) + "\n")
275
+ modules.append(module.__name__)
276
+ LOGFILE.write(f"{test}\n")
277
+ except AttributeError:
278
+ LOGFILE.write(f"{test}\n")
279
+
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, Evgeni Burovski
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.