Skip to content

Commit 3a5d8c2

Browse files
authored
Merge pull request #11408 from anntzer/check_figures_equal
Figure equality-based tests.
2 parents 0a7c0d2 + 9cd3fbe commit 3a5d8c2

File tree

3 files changed

+70
-25
lines changed

3 files changed

+70
-25
lines changed

lib/matplotlib/testing/decorators.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,46 @@ def image_comparison(baseline_images, extensions=None, tol=0,
407407
savefig_kwargs=savefig_kwarg, style=style)
408408

409409

410+
def check_figures_equal(*, extensions=("png", "pdf", "svg"), tol=0):
411+
"""
412+
Decorator for test cases that generate and compare two figures.
413+
414+
The decorated function must take two arguments, *fig_test* and *fig_ref*,
415+
and draw the test and reference images on them. After the function
416+
returns, the figures are saved and compared.
417+
418+
Arguments
419+
---------
420+
extensions : list, default: ["png", "pdf", "svg"]
421+
The extensions to test.
422+
tol : float
423+
The RMS threshold above which the test is considered failed.
424+
"""
425+
426+
def decorator(func):
427+
import pytest
428+
429+
_, result_dir = map(Path, _image_directories(func))
430+
431+
@pytest.mark.parametrize("ext", extensions)
432+
def wrapper(ext):
433+
fig_test = plt.figure("test")
434+
fig_ref = plt.figure("reference")
435+
func(fig_test, fig_ref)
436+
test_image_path = str(
437+
result_dir / (func.__name__ + "." + ext))
438+
ref_image_path = str(
439+
result_dir / (func.__name__ + "-expected." + ext))
440+
fig_test.savefig(test_image_path)
441+
fig_ref.savefig(ref_image_path)
442+
_raise_on_image_difference(
443+
ref_image_path, test_image_path, tol=tol)
444+
445+
return wrapper
446+
447+
return decorator
448+
449+
410450
def _image_directories(func):
411451
"""
412452
Compute the baseline and result image directories for testing *func*.

lib/matplotlib/tests/test_axes.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import warnings
1515

1616
import matplotlib
17-
from matplotlib.testing.decorators import image_comparison
17+
from matplotlib.testing.decorators import image_comparison, check_figures_equal
1818
import matplotlib.pyplot as plt
1919
import matplotlib.markers as mmarkers
2020
import matplotlib.patches as mpatches
@@ -5702,18 +5702,11 @@ def test_plot_columns_cycle_deprecation():
57025702
plt.plot(np.zeros((2, 2)), np.zeros((2, 3)))
57035703

57045704

5705-
def test_markerfacecolor_none_alpha():
5706-
fig1, ax1 = plt.subplots()
5707-
ax1.plot(0, "o", mfc="none", alpha=.5)
5708-
buf1 = io.BytesIO()
5709-
fig1.savefig(buf1)
5710-
5711-
fig2, ax2 = plt.subplots()
5712-
ax2.plot(0, "o", mfc="w", alpha=.5)
5713-
buf2 = io.BytesIO()
5714-
fig2.savefig(buf2)
5715-
5716-
assert buf1.getvalue() == buf2.getvalue()
5705+
# pdf and svg tests fail using travis' old versions of gs and inkscape.
5706+
@check_figures_equal(extensions=["png"])
5707+
def test_markerfacecolor_none_alpha(fig_test, fig_ref):
5708+
fig_test.subplots().plot(0, "o", mfc="none", alpha=.5)
5709+
fig_ref.subplots().plot(0, "o", mfc="w", alpha=.5)
57175710

57185711

57195712
def test_tick_padding_tightbbox():

tools/triage_tests.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,22 @@ def set_large_image(self, index):
192192
self.thumbnails[self.current_thumbnail].setFrameShape(1)
193193

194194
def accept_test(self):
195-
self.entries[self.current_entry].accept()
195+
entry = self.entries[self.current_entry]
196+
if entry.status == 'autogen':
197+
print('Cannot accept autogenerated test cases.')
198+
return
199+
entry.accept()
196200
self.filelist.currentItem().setText(
197201
self.entries[self.current_entry].display)
198202
# Auto-move to the next entry
199203
self.set_entry(min((self.current_entry + 1), len(self.entries) - 1))
200204

201205
def reject_test(self):
202-
self.entries[self.current_entry].reject()
206+
entry = self.entries[self.current_entry]
207+
if entry.status == 'autogen':
208+
print('Cannot reject autogenerated test cases.')
209+
return
210+
entry.reject()
203211
self.filelist.currentItem().setText(
204212
self.entries[self.current_entry].display)
205213
# Auto-move to the next entry
@@ -261,11 +269,14 @@ def __init__(self, path, root, source):
261269
]
262270
self.thumbnails = [os.path.join(self.dir, x) for x in self.thumbnails]
263271

264-
self.status = 'unknown'
265-
266-
if self.same(os.path.join(self.dir, self.generated),
272+
if not Path(self.destdir, self.generated).exists():
273+
# This case arises from a check_figures_equal test.
274+
self.status = 'autogen'
275+
elif self.same(os.path.join(self.dir, self.generated),
267276
os.path.join(self.destdir, self.generated)):
268277
self.status = 'accept'
278+
else:
279+
self.status = 'unknown'
269280

270281
def same(self, a, b):
271282
"""
@@ -297,16 +308,18 @@ def display(self):
297308
Get the display string for this entry. This is the text that
298309
appears in the list widget.
299310
"""
300-
status_map = {'unknown': '\N{BALLOT BOX}',
301-
'accept': '\N{BALLOT BOX WITH CHECK}',
302-
'reject': '\N{BALLOT BOX WITH X}'}
311+
status_map = {
312+
'unknown': '\N{BALLOT BOX}',
313+
'accept': '\N{BALLOT BOX WITH CHECK}',
314+
'reject': '\N{BALLOT BOX WITH X}',
315+
'autogen': '\N{WHITE SQUARE CONTAINING BLACK SMALL SQUARE}',
316+
}
303317
box = status_map[self.status]
304318
return '{} {} [{}]'.format(box, self.name, self.extension)
305319

306320
def accept(self):
307321
"""
308-
Accept this test by copying the generated result to the
309-
source tree.
322+
Accept this test by copying the generated result to the source tree.
310323
"""
311324
a = os.path.join(self.dir, self.generated)
312325
b = os.path.join(self.destdir, self.generated)
@@ -315,8 +328,7 @@ def accept(self):
315328

316329
def reject(self):
317330
"""
318-
Reject this test by copying the expected result to the
319-
source tree.
331+
Reject this test by copying the expected result to the source tree.
320332
"""
321333
a = os.path.join(self.dir, self.expected)
322334
b = os.path.join(self.destdir, self.generated)

0 commit comments

Comments
 (0)