8000 Simplify cleanup decorator implementation. · matplotlib/matplotlib@393aa4c · GitHub
[go: up one dir, main page]

Skip to content

Commit 393aa4c

Browse files
committed
Simplify cleanup decorator implementation.
Introduce a single private `_cleanup_cm` contextmanager and use it to implement `CleanupTestCase` and `@cleanup`. Use `warnings.catch_warnings` to avoid completely destroying a preexisting warnings filter, instead just restoring the filter that existed before the test started. Use `matplotlib.style.context` to restore the style at exit, as it relies on rc_context which is ultimately more efficient than `rcParams.update` as it skips revalidation. Deprecate CleanupTest (and implement it in terms of CleanupTestCase), as it is clearly a nose-oriented base class that could have been deprecated at the same time as ImageComparisonTest.
1 parent 1e6790f commit 393aa4c

File tree

3 files changed

+38
-58
lines changed

3 files changed

+38
-58
lines changed

doc/api/next_api_changes/2018-02-15-AL-deprecations.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ The following classes, methods, functions, and attributes are deprecated:
2525
- ``mathtext.unichr_safe`` (use ``chr`` instead),
2626
- ``table.Table.get_child_artists`` (use ``get_children`` instead),
2727
- ``testing.compare.ImageComparisonTest``, ``testing.compare.compare_float``,
28-
- ``testing.decorators.skip_if_command_unavailable``.
28+
- ``testing.decorators.CleanupTest``,
29+
``testing.decorators.skip_if_command_unavailable``,
2930
- ``FigureCanvasQT.keyAutoRepeat`` (directly check
3031
``event.guiEvent.isAutoRepeat()`` in the event handler to decide whether to
3132
handle autorepeated key presses).
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The cleanup decorators and test classes in matplotlib.testing.decorators no longer destroy the warnings filter on exit
2+
``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
3+
Instead, they restore the warnings filter that existed before the test started
4+
using ``warnings.catch_warnings``.

lib/matplotlib/testing/decorators.py

Lines changed: 32 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,63 +8,49 @@
88
import unittest
99
import warnings
1010

11-
# Note - don't import nose up here - import it only as needed in functions.
12-
# This allows other functions here to be used by pytest-based testing suites
13-
# without requiring nose to be installed.
14-
15-
1611
import matplotlib as mpl
1712
import matplotlib.style
1813
import matplotlib.units
1914
import matplotlib.testing
2015
from matplotlib import cbook
21-
from matplotlib import ticker
22-
from matplotlib import pyplot as plt
2316
from matplotlib import ft2font
24-
from matplotlib.testing.compare import (
25-
comparable_formats, compare_images, make_test_filename)
17+
from matplotlib import pyplot as plt
18+
from matplotlib import ticker
2619
from . import is_called_from_pytest
20+
from .compare import comparable_formats, compare_images, make_test_filename
2721
from .exceptions import ImageComparisonFailure
2822

2923

30-
def _do_cleanup(original_units_registry, original_settings):
31-
plt.close('all')
32-
33-
mpl.rcParams.clear()
34-
mpl.rcParams.update(original_settings)
35-
matplotlib.units.registry.clear()
36-
matplotlib.units.registry.update(original_units_registry)
37-
warnings.resetwarnings() # reset any warning filters set in tests
38-
39-
40-
class CleanupTest(object):
41-
@classmethod
42-
def setup_class(cls):
43-
cls.original_units_registry = matplotlib.units.registry.copy()
44-
cls.original_settings = mpl.rcParams.copy()
45-
matplotlib.testing.setup()
46-
47-
@classmethod
48-
def teardown_class(cls):
49-
_do_cleanup(cls.original_units_registry,
50-
cls.original_settings)
51-
52-
def test(self):
53-
self._func()
24+
@contextlib.contextmanager
25+
def _cleanup_cm():
26+
orig_units_registry = matplotlib.units.registry.copy()
27+
try:
28+
with warnings.catch_warnings(), matplotlib.style.context(style):
29+
yield
30+
finally:
31+
matplotlib.units.registry.clear()
32+
matplotlib.units.registry.update(orig_units_registry)
33+
plt.close("all")
5434

5535

5636
class CleanupTestCase(unittest.TestCase):
57-
'''A wrapper for unittest.TestCase that includes cleanup operations'''
37+
"""A wrapper for unittest.TestCase that includes cleanup operations."""
5838
@classmethod
5939
def setUpClass(cls):
60-
import matplotlib.units
61-
cls.original_units_registry = matplotlib.units.registry.copy()
62-
cls.original_settings = mpl.rcParams.copy()
40+
cls._cm = _cleanup_cm().__enter__()
6341

6442
@classmethod
6543
def tearDownClass(cls):
66-
_do_cleanup(cls.original_units_registry,
67-
cls.original_settings)
44+
cls._cm.__exit__(None, None, None)
45+
46+
47+
@cbook.deprecated("3.0")
48+
class CleanupTest(object):
49+
setup_class = classmethod(CleanupTestCase.setUpClass.__func__)
50+
teardown_class = classmethod(CleanupTestCase.tearDownClass.__func__)
51+
52+
def test(self):
53+
self._func()
6854

6955

7056
def cleanup(style=None):
@@ -78,34 +64,23 @@ def cleanup(style=None):
7864
The name of the style to apply.
7965
"""
8066

81-
# If cleanup is used without arguments, `style` will be a
82-
# callable, and we pass it directly to the wrapper generator. If
83-
# cleanup if called with an argument, it is a string naming a
84-
# style, and the function will be passed as an argument to what we
85-
# return. This is a confusing, but somewhat standard, pattern for
86-
# writing a decorator with optional arguments.
67+
# If cleanup is used without arguments, `style` will be a callable, and we
68+
# pass it directly to the wrapper generator. If cleanup if called with an
69+
# argument, it is a string naming a style, and the function will be passed
70+
# as an argument to what we return. This is a confusing, but somewhat
71+
# standard, pattern for writing a decorator with optional arguments.
8772

8873
def make_cleanup(func):
8974
if inspect.isgeneratorfunction(func):
9075
@functools.wraps(func)
9176
def wrapped_callable(*args, **kwargs):
92-
original_units_registry = matplotlib.units.registry.copy()
93-
original_settings = mpl.rcParams.copy()
94-
matplotlib.style.use(style)
95-
try:
77+
with _cleanup_cm():
9678
yield from func(*args, **kwargs)
97-
finally:
98-
_do_cleanup(original_units_registry, original_settings)
9979
else:
10080
@functools.wraps(func)
10181
def wrapped_callable(*args, **kwargs):
102-
original_units_registry = matplotlib.units.registry.copy()
103-
original_settings = mpl.rcParams.copy()
104-
matplotlib.style.use(style)
105-
try:
82+
with _cleanup_cm():
10683
func(*args, **kwargs)
107-
finally:
108-
_do_cleanup(original_units_registry, original_settings)
10984

11085
return wrapped_callable
11186

0 commit comments

Comments
 (0)
0