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