Skip to content

Commit f239b06

Browse files
committed
adds keep_ignores kwarg to pytest.warns
1 parent 9cc6b50 commit f239b06

File tree

4 files changed

+56
-4
lines changed

4 files changed

+56
-4
lines changed

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ merlinux GmbH, Germany, office at merlinux eu
44
Contributors include::
55

66
Aaron Coleman
7+
Aaron Zolnai-Lucas
78
Abdeali JK
89
Abdelrahman Elbehery
910
Abhijeet Kasurde

changelog/11933.improvement.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Now :func:`pytest.warns` can take an optional boolean keyword argument ``keep_ignores`` to keep existing ignore filters active when used as a context manager.

src/_pytest/recwarn.py

+42-4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ def warns(
9090
expected_warning: type[Warning] | tuple[type[Warning], ...] = ...,
9191
*,
9292
match: str | Pattern[str] | None = ...,
93+
keep_ignores: bool = ...,
9394
) -> WarningsChecker: ...
9495

9596

@@ -106,6 +107,7 @@ def warns(
106107
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
107108
*args: Any,
108109
match: str | Pattern[str] | None = None,
110+
keep_ignores: bool = False,
109111
**kwargs: Any,
110112
) -> WarningsChecker | Any:
111113
r"""Assert that code raises a particular class of warning.
@@ -140,6 +142,22 @@ def warns(
140142
...
141143
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
142144
145+
You may also set the keyword argument ``keep_ignores`` to avoid catching warnings
146+
which were filtered out, in pytest configuration or otherwise::
147+
148+
>>> warnings.simplefilter("ignore", category=FutureWarning)
149+
>>> with pytest.warns(UserWarning, keep_ignores=True):
150+
... warnings.warn("ignore this warning", UserWarning)
151+
Traceback (most recent call last):
152+
...
153+
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
154+
155+
>>> with pytest.warns(RuntimeWarning):
156+
>>> warnings.simplefilter("ignore", category=FutureWarning)
157+
>>> with pytest.warns(UserWarning, keep_ignores=True):
158+
... warnings.warn("ignore this warning", UserWarning)
159+
warnings.warn("keep this warning", RuntimeWarning)
160+
143161
**Using with** ``pytest.mark.parametrize``
144162
145163
When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests
@@ -157,7 +175,12 @@ def warns(
157175
f"Unexpected keyword arguments passed to pytest.warns: {argnames}"
158176
"\nUse context-manager form instead?"
159177
)
160-
return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
178+
return WarningsChecker(
179+
expected_warning,
180+
match_expr=match,
181+
keep_ignores=keep_ignores,
182+
_ispytest=True,
183+
)
161184
else:
162185
func = args[0]
163186
if not callable(func):
@@ -179,11 +202,12 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
179202
180203
"""
181204

182-
def __init__(self, *, _ispytest: bool = False) -> None:
205+
def __init__(self, *, keep_ignores: bool = False, _ispytest: bool = False) -> None:
183206
check_ispytest(_ispytest)
184207
super().__init__(record=True)
185208
self._entered = False
186209
self._list: list[warnings.WarningMessage] = []
210+
self._keep_ignores = keep_ignores
187211

188212
@property
189213
def list(self) -> list[warnings.WarningMessage]:
@@ -233,7 +257,20 @@ def __enter__(self) -> Self:
233257
# record=True means it's None.
234258
assert _list is not None
235259
self._list = _list
236-
warnings.simplefilter("always")
260+
261+
if self._keep_ignores:
262+
for action, message, category, module, lineno in reversed(warnings.filters):
263+
if isinstance(module, re.Pattern):
264+
module = getattr(module, "pattern", None) # type: ignore[unreachable]
265+
warnings.filterwarnings(
266+
action="always" if action != "ignore" else "ignore",
267+
message=message if isinstance(message, str) else "",
268+
category=category,
269+
module=module if isinstance(module, str) else "",
270+
lineno=lineno,
271+
)
272+
else:
273+
warnings.simplefilter("always")
237274
return self
238275

239276
def __exit__(
@@ -259,11 +296,12 @@ def __init__(
259296
self,
260297
expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
261298
match_expr: str | Pattern[str] | None = None,
299+
keep_ignores: bool = False,
262300
*,
263301
_ispytest: bool = False,
264302
) -> None:
265303
check_ispytest(_ispytest)
266-
super().__init__(_ispytest=True)
304+
super().__init__(keep_ignores=keep_ignores, _ispytest=True)
267305

268306
msg = "exceptions must be derived from Warning, not %s"
269307
if isinstance(expected_warning, tuple):

testing/test_recwarn.py

+12
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,18 @@ def test_match_regex(self) -> None:
404404
with pytest.warns(FutureWarning, match=r"must be \d+$"):
405405
warnings.warn("value must be 42", UserWarning)
406406

407+
def test_keep_ignores(self) -> None:
408+
with warnings.catch_warnings():
409+
warnings.filterwarnings("error", category=UserWarning)
410+
with pytest.warns(UserWarning, keep_ignores=True):
411+
warnings.warn("keep this warning", UserWarning)
412+
413+
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
414+
with warnings.catch_warnings():
415+
warnings.filterwarnings("ignore", message="ignore this")
416+
with pytest.warns(UserWarning, keep_ignores=True):
417+
warnings.warn("ignore this warning", FutureWarning)
418+
407419
def test_one_from_multiple_warns(self) -> None:
408420
with pytest.warns():
409421
with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):

0 commit comments

Comments
 (0)