Skip to content

Commit 5ed01ab

Browse files
committed
Use test cache for test result images too.
For image comparison tests in svg/pdf/ps formats, the result images are converted to png for comparison. Previously the conversion results were cached for the baseline images, but not for the test-generated images (because of non-deterministic svg/pdf/etc. results, due to hash-salting, dict ordering, etc.). Now that the test-generated images are generally deterministic, we can enable the cache for baseline images as well. This speeds up `pytest -k '[svg]'` by ~30% (81s initially -> 55s on a seeded cache) and `pytest -k '[pdf]'` by ~10% (62s -> 55s) (there are too few (e)ps image comparison tests to see an effect). Also add logging regarding the cache which may help troubleshooting determinacy problems. A simple cache eviction mechanism prevents the cache from growing without bounds, limiting it to 2x the size of the baseline_images directory. This is a much simpler version of PR7764, which added more sophisticated reporting of cache hits and misses and cache eviction.
1 parent db7cd7e commit 5ed01ab

File tree

1 file changed

+35
-2
lines changed

1 file changed

+35
-2
lines changed

lib/matplotlib/testing/compare.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""
22
Provides a collection of utilities for comparing (image) results.
3-
43
"""
54

65
import atexit
6+
import functools
77
import hashlib
8+
import logging
89
import os
910
from pathlib import Path
1011
import re
@@ -19,6 +20,8 @@
1920
import matplotlib as mpl
2021
from matplotlib.testing.exceptions import ImageComparisonFailure
2122

23+
_log = logging.getLogger(__name__)
24+
2225
__all__ = ['compare_images', 'comparable_formats']
2326

2427

@@ -281,21 +284,51 @@ def convert(filename, cache):
281284
cache_dir = None
282285

283286
if cache_dir is not None:
287+
_register_conversion_cache_cleaner_once()
284288
hash_value = get_file_hash(filename)
285289
new_ext = os.path.splitext(newname)[1]
286290
cached_file = os.path.join(cache_dir, hash_value + new_ext)
287291
if os.path.exists(cached_file):
292+
_log.debug("For %s: reusing cached conversion.", filename)
288293
shutil.copyfile(cached_file, newname)
289294
return newname
290295

296+
_log.debug("For %s: converting to png.", filename)
291297
converter[extension](filename, newname)
292298

293299
if cache_dir is not None:
300+
_log.debug("For %s: caching conversion result.", filename)
294301
shutil.copyfile(newname, cached_file)
295302

296303
return newname
297304

298305

306+
def _clean_conversion_cache():
307+
# This will actually ignore mpl_toolkits baseline images, but they're
308+
# relatively small.
309+
baseline_images_size = sum(
310+
path.stat().st_size
311+
for path in Path(mpl.__file__).parent.glob("**/baseline_images/**/*"))
312+
# 2x: one full copy of baselines, and one full copy of test results
313+
# (actually an overestimate: we don't convert png baselines and results).
314+
max_cache_size = 2 * baseline_images_size
315+
# Reduce cache until it fits.
316+
cache_stat = {
317+
path: path.stat() for path in Path(get_cache_dir()).glob("*")}
318+
cache_size = sum(stat.st_size for stat in cache_stat.values())
319+
paths_by_atime = sorted( # Oldest at the end.
320+
cache_stat, key=lambda path: cache_stat[path].st_atime, reverse=True)
321+
while cache_size > max_cache_size:
322+
path = paths_by_atime.pop()
323+
cache_size -= cache_stat[path].st_size
324+
path.unlink()
325+
326+
327+
@functools.lru_cache() # Ensure this is only registered once.
328+
def _register_conversion_cache_cleaner_once():
329+
atexit.register(_clean_conversion_cache)
330+
331+
299332
def crop_to_same(actual_path, actual_image, expected_path, expected_image):
300333
# clip the images to the same size -- this is useful only when
301334
# comparing eps to pdf
@@ -382,7 +415,7 @@ def compare_images(expected, actual, tol, in_decorator=False):
382415
raise IOError('Baseline image %r does not exist.' % expected)
383416
extension = expected.split('.')[-1]
384417
if extension != 'png':
385-
actual = convert(actual, cache=False)
418+
actual = convert(actual, cache=True)
386419
expected = convert(expected, cache=True)
387420

388421
# open the image files and remove the alpha channel (if it exists)

0 commit comments

Comments
 (0)