Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit7bae7dc

Browse files
authored
Merge pull request#20907 from QuLogic/qt-interrupt-popen
Move sigint tests into subprocesses
2 parents36c0efa +49e1cce commit7bae7dc

File tree

3 files changed

+160
-63
lines changed

3 files changed

+160
-63
lines changed

‎.appveyor.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ install:
6060
# pull pywin32 from conda because on py38 there is something wrong with finding
6161
# the dlls when insalled from pip
6262
-conda install -c conda-forge pywin32
63+
# install pyqt from conda-forge
64+
-conda install -c conda-forge pyqt
6365
-echo %PYTHON_VERSION% %TARGET_ARCH%
6466
# Install dependencies from PyPI.
6567
-python -m pip install --upgrade -r requirements/testing/all.txt %EXTRAREQS% %PINNEDVERS%

‎lib/matplotlib/backends/qt_compat.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,20 @@ def _maybe_allow_interrupt(qapp):
228228
rsock.fileno(),_enum('QtCore.QSocketNotifier.Type').Read
229229
)
230230

231+
# We do not actually care about this value other than running some
232+
# Python code to ensure that the interpreter has a chance to handle the
233+
# signal in Python land. We also need to drain the socket because it
234+
# will be written to as part of the wakeup! There are some cases where
235+
# this may fire too soon / more than once on Windows so we should be
236+
# forgiving about reading an empty socket.
237+
rsock.setblocking(False)
231238
# Clear the socket to re-arm the notifier.
232-
sn.activated.connect(lambda*args:rsock.recv(1))
239+
@sn.activated.connect
240+
def_may_clear_sock(*args):
241+
try:
242+
rsock.recv(1)
243+
exceptBlockingIOError:
244+
pass
233245

234246
defhandle(*args):
235247
nonlocalhandler_args

‎lib/matplotlib/tests/test_backend_qt.py

Lines changed: 145 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
pytestmark=pytest.mark.skip('No usable Qt bindings')
2525

2626

27+
_test_timeout=60# A reasonably safe value for slower architectures.
28+
29+
2730
@pytest.fixture
2831
defqt_core(request):
2932
backend,=request.node.get_closest_marker('backend').args
@@ -33,19 +36,6 @@ def qt_core(request):
3336
returnQtCore
3437

3538

36-
@pytest.fixture
37-
defplatform_simulate_ctrl_c(request):
38-
importsignal
39-
fromfunctoolsimportpartial
40-
41-
ifhasattr(signal,"CTRL_C_EVENT"):
42-
win32api=pytest.importorskip('win32api')
43-
returnpartial(win32api.GenerateConsoleCtrlEvent,0,0)
44-
else:
45-
# we're not on windows
46-
returnpartial(os.kill,os.getpid(),signal.SIGINT)
47-
48-
4939
@pytest.mark.backend('QtAgg',skip_on_importerror=True)
5040
deftest_fig_close():
5141

@@ -64,50 +54,143 @@ def test_fig_close():
6454
assertinit_figs==Gcf.figs
6555

6656

67-
@pytest.mark.backend('QtAgg',skip_on_importerror=True)
68-
@pytest.mark.parametrize("target, kwargs", [
69-
(plt.show, {"block":True}),
70-
(plt.pause, {"interval":10})
71-
])
72-
deftest_sigint(qt_core,platform_simulate_ctrl_c,target,
73-
kwargs):
74-
plt.figure()
75-
deffire_signal():
76-
platform_simulate_ctrl_c()
57+
classWaitForStringPopen(subprocess.Popen):
58+
"""
59+
A Popen that passes flags that allow triggering KeyboardInterrupt.
60+
"""
7761

78-
qt_core.QTimer.singleShot(100,fire_signal)
79-
withpytest.raises(KeyboardInterrupt):
62+
def__init__(self,*args,**kwargs):
63+
ifsys.platform=='win32':
64+
kwargs['creationflags']=subprocess.CREATE_NEW_CONSOLE
65+
super().__init__(
66+
*args,**kwargs,
67+
# Force Agg so that each test can switch to its desired Qt backend.
68+
env={**os.environ,"MPLBACKEND":"Agg","SOURCE_DATE_EPOCH":"0"},
69+
stdout=subprocess.PIPE,universal_newlines=True)
70+
71+
defwait_for(self,terminator):
72+
"""Read until the terminator is reached."""
73+
buf=''
74+
whileTrue:
75+
c=self.stdout.read(1)
76+
ifnotc:
77+
raiseRuntimeError(
78+
f'Subprocess died before emitting expected{terminator!r}')
79+
buf+=c
80+
ifbuf.endswith(terminator):
81+
return
82+
83+
84+
def_test_sigint_impl(backend,target_name,kwargs):
85+
importsys
86+
importmatplotlib.pyplotasplt
87+
importos
88+
importthreading
89+
90+
plt.switch_backend(backend)
91+
frommatplotlib.backends.qt_compatimportQtCore
92+
93+
definterupter():
94+
ifsys.platform=='win32':
95+
importwin32api
96+
win32api.GenerateConsoleCtrlEvent(0,0)
97+
else:
98+
importsignal
99+
os.kill(os.getpid(),signal.SIGINT)
100+
101+
target=getattr(plt,target_name)
102+
timer=threading.Timer(1,interupter)
103+
fig=plt.figure()
104+
fig.canvas.mpl_connect(
105+
'draw_event',
106+
lambda*args:print('DRAW',flush=True)
107+
)
108+
fig.canvas.mpl_connect(
109+
'draw_event',
110+
lambda*args:timer.start()
111+
)
112+
try:
80113
target(**kwargs)
114+
exceptKeyboardInterrupt:
115+
print('SUCCESS',flush=True)
81116

82117

83118
@pytest.mark.backend('QtAgg',skip_on_importerror=True)
84119
@pytest.mark.parametrize("target, kwargs", [
85-
(plt.show, {"block":True}),
86-
(plt.pause, {"interval":10})
120+
('show', {'block':True}),
121+
('pause', {'interval':10})
87122
])
88-
deftest_other_signal_before_sigint(qt_core,platform_simulate_ctrl_c,
89-
target,kwargs):
90-
plt.figure()
123+
deftest_sigint(target,kwargs):
124+
backend=plt.get_backend()
125+
proc=WaitForStringPopen(
126+
[sys.executable,"-c",
127+
inspect.getsource(_test_sigint_impl)+
128+
f"\n_test_sigint_impl({backend!r},{target!r},{kwargs!r})"])
129+
try:
130+
proc.wait_for('DRAW')
131+
stdout,_=proc.communicate(timeout=_test_timeout)
132+
except:
133+
proc.kill()
134+
stdout,_=proc.communicate()
135+
raise
136+
print(stdout)
137+
assert'SUCCESS'instdout
138+
139+
140+
def_test_other_signal_before_sigint_impl(backend,target_name,kwargs):
141+
importsignal
142+
importsys
143+
importmatplotlib.pyplotasplt
144+
plt.switch_backend(backend)
145+
frommatplotlib.backends.qt_compatimportQtCore
91146

92-
sigcld_caught=False
93-
defcustom_sigpipe_handler(signum,frame):
94-
nonlocalsigcld_caught
95-
sigcld_caught=True
96-
signal.signal(signal.SIGCHLD,custom_sigpipe_handler)
147+
target=getattr(plt,target_name)
97148

98-
deffire_other_signal():
99-
os.kill(os.getpid(),signal.SIGCHLD)
149+
fig=plt.figure()
150+
fig.canvas.mpl_connect('draw_event',
151+
lambda*args:print('DRAW',flush=True))
100152

101-
deffire_sigint():
102-
platform_simulate_ctrl_c()
153+
timer=fig.canvas.new_timer(interval=1)
154+
timer.single_shot=True
155+
timer.add_callback(print,'SIGUSR1',flush=True)
103156

104-
qt_core.QTimer.singleShot(50,fire_other_signal)
105-
qt_core.QTimer.singleShot(100,fire_sigint)
157+
defcustom_signal_handler(signum,frame):
158+
timer.start()
159+
signal.signal(signal.SIGUSR1,custom_signal_handler)
106160

107-
withpytest.raises(KeyboardInterrupt):
161+
try:
108162
target(**kwargs)
163+
exceptKeyboardInterrupt:
164+
print('SUCCESS',flush=True)
109165

110-
assertsigcld_caught
166+
167+
@pytest.mark.skipif(sys.platform=='win32',
168+
reason='No other signal available to send on Windows')
169+
@pytest.mark.backend('QtAgg',skip_on_importerror=True)
170+
@pytest.mark.parametrize("target, kwargs", [
171+
('show', {'block':True}),
172+
('pause', {'interval':10})
173+
])
174+
deftest_other_signal_before_sigint(target,kwargs):
175+
backend=plt.get_backend()
176+
proc=WaitForStringPopen(
177+
[sys.executable,"-c",
178+
inspect.getsource(_test_other_signal_before_sigint_impl)+
179+
"\n_test_other_signal_before_sigint_impl("
180+
f"{backend!r},{target!r},{kwargs!r})"])
181+
try:
182+
proc.wait_for('DRAW')
183+
os.kill(proc.pid,signal.SIGUSR1)
184+
proc.wait_for('SIGUSR1')
185+
os.kill(proc.pid,signal.SIGINT)
186+
stdout,_=proc.communicate(timeout=_test_timeout)
187+
except:
188+
proc.kill()
189+
stdout,_=proc.communicate()
190+
raise
191+
print(stdout)
192+
assert'SUCCESS'instdout
193+
plt.figure()
111194

112195

113196
@pytest.mark.backend('Qt5Agg')
@@ -140,29 +223,31 @@ def custom_handler(signum, frame):
140223

141224
signal.signal(signal.SIGINT,custom_handler)
142225

143-
# mainloop() sets SIGINT, starts Qt event loop (which triggers timer and
144-
# exits) and then mainloop() resets SIGINT
145-
matplotlib.backends.backend_qt._BackendQT.mainloop()
226+
try:
227+
# mainloop() sets SIGINT, starts Qt event loop (which triggers timer
228+
# and exits) and then mainloop() resets SIGINT
229+
matplotlib.backends.backend_qt._BackendQT.mainloop()
146230

147-
# Assert: signal handler during loop execution is changed
148-
# (can't test equality with func)
149-
assertevent_loop_handler!=custom_handler
231+
# Assert: signal handler during loop execution is changed
232+
# (can't test equality with func)
233+
assertevent_loop_handler!=custom_handler
150234

151-
# Assert: current signal handler is the same as the one we set before
152-
assertsignal.getsignal(signal.SIGINT)==custom_handler
235+
# Assert: current signal handler is the same as the one we set before
236+
assertsignal.getsignal(signal.SIGINT)==custom_handler
153237

154-
# Repeat again to test that SIG_DFL and SIG_IGN will not be overridden
155-
forcustom_handlerin (signal.SIG_DFL,signal.SIG_IGN):
156-
qt_core.QTimer.singleShot(0,fire_signal_and_quit)
157-
signal.signal(signal.SIGINT,custom_handler)
238+
# Repeat again to test that SIG_DFL and SIG_IGN will not be overridden
239+
forcustom_handlerin (signal.SIG_DFL,signal.SIG_IGN):
240+
qt_core.QTimer.singleShot(0,fire_signal_and_quit)
241+
signal.signal(signal.SIGINT,custom_handler)
158242

159-
_BackendQT5.mainloop()
243+
_BackendQT5.mainloop()
160244

161-
assertevent_loop_handler==custom_handler
162-
assertsignal.getsignal(signal.SIGINT)==custom_handler
245+
assertevent_loop_handler==custom_handler
246+
assertsignal.getsignal(signal.SIGINT)==custom_handler
163247

164-
# Reset SIGINT handler to what it was before the test
165-
signal.signal(signal.SIGINT,original_handler)
248+
finally:
249+
# Reset SIGINT handler to what it was before the test
250+
signal.signal(signal.SIGINT,original_handler)
166251

167252

168253
@pytest.mark.parametrize(
@@ -548,8 +633,6 @@ def _get_testable_qt_backends():
548633
envs.append(pytest.param(env,marks=marks,id=str(env)))
549634
returnenvs
550635

551-
_test_timeout=60# A reasonably safe value for slower architectures.
552-
553636

554637
@pytest.mark.parametrize("env",_get_testable_qt_backends())
555638
deftest_enums_available(env):

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp