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