Coverage for /usr/lib/python3/dist-packages/scipy/_lib/_testutils.py: 22%

121 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1""" 

2Generic test utilities. 

3 

4""" 

5 

6import os 

7import re 

8import sys 

9import numpy as np 

10import inspect 

11 

12 

13__all__ = ['PytestTester', 'check_free_memory', '_TestPythranFunc'] 

14 

15 

16class FPUModeChangeWarning(RuntimeWarning): 

17 """Warning about FPU mode change""" 

18 pass 

19 

20 

21class PytestTester: 

22 """ 

23 Run tests for this namespace 

24 

25 ``scipy.test()`` runs tests for all of SciPy, with the default settings. 

26 When used from a submodule (e.g., ``scipy.cluster.test()``, only the tests 

27 for that namespace are run. 

28 

29 Parameters 

30 ---------- 

31 label : {'fast', 'full'}, optional 

32 Whether to run only the fast tests, or also those marked as slow. 

33 Default is 'fast'. 

34 verbose : int, optional 

35 Test output verbosity. Default is 1. 

36 extra_argv : list, optional 

37 Arguments to pass through to Pytest. 

38 doctests : bool, optional 

39 Whether to run doctests or not. Default is False. 

40 coverage : bool, optional 

41 Whether to run tests with code coverage measurements enabled. 

42 Default is False. 

43 tests : list of str, optional 

44 List of module names to run tests for. By default, uses the module 

45 from which the ``test`` function is called. 

46 parallel : int, optional 

47 Run tests in parallel with pytest-xdist, if number given is larger than 

48 1. Default is 1. 

49 

50 """ 

51 def __init__(self, module_name): 

52 self.module_name = module_name 

53 

54 def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False, 

55 coverage=False, tests=None, parallel=None): 

56 import pytest 

57 

58 module = sys.modules[self.module_name] 

59 module_path = os.path.abspath(module.__path__[0]) 

60 

61 pytest_args = ['--showlocals', '--tb=short'] 

62 

63 if doctests: 

64 raise ValueError("Doctests not supported") 

65 

66 if extra_argv: 

67 pytest_args += list(extra_argv) 

68 

69 if verbose and int(verbose) > 1: 

70 pytest_args += ["-" + "v"*(int(verbose)-1)] 

71 

72 if coverage: 

73 pytest_args += ["--cov=" + module_path] 

74 

75 if label == "fast": 

76 pytest_args += ["-m", "not slow"] 

77 elif label != "full": 

78 pytest_args += ["-m", label] 

79 

80 if tests is None: 

81 tests = [self.module_name] 

82 

83 if parallel is not None and parallel > 1: 

84 if _pytest_has_xdist(): 

85 pytest_args += ['-n', str(parallel)] 

86 else: 

87 import warnings 

88 warnings.warn('Could not run tests in parallel because ' 

89 'pytest-xdist plugin is not available.') 

90 

91 pytest_args += ['--pyargs'] + list(tests) 

92 

93 try: 

94 code = pytest.main(pytest_args) 

95 except SystemExit as exc: 

96 code = exc.code 

97 

98 return (code == 0) 

99 

100 

101class _TestPythranFunc: 

102 ''' 

103 These are situations that can be tested in our pythran tests: 

104 - A function with multiple array arguments and then 

105 other positional and keyword arguments. 

106 - A function with array-like keywords (e.g. `def somefunc(x0, x1=None)`. 

107 Note: list/tuple input is not yet tested! 

108 

109 `self.arguments`: A dictionary which key is the index of the argument, 

110 value is tuple(array value, all supported dtypes) 

111 `self.partialfunc`: A function used to freeze some non-array argument 

112 that of no interests in the original function 

113 ''' 

114 ALL_INTEGER = [np.int8, np.int16, np.int32, np.int64, np.intc, np.intp] 

115 ALL_FLOAT = [np.float32, np.float64] 

116 ALL_COMPLEX = [np.complex64, np.complex128] 

117 

118 def setup_method(self): 

119 self.arguments = {} 

120 self.partialfunc = None 

121 self.expected = None 

122 

123 def get_optional_args(self, func): 

124 # get optional arguments with its default value, 

125 # used for testing keywords 

126 signature = inspect.signature(func) 

127 optional_args = {} 

128 for k, v in signature.parameters.items(): 

129 if v.default is not inspect.Parameter.empty: 

130 optional_args[k] = v.default 

131 return optional_args 

132 

133 def get_max_dtype_list_length(self): 

134 # get the max supported dtypes list length in all arguments 

135 max_len = 0 

136 for arg_idx in self.arguments: 

137 cur_len = len(self.arguments[arg_idx][1]) 

138 if cur_len > max_len: 

139 max_len = cur_len 

140 return max_len 

141 

142 def get_dtype(self, dtype_list, dtype_idx): 

143 # get the dtype from dtype_list via index 

144 # if the index is out of range, then return the last dtype 

145 if dtype_idx > len(dtype_list)-1: 

146 return dtype_list[-1] 

147 else: 

148 return dtype_list[dtype_idx] 

149 

150 def test_all_dtypes(self): 

151 for type_idx in range(self.get_max_dtype_list_length()): 

152 args_array = [] 

153 for arg_idx in self.arguments: 

154 new_dtype = self.get_dtype(self.arguments[arg_idx][1], 

155 type_idx) 

156 args_array.append(self.arguments[arg_idx][0].astype(new_dtype)) 

157 self.pythranfunc(*args_array) 

158 

159 def test_views(self): 

160 args_array = [] 

161 for arg_idx in self.arguments: 

162 args_array.append(self.arguments[arg_idx][0][::-1][::-1]) 

163 self.pythranfunc(*args_array) 

164 

165 def test_strided(self): 

166 args_array = [] 

167 for arg_idx in self.arguments: 

168 args_array.append(np.repeat(self.arguments[arg_idx][0], 

169 2, axis=0)[::2]) 

170 self.pythranfunc(*args_array) 

171 

172 

173def _pytest_has_xdist(): 

174 """ 

175 Check if the pytest-xdist plugin is installed, providing parallel tests 

176 """ 

177 # Check xdist exists without importing, otherwise pytests emits warnings 

178 from importlib.util import find_spec 

179 return find_spec('xdist') is not None 

180 

181 

182def check_free_memory(free_mb): 

183 """ 

184 Check *free_mb* of memory is available, otherwise do pytest.skip 

185 """ 

186 import pytest 

187 

188 try: 

189 mem_free = _parse_size(os.environ['SCIPY_AVAILABLE_MEM']) 

190 msg = '{} MB memory required, but environment SCIPY_AVAILABLE_MEM={}'.format( 

191 free_mb, os.environ['SCIPY_AVAILABLE_MEM']) 

192 except KeyError: 

193 mem_free = _get_mem_available() 

194 if mem_free is None: 

195 pytest.skip("Could not determine available memory; set SCIPY_AVAILABLE_MEM " 

196 "variable to free memory in MB to run the test.") 

197 msg = '{} MB memory required, but {} MB available'.format( 

198 free_mb, mem_free/1e6) 

199 

200 if mem_free < free_mb * 1e6: 

201 pytest.skip(msg) 

202 

203 

204def _parse_size(size_str): 

205 suffixes = {'': 1e6, 

206 'b': 1.0, 

207 'k': 1e3, 'M': 1e6, 'G': 1e9, 'T': 1e12, 

208 'kb': 1e3, 'Mb': 1e6, 'Gb': 1e9, 'Tb': 1e12, 

209 'kib': 1024.0, 'Mib': 1024.0**2, 'Gib': 1024.0**3, 'Tib': 1024.0**4} 

210 m = re.match(r'^\s*(\d+)\s*({})\s*$'.format('|'.join(suffixes.keys())), 

211 size_str, 

212 re.I) 

213 if not m or m.group(2) not in suffixes: 

214 raise ValueError("Invalid size string") 

215 

216 return float(m.group(1)) * suffixes[m.group(2)] 

217 

218 

219def _get_mem_available(): 

220 """ 

221 Get information about memory available, not counting swap. 

222 """ 

223 try: 

224 import psutil 

225 return psutil.virtual_memory().available 

226 except (ImportError, AttributeError): 

227 pass 

228 

229 if sys.platform.startswith('linux'): 

230 info = {} 

231 with open('/proc/meminfo') as f: 

232 for line in f: 

233 p = line.split() 

234 info[p[0].strip(':').lower()] = float(p[1]) * 1e3 

235 

236 if 'memavailable' in info: 

237 # Linux >= 3.14 

238 return info['memavailable'] 

239 else: 

240 return info['memfree'] + info['cached'] 

241 

242 return None