181 lines
5.8 KiB
Python
181 lines
5.8 KiB
Python
![]() |
from collections.abc import Sequence
|
||
|
from typing import Any
|
||
|
|
||
|
import pytest
|
||
|
|
||
|
import matplotlib as mpl
|
||
|
from matplotlib.backends import BackendFilter, backend_registry
|
||
|
|
||
|
|
||
|
@pytest.fixture
|
||
|
def clear_backend_registry():
|
||
|
# Fixture that clears the singleton backend_registry before and after use
|
||
|
# so that the test state remains isolated.
|
||
|
backend_registry._clear()
|
||
|
yield
|
||
|
backend_registry._clear()
|
||
|
|
||
|
|
||
|
def has_duplicates(seq: Sequence[Any]) -> bool:
|
||
|
return len(seq) > len(set(seq))
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'framework,expected',
|
||
|
[
|
||
|
('qt', 'qtagg'),
|
||
|
('gtk3', 'gtk3agg'),
|
||
|
('gtk4', 'gtk4agg'),
|
||
|
('wx', 'wxagg'),
|
||
|
('tk', 'tkagg'),
|
||
|
('macosx', 'macosx'),
|
||
|
('headless', 'agg'),
|
||
|
('does not exist', None),
|
||
|
]
|
||
|
)
|
||
|
def test_backend_for_gui_framework(framework, expected):
|
||
|
assert backend_registry.backend_for_gui_framework(framework) == expected
|
||
|
|
||
|
|
||
|
def test_list_builtin():
|
||
|
backends = backend_registry.list_builtin()
|
||
|
assert not has_duplicates(backends)
|
||
|
# Compare using sets as order is not important
|
||
|
assert {*backends} == {
|
||
|
'gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook',
|
||
|
'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg',
|
||
|
'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo', 'agg', 'cairo', 'pdf', 'pgf',
|
||
|
'ps', 'svg', 'template',
|
||
|
}
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'filter,expected',
|
||
|
[
|
||
|
(BackendFilter.INTERACTIVE,
|
||
|
['gtk3agg', 'gtk3cairo', 'gtk4agg', 'gtk4cairo', 'macosx', 'nbagg', 'notebook',
|
||
|
'qtagg', 'qtcairo', 'qt5agg', 'qt5cairo', 'tkagg',
|
||
|
'tkcairo', 'webagg', 'wx', 'wxagg', 'wxcairo']),
|
||
|
(BackendFilter.NON_INTERACTIVE,
|
||
|
['agg', 'cairo', 'pdf', 'pgf', 'ps', 'svg', 'template']),
|
||
|
]
|
||
|
)
|
||
|
def test_list_builtin_with_filter(filter, expected):
|
||
|
backends = backend_registry.list_builtin(filter)
|
||
|
assert not has_duplicates(backends)
|
||
|
# Compare using sets as order is not important
|
||
|
assert {*backends} == {*expected}
|
||
|
|
||
|
|
||
|
def test_list_gui_frameworks():
|
||
|
frameworks = backend_registry.list_gui_frameworks()
|
||
|
assert not has_duplicates(frameworks)
|
||
|
# Compare using sets as order is not important
|
||
|
assert {*frameworks} == {
|
||
|
"gtk3", "gtk4", "macosx", "qt", "qt5", "qt6", "tk", "wx",
|
||
|
}
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("backend, is_valid", [
|
||
|
("agg", True),
|
||
|
("QtAgg", True),
|
||
|
("module://anything", True),
|
||
|
("made-up-name", False),
|
||
|
])
|
||
|
def test_is_valid_backend(backend, is_valid):
|
||
|
assert backend_registry.is_valid_backend(backend) == is_valid
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("backend, normalized", [
|
||
|
("agg", "matplotlib.backends.backend_agg"),
|
||
|
("QtAgg", "matplotlib.backends.backend_qtagg"),
|
||
|
("module://Anything", "Anything"),
|
||
|
])
|
||
|
def test_backend_normalization(backend, normalized):
|
||
|
assert backend_registry._backend_module_name(backend) == normalized
|
||
|
|
||
|
|
||
|
def test_deprecated_rcsetup_attributes():
|
||
|
match = "was deprecated in Matplotlib 3.9"
|
||
|
with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
|
||
|
mpl.rcsetup.interactive_bk
|
||
|
with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
|
||
|
mpl.rcsetup.non_interactive_bk
|
||
|
with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):
|
||
|
mpl.rcsetup.all_backends
|
||
|
|
||
|
|
||
|
def test_entry_points_inline():
|
||
|
pytest.importorskip('matplotlib_inline')
|
||
|
backends = backend_registry.list_all()
|
||
|
assert 'inline' in backends
|
||
|
|
||
|
|
||
|
def test_entry_points_ipympl():
|
||
|
pytest.importorskip('ipympl')
|
||
|
backends = backend_registry.list_all()
|
||
|
assert 'ipympl' in backends
|
||
|
assert 'widget' in backends
|
||
|
|
||
|
|
||
|
def test_entry_point_name_shadows_builtin(clear_backend_registry):
|
||
|
with pytest.raises(RuntimeError):
|
||
|
backend_registry._validate_and_store_entry_points(
|
||
|
[('qtagg', 'module1')])
|
||
|
|
||
|
|
||
|
def test_entry_point_name_duplicate(clear_backend_registry):
|
||
|
with pytest.raises(RuntimeError):
|
||
|
backend_registry._validate_and_store_entry_points(
|
||
|
[('some_name', 'module1'), ('some_name', 'module2')])
|
||
|
|
||
|
|
||
|
def test_entry_point_identical(clear_backend_registry):
|
||
|
# Issue https://github.com/matplotlib/matplotlib/issues/28367
|
||
|
# Multiple entry points with the same name and value (value is the module)
|
||
|
# are acceptable.
|
||
|
n = len(backend_registry._name_to_module)
|
||
|
backend_registry._validate_and_store_entry_points(
|
||
|
[('some_name', 'some.module'), ('some_name', 'some.module')])
|
||
|
assert len(backend_registry._name_to_module) == n+1
|
||
|
assert backend_registry._name_to_module['some_name'] == 'module://some.module'
|
||
|
|
||
|
|
||
|
def test_entry_point_name_is_module(clear_backend_registry):
|
||
|
with pytest.raises(RuntimeError):
|
||
|
backend_registry._validate_and_store_entry_points(
|
||
|
[('module://backend.something', 'module1')])
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize('backend', [
|
||
|
'agg',
|
||
|
'module://matplotlib.backends.backend_agg',
|
||
|
])
|
||
|
def test_load_entry_points_only_if_needed(clear_backend_registry, backend):
|
||
|
assert not backend_registry._loaded_entry_points
|
||
|
check = backend_registry.resolve_backend(backend)
|
||
|
assert check == (backend, None)
|
||
|
assert not backend_registry._loaded_entry_points
|
||
|
backend_registry.list_all() # Force load of entry points
|
||
|
assert backend_registry._loaded_entry_points
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
'gui_or_backend, expected_backend, expected_gui',
|
||
|
[
|
||
|
('agg', 'agg', None),
|
||
|
('qt', 'qtagg', 'qt'),
|
||
|
('TkCairo', 'tkcairo', 'tk'),
|
||
|
]
|
||
|
)
|
||
|
def test_resolve_gui_or_backend(gui_or_backend, expected_backend, expected_gui):
|
||
|
backend, gui = backend_registry.resolve_gui_or_backend(gui_or_backend)
|
||
|
assert backend == expected_backend
|
||
|
assert gui == expected_gui
|
||
|
|
||
|
|
||
|
def test_resolve_gui_or_backend_invalid():
|
||
|
match = "is not a recognised GUI loop or backend name"
|
||
|
with pytest.raises(RuntimeError, match=match):
|
||
|
backend_registry.resolve_gui_or_backend('no-such-name')
|