From f0ad7083a428bbeee74aed23cfdeeaacc00780b2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Wed, 13 Mar 2019 21:47:05 -0500 Subject: [PATCH 1/5] TST: assert_produces_warning works with filterwarnings Previously, assert_produces_warning did not play well with pytest's filterwarnings. ``` ===================================================================================== FAILURES ====================================================================================== ____________________________________________________________________ test_assert_produces_warning_honors_filter _____________________________________________________________________ @pytest.mark.filterwarnings('ignore:f1:FutureWarning') def test_assert_produces_warning_honors_filter(): with tm.assert_produces_warning(RuntimeWarning): > f() pandas/tests/util/test_assert_produces_warning.py:17: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = , type = None, value = None, traceback = None def __exit__(self, type, value, traceback): if type is None: try: > next(self.gen) E AssertionError: Caused unexpected warning(s): [('FutureWarning', FutureWarning('f1'), '/Users/taugspurger/sandbox/pandas/pandas/tests/util/test_assert_produces_warni ng.py', 10)]. /usr/local/Cellar/python/3.7.2_2/Frameworks/Python.framework/Versions/3.7/lib/python3.7/contextlib.py:119: AssertionError ============================================================================= 1 failed in 0.20 seconds ==============================================================================``` Internally, `assert_produces_warning` sets a new `filter`, which overrides any filters set by pytest. We allow for `filter_level=None` to not set a filter. --- .../tests/util/test_assert_produces_warning.py | 16 ++++++++++++++++ pandas/util/testing.py | 6 ++++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 pandas/tests/util/test_assert_produces_warning.py diff --git a/pandas/tests/util/test_assert_produces_warning.py b/pandas/tests/util/test_assert_produces_warning.py new file mode 100644 index 0000000000000..bdec2b1a43791 --- /dev/null +++ b/pandas/tests/util/test_assert_produces_warning.py @@ -0,0 +1,16 @@ +import warnings + +import pytest + +import pandas.util.testing as tm + + +def f(): + warnings.warn('f1', FutureWarning) + warnings.warn('f2', RuntimeWarning) + + +@pytest.mark.filterwarnings('ignore:f1:FutureWarning') +def test_assert_produces_warning_honors_filter(): + with tm.assert_produces_warning(RuntimeWarning, filter_level=None): + f() diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 6e88cd7f72dcd..c01e2dce75c56 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2584,7 +2584,7 @@ def assert_produces_warning(expected_warning=Warning, filter_level="always", The type of Exception raised. ``exception.Warning`` is the base class for all warnings. To check that no warning is returned, specify ``False`` or ``None``. - filter_level : str, default "always" + filter_level : str or None, default "always" Specifies whether warnings are ignored, displayed, or turned into errors. Valid values are: @@ -2597,6 +2597,7 @@ class for all warnings. To check that no warning is returned, * "module" - print the warning the first time it is generated from each module * "once" - print the warning the first time it is generated + * None - do not apply a new filter clear : str, default None If not ``None`` then remove any previously raised warnings from @@ -2646,7 +2647,8 @@ class for all warnings. To check that no warning is returned, pass saw_warning = False - warnings.simplefilter(filter_level) + if filter_level: + warnings.simplefilter(filter_level) yield w extra_warnings = [] From c05306d6162231220a4eeb17c08f028c08bcb47c Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 14 Mar 2019 06:19:30 -0500 Subject: [PATCH 2/5] take 2 --- pandas/tests/util/test_assert_produces_warning.py | 15 +++++++++++---- pandas/util/testing.py | 8 ++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pandas/tests/util/test_assert_produces_warning.py b/pandas/tests/util/test_assert_produces_warning.py index bdec2b1a43791..0cef2131783e4 100644 --- a/pandas/tests/util/test_assert_produces_warning.py +++ b/pandas/tests/util/test_assert_produces_warning.py @@ -5,12 +5,19 @@ import pandas.util.testing as tm -def f(): - warnings.warn('f1', FutureWarning) - warnings.warn('f2', RuntimeWarning) +def f(a=FutureWarning, b=RuntimeWarning): + warnings.warn('f1', a) + warnings.warn('f2', b) @pytest.mark.filterwarnings('ignore:f1:FutureWarning') +@pytest.mark.filterwarnings('ignore:f2:RuntimeWarning') def test_assert_produces_warning_honors_filter(): - with tm.assert_produces_warning(RuntimeWarning, filter_level=None): + with tm.assert_produces_warning(RuntimeWarning): f() + + +@pytest.mark.filterwarnings('ignore:f1:FutureWarning') +def test_assert_produces_warning_message(): + with tm.assert_produces_warning(FutureWarning, message='f2'): + f(FutureWarning, FutureWarning) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index c01e2dce75c56..a49363eb6c1d7 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2571,7 +2571,8 @@ def exception_matches(self, exc_type, exc_value, trace_back): @contextmanager def assert_produces_warning(expected_warning=Warning, filter_level="always", - clear=None, check_stacklevel=True): + clear=None, check_stacklevel=True, + message=''): """ Context manager for running code expected to either raise a specific warning, or not raise any warnings. Verifies that the code raises the @@ -2609,6 +2610,9 @@ class for all warnings. To check that no warning is returned, If True, displays the line that called the function containing the warning to show were the function is called. Otherwise, the line that implements the function is displayed. + message : str, default '' + Use in the filter with `filter_level` and `expected_warning` + the control which warnings the filter applies to. Examples -------- @@ -2648,7 +2652,7 @@ class for all warnings. To check that no warning is returned, saw_warning = False if filter_level: - warnings.simplefilter(filter_level) + warnings.filterwarnings(filter_level, message, expected_warning) yield w extra_warnings = [] From a55659c68107c5ef8ff4336f6f0932e3c95968b9 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 14 Mar 2019 06:55:49 -0500 Subject: [PATCH 3/5] handle no warning --- pandas/util/testing.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index a49363eb6c1d7..86dbccbc15673 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2651,8 +2651,11 @@ class for all warnings. To check that no warning is returned, pass saw_warning = False - if filter_level: + if expected_warning and filter_level: warnings.filterwarnings(filter_level, message, expected_warning) + elif filter_level: + # no expected warnings. + warnings.simplefilter(filter_level) yield w extra_warnings = [] From ff66c6b9aa6218b59bf74a1eab56f1b9c41582e0 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 14 Mar 2019 07:49:15 -0500 Subject: [PATCH 4/5] remove message --- pandas/tests/util/test_assert_produces_warning.py | 6 ------ pandas/util/testing.py | 9 +++------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/pandas/tests/util/test_assert_produces_warning.py b/pandas/tests/util/test_assert_produces_warning.py index 0cef2131783e4..0c1da0ac75e54 100644 --- a/pandas/tests/util/test_assert_produces_warning.py +++ b/pandas/tests/util/test_assert_produces_warning.py @@ -15,9 +15,3 @@ def f(a=FutureWarning, b=RuntimeWarning): def test_assert_produces_warning_honors_filter(): with tm.assert_produces_warning(RuntimeWarning): f() - - -@pytest.mark.filterwarnings('ignore:f1:FutureWarning') -def test_assert_produces_warning_message(): - with tm.assert_produces_warning(FutureWarning, message='f2'): - f(FutureWarning, FutureWarning) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 86dbccbc15673..9d94201a5c08b 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2571,8 +2571,7 @@ def exception_matches(self, exc_type, exc_value, trace_back): @contextmanager def assert_produces_warning(expected_warning=Warning, filter_level="always", - clear=None, check_stacklevel=True, - message=''): + clear=None, check_stacklevel=True): """ Context manager for running code expected to either raise a specific warning, or not raise any warnings. Verifies that the code raises the @@ -2610,9 +2609,6 @@ class for all warnings. To check that no warning is returned, If True, displays the line that called the function containing the warning to show were the function is called. Otherwise, the line that implements the function is displayed. - message : str, default '' - Use in the filter with `filter_level` and `expected_warning` - the control which warnings the filter applies to. Examples -------- @@ -2652,7 +2648,8 @@ class for all warnings. To check that no warning is returned, saw_warning = False if expected_warning and filter_level: - warnings.filterwarnings(filter_level, message, expected_warning) + warnings.filterwarnings(filter_level, message='', + category=expected_warning) elif filter_level: # no expected warnings. warnings.simplefilter(filter_level) From 3e01ab4c297375d703365dbfe19ce27b3a8b91f2 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Thu, 14 Mar 2019 15:26:27 -0500 Subject: [PATCH 5/5] extra warnings option --- .../util/test_assert_produces_warning.py | 15 +++++++++----- pandas/util/testing.py | 20 +++++++++---------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pandas/tests/util/test_assert_produces_warning.py b/pandas/tests/util/test_assert_produces_warning.py index 0c1da0ac75e54..e125f01a494e7 100644 --- a/pandas/tests/util/test_assert_produces_warning.py +++ b/pandas/tests/util/test_assert_produces_warning.py @@ -5,13 +5,18 @@ import pandas.util.testing as tm -def f(a=FutureWarning, b=RuntimeWarning): - warnings.warn('f1', a) - warnings.warn('f2', b) +def f(): + warnings.warn('f1', FutureWarning) + warnings.warn('f2', RuntimeWarning) @pytest.mark.filterwarnings('ignore:f1:FutureWarning') -@pytest.mark.filterwarnings('ignore:f2:RuntimeWarning') def test_assert_produces_warning_honors_filter(): - with tm.assert_produces_warning(RuntimeWarning): + # raise by default + with pytest.raises(AssertionError): + with tm.assert_produces_warning(RuntimeWarning): + f() + + with tm.assert_produces_warning(RuntimeWarning, + raise_on_extra_warnings=False): f() diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 9d94201a5c08b..0481e88bb9a05 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -2571,7 +2571,8 @@ def exception_matches(self, exc_type, exc_value, trace_back): @contextmanager def assert_produces_warning(expected_warning=Warning, filter_level="always", - clear=None, check_stacklevel=True): + clear=None, check_stacklevel=True, + raise_on_extra_warnings=True): """ Context manager for running code expected to either raise a specific warning, or not raise any warnings. Verifies that the code raises the @@ -2597,7 +2598,6 @@ class for all warnings. To check that no warning is returned, * "module" - print the warning the first time it is generated from each module * "once" - print the warning the first time it is generated - * None - do not apply a new filter clear : str, default None If not ``None`` then remove any previously raised warnings from @@ -2609,6 +2609,9 @@ class for all warnings. To check that no warning is returned, If True, displays the line that called the function containing the warning to show were the function is called. Otherwise, the line that implements the function is displayed. + raise_on_extra_warnings : bool, default True + Whether extra warnings not of the type `expected_warning` should + cause the test to fail. Examples -------- @@ -2647,12 +2650,7 @@ class for all warnings. To check that no warning is returned, pass saw_warning = False - if expected_warning and filter_level: - warnings.filterwarnings(filter_level, message='', - category=expected_warning) - elif filter_level: - # no expected warnings. - warnings.simplefilter(filter_level) + warnings.simplefilter(filter_level) yield w extra_warnings = [] @@ -2682,8 +2680,10 @@ class for all warnings. To check that no warning is returned, msg = "Did not see expected warning of class {name!r}.".format( name=expected_warning.__name__) assert saw_warning, msg - assert not extra_warnings, ("Caused unexpected warning(s): {extra!r}." - ).format(extra=extra_warnings) + if raise_on_extra_warnings and extra_warnings: + raise AssertionError( + "Caused unexpected warning(s): {!r}.".format(extra_warnings) + ) class RNGContext(object):