test_backend_registry.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. from collections.abc import Sequence
  2. from typing import Any
  3. import pytest
  4. import matplotlib as mpl
  5. from matplotlib.backends import BackendFilter, backend_registry
  6. @pytest.fixture
  7. def clear_backend_registry():
  8. # Fixture that clears the singleton backend_registry before and after use
  9. # so that the test state remains isolated.
  10. backend_registry._clear()
  11. yield
  12. backend_registry._clear()
  13. def has_duplicates(seq: Sequence[Any]) -> bool:
  14. return len(seq) > len(set(seq))
  15. @pytest.mark.parametrize(
  16. 'framework,expected',
  17. [
  18. ('qt', 'qtagg'),
  19. ('gtk3', 'gtk3agg'),
  20. ('gtk4', 'gtk4agg'),
  21. ('wx', 'wxagg'),
  22. ('tk', 'tkagg'),
  23. ('macosx', 'macosx'),
  24. ('headless', 'agg'),
  25. ('does not exist', None),
  26. ]
  27. )
  28. def test_backend_for_gui_framework(framework, expected):
  29. assert backend_registry.backend_for_gui_framework(framework) == expected
  30. def test_list_builtin():
  31. backends = backend_registry.list_builtin()
  32. assert not has_duplicates(backends)
  33. # Compare using sets as order is not important
  34. assert {*backends} == {
  35. 'gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook',
  36. 'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg',
  37. 'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo', 'agg', 'cairo', 'pdf', 'pgf',
  38. 'ps', 'svg', 'template',
  39. }
  40. @pytest.mark.parametrize(
  41. 'filter,expected',
  42. [
  43. (BackendFilter.INTERACTIVE,
  44. ['gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook',
  45. 'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg',
  46. 'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo']),
  47. (BackendFilter.NON_INTERACTIVE,
  48. ['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']),
  49. ]
  50. )
  51. def test_list_builtin_with_filter(filter, expected):
  52. backends = backend_registry.list_builtin(filter)
  53. assert not has_duplicates(backends)
  54. # Compare using sets as order is not important
  55. assert {*backends} == {*expected}
  56. def test_list_gui_frameworks():
  57. frameworks = backend_registry.list_gui_frameworks()
  58. assert not has_duplicates(frameworks)
  59. # Compare using sets as order is not important
  60. assert {*frameworks} == {
  61. "gtk3", "gtk4", "macosx", "qt", "qt5", "qt6", "tk", "wx",
  62. }
  63. @pytest.mark.parametrize("backend, is_valid", [
  64. ("agg", True),
  65. ("QtAgg", True),
  66. ("module://anything", True),
  67. ("made-up-name", False),
  68. ])
  69. def test_is_valid_backend(backend, is_valid):
  70. assert backend_registry.is_valid_backend(backend) == is_valid
  71. @pytest.mark.parametrize("backend, normalized", [
  72. ("agg", "matplotlib.backends.backend_agg"),
  73. ("QtAgg", "matplotlib.backends.backend_qtagg"),
  74. ("module://Anything", "Anything"),
  75. ])
  76. def test_backend_normalization(backend, normalized):
  77. assert backend_registry._backend_module_name(backend) == normalized
  78. def test_deprecated_rcsetup_attributes():
  79. match = "was deprecated in Matplotlib 3.9"
  80. with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
  81. mpl.rcsetup.interactive_bk
  82. with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
  83. mpl.rcsetup.non_interactive_bk
  84. with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
  85. mpl.rcsetup.all_backends
  86. def test_entry_points_inline():
  87. pytest.importorskip('matplotlib_inline')
  88. backends = backend_registry.list_all()
  89. assert 'inline' in backends
  90. def test_entry_points_ipympl():
  91. pytest.importorskip('ipympl')
  92. backends = backend_registry.list_all()
  93. assert 'ipympl' in backends
  94. assert 'widget' in backends
  95. def test_entry_point_name_shadows_builtin(clear_backend_registry):
  96. with pytest.raises(RuntimeError):
  97. backend_registry._validate_and_store_entry_points(
  98. [('qtagg', 'module1')])
  99. def test_entry_point_name_duplicate(clear_backend_registry):
  100. with pytest.raises(RuntimeError):
  101. backend_registry._validate_and_store_entry_points(
  102. [('some_name', 'module1'), ('some_name', 'module2')])
  103. def test_entry_point_identical(clear_backend_registry):
  104. # Issue https://github.com/matplotlib/matplotlib/issues/28367
  105. # Multiple entry points with the same name and value (value is the module)
  106. # are acceptable.
  107. n = len(backend_registry._name_to_module)
  108. backend_registry._validate_and_store_entry_points(
  109. [('some_name', 'some.module'), ('some_name', 'some.module')])
  110. assert len(backend_registry._name_to_module) == n+1
  111. assert backend_registry._name_to_module['some_name'] == 'module://some.module'
  112. def test_entry_point_name_is_module(clear_backend_registry):
  113. with pytest.raises(RuntimeError):
  114. backend_registry._validate_and_store_entry_points(
  115. [('module://backend.something', 'module1')])
  116. @pytest.mark.parametrize('backend', [
  117. 'agg',
  118. 'module://matplotlib.backends.backend_agg',
  119. ])
  120. def test_load_entry_points_only_if_needed(clear_backend_registry, backend):
  121. assert not backend_registry._loaded_entry_points
  122. check = backend_registry.resolve_backend(backend)
  123. assert check == (backend, None)
  124. assert not backend_registry._loaded_entry_points
  125. backend_registry.list_all() # Force load of entry points
  126. assert backend_registry._loaded_entry_points
  127. @pytest.mark.parametrize(
  128. 'gui_or_backend, expected_backend, expected_gui',
  129. [
  130. ('agg', 'agg', None),
  131. ('qt', 'qtagg', 'qt'),
  132. ('TkCairo', 'tkcairo', 'tk'),
  133. ]
  134. )
  135. def test_resolve_gui_or_backend(gui_or_backend, expected_backend, expected_gui):
  136. backend, gui = backend_registry.resolve_gui_or_backend(gui_or_backend)
  137. assert backend == expected_backend
  138. assert gui == expected_gui
  139. def test_resolve_gui_or_backend_invalid():
  140. match = "is not a recognised GUI loop or backend name"
  141. with pytest.raises(RuntimeError, match=match):
  142. backend_registry.resolve_gui_or_backend('no-such-name')