From 8a0e56eeae140e27ae6563dfe078f83f4e699c0c Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 1 Jun 2017 21:10:28 -0400 Subject: [PATCH 1/4] Update FreeType version in test_tightlayout4. 2.5.5 is what's in my conda env, and 2.6.1 is what's built with the local_freetype option and is used on Travis and AppVeyor without issue. --- lib/matplotlib/tests/test_tightlayout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 76d9c1424212..03ac60a3e0d0 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -58,7 +58,7 @@ def test_tight_layout3(): @image_comparison(baseline_images=['tight_layout4'], - freetype_version=('2.4.5', '2.4.9')) + freetype_version=('2.5.5', '2.6.1')) def test_tight_layout4(): 'Test tight_layout for subplot2grid' From 7a49d2085f3d46ba9b94c034ea2a34184d064560 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 2 Jun 2017 22:44:03 -0400 Subject: [PATCH 2/4] Add a unique number to any renderer hash keys. The result of id() is only guaranteed to be unique for objects that exist at the same time. This global integer is a quick and light way to ensure that new renderers that match an old one don't produce the same caching key. --- .../2017-06-03-ES_unique_renderer.rst | 11 ++++++++++ lib/matplotlib/backend_bases.py | 20 +++++++++++++++++++ lib/matplotlib/backends/backend_mixed.py | 13 ++++++++++++ lib/matplotlib/text.py | 3 ++- 4 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 doc/api/api_changes/2017-06-03-ES_unique_renderer.rst diff --git a/doc/api/api_changes/2017-06-03-ES_unique_renderer.rst b/doc/api/api_changes/2017-06-03-ES_unique_renderer.rst new file mode 100644 index 000000000000..3dd58e0a16f3 --- /dev/null +++ b/doc/api/api_changes/2017-06-03-ES_unique_renderer.rst @@ -0,0 +1,11 @@ +Unique identifier added to `RendererBase` classes +````````````````````````````````````````````````` + +Since ``id()`` is not guaranteed to be unique between objects that exist at +different times, a new private property ``_uid`` has been added to +`RendererBase` which is used along with the renderer's ``id()`` to cache +certain expensive operations. + +If a custom renderer does not subclass `RendererBase` or `MixedModeRenderer`, +it is not required to implement this ``_uid`` property, but this may produce +incorrect behavior when the renderers' ``id()`` clashes. diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index 3438210023f1..d5366850086e 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -100,6 +100,11 @@ } +# Used to ensure that caching based on renderer id() is unique without being as +# expensive as a real UUID. +_unique_renderer_id = 0 + + def register_backend(format, backend, description=None): """ Register a backend for saving to a given file format. @@ -212,10 +217,25 @@ class RendererBase(object): """ def __init__(self): + self._id = None self._texmanager = None self._text2path = textpath.TextToPath() + @property + def _uid(self): + """ + A lightweight id for unique-ification purposes. + + Along with id(self), this combination should be unique enough to use as + part of a caching key. + """ + if self._id is None: + global _unique_renderer_id + _unique_renderer_id += 1 + self._id = _unique_renderer_id + return self._id + def open_group(self, s, gid=None): """ Open a grouping element with label *s*. If *gid* is given, use diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 40d7fd64398c..3bd7a8c7d647 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -49,6 +49,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer, if raster_renderer_class is None: raster_renderer_class = RendererAgg + self._id = None self._raster_renderer_class = raster_renderer_class self._width = width self._height = height @@ -80,6 +81,18 @@ def __init__(self, figure, width, height, dpi, vector_renderer, _text2path _get_text_path_transform height width """.split() + @property + def _uid(self): + """ + See matplotlib.backend_bases.RendererBase._uid. + """ + if self._id is None: + from matplotlib import backend_bases + backend_bases._unique_renderer_id + backend_bases._unique_renderer_id += 1 + self._id = backend_bases._unique_renderer_id + return self._id + def _set_current_renderer(self, renderer): self._renderer = renderer diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index b11ae28ba0cc..caa5dcdaf73f 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -907,11 +907,12 @@ def get_prop_tup(self, renderer=None): need to know if the text has changed. """ x, y = self.get_unitless_position() + renderer = renderer or self._renderer return (x, y, self.get_text(), self._color, self._verticalalignment, self._horizontalalignment, hash(self._fontproperties), self._rotation, self._rotation_mode, - self.figure.dpi, id(renderer or self._renderer), + self.figure.dpi, id(renderer), getattr(renderer, '_uid', 0), self._linespacing ) From e05072980ba9930fd79958af4698e4214fe55e09 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 3 Jun 2017 02:23:46 -0400 Subject: [PATCH 3/4] Update tests that are marked flaky. The Mathtext tests should no longer be flaky based on the previous change. Mark some PS tests as flaky because they require a lock that sometimes gets stuck on AppVeyor due to the multiple processes. --- lib/matplotlib/tests/test_backend_pdf.py | 2 ++ lib/matplotlib/tests/test_backend_ps.py | 2 ++ lib/matplotlib/tests/test_mathtext.py | 6 ------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/tests/test_backend_pdf.py b/lib/matplotlib/tests/test_backend_pdf.py index 3529ea8541db..d52fc3efdf66 100644 --- a/lib/matplotlib/tests/test_backend_pdf.py +++ b/lib/matplotlib/tests/test_backend_pdf.py @@ -178,6 +178,8 @@ def test_grayscale_alpha(): ax.set_yticks([]) +# This tests tends to hit a TeX cache lock on AppVeyor. +@pytest.mark.flaky(reruns=3) @needs_tex def test_missing_psfont(monkeypatch): """An error is raised if a TeX font lacks a Type-1 equivalent""" diff --git a/lib/matplotlib/tests/test_backend_ps.py b/lib/matplotlib/tests/test_backend_ps.py index 8ead41d67d01..cd6e5feae14a 100644 --- a/lib/matplotlib/tests/test_backend_ps.py +++ b/lib/matplotlib/tests/test_backend_ps.py @@ -27,6 +27,8 @@ reason="This test needs a TeX installation") +# This tests tends to hit a TeX cache lock on AppVeyor. +@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('format, use_log, rcParams', [ ('ps', False, {}), needs_ghostscript(('ps', False, {'ps.usedistiller': 'ghostscript'})), diff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py index f913445ac6c1..544d3ef89201 100644 --- a/lib/matplotlib/tests/test_mathtext.py +++ b/lib/matplotlib/tests/test_mathtext.py @@ -167,9 +167,6 @@ def baseline_images(request, fontset, index): return ['%s_%s_%02d' % (request.param, fontset, index)] -# See #7911 for why these tests are flaky and #7107 for why they are not so -# easy to fix. -@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('index, test', enumerate(math_tests), ids=[str(index) for index in range(len(math_tests))]) @pytest.mark.parametrize('fontset', @@ -184,9 +181,6 @@ def test_mathtext_rendering(baseline_images, fontset, index, test): horizontalalignment='center', verticalalignment='center') -# See #7911 for why these tests are flaky and #7107 for why they are not so -# easy to fix. -@pytest.mark.flaky(reruns=3) @pytest.mark.parametrize('index, test', enumerate(font_tests), ids=[str(index) for index in range(len(font_tests))]) @pytest.mark.parametrize('fontset', From 2005738a8dca9e1663fafc7114ffc0f4b982b012 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Sat, 3 Jun 2017 20:16:40 -0400 Subject: [PATCH 4/4] Use itertools.count for renderer unique ID. As suggested by @anntzer. --- lib/matplotlib/backend_bases.py | 25 ++++++++---------------- lib/matplotlib/backends/backend_mixed.py | 17 ++++------------ 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d5366850086e..7331d71f8014 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -41,6 +41,7 @@ from contextlib import contextmanager import importlib import io +import itertools import os import sys import time @@ -101,8 +102,9 @@ # Used to ensure that caching based on renderer id() is unique without being as -# expensive as a real UUID. -_unique_renderer_id = 0 +# expensive as a real UUID. 0 is used for renderers that don't derive from +# here, so start at 1. +_unique_renderer_id = itertools.count(1) def register_backend(format, backend, description=None): @@ -217,25 +219,14 @@ class RendererBase(object): """ def __init__(self): - self._id = None + # A lightweight id for unique-ification purposes. Along with id(self), + # the combination should be unique enough to use as part of a cache key. + self._uid = next(_unique_renderer_id) + self._texmanager = None self._text2path = textpath.TextToPath() - @property - def _uid(self): - """ - A lightweight id for unique-ification purposes. - - Along with id(self), this combination should be unique enough to use as - part of a caching key. - """ - if self._id is None: - global _unique_renderer_id - _unique_renderer_id += 1 - self._id = _unique_renderer_id - return self._id - def open_group(self, s, gid=None): """ Open a grouping element with label *s*. If *gid* is given, use diff --git a/lib/matplotlib/backends/backend_mixed.py b/lib/matplotlib/backends/backend_mixed.py index 3bd7a8c7d647..a93ef062f279 100644 --- a/lib/matplotlib/backends/backend_mixed.py +++ b/lib/matplotlib/backends/backend_mixed.py @@ -5,6 +5,7 @@ import six +import matplotlib.backend_bases from matplotlib.backends.backend_agg import RendererAgg from matplotlib.tight_bbox import process_figure_for_rasterizing @@ -49,7 +50,9 @@ def __init__(self, figure, width, height, dpi, vector_renderer, if raster_renderer_class is None: raster_renderer_class = RendererAgg - self._id = None + # See matplotlib.backend_bases.RendererBase._uid. + self._uid = next(matplotlib.backend_bases._unique_renderer_id) + self._raster_renderer_class = raster_renderer_class self._width = width self._height = height @@ -81,18 +84,6 @@ def __init__(self, figure, width, height, dpi, vector_renderer, _text2path _get_text_path_transform height width """.split() - @property - def _uid(self): - """ - See matplotlib.backend_bases.RendererBase._uid. - """ - if self._id is None: - from matplotlib import backend_bases - backend_bases._unique_renderer_id - backend_bases._unique_renderer_id += 1 - self._id = backend_bases._unique_renderer_id - return self._id - def _set_current_renderer(self, renderer): self._renderer = renderer