|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- """Exception classes and constants handling test outcomes as well as
- functions creating them."""
- import sys
- from typing import Any
- from typing import Callable
- from typing import cast
- from typing import Optional
- from typing import Type
- from typing import TypeVar
-
- TYPE_CHECKING = False # Avoid circular import through compat.
-
- if TYPE_CHECKING:
- from typing import NoReturn
- from typing_extensions import Protocol
- else:
- # typing.Protocol is only available starting from Python 3.8. It is also
- # available from typing_extensions, but we don't want a runtime dependency
- # on that. So use a dummy runtime implementation.
- from typing import Generic
-
- Protocol = Generic
-
-
- class OutcomeException(BaseException):
- """OutcomeException and its subclass instances indicate and contain info
- about test and collection outcomes."""
-
- def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
- if msg is not None and not isinstance(msg, str):
- error_msg = ( # type: ignore[unreachable]
- "{} expected string as 'msg' parameter, got '{}' instead.\n"
- "Perhaps you meant to use a mark?"
- )
- raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__))
- BaseException.__init__(self, msg)
- self.msg = msg
- self.pytrace = pytrace
-
- def __repr__(self) -> str:
- if self.msg is not None:
- return self.msg
- return f"<{self.__class__.__name__} instance>"
-
- __str__ = __repr__
-
-
- TEST_OUTCOME = (OutcomeException, Exception)
-
-
- class Skipped(OutcomeException):
- # XXX hackish: on 3k we fake to live in the builtins
- # in order to have Skipped exception printing shorter/nicer
- __module__ = "builtins"
-
- def __init__(
- self,
- msg: Optional[str] = None,
- pytrace: bool = True,
- allow_module_level: bool = False,
- ) -> None:
- OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
- self.allow_module_level = allow_module_level
-
-
- class Failed(OutcomeException):
- """Raised from an explicit call to pytest.fail()."""
-
- __module__ = "builtins"
-
-
- class Exit(Exception):
- """Raised for immediate program exits (no tracebacks/summaries)."""
-
- def __init__(
- self, msg: str = "unknown reason", returncode: Optional[int] = None
- ) -> None:
- self.msg = msg
- self.returncode = returncode
- super().__init__(msg)
-
-
- # Elaborate hack to work around https://github.com/python/mypy/issues/2087.
- # Ideally would just be `exit.Exception = Exit` etc.
-
- _F = TypeVar("_F", bound=Callable[..., object])
- _ET = TypeVar("_ET", bound=Type[BaseException])
-
-
- class _WithException(Protocol[_F, _ET]):
- Exception: _ET
- __call__: _F
-
-
- def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _ET]]:
- def decorate(func: _F) -> _WithException[_F, _ET]:
- func_with_exception = cast(_WithException[_F, _ET], func)
- func_with_exception.Exception = exception_type
- return func_with_exception
-
- return decorate
-
-
- # Exposed helper methods.
-
-
- @_with_exception(Exit)
- def exit(msg: str, returncode: Optional[int] = None) -> "NoReturn":
- """Exit testing process.
-
- :param str msg: Message to display upon exit.
- :param int returncode: Return code to be used when exiting pytest.
- """
- __tracebackhide__ = True
- raise Exit(msg, returncode)
-
-
- @_with_exception(Skipped)
- def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn":
- """Skip an executing test with the given message.
-
- This function should be called only during testing (setup, call or teardown) or
- during collection by using the ``allow_module_level`` flag. This function can
- be called in doctests as well.
-
- :param bool allow_module_level:
- Allows this function to be called at module level, skipping the rest
- of the module. Defaults to False.
-
- .. note::
- It is better to use the :ref:`pytest.mark.skipif ref` marker when
- possible to declare a test to be skipped under certain conditions
- like mismatching platforms or dependencies.
- Similarly, use the ``# doctest: +SKIP`` directive (see `doctest.SKIP
- <https://docs.python.org/3/library/doctest.html#doctest.SKIP>`_)
- to skip a doctest statically.
- """
- __tracebackhide__ = True
- raise Skipped(msg=msg, allow_module_level=allow_module_level)
-
-
- @_with_exception(Failed)
- def fail(msg: str = "", pytrace: bool = True) -> "NoReturn":
- """Explicitly fail an executing test with the given message.
-
- :param str msg:
- The message to show the user as reason for the failure.
- :param bool pytrace:
- If False, msg represents the full failure information and no
- python traceback will be reported.
- """
- __tracebackhide__ = True
- raise Failed(msg=msg, pytrace=pytrace)
-
-
- class XFailed(Failed):
- """Raised from an explicit call to pytest.xfail()."""
-
-
- @_with_exception(XFailed)
- def xfail(reason: str = "") -> "NoReturn":
- """Imperatively xfail an executing test or setup function with the given reason.
-
- This function should be called only during testing (setup, call or teardown).
-
- .. note::
- It is better to use the :ref:`pytest.mark.xfail ref` marker when
- possible to declare a test to be xfailed under certain conditions
- like known bugs or missing features.
- """
- __tracebackhide__ = True
- raise XFailed(reason)
-
-
- def importorskip(
- modname: str, minversion: Optional[str] = None, reason: Optional[str] = None
- ) -> Any:
- """Import and return the requested module ``modname``, or skip the
- current test if the module cannot be imported.
-
- :param str modname:
- The name of the module to import.
- :param str minversion:
- If given, the imported module's ``__version__`` attribute must be at
- least this minimal version, otherwise the test is still skipped.
- :param str reason:
- If given, this reason is shown as the message when the module cannot
- be imported.
-
- :returns:
- The imported module. This should be assigned to its canonical name.
-
- Example::
-
- docutils = pytest.importorskip("docutils")
- """
- import warnings
-
- __tracebackhide__ = True
- compile(modname, "", "eval") # to catch syntaxerrors
-
- with warnings.catch_warnings():
- # Make sure to ignore ImportWarnings that might happen because
- # of existing directories with the same name we're trying to
- # import but without a __init__.py file.
- warnings.simplefilter("ignore")
- try:
- __import__(modname)
- except ImportError as exc:
- if reason is None:
- reason = f"could not import {modname!r}: {exc}"
- raise Skipped(reason, allow_module_level=True) from None
- mod = sys.modules[modname]
- if minversion is None:
- return mod
- verattr = getattr(mod, "__version__", None)
- if minversion is not None:
- # Imported lazily to improve start-up time.
- from packaging.version import Version
-
- if verattr is None or Version(verattr) < Version(minversion):
- raise Skipped(
- "module %r has __version__ %r, required is: %r"
- % (modname, verattr, minversion),
- allow_module_level=True,
- )
- return mod
|