diff --git a/lib/matplotlib/tests/test_backend_tk.py b/lib/matplotlib/tests/test_backend_tk.py index 70eed6d75a81..126813e7cb87 100644 --- a/lib/matplotlib/tests/test_backend_tk.py +++ b/lib/matplotlib/tests/test_backend_tk.py @@ -1,59 +1,108 @@ import os import subprocess import sys -import tkinter -import numpy as np import pytest -from matplotlib import pyplot as plt +_test_timeout = 10 # Empirically, 1s is not enough on Travis. + +# NOTE: TkAgg tests seem to have interactions between tests, +# So isolate each test in a subprocess. See GH#18261 @pytest.mark.backend('TkAgg', skip_on_importerror=True) def test_blit(): - from matplotlib.backends import _tkagg - def evil_blit(photoimage, aggimage, offsets, bboxptr): - data = np.asarray(aggimage) - height, width = data.shape[:2] - dataptr = (height, width, data.ctypes.data) - _tkagg.blit( - photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets, - bboxptr) - - fig, ax = plt.subplots() - for bad_boxes in ((-1, 2, 0, 2), - (2, 0, 0, 2), - (1, 6, 0, 2), - (0, 2, -1, 2), - (0, 2, 2, 0), - (0, 2, 1, 6)): - with pytest.raises(ValueError): - evil_blit(fig.canvas._tkphoto, - np.ones((4, 4, 4)), - (0, 1, 2, 3), - bad_boxes) + script = """ +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.backends import _tkagg +def evil_blit(photoimage, aggimage, offsets, bboxptr): + data = np.asarray(aggimage) + height, width = data.shape[:2] + dataptr = (height, width, data.ctypes.data) + _tkagg.blit( + photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets, + bboxptr) + +fig, ax = plt.subplots() +bad_boxes = ((-1, 2, 0, 2), + (2, 0, 0, 2), + (1, 6, 0, 2), + (0, 2, -1, 2), + (0, 2, 2, 0), + (0, 2, 1, 6)) +for bad_box in bad_boxes: + try: + evil_blit(fig.canvas._tkphoto, + np.ones((4, 4, 4)), + (0, 1, 2, 3), + bad_box) + except ValueError: + print("success") +""" + try: + proc = subprocess.run( + [sys.executable, "-c", script], + env={**os.environ, + "MPLBACKEND": "TkAgg", + "SOURCE_DATE_EPOCH": "0"}, + timeout=_test_timeout, + stdout=subprocess.PIPE, + check=True, + universal_newlines=True, + ) + except subprocess.TimeoutExpired: + pytest.fail("Subprocess timed out") + except subprocess.CalledProcessError: + pytest.fail("Likely regression on out-of-bounds data access" + " in _tkagg.cpp") + else: + print(proc.stdout) + assert proc.stdout.count("success") == 6 # len(bad_boxes) @pytest.mark.backend('TkAgg', skip_on_importerror=True) def test_figuremanager_preserves_host_mainloop(): - success = False + script = """ +import tkinter +import matplotlib.pyplot as plt +success = False - def do_plot(): - plt.figure() - plt.plot([1, 2], [3, 5]) - plt.close() - root.after(0, legitimate_quit) +def do_plot(): + plt.figure() + plt.plot([1, 2], [3, 5]) + plt.close() + root.after(0, legitimate_quit) - def legitimate_quit(): - root.quit() - nonlocal success - success = True +def legitimate_quit(): + root.quit() + global success + success = True - root = tkinter.Tk() - root.after(0, do_plot) - root.mainloop() +root = tkinter.Tk() +root.after(0, do_plot) +root.mainloop() - assert success +if success: + print("success") +""" + try: + proc = subprocess.run( + [sys.executable, "-c", script], + env={**os.environ, + "MPLBACKEND": "TkAgg", + "SOURCE_DATE_EPOCH": "0"}, + timeout=_test_timeout, + stdout=subprocess.PIPE, + check=True, + universal_newlines=True, + ) + except subprocess.TimeoutExpired: + pytest.fail("Subprocess timed out") + except subprocess.CalledProcessError: + pytest.fail("Subprocess failed to test intended behavior") + else: + assert proc.stdout.count("success") == 1 @pytest.mark.backend('TkAgg', skip_on_importerror=True) @@ -90,7 +139,7 @@ def target(): env={**os.environ, "MPLBACKEND": "TkAgg", "SOURCE_DATE_EPOCH": "0"}, - timeout=10, + timeout=_test_timeout, stdout=subprocess.PIPE, universal_newlines=True, check=True @@ -102,14 +151,86 @@ def target(): assert proc.stdout.count("success") == 1 +@pytest.mark.backend('TkAgg', skip_on_importerror=True) +@pytest.mark.flaky(reruns=3) +def test_never_update(): + script = """ +import tkinter +del tkinter.Misc.update +del tkinter.Misc.update_idletasks + +import matplotlib.pyplot as plt +fig = plt.figure() +plt.show(block=False) + +# regression test on FigureCanvasTkAgg +plt.draw() +# regression test on NavigationToolbar2Tk +fig.canvas.toolbar.configure_subplots() + +# check for update() or update_idletasks() in the event queue +# functionally equivalent to tkinter.Misc.update +# must pause >= 1 ms to process tcl idle events plus +# extra time to avoid flaky tests on slow systems +plt.pause(0.1) + +# regression test on FigureCanvasTk filter_destroy callback +plt.close(fig) +""" + try: + proc = subprocess.run( + [sys.executable, "-c", script], + env={**os.environ, + "MPLBACKEND": "TkAgg", + "SOURCE_DATE_EPOCH": "0"}, + timeout=_test_timeout, + capture_output=True, + universal_newlines=True, + ) + except subprocess.TimeoutExpired: + pytest.fail("Subprocess timed out") + else: + # test framework doesn't see tkinter callback exceptions normally + # see tkinter.Misc.report_callback_exception + assert "Exception in Tkinter callback" not in proc.stderr + # make sure we can see other issues + print(proc.stderr, file=sys.stderr) + # Checking return code late so the Tkinter assertion happens first + if proc.returncode: + pytest.fail("Subprocess failed to test intended behavior") + + @pytest.mark.backend('TkAgg', skip_on_importerror=True) def test_missing_back_button(): - from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk - class Toolbar(NavigationToolbar2Tk): - # only display the buttons we need - toolitems = [t for t in NavigationToolbar2Tk.toolitems if - t[0] in ('Home', 'Pan', 'Zoom')] - - fig = plt.figure() - # this should not raise - Toolbar(fig.canvas, fig.canvas.manager.window) + script = """ +import matplotlib.pyplot as plt +from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk +class Toolbar(NavigationToolbar2Tk): + # only display the buttons we need + toolitems = [t for t in NavigationToolbar2Tk.toolitems if + t[0] in ('Home', 'Pan', 'Zoom')] + +fig = plt.figure() +print("setup complete") +# this should not raise +Toolbar(fig.canvas, fig.canvas.manager.window) +print("success") +""" + try: + proc = subprocess.run( + [sys.executable, "-c", script], + env={**os.environ, + "MPLBACKEND": "TkAgg", + "SOURCE_DATE_EPOCH": "0"}, + timeout=_test_timeout, + stdout=subprocess.PIPE, + universal_newlines=True, + ) + except subprocess.TimeoutExpired: + pytest.fail("Subprocess timed out") + else: + assert proc.stdout.count("setup complete") == 1 + assert proc.stdout.count("success") == 1 + # Checking return code late so the stdout assertions happen first + if proc.returncode: + pytest.fail("Subprocess failed to test intended behavior") diff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py index 4494efa35437..000ff971bc97 100644 --- a/lib/matplotlib/tests/test_backends_interactive.py +++ b/lib/matplotlib/tests/test_backends_interactive.py @@ -201,35 +201,6 @@ def test_webagg(): assert proc.wait(timeout=_test_timeout) == 0 -@pytest.mark.backend('TkAgg', skip_on_importerror=True) -def test_never_update(monkeypatch, capsys): - import tkinter - monkeypatch.delattr(tkinter.Misc, 'update') - monkeypatch.delattr(tkinter.Misc, 'update_idletasks') - - import matplotlib.pyplot as plt - fig = plt.figure() - plt.show(block=False) - - # regression test on FigureCanvasTkAgg - plt.draw() - # regression test on NavigationToolbar2Tk - fig.canvas.toolbar.configure_subplots() - - # check for update() or update_idletasks() in the event queue - # functionally equivalent to tkinter.Misc.update - # must pause >= 1 ms to process tcl idle events plus - # extra time to avoid flaky tests on slow systems - plt.pause(0.1) - - # regression test on FigureCanvasTk filter_destroy callback - plt.close(fig) - - # test framework doesn't see tkinter callback exceptions normally - # see tkinter.Misc.report_callback_exception - assert "Exception in Tkinter callback" not in capsys.readouterr().err - - @pytest.mark.skipif(sys.platform != "linux", reason="this a linux-only test") @pytest.mark.backend('Qt5Agg', skip_on_importerror=True) def test_lazy_linux_headless():
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: