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