|
1 | 1 | """
|
2 | 2 | Provides a collection of utilities for comparing (image) results.
|
3 |
| -
|
4 | 3 | """
|
5 | 4 |
|
6 | 5 | import atexit
|
| 6 | +import functools |
7 | 7 | import hashlib
|
| 8 | +import logging |
8 | 9 | import os
|
9 | 10 | from pathlib import Path
|
10 | 11 | import re
|
|
19 | 20 | import matplotlib as mpl
|
20 | 21 | from matplotlib.testing.exceptions import ImageComparisonFailure
|
21 | 22 |
|
| 23 | +_log = logging.getLogger(__name__) |
| 24 | + |
22 | 25 | __all__ = ['compare_images', 'comparable_formats']
|
23 | 26 |
|
24 | 27 |
|
@@ -281,21 +284,51 @@ def convert(filename, cache):
|
281 | 284 | cache_dir = None
|
282 | 285 |
|
283 | 286 | if cache_dir is not None:
|
| 287 | + _register_conversion_cache_cleaner_once() |
284 | 288 | hash_value = get_file_hash(filename)
|
285 | 289 | new_ext = os.path.splitext(newname)[1]
|
286 | 290 | cached_file = os.path.join(cache_dir, hash_value + new_ext)
|
287 | 291 | if os.path.exists(cached_file):
|
| 292 | + _log.debug("For %s: reusing cached conversion.", filename) |
288 | 293 | shutil.copyfile(cached_file, newname)
|
289 | 294 | return newname
|
290 | 295 |
|
| 296 | + _log.debug("For %s: converting to png.", filename) |
291 | 297 | converter[extension](filename, newname)
|
292 | 298 |
|
293 | 299 | if cache_dir is not None:
|
| 300 | + _log.debug("For %s: caching conversion result.", filename) |
294 | 301 | shutil.copyfile(newname, cached_file)
|
295 | 302 |
|
296 | 303 | return newname
|
297 | 304 |
|
298 | 305 |
|
| 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 | + |
299 | 332 | def crop_to_same(actual_path, actual_image, expected_path, expected_image):
|
300 | 333 | # clip the images to the same size -- this is useful only when
|
301 | 334 | # comparing eps to pdf
|
@@ -382,7 +415,7 @@ def compare_images(expected, actual, tol, in_decorator=False):
|
382 | 415 | raise IOError('Baseline image %r does not exist.' % expected)
|
383 | 416 | extension = expected.split('.')[-1]
|
384 | 417 | if extension != 'png':
|
385 |
| - actual = convert(actual, cache=False) |
| 418 | + actual = convert(actual, cache=True) |
386 | 419 | expected = convert(expected, cache=True)
|
387 | 420 |
|
388 | 421 | # open the image files and remove the alpha channel (if it exists)
|
|
0 commit comments