##// END OF EJS Templates
Fix onlyif_cmds_exist skip message
Nikita Kniazev -
Show More
@@ -1,399 +1,399 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Decorators for labeling test objects.
3 3
4 4 Decorators that merely return a modified version of the original function
5 5 object are straightforward. Decorators that return a new function object need
6 6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 7 decorator, in order to preserve metadata such as function name, setup and
8 8 teardown functions and so on - see nose.tools for more information.
9 9
10 10 This module provides a set of useful decorators meant to be ready to use in
11 11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 12 find yourself writing a new one that may be of generic use, add it here.
13 13
14 14 Included decorators:
15 15
16 16
17 17 Lightweight testing that remains unittest-compatible.
18 18
19 19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 20 function as a unittest TestCase. Then, both nose and normal unittest will
21 21 recognize it as such. This will make it easier to migrate away from Nose if
22 22 we ever need/want to while maintaining very lightweight tests.
23 23
24 24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 26 available, OR use equivalent code in IPython.external._decorators, which
27 27 we've copied verbatim from numpy.
28 28
29 29 """
30 30
31 31 # Copyright (c) IPython Development Team.
32 32 # Distributed under the terms of the Modified BSD License.
33 33
34 34 import os
35 35 import shutil
36 36 import sys
37 37 import tempfile
38 38 import unittest
39 39 import warnings
40 40 from importlib import import_module
41 41
42 42 from decorator import decorator
43 43
44 44 # Expose the unittest-driven decorators
45 45 from .ipunittest import ipdoctest, ipdocstring
46 46
47 47 # Grab the numpy-specific decorators which we keep in a file that we
48 48 # occasionally update from upstream: decorators.py is a copy of
49 49 # numpy.testing.decorators, we expose all of it here.
50 50 from IPython.external.decorators import knownfailureif
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Classes and functions
54 54 #-----------------------------------------------------------------------------
55 55
56 56 # Simple example of the basic idea
57 57 def as_unittest(func):
58 58 """Decorator to make a simple function into a normal test via unittest."""
59 59 class Tester(unittest.TestCase):
60 60 def test(self):
61 61 func()
62 62
63 63 Tester.__name__ = func.__name__
64 64
65 65 return Tester
66 66
67 67 # Utility functions
68 68
69 69 def apply_wrapper(wrapper, func):
70 70 """Apply a wrapper to a function for decoration.
71 71
72 72 This mixes Michele Simionato's decorator tool with nose's make_decorator,
73 73 to apply a wrapper in a decorator so that all nose attributes, as well as
74 74 function signature and other properties, survive the decoration cleanly.
75 75 This will ensure that wrapped functions can still be well introspected via
76 76 IPython, for example.
77 77 """
78 78 warnings.warn("The function `apply_wrapper` is deprecated since IPython 4.0",
79 79 DeprecationWarning, stacklevel=2)
80 80 import nose.tools
81 81
82 82 return decorator(wrapper,nose.tools.make_decorator(func)(wrapper))
83 83
84 84
85 85 def make_label_dec(label, ds=None):
86 86 """Factory function to create a decorator that applies one or more labels.
87 87
88 88 Parameters
89 89 ----------
90 90 label : string or sequence
91 91 One or more labels that will be applied by the decorator to the functions
92 92 it decorates. Labels are attributes of the decorated function with their
93 93 value set to True.
94 94
95 95 ds : string
96 96 An optional docstring for the resulting decorator. If not given, a
97 97 default docstring is auto-generated.
98 98
99 99 Returns
100 100 -------
101 101 A decorator.
102 102
103 103 Examples
104 104 --------
105 105
106 106 A simple labeling decorator:
107 107
108 108 >>> slow = make_label_dec('slow')
109 109 >>> slow.__doc__
110 110 "Labels a test as 'slow'."
111 111
112 112 And one that uses multiple labels and a custom docstring:
113 113
114 114 >>> rare = make_label_dec(['slow','hard'],
115 115 ... "Mix labels 'slow' and 'hard' for rare tests.")
116 116 >>> rare.__doc__
117 117 "Mix labels 'slow' and 'hard' for rare tests."
118 118
119 119 Now, let's test using this one:
120 120 >>> @rare
121 121 ... def f(): pass
122 122 ...
123 123 >>>
124 124 >>> f.slow
125 125 True
126 126 >>> f.hard
127 127 True
128 128 """
129 129
130 130 warnings.warn("The function `make_label_dec` is deprecated since IPython 4.0",
131 131 DeprecationWarning, stacklevel=2)
132 132 if isinstance(label, str):
133 133 labels = [label]
134 134 else:
135 135 labels = label
136 136
137 137 # Validate that the given label(s) are OK for use in setattr() by doing a
138 138 # dry run on a dummy function.
139 139 tmp = lambda : None
140 140 for label in labels:
141 141 setattr(tmp,label,True)
142 142
143 143 # This is the actual decorator we'll return
144 144 def decor(f):
145 145 for label in labels:
146 146 setattr(f,label,True)
147 147 return f
148 148
149 149 # Apply the user's docstring, or autogenerate a basic one
150 150 if ds is None:
151 151 ds = "Labels a test as %r." % label
152 152 decor.__doc__ = ds
153 153
154 154 return decor
155 155
156 156
157 157 def skip_iptest_but_not_pytest(f):
158 158 """
159 159 Warning this will make the test invisible to iptest.
160 160 """
161 161 import os
162 162
163 163 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
164 164 f.__test__ = False
165 165 return f
166 166
167 167
168 168 # Inspired by numpy's skipif, but uses the full apply_wrapper utility to
169 169 # preserve function metadata better and allows the skip condition to be a
170 170 # callable.
171 171 def skipif(skip_condition, msg=None):
172 172 ''' Make function raise SkipTest exception if skip_condition is true
173 173
174 174 Parameters
175 175 ----------
176 176
177 177 skip_condition : bool or callable
178 178 Flag to determine whether to skip test. If the condition is a
179 179 callable, it is used at runtime to dynamically make the decision. This
180 180 is useful for tests that may require costly imports, to delay the cost
181 181 until the test suite is actually executed.
182 182 msg : string
183 183 Message to give on raising a SkipTest exception.
184 184
185 185 Returns
186 186 -------
187 187 decorator : function
188 188 Decorator, which, when applied to a function, causes SkipTest
189 189 to be raised when the skip_condition was True, and the function
190 190 to be called normally otherwise.
191 191
192 192 Notes
193 193 -----
194 194 You will see from the code that we had to further decorate the
195 195 decorator with the nose.tools.make_decorator function in order to
196 196 transmit function name, and various other metadata.
197 197 '''
198 198
199 199 def skip_decorator(f):
200 200 # Local import to avoid a hard nose dependency and only incur the
201 201 # import time overhead at actual test-time.
202 202 import nose
203 203
204 204 # Allow for both boolean or callable skip conditions.
205 205 if callable(skip_condition):
206 206 skip_val = skip_condition
207 207 else:
208 208 skip_val = lambda : skip_condition
209 209
210 210 def get_msg(func,msg=None):
211 211 """Skip message with information about function being skipped."""
212 212 if msg is None: out = 'Test skipped due to test condition.'
213 213 else: out = msg
214 214 return "Skipping test: %s. %s" % (func.__name__,out)
215 215
216 216 # We need to define *two* skippers because Python doesn't allow both
217 217 # return with value and yield inside the same function.
218 218 def skipper_func(*args, **kwargs):
219 219 """Skipper for normal test functions."""
220 220 if skip_val():
221 221 raise nose.SkipTest(get_msg(f,msg))
222 222 else:
223 223 return f(*args, **kwargs)
224 224
225 225 def skipper_gen(*args, **kwargs):
226 226 """Skipper for test generators."""
227 227 if skip_val():
228 228 raise nose.SkipTest(get_msg(f,msg))
229 229 else:
230 230 for x in f(*args, **kwargs):
231 231 yield x
232 232
233 233 # Choose the right skipper to use when building the actual generator.
234 234 if nose.util.isgenerator(f):
235 235 skipper = skipper_gen
236 236 else:
237 237 skipper = skipper_func
238 238
239 239 return nose.tools.make_decorator(f)(skipper)
240 240
241 241 return skip_decorator
242 242
243 243 # A version with the condition set to true, common case just to attach a message
244 244 # to a skip decorator
245 245 def skip(msg=None):
246 246 """Decorator factory - mark a test function for skipping from test suite.
247 247
248 248 Parameters
249 249 ----------
250 250 msg : string
251 251 Optional message to be added.
252 252
253 253 Returns
254 254 -------
255 255 decorator : function
256 256 Decorator, which, when applied to a function, causes SkipTest
257 257 to be raised, with the optional message added.
258 258 """
259 259 if msg and not isinstance(msg, str):
260 260 raise ValueError('invalid object passed to `@skip` decorator, did you '
261 261 'meant `@skip()` with brackets ?')
262 262 return skipif(True, msg)
263 263
264 264
265 265 def onlyif(condition, msg):
266 266 """The reverse from skipif, see skipif for details."""
267 267
268 268 if callable(condition):
269 269 skip_condition = lambda : not condition()
270 270 else:
271 271 skip_condition = lambda : not condition
272 272
273 273 return skipif(skip_condition, msg)
274 274
275 275 #-----------------------------------------------------------------------------
276 276 # Utility functions for decorators
277 277 def module_not_available(module):
278 278 """Can module be imported? Returns true if module does NOT import.
279 279
280 280 This is used to make a decorator to skip tests that require module to be
281 281 available, but delay the 'import numpy' to test execution time.
282 282 """
283 283 try:
284 284 mod = import_module(module)
285 285 mod_not_avail = False
286 286 except ImportError:
287 287 mod_not_avail = True
288 288
289 289 return mod_not_avail
290 290
291 291
292 292 def decorated_dummy(dec, name):
293 293 """Return a dummy function decorated with dec, with the given name.
294 294
295 295 Examples
296 296 --------
297 297 import IPython.testing.decorators as dec
298 298 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
299 299 """
300 300 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
301 301 DeprecationWarning, stacklevel=2)
302 302 dummy = lambda: None
303 303 dummy.__name__ = name
304 304 return dec(dummy)
305 305
306 306 #-----------------------------------------------------------------------------
307 307 # Decorators for public use
308 308
309 309 # Decorators to skip certain tests on specific platforms.
310 310 skip_win32 = skipif(sys.platform == 'win32',
311 311 "This test does not run under Windows")
312 312 skip_linux = skipif(sys.platform.startswith('linux'),
313 313 "This test does not run under Linux")
314 314 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
315 315
316 316
317 317 # Decorators to skip tests if not on specific platforms.
318 318 skip_if_not_win32 = skipif(sys.platform != 'win32',
319 319 "This test only runs under Windows")
320 320 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
321 321 "This test only runs under Linux")
322 322 skip_if_not_osx = skipif(sys.platform != 'darwin',
323 323 "This test only runs under OSX")
324 324
325 325
326 326 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
327 327 os.environ.get('DISPLAY', '') == '')
328 328 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
329 329
330 330 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
331 331
332 332
333 333 # Decorators to skip certain tests on specific platform/python combinations
334 334 skip_win32_py38 = skipif(sys.version_info > (3,8) and os.name == 'nt')
335 335
336 336
337 337 # not a decorator itself, returns a dummy function to be used as setup
338 338 def skip_file_no_x11(name):
339 339 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
340 340 DeprecationWarning, stacklevel=2)
341 341 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
342 342
343 343 # Other skip decorators
344 344
345 345 # generic skip without module
346 346 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
347 347
348 348 skipif_not_numpy = skip_without('numpy')
349 349
350 350 skipif_not_matplotlib = skip_without('matplotlib')
351 351
352 352 skipif_not_sympy = skip_without('sympy')
353 353
354 354 skip_known_failure = knownfailureif(True,'This test is known to fail')
355 355
356 356 # A null 'decorator', useful to make more readable code that needs to pick
357 357 # between different decorators based on OS or other conditions
358 358 null_deco = lambda f: f
359 359
360 360 # Some tests only run where we can use unicode paths. Note that we can't just
361 361 # check os.path.supports_unicode_filenames, which is always False on Linux.
362 362 try:
363 363 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
364 364 except UnicodeEncodeError:
365 365 unicode_paths = False
366 366 else:
367 367 unicode_paths = True
368 368 f.close()
369 369
370 370 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
371 371 "where we can use unicode in filenames."))
372 372
373 373
374 374 def onlyif_cmds_exist(*commands):
375 375 """
376 376 Decorator to skip test when at least one of `commands` is not found.
377 377 """
378 378 for cmd in commands:
379 reason = "This test runs only if command '{cmd}' is installed"
379 reason = f"This test runs only if command '{cmd}' is installed"
380 380 if not shutil.which(cmd):
381 381 if os.environ.get("IPTEST_WORKING_DIR", None) is not None:
382 382 return skip(reason)
383 383 else:
384 384 import pytest
385 385
386 386 return pytest.mark.skip(reason=reason)
387 387 return null_deco
388 388
389 389 def onlyif_any_cmd_exists(*commands):
390 390 """
391 391 Decorator to skip test unless at least one of `commands` is found.
392 392 """
393 393 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
394 394 DeprecationWarning, stacklevel=2)
395 395 for cmd in commands:
396 396 if shutil.which(cmd):
397 397 return null_deco
398 398 return skip("This test runs only if one of the commands {0} "
399 399 "is installed".format(commands))
General Comments 0
You need to be logged in to leave comments. Login now