Skip to content

Commit 8a6c9b7

Browse files
authored
Merge branch 'pytest-dev:main' into fix-13314-add-all-plugins-to-pytester-subprocess
2 parents 53d8dcc + 33e2e26 commit 8a6c9b7

20 files changed

+671
-418
lines changed

.pre-commit-config.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/astral-sh/ruff-pre-commit
3-
rev: "v0.9.10"
3+
rev: "v0.11.2"
44
hooks:
55
- id: ruff
66
args: ["--fix"]
@@ -12,7 +12,7 @@ repos:
1212
- id: end-of-file-fixer
1313
- id: check-yaml
1414
- repo: https://github.com/woodruffw/zizmor-pre-commit
15-
rev: v1.4.1
15+
rev: v1.5.2
1616
hooks:
1717
- id: zizmor
1818
- repo: https://github.com/adamchainz/blacken-docs

changelog/13192.feature.1.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:func:`pytest.raises` will now print a helpful string diff if matching fails and the match parameter has ``^`` and ``$`` and is otherwise escaped.

changelog/13192.feature.2.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
You can now pass :func:`with pytest.raises(check=fn): <pytest.raises>`, where ``fn`` is a function which takes a raised exception and returns a boolean. The ``raises`` fails if no exception was raised (as usual), passes if an exception is raised and ``fn`` returns ``True`` (as well as ``match`` and the type matching, if specified, which are checked before), and propagates the exception if ``fn`` returns ``False`` (which likely also fails the test).

changelog/13192.feature.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:func:`pytest.raises` will now raise a warning when passing an empty string to ``match``, as this will match against any value. Use ``match="^$"`` if you want to check that an exception has no message.

changelog/13317.packaging.rst

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Specified minimum allowed versions of ``colorama``, ``iniconfig``,
2+
and ``packaging``; and bumped the minimum allowed version
3+
of ``exceptiongroup`` for ``python_version<'3.11'`` from a release
4+
candidate to a full release.

doc/en/reference/plugin_list.rst

+219-83
Large diffs are not rendered by default.

pyproject.toml

+8-4
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ dynamic = [
4646
"version",
4747
]
4848
dependencies = [
49-
"colorama; sys_platform=='win32'",
50-
"exceptiongroup>=1.0.0rc8; python_version<'3.11'",
51-
"iniconfig",
52-
"packaging",
49+
"colorama>=0.4; sys_platform=='win32'",
50+
"exceptiongroup>=1; python_version<'3.11'",
51+
"iniconfig>=1",
52+
"packaging>=20",
5353
"pluggy>=1.5,<2",
5454
"pygments>=2.7.2",
5555
"tomli>=1; python_version<'3.11'",
@@ -167,6 +167,10 @@ lint.per-file-ignores."src/_pytest/_py/**/*.py" = [
167167
lint.per-file-ignores."src/_pytest/_version.py" = [
168168
"I001",
169169
]
170+
# can't be disabled on a line-by-line basis in file
171+
lint.per-file-ignores."testing/code/test_source.py" = [
172+
"F841",
173+
]
170174
lint.per-file-ignores."testing/python/approx.py" = [
171175
"B015",
172176
]

src/_pytest/mark/structures.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from _pytest.deprecated import check_ispytest
2929
from _pytest.deprecated import MARKED_FIXTURE
3030
from _pytest.outcomes import fail
31-
from _pytest.raises_group import AbstractRaises
31+
from _pytest.raises import AbstractRaises
3232
from _pytest.scope import _ScopeName
3333
from _pytest.warning_types import PytestUnknownMarkWarning
3434

src/_pytest/python_api.py

-237
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# mypy: allow-untyped-defs
22
from __future__ import annotations
33

4-
from collections.abc import Callable
54
from collections.abc import Collection
65
from collections.abc import Mapping
76
from collections.abc import Sequence
@@ -10,23 +9,14 @@
109
import math
1110
from numbers import Complex
1211
import pprint
13-
import re
1412
import sys
1513
from typing import Any
16-
from typing import overload
1714
from typing import TYPE_CHECKING
18-
from typing import TypeVar
19-
20-
from _pytest._code import ExceptionInfo
21-
from _pytest.outcomes import fail
22-
from _pytest.raises_group import RaisesExc
2315

2416

2517
if TYPE_CHECKING:
2618
from numpy import ndarray
2719

28-
E = TypeVar("E", bound=BaseException, default=BaseException)
29-
3020

3121
def _compare_approx(
3222
full_object: object,
@@ -778,230 +768,3 @@ def _as_numpy_array(obj: object) -> ndarray | None:
778768
elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"):
779769
return np.asarray(obj)
780770
return None
781-
782-
783-
# builtin pytest.raises helper
784-
# FIXME: This should probably me moved to 'src/_pytest.raises_group.py'
785-
# (and rename the file to 'raises.py')
786-
# since it's much more closely tied to those than to the other stuff in this file.
787-
788-
789-
@overload
790-
def raises(
791-
expected_exception: type[E] | tuple[type[E], ...],
792-
*,
793-
match: str | re.Pattern[str] | None = ...,
794-
check: Callable[[E], bool] = ...,
795-
) -> RaisesExc[E]: ...
796-
797-
798-
@overload
799-
def raises(
800-
*,
801-
match: str | re.Pattern[str],
802-
# If exception_type is not provided, check() must do any typechecks itself.
803-
check: Callable[[BaseException], bool] = ...,
804-
) -> RaisesExc[BaseException]: ...
805-
806-
807-
@overload
808-
def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException]: ...
809-
810-
811-
@overload
812-
def raises(
813-
expected_exception: type[E] | tuple[type[E], ...],
814-
func: Callable[..., Any],
815-
*args: Any,
816-
**kwargs: Any,
817-
) -> ExceptionInfo[E]: ...
818-
819-
820-
def raises(
821-
expected_exception: type[E] | tuple[type[E], ...] | None = None,
822-
*args: Any,
823-
**kwargs: Any,
824-
) -> RaisesExc[BaseException] | ExceptionInfo[E]:
825-
r"""Assert that a code block/function call raises an exception type, or one of its subclasses.
826-
827-
:param expected_exception:
828-
The expected exception type, or a tuple if one of multiple possible
829-
exception types are expected. Note that subclasses of the passed exceptions
830-
will also match.
831-
832-
:kwparam str | re.Pattern[str] | None match:
833-
If specified, a string containing a regular expression,
834-
or a regular expression object, that is tested against the string
835-
representation of the exception and its :pep:`678` `__notes__`
836-
using :func:`re.search`.
837-
838-
To match a literal string that may contain :ref:`special characters
839-
<re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
840-
841-
(This is only used when ``pytest.raises`` is used as a context manager,
842-
and passed through to the function otherwise.
843-
When using ``pytest.raises`` as a function, you can use:
844-
``pytest.raises(Exc, func, match="passed on").match("my pattern")``.)
845-
846-
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
847-
type, or any of its subclasses::
848-
849-
>>> import pytest
850-
>>> with pytest.raises(ZeroDivisionError):
851-
... 1/0
852-
853-
If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example
854-
above), or no exception at all, the check will fail instead.
855-
856-
You can also use the keyword argument ``match`` to assert that the
857-
exception matches a text or regex::
858-
859-
>>> with pytest.raises(ValueError, match='must be 0 or None'):
860-
... raise ValueError("value must be 0 or None")
861-
862-
>>> with pytest.raises(ValueError, match=r'must be \d+$'):
863-
... raise ValueError("value must be 42")
864-
865-
The ``match`` argument searches the formatted exception string, which includes any
866-
`PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``:
867-
868-
>>> with pytest.raises(ValueError, match=r"had a note added"): # doctest: +SKIP
869-
... e = ValueError("value must be 42")
870-
... e.add_note("had a note added")
871-
... raise e
872-
873-
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
874-
details of the captured exception::
875-
876-
>>> with pytest.raises(ValueError) as exc_info:
877-
... raise ValueError("value must be 42")
878-
>>> assert exc_info.type is ValueError
879-
>>> assert exc_info.value.args[0] == "value must be 42"
880-
881-
.. warning::
882-
883-
Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this::
884-
885-
# Careful, this will catch ANY exception raised.
886-
with pytest.raises(Exception):
887-
some_function()
888-
889-
Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide
890-
real bugs, where the user wrote this expecting a specific exception, but some other exception is being
891-
raised due to a bug introduced during a refactoring.
892-
893-
Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch
894-
**any** exception raised.
895-
896-
.. note::
897-
898-
When using ``pytest.raises`` as a context manager, it's worthwhile to
899-
note that normal context manager rules apply and that the exception
900-
raised *must* be the final line in the scope of the context manager.
901-
Lines of code after that, within the scope of the context manager will
902-
not be executed. For example::
903-
904-
>>> value = 15
905-
>>> with pytest.raises(ValueError) as exc_info:
906-
... if value > 10:
907-
... raise ValueError("value must be <= 10")
908-
... assert exc_info.type is ValueError # This will not execute.
909-
910-
Instead, the following approach must be taken (note the difference in
911-
scope)::
912-
913-
>>> with pytest.raises(ValueError) as exc_info:
914-
... if value > 10:
915-
... raise ValueError("value must be <= 10")
916-
...
917-
>>> assert exc_info.type is ValueError
918-
919-
**Expecting exception groups**
920-
921-
When expecting exceptions wrapped in :exc:`BaseExceptionGroup` or
922-
:exc:`ExceptionGroup`, you should instead use :class:`pytest.RaisesGroup`.
923-
924-
**Using with** ``pytest.mark.parametrize``
925-
926-
When using :ref:`pytest.mark.parametrize ref`
927-
it is possible to parametrize tests such that
928-
some runs raise an exception and others do not.
929-
930-
See :ref:`parametrizing_conditional_raising` for an example.
931-
932-
.. seealso::
933-
934-
:ref:`assertraises` for more examples and detailed discussion.
935-
936-
**Legacy form**
937-
938-
It is possible to specify a callable by passing a to-be-called lambda::
939-
940-
>>> raises(ZeroDivisionError, lambda: 1/0)
941-
<ExceptionInfo ...>
942-
943-
or you can specify an arbitrary callable with arguments::
944-
945-
>>> def f(x): return 1/x
946-
...
947-
>>> raises(ZeroDivisionError, f, 0)
948-
<ExceptionInfo ...>
949-
>>> raises(ZeroDivisionError, f, x=0)
950-
<ExceptionInfo ...>
951-
952-
The form above is fully supported but discouraged for new code because the
953-
context manager form is regarded as more readable and less error-prone.
954-
955-
.. note::
956-
Similar to caught exception objects in Python, explicitly clearing
957-
local references to returned ``ExceptionInfo`` objects can
958-
help the Python interpreter speed up its garbage collection.
959-
960-
Clearing those references breaks a reference cycle
961-
(``ExceptionInfo`` --> caught exception --> frame stack raising
962-
the exception --> current frame stack --> local variables -->
963-
``ExceptionInfo``) which makes Python keep all objects referenced
964-
from that cycle (including all local variables in the current
965-
frame) alive until the next cyclic garbage collection run.
966-
More detailed information can be found in the official Python
967-
documentation for :ref:`the try statement <python:try>`.
968-
"""
969-
__tracebackhide__ = True
970-
971-
if not args:
972-
if set(kwargs) - {"match", "check", "expected_exception"}:
973-
msg = "Unexpected keyword arguments passed to pytest.raises: "
974-
msg += ", ".join(sorted(kwargs))
975-
msg += "\nUse context-manager form instead?"
976-
raise TypeError(msg)
977-
978-
if expected_exception is None:
979-
return RaisesExc(**kwargs)
980-
return RaisesExc(expected_exception, **kwargs)
981-
982-
if not expected_exception:
983-
raise ValueError(
984-
f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. "
985-
f"Raising exceptions is already understood as failing the test, so you don't need "
986-
f"any special code to say 'this should never raise an exception'."
987-
)
988-
func = args[0]
989-
if not callable(func):
990-
raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
991-
with RaisesExc(expected_exception) as excinfo:
992-
func(*args[1:], **kwargs)
993-
try:
994-
return excinfo
995-
finally:
996-
del excinfo
997-
998-
999-
# note: RaisesExc/RaisesGroup uses fail() internally, so this alias
1000-
# indicates (to [internal] plugins?) that `pytest.raises` will
1001-
# raise `_pytest.outcomes.Failed`, where
1002-
# `outcomes.Failed is outcomes.fail.Exception is raises.Exception`
1003-
# note: this is *not* the same as `_pytest.main.Failed`
1004-
# note: mypy does not recognize this attribute, and it's not possible
1005-
# to use a protocol/decorator like the others in outcomes due to
1006-
# https://github.com/python/mypy/issues/18715
1007-
raises.Exception = fail.Exception # type: ignore[attr-defined]

0 commit comments

Comments
 (0)