Format: text/x-python
⬇ Download File (6.7 KB)
   1  # -*- coding: utf-8 -*-
   2  """Decorators for labeling test objects.
   3  
   4  Decorators that merely return a modified version of the original function
   5  object are straightforward.  Decorators that return a new function object need
   6  to use nose.tools.make_decorator(original_function)(decorator) in returning the
   7  decorator, in order to preserve metadata such as function name, setup and
   8  teardown functions and so on - see nose.tools for more information.
   9  
  10  This module provides a set of useful decorators meant to be ready to use in
  11  your own tests.  See the bottom of the file for the ready-made ones, and if you
  12  find yourself writing a new one that may be of generic use, add it here.
  13  
  14  Included decorators:
  15  
  16  
  17  Lightweight testing that remains unittest-compatible.
  18  
  19  - An @as_unittest decorator can be used to tag any normal parameter-less
  20    function as a unittest TestCase.  Then, both nose and normal unittest will
  21    recognize it as such.  This will make it easier to migrate away from Nose if
  22    we ever need/want to while maintaining very lightweight tests.
  23  
  24  NOTE: This file contains IPython-specific decorators. Using the machinery in
  25  IPython.external.decorators, we import either numpy.testing.decorators if numpy is
  26  available, OR use equivalent code in IPython.external._decorators, which
  27  we've copied verbatim from numpy.
  28  
  29  """
  30  
  31  # Copyright (c) IPython Development Team.
  32  # Distributed under the terms of the Modified BSD License.
  33  
  34  import os
  35  import shutil
  36  import sys
  37  import tempfile
  38  import unittest
  39  from importlib import import_module
  40  
  41  from decorator import decorator
  42  
  43  # Expose the unittest-driven decorators
  44  from .ipunittest import ipdoctest, ipdocstring
  45  
  46  #-----------------------------------------------------------------------------
  47  # Classes and functions
  48  #-----------------------------------------------------------------------------
  49  
  50  # Simple example of the basic idea
  51  def as_unittest(func):
  52      """Decorator to make a simple function into a normal test via unittest."""
  53      class Tester(unittest.TestCase):
  54          def test(self):
  55              func()
  56  
  57      Tester.__name__ = func.__name__
  58  
  59      return Tester
  60  
  61  # Utility functions
  62  
  63  
  64  def skipif(skip_condition, msg=None):
  65      """Make function raise SkipTest exception if skip_condition is true
  66  
  67      Parameters
  68      ----------
  69  
  70      skip_condition : bool or callable
  71        Flag to determine whether to skip test. If the condition is a
  72        callable, it is used at runtime to dynamically make the decision. This
  73        is useful for tests that may require costly imports, to delay the cost
  74        until the test suite is actually executed.
  75      msg : string
  76        Message to give on raising a SkipTest exception.
  77  
  78      Returns
  79      -------
  80      decorator : function
  81        Decorator, which, when applied to a function, causes SkipTest
  82        to be raised when the skip_condition was True, and the function
  83        to be called normally otherwise.
  84      """
  85      if msg is None:
  86          msg = "Test skipped due to test condition."
  87  
  88      import pytest
  89  
  90      assert isinstance(skip_condition, bool)
  91      return pytest.mark.skipif(skip_condition, reason=msg)
  92  
  93  
  94  # A version with the condition set to true, common case just to attach a message
  95  # to a skip decorator
  96  def skip(msg=None):
  97      """Decorator factory - mark a test function for skipping from test suite.
  98  
  99      Parameters
 100      ----------
 101        msg : string
 102          Optional message to be added.
 103  
 104      Returns
 105      -------
 106         decorator : function
 107           Decorator, which, when applied to a function, causes SkipTest
 108           to be raised, with the optional message added.
 109        """
 110      if msg and not isinstance(msg, str):
 111          raise ValueError('invalid object passed to `@skip` decorator, did you '
 112                           'meant `@skip()` with brackets ?')
 113      return skipif(True, msg)
 114  
 115  
 116  def onlyif(condition, msg):
 117      """The reverse from skipif, see skipif for details."""
 118  
 119      return skipif(not condition, msg)
 120  
 121  #-----------------------------------------------------------------------------
 122  # Utility functions for decorators
 123  def module_not_available(module):
 124      """Can module be imported?  Returns true if module does NOT import.
 125  
 126      This is used to make a decorator to skip tests that require module to be
 127      available, but delay the 'import numpy' to test execution time.
 128      """
 129      try:
 130          mod = import_module(module)
 131          mod_not_avail = False
 132      except ImportError:
 133          mod_not_avail = True
 134  
 135      return mod_not_avail
 136  
 137  
 138  #-----------------------------------------------------------------------------
 139  # Decorators for public use
 140  
 141  # Decorators to skip certain tests on specific platforms.
 142  skip_win32 = skipif(sys.platform == 'win32',
 143                      "This test does not run under Windows")
 144  skip_linux = skipif(sys.platform.startswith('linux'),
 145                      "This test does not run under Linux")
 146  skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
 147  
 148  
 149  # Decorators to skip tests if not on specific platforms.
 150  skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows")
 151  skip_if_not_linux = skipif(
 152      not sys.platform.startswith("linux"), "This test only runs under Linux"
 153  )
 154  skip_if_not_osx = skipif(
 155      not sys.platform.startswith("darwin"), "This test only runs under macOS"
 156  )
 157  
 158  _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
 159                    os.environ.get('DISPLAY', '') == '')
 160  _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
 161  
 162  skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
 163  
 164  # Other skip decorators
 165  
 166  # generic skip without module
 167  skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
 168  
 169  skipif_not_numpy = skip_without('numpy')
 170  
 171  skipif_not_matplotlib = skip_without('matplotlib')
 172  
 173  # A null 'decorator', useful to make more readable code that needs to pick
 174  # between different decorators based on OS or other conditions
 175  null_deco = lambda f: f
 176  
 177  # Some tests only run where we can use unicode paths. Note that we can't just
 178  # check os.path.supports_unicode_filenames, which is always False on Linux.
 179  try:
 180      f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
 181  except UnicodeEncodeError:
 182      unicode_paths = False
 183  else:
 184      unicode_paths = True
 185      f.close()
 186  
 187  onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
 188                                      "where we can use unicode in filenames."))
 189  
 190  
 191  def onlyif_cmds_exist(*commands):
 192      """
 193      Decorator to skip test when at least one of `commands` is not found.
 194      """
 195      assert (
 196          os.environ.get("IPTEST_WORKING_DIR", None) is None
 197      ), "iptest deprecated since IPython 8.0"
 198      for cmd in commands:
 199          reason = f"This test runs only if command '{cmd}' is installed"
 200          if not shutil.which(cmd):
 201              import pytest
 202  
 203              return pytest.mark.skip(reason=reason)
 204      return null_deco
 205