##// END OF EJS Templates
Change doctest that was failing due to python2/3 print syntax issues.
Fernando Perez -
Show More
@@ -1,358 +1,360 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 inspect
51 import inspect
52 import sys
52 import sys
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, ParametricTestCase
63 from _paramtestpy2 import parametric, ParametricTestCase
64 else:
64 else:
65 from _paramtestpy3 import parametric, ParametricTestCase
65 from _paramtestpy3 import parametric, ParametricTestCase
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 >>> slow = make_label_dec('slow')
132 >>> print slow.__doc__
133 Labels a test as 'slow'.
134
131
132 >>> slow = make_label_dec('slow')
133 >>> slow.__doc__
134 "Labels a test as 'slow'."
135
135 And one that uses multiple labels and a custom docstring:
136 And one that uses multiple labels and a custom docstring:
137
136 >>> rare = make_label_dec(['slow','hard'],
138 >>> rare = make_label_dec(['slow','hard'],
137 ... "Mix labels 'slow' and 'hard' for rare tests.")
139 ... "Mix labels 'slow' and 'hard' for rare tests.")
138 >>> print rare.__doc__
140 >>> rare.__doc__
139 Mix labels 'slow' and 'hard' for rare tests.
141 "Mix labels 'slow' and 'hard' for rare tests."
140
142
141 Now, let's test using this one:
143 Now, let's test using this one:
142 >>> @rare
144 >>> @rare
143 ... def f(): pass
145 ... def f(): pass
144 ...
146 ...
145 >>>
147 >>>
146 >>> f.slow
148 >>> f.slow
147 True
149 True
148 >>> f.hard
150 >>> f.hard
149 True
151 True
150 """
152 """
151
153
152 if isinstance(label,basestring):
154 if isinstance(label,basestring):
153 labels = [label]
155 labels = [label]
154 else:
156 else:
155 labels = label
157 labels = label
156
158
157 # 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
158 # dry run on a dummy function.
160 # dry run on a dummy function.
159 tmp = lambda : None
161 tmp = lambda : None
160 for label in labels:
162 for label in labels:
161 setattr(tmp,label,True)
163 setattr(tmp,label,True)
162
164
163 # This is the actual decorator we'll return
165 # This is the actual decorator we'll return
164 def decor(f):
166 def decor(f):
165 for label in labels:
167 for label in labels:
166 setattr(f,label,True)
168 setattr(f,label,True)
167 return f
169 return f
168
170
169 # Apply the user's docstring, or autogenerate a basic one
171 # Apply the user's docstring, or autogenerate a basic one
170 if ds is None:
172 if ds is None:
171 ds = "Labels a test as %r." % label
173 ds = "Labels a test as %r." % label
172 decor.__doc__ = ds
174 decor.__doc__ = ds
173
175
174 return decor
176 return decor
175
177
176
178
177 # 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
178 # 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
179 # callable.
181 # callable.
180 def skipif(skip_condition, msg=None):
182 def skipif(skip_condition, msg=None):
181 ''' Make function raise SkipTest exception if skip_condition is true
183 ''' Make function raise SkipTest exception if skip_condition is true
182
184
183 Parameters
185 Parameters
184 ----------
186 ----------
185 skip_condition : bool or callable.
187 skip_condition : bool or callable.
186 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
187 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
188 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
189 until the test suite is actually executed.
191 until the test suite is actually executed.
190 msg : string
192 msg : string
191 Message to give on raising a SkipTest exception
193 Message to give on raising a SkipTest exception
192
194
193 Returns
195 Returns
194 -------
196 -------
195 decorator : function
197 decorator : function
196 Decorator, which, when applied to a function, causes SkipTest
198 Decorator, which, when applied to a function, causes SkipTest
197 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
198 to be called normally otherwise.
200 to be called normally otherwise.
199
201
200 Notes
202 Notes
201 -----
203 -----
202 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
203 decorator with the nose.tools.make_decorator function in order to
205 decorator with the nose.tools.make_decorator function in order to
204 transmit function name, and various other metadata.
206 transmit function name, and various other metadata.
205 '''
207 '''
206
208
207 def skip_decorator(f):
209 def skip_decorator(f):
208 # 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
209 # import time overhead at actual test-time.
211 # import time overhead at actual test-time.
210 import nose
212 import nose
211
213
212 # Allow for both boolean or callable skip conditions.
214 # Allow for both boolean or callable skip conditions.
213 if callable(skip_condition):
215 if callable(skip_condition):
214 skip_val = skip_condition
216 skip_val = skip_condition
215 else:
217 else:
216 skip_val = lambda : skip_condition
218 skip_val = lambda : skip_condition
217
219
218 def get_msg(func,msg=None):
220 def get_msg(func,msg=None):
219 """Skip message with information about function being skipped."""
221 """Skip message with information about function being skipped."""
220 if msg is None: out = 'Test skipped due to test condition.'
222 if msg is None: out = 'Test skipped due to test condition.'
221 else: out = msg
223 else: out = msg
222 return "Skipping test: %s. %s" % (func.__name__,out)
224 return "Skipping test: %s. %s" % (func.__name__,out)
223
225
224 # 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
225 # return with value and yield inside the same function.
227 # return with value and yield inside the same function.
226 def skipper_func(*args, **kwargs):
228 def skipper_func(*args, **kwargs):
227 """Skipper for normal test functions."""
229 """Skipper for normal test functions."""
228 if skip_val():
230 if skip_val():
229 raise nose.SkipTest(get_msg(f,msg))
231 raise nose.SkipTest(get_msg(f,msg))
230 else:
232 else:
231 return f(*args, **kwargs)
233 return f(*args, **kwargs)
232
234
233 def skipper_gen(*args, **kwargs):
235 def skipper_gen(*args, **kwargs):
234 """Skipper for test generators."""
236 """Skipper for test generators."""
235 if skip_val():
237 if skip_val():
236 raise nose.SkipTest(get_msg(f,msg))
238 raise nose.SkipTest(get_msg(f,msg))
237 else:
239 else:
238 for x in f(*args, **kwargs):
240 for x in f(*args, **kwargs):
239 yield x
241 yield x
240
242
241 # Choose the right skipper to use when building the actual generator.
243 # Choose the right skipper to use when building the actual generator.
242 if nose.util.isgenerator(f):
244 if nose.util.isgenerator(f):
243 skipper = skipper_gen
245 skipper = skipper_gen
244 else:
246 else:
245 skipper = skipper_func
247 skipper = skipper_func
246
248
247 return nose.tools.make_decorator(f)(skipper)
249 return nose.tools.make_decorator(f)(skipper)
248
250
249 return skip_decorator
251 return skip_decorator
250
252
251 # 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
252 # to a skip decorator
254 # to a skip decorator
253 def skip(msg=None):
255 def skip(msg=None):
254 """Decorator factory - mark a test function for skipping from test suite.
256 """Decorator factory - mark a test function for skipping from test suite.
255
257
256 Parameters
258 Parameters
257 ----------
259 ----------
258 msg : string
260 msg : string
259 Optional message to be added.
261 Optional message to be added.
260
262
261 Returns
263 Returns
262 -------
264 -------
263 decorator : function
265 decorator : function
264 Decorator, which, when applied to a function, causes SkipTest
266 Decorator, which, when applied to a function, causes SkipTest
265 to be raised, with the optional message added.
267 to be raised, with the optional message added.
266 """
268 """
267
269
268 return skipif(True,msg)
270 return skipif(True,msg)
269
271
270
272
271 def onlyif(condition, msg):
273 def onlyif(condition, msg):
272 """The reverse from skipif, see skipif for details."""
274 """The reverse from skipif, see skipif for details."""
273
275
274 if callable(condition):
276 if callable(condition):
275 skip_condition = lambda : not condition()
277 skip_condition = lambda : not condition()
276 else:
278 else:
277 skip_condition = lambda : not condition
279 skip_condition = lambda : not condition
278
280
279 return skipif(skip_condition, msg)
281 return skipif(skip_condition, msg)
280
282
281 #-----------------------------------------------------------------------------
283 #-----------------------------------------------------------------------------
282 # Utility functions for decorators
284 # Utility functions for decorators
283 def module_not_available(module):
285 def module_not_available(module):
284 """Can module be imported? Returns true if module does NOT import.
286 """Can module be imported? Returns true if module does NOT import.
285
287
286 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
287 available, but delay the 'import numpy' to test execution time.
289 available, but delay the 'import numpy' to test execution time.
288 """
290 """
289 try:
291 try:
290 mod = __import__(module)
292 mod = __import__(module)
291 mod_not_avail = False
293 mod_not_avail = False
292 except ImportError:
294 except ImportError:
293 mod_not_avail = True
295 mod_not_avail = True
294
296
295 return mod_not_avail
297 return mod_not_avail
296
298
297 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
298 # Decorators for public use
300 # Decorators for public use
299
301
300 # Decorators to skip certain tests on specific platforms.
302 # Decorators to skip certain tests on specific platforms.
301 skip_win32 = skipif(sys.platform == 'win32',
303 skip_win32 = skipif(sys.platform == 'win32',
302 "This test does not run under Windows")
304 "This test does not run under Windows")
303 skip_linux = skipif(sys.platform.startswith('linux'),
305 skip_linux = skipif(sys.platform.startswith('linux'),
304 "This test does not run under Linux")
306 "This test does not run under Linux")
305 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
307 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
306
308
307
309
308 # Decorators to skip tests if not on specific platforms.
310 # Decorators to skip tests if not on specific platforms.
309 skip_if_not_win32 = skipif(sys.platform != 'win32',
311 skip_if_not_win32 = skipif(sys.platform != 'win32',
310 "This test only runs under Windows")
312 "This test only runs under Windows")
311 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
313 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
312 "This test only runs under Linux")
314 "This test only runs under Linux")
313 skip_if_not_osx = skipif(sys.platform != 'darwin',
315 skip_if_not_osx = skipif(sys.platform != 'darwin',
314 "This test only runs under OSX")
316 "This test only runs under OSX")
315
317
316 # Other skip decorators
318 # Other skip decorators
317
319
318 # generic skip without module
320 # generic skip without module
319 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
321 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
320
322
321 skipif_not_numpy = skip_without('numpy')
323 skipif_not_numpy = skip_without('numpy')
322
324
323 skipif_not_matplotlib = skip_without('matplotlib')
325 skipif_not_matplotlib = skip_without('matplotlib')
324
326
325 skipif_not_sympy = skip_without('sympy')
327 skipif_not_sympy = skip_without('sympy')
326
328
327 skip_known_failure = knownfailureif(True,'This test is known to fail')
329 skip_known_failure = knownfailureif(True,'This test is known to fail')
328
330
329 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
331 known_failure_py3 = knownfailureif(sys.version_info[0] >= 3,
330 'This test is known to fail on Python 3.')
332 'This test is known to fail on Python 3.')
331
333
332 # A null 'decorator', useful to make more readable code that needs to pick
334 # A null 'decorator', useful to make more readable code that needs to pick
333 # between different decorators based on OS or other conditions
335 # between different decorators based on OS or other conditions
334 null_deco = lambda f: f
336 null_deco = lambda f: f
335
337
336 # Some tests only run where we can use unicode paths. Note that we can't just
338 # Some tests only run where we can use unicode paths. Note that we can't just
337 # check os.path.supports_unicode_filenames, which is always False on Linux.
339 # check os.path.supports_unicode_filenames, which is always False on Linux.
338 try:
340 try:
339 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
341 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
340 except UnicodeEncodeError:
342 except UnicodeEncodeError:
341 unicode_paths = False
343 unicode_paths = False
342 else:
344 else:
343 unicode_paths = True
345 unicode_paths = True
344 f.close()
346 f.close()
345
347
346 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
348 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
347 "where we can use unicode in filenames."))
349 "where we can use unicode in filenames."))
348
350
349
351
350 def onlyif_cmds_exist(*commands):
352 def onlyif_cmds_exist(*commands):
351 """
353 """
352 Decorator to skip test when at least one of `commands` is not found.
354 Decorator to skip test when at least one of `commands` is not found.
353 """
355 """
354 for cmd in commands:
356 for cmd in commands:
355 if not is_cmd_found(cmd):
357 if not is_cmd_found(cmd):
356 return skip("This test runs only if command '{0}' "
358 return skip("This test runs only if command '{0}' "
357 "is installed".format(cmd))
359 "is installed".format(cmd))
358 return null_deco
360 return null_deco
General Comments 0
You need to be logged in to leave comments. Login now