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