Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/outcomes.py: 44%
110 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
1"""Exception classes and constants handling test outcomes as well as
2functions creating them."""
3import sys
4import warnings
5from typing import Any
6from typing import Callable
7from typing import cast
8from typing import NoReturn
9from typing import Optional
10from typing import Type
11from typing import TypeVar
13from _pytest.deprecated import KEYWORD_MSG_ARG
15TYPE_CHECKING = False # Avoid circular import through compat.
17if TYPE_CHECKING:
18 from typing_extensions import Protocol
19else:
20 # typing.Protocol is only available starting from Python 3.8. It is also
21 # available from typing_extensions, but we don't want a runtime dependency
22 # on that. So use a dummy runtime implementation.
23 from typing import Generic
25 Protocol = Generic
28class OutcomeException(BaseException):
29 """OutcomeException and its subclass instances indicate and contain info
30 about test and collection outcomes."""
32 def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
33 if msg is not None and not isinstance(msg, str):
34 error_msg = ( # type: ignore[unreachable]
35 "{} expected string as 'msg' parameter, got '{}' instead.\n"
36 "Perhaps you meant to use a mark?"
37 )
38 raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__))
39 super().__init__(msg)
40 self.msg = msg
41 self.pytrace = pytrace
43 def __repr__(self) -> str:
44 if self.msg is not None:
45 return self.msg
46 return f"<{self.__class__.__name__} instance>"
48 __str__ = __repr__
51TEST_OUTCOME = (OutcomeException, Exception)
54class Skipped(OutcomeException):
55 # XXX hackish: on 3k we fake to live in the builtins
56 # in order to have Skipped exception printing shorter/nicer
57 __module__ = "builtins"
59 def __init__(
60 self,
61 msg: Optional[str] = None,
62 pytrace: bool = True,
63 allow_module_level: bool = False,
64 *,
65 _use_item_location: bool = False,
66 ) -> None:
67 super().__init__(msg=msg, pytrace=pytrace)
68 self.allow_module_level = allow_module_level
69 # If true, the skip location is reported as the item's location,
70 # instead of the place that raises the exception/calls skip().
71 self._use_item_location = _use_item_location
74class Failed(OutcomeException):
75 """Raised from an explicit call to pytest.fail()."""
77 __module__ = "builtins"
80class Exit(Exception):
81 """Raised for immediate program exits (no tracebacks/summaries)."""
83 def __init__(
84 self, msg: str = "unknown reason", returncode: Optional[int] = None
85 ) -> None:
86 self.msg = msg
87 self.returncode = returncode
88 super().__init__(msg)
91# Elaborate hack to work around https://github.com/python/mypy/issues/2087.
92# Ideally would just be `exit.Exception = Exit` etc.
94_F = TypeVar("_F", bound=Callable[..., object])
95_ET = TypeVar("_ET", bound=Type[BaseException])
98class _WithException(Protocol[_F, _ET]):
99 Exception: _ET
100 __call__: _F
103def _with_exception(exception_type: _ET) -> Callable[[_F], _WithException[_F, _ET]]:
104 def decorate(func: _F) -> _WithException[_F, _ET]:
105 func_with_exception = cast(_WithException[_F, _ET], func)
106 func_with_exception.Exception = exception_type
107 return func_with_exception
109 return decorate
112# Exposed helper methods.
115@_with_exception(Exit)
116def exit(
117 reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None
118) -> NoReturn:
119 """Exit testing process.
121 :param reason:
122 The message to show as the reason for exiting pytest. reason has a default value
123 only because `msg` is deprecated.
125 :param returncode:
126 Return code to be used when exiting pytest.
128 :param msg:
129 Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
130 """
131 __tracebackhide__ = True
132 from _pytest.config import UsageError
134 if reason and msg:
135 raise UsageError(
136 "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`."
137 )
138 if not reason:
139 if msg is None:
140 raise UsageError("exit() requires a reason argument")
141 warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2)
142 reason = msg
143 raise Exit(reason, returncode)
146@_with_exception(Skipped)
147def skip(
148 reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None
149) -> NoReturn:
150 """Skip an executing test with the given message.
152 This function should be called only during testing (setup, call or teardown) or
153 during collection by using the ``allow_module_level`` flag. This function can
154 be called in doctests as well.
156 :param reason:
157 The message to show the user as reason for the skip.
159 :param allow_module_level:
160 Allows this function to be called at module level.
161 Raising the skip exception at module level will stop
162 the execution of the module and prevent the collection of all tests in the module,
163 even those defined before the `skip` call.
165 Defaults to False.
167 :param msg:
168 Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
170 .. note::
171 It is better to use the :ref:`pytest.mark.skipif ref` marker when
172 possible to declare a test to be skipped under certain conditions
173 like mismatching platforms or dependencies.
174 Similarly, use the ``# doctest: +SKIP`` directive (see :py:data:`doctest.SKIP`)
175 to skip a doctest statically.
176 """
177 __tracebackhide__ = True
178 reason = _resolve_msg_to_reason("skip", reason, msg)
179 raise Skipped(msg=reason, allow_module_level=allow_module_level)
182@_with_exception(Failed)
183def fail(reason: str = "", pytrace: bool = True, msg: Optional[str] = None) -> NoReturn:
184 """Explicitly fail an executing test with the given message.
186 :param reason:
187 The message to show the user as reason for the failure.
189 :param pytrace:
190 If False, msg represents the full failure information and no
191 python traceback will be reported.
193 :param msg:
194 Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
195 """
196 __tracebackhide__ = True
197 reason = _resolve_msg_to_reason("fail", reason, msg)
198 raise Failed(msg=reason, pytrace=pytrace)
201def _resolve_msg_to_reason(
202 func_name: str, reason: str, msg: Optional[str] = None
203) -> str:
204 """
205 Handles converting the deprecated msg parameter if provided into
206 reason, raising a deprecation warning. This function will be removed
207 when the optional msg argument is removed from here in future.
209 :param str func_name:
210 The name of the offending function, this is formatted into the deprecation message.
212 :param str reason:
213 The reason= passed into either pytest.fail() or pytest.skip()
215 :param str msg:
216 The msg= passed into either pytest.fail() or pytest.skip(). This will
217 be converted into reason if it is provided to allow pytest.skip(msg=) or
218 pytest.fail(msg=) to continue working in the interim period.
220 :returns:
221 The value to use as reason.
223 """
224 __tracebackhide__ = True
225 if msg is not None:
227 if reason:
228 from pytest import UsageError
230 raise UsageError(
231 f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted."
232 )
233 warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3)
234 reason = msg
235 return reason
238class XFailed(Failed):
239 """Raised from an explicit call to pytest.xfail()."""
242@_with_exception(XFailed)
243def xfail(reason: str = "") -> NoReturn:
244 """Imperatively xfail an executing test or setup function with the given reason.
246 This function should be called only during testing (setup, call or teardown).
248 :param reason:
249 The message to show the user as reason for the xfail.
251 .. note::
252 It is better to use the :ref:`pytest.mark.xfail ref` marker when
253 possible to declare a test to be xfailed under certain conditions
254 like known bugs or missing features.
255 """
256 __tracebackhide__ = True
257 raise XFailed(reason)
260def importorskip(
261 modname: str, minversion: Optional[str] = None, reason: Optional[str] = None
262) -> Any:
263 """Import and return the requested module ``modname``, or skip the
264 current test if the module cannot be imported.
266 :param modname:
267 The name of the module to import.
268 :param minversion:
269 If given, the imported module's ``__version__`` attribute must be at
270 least this minimal version, otherwise the test is still skipped.
271 :param reason:
272 If given, this reason is shown as the message when the module cannot
273 be imported.
275 :returns:
276 The imported module. This should be assigned to its canonical name.
278 Example::
280 docutils = pytest.importorskip("docutils")
281 """
282 import warnings
284 __tracebackhide__ = True
285 compile(modname, "", "eval") # to catch syntaxerrors
287 with warnings.catch_warnings():
288 # Make sure to ignore ImportWarnings that might happen because
289 # of existing directories with the same name we're trying to
290 # import but without a __init__.py file.
291 warnings.simplefilter("ignore")
292 try:
293 __import__(modname)
294 except ImportError as exc:
295 if reason is None:
296 reason = f"could not import {modname!r}: {exc}"
297 raise Skipped(reason, allow_module_level=True) from None
298 mod = sys.modules[modname]
299 if minversion is None:
300 return mod
301 verattr = getattr(mod, "__version__", None)
302 if minversion is not None:
303 # Imported lazily to improve start-up time.
304 from packaging.version import Version
306 if verattr is None or Version(verattr) < Version(minversion):
307 raise Skipped(
308 "module %r has __version__ %r, required is: %r"
309 % (modname, verattr, minversion),
310 allow_module_level=True,
311 )
312 return mod