diff options
author | Stephen Finucane <stephenfin@redhat.com> | 2021-12-24 11:21:24 +0000 |
---|---|---|
committer | Stephen Finucane <stephen@that.guru> | 2022-10-19 11:51:24 +0100 |
commit | 4cd367fb4bb9038c3b236532173ea68ba8c6d5c3 (patch) | |
tree | 696ce9e09f38ed3a19df4e619c683f9a80cd3ad6 | |
parent | f0376a18496f2707f41a028882a500b83192b8d0 (diff) | |
download | fixtures-git-4cd367fb4bb9038c3b236532173ea68ba8c6d5c3.tar.gz |
Add WarningsFilter fixture
This has enough users around OpenStack to justify adding it to
'fixtures' proper. It's intentionally dumb, since the main purpose of
this is to avoid people calling `resetwarnings` in their variant of the
fixture, as that clears *all* filters including those we don't control.
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | fixtures/__init__.py | 2 | ||||
-rw-r--r-- | fixtures/_fixtures/__init__.py | 4 | ||||
-rw-r--r-- | fixtures/_fixtures/warnings.py | 51 | ||||
-rw-r--r-- | fixtures/tests/_fixtures/test_warnings.py | 59 |
5 files changed, 116 insertions, 4 deletions
@@ -8,6 +8,10 @@ NEXT * Drop support for Python 3.6 (EOL) (Stephen Finucane) +* Add a new ``WarningsFilter`` filter, allowing users to filter warnings as + part of their tests, before restoring said filters. + (Stephen Finucane) + 4.0.1 ~~~~~ diff --git a/fixtures/__init__.py b/fixtures/__init__.py index a47f9e8..a4b9808 100644 --- a/fixtures/__init__.py +++ b/fixtures/__init__.py @@ -73,6 +73,7 @@ __all__ = [ 'Timeout', 'TimeoutException', 'WarningsCapture', + 'WarningsFilter', '__version__', 'version', ] @@ -110,6 +111,7 @@ from fixtures._fixtures import ( Timeout, TimeoutException, WarningsCapture, + WarningsFilter, ) from fixtures.testcase import TestWithFixtures diff --git a/fixtures/_fixtures/__init__.py b/fixtures/_fixtures/__init__.py index b60a6fb..a1bca03 100644 --- a/fixtures/_fixtures/__init__.py +++ b/fixtures/_fixtures/__init__.py @@ -40,7 +40,8 @@ __all__ = [ 'Timeout', 'TimeoutException', 'WarningsCapture', - ] + 'WarningsFilter', +] from fixtures._fixtures.environ import ( @@ -83,4 +84,5 @@ from fixtures._fixtures.timeout import ( ) from fixtures._fixtures.warnings import ( WarningsCapture, + WarningsFilter, ) diff --git a/fixtures/_fixtures/warnings.py b/fixtures/_fixtures/warnings.py index 8184403..da13c25 100644 --- a/fixtures/_fixtures/warnings.py +++ b/fixtures/_fixtures/warnings.py @@ -11,10 +11,9 @@ # license you chose for the specific language governing permissions and # limitations under that license. -from __future__ import absolute_import - __all__ = [ 'WarningsCapture', + 'WarningsFilter', ] import warnings @@ -38,3 +37,51 @@ class WarningsCapture(fixtures.Fixture): patch = fixtures.MonkeyPatch("warnings.showwarning", self._showwarning) self.useFixture(patch) self.captures = [] + + +class WarningsFilter(fixtures.Fixture): + """Configure warnings filters. + + While ``WarningsFilter`` is active, warnings will be filtered per + configuration. + """ + + def __init__(self, filters=None): + """Create a WarningsFilter fixture. + + :param filters: An optional list of dictionaries with arguments + corresponding to the arguments to + :py:func:`warnings.filterwarnings`. For example:: + + [ + { + 'action': 'ignore', + 'message': 'foo', + 'category': DeprecationWarning, + }, + ] + + Order is important: entries closer to the front of the list + override entries later in the list, if both match a particular + warning. + + Alternatively, you can configure warnings within the context of the + fixture. + + See `the Python documentation`__ for more information. + + __: https://docs.python.org/3/library/warnings.html#the-warnings-filter + """ + super().__init__() + self.filters = filters or [] + + def _setUp(self): + self._original_warning_filters = warnings.filters[:] + + for filt in self.filters: + warnings.filterwarnings(**filt) + + self.addCleanup(self._reset_warning_filters) + + def _reset_warning_filters(self): + warnings.filters[:] = self._original_warning_filters diff --git a/fixtures/tests/_fixtures/test_warnings.py b/fixtures/tests/_fixtures/test_warnings.py index c9e68bf..4b037be 100644 --- a/fixtures/tests/_fixtures/test_warnings.py +++ b/fixtures/tests/_fixtures/test_warnings.py @@ -18,11 +18,12 @@ import testtools import fixtures -class TestWarnings(testtools.TestCase, fixtures.TestWithFixtures): +class TestWarningsCapture(testtools.TestCase, fixtures.TestWithFixtures): def test_capture_reuse(self): # DeprecationWarnings are hidden by default in Python 3.2+, enable them # https://docs.python.org/3/library/warnings.html#default-warning-filter + self.useFixture(fixtures.WarningsFilter()) warnings.simplefilter("always") w = fixtures.WarningsCapture() @@ -35,6 +36,7 @@ class TestWarnings(testtools.TestCase, fixtures.TestWithFixtures): def test_capture_message(self): # DeprecationWarnings are hidden by default in Python 3.2+, enable them # https://docs.python.org/3/library/warnings.html#default-warning-filter + self.useFixture(fixtures.WarningsFilter()) warnings.simplefilter("always") w = self.useFixture(fixtures.WarningsCapture()) @@ -45,6 +47,7 @@ class TestWarnings(testtools.TestCase, fixtures.TestWithFixtures): def test_capture_category(self): # DeprecationWarnings are hidden by default in Python 3.2+, enable them # https://docs.python.org/3/library/warnings.html#default-warning-filter + self.useFixture(fixtures.WarningsFilter()) warnings.simplefilter("always") w = self.useFixture(fixtures.WarningsCapture()) @@ -59,3 +62,57 @@ class TestWarnings(testtools.TestCase, fixtures.TestWithFixtures): for i, category in enumerate(categories): c = w.captures[i] self.assertEqual(category, c.category) + + +class TestWarningsFilter(testtools.TestCase, fixtures.TestWithFixtures): + + def test_filter(self): + fixture = fixtures.WarningsFilter( + [ + { + 'action': 'ignore', + 'category': DeprecationWarning, + }, + { + 'action': 'once', + 'category': UserWarning, + }, + ], + ) + self.useFixture(fixture) + with warnings.catch_warnings(record=True) as w: + warnings.warn('deprecated', DeprecationWarning) + warnings.warn('user', UserWarning) + + # only the user warning should be present, and it should only have been + # raised once + self.assertEqual(1, len(w)) + + def test_filters_restored(self): + + class CustomWarning(Warning): + pass + + fixture = fixtures.WarningsFilter( + [ + { + 'action': 'once', + 'category': CustomWarning, + }, + ], + ) + + # we copy the filter values rather than a reference to the containing + # list since that can change + old_filters = warnings.filters[:] + + # NOTE: we intentionally do not use 'self.useFixture' since we want to + # teardown the fixture manually here before we exit this test method + with fixture: + new_filters = warnings.filters[:] + self.assertEqual(len(old_filters) + 1, len(new_filters)) + self.assertNotEqual(old_filters, new_filters) + + new_filters = warnings.filters[:] + self.assertEqual(len(old_filters), len(new_filters)) + self.assertEqual(old_filters, new_filters) |