##// END OF EJS Templates
Fix skip decorator....
Matthias Bussonnier -
Show More
@@ -1,376 +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 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 knownfailureif
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
249 return skipif(True,msg)
248 if msg and not isinstance(msg, str):
249 raise ValueError('invalid object passed to `@skip` decorator, did you '
250 'meant `@skip()` with brackets ?')
251 return skipif(True, msg)
250 252
251 253
252 254 def onlyif(condition, msg):
253 255 """The reverse from skipif, see skipif for details."""
254 256
255 257 if callable(condition):
256 258 skip_condition = lambda : not condition()
257 259 else:
258 260 skip_condition = lambda : not condition
259 261
260 262 return skipif(skip_condition, msg)
261 263
262 264 #-----------------------------------------------------------------------------
263 265 # Utility functions for decorators
264 266 def module_not_available(module):
265 267 """Can module be imported? Returns true if module does NOT import.
266 268
267 269 This is used to make a decorator to skip tests that require module to be
268 270 available, but delay the 'import numpy' to test execution time.
269 271 """
270 272 try:
271 273 mod = import_module(module)
272 274 mod_not_avail = False
273 275 except ImportError:
274 276 mod_not_avail = True
275 277
276 278 return mod_not_avail
277 279
278 280
279 281 def decorated_dummy(dec, name):
280 282 """Return a dummy function decorated with dec, with the given name.
281 283
282 284 Examples
283 285 --------
284 286 import IPython.testing.decorators as dec
285 287 setup = dec.decorated_dummy(dec.skip_if_no_x11, __name__)
286 288 """
287 289 warnings.warn("The function `decorated_dummy` is deprecated since IPython 4.0",
288 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 323 warnings.warn("The function `skip_file_no_x11` is deprecated since IPython 4.0",
322 324 DeprecationWarning, stacklevel=2)
323 325 return decorated_dummy(skip_if_no_x11, name) if _x11_skip_cond else None
324 326
325 327 # Other skip decorators
326 328
327 329 # generic skip without module
328 330 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
329 331
330 332 skipif_not_numpy = skip_without('numpy')
331 333
332 334 skipif_not_matplotlib = skip_without('matplotlib')
333 335
334 336 skipif_not_sympy = skip_without('sympy')
335 337
336 338 skip_known_failure = knownfailureif(True,'This test is known to fail')
337 339
338 340 # A null 'decorator', useful to make more readable code that needs to pick
339 341 # between different decorators based on OS or other conditions
340 342 null_deco = lambda f: f
341 343
342 344 # Some tests only run where we can use unicode paths. Note that we can't just
343 345 # check os.path.supports_unicode_filenames, which is always False on Linux.
344 346 try:
345 347 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
346 348 except UnicodeEncodeError:
347 349 unicode_paths = False
348 350 else:
349 351 unicode_paths = True
350 352 f.close()
351 353
352 354 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
353 355 "where we can use unicode in filenames."))
354 356
355 357
356 358 def onlyif_cmds_exist(*commands):
357 359 """
358 360 Decorator to skip test when at least one of `commands` is not found.
359 361 """
360 362 for cmd in commands:
361 363 if not shutil.which(cmd):
362 364 return skip("This test runs only if command '{0}' "
363 365 "is installed".format(cmd))
364 366 return null_deco
365 367
366 368 def onlyif_any_cmd_exists(*commands):
367 369 """
368 370 Decorator to skip test unless at least one of `commands` is found.
369 371 """
370 372 warnings.warn("The function `onlyif_any_cmd_exists` is deprecated since IPython 4.0",
371 373 DeprecationWarning, stacklevel=2)
372 374 for cmd in commands:
373 375 if shutil.which(cmd):
374 376 return null_deco
375 377 return skip("This test runs only if one of the commands {0} "
376 378 "is installed".format(commands))
@@ -1,164 +1,164 b''
1 1 """Tests for the decorators we've created for IPython.
2 2 """
3 3
4 4 # Module imports
5 5 # Std lib
6 6 import inspect
7 7 import sys
8 8
9 9 # Third party
10 10 import nose.tools as nt
11 11
12 12 # Our own
13 13 from IPython.testing import decorators as dec
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Utilities
17 17
18 18 # Note: copied from OInspect, kept here so the testing stuff doesn't create
19 19 # circular dependencies and is easier to reuse.
20 20 def getargspec(obj):
21 21 """Get the names and default values of a function's arguments.
22 22
23 23 A tuple of four things is returned: (args, varargs, varkw, defaults).
24 24 'args' is a list of the argument names (it may contain nested lists).
25 25 'varargs' and 'varkw' are the names of the * and ** arguments or None.
26 26 'defaults' is an n-tuple of the default values of the last n arguments.
27 27
28 28 Modified version of inspect.getargspec from the Python Standard
29 29 Library."""
30 30
31 31 if inspect.isfunction(obj):
32 32 func_obj = obj
33 33 elif inspect.ismethod(obj):
34 34 func_obj = obj.__func__
35 35 else:
36 36 raise TypeError('arg is not a Python function')
37 37 args, varargs, varkw = inspect.getargs(func_obj.__code__)
38 38 return args, varargs, varkw, func_obj.__defaults__
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Testing functions
42 42
43 43 @dec.as_unittest
44 44 def trivial():
45 45 """A trivial test"""
46 46 pass
47 47
48 48
49 @dec.skip
49 @dec.skip()
50 50 def test_deliberately_broken():
51 51 """A deliberately broken test - we want to skip this one."""
52 52 1/0
53 53
54 54 @dec.skip('Testing the skip decorator')
55 55 def test_deliberately_broken2():
56 56 """Another deliberately broken test - we want to skip this one."""
57 57 1/0
58 58
59 59
60 60 # Verify that we can correctly skip the doctest for a function at will, but
61 61 # that the docstring itself is NOT destroyed by the decorator.
62 62 def doctest_bad(x,y=1,**k):
63 63 """A function whose doctest we need to skip.
64 64
65 65 >>> 1+1
66 66 3
67 67 """
68 68 print('x:',x)
69 69 print('y:',y)
70 70 print('k:',k)
71 71
72 72
73 73 def call_doctest_bad():
74 74 """Check that we can still call the decorated functions.
75 75
76 76 >>> doctest_bad(3,y=4)
77 77 x: 3
78 78 y: 4
79 79 k: {}
80 80 """
81 81 pass
82 82
83 83
84 84 def test_skip_dt_decorator():
85 85 """Doctest-skipping decorator should preserve the docstring.
86 86 """
87 87 # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
88 88 check = """A function whose doctest we need to skip.
89 89
90 90 >>> 1+1
91 91 3
92 92 """
93 93 # Fetch the docstring from doctest_bad after decoration.
94 94 val = doctest_bad.__doc__
95 95
96 96 nt.assert_equal(check,val,"doctest_bad docstrings don't match")
97 97
98 98
99 99 # Doctest skipping should work for class methods too
100 100 class FooClass(object):
101 101 """FooClass
102 102
103 103 Example:
104 104
105 105 >>> 1+1
106 106 2
107 107 """
108 108
109 109 def __init__(self,x):
110 110 """Make a FooClass.
111 111
112 112 Example:
113 113
114 114 >>> f = FooClass(3)
115 115 junk
116 116 """
117 117 print('Making a FooClass.')
118 118 self.x = x
119 119
120 120 def bar(self,y):
121 121 """Example:
122 122
123 123 >>> ff = FooClass(3)
124 124 >>> ff.bar(0)
125 125 boom!
126 126 >>> 1/0
127 127 bam!
128 128 """
129 129 return 1/y
130 130
131 131 def baz(self,y):
132 132 """Example:
133 133
134 134 >>> ff2 = FooClass(3)
135 135 Making a FooClass.
136 136 >>> ff2.baz(3)
137 137 True
138 138 """
139 139 return self.x==y
140 140
141 141
142 142 def test_skip_dt_decorator2():
143 143 """Doctest-skipping decorator should preserve function signature.
144 144 """
145 145 # Hardcoded correct answer
146 146 dtargs = (['x', 'y'], None, 'k', (1,))
147 147 # Introspect out the value
148 148 dtargsr = getargspec(doctest_bad)
149 149 assert dtargsr==dtargs, \
150 150 "Incorrectly reconstructed args for doctest_bad: %s" % (dtargsr,)
151 151
152 152
153 153 @dec.skip_linux
154 154 def test_linux():
155 155 nt.assert_false(sys.platform.startswith('linux'),"This test can't run under linux")
156 156
157 157 @dec.skip_win32
158 158 def test_win32():
159 159 nt.assert_not_equal(sys.platform,'win32',"This test can't run under windows")
160 160
161 161 @dec.skip_osx
162 162 def test_osx():
163 163 nt.assert_not_equal(sys.platform,'darwin',"This test can't run under osx")
164 164
General Comments 0
You need to be logged in to leave comments. Login now