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

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 

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 

25 

26 

27class FPUModeChangeWarning(RuntimeWarning): 

28 """Warning about FPU mode change""" 

29 pass 

30 

31 

32class PytestTester: 

33 """ 

34 Run tests for this namespace 

35 

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. 

39 

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. 

60 

61 """ 

62 def __init__(self, module_name): 

63 self.module_name = module_name 

64 

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

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

67 import pytest 

68 

69 module = sys.modules[self.module_name] 

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

71 

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

73 

74 if doctests: 

75 raise ValueError("Doctests not supported") 

76 

77 if extra_argv: 

78 pytest_args += list(extra_argv) 

79 

80 if verbose and int(verbose) > 1: 

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

82 

83 if coverage: 

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

85 

86 if label == "fast": 

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

88 elif label != "full": 

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

90 

91 if tests is None: 

92 tests = [self.module_name] 

93 

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) 

102 

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

104 

105 try: 

106 code = pytest.main(pytest_args) 

107 except SystemExit as exc: 

108 code = exc.code 

109 

110 return (code == 0) 

111 

112 

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! 

120 

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] 

129 

130 def setup_method(self): 

131 self.arguments = {} 

132 self.partialfunc = None 

133 self.expected = None 

134 

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 

144 

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 

153 

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] 

161 

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) 

170 

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) 

176 

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) 

183 

184 

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 

192 

193 

194def check_free_memory(free_mb): 

195 """ 

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

197 """ 

198 import pytest 

199 

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' 

210 

211 if mem_free < free_mb * 1e6: 

212 pytest.skip(msg) 

213 

214 

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

226 

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

228 

229 

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 

239 

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 

246 

247 if 'memavailable' in info: 

248 # Linux >= 3.14 

249 return info['memavailable'] 

250 else: 

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

252 

253 return None