decorators.py 13 KB

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