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