123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import importlib
- import importlib.util
- import os
- import signal
- import subprocess
- import sys
- import time
- import urllib.request
- import pytest
- import matplotlib as mpl
- # Minimal smoke-testing of the backends for which the dependencies are
- # PyPI-installable on Travis. They are not available for all tested Python
- # versions so we don't fail on missing backends.
- def _get_testable_interactive_backends():
- backends = []
- for deps, backend in [
- (["cairo", "gi"], "gtk3agg"),
- (["cairo", "gi"], "gtk3cairo"),
- (["PyQt5"], "qt5agg"),
- (["PyQt5", "cairocffi"], "qt5cairo"),
- (["tkinter"], "tkagg"),
- (["wx"], "wx"),
- (["wx"], "wxagg"),
- ]:
- reason = None
- if not os.environ.get("DISPLAY"):
- reason = "No $DISPLAY"
- elif any(importlib.util.find_spec(dep) is None for dep in deps):
- reason = "Missing dependency"
- if reason:
- backend = pytest.param(
- backend, marks=pytest.mark.skip(reason=reason))
- backends.append(backend)
- return backends
- # Using a timer not only allows testing of timers (on other backends), but is
- # also necessary on gtk3 and wx, where a direct call to key_press_event("q")
- # from draw_event causes breakage due to the canvas widget being deleted too
- # early. Also, gtk3 redefines key_press_event with a different signature, so
- # we directly invoke it from the superclass instead.
- _test_script = """\
- import importlib
- import importlib.util
- import sys
- from unittest import TestCase
- import matplotlib as mpl
- from matplotlib import pyplot as plt, rcParams
- from matplotlib.backend_bases import FigureCanvasBase
- rcParams.update({
- "webagg.open_in_browser": False,
- "webagg.port_retries": 1,
- })
- backend = plt.rcParams["backend"].lower()
- assert_equal = TestCase().assertEqual
- assert_raises = TestCase().assertRaises
- if backend.endswith("agg") and not backend.startswith(("gtk3", "web")):
- # Force interactive framework setup.
- plt.figure()
- # Check that we cannot switch to a backend using another interactive
- # framework, but can switch to a backend using cairo instead of agg, or a
- # non-interactive backend. In the first case, we use tkagg as the "other"
- # interactive backend as it is (essentially) guaranteed to be present.
- # Moreover, don't test switching away from gtk3 (as Gtk.main_level() is
- # not set up at this point yet) and webagg (which uses no interactive
- # framework).
- if backend != "tkagg":
- with assert_raises(ImportError):
- mpl.use("tkagg", force=True)
- def check_alt_backend(alt_backend):
- mpl.use(alt_backend, force=True)
- fig = plt.figure()
- assert_equal(
- type(fig.canvas).__module__,
- "matplotlib.backends.backend_{}".format(alt_backend))
- if importlib.util.find_spec("cairocffi"):
- check_alt_backend(backend[:-3] + "cairo")
- check_alt_backend("svg")
- mpl.use(backend, force=True)
- fig, ax = plt.subplots()
- assert_equal(
- type(fig.canvas).__module__,
- "matplotlib.backends.backend_{}".format(backend))
- ax.plot([0, 1], [2, 3])
- timer = fig.canvas.new_timer(1)
- timer.add_callback(FigureCanvasBase.key_press_event, fig.canvas, "q")
- # Trigger quitting upon draw.
- fig.canvas.mpl_connect("draw_event", lambda event: timer.start())
- plt.show()
- """
- _test_timeout = 10 # Empirically, 1s is not enough on Travis.
- @pytest.mark.parametrize("backend", _get_testable_interactive_backends())
- @pytest.mark.flaky(reruns=3)
- def test_interactive_backend(backend):
- proc = subprocess.run([sys.executable, "-c", _test_script],
- env={**os.environ, "MPLBACKEND": backend},
- timeout=_test_timeout)
- if proc.returncode:
- pytest.fail("The subprocess returned with non-zero exit status "
- f"{proc.returncode}.")
- @pytest.mark.skipif('SYSTEM_TEAMFOUNDATIONCOLLECTIONURI' in os.environ,
- reason="this test fails an azure for unknown reasons")
- @pytest.mark.skipif(os.name == "nt", reason="Cannot send SIGINT on Windows.")
- def test_webagg():
- pytest.importorskip("tornado")
- proc = subprocess.Popen([sys.executable, "-c", _test_script],
- env={**os.environ, "MPLBACKEND": "webagg"})
- url = "http://{}:{}".format(
- mpl.rcParams["webagg.address"], mpl.rcParams["webagg.port"])
- timeout = time.perf_counter() + _test_timeout
- while True:
- try:
- retcode = proc.poll()
- # check that the subprocess for the server is not dead
- assert retcode is None
- conn = urllib.request.urlopen(url)
- break
- except urllib.error.URLError:
- if time.perf_counter() > timeout:
- pytest.fail("Failed to connect to the webagg server.")
- else:
- continue
- conn.close()
- proc.send_signal(signal.SIGINT)
- assert proc.wait(timeout=_test_timeout) == 0
|