conftest.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import pytest
  2. import sys
  3. import matplotlib
  4. from matplotlib import _api
  5. def pytest_configure(config):
  6. # config is initialized here rather than in pytest.ini so that `pytest
  7. # --pyargs matplotlib` (which would not find pytest.ini) works. The only
  8. # entries in pytest.ini set minversion (which is checked earlier),
  9. # testpaths/python_files, as they are required to properly find the tests
  10. for key, value in [
  11. ("markers", "flaky: (Provided by pytest-rerunfailures.)"),
  12. ("markers", "timeout: (Provided by pytest-timeout.)"),
  13. ("markers", "backend: Set alternate Matplotlib backend temporarily."),
  14. ("markers", "baseline_images: Compare output against references."),
  15. ("markers", "pytz: Tests that require pytz to be installed."),
  16. ("filterwarnings", "error"),
  17. ("filterwarnings",
  18. "ignore:.*The py23 module has been deprecated:DeprecationWarning"),
  19. ("filterwarnings",
  20. r"ignore:DynamicImporter.find_spec\(\) not found; "
  21. r"falling back to find_module\(\):ImportWarning"),
  22. ]:
  23. config.addinivalue_line(key, value)
  24. matplotlib.use('agg', force=True)
  25. matplotlib._called_from_pytest = True
  26. matplotlib._init_tests()
  27. def pytest_unconfigure(config):
  28. matplotlib._called_from_pytest = False
  29. @pytest.fixture(autouse=True)
  30. def mpl_test_settings(request):
  31. from matplotlib.testing.decorators import _cleanup_cm
  32. with _cleanup_cm():
  33. backend = None
  34. backend_marker = request.node.get_closest_marker('backend')
  35. prev_backend = matplotlib.get_backend()
  36. if backend_marker is not None:
  37. assert len(backend_marker.args) == 1, \
  38. "Marker 'backend' must specify 1 backend."
  39. backend, = backend_marker.args
  40. skip_on_importerror = backend_marker.kwargs.get(
  41. 'skip_on_importerror', False)
  42. # special case Qt backend importing to avoid conflicts
  43. if backend.lower().startswith('qt5'):
  44. if any(sys.modules.get(k) for k in ('PyQt4', 'PySide')):
  45. pytest.skip('Qt4 binding already imported')
  46. matplotlib.testing.setup()
  47. with _api.suppress_matplotlib_deprecation_warning():
  48. if backend is not None:
  49. # This import must come after setup() so it doesn't load the
  50. # default backend prematurely.
  51. import matplotlib.pyplot as plt
  52. try:
  53. plt.switch_backend(backend)
  54. except ImportError as exc:
  55. # Should only occur for the cairo backend tests, if neither
  56. # pycairo nor cairocffi are installed.
  57. if 'cairo' in backend.lower() or skip_on_importerror:
  58. pytest.skip("Failed to switch to backend "
  59. f"{backend} ({exc}).")
  60. else:
  61. raise
  62. # Default of cleanup and image_comparison too.
  63. matplotlib.style.use(["classic", "_classic_test_patch"])
  64. try:
  65. yield
  66. finally:
  67. if backend is not None:
  68. plt.close("all")
  69. matplotlib.use(prev_backend)
  70. @pytest.fixture
  71. def pd():
  72. """
  73. Fixture to import and configure pandas. Using this fixture, the test is skipped when
  74. pandas is not installed. Use this fixture instead of importing pandas in test files.
  75. Examples
  76. --------
  77. Request the pandas fixture by passing in ``pd`` as an argument to the test ::
  78. def test_matshow_pandas(pd):
  79. df = pd.DataFrame({'x':[1,2,3], 'y':[4,5,6]})
  80. im = plt.figure().subplots().matshow(df)
  81. np.testing.assert_array_equal(im.get_array(), df)
  82. """
  83. pd = pytest.importorskip('pandas')
  84. try:
  85. from pandas.plotting import (
  86. deregister_matplotlib_converters as deregister)
  87. deregister()
  88. except ImportError:
  89. pass
  90. return pd
  91. @pytest.fixture
  92. def xr():
  93. """
  94. Fixture to import xarray so that the test is skipped when xarray is not installed.
  95. Use this fixture instead of importing xrray in test files.
  96. Examples
  97. --------
  98. Request the xarray fixture by passing in ``xr`` as an argument to the test ::
  99. def test_imshow_xarray(xr):
  100. ds = xr.DataArray(np.random.randn(2, 3))
  101. im = plt.figure().subplots().imshow(ds)
  102. np.testing.assert_array_equal(im.get_array(), ds)
  103. """
  104. xr = pytest.importorskip('xarray')
  105. return xr
  106. @pytest.fixture
  107. def text_placeholders(monkeypatch):
  108. """
  109. Replace texts with placeholder rectangles.
  110. The rectangle size only depends on the font size and the number of characters. It is
  111. thus insensitive to font properties and rendering details. This should be used for
  112. tests that depend on text geometries but not the actual text rendering, e.g. layout
  113. tests.
  114. """
  115. from matplotlib.patches import Rectangle
  116. def patched_get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi):
  117. """
  118. Replace ``_get_text_metrics_with_cache`` with fixed results.
  119. The usual ``renderer.get_text_width_height_descent`` would depend on font
  120. metrics; instead the fixed results are based on font size and the length of the
  121. string only.
  122. """
  123. # While get_window_extent returns pixels and font size is in points, font size
  124. # includes ascenders and descenders. Leaving out this factor and setting
  125. # descent=0 ends up with a box that is relatively close to DejaVu Sans.
  126. height = fontprop.get_size()
  127. width = len(text) * height / 1.618 # Golden ratio for character size.
  128. descent = 0
  129. return width, height, descent
  130. def patched_text_draw(self, renderer):
  131. """
  132. Replace ``Text.draw`` with a fixed bounding box Rectangle.
  133. The bounding box corresponds to ``Text.get_window_extent``, which ultimately
  134. depends on the above patched ``_get_text_metrics_with_cache``.
  135. """
  136. if renderer is not None:
  137. self._renderer = renderer
  138. if not self.get_visible():
  139. return
  140. if self.get_text() == '':
  141. return
  142. bbox = self.get_window_extent()
  143. rect = Rectangle(bbox.p0, bbox.width, bbox.height,
  144. facecolor=self.get_color(), edgecolor='none')
  145. rect.draw(renderer)
  146. monkeypatch.setattr('matplotlib.text._get_text_metrics_with_cache',
  147. patched_get_text_metrics_with_cache)
  148. monkeypatch.setattr('matplotlib.text.Text.draw', patched_text_draw)