##// END OF EJS Templates
Update decorators.py
Laurent Gautier -
Show More
@@ -1,376 +1,376 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 *
50 from IPython.external.decorators import *
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 # 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
158 # 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
159 # callable.
159 # callable.
160 def skipif(skip_condition, msg=None):
160 def skipif(skip_condition, msg=None):
161 ''' Make function raise SkipTest exception if skip_condition is true
161 ''' Make function raise SkipTest exception if skip_condition is true
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165
165
166 skip_condition : bool or callable
166 skip_condition : bool or callable
167 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
168 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
169 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
170 until the test suite is actually executed.
170 until the test suite is actually executed.
171 msg : string
171 msg : string
172 Message to give on raising a SkipTest exception.
172 Message to give on raising a SkipTest exception.
173
173
174 Returns
174 Returns
175 -------
175 -------
176 decorator : function
176 decorator : function
177 Decorator, which, when applied to a function, causes SkipTest
177 Decorator, which, when applied to a function, causes SkipTest
178 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
179 to be called normally otherwise.
179 to be called normally otherwise.
180
180
181 Notes
181 Notes
182 -----
182 -----
183 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
184 decorator with the nose.tools.make_decorator function in order to
184 decorator with the nose.tools.make_decorator function in order to
185 transmit function name, and various other metadata.
185 transmit function name, and various other metadata.
186 '''
186 '''
187
187
188 def skip_decorator(f):
188 def skip_decorator(f):
189 # 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
190 # import time overhead at actual test-time.
190 # import time overhead at actual test-time.
191 import nose
191 import nose
192
192
193 # Allow for both boolean or callable skip conditions.
193 # Allow for both boolean or callable skip conditions.
194 if callable(skip_condition):
194 if callable(skip_condition):
195 skip_val = skip_condition
195 skip_val = skip_condition
196 else:
196 else:
197 skip_val = lambda : skip_condition
197 skip_val = lambda : skip_condition
198
198
199 def get_msg(func,msg=None):
199 def get_msg(func,msg=None):
200 """Skip message with information about function being skipped."""
200 """Skip message with information about function being skipped."""
201 if msg is None: out = 'Test skipped due to test condition.'
201 if msg is None: out = 'Test skipped due to test condition.'
202 else: out = msg
202 else: out = msg
203 return "Skipping test: %s. %s" % (func.__name__,out)
203 return "Skipping test: %s. %s" % (func.__name__,out)
204
204
205 # 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
206 # return with value and yield inside the same function.
206 # return with value and yield inside the same function.
207 def skipper_func(*args, **kwargs):
207 def skipper_func(*args, **kwargs):
208 """Skipper for normal test functions."""
208 """Skipper for normal test functions."""
209 if skip_val():
209 if skip_val():
210 raise nose.SkipTest(get_msg(f,msg))
210 raise nose.SkipTest(get_msg(f,msg))
211 else:
211 else:
212 return f(*args, **kwargs)
212 return f(*args, **kwargs)
213
213
214 def skipper_gen(*args, **kwargs):
214 def skipper_gen(*args, **kwargs):
215 """Skipper for test generators."""
215 """Skipper for test generators."""
216 if skip_val():
216 if skip_val():
217 raise nose.SkipTest(get_msg(f,msg))
217 raise nose.SkipTest(get_msg(f,msg))
218 else:
218 else:
219 for x in f(*args, **kwargs):
219 for x in f(*args, **kwargs):
220 yield x
220 yield x
221
221
222 # Choose the right skipper to use when building the actual generator.
222 # Choose the right skipper to use when building the actual generator.
223 if nose.util.isgenerator(f):
223 if nose.util.isgenerator(f):
224 skipper = skipper_gen
224 skipper = skipper_gen
225 else:
225 else:
226 skipper = skipper_func
226 skipper = skipper_func
227
227
228 return nose.tools.make_decorator(f)(skipper)
228 return nose.tools.make_decorator(f)(skipper)
229
229
230 return skip_decorator
230 return skip_decorator
231
231
232 # 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
233 # to a skip decorator
233 # to a skip decorator
234 def skip(msg=None):
234 def skip(msg=None):
235 """Decorator factory - mark a test function for skipping from test suite.
235 """Decorator factory - mark a test function for skipping from test suite.
236
236
237 Parameters
237 Parameters
238 ----------
238 ----------
239 msg : string
239 msg : string
240 Optional message to be added.
240 Optional message to be added.
241
241
242 Returns
242 Returns
243 -------
243 -------
244 decorator : function
244 decorator : function
245 Decorator, which, when applied to a function, causes SkipTest
245 Decorator, which, when applied to a function, causes SkipTest
246 to be raised, with the optional message added.
246 to be raised, with the optional message added.
247 """
247 """
248
248
249 return skipif(True,msg)
249 return skipif(True,msg)
250
250
251
251
252 def onlyif(condition, msg):
252 def onlyif(condition, msg):
253 """The reverse from skipif, see skipif for details."""
253 """The reverse from skipif, see skipif for details."""
254
254
255 if callable(condition):
255 if callable(condition):
256 skip_condition = lambda : not condition()
256 skip_condition = lambda : not condition()
257 else:
257 else:
258 skip_condition = lambda : not condition
258 skip_condition = lambda : not condition
259
259
260 return skipif(skip_condition, msg)
260 return skipif(skip_condition, msg)
261
261
262 #-----------------------------------------------------------------------------
262 #-----------------------------------------------------------------------------
263 # Utility functions for decorators
263 # Utility functions for decorators
264 def module_not_available(module):
264 def module_not_available(module):
265 """Can module be imported? Returns true if module does NOT import.
265 """Can module be imported? Returns true if module does NOT import.
266
266
267 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
268 available, but delay the 'import numpy' to test execution time.
268 available, but delay the 'import numpy' to test execution time.
269 """
269 """
270 try:
270 try:
271 mod = import_module(module)
271 mod = import_module(module)
272 mod_not_avail = False
272 mod_not_avail = False
273 except ImportError:
273 except ImportError:
274 mod_not_avail = True
274 mod_not_avail = True
275
275
276 return mod_not_avail
276 return mod_not_avail
277
277
278
278
279 def decorated_dummy(dec, name):
279 def decorated_dummy(dec, name):
280 """Return a dummy function decorated with dec, with the given name.
280 """Return a dummy function decorated with dec, with the given name.
281
281
282 Examples
282 Examples
283 --------
283 --------
284 import IPython.testing.decorators as dec
284 import IPython.testing.decorators as dec
285 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
285 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
286 """
286 """
287 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
287 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
288 DeprecationWarning, stacklevel=2)
288 DeprecationWarning, stacklevel=2)
289 dummy = lambda: None
289 dummy = lambda: None
290 dummy.__name__ = name
290 dummy.__name__ = name
291 return dec(dummy)
291 return dec(dummy)
292
292
293 #-----------------------------------------------------------------------------
293 #-----------------------------------------------------------------------------
294 # Decorators for public use
294 # Decorators for public use
295
295
296 # Decorators to skip certain tests on specific platforms.
296 # Decorators to skip certain tests on specific platforms.
297 skip_win32 = skipif(sys.platform == 'win32',
297 skip_win32 = skipif(sys.platform == 'win32',
298 "This test does not run under Windows")
298 "This test does not run under Windows")
299 skip_linux = skipif(sys.platform.startswith('linux'),
299 skip_linux = skipif(sys.platform.startswith('linux'),
300 "This test does not run under Linux")
300 "This test does not run under Linux")
301 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
301 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
302
302
303
303
304 # Decorators to skip tests if not on specific platforms.
304 # Decorators to skip tests if not on specific platforms.
305 skip_if_not_win32 = skipif(sys.platform != 'win32',
305 skip_if_not_win32 = skipif(sys.platform != 'win32',
306 "This test only runs under Windows")
306 "This test only runs under Windows")
307 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
307 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
308 "This test only runs under Linux")
308 "This test only runs under Linux")
309 skip_if_not_osx = skipif(sys.platform != 'darwin',
309 skip_if_not_osx = skipif(sys.platform != 'darwin',
310 "This test only runs under OSX")
310 "This test only runs under OSX")
311
311
312
312
313 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
313 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
314 os.environ.get('DISPLAY', '') == '')
314 os.environ.get('DISPLAY', '') == '')
315 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
315 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
316
316
317 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
317 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
318
318
319 # not a decorator itself, returns a dummy function to be used as setup
319 # not a decorator itself, returns a dummy function to be used as setup
320 def skip_file_no_x11(name):
320 def skip_file_no_x11(name):
321 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
321 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
322 DeprecationWarning, stacklevel=2)
322 DeprecationWarning, stacklevel=2)
323 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
323 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
324
324
325 # Other skip decorators
325 # Other skip decorators
326
326
327 # generic skip without module
327 # generic skip without module
328 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
328 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
329
329
330 skipif_not_numpy = skip_without('numpy')
330 skipif_not_numpy = skip_without('numpy')
331
331
332 skipif_not_matplotlib = skip_without('matplotlib')
332 skipif_not_matplotlib = skip_without('matplotlib')
333
333
334 skipif_not_sympy = skip_without('sympy')
334 skipif_not_sympy = skip_without('sympy')
335
335
336 skip_known_failure = knownfailureif(True,'This test is known to fail')
336 skip_known_failure = dec.knownfailureif(True,'This test is known to fail')
337
337
338 # A null 'decorator', useful to make more readable code that needs to pick
338 # A null 'decorator', useful to make more readable code that needs to pick
339 # between different decorators based on OS or other conditions
339 # between different decorators based on OS or other conditions
340 null_deco = lambda f: f
340 null_deco = lambda f: f
341
341
342 # Some tests only run where we can use unicode paths. Note that we can't just
342 # Some tests only run where we can use unicode paths. Note that we can't just
343 # check os.path.supports_unicode_filenames, which is always False on Linux.
343 # check os.path.supports_unicode_filenames, which is always False on Linux.
344 try:
344 try:
345 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
345 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
346 except UnicodeEncodeError:
346 except UnicodeEncodeError:
347 unicode_paths = False
347 unicode_paths = False
348 else:
348 else:
349 unicode_paths = True
349 unicode_paths = True
350 f.close()
350 f.close()
351
351
352 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
352 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
353 "where we can use unicode in filenames."))
353 "where we can use unicode in filenames."))
354
354
355
355
356 def onlyif_cmds_exist(*commands):
356 def onlyif_cmds_exist(*commands):
357 """
357 """
358 Decorator to skip test when at least one of `commands` is not found.
358 Decorator to skip test when at least one of `commands` is not found.
359 """
359 """
360 for cmd in commands:
360 for cmd in commands:
361 if not shutil.which(cmd):
361 if not shutil.which(cmd):
362 return skip("This test runs only if command '{0}' "
362 return skip("This test runs only if command '{0}' "
363 "is installed".format(cmd))
363 "is installed".format(cmd))
364 return null_deco
364 return null_deco
365
365
366 def onlyif_any_cmd_exists(*commands):
366 def onlyif_any_cmd_exists(*commands):
367 """
367 """
368 Decorator to skip test unless at least one of `commands` is found.
368 Decorator to skip test unless at least one of `commands` is found.
369 """
369 """
370 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
370 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
371 DeprecationWarning, stacklevel=2)
371 DeprecationWarning, stacklevel=2)
372 for cmd in commands:
372 for cmd in commands:
373 if shutil.which(cmd):
373 if shutil.which(cmd):
374 return null_deco
374 return null_deco
375 return skip("This test runs only if one of the commands {0} "
375 return skip("This test runs only if one of the commands {0} "
376 "is installed".format(commands))
376 "is installed".format(commands))
General Comments 0
You need to be logged in to leave comments. Login now