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