Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Move sigint tests into subprocesses#20907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
886157f
2974f3c
df09043
9e84366
9d13e7d
e13caa4
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -24,6 +24,9 @@ | ||
pytestmark = pytest.mark.skip('No usable Qt bindings') | ||
_test_timeout = 60 # A reasonably safe value for slower architectures. | ||
@pytest.fixture | ||
def qt_core(request): | ||
backend, = request.node.get_closest_marker('backend').args | ||
@@ -33,19 +36,6 @@ def qt_core(request): | ||
return QtCore | ||
@pytest.mark.backend('QtAgg', skip_on_importerror=True) | ||
def test_fig_close(): | ||
@@ -64,50 +54,143 @@ def test_fig_close(): | ||
assert init_figs == Gcf.figs | ||
class WaitForStringPopen(subprocess.Popen): | ||
""" | ||
A Popen that passes flags that allow triggering KeyboardInterrupt. | ||
""" | ||
def __init__(self, *args, **kwargs): | ||
if sys.platform == 'win32': | ||
kwargs['creationflags'] = subprocess.CREATE_NEW_CONSOLE | ||
super().__init__( | ||
*args, **kwargs, | ||
# Force Agg so that each test can switch to its desired Qt backend. | ||
env={**os.environ, "MPLBACKEND": "Agg", "SOURCE_DATE_EPOCH": "0"}, | ||
stdout=subprocess.PIPE, universal_newlines=True) | ||
def wait_for(self, terminator): | ||
"""Read until the terminator is reached.""" | ||
buf = '' | ||
while True: | ||
c = self.stdout.read(1) | ||
if not c: | ||
raise RuntimeError( | ||
f'Subprocess died before emitting expected {terminator!r}') | ||
buf += c | ||
if buf.endswith(terminator): | ||
return | ||
def _test_sigint_impl(backend, target_name, kwargs): | ||
import sys | ||
import matplotlib.pyplot as plt | ||
import os | ||
import threading | ||
plt.switch_backend(backend) | ||
from matplotlib.backends.qt_compat import QtCore | ||
def interupter(): | ||
if sys.platform == 'win32': | ||
import win32api | ||
win32api.GenerateConsoleCtrlEvent(0, 0) | ||
else: | ||
import signal | ||
os.kill(os.getpid(), signal.SIGINT) | ||
target = getattr(plt, target_name) | ||
timer = threading.Timer(1, interupter) | ||
fig = plt.figure() | ||
fig.canvas.mpl_connect( | ||
'draw_event', | ||
lambda *args: print('DRAW', flush=True) | ||
) | ||
fig.canvas.mpl_connect( | ||
'draw_event', | ||
lambda *args: timer.start() | ||
) | ||
try: | ||
target(**kwargs) | ||
except KeyboardInterrupt: | ||
print('SUCCESS', flush=True) | ||
@pytest.mark.backend('QtAgg', skip_on_importerror=True) | ||
@pytest.mark.parametrize("target, kwargs", [ | ||
('show', {'block': True}), | ||
('pause', {'interval': 10}) | ||
]) | ||
def test_sigint(target, kwargs): | ||
backend = plt.get_backend() | ||
proc = WaitForStringPopen( | ||
[sys.executable, "-c", | ||
inspect.getsource(_test_sigint_impl) + | ||
f"\n_test_sigint_impl({backend!r}, {target!r}, {kwargs!r})"]) | ||
try: | ||
proc.wait_for('DRAW') | ||
stdout, _ = proc.communicate(timeout=_test_timeout) | ||
except: | ||
proc.kill() | ||
stdout, _ = proc.communicate() | ||
raise | ||
print(stdout) | ||
assert 'SUCCESS' in stdout | ||
def _test_other_signal_before_sigint_impl(backend, target_name, kwargs): | ||
import signal | ||
import sys | ||
import matplotlib.pyplot as plt | ||
plt.switch_backend(backend) | ||
from matplotlib.backends.qt_compat import QtCore | ||
target = getattr(plt, target_name) | ||
fig = plt.figure() | ||
fig.canvas.mpl_connect('draw_event', | ||
lambda *args: print('DRAW', flush=True)) | ||
timer = fig.canvas.new_timer(interval=1) | ||
timer.single_shot = True | ||
timer.add_callback(print, 'SIGUSR1', flush=True) | ||
def custom_signal_handler(signum, frame): | ||
timer.start() | ||
signal.signal(signal.SIGUSR1, custom_signal_handler) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. There is a whole bunch of code here that is not being run. Is that on purpose/it will be run at a later date? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Everything in | ||
try: | ||
target(**kwargs) | ||
except KeyboardInterrupt: | ||
print('SUCCESS', flush=True) | ||
@pytest.mark.skipif(sys.platform == 'win32', | ||
reason='No other signal available to send on Windows') | ||
@pytest.mark.backend('QtAgg', skip_on_importerror=True) | ||
@pytest.mark.parametrize("target, kwargs", [ | ||
('show', {'block': True}), | ||
('pause', {'interval': 10}) | ||
]) | ||
def test_other_signal_before_sigint(target, kwargs): | ||
backend = plt.get_backend() | ||
proc = WaitForStringPopen( | ||
[sys.executable, "-c", | ||
inspect.getsource(_test_other_signal_before_sigint_impl) + | ||
"\n_test_other_signal_before_sigint_impl(" | ||
f"{backend!r}, {target!r}, {kwargs!r})"]) | ||
try: | ||
proc.wait_for('DRAW') | ||
os.kill(proc.pid, signal.SIGUSR1) | ||
proc.wait_for('SIGUSR1') | ||
os.kill(proc.pid, signal.SIGINT) | ||
stdout, _ = proc.communicate(timeout=_test_timeout) | ||
except: | ||
proc.kill() | ||
stdout, _ = proc.communicate() | ||
raise | ||
print(stdout) | ||
assert 'SUCCESS' in stdout | ||
plt.figure() | ||
@pytest.mark.backend('Qt5Agg') | ||
@@ -140,29 +223,31 @@ def custom_handler(signum, frame): | ||
signal.signal(signal.SIGINT, custom_handler) | ||
try: | ||
# mainloop() sets SIGINT, starts Qt event loop (which triggers timer | ||
# and exits) and then mainloop() resets SIGINT | ||
matplotlib.backends.backend_qt._BackendQT.mainloop() | ||
# Assert: signal handler during loop execution is changed | ||
# (can't test equality with func) | ||
assert event_loop_handler != custom_handler | ||
# Assert: current signal handler is the same as the one we set before | ||
assert signal.getsignal(signal.SIGINT) == custom_handler | ||
# Repeat again to test that SIG_DFL and SIG_IGN will not be overridden | ||
for custom_handler in (signal.SIG_DFL, signal.SIG_IGN): | ||
qt_core.QTimer.singleShot(0, fire_signal_and_quit) | ||
signal.signal(signal.SIGINT, custom_handler) | ||
_BackendQT5.mainloop() | ||
assert event_loop_handler == custom_handler | ||
assert signal.getsignal(signal.SIGINT) == custom_handler | ||
finally: | ||
# Reset SIGINT handler to what it was before the test | ||
signal.signal(signal.SIGINT, original_handler) | ||
@pytest.mark.parametrize( | ||
@@ -548,8 +633,6 @@ def _get_testable_qt_backends(): | ||
envs.append(pytest.param(env, marks=marks, id=str(env))) | ||
return envs | ||
@pytest.mark.parametrize("env", _get_testable_qt_backends()) | ||
def test_enums_available(env): | ||