1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
"""Test eventloop integration"""
import asyncio
import os
import sys
import threading
import time
import pytest
from ipykernel.eventloops import (
enable_gui,
loop_asyncio,
loop_cocoa,
loop_tk,
)
from .utils import flush_channels, start_new_kernel
KC = KM = None
qt_guis_avail = []
gui_to_module = {"qt6": "PySide6", "qt5": "PyQt5"}
def _get_qt_vers():
"""If any version of Qt is available, this will populate `guis_avail` with 'qt' and 'qtx'. Due
to the import mechanism, we can't import multiple versions of Qt in one session."""
for gui in ["qt6", "qt5"]:
print(f"Trying {gui}")
try:
__import__(gui_to_module[gui])
qt_guis_avail.append(gui)
if "QT_API" in os.environ:
del os.environ["QT_API"]
except ImportError:
pass # that version of Qt isn't available.
_get_qt_vers()
@pytest.fixture(autouse=True)
def _setup_env():
"""start the global kernel (if it isn't running) and return its client"""
global KM, KC
KM, KC = start_new_kernel()
flush_channels(KC)
yield
assert KM is not None
assert KC is not None
KC.stop_channels()
KM.shutdown_kernel(now=True)
windows_skip = pytest.mark.skipif(os.name == "nt", reason="causing failures on windows")
# some part of this module seems to hang when run with xvfb
pytestmark = pytest.mark.skipif(
sys.platform == "linux" and bool(os.getenv("CI")), reason="hangs on linux CI"
)
@windows_skip
@pytest.mark.skipif(sys.platform == "darwin", reason="hangs on macos")
def test_tk_loop(kernel):
def do_thing():
time.sleep(1)
try:
kernel.app_wrapper.app.quit()
# guard for tk failing to start (if there is no display)
except AttributeError:
pass
t = threading.Thread(target=do_thing)
t.start()
# guard for tk failing to start (if there is no display)
try:
loop_tk(kernel)
except Exception:
pass
t.join()
@windows_skip
def test_asyncio_loop(kernel):
def do_thing():
loop.call_soon(loop.stop)
loop = asyncio.get_event_loop()
loop.call_soon(do_thing)
loop_asyncio(kernel)
@windows_skip
def test_enable_gui(kernel):
enable_gui("tk", kernel)
@pytest.mark.skipif(sys.platform != "darwin", reason="MacOS-only")
def test_cocoa_loop(kernel):
loop_cocoa(kernel)
@pytest.mark.parametrize("gui", qt_guis_avail)
def test_qt_enable_gui(gui, kernel, capsys):
if os.getenv("GITHUB_ACTIONS", None) == "true" and gui == "qt5":
pytest.skip("Qt5 and GitHub action crash CPython")
if gui == "qt6" and sys.version_info < (3, 10):
pytest.skip(
"qt6 fails on 3.9 with AttributeError: module 'PySide6.QtPrintSupport' has no attribute 'QApplication'"
)
if sys.platform == "linux" and gui == "qt6" and os.getenv("GITHUB_ACTIONS", None) == "true":
pytest.skip("qt6 fails on github CI with missing libEGL.so.1")
enable_gui(gui, kernel)
# We store the `QApplication` instance in the kernel.
assert hasattr(kernel, "app")
# And the `QEventLoop` is added to `app`:`
assert hasattr(kernel.app, "qt_event_loop")
# Don't create another app even if `gui` is the same.
app = kernel.app
enable_gui(gui, kernel)
assert app == kernel.app
# Event loop integration can be turned off.
enable_gui(None, kernel)
assert not hasattr(kernel, "app")
# But now we're stuck with this version of Qt for good; can't switch.
for not_gui in ["qt6", "qt5"]:
if not_gui not in qt_guis_avail:
break
enable_gui(not_gui, kernel)
captured = capsys.readouterr()
assert captured.out == f"Cannot switch Qt versions for this session; you must use {gui}.\n"
# Check 'qt' gui, which means "the best available"
enable_gui(None, kernel)
enable_gui("qt", kernel)
assert gui_to_module[gui] in str(kernel.app)
|