11import os
22import subprocess
33import sys
4- import tkinter
54
6- import numpy as np
75import 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 )
1314def 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 )
3865def 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 )
106204def 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" )
0 commit comments