|
3 | 3 | """
|
4 | 4 |
|
5 | 5 | import atexit
|
| 6 | +import functools |
6 | 7 | import hashlib
|
| 8 | +import logging |
7 | 9 | import os
|
8 | 10 | from pathlib import Path
|
9 | 11 | import re
|
|
19 | 21 | from matplotlib import cbook
|
20 | 22 | from matplotlib.testing.exceptions import ImageComparisonFailure
|
21 | 23 |
|
| 24 | +_log = logging.getLogger(__name__) |
| 25 | + |
22 | 26 | __all__ = ['compare_images', 'comparable_formats']
|
23 | 27 |
|
24 | 28 |
|
@@ -285,20 +289,50 @@ def convert(filename, cache):
|
285 | 289 | cache_dir = Path(get_cache_dir()) if cache else None
|
286 | 290 |
|
287 | 291 | if cache_dir is not None:
|
| 292 | + _register_conversion_cache_cleaner_once() |
288 | 293 | hash_value = get_file_hash(path)
|
289 | 294 | cached_path = cache_dir / (hash_value + newpath.suffix)
|
290 | 295 | if cached_path.exists():
|
| 296 | + _log.debug("For %s: reusing cached conversion.", filename) |
291 | 297 | shutil.copyfile(cached_path, newpath)
|
292 | 298 | return str(newpath)
|
293 | 299 |
|
| 300 | + _log.debug("For %s: converting to png.", filename) |
294 | 301 | converter[path.suffix[1:]](path, newpath)
|
295 | 302 |
|
296 | 303 | if cache_dir is not None:
|
| 304 | + _log.debug("For %s: caching conversion result.", filename) |
297 | 305 | shutil.copyfile(newpath, cached_path)
|
298 | 306 |
|
299 | 307 | return str(newpath)
|
300 | 308 |
|
301 | 309 |
|
| 310 | +def _clean_conversion_cache(): |
| 311 | + # This will actually ignore mpl_toolkits baseline images, but they're |
| 312 | + # relatively small. |
| 313 | + baseline_images_size = sum( |
| 314 | + path.stat().st_size |
| 315 | + for path in Path(mpl.__file__).parent.glob("**/baseline_images/**/*")) |
| 316 | + # 2x: one full copy of baselines, and one full copy of test results |
| 317 | + # (actually an overestimate: we don't convert png baselines and results). |
| 318 | + max_cache_size = 2 * baseline_images_size |
| 319 | + # Reduce cache until it fits. |
| 320 | + cache_stat = { |
| 321 | + path: path.stat() for path in Path(get_cache_dir()).glob("*")} |
| 322 | + cache_size = sum(stat.st_size for stat in cache_stat.values()) |
| 323 | + paths_by_atime = sorted( # Oldest at the end. |
| 324 | + cache_stat, key=lambda path: cache_stat[path].st_atime, reverse=True) |
| 325 | + while cache_size > max_cache_size: |
| 326 | + path = paths_by_atime.pop() |
| 327 | + cache_size -= cache_stat[path].st_size |
| 328 | + path.unlink() |
| 329 | + |
| 330 | + |
| 331 | +@functools.lru_cache() # Ensure this is only registered once. |
| 332 | +def _register_conversion_cache_cleaner_once(): |
| 333 | + atexit.register(_clean_conversion_cache) |
| 334 | + |
| 335 | + |
302 | 336 | def crop_to_same(actual_path, actual_image, expected_path, expected_image):
|
303 | 337 | # clip the images to the same size -- this is useful only when
|
304 | 338 | # comparing eps to pdf
|
@@ -387,7 +421,7 @@ def compare_images(expected, actual, tol, in_decorator=False):
|
387 | 421 | raise IOError('Baseline image %r does not exist.' % expected)
|
388 | 422 | extension = expected.split('.')[-1]
|
389 | 423 | if extension != 'png':
|
390 |
| - actual = convert(actual, cache=False) |
| 424 | + actual = convert(actual, cache=True) |
391 | 425 | expected = convert(expected, cache=True)
|
392 | 426 |
|
393 | 427 | # open the image files and remove the alpha channel (if it exists)
|
|
0 commit comments