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

Skip to content

Commit 7ff4d34

Browse files
committed
Make more of testing/ pytest-independent.
This makes it actually possible to write tests independently of pytest (the API is still private, but could be made public): ``` import unittest from matplotlib import pyplot as plt from matplotlib.testing.decorators import _make_image_comparator class TestFoo(unittest.TestCase): @_make_image_comparator(baseline_images=["foo"], extension="png") def test_bar(self): fig, ax = plt.subplots() ``` works as expected.
1 parent 3e5dcc6 commit 7ff4d34

File tree

3 files changed

+65
-73
lines changed

3 files changed

+65
-73
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
API changes
2+
```````````
3+
4+
``testing.compare.convert`` now raises ``unittest.SkipTest`` instead of a
5+
pytest skip for uncomparable formats. Note that pytest will correctly handle
6+
such tests as being skipped.

lib/matplotlib/testing/compare.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import subprocess
1313
import sys
1414
from tempfile import TemporaryFile
15+
import unittest
1516

1617
import numpy as np
1718
import PIL
@@ -254,6 +255,12 @@ def comparable_formats():
254255
return ['png', *converter]
255256

256257

258+
def _skip_if_uncomparable(ext):
259+
"""Raise `unittest.SkipTest` if an *ext* files cannot be compared."""
260+
if ext not in comparable_formats():
261+
raise unittest.SkipTest(f"Lacking dependency to compare {ext} files")
262+
263+
257264
def convert(filename, cache):
258265
"""
259266
Convert the named file to png; return the name of the created file.
@@ -264,9 +271,7 @@ def convert(filename, cache):
264271
size of the cache, so it may need to be manually cleared periodically.
265272
"""
266273
base, extension = os.fspath(filename).rsplit('.', 1)
267-
if extension not in converter:
268-
import pytest
269-
pytest.skip(f"Don't know how to convert {extension} files to png")
274+
_skip_if_uncomparable(extension)
270275
newname = base + '_' + extension + '.png'
271276
if not os.path.exists(filename):
272277
raise IOError("'%s' does not exist" % filename)

lib/matplotlib/testing/decorators.py

Lines changed: 51 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from matplotlib import pyplot as plt
1919
from matplotlib import ticker
2020
from . import is_called_from_pytest
21-
from .compare import comparable_formats, compare_images, make_test_filename
21+
from .compare import compare_images, make_test_filename, _skip_if_uncomparable
2222
from .exceptions import ImageComparisonFailure
2323

2424

@@ -136,54 +136,33 @@ def _raise_on_image_difference(expected, actual, tol):
136136
% err)
137137

138138

139-
def _skip_if_format_is_uncomparable(extension):
140-
import pytest
141-
return pytest.mark.skipif(
142-
extension not in comparable_formats(),
143-
reason='Cannot compare {} files on this system'.format(extension))
144-
145-
146-
def _mark_skip_if_format_is_uncomparable(extension):
147-
import pytest
148-
if isinstance(extension, str):
149-
name = extension
150-
marks = []
151-
elif isinstance(extension, tuple):
152-
# Extension might be a pytest ParameterSet instead of a plain string.
153-
# Unfortunately, this type is not exposed, so since it's a namedtuple,
154-
# check for a tuple instead.
155-
name, = extension.values
156-
marks = [*extension.marks]
157-
else:
158-
# Extension might be a pytest marker instead of a plain string.
159-
name, = extension.args
160-
marks = [extension.mark]
161-
return pytest.param(name,
162-
marks=[*marks, _skip_if_format_is_uncomparable(name)])
163-
164-
165-
class _ImageComparisonBase:
139+
def _make_image_comparator(func=None,
140+
baseline_images=None, *, extension=None, tol=0,
141+
remove_text=False, savefig_kwargs=None):
166142
"""
167-
Image comparison base class
143+
Image comparison base helper.
168144
169-
This class provides *just* the comparison-related functionality and avoids
145+
This helper provides *just* the comparison-related functionality and avoids
170146
any code that would be specific to any testing framework.
171147
"""
148+
if func is None:
149+
return functools.partial(
150+
_make_image_comparator,
151+
baseline_images=baseline_images, extension=extension, tol=tol,
152+
remove_text=remove_text, savefig_kwargs=savefig_kwargs)
153+
154+
if savefig_kwargs is None:
155+
savefig_kwargs = {}
172156

173-
def __init__(self, func, tol, remove_text, savefig_kwargs):
174-
self.func = func
175-
self.baseline_dir, self.result_dir = _image_directories(func)
176-
self.tol = tol
177-
self.remove_text = remove_text
178-
self.savefig_kwargs = savefig_kwargs
157+
baseline_dir, result_dir = _image_directories(func)
179158

180-
def copy_baseline(self, baseline, extension):
181-
baseline_path = self.baseline_dir / baseline
159+
def _copy_baseline(baseline):
160+
baseline_path = baseline_dir / baseline
182161
orig_expected_path = baseline_path.with_suffix(f'.{extension}')
183162
if extension == 'eps' and not orig_expected_path.exists():
184163
orig_expected_path = orig_expected_path.with_suffix('.pdf')
185164
expected_fname = make_test_filename(
186-
self.result_dir / orig_expected_path.name, 'expected')
165+
result_dir / orig_expected_path.name, 'expected')
187166
try:
188167
# os.symlink errors if the target already exists.
189168
with contextlib.suppress(OSError):
@@ -198,24 +177,33 @@ def copy_baseline(self, baseline, extension):
198177
f"following file cannot be accessed: {orig_expected_path}")
199178
return expected_fname
200179

201-
def compare(self, idx, baseline, extension):
180+
@functools.wraps(func)
181+
def wrapper(*args, **kwargs):
202182
__tracebackhide__ = True
203-
fignum = plt.get_fignums()[idx]
204-
fig = plt.figure(fignum)
205-
206-
if self.remove_text:
207-
remove_ticks_and_titles(fig)
208-
209-
actual_path = (self.result_dir / baseline).with_suffix(f'.{extension}')
210-
kwargs = self.savefig_kwargs.copy()
211-
if extension == 'pdf':
212-
kwargs.setdefault('metadata',
213-
{'Creator': None, 'Producer': None,
214-
'CreationDate': None})
215-
fig.savefig(actual_path, **kwargs)
216-
217-
expected_path = self.copy_baseline(baseline, extension)
218-
_raise_on_image_difference(expected_path, actual_path, self.tol)
183+
_skip_if_uncomparable(extension)
184+
185+
func(*args, **kwargs)
186+
187+
fignums = plt.get_fignums()
188+
assert len(fignums) == len(baseline_images), (
189+
"Test generated {} images but there are {} baseline images"
190+
.format(len(fignums), len(baseline_images)))
191+
for baseline_image, fignum in zip(baseline_images, fignums):
192+
fig = plt.figure(fignum)
193+
if remove_text:
194+
remove_ticks_and_titles(fig)
195+
actual_path = ((result_dir / baseline_image)
196+
.with_suffix(f'.{extension}'))
197+
kwargs = savefig_kwargs.copy()
198+
if extension == 'pdf':
199+
kwargs.setdefault('metadata',
200+
{'Creator': None, 'Producer': None,
201+
'CreationDate': None})
202+
fig.savefig(actual_path, **kwargs)
203+
expected_path = _copy_baseline(baseline_image)
204+
_raise_on_image_difference(expected_path, actual_path, tol)
205+
206+
return wrapper
219207

220208

221209
def _pytest_image_comparison(baseline_images, extensions, tol,
@@ -231,8 +219,6 @@ def _pytest_image_comparison(baseline_images, extensions, tol,
231219
"""
232220
import pytest
233221

234-
extensions = map(_mark_skip_if_format_is_uncomparable, extensions)
235-
236222
def decorator(func):
237223
@functools.wraps(func)
238224
# Parameter indirection; see docstring above and comment below.
@@ -245,23 +231,19 @@ def decorator(func):
245231
@functools.wraps(func)
246232
def wrapper(*args, **kwargs):
247233
__tracebackhide__ = True
248-
img = _ImageComparisonBase(func, tol=tol, remove_text=remove_text,
249-
savefig_kwargs=savefig_kwargs)
250-
matplotlib.testing.set_font_settings_for_testing()
251-
func(*args, **kwargs)
252-
253234
# Parameter indirection:
254235
# This is hacked on via the mpl_image_comparison_parameters fixture
255236
# so that we don't need to modify the function's real signature for
256237
# any parametrization. Modifying the signature is very very tricky
257238
# and likely to confuse pytest.
258239
baseline_images, extension = func.parameters
259240

260-
assert len(plt.get_fignums()) == len(baseline_images), (
261-
"Test generated {} images but there are {} baseline images"
262-
.format(len(plt.get_fignums()), len(baseline_images)))
263-
for idx, baseline in enumerate(baseline_images):
264-
img.compare(idx, baseline, extension)
241+
matplotlib.testing.set_font_settings_for_testing()
242+
comparator = _make_image_comparator(
243+
func,
244+
baseline_images=baseline_images, extension=extension, tol=tol,
245+
remove_text=remove_text, savefig_kwargs=savefig_kwargs)
246+
comparator(*args, **kwargs)
265247

266248
return wrapper
267249

@@ -345,8 +327,6 @@ def image_comparison(baseline_images, extensions=None, tol=0,
345327
if extensions is None:
346328
# Default extensions to test, if not set via baseline_images.
347329
extensions = ['png', 'pdf', 'svg']
348-
if savefig_kwarg is None:
349-
savefig_kwarg = dict() # default no kwargs to savefig
350330
return _pytest_image_comparison(
351331
baseline_images=baseline_images, extensions=extensions, tol=tol,
352332
freetype_version=freetype_version, remove_text=remove_text,
@@ -389,6 +369,7 @@ def decorator(func):
389369

390370
@pytest.mark.parametrize("ext", extensions)
391371
def wrapper(*args, ext, **kwargs):
372+
_skip_if_uncomparable(ext)
392373
try:
393374
fig_test = plt.figure("test")
394375
fig_ref = plt.figure("reference")

0 commit comments

Comments
 (0)