8000 TST: Make image_comparison more pytest-y. · matplotlib/matplotlib@bdd947c · GitHub
[go: up one dir, main page]

Skip to content

Commit bdd947c

Browse files
committed
TST: Make image_comparison more pytest-y.
Instead of a heavy do-it-all class, split ImageComparisonDecorator into a smaller class that does just the comparison stuff and one that does only nose. For pytest, use a wrapper function that's decorated only by pytest decorators, and don't try to modify the function signature. By using a separate fixture, we can indirectly return the parameterized arguments instead. This stops pytest from getting confused about what takes what argument. The biggest benefit is that test code is now run as the *test*, whereas previously, it was run as the *setup* causing it to have all sorts of semantic irregularities.
1 parent 18edd50 commit bdd947c

File tree

5 files changed

+104
-68
lines changed

5 files changed

+104
-68
lines changed

lib/matplotlib/sphinxext/tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
unicode_literals)
33

44
from matplotlib.testing.conftest import (mpl_test_settings,
5+
mpl_image_comparison_parameters,
56
pytest_configure, pytest_unconfigure)

lib/matplotlib/testing/conftest.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,19 @@ def mpl_test_settings(request):
5353
plt.switch_backend(prev_backend)
5454
_do_cleanup(original_units_registry,
5555
original_settings)
56+
57+
58+
@pytest.fixture
59+
def mpl_image_comparison_parameters(request, extension):
60+
# This fixture is applied automatically by the image_comparison decorator.
61+
#
62+
# The sole purpose of this fixture is to provide an indirect method of
63+
# obtaining parameters *without* modifying the decorated function
64+
# signature. In this way, the function signature can stay the same and
65+
# pytest won't get confused.
66+
# We annotate the decorated function with any parameters captured by this
67+
# fixture so that they can be used by the wrapper in image_comparison.
68+
func = request.function
69+
func.__wrapped__.parameters = (extension, )
70+
yield
71+
delattr(func.__wrapped__, 'parameters')

lib/matplotlib/testing/decorators.py

Lines changed: 85 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -233,42 +233,18 @@ def _mark_xfail_if_format_is_uncomparable(extension):
233233
return extension
234234

235235

236-
class ImageComparisonDecorator(CleanupTest):
237-
def __init__(self, baseline_images, extensions, tol,
238-
freetype_version, remove_text, savefig_kwargs, style):
236+
class _ImageComparisonBase(object):
237+
def __init__(self, tol, remove_text, savefig_kwargs):
239238
self.func = self.baseline_dir = self.result_dir = None
240-
self.baseline_images = baseline_images
241-
self.extensions = extensions
242239
self.tol = tol
243-
self.freetype_version = freetype_version
244240
self.remove_text = remove_text
245241
self.savefig_kwargs = savefig_kwargs
246-
self.style = style
247242

248243
def delayed_init(self, func):
249244
assert self.func is None, "it looks like same decorator used twice"
250245
self.func = func
251246
self.baseline_dir, self.result_dir = _image_directories(func)
252247

253-
def setup(self):
254-
func = self.func
255-
plt.close('all')
256-
self.setup_class()
257-
try:
258-
matplotlib.style.use(self.style)
259-
matplotlib.testing.set_font_settings_for_testing()
260-
func()
261-
assert len(plt.get_fignums()) == len(self.baseline_images), (
262-
"Test generated {} images but there are {} baseline images"
263-
.format(len(plt.get_fignums()), len(self.baseline_images)))
264-
except:
265-
# Restore original settings before raising errors during the update.
266-
self.teardown_class()
267-
raise
268-
269-
def teardown(self):
270-
self.teardown_class()
271-
272248
def copy_baseline(self, baseline, extension):
273249
baseline_path = os.path.join(self.baseline_dir, baseline)
274250
orig_expected_fname = baseline_path + '.' + extension
@@ -304,6 +280,34 @@ def compare(self, idx, baseline, extension):
304280
expected_fname = self.copy_baseline(baseline, extension)
305281
_raise_on_image_difference(expected_fname, actual_fname, self.tol)
306282

283+
284+
class ImageComparisonDecorator(CleanupTest, _ImageComparisonBase):
285+
def __init__(self, baseline_images, extensions, tol,
286+
freetype_version, remove_text, savefig_kwargs, style):
287+
_ImageComparisonBase.__init__(self, tol, remove_text, savefig_kwargs)
288+
self.baseline_images = baseline_images
289+
self.extensions = extensions
290+
self.freetype_version = freetype_version
291+
self.style = style
292+
293+
def setup(self):
294+
plt.close('all')
295+
self.setup_class()
296+
try:
297+
matplotlib.style.use(self.style)
298+
matplotlib.testing.set_font_settings_for_testing()
299+
func()
300+
assert len(plt.get_fignums()) == len(self.baseline_images), (
301+
"Test generated {} images but there are {} baseline images"
302+
.format(len(plt.get_fignums()), len(self.baseline_images)))
303+
except:
304+
# Restore original settings before raising errors.
305+
self.teardown_class()
306+
raise
307+
308+
def teardown(self):
309+
self.teardown_class()
310+
307311
def nose_runner(self):
308312
func = self.compare
309313
func = _checked_on_freetype_version(self.freetype_version)(func)
@@ -313,52 +317,59 @@ def nose_runner(self):
313317
for extension in self.extensions:
314318
yield funcs[extension], idx, baseline, extension
315319

316-
def pytest_runner(self):
317-
from pytest import mark
320+
def __call__(self, func):
321+
self.delayed_init(func)
322+
import nose.tools
318323

319-
extensions = map(_mark_xfail_if_format_is_uncomparable,
320-
self.extensions)
324+
@nose.tools.with_setup(self.setup, self.teardown)
325+
def runner_wrapper():
326+
try:
327+
for case in self.nose_runner():
328+
yield case
329+
except GeneratorExit:
330+
# nose bug...
331+
self.teardown()
321332

322-
if len(set(self.baseline_images)) == len(self.baseline_images):
323-
@mark.parametrize("extension", extensions)
324-
@mark.parametrize("idx,baseline", enumerate(self.baseline_images))
325-
@_checked_on_freetype_version(self.freetype_version)
326-
def wrapper(idx, baseline, extension):
327-
__tracebackhide__ = True
328-
self.compare(idx, baseline, extension)
329-
else:
330-
# Some baseline images are repeated, so run this in serial.
331-
@mark.parametrize("extension", extensions)
332-
@_checked_on_freetype_version(self.freetype_version)
333-
def wrapper(extension):
334-
__tracebackhide__ = True
335-
for idx, baseline in enumerate(self.baseline_images):
336-
self.compare(idx, baseline, extension)
333+
return _copy_metadata(func, runner_wrapper)
337334

338335

339-
# sadly we cannot use fixture here because of visibility problems
340-
# and for for obvious reason avoid `_nose.tools.with_setup`
341-
wrapper.setup, wrapper.teardown = self.setup, self.teardown
336+
def _pytest_image_comparison(baseline_images, extensions, tol,
337+
freetype_version, remove_text, savefig_kwargs,
338+
style):
339+
import pytest
342340

343-
return wrapper
341+
extensions = map(_mark_xfail_if_format_is_uncomparable, extensions)
344342

345-
def __call__(self, func):
346-
self.delayed_init(func)
347-
if is_called_from_pytest():
348-
return _copy_metadata(func, self.pytest_runner())
349-
else:
350-
import nose.tools
343+
def decorator(func):
344+
@pytest.mark.usefixtures('mpl_image_comparison_parameters')
345+
@pytest.mark.parametrize('extension', extensions)
346+
@pytest.mark.style(style)
347+
@_checked_on_freetype_version(freetype_version)
348+
@functools.wraps(func)
349+
def wrapper(*args, **kwargs):
350+
__tracebackhide__ = True
351+
img = _ImageComparisonBase(tol=tol, remove_text=remove_text,
352+
savefig_kwargs=savefig_kwargs)
353+
img.delayed_init(func)
354+
matplotlib.testing.set_font_settings_for_testing()
355+
func(*args, **kwargs)
351356

352-
@nose.tools.with_setup(self.setup, self.teardown)
353-
def runner_wrapper():
354-
try:
355-
for case in self.nose_runner():
356-
yield case
357-
except GeneratorExit:
358-
# nose bug...
359-
self.teardown()
357+
# This is hacked on via the mpl_image_comparison_parameters fixture
358+
# so that we don't need to modify the function's real signature for
359+
# any parametrization. Modifying the signature is very very tricky
360+
# and likely to confuse pytest.
361+
extension, = func.parameters
362+
363+
assert len(plt.get_fignums()) == len(baseline_images), (
364+
"Test generated {} images but there are {} baseline images"
365+
.format(len(plt.get_fignums()), len(baseline_images)))
366+
for idx, baseline in enumerate(baseline_images):
367+
img.compare(idx, baseline, extension)
360368

361-
return _copy_metadata(func, runner_wrapper)
369+
wrapper.__wrapped__ = func # For Python 2.7.
370+
return _copy_metadata(func, wrapper)
371+
372+
return decorator
362373

363374

364375
def image_comparison(baseline_images=None, extensions=None, tol=0,
@@ -414,10 +425,16 @@ def image_comparison(baseline_images=None, extensions=None, tol=0,
414425
#default no kwargs to savefig
415426
savefig_kwarg = dict()
416427

417-
return ImageComparisonDecorator(
418-
baseline_images=baseline_images, extensions=extensions, tol=tol,
419-
freetype_version=freetype_version, remove_text=remove_text,
420-
savefig_kwargs=savefig_kwarg, style=style)
428+
if is_called_from_pytest():
429+
return _pytest_image_comparison(
430+
baseline_images=baseline_images, extensions=extensions, tol=tol,
431+
freetype_version=freetype_version, remove_text=remove_text,
432+
savefig_kwargs=savefig_kwarg, style=style)
433+
else:
434+
return ImageComparisonDecorator(
435+
baseline_images=baseline_images, extensions=extensions, tol=tol,
436+
freetype_version=freetype_version, remove_text=remove_text,
437+
savefig_kwargs=savefig_kwarg, style=style)
421438

422439

423440
def _image_directories(func):

lib/matplotlib/tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
unicode_literals)
33

44
from matplotlib.testing.conftest import (mpl_test_settings,
5+
mpl_image_comparison_parameters,
56
pytest_configure, pytest_unconfigure)

lib/mpl_toolkits/tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
unicode_literals)
33

44
from matplotlib.testing.conftest import (mpl_test_settings,
5+
mpl_image_comparison_parameters,
56
pytest_configure, pytest_unconfigure)

0 commit comments

Comments
 (0)
0