Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ff0391d

Browse files
authored
Merge pull request #18261 from richardsheridan/backend_tk_tests_to_subprocess
TST: Migrate tk backend tests into subprocesses
2 parents d592ebb + 513034c commit ff0391d

2 files changed

Lines changed: 169 additions & 77 deletions

File tree

Lines changed: 169 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,108 @@
11
import os
22
import subprocess
33
import sys
4-
import tkinter
54

6-
import numpy as np
75
import pytest
86

9-
from matplotlib import pyplot as plt
7+
_test_timeout = 10 # Empirically, 1s is not enough on Travis.
8+
9+
# NOTE: TkAgg tests seem to have interactions between tests,
10+
# So isolate each test in a subprocess. See GH#18261
1011

1112

1213
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
1314
def test_blit():
14-
from matplotlib.backends import _tkagg
15-
def evil_blit(photoimage, aggimage, offsets, bboxptr):
16-
data = np.asarray(aggimage)
17-
height, width = data.shape[:2]
18-
dataptr = (height, width, data.ctypes.data)
19-
_tkagg.blit(
20-
photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets,
21-
bboxptr)
22-
23-
fig, ax = plt.subplots()
24-
for bad_boxes in ((-1, 2, 0, 2),
25-
(2, 0, 0, 2),
26-
(1, 6, 0, 2),
27-
(0, 2, -1, 2),
28-
(0, 2, 2, 0),
29-
(0, 2, 1, 6)):
30-
with pytest.raises(ValueError):
31-
evil_blit(fig.canvas._tkphoto,
32-
np.ones((4, 4, 4)),
33-
(0, 1, 2, 3),
34-
bad_boxes)
15+
script = """
16+
import matplotlib.pyplot as plt
17+
import numpy as np
18+
from matplotlib.backends import _tkagg
19+
def evil_blit(photoimage, aggimage, offsets, bboxptr):
20+
data = np.asarray(aggimage)
21+
height, width = data.shape[:2]
22+
dataptr = (height, width, data.ctypes.data)
23+
_tkagg.blit(
24+
photoimage.tk.interpaddr(), str(photoimage), dataptr, offsets,
25+
bboxptr)
26+
27+
fig, ax = plt.subplots()
28+
bad_boxes = ((-1, 2, 0, 2),
29+
(2, 0, 0, 2),
30+
(1, 6, 0, 2),
31+
(0, 2, -1, 2),
32+
(0, 2, 2, 0),
33+
(0, 2, 1, 6))
34+
for bad_box in bad_boxes:
35+
try:
36+
evil_blit(fig.canvas._tkphoto,
37+
np.ones((4, 4, 4)),
38+
(0, 1, 2, 3),
39+
bad_box)
40+
except ValueError:
41+
print("success")
42+
"""
43+
try:
44+
proc = subprocess.run(
45+
[sys.executable, "-c", script],
46+
env={**os.environ,
47+
"MPLBACKEND": "TkAgg",
48+
"SOURCE_DATE_EPOCH": "0"},
49+
timeout=_test_timeout,
50+
stdout=subprocess.PIPE,
51+
check=True,
52+
universal_newlines=True,
53+
)
54+
except subprocess.TimeoutExpired:
55+
pytest.fail("Subprocess timed out")
56+
except subprocess.CalledProcessError:
57+
pytest.fail("Likely regression on out-of-bounds data access"
58+
" in _tkagg.cpp")
59+
else:
60+
print(proc.stdout)
61+
assert proc.stdout.count("success") == 6 # len(bad_boxes)
3562

3663

3764
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
3865
def test_figuremanager_preserves_host_mainloop():
39-
success = False
66+
script = """
67+
import tkinter
68+
import matplotlib.pyplot as plt
69+
success = False
4070
41-
def do_plot():
42-
plt.figure()
43-
plt.plot([1, 2], [3, 5])
44-
plt.close()
45-
root.after(0, legitimate_quit)
71+
def do_plot():
72+
plt.figure()
73+
plt.plot([1, 2], [3, 5])
74+
plt.close()
75+
root.after(0, legitimate_quit)
4676
47-
def legitimate_quit():
48-
root.quit()
49-
nonlocal success
50-
success = True
77+
def legitimate_quit():
78+
root.quit()
79+
global success
80+
success = True
5181
52-
root = tkinter.Tk()
53-
root.after(0, do_plot)
54-
root.mainloop()
82+
root = tkinter.Tk()
83+
root.after(0, do_plot)
84+
root.mainloop()
5585
56-
assert success
86+
if success:
87+
print("success")
88+
"""
89+
try:
90+
proc = subprocess.run(
91+
[sys.executable, "-c", script],
92+
env={**os.environ,
93+
"MPLBACKEND": "TkAgg",
94+
"SOURCE_DATE_EPOCH": "0"},
95+
timeout=_test_timeout,
96+
stdout=subprocess.PIPE,
97+
check=True,
98+
universal_newlines=True,
99+
)
100+
except subprocess.TimeoutExpired:
101+
pytest.fail("Subprocess timed out")
102+
except subprocess.CalledProcessError:
103+
pytest.fail("Subprocess failed to test intended behavior")
104+
else:
105+
assert proc.stdout.count("success") == 1
57106

58107

59108
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
@@ -90,7 +139,7 @@ def target():
90139
env={**os.environ,
91140
"MPLBACKEND": "TkAgg",
92141
"SOURCE_DATE_EPOCH": "0"},
93-
timeout=10,
142+
timeout=_test_timeout,
94143
stdout=subprocess.PIPE,
95144
universal_newlines=True,
96145
check=True
@@ -102,14 +151,86 @@ def target():
102151
assert proc.stdout.count("success") == 1
103152

104153

154+
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
155+
@pytest.mark.flaky(reruns=3)
156+
def test_never_update():
157+
script = """
158+
import tkinter
159+
del tkinter.Misc.update
160+
del tkinter.Misc.update_idletasks
161+
162+
import matplotlib.pyplot as plt
163+
fig = plt.figure()
164+
plt.show(block=False)
165+
166+
# regression test on FigureCanvasTkAgg
167+
plt.draw()
168+
# regression test on NavigationToolbar2Tk
169+
fig.canvas.toolbar.configure_subplots()
170+
171+
# check for update() or update_idletasks() in the event queue
172+
# functionally equivalent to tkinter.Misc.update
173+
# must pause >= 1 ms to process tcl idle events plus
174+
# extra time to avoid flaky tests on slow systems
175+
plt.pause(0.1)
176+
177+
# regression test on FigureCanvasTk filter_destroy callback
178+
plt.close(fig)
179+
"""
180+
try:
181+
proc = subprocess.run(
182+
[sys.executable, "-c", script],
183+
env={**os.environ,
184+
"MPLBACKEND": "TkAgg",
185+
"SOURCE_DATE_EPOCH": "0"},
186+
timeout=_test_timeout,
187+
capture_output=True,
188+
universal_newlines=True,
189+
)
190+
except subprocess.TimeoutExpired:
191+
pytest.fail("Subprocess timed out")
192+
else:
193+
# test framework doesn't see tkinter callback exceptions normally
194+
# see tkinter.Misc.report_callback_exception
195+
assert "Exception in Tkinter callback" not in proc.stderr
196+
# make sure we can see other issues
197+
print(proc.stderr, file=sys.stderr)
198+
# Checking return code late so the Tkinter assertion happens first
199+
if proc.returncode:
200+
pytest.fail("Subprocess failed to test intended behavior")
201+
202+
105203
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
106204
def test_missing_back_button():
107-
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
108-
class Toolbar(NavigationToolbar2Tk):
109-
# only display the buttons we need
110-
toolitems = [t for t in NavigationToolbar2Tk.toolitems if
111-
t[0] in ('Home', 'Pan', 'Zoom')]
112-
113-
fig = plt.figure()
114-
# this should not raise
115-
Toolbar(fig.canvas, fig.canvas.manager.window)
205+
script = """
206+
import matplotlib.pyplot as plt
207+
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
208+
class Toolbar(NavigationToolbar2Tk):
209+
# only display the buttons we need
210+
toolitems = [t for t in NavigationToolbar2Tk.toolitems if
211+
t[0] in ('Home', 'Pan', 'Zoom')]
212+
213+
fig = plt.figure()
214+
print("setup complete")
215+
# this should not raise
216+
Toolbar(fig.canvas, fig.canvas.manager.window)
217+
print("success")
218+
"""
219+
try:
220+
proc = subprocess.run(
221+
[sys.executable, "-c", script],
222+
env={**os.environ,
223+
"MPLBACKEND": "TkAgg",
224+
"SOURCE_DATE_EPOCH": "0"},
225+
timeout=_test_timeout,
226+
stdout=subprocess.PIPE,
227+
universal_newlines=True,
228+
)
229+
except subprocess.TimeoutExpired:
230+
pytest.fail("Subprocess timed out")
231+
else:
232+
assert proc.stdout.count("setup complete") == 1
233+
assert proc.stdout.count("success") == 1
234+
# Checking return code late so the stdout assertions happen first
235+
if proc.returncode:
236+
pytest.fail("Subprocess failed to test intended behavior")

lib/matplotlib/tests/test_backends_interactive.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -201,35 +201,6 @@ def test_webagg():
201201
assert proc.wait(timeout=_test_timeout) == 0
202202

203203

204-
@pytest.mark.backend('TkAgg', skip_on_importerror=True)
205-
def test_never_update(monkeypatch, capsys):
206-
import tkinter
207-
monkeypatch.delattr(tkinter.Misc, 'update')
208-
monkeypatch.delattr(tkinter.Misc, 'update_idletasks')
209-
210-
import matplotlib.pyplot as plt
211-
fig = plt.figure()
212-
plt.show(block=False)
213-
214-
# regression test on FigureCanvasTkAgg
215-
plt.draw()
216-
# regression test on NavigationToolbar2Tk
217-
fig.canvas.toolbar.configure_subplots()
218-
219-
# check for update() or update_idletasks() in the event queue
220-
# functionally equivalent to tkinter.Misc.update
221-
# must pause >= 1 ms to process tcl idle events plus
222-
# extra time to avoid flaky tests on slow systems
223-
plt.pause(0.1)
224-
225-
# regression test on FigureCanvasTk filter_destroy callback
226-
plt.close(fig)
227-
228-
# test framework doesn't see tkinter callback exceptions normally
229-
# see tkinter.Misc.report_callback_exception
230-
assert "Exception in Tkinter callback" not in capsys.readouterr().err
231-
232-
233204
@pytest.mark.skipif(sys.platform != "linux", reason="this a linux-only test")
234205
@pytest.mark.backend('Qt5Agg', skip_on_importerror=True)
235206
def test_lazy_linux_headless():

0 commit comments

Comments
 (0)