Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/_lib/_testutils.py: 25%
132 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:43 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:43 +0000
1"""
2Generic test utilities.
4"""
6import os
7import re
8import sys
9import numpy as np
10import inspect
11import sysconfig
14__all__ = ['PytestTester', 'check_free_memory', '_TestPythranFunc', 'IS_MUSL']
17IS_MUSL = False
18try:
19 # Note that packaging is not a dependency, hence we need this try-except:
20 from packaging.tags import sys_tags
21 _tags = list(sys_tags())
22 if 'musllinux' in _tags[0].platform:
23 IS_MUSL = True
24except ImportError:
25 # fallback to sysconfig (might be flaky)
26 v = sysconfig.get_config_var('HOST_GNU_TYPE') or ''
27 if 'musl' in v:
28 IS_MUSL = True
31class FPUModeChangeWarning(RuntimeWarning):
32 """Warning about FPU mode change"""
33 pass
36class PytestTester:
37 """
38 Run tests for this namespace
40 ``scipy.test()`` runs tests for all of SciPy, with the default settings.
41 When used from a submodule (e.g., ``scipy.cluster.test()``, only the tests
42 for that namespace are run.
44 Parameters
45 ----------
46 label : {'fast', 'full'}, optional
47 Whether to run only the fast tests, or also those marked as slow.
48 Default is 'fast'.
49 verbose : int, optional
50 Test output verbosity. Default is 1.
51 extra_argv : list, optional
52 Arguments to pass through to Pytest.
53 doctests : bool, optional
54 Whether to run doctests or not. Default is False.
55 coverage : bool, optional
56 Whether to run tests with code coverage measurements enabled.
57 Default is False.
58 tests : list of str, optional
59 List of module names to run tests for. By default, uses the module
60 from which the ``test`` function is called.
61 parallel : int, optional
62 Run tests in parallel with pytest-xdist, if number given is larger than
63 1. Default is 1.
65 """
66 def __init__(self, module_name):
67 self.module_name = module_name
69 def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False,
70 coverage=False, tests=None, parallel=None):
71 import pytest
73 module = sys.modules[self.module_name]
74 module_path = os.path.abspath(module.__path__[0])
76 pytest_args = ['--showlocals', '--tb=short']
78 if doctests:
79 raise ValueError("Doctests not supported")
81 if extra_argv:
82 pytest_args += list(extra_argv)
84 if verbose and int(verbose) > 1:
85 pytest_args += ["-" + "v"*(int(verbose)-1)]
87 if coverage:
88 pytest_args += ["--cov=" + module_path]
90 if label == "fast":
91 pytest_args += ["-m", "not slow"]
92 elif label != "full":
93 pytest_args += ["-m", label]
95 if tests is None:
96 tests = [self.module_name]
98 if parallel is not None and parallel > 1:
99 if _pytest_has_xdist():
100 pytest_args += ['-n', str(parallel)]
101 else:
102 import warnings
103 warnings.warn('Could not run tests in parallel because '
104 'pytest-xdist plugin is not available.')
106 pytest_args += ['--pyargs'] + list(tests)
108 try:
109 code = pytest.main(pytest_args)
110 except SystemExit as exc:
111 code = exc.code
113 return (code == 0)
116class _TestPythranFunc:
117 '''
118 These are situations that can be tested in our pythran tests:
119 - A function with multiple array arguments and then
120 other positional and keyword arguments.
121 - A function with array-like keywords (e.g. `def somefunc(x0, x1=None)`.
122 Note: list/tuple input is not yet tested!
124 `self.arguments`: A dictionary which key is the index of the argument,
125 value is tuple(array value, all supported dtypes)
126 `self.partialfunc`: A function used to freeze some non-array argument
127 that of no interests in the original function
128 '''
129 ALL_INTEGER = [np.int8, np.int16, np.int32, np.int64, np.intc, np.intp]
130 ALL_FLOAT = [np.float32, np.float64]
131 ALL_COMPLEX = [np.complex64, np.complex128]
133 def setup_method(self):
134 self.arguments = {}
135 self.partialfunc = None
136 self.expected = None
138 def get_optional_args(self, func):
139 # get optional arguments with its default value,
140 # used for testing keywords
141 signature = inspect.signature(func)
142 optional_args = {}
143 for k, v in signature.parameters.items():
144 if v.default is not inspect.Parameter.empty:
145 optional_args[k] = v.default
146 return optional_args
148 def get_max_dtype_list_length(self):
149 # get the max supported dtypes list length in all arguments
150 max_len = 0
151 for arg_idx in self.arguments:
152 cur_len = len(self.arguments[arg_idx][1])
153 if cur_len > max_len:
154 max_len = cur_len
155 return max_len
157 def get_dtype(self, dtype_list, dtype_idx):
158 # get the dtype from dtype_list via index
159 # if the index is out of range, then return the last dtype
160 if dtype_idx > len(dtype_list)-1:
161 return dtype_list[-1]
162 else:
163 return dtype_list[dtype_idx]
165 def test_all_dtypes(self):
166 for type_idx in range(self.get_max_dtype_list_length()):
167 args_array = []
168 for arg_idx in self.arguments:
169 new_dtype = self.get_dtype(self.arguments[arg_idx][1],
170 type_idx)
171 args_array.append(self.arguments[arg_idx][0].astype(new_dtype))
172 self.pythranfunc(*args_array)
174 def test_views(self):
175 args_array = []
176 for arg_idx in self.arguments:
177 args_array.append(self.arguments[arg_idx][0][::-1][::-1])
178 self.pythranfunc(*args_array)
180 def test_strided(self):
181 args_array = []
182 for arg_idx in self.arguments:
183 args_array.append(np.repeat(self.arguments[arg_idx][0],
184 2, axis=0)[::2])
185 self.pythranfunc(*args_array)
188def _pytest_has_xdist():
189 """
190 Check if the pytest-xdist plugin is installed, providing parallel tests
191 """
192 # Check xdist exists without importing, otherwise pytests emits warnings
193 from importlib.util import find_spec
194 return find_spec('xdist') is not None
197def check_free_memory(free_mb):
198 """
199 Check *free_mb* of memory is available, otherwise do pytest.skip
200 """
201 import pytest
203 try:
204 mem_free = _parse_size(os.environ['SCIPY_AVAILABLE_MEM'])
205 msg = '{} MB memory required, but environment SCIPY_AVAILABLE_MEM={}'.format(
206 free_mb, os.environ['SCIPY_AVAILABLE_MEM'])
207 except KeyError:
208 mem_free = _get_mem_available()
209 if mem_free is None:
210 pytest.skip("Could not determine available memory; set SCIPY_AVAILABLE_MEM "
211 "variable to free memory in MB to run the test.")
212 msg = '{} MB memory required, but {} MB available'.format(
213 free_mb, mem_free/1e6)
215 if mem_free < free_mb * 1e6:
216 pytest.skip(msg)
219def _parse_size(size_str):
220 suffixes = {'': 1e6,
221 'b': 1.0,
222 'k': 1e3, 'M': 1e6, 'G': 1e9, 'T': 1e12,
223 'kb': 1e3, 'Mb': 1e6, 'Gb': 1e9, 'Tb': 1e12,
224 'kib': 1024.0, 'Mib': 1024.0**2, 'Gib': 1024.0**3, 'Tib': 1024.0**4}
225 m = re.match(r'^\s*(\d+)\s*({})\s*$'.format('|'.join(suffixes.keys())),
226 size_str,
227 re.I)
228 if not m or m.group(2) not in suffixes:
229 raise ValueError("Invalid size string")
231 return float(m.group(1)) * suffixes[m.group(2)]
234def _get_mem_available():
235 """
236 Get information about memory available, not counting swap.
237 """
238 try:
239 import psutil
240 return psutil.virtual_memory().available
241 except (ImportError, AttributeError):
242 pass
244 if sys.platform.startswith('linux'):
245 info = {}
246 with open('/proc/meminfo') as f:
247 for line in f:
248 p = line.split()
249 info[p[0].strip(':').lower()] = float(p[1]) * 1e3
251 if 'memavailable' in info:
252 # Linux >= 3.14
253 return info['memavailable']
254 else:
255 return info['memfree'] + info['cached']
257 return None