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