| | """Exception classes and constants handling test outcomes as well as |
| | functions creating them.""" |
| |
|
| | from __future__ import annotations |
| |
|
| | import sys |
| | from typing import Any |
| | from typing import Callable |
| | from typing import cast |
| | from typing import NoReturn |
| | from typing import Protocol |
| | from typing import Type |
| | from typing import TypeVar |
| |
|
| | from .warning_types import PytestDeprecationWarning |
| |
|
| |
|
| | class OutcomeException(BaseException): |
| | """OutcomeException and its subclass instances indicate and contain info |
| | about test and collection outcomes.""" |
| |
|
| | def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: |
| | if msg is not None and not isinstance(msg, str): |
| | error_msg = ( |
| | "{} 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__)) |
| | super().__init__(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): |
| | |
| | |
| | __module__ = "builtins" |
| |
|
| | def __init__( |
| | self, |
| | msg: str | None = None, |
| | pytrace: bool = True, |
| | allow_module_level: bool = False, |
| | *, |
| | _use_item_location: bool = False, |
| | ) -> None: |
| | super().__init__(msg=msg, pytrace=pytrace) |
| | self.allow_module_level = allow_module_level |
| | |
| | |
| | self._use_item_location = _use_item_location |
| |
|
| |
|
| | 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: int | None = None |
| | ) -> None: |
| | self.msg = msg |
| | self.returncode = returncode |
| | super().__init__(msg) |
| |
|
| |
|
| | |
| | |
| |
|
| | _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 |
| |
|
| |
|
| | |
| |
|
| |
|
| | @_with_exception(Exit) |
| | def exit( |
| | reason: str = "", |
| | returncode: int | None = None, |
| | ) -> NoReturn: |
| | """Exit testing process. |
| | |
| | :param reason: |
| | The message to show as the reason for exiting pytest. reason has a default value |
| | only because `msg` is deprecated. |
| | |
| | :param returncode: |
| | Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. |
| | |
| | :raises pytest.exit.Exception: |
| | The exception that is raised. |
| | """ |
| | __tracebackhide__ = True |
| | raise Exit(reason, returncode) |
| |
|
| |
|
| | @_with_exception(Skipped) |
| | def skip( |
| | reason: 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 reason: |
| | The message to show the user as reason for the skip. |
| | |
| | :param allow_module_level: |
| | Allows this function to be called at module level. |
| | Raising the skip exception at module level will stop |
| | the execution of the module and prevent the collection of all tests in the module, |
| | even those defined before the `skip` call. |
| | |
| | Defaults to False. |
| | |
| | :raises pytest.skip.Exception: |
| | The exception that is raised. |
| | |
| | .. 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 :py:data:`doctest.SKIP`) |
| | to skip a doctest statically. |
| | """ |
| | __tracebackhide__ = True |
| | raise Skipped(msg=reason, allow_module_level=allow_module_level) |
| |
|
| |
|
| | @_with_exception(Failed) |
| | def fail(reason: str = "", pytrace: bool = True) -> NoReturn: |
| | """Explicitly fail an executing test with the given message. |
| | |
| | :param reason: |
| | The message to show the user as reason for the failure. |
| | |
| | :param pytrace: |
| | If False, msg represents the full failure information and no |
| | python traceback will be reported. |
| | |
| | :raises pytest.fail.Exception: |
| | The exception that is raised. |
| | """ |
| | __tracebackhide__ = True |
| | raise Failed(msg=reason, 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). |
| | |
| | No other code is executed after using ``xfail()`` (it is implemented |
| | internally by raising an exception). |
| | |
| | :param reason: |
| | The message to show the user as reason for the xfail. |
| | |
| | .. 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. |
| | |
| | :raises pytest.xfail.Exception: |
| | The exception that is raised. |
| | """ |
| | __tracebackhide__ = True |
| | raise XFailed(reason) |
| |
|
| |
|
| | def importorskip( |
| | modname: str, |
| | minversion: str | None = None, |
| | reason: str | None = None, |
| | *, |
| | exc_type: type[ImportError] | None = None, |
| | ) -> Any: |
| | """Import and return the requested module ``modname``, or skip the |
| | current test if the module cannot be imported. |
| | |
| | :param modname: |
| | The name of the module to import. |
| | :param minversion: |
| | If given, the imported module's ``__version__`` attribute must be at |
| | least this minimal version, otherwise the test is still skipped. |
| | :param reason: |
| | If given, this reason is shown as the message when the module cannot |
| | be imported. |
| | :param exc_type: |
| | The exception that should be captured in order to skip modules. |
| | Must be :py:class:`ImportError` or a subclass. |
| | |
| | If the module can be imported but raises :class:`ImportError`, pytest will |
| | issue a warning to the user, as often users expect the module not to be |
| | found (which would raise :class:`ModuleNotFoundError` instead). |
| | |
| | This warning can be suppressed by passing ``exc_type=ImportError`` explicitly. |
| | |
| | See :ref:`import-or-skip-import-error` for details. |
| | |
| | |
| | :returns: |
| | The imported module. This should be assigned to its canonical name. |
| | |
| | :raises pytest.skip.Exception: |
| | If the module cannot be imported. |
| | |
| | Example:: |
| | |
| | docutils = pytest.importorskip("docutils") |
| | |
| | .. versionadded:: 8.2 |
| | |
| | The ``exc_type`` parameter. |
| | """ |
| | import warnings |
| |
|
| | __tracebackhide__ = True |
| | compile(modname, "", "eval") |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | if exc_type is None: |
| | exc_type = ImportError |
| | warn_on_import_error = True |
| | else: |
| | warn_on_import_error = False |
| |
|
| | skipped: Skipped | None = None |
| | warning: Warning | None = None |
| |
|
| | with warnings.catch_warnings(): |
| | |
| | |
| | |
| | warnings.simplefilter("ignore") |
| |
|
| | try: |
| | __import__(modname) |
| | except exc_type as exc: |
| | |
| | if reason is None: |
| | reason = f"could not import {modname!r}: {exc}" |
| | skipped = Skipped(reason, allow_module_level=True) |
| |
|
| | if warn_on_import_error and not isinstance(exc, ModuleNotFoundError): |
| | lines = [ |
| | "", |
| | f"Module '{modname}' was found, but when imported by pytest it raised:", |
| | f" {exc!r}", |
| | "In pytest 9.1 this warning will become an error by default.", |
| | "You can fix the underlying problem, or alternatively overwrite this behavior and silence this " |
| | "warning by passing exc_type=ImportError explicitly.", |
| | "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror", |
| | ] |
| | warning = PytestDeprecationWarning("\n".join(lines)) |
| |
|
| | if warning: |
| | warnings.warn(warning, stacklevel=2) |
| | if skipped: |
| | raise skipped |
| |
|
| | mod = sys.modules[modname] |
| | if minversion is None: |
| | return mod |
| | verattr = getattr(mod, "__version__", None) |
| | if minversion is not None: |
| | |
| | from packaging.version import Version |
| |
|
| | if verattr is None or Version(verattr) < Version(minversion): |
| | raise Skipped( |
| | f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}", |
| | allow_module_level=True, |
| | ) |
| | return mod |
| |
|