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