Skip to content

Commit 252c406

Browse files
authored
Add more testing and deprecate the Gtk event loops (#1036)
1 parent ad77868 commit 252c406

File tree

12 files changed

+204
-124
lines changed

12 files changed

+204
-124
lines changed

ipykernel/comm/comm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
# this is the class that will be created if we do comm.create_comm
1616
class BaseComm(comm.base_comm.BaseComm):
17-
kernel: Optional[Kernel]
17+
kernel: Optional[Kernel] = None
1818

1919
def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
2020
"""Helper for sending a comm message on IOPub"""

ipykernel/gui/gtk3embed.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# -----------------------------------------------------------------------------
1313
# stdlib
1414
import sys
15+
import warnings
16+
17+
warnings.warn("The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning)
1518

1619
# Third-party
1720
import gi

ipykernel/gui/gtkembed.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# -----------------------------------------------------------------------------
1313
# stdlib
1414
import sys
15+
import warnings
16+
17+
warnings.warn("The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning)
1518

1619
# Third-party
1720
import gobject
Lines changed: 82 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
# Copyright (c) IPython Development Team.
22
# Distributed under the terms of the Modified BSD License.
33

4-
import asyncio
54
import sys
6-
import unittest
75
from contextlib import contextmanager
86
from io import StringIO
97

108
import pytest
11-
import tornado
129
from IPython.utils.io import capture_output
1310
from jupyter_client.session import Session
1411

@@ -20,44 +17,6 @@
2017
orig_msg = Session.msg
2118

2219

23-
def _init_asyncio_patch():
24-
"""set default asyncio policy to be compatible with tornado
25-
26-
Tornado 6 (at least) is not compatible with the default
27-
asyncio implementation on Windows
28-
29-
Pick the older SelectorEventLoopPolicy on Windows
30-
if the known-incompatible default policy is in use.
31-
32-
do this as early as possible to make it a low priority and overrideable
33-
34-
ref: https://github.com/tornadoweb/tornado/issues/2608
35-
36-
FIXME: if/when tornado supports the defaults in asyncio,
37-
remove and bump tornado requirement for py38
38-
"""
39-
if (
40-
sys.platform.startswith("win")
41-
and sys.version_info >= (3, 8)
42-
and tornado.version_info < (6, 1)
43-
):
44-
import asyncio
45-
46-
try:
47-
from asyncio import (
48-
WindowsProactorEventLoopPolicy,
49-
WindowsSelectorEventLoopPolicy,
50-
)
51-
except ImportError:
52-
pass
53-
# not affected
54-
else:
55-
if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy:
56-
# WindowsProactorEventLoopPolicy is not compatible with tornado 6
57-
# fallback to the pre-3.8 default of Selector
58-
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
59-
60-
6120
def _inject_cell_id(_self, *args, **kwargs):
6221
"""
6322
This patch jupyter_client.session:Session.msg to add a cell_id to the return message metadata
@@ -78,79 +37,85 @@ def patch_cell_id():
7837
Session.msg = orig_msg
7938

8039

81-
class InProcessKernelTestCase(unittest.TestCase):
82-
def setUp(self):
83-
_init_asyncio_patch()
84-
self.km = InProcessKernelManager()
85-
self.km.start_kernel()
86-
self.kc = self.km.client()
87-
self.kc.start_channels()
88-
self.kc.wait_for_ready()
89-
90-
def test_with_cell_id(self):
91-
92-
with patch_cell_id():
93-
self.kc.execute("1+1")
94-
95-
def test_pylab(self):
96-
"""Does %pylab work in the in-process kernel?"""
97-
_ = pytest.importorskip("matplotlib", reason="This test requires matplotlib")
98-
kc = self.kc
99-
kc.execute("%pylab")
100-
out, err = assemble_output(kc.get_iopub_msg)
101-
self.assertIn("matplotlib", out)
102-
103-
def test_raw_input(self):
104-
"""Does the in-process kernel handle raw_input correctly?"""
105-
io = StringIO("foobar\n")
106-
sys_stdin = sys.stdin
107-
sys.stdin = io
108-
try:
109-
self.kc.execute("x = input()")
110-
finally:
111-
sys.stdin = sys_stdin
112-
assert self.km.kernel.shell.user_ns.get("x") == "foobar"
113-
114-
@pytest.mark.skipif("__pypy__" in sys.builtin_module_names, reason="fails on pypy")
115-
def test_stdout(self):
116-
"""Does the in-process kernel correctly capture IO?"""
117-
kernel = InProcessKernel()
118-
119-
with capture_output() as io:
120-
kernel.shell.run_cell('print("foo")')
121-
assert io.stdout == "foo\n"
122-
123-
kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session)
124-
kernel.frontends.append(kc)
125-
kc.execute('print("bar")')
126-
out, err = assemble_output(kc.get_iopub_msg)
127-
assert out == "bar\n"
128-
129-
@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing")
130-
def test_capfd(self):
131-
"""Does correctly capture fd"""
132-
kernel = InProcessKernel()
133-
134-
with capture_output() as io:
135-
kernel.shell.run_cell('print("foo")')
136-
assert io.stdout == "foo\n"
137-
138-
kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session)
139-
kernel.frontends.append(kc)
140-
kc.execute("import os")
141-
kc.execute('os.system("echo capfd")')
142-
out, err = assemble_output(kc.iopub_channel)
143-
assert out == "capfd\n"
144-
145-
def test_getpass_stream(self):
146-
"Tests that kernel getpass accept the stream parameter"
147-
kernel = InProcessKernel()
148-
kernel._allow_stdin = True
149-
kernel._input_request = lambda *args, **kwargs: None
150-
151-
kernel.getpass(stream="non empty")
152-
153-
def test_do_execute(self):
154-
kernel = InProcessKernel()
155-
asyncio.run(kernel.do_execute("a=1", True))
156-
assert kernel.shell.user_ns["a"] == 1
40+
@pytest.fixture()
41+
def kc():
42+
km = InProcessKernelManager()
43+
km.start_kernel()
44+
kc = km.client()
45+
kc.start_channels()
46+
kc.wait_for_ready()
47+
yield kc
48+
49+
50+
def test_with_cell_id(kc):
51+
52+
with patch_cell_id():
53+
kc.execute("1+1")
54+
55+
56+
def test_pylab(kc):
57+
"""Does %pylab work in the in-process kernel?"""
58+
_ = pytest.importorskip("matplotlib", reason="This test requires matplotlib")
59+
kc.execute("%pylab")
60+
out, err = assemble_output(kc.get_iopub_msg)
61+
assert "matplotlib" in out
62+
63+
64+
def test_raw_input(kc):
65+
"""Does the in-process kernel handle raw_input correctly?"""
66+
io = StringIO("foobar\n")
67+
sys_stdin = sys.stdin
68+
sys.stdin = io
69+
try:
70+
kc.execute("x = input()")
71+
finally:
72+
sys.stdin = sys_stdin
73+
assert kc.kernel.shell.user_ns.get("x") == "foobar"
74+
75+
76+
@pytest.mark.skipif("__pypy__" in sys.builtin_module_names, reason="fails on pypy")
77+
def test_stdout(kc):
78+
"""Does the in-process kernel correctly capture IO?"""
79+
kernel = InProcessKernel()
80+
81+
with capture_output() as io:
82+
kernel.shell.run_cell('print("foo")')
83+
assert io.stdout == "foo\n"
84+
85+
kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session)
86+
kernel.frontends.append(kc)
87+
kc.execute('print("bar")')
88+
out, err = assemble_output(kc.get_iopub_msg)
89+
assert out == "bar\n"
90+
91+
92+
@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing")
93+
def test_capfd(kc):
94+
"""Does correctly capture fd"""
95+
kernel = InProcessKernel()
96+
97+
with capture_output() as io:
98+
kernel.shell.run_cell('print("foo")')
99+
assert io.stdout == "foo\n"
100+
101+
kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session)
102+
kernel.frontends.append(kc)
103+
kc.execute("import os")
104+
kc.execute('os.system("echo capfd")')
105+
out, err = assemble_output(kc.iopub_channel)
106+
assert out == "capfd\n"
107+
108+
109+
def test_getpass_stream(kc):
110+
"Tests that kernel getpass accept the stream parameter"
111+
kernel = InProcessKernel()
112+
kernel._allow_stdin = True
113+
kernel._input_request = lambda *args, **kwargs: None
114+
115+
kernel.getpass(stream="non empty")
116+
117+
118+
async def test_do_execute(kc):
119+
kernel = InProcessKernel()
120+
await kernel.do_execute("a=1", True)
121+
assert kernel.shell.user_ns["a"] == 1

ipykernel/kernelapp.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,9 @@ def abs_connection_file(self):
168168
trio_loop = Bool(False, help="Set main event loop.").tag(config=True)
169169
quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True)
170170
outstream_class = DottedObjectName(
171-
"ipykernel.iostream.OutStream", help="The importstring for the OutStream factory"
171+
"ipykernel.iostream.OutStream",
172+
help="The importstring for the OutStream factory",
173+
allow_none=True,
172174
).tag(config=True)
173175
displayhook_class = DottedObjectName(
174176
"ipykernel.displayhook.ZMQDisplayHook", help="The importstring for the DisplayHook factory"
@@ -717,7 +719,7 @@ def start(self):
717719
launch_new_instance = IPKernelApp.launch_instance
718720

719721

720-
def main():
722+
def main(): # pragma: no cover
721723
"""Run an IPKernel as an application"""
722724
app = IPKernelApp.instance()
723725
app.initialize()

ipykernel/tests/test_comm.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from ipykernel.comm import Comm
2+
3+
4+
async def test_comm(kernel):
5+
c = Comm()
6+
c.kernel = kernel
7+
c.publish_msg("foo")

ipykernel/tests/test_embed_kernel.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
"""test IPython.embed_kernel()"""
1+
"""test embed_kernel"""
22

33
# Copyright (c) IPython Development Team.
44
# Distributed under the terms of the Modified BSD License.
55

66
import json
77
import os
88
import sys
9+
import threading
910
import time
1011
from contextlib import contextmanager
1112
from subprocess import PIPE, Popen
@@ -15,6 +16,8 @@
1516
from jupyter_client import BlockingKernelClient
1617
from jupyter_core import paths
1718

19+
from ipykernel.embed import IPKernelApp, embed_kernel
20+
1821
SETUP_TIMEOUT = 60
1922
TIMEOUT = 15
2023

@@ -193,3 +196,20 @@ def test_embed_kernel_reentrant():
193196
client.execute("get_ipython().exit_now = True")
194197
msg = client.get_shell_msg(timeout=TIMEOUT)
195198
time.sleep(0.2)
199+
200+
201+
def test_embed_kernel_func():
202+
from types import ModuleType
203+
204+
module = ModuleType("test")
205+
206+
def trigger_stop():
207+
time.sleep(1)
208+
app = IPKernelApp.instance()
209+
app.io_loop.add_callback(app.io_loop.stop)
210+
IPKernelApp.clear_instance()
211+
212+
thread = threading.Thread(target=trigger_stop)
213+
thread.start()
214+
215+
embed_kernel(module, outstream_class=None)

ipykernel/tests/test_eventloop.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
import sys
66
import threading
77
import time
8+
from unittest.mock import MagicMock
89

910
import pytest
1011
import tornado
1112

12-
from ipykernel.eventloops import enable_gui, loop_asyncio, loop_tk
13+
from ipykernel.eventloops import enable_gui, loop_asyncio, loop_cocoa, loop_tk
1314

1415
from .utils import execute, flush_channels, start_new_kernel
1516

@@ -88,3 +89,9 @@ def do_thing():
8889
@windows_skip
8990
def test_enable_gui(kernel):
9091
enable_gui("tk", kernel)
92+
93+
94+
@pytest.mark.skipif(sys.platform != "darwin", reason="MacOS-only")
95+
def test_cocoa_loop(kernel):
96+
kernel.shell = MagicMock()
97+
loop_cocoa(kernel)

ipykernel/tests/test_kernel.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,10 @@ def _start_children():
534534
platform.python_implementation() == "PyPy",
535535
reason="does not work on PyPy",
536536
)
537+
@pytest.mark.skipif(
538+
sys.platform.lower() == "linux",
539+
reason="Stalls on linux",
540+
)
537541
def test_shutdown_subprocesses():
538542
"""Kernel exits after polite shutdown_request"""
539543
with new_kernel() as kc:

0 commit comments

Comments
 (0)