Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f564af7

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 50b0e16 commit f564af7

File tree

4 files changed

+69
-90
lines changed

4 files changed

+69
-90
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/conftest.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,37 @@ def pytest_unconfigure(config):
1616

1717
@pytest.fixture(autouse=True)
1818
def mpl_test_settings(request):
19-
from matplotlib.testing.decorators import _do_cleanup
20-
21-
original_units_registry = matplotlib.units.registry.copy()
22-
original_settings = matplotlib.rcParams.copy()
23-
24-
backend = None
25-
backend_marker = request.keywords.get('backend')
26-
if backend_marker is not None:
27-
assert len(backend_marker.args) == 1, \
28-
"Marker 'backend' must specify 1 backend."
29-
backend = backend_marker.args[0]
30-
prev_backend = matplotlib.get_backend()
31-
32-
style = '_classic_test' # Default of cleanup and image_comparison too.
33-
style_marker = request.keywords.get('style')
34-
if style_marker is not None:
35-
assert len(style_marker.args) == 1, \
36-
"Marker 'style' must specify 1 style."
37-
style = style_marker.args[0]
38-
39-
matplotlib.testing.setup()
40-
if backend is not None:
41-
# This import must come after setup() so it doesn't load the default
42-
# backend prematurely.
43-
import matplotlib.pyplot as plt
44-
plt.switch_backend(backend)
45-
matplotlib.style.use(style)
46-
try:
47-
yield
48-
finally:
19+
from matplotlib.testing.decorators import _cleanup_cm
20+
21+
with _cleanup_cm():
22+
23+
backend = None
24+
backend_marker = request.keywords.get('backend')
25+
if backend_marker is not None:
26+
assert len(backend_marker.args) == 1, \
27+
"Marker 'backend' must specify 1 backend."
28+
backend = backend_marker.args[0]
29+
prev_backend = matplotlib.get_backend()
30+
31+
style = '_classic_test' # Default of cleanup and image_comparison too.
32+
style_marker = request.keywords.get('style')
33+
if style_marker is not None:
34+
assert len(style_marker.args) == 1, \
35+
"Marker 'style' must specify 1 style."
36+
style = style_marker.args[0]
37+
38+
matplotlib.testing.setup()
4939
if backend is not None:
50-
plt.switch_backend(prev_backend)
51-
_do_cleanup(original_units_registry, original_settings)
40+
# This import must come after setup() so it doesn't load the
41+
# default backend prematurely.
42+
import matplotlib.pyplot as plt
43+
plt.switch_backend(backend)
44+
matplotlib.style.use(style)
45+
try:
46+
yield
47+
finally:
48+
if backend is not None:
49+
plt.switch_backend(prev_backend)
5250

5351

5452
@pytest.fixture

lib/matplotlib/testing/decorators.py

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
from distutils.version import StrictVersion
23
import functools
34
import inspect
@@ -8,63 +9,49 @@
89
import unittest
910
import warnings
1011

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-
1612
import matplotlib as mpl
1713
import matplotlib.style
1814
import matplotlib.units
1915
import matplotlib.testing
2016
from matplotlib import cbook
21-
from matplotlib import ticker
22-
from matplotlib import pyplot as plt
2317
from matplotlib import ft2font
24-
from matplotlib.testing.compare import (
25-
comparable_formats, compare_images, make_test_filename)
18+
from matplotlib import pyplot as plt
19+
from matplotlib import ticker
2620
from . import is_called_from_pytest
21+
from .compare import comparable_formats, compare_images, make_test_filename
2722
from .exceptions import ImageComparisonFailure
2823

2924

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()
25+
@contextlib.contextmanager
26+
def _cleanup_cm():
27+
orig_units_registry = matplotlib.units.registry.copy()
28+
try:
29+
with warnings.catch_warnings(), matplotlib.rc_context():
30+
yield
31+
finally:
32+
matplotlib.units.registry.clear()
33+
matplotlib.units.registry.update(orig_units_registry)
34+
plt.close("all")
5435

5536

5637
class CleanupTestCase(unittest.TestCase):
57-
'''A wrapper for unittest.TestCase that includes cleanup operations'''
38+
"""A wrapper for unittest.TestCase that includes cleanup operations."""
5839
@classmethod
5940
def setUpClass(cls):
60-
import matplotlib.units
61-
cls.original_units_registry = matplotlib.units.registry.copy()
62-
cls.original_settings = mpl.rcParams.copy()
41+
cls._cm = _cleanup_cm().__enter__()
6342

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

6956

7057
def cleanup(style=None):
@@ -78,34 +65,23 @@ def cleanup(style=None):
7865
The name of the style to apply.
7966
"""
8067

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.
68+
# If cleanup is used without arguments, `style` will be a callable, and we
69+
# pass it directly to the wrapper generator. If cleanup if called with an
70+
# argument, it is a string naming a style, and the function will be passed
71+
# as an argument to what we return. This is a confusing, but somewhat
72+
# standard, pattern for writing a decorator with optional arguments.
8773

8874
def make_cleanup(func):
8975
if inspect.isgeneratorfunction(func):
9076
@functools.wraps(func)
9177
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:
78+
with _cleanup_cm(), matplotlib.style.context(style):
9679
yield from func(*args, **kwargs)
97-
finally:
98-
_do_cleanup(original_units_registry, original_settings)
9980
else:
10081
@functools.wraps(func)
10182
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:
83+
with _cleanup_cm(), matplotlib.style.context(style):
10684
func(*args, **kwargs)
107-
finally:
108-
_do_cleanup(original_units_registry, original_settings)
10985

11086
return wrapped_callable
11187

0 commit comments

Comments
 (0)