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