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