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