You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
7.2KB

  1. """Exception classes and constants handling test outcomes as well as
  2. functions creating them."""
  3. import sys
  4. from typing import Any
  5. from typing import Callable
  6. from typing import cast
  7. from typing import Optional
  8. from typing import Type
  9. from typing import TypeVar
  10. TYPE_CHECKING = False # Avoid circular import through compat.
  11. if TYPE_CHECKING:
  12. from typing import NoReturn
  13. from typing_extensions import Protocol
  14. else:
  15. # typing.Protocol is only available starting from Python 3.8. It is also
  16. # available from typing_extensions, but we don't want a runtime dependency
  17. # on that. So use a dummy runtime implementation.
  18. from typing import Generic
  19. Protocol = Generic
  20. class OutcomeException(BaseException):
  21. """OutcomeException and its subclass instances indicate and contain info
  22. about test and collection outcomes."""
  23. def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
  24. if msg is not None and not isinstance(msg, str):
  25. error_msg = ( # type: ignore[unreachable]
  26. "{} expected string as 'msg' parameter, got '{}' instead.\n"
  27. "Perhaps you meant to use a mark?"
  28. )
  29. raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__))
  30. BaseException.__init__(self, msg)
  31. self.msg = msg
  32. self.pytrace = pytrace
  33. def __repr__(self) -> str:
  34. if self.msg is not None:
  35. return self.msg
  36. return f"<{self.__class__.__name__} instance>"
  37. __str__ = __repr__
  38. TEST_OUTCOME = (OutcomeException, Exception)
  39. class Skipped(OutcomeException):
  40. # XXX hackish: on 3k we fake to live in the builtins
  41. # in order to have Skipped exception printing shorter/nicer
  42. __module__ = "builtins"
  43. def __init__(
  44. self,
  45. msg: Optional[str] = None,
  46. pytrace: bool = True,
  47. allow_module_level: bool = False,
  48. ) -> None:
  49. OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
  50. self.allow_module_level = allow_module_level
  51. class Failed(OutcomeException):
  52. """Raised from an explicit call to pytest.fail()."""
  53. __module__ = "builtins"
  54. class Exit(Exception):
  55. """Raised for immediate program exits (no tracebacks/summaries)."""
  56. def __init__(
  57. self, msg: str = "unknown reason", returncode: Optional[int] = None
  58. ) -> None:
  59. self.msg = msg
  60. self.returncode = returncode
  61. super().__init__(msg)
  62. # Elaborate hack to work around https://github.com/python/mypy/issues/2087.
  63. # Ideally would just be `exit.Exception = Exit` etc.
  64. _F = TypeVar("_F", bound=Callable[..., object])
  65. _ET = TypeVar("_ET", bound=Type[BaseException])
  66. class _WithException(Protocol[_F, _ET]):
  67. Exception: _ET
  68. __call__: _F
  69. def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _ET]]:
  70. def decorate(func: _F) -> _WithException[_F, _ET]:
  71. func_with_exception = cast(_WithException[_F, _ET], func)
  72. func_with_exception.Exception = exception_type
  73. return func_with_exception
  74. return decorate
  75. # Exposed helper methods.
  76. @_with_exception(Exit)
  77. def exit(msg: str, returncode: Optional[int] = None) -> "NoReturn":
  78. """Exit testing process.
  79. :param str msg: Message to display upon exit.
  80. :param int returncode: Return code to be used when exiting pytest.
  81. """
  82. __tracebackhide__ = True
  83. raise Exit(msg, returncode)
  84. @_with_exception(Skipped)
  85. def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn":
  86. """Skip an executing test with the given message.
  87. This function should be called only during testing (setup, call or teardown) or
  88. during collection by using the ``allow_module_level`` flag. This function can
  89. be called in doctests as well.
  90. :param bool allow_module_level:
  91. Allows this function to be called at module level, skipping the rest
  92. of the module. Defaults to False.
  93. .. note::
  94. It is better to use the :ref:`pytest.mark.skipif ref` marker when
  95. possible to declare a test to be skipped under certain conditions
  96. like mismatching platforms or dependencies.
  97. Similarly, use the ``# doctest: +SKIP`` directive (see `doctest.SKIP
  98. <https://docs.python.org/3/library/doctest.html#doctest.SKIP>`_)
  99. to skip a doctest statically.
  100. """
  101. __tracebackhide__ = True
  102. raise Skipped(msg=msg, allow_module_level=allow_module_level)
  103. @_with_exception(Failed)
  104. def fail(msg: str = "", pytrace: bool = True) -> "NoReturn":
  105. """Explicitly fail an executing test with the given message.
  106. :param str msg:
  107. The message to show the user as reason for the failure.
  108. :param bool pytrace:
  109. If False, msg represents the full failure information and no
  110. python traceback will be reported.
  111. """
  112. __tracebackhide__ = True
  113. raise Failed(msg=msg, pytrace=pytrace)
  114. class XFailed(Failed):
  115. """Raised from an explicit call to pytest.xfail()."""
  116. @_with_exception(XFailed)
  117. def xfail(reason: str = "") -> "NoReturn":
  118. """Imperatively xfail an executing test or setup function with the given reason.
  119. This function should be called only during testing (setup, call or teardown).
  120. .. note::
  121. It is better to use the :ref:`pytest.mark.xfail ref` marker when
  122. possible to declare a test to be xfailed under certain conditions
  123. like known bugs or missing features.
  124. """
  125. __tracebackhide__ = True
  126. raise XFailed(reason)
  127. def importorskip(
  128. modname: str, minversion: Optional[str] = None, reason: Optional[str] = None
  129. ) -> Any:
  130. """Import and return the requested module ``modname``, or skip the
  131. current test if the module cannot be imported.
  132. :param str modname:
  133. The name of the module to import.
  134. :param str minversion:
  135. If given, the imported module's ``__version__`` attribute must be at
  136. least this minimal version, otherwise the test is still skipped.
  137. :param str reason:
  138. If given, this reason is shown as the message when the module cannot
  139. be imported.
  140. :returns:
  141. The imported module. This should be assigned to its canonical name.
  142. Example::
  143. docutils = pytest.importorskip("docutils")
  144. """
  145. import warnings
  146. __tracebackhide__ = True
  147. compile(modname, "", "eval") # to catch syntaxerrors
  148. with warnings.catch_warnings():
  149. # Make sure to ignore ImportWarnings that might happen because
  150. # of existing directories with the same name we're trying to
  151. # import but without a __init__.py file.
  152. warnings.simplefilter("ignore")
  153. try:
  154. __import__(modname)
  155. except ImportError as exc:
  156. if reason is None:
  157. reason = f"could not import {modname!r}: {exc}"
  158. raise Skipped(reason, allow_module_level=True) from None
  159. mod = sys.modules[modname]
  160. if minversion is None:
  161. return mod
  162. verattr = getattr(mod, "__version__", None)
  163. if minversion is not None:
  164. # Imported lazily to improve start-up time.
  165. from packaging.version import Version
  166. if verattr is None or Version(verattr) < Version(minversion):
  167. raise Skipped(
  168. "module %r has __version__ %r, required is: %r"
  169. % (modname, verattr, minversion),
  170. allow_module_level=True,
  171. )
  172. return mod