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

1""" 

2Generic test utilities. 

3 

4""" 

5 

6import os 

7import re 

8import sys 

9import numpy as np 

10import inspect 

11import sysconfig 

12 

13 

14__all__ = ['PytestTester', 'check_free_memory', '_TestPythranFunc', 'IS_MUSL'] 

15 

16 

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 

29 

30 

31class FPUModeChangeWarning(RuntimeWarning): 

32 """Warning about FPU mode change""" 

33 pass 

34 

35 

36class PytestTester: 

37 """ 

38 Run tests for this namespace 

39 

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. 

43 

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. 

64 

65 """ 

66 def __init__(self, module_name): 

67 self.module_name = module_name 

68 

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

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

71 import pytest 

72 

73 module = sys.modules[self.module_name] 

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

75 

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

77 

78 if doctests: 

79 raise ValueError("Doctests not supported") 

80 

81 if extra_argv: 

82 pytest_args += list(extra_argv) 

83 

84 if verbose and int(verbose) > 1: 

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

86 

87 if coverage: 

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

89 

90 if label == "fast": 

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

92 elif label != "full": 

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

94 

95 if tests is None: 

96 tests = [self.module_name] 

97 

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.') 

105 

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

107 

108 try: 

109 code = pytest.main(pytest_args) 

110 except SystemExit as exc: 

111 code = exc.code 

112 

113 return (code == 0) 

114 

115 

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! 

123 

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] 

132 

133 def setup_method(self): 

134 self.arguments = {} 

135 self.partialfunc = None 

136 self.expected = None 

137 

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 

147 

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 

156 

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] 

164 

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) 

173 

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) 

179 

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) 

186 

187 

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 

195 

196 

197def check_free_memory(free_mb): 

198 """ 

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

200 """ 

201 import pytest 

202 

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) 

214 

215 if mem_free < free_mb * 1e6: 

216 pytest.skip(msg) 

217 

218 

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") 

230 

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

232 

233 

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 

243 

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 

250 

251 if 'memavailable' in info: 

252 # Linux >= 3.14 

253 return info['memavailable'] 

254 else: 

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

256 

257 return None