From 8b2f83772d3fbfbbef65c37673b8768ba67b71c5 Mon Sep 17 00:00:00 2001 From: Olga Matoula Date: Sat, 15 May 2021 15:15:43 +0100 Subject: [PATCH] Catch any warning on warns with no arg passed --- AUTHORS | 1 + changelog/8645.improvement.rst | 4 ++++ doc/en/how-to/capture-warnings.rst | 4 ++-- src/_pytest/deprecated.py | 5 +++++ src/_pytest/recwarn.py | 8 +++++--- testing/deprecated_test.py | 8 ++++++++ testing/test_recwarn.py | 13 ++++++++++++- testing/test_tmpdir.py | 13 ++++++++----- 8 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 changelog/8645.improvement.rst diff --git a/AUTHORS b/AUTHORS index 5f300c9d4..b822d469a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -231,6 +231,7 @@ Nicholas Murphy Niclas Olofsson Nicolas Delaby Nikolay Kondratyev +Olga Matoula Oleg Pidsadnyi Oleg Sushchenko Oliver Bestwalter diff --git a/changelog/8645.improvement.rst b/changelog/8645.improvement.rst new file mode 100644 index 000000000..b3a68b0b8 --- /dev/null +++ b/changelog/8645.improvement.rst @@ -0,0 +1,4 @@ +Reducing confusion from `pytest.warns(None)` by: + +- Allowing no arguments to be passed in order to catch any exception (no argument defaults to `Warning`). +- Emit a deprecation warning if passed `None`. diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index 1bafaeeb9..28e071c45 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -332,11 +332,11 @@ You can record raised warnings either using func:`pytest.warns` or with the ``recwarn`` fixture. To record with func:`pytest.warns` without asserting anything about the warnings, -pass ``None`` as the expected warning type: +pass no arguments as the expected warning type and it will default to a generic Warning: .. code-block:: python - with pytest.warns(None) as record: + with pytest.warns() as record: warnings.warn("user", UserWarning) warnings.warn("runtime", RuntimeWarning) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 7a09d5163..9f4b71bdc 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -101,6 +101,11 @@ HOOK_LEGACY_PATH_ARG = UnformattedWarning( "see https://docs.pytest.org/en/latest/deprecations.html" "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path", ) + +WARNS_NONE_ARG = PytestDeprecationWarning( + "Please pass an explicit Warning type or tuple of Warning types." +) + # You want to make some `__init__` or function "private". # # def my_private_function(some, args): diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index bc3a2835e..852b4c191 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -17,6 +17,7 @@ from typing import Union from _pytest.compat import final from _pytest.deprecated import check_ispytest +from _pytest.deprecated import WARNS_NONE_ARG from _pytest.fixtures import fixture from _pytest.outcomes import fail @@ -83,7 +84,7 @@ def deprecated_call( @overload def warns( - expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]], + expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]] = ..., *, match: Optional[Union[str, Pattern[str]]] = ..., ) -> "WarningsChecker": @@ -101,7 +102,7 @@ def warns( def warns( - expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]], + expected_warning: Optional[Union[Type[Warning], Tuple[Type[Warning], ...]]] = Warning, *args: Any, match: Optional[Union[str, Pattern[str]]] = None, **kwargs: Any, @@ -232,7 +233,7 @@ class WarningsChecker(WarningsRecorder): self, expected_warning: Optional[ Union[Type[Warning], Tuple[Type[Warning], ...]] - ] = None, + ] = Warning, match_expr: Optional[Union[str, Pattern[str]]] = None, *, _ispytest: bool = False, @@ -242,6 +243,7 @@ class WarningsChecker(WarningsRecorder): msg = "exceptions must be derived from Warning, not %s" if expected_warning is None: + warnings.warn(WARNS_NONE_ARG, stacklevel=4) expected_warning_tup = None elif isinstance(expected_warning, tuple): for exc in expected_warning: diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 1d012adf2..86650877e 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -178,3 +178,11 @@ def test_hookproxy_warnings_for_fspath(tmp_path, hooktype, request): assert l1 < record.lineno < l2 hooks.pytest_ignore_collect(config=request.config, fspath=tmp_path) + + +def test_warns_none_is_deprecated(): + with pytest.warns( + PytestDeprecationWarning, + match="Please pass an explicit Warning type or tuple of Warning types.", + ): + pytest.warns(None) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 91efe6d23..c73ab8a11 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -298,7 +298,7 @@ class TestWarns: assert str(record[0].message) == "user" def test_record_only(self) -> None: - with pytest.warns(None) as record: + with pytest.warns() as record: warnings.warn("user", UserWarning) warnings.warn("runtime", RuntimeWarning) @@ -306,6 +306,17 @@ class TestWarns: assert str(record[0].message) == "user" assert str(record[1].message) == "runtime" + def test_record_only_none_deprecated_warn(self) -> None: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with pytest.warns(None) as record: + warnings.warn("user", UserWarning) + warnings.warn("runtime", RuntimeWarning) + + assert len(record) == 2 + assert str(record[0].message) == "user" + assert str(record[1].message) == "runtime" + def test_record_by_subclass(self) -> None: with pytest.warns(Warning) as record: warnings.warn("user", UserWarning) diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 40e75663c..fe4b8b8cd 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,6 +1,7 @@ import os import stat import sys +import warnings from pathlib import Path from typing import Callable from typing import cast @@ -400,11 +401,13 @@ class TestRmRf: assert fn.is_file() # ignored function - with pytest.warns(None) as warninfo: - exc_info4 = (None, PermissionError(), None) - on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) - assert fn.is_file() - assert not [x.message for x in warninfo] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with pytest.warns(None) as warninfo: + exc_info4 = (None, PermissionError(), None) + on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) + assert fn.is_file() + assert not [x.message for x in warninfo] exc_info5 = (None, PermissionError(), None) on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)