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