Skip to content

Commit c198a7a

Browse files
authored
Merge pull request #8677 from olgarithms/warns-no-arg-catches-any
2 parents ff6d297 + 3f414d7 commit c198a7a

File tree

8 files changed

+52
-12
lines changed

8 files changed

+52
-12
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ Nicholas Murphy
231231
Niclas Olofsson
232232
Nicolas Delaby
233233
Nikolay Kondratyev
234+
Olga Matoula
234235
Oleg Pidsadnyi
235236
Oleg Sushchenko
236237
Oliver Bestwalter

changelog/8645.improvement.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Reducing confusion from `pytest.warns(None)` by:
2+
3+
- Allowing no arguments to be passed in order to catch any exception (no argument defaults to `Warning`).
4+
- Emit a deprecation warning if passed `None`.

doc/en/how-to/capture-warnings.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,11 +332,11 @@ You can record raised warnings either using func:`pytest.warns` or with
332332
the ``recwarn`` fixture.
333333

334334
To record with func:`pytest.warns` without asserting anything about the warnings,
335-
pass ``None`` as the expected warning type:
335+
pass no arguments as the expected warning type and it will default to a generic Warning:
336336

337337
.. code-block:: python
338338
339-
with pytest.warns(None) as record:
339+
with pytest.warns() as record:
340340
warnings.warn("user", UserWarning)
341341
warnings.warn("runtime", RuntimeWarning)
342342

src/_pytest/deprecated.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@
101101
"see https://docs.pytest.org/en/latest/deprecations.html"
102102
"#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
103103
)
104+
105+
WARNS_NONE_ARG = PytestDeprecationWarning(
106+
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
107+
" Replace pytest.warns(None) by simply pytest.warns()."
108+
)
109+
104110
# You want to make some `__init__` or function "private".
105111
#
106112
# def my_private_function(some, args):

src/_pytest/recwarn.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from _pytest.compat import final
1919
from _pytest.deprecated import check_ispytest
20+
from _pytest.deprecated import WARNS_NONE_ARG
2021
from _pytest.fixtures import fixture
2122
from _pytest.outcomes import fail
2223

@@ -83,7 +84,7 @@ def deprecated_call(
8384

8485
@overload
8586
def warns(
86-
expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]],
87+
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ...,
8788
*,
8889
match: Optional[Union[str, Pattern[str]]] = ...,
8990
) -> "WarningsChecker":
@@ -92,7 +93,7 @@ def warns(
9293

9394
@overload
9495
def warns(
95-
expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]],
96+
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]],
9697
func: Callable[..., T],
9798
*args: Any,
9899
**kwargs: Any,
@@ -101,7 +102,7 @@ def warns(
101102

102103

103104
def warns(
104-
expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]],
105+
expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning,
105106
*args: Any,
106107
match: Optional[Union[str, Pattern[str]]] = None,
107108
**kwargs: Any,
@@ -232,7 +233,7 @@ def __init__(
232233
self,
233234
expected_warning: Optional[
234235
Union[Type[Warning], Tuple[Type[Warning], ...]]
235-
] = None,
236+
] = Warning,
236237
match_expr: Optional[Union[str, Pattern[str]]] = None,
237238
*,
238239
_ispytest: bool = False,
@@ -242,6 +243,7 @@ def __init__(
242243

243244
msg = "exceptions must be derived from Warning, not %s"
244245
if expected_warning is None:
246+
warnings.warn(WARNS_NONE_ARG, stacklevel=4)
245247
expected_warning_tup = None
246248
elif isinstance(expected_warning, tuple):
247249
for exc in expected_warning:

testing/deprecated_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,15 @@ def test_hookproxy_warnings_for_fspath(tmp_path, hooktype, request):
178178
assert l1 < record.lineno < l2
179179

180180
hooks.pytest_ignore_collect(config=request.config, fspath=tmp_path)
181+
182+
183+
def test_warns_none_is_deprecated():
184+
with pytest.warns(
185+
PytestDeprecationWarning,
186+
match=re.escape(
187+
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n "
188+
"Replace pytest.warns(None) by simply pytest.warns()."
189+
),
190+
):
191+
with pytest.warns(None): # type: ignore[call-overload]
192+
pass

testing/test_recwarn.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,26 @@ def test_record(self) -> None:
298298
assert str(record[0].message) == "user"
299299

300300
def test_record_only(self) -> None:
301-
with pytest.warns(None) as record:
301+
with pytest.warns() as record:
302302
warnings.warn("user", UserWarning)
303303
warnings.warn("runtime", RuntimeWarning)
304304

305305
assert len(record) == 2
306306
assert str(record[0].message) == "user"
307307
assert str(record[1].message) == "runtime"
308308

309+
def test_record_only_none_deprecated_warn(self) -> None:
310+
# This should become an error when WARNS_NONE_ARG is removed in Pytest 7.0
311+
with warnings.catch_warnings():
312+
warnings.simplefilter("ignore")
313+
with pytest.warns(None) as record: # type: ignore[call-overload]
314+
warnings.warn("user", UserWarning)
315+
warnings.warn("runtime", RuntimeWarning)
316+
317+
assert len(record) == 2
318+
assert str(record[0].message) == "user"
319+
assert str(record[1].message) == "runtime"
320+
309321
def test_record_by_subclass(self) -> None:
310322
with pytest.warns(Warning) as record:
311323
warnings.warn("user", UserWarning)

testing/test_tmpdir.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import stat
33
import sys
4+
import warnings
45
from pathlib import Path
56
from typing import Callable
67
from typing import cast
@@ -400,11 +401,13 @@ def test_on_rm_rf_error(self, tmp_path: Path) -> None:
400401
assert fn.is_file()
401402

402403
# ignored function
403-
with pytest.warns(None) as warninfo:
404-
exc_info4 = (None, PermissionError(), None)
405-
on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
406-
assert fn.is_file()
407-
assert not [x.message for x in warninfo]
404+
with warnings.catch_warnings():
405+
warnings.simplefilter("ignore")
406+
with pytest.warns(None) as warninfo: # type: ignore[call-overload]
407+
exc_info4 = (None, PermissionError(), None)
408+
on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
409+
assert fn.is_file()
410+
assert not [x.message for x in warninfo]
408411

409412
exc_info5 = (None, PermissionError(), None)
410413
on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)

0 commit comments

Comments
 (0)