8000 Make more of testing/ pytest-independent. · matplotlib/matplotlib@4b55644 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4b55644

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 d03f9e5 commit 4b55644

File tree

3 files changed

+65
-71
lines changed

3 files changed

+65
-71
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import subprocess
1212
import sys
1313
from tempfile import TemporaryFile
14+
import unittest
1415

1516
import numpy as np
1617
import PIL
@@ -252,6 +253,12 @@ def comparable_formats():
252253
return ['png', *converter]
253254

254255

256+
def _skip_if_uncomparable(ext):
257+
"""Raise `unittest.SkipTest` if an *ext* files cannot be compared."""
258+
if ext not in comparable_formats():
259+
raise unittest.SkipTest(f"Lacking dependency to compare {ext} files")
260+
261+
255262
def convert(filename, cache):
256263
"""
257264
Convert the named file to png; return the name of the created file.
@@ -264,6 +271,7 @@ def convert(filename, cache):
264271
path = Path(filename)
265272
if not path.exists():
266273
raise IOError(f"{path} does not exist")
274+
_skip_if_uncomparable(path.suffix[1:])
267275
if path.suffix[1:] not in converter:
268276
import pytest
269277
pytest.skip(f"Don't know how to convert {path.suffix} files to png")

lib/matplotlib/testing/decorators.py

Lines changed: 51 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
from matplotlib import ft2font
1919
from matplotlib import pyplot as plt
2020
from matplotlib import ticker
21-
22-
from .compare import comparable_formats, compare_images, make_test_filename
21+
from .compare import compare_images, make_test_filename, _skip_if_uncomparable
2322
from .exceptions import ImageComparisonFailure
2423

2524

@@ -137,54 +136,33 @@ def _raise_on_image_difference(expected, actual, tol):
137136
% err)
138137

139138

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

174-
def __init__(self, func, tol, remove_text, savefig_kwargs):
175-
self.func = func
176-
self.baseline_dir, self.result_dir = _image_directories(func)
177-
self.tol = tol
178-
self.remove_text = remove_text
179-
self.savefig_kwargs = savefig_kwargs
154+
if savefig_kwargs is None:
155+
savefig_kwargs = {}
180156

181-
def copy_baseline(self, baseline, extension):
182-
baseline_path = self.baseline_dir / baseline
157+
baseline_dir, result_dir = _image_directories(func)
158+
159+
def _copy_baseline(baseline):
160+
baseline_path = baseline_dir / baseline
183161
orig_expected_path = baseline_path.with_suffix(f'.{extension}')
184162
if extension == 'eps' and not orig_expected_path.exists():
185163
orig_expected_path = orig_expected_path.with_suffix('.pdf')
186164
expected_fname = make_test_filename(
187-
self.result_dir / orig_expected_path.name, 'expected')
165+
result_dir / orig_expected_path.name, 'expected')
188166
try:
189167
# os.symlink errors if the target already exists.
190168
with contextlib.suppress(OSError):
@@ -200,24 +178,33 @@ def copy_baseline(self, baseline, extension):
200178
f"{orig_expected_path}") from err
201179
return expected_fname
202180

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

222209

223210
def _pytest_image_comparison(baseline_images, extensions, tol,
@@ -233,8 +220,6 @@ def _pytest_image_comparison(baseline_images, extensions, tol,
233220
"""
234221
import pytest
235222

236-
extensions = map(_mark_skip_if_format_is_uncomparable, extensions)
237-
238223
def decorator(func):
239224
@functools.wraps(func)
240225
# Parameter indirection; see docstring above and comment below.
@@ -247,23 +232,19 @@ def decorator(func):
247232
@functools.wraps(func)
248233
def wrapper(*args, **kwargs):
249234
__tracebackhide__ = True
250-
img = _ImageComparisonBase(func, tol=tol, remove_text=remove_text,
251-
savefig_kwargs=savefig_kwargs)
252-
matplotlib.testing.set_font_settings_for_testing()
253-
func(*args, **kwargs)
254-
255235
# Parameter indirection:
256236
# This is hacked on via the mpl_image_comparison_parameters fixture
257237
# so that we don't need to modify the function's real signature for
258238
# any parametrization. Modifying the signature is very very tricky
259239
# and likely to confuse pytest.
260240
baseline_images, extension = func.parameters
261241

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

268249
return wrapper
269250

@@ -347,8 +328,6 @@ def image_comparison(baseline_images, extensions=None, tol=0,
347328
if extensions is None:
348329
# Default extensions to test, if not set via baseline_images.
349330
extensions = ['png', 'pdf', 'svg']
350-
if savefig_kwarg is None:
351-
savefig_kwarg = dict() # default no kwargs to savefig
352331
return _pytest_image_comparison(
353332
baseline_images=baseline_images, extensions=extensions, tol=tol,
354333
freetype_version=freetype_version, remove_text=remove_text,
@@ -400,6 +379,7 @@ def decorator(func):
400379
@pytest.mark.parametrize("ext", extensions)
401380
def wrapper(*args, **kwargs):
402381
ext = kwargs['ext']
382+
_skip_if_uncomparable(ext)
403383
if 'ext' not in old_sig.parameters:
404384
kwargs.pop('ext')
405385
request = kwargs['request']

0 commit comments

Comments
 (0)
0