first commit

This commit is contained in:
Ayxan
2022-05-23 00:16:32 +04:00
commit d660f2a4ca
24786 changed files with 4428337 additions and 0 deletions

View File

@ -0,0 +1,47 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import os
import shutil
import sys
import tempfile
from unittest.mock import patch
from ipykernel.kernelspec import install
pjoin = os.path.join
tmp = None
patchers = []
def setup():
"""setup temporary env for tests"""
global tmp
tmp = tempfile.mkdtemp()
patchers[:] = [
patch.dict(
os.environ,
{
"HOME": tmp,
# Let tests work with --user install when HOME is changed:
"PYTHONPATH": os.pathsep.join(sys.path),
},
),
]
for p in patchers:
p.start()
# install IPython in the temp home:
install(user=True)
def teardown():
for p in patchers:
p.stop()
try:
shutil.rmtree(tmp)
except OSError:
# no such file
pass

View File

@ -0,0 +1,17 @@
"""test utilities that use async/await syntax
a separate file to avoid syntax errors on Python 2
"""
import asyncio
def async_func():
"""Simple async function to schedule a task on the current eventloop"""
loop = asyncio.get_event_loop()
assert loop.is_running()
async def task():
await asyncio.sleep(1)
loop.create_task(task())

View File

@ -0,0 +1,28 @@
import asyncio
import os
try:
import resource
except ImportError:
# Windows
resource = None
# Handle resource limit
# Ensure a minimal soft limit of DEFAULT_SOFT if the current hard limit is at least that much.
if resource is not None:
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
DEFAULT_SOFT = 4096
if hard >= DEFAULT_SOFT:
soft = DEFAULT_SOFT
if hard < soft:
hard = soft
resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard))
# Enforce selector event loop on Windows.
if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

View File

@ -0,0 +1,58 @@
"""Test async/await integration"""
import pytest
from .test_message_spec import validate_message
from .utils import TIMEOUT, execute, flush_channels, start_new_kernel
KC = KM = None
def setup_function():
"""start the global kernel (if it isn't running) and return its client"""
global KM, KC
KM, KC = start_new_kernel()
flush_channels(KC)
def teardown_function():
KC.stop_channels()
KM.shutdown_kernel(now=True)
def test_async_await():
flush_channels(KC)
msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC)
assert content["status"] == "ok", content
@pytest.mark.parametrize("asynclib", ["asyncio", "trio", "curio"])
def test_async_interrupt(asynclib, request):
try:
__import__(asynclib)
except ImportError:
pytest.skip("Requires %s" % asynclib)
request.addfinalizer(lambda: execute("%autoawait asyncio", KC))
flush_channels(KC)
msg_id, content = execute("%autoawait " + asynclib, KC)
assert content["status"] == "ok", content
flush_channels(KC)
msg_id = KC.execute(f"print('begin'); import {asynclib}; await {asynclib}.sleep(5)")
busy = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(busy, "status", msg_id)
assert busy["content"]["execution_state"] == "busy"
echo = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(echo, "execute_input")
stream = KC.get_iopub_msg(timeout=TIMEOUT)
# wait for the stream output to be sure kernel is in the async block
validate_message(stream, "stream")
assert stream["content"]["text"] == "begin\n"
KM.interrupt_kernel()
reply = KC.get_shell_msg()["content"]
assert reply["status"] == "error", reply
assert reply["ename"] in {"CancelledError", "KeyboardInterrupt"}
flush_channels(KC)

View File

@ -0,0 +1,131 @@
"""Tests for kernel connection utilities"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import errno
import json
import os
from tempfile import TemporaryDirectory
from unittest.mock import patch
import pytest
import zmq
from traitlets.config import Config
from ipykernel import connect
from ipykernel.kernelapp import IPKernelApp
from .utils import TemporaryWorkingDirectory
sample_info = {
"ip": "1.2.3.4",
"transport": "ipc",
"shell_port": 1,
"hb_port": 2,
"iopub_port": 3,
"stdin_port": 4,
"control_port": 5,
"key": b"abc123",
"signature_scheme": "hmac-md5",
}
class DummyKernelApp(IPKernelApp):
def _default_shell_port(self):
return 0
def initialize(self, argv=None):
self.init_profile_dir()
self.init_connection_file()
def test_get_connection_file():
cfg = Config()
with TemporaryWorkingDirectory() as d:
cfg.ProfileDir.location = d
cf = "kernel.json"
app = DummyKernelApp(config=cfg, connection_file=cf)
app.initialize()
profile_cf = os.path.join(app.connection_dir, cf)
assert profile_cf == app.abs_connection_file
with open(profile_cf, "w") as f:
f.write("{}")
assert os.path.exists(profile_cf)
assert connect.get_connection_file(app) == profile_cf
app.connection_file = cf
assert connect.get_connection_file(app) == profile_cf
def test_get_connection_info():
with TemporaryDirectory() as d:
cf = os.path.join(d, "kernel.json")
connect.write_connection_file(cf, **sample_info)
json_info = connect.get_connection_info(cf)
info = connect.get_connection_info(cf, unpack=True)
assert isinstance(json_info, str)
sub_info = {k: v for k, v in info.items() if k in sample_info}
assert sub_info == sample_info
info2 = json.loads(json_info)
info2["key"] = info2["key"].encode("utf-8")
sub_info2 = {k: v for k, v in info.items() if k in sample_info}
assert sub_info2 == sample_info
def test_port_bind_failure_raises(request):
cfg = Config()
with TemporaryWorkingDirectory() as d:
cfg.ProfileDir.location = d
cf = "kernel.json"
app = DummyKernelApp(config=cfg, connection_file=cf)
request.addfinalizer(app.close)
app.initialize()
with patch.object(app, "_try_bind_socket") as mock_try_bind:
mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types")
with pytest.raises(zmq.ZMQError):
app.init_sockets()
assert mock_try_bind.call_count == 1
def test_port_bind_failure_recovery(request):
try:
errno.WSAEADDRINUSE
except AttributeError:
# Fake windows address in-use code
p = patch.object(errno, "WSAEADDRINUSE", 12345, create=True)
p.start()
request.addfinalizer(p.stop)
cfg = Config()
with TemporaryWorkingDirectory() as d:
cfg.ProfileDir.location = d
cf = "kernel.json"
app = DummyKernelApp(config=cfg, connection_file=cf)
request.addfinalizer(app.close)
app.initialize()
with patch.object(app, "_try_bind_socket") as mock_try_bind:
mock_try_bind.side_effect = [
zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"),
zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows"),
] + [0] * 100
# Shouldn't raise anything as retries will kick in
app.init_sockets()
def test_port_bind_failure_gives_up_retries(request):
cfg = Config()
with TemporaryWorkingDirectory() as d:
cfg.ProfileDir.location = d
cf = "kernel.json"
app = DummyKernelApp(config=cfg, connection_file=cf)
request.addfinalizer(app.close)
app.initialize()
with patch.object(app, "_try_bind_socket") as mock_try_bind:
mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind")
with pytest.raises(zmq.ZMQError):
app.init_sockets()
assert mock_try_bind.call_count == 100

View File

@ -0,0 +1,284 @@
import sys
import pytest
from .utils import TIMEOUT, get_reply, new_kernel
seq = 0
# Skip if debugpy is not available
pytest.importorskip("debugpy")
def wait_for_debug_request(kernel, command, arguments=None, full_reply=False):
"""Carry out a debug request and return the reply content.
It does not check if the request was successful.
"""
global seq
seq += 1
msg = kernel.session.msg(
"debug_request",
{
"type": "request",
"seq": seq,
"command": command,
"arguments": arguments or {},
},
)
kernel.control_channel.send(msg)
reply = get_reply(kernel, msg["header"]["msg_id"], channel="control")
return reply if full_reply else reply["content"]
@pytest.fixture
def kernel():
with new_kernel() as kc:
yield kc
@pytest.fixture
def kernel_with_debug(kernel):
# Initialize
wait_for_debug_request(
kernel,
"initialize",
{
"clientID": "test-client",
"clientName": "testClient",
"adapterID": "",
"pathFormat": "path",
"linesStartAt1": True,
"columnsStartAt1": True,
"supportsVariableType": True,
"supportsVariablePaging": True,
"supportsRunInTerminalRequest": True,
"locale": "en",
},
)
# Attach
wait_for_debug_request(kernel, "attach")
try:
yield kernel
finally:
# Detach
wait_for_debug_request(kernel, "disconnect", {"restart": False, "terminateDebuggee": True})
def test_debug_initialize(kernel):
reply = wait_for_debug_request(
kernel,
"initialize",
{
"clientID": "test-client",
"clientName": "testClient",
"adapterID": "",
"pathFormat": "path",
"linesStartAt1": True,
"columnsStartAt1": True,
"supportsVariableType": True,
"supportsVariablePaging": True,
"supportsRunInTerminalRequest": True,
"locale": "en",
},
)
assert reply["success"]
def test_attach_debug(kernel_with_debug):
reply = wait_for_debug_request(
kernel_with_debug, "evaluate", {"expression": "'a' + 'b'", "context": "repl"}
)
assert reply["success"]
assert reply["body"]["result"] == ""
def test_set_breakpoints(kernel_with_debug):
code = """def f(a, b):
c = a + b
return c
f(2, 3)"""
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
source = r["body"]["sourcePath"]
reply = wait_for_debug_request(
kernel_with_debug,
"setBreakpoints",
{
"breakpoints": [{"line": 2}],
"source": {"path": source},
"sourceModified": False,
},
)
assert reply["success"]
assert len(reply["body"]["breakpoints"]) == 1
assert reply["body"]["breakpoints"][0]["verified"]
assert reply["body"]["breakpoints"][0]["source"]["path"] == source
r = wait_for_debug_request(kernel_with_debug, "debugInfo")
assert source in map(lambda b: b["source"], r["body"]["breakpoints"])
r = wait_for_debug_request(kernel_with_debug, "configurationDone")
assert r["success"]
def test_stop_on_breakpoint(kernel_with_debug):
code = """def f(a, b):
c = a + b
return c
f(2, 3)"""
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
source = r["body"]["sourcePath"]
wait_for_debug_request(kernel_with_debug, "debugInfo")
wait_for_debug_request(
kernel_with_debug,
"setBreakpoints",
{
"breakpoints": [{"line": 2}],
"source": {"path": source},
"sourceModified": False,
},
)
wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True)
kernel_with_debug.execute(code)
# Wait for stop on breakpoint
msg = {"msg_type": "", "content": {}}
while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped":
msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT)
assert msg["content"]["body"]["reason"] == "breakpoint"
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="TODO Does not work on Python 3.10")
def test_breakpoint_in_cell_with_leading_empty_lines(kernel_with_debug):
code = """
def f(a, b):
c = a + b
return c
f(2, 3)"""
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
source = r["body"]["sourcePath"]
wait_for_debug_request(kernel_with_debug, "debugInfo")
wait_for_debug_request(
kernel_with_debug,
"setBreakpoints",
{
"breakpoints": [{"line": 6}],
"source": {"path": source},
"sourceModified": False,
},
)
wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True)
kernel_with_debug.execute(code)
# Wait for stop on breakpoint
msg = {"msg_type": "", "content": {}}
while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped":
msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT)
assert msg["content"]["body"]["reason"] == "breakpoint"
def test_rich_inspect_not_at_breakpoint(kernel_with_debug):
var_name = "text"
value = "Hello the world"
code = f"""{var_name}='{value}'
print({var_name})
"""
msg_id = kernel_with_debug.execute(code)
get_reply(kernel_with_debug, msg_id)
r = wait_for_debug_request(kernel_with_debug, "inspectVariables")
assert var_name in list(map(lambda v: v["name"], r["body"]["variables"]))
reply = wait_for_debug_request(
kernel_with_debug,
"richInspectVariables",
{"variableName": var_name},
)
assert reply["body"]["data"] == {"text/plain": f"'{value}'"}
def test_rich_inspect_at_breakpoint(kernel_with_debug):
code = """def f(a, b):
c = a + b
return c
f(2, 3)"""
r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code})
source = r["body"]["sourcePath"]
wait_for_debug_request(
kernel_with_debug,
"setBreakpoints",
{
"breakpoints": [{"line": 2}],
"source": {"path": source},
"sourceModified": False,
},
)
r = wait_for_debug_request(kernel_with_debug, "debugInfo")
r = wait_for_debug_request(kernel_with_debug, "configurationDone")
kernel_with_debug.execute(code)
# Wait for stop on breakpoint
msg = {"msg_type": "", "content": {}}
while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped":
msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT)
stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][
"stackFrames"
]
scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]})[
"body"
]["scopes"]
locals_ = wait_for_debug_request(
kernel_with_debug,
"variables",
{
"variablesReference": next(filter(lambda s: s["name"] == "Locals", scopes))[
"variablesReference"
]
},
)["body"]["variables"]
reply = wait_for_debug_request(
kernel_with_debug,
"richInspectVariables",
{"variableName": locals_[0]["name"], "frameId": stacks[0]["id"]},
)
assert reply["body"]["data"] == {"text/plain": locals_[0]["value"]}
def test_convert_to_long_pathname():
if sys.platform == "win32":
from ipykernel.compiler import _convert_to_long_pathname
_convert_to_long_pathname(__file__)

View File

@ -0,0 +1,190 @@
"""test IPython.embed_kernel()"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
import os
import sys
import time
from contextlib import contextmanager
from subprocess import PIPE, Popen
from flaky import flaky
from jupyter_client import BlockingKernelClient
from jupyter_core import paths
SETUP_TIMEOUT = 60
TIMEOUT = 15
@contextmanager
def setup_kernel(cmd):
"""start an embedded kernel in a subprocess, and wait for it to be ready
Returns
-------
kernel_manager: connected KernelManager instance
"""
def connection_file_ready(connection_file):
"""Check if connection_file is a readable json file."""
if not os.path.exists(connection_file):
return False
try:
with open(connection_file) as f:
json.load(f)
return True
except ValueError:
return False
kernel = Popen([sys.executable, "-c", cmd], stdout=PIPE, stderr=PIPE, encoding="utf-8")
try:
connection_file = os.path.join(
paths.jupyter_runtime_dir(),
"kernel-%i.json" % kernel.pid,
)
# wait for connection file to exist, timeout after 5s
tic = time.time()
while (
not connection_file_ready(connection_file)
and kernel.poll() is None
and time.time() < tic + SETUP_TIMEOUT
):
time.sleep(0.1)
# Wait 100ms for the writing to finish
time.sleep(0.1)
if kernel.poll() is not None:
o, e = kernel.communicate()
raise OSError("Kernel failed to start:\n%s" % e)
if not os.path.exists(connection_file):
if kernel.poll() is None:
kernel.terminate()
raise OSError("Connection file %r never arrived" % connection_file)
client = BlockingKernelClient(connection_file=connection_file)
client.load_connection_file()
client.start_channels()
client.wait_for_ready()
try:
yield client
finally:
client.stop_channels()
finally:
kernel.terminate()
kernel.wait()
# Make sure all the fds get closed.
for attr in ["stdout", "stderr", "stdin"]:
fid = getattr(kernel, attr)
if fid:
fid.close()
@flaky(max_runs=3)
def test_embed_kernel_basic():
"""IPython.embed_kernel() is basically functional"""
cmd = "\n".join(
[
"from IPython import embed_kernel",
"def go():",
" a=5",
' b="hi there"',
" embed_kernel()",
"go()",
"",
]
)
with setup_kernel(cmd) as client:
# oinfo a (int)
client.inspect("a")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
client.execute("c=a*2")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["status"] == "ok"
# oinfo c (should be 10)
client.inspect("c")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "10" in text
@flaky(max_runs=3)
def test_embed_kernel_namespace():
"""IPython.embed_kernel() inherits calling namespace"""
cmd = "\n".join(
[
"from IPython import embed_kernel",
"def go():",
" a=5",
' b="hi there"',
" embed_kernel()",
"go()",
"",
]
)
with setup_kernel(cmd) as client:
# oinfo a (int)
client.inspect("a")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "5" in text
# oinfo b (str)
client.inspect("b")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "hi there" in text
# oinfo c (undefined)
client.inspect("c")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert not content["found"]
@flaky(max_runs=3)
def test_embed_kernel_reentrant():
"""IPython.embed_kernel() can be called multiple times"""
cmd = "\n".join(
[
"from IPython import embed_kernel",
"count = 0",
"def go():",
" global count",
" embed_kernel()",
" count = count + 1",
"",
"while True: go()",
"",
]
)
with setup_kernel(cmd) as client:
for i in range(5):
client.inspect("count")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert str(i) in text
# exit from embed_kernel
client.execute("get_ipython().exit_now = True")
msg = client.get_shell_msg(timeout=TIMEOUT)
time.sleep(0.2)

View File

@ -0,0 +1,43 @@
"""Test eventloop integration"""
import pytest
import tornado
from .utils import execute, flush_channels, start_new_kernel
KC = KM = None
def setup():
"""start the global kernel (if it isn't running) and return its client"""
global KM, KC
KM, KC = start_new_kernel()
flush_channels(KC)
def teardown():
KC.stop_channels()
KM.shutdown_kernel(now=True)
async_code = """
from ipykernel.tests._asyncio_utils import async_func
async_func()
"""
@pytest.mark.skipif(tornado.version_info < (5,), reason="only relevant on tornado 5")
def test_asyncio_interrupt():
flush_channels(KC)
msg_id, content = execute("%gui asyncio", KC)
assert content["status"] == "ok", content
flush_channels(KC)
msg_id, content = execute(async_code, KC)
assert content["status"] == "ok", content
KM.interrupt_kernel()
flush_channels(KC)
msg_id, content = execute(async_code, KC)
assert content["status"] == "ok"

View File

@ -0,0 +1,59 @@
"""Tests for heartbeat thread"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import errno
from unittest.mock import patch
import pytest
import zmq
from ipykernel.heartbeat import Heartbeat
def test_port_bind_failure_raises():
heart = Heartbeat(None)
with patch.object(heart, "_try_bind_socket") as mock_try_bind:
mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types")
with pytest.raises(zmq.ZMQError):
heart._bind_socket()
assert mock_try_bind.call_count == 1
def test_port_bind_success():
heart = Heartbeat(None)
with patch.object(heart, "_try_bind_socket") as mock_try_bind:
heart._bind_socket()
assert mock_try_bind.call_count == 1
def test_port_bind_failure_recovery():
try:
errno.WSAEADDRINUSE
except AttributeError:
# Fake windows address in-use code
errno.WSAEADDRINUSE = 12345
try:
heart = Heartbeat(None)
with patch.object(heart, "_try_bind_socket") as mock_try_bind:
mock_try_bind.side_effect = [
zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"),
zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows"),
] + [0] * 100
# Shouldn't raise anything as retries will kick in
heart._bind_socket()
finally:
# Cleanup fake assignment
if errno.WSAEADDRINUSE == 12345:
del errno.WSAEADDRINUSE
def test_port_bind_failure_gives_up_retries():
heart = Heartbeat(None)
with patch.object(heart, "_try_bind_socket") as mock_try_bind:
mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind")
with pytest.raises(zmq.ZMQError):
heart._bind_socket()
assert mock_try_bind.call_count == 100

View File

@ -0,0 +1,53 @@
"""Test IO capturing functionality"""
import io
import pytest
import zmq
from jupyter_client.session import Session
from ipykernel.iostream import IOPubThread, OutStream
def test_io_api():
"""Test that wrapped stdout has the same API as a normal TextIO object"""
session = Session()
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
thread = IOPubThread(pub)
thread.start()
stream = OutStream(session, thread, "stdout")
# cleanup unused zmq objects before we start testing
thread.stop()
thread.close()
ctx.term()
assert stream.errors is None
assert not stream.isatty()
with pytest.raises(io.UnsupportedOperation):
stream.detach()
with pytest.raises(io.UnsupportedOperation):
next(stream)
with pytest.raises(io.UnsupportedOperation):
stream.read()
with pytest.raises(io.UnsupportedOperation):
stream.readline()
with pytest.raises(io.UnsupportedOperation):
stream.seek(0)
with pytest.raises(io.UnsupportedOperation):
stream.tell()
with pytest.raises(TypeError):
stream.write(b"")
def test_io_isatty():
session = Session()
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
thread = IOPubThread(pub)
thread.start()
stream = OutStream(session, thread, "stdout", isatty=True)
assert stream.isatty()

View File

@ -0,0 +1,119 @@
"""Test suite for our JSON utilities."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
import numbers
from binascii import a2b_base64
from datetime import datetime
import pytest
from jupyter_client._version import version_info as jupyter_client_version
from .. import jsonutil
from ..jsonutil import encode_images, json_clean
JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0]
class MyInt:
def __int__(self):
return 389
numbers.Integral.register(MyInt)
class MyFloat:
def __float__(self):
return 3.14
numbers.Real.register(MyFloat)
@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test():
# list of input/expected output. Use None for the expected output if it
# can be the same as the input.
pairs = [
(1, None), # start with scalars
(1.0, None),
("a", None),
(True, None),
(False, None),
(None, None),
# Containers
([1, 2], None),
((1, 2), [1, 2]),
({1, 2}, [1, 2]),
(dict(x=1), None),
({"x": 1, "y": [1, 2, 3], "1": "int"}, None),
# More exotic objects
((x for x in range(3)), [0, 1, 2]),
(iter([1, 2]), [1, 2]),
(datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"),
(MyFloat(), 3.14),
(MyInt(), 389),
]
for val, jval in pairs:
if jval is None:
jval = val
out = json_clean(val)
# validate our cleanup
assert out == jval
# and ensure that what we return, indeed encodes cleanly
json.loads(json.dumps(out))
@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_encode_images():
# invalid data, but the header and footer are from real files
pngdata = b"\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82"
jpegdata = b"\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9"
pdfdata = b"%PDF-1.\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>"
bindata = b"\xff\xff\xff\xff"
fmt = {
"image/png": pngdata,
"image/jpeg": jpegdata,
"application/pdf": pdfdata,
"application/unrecognized": bindata,
}
encoded = json_clean(encode_images(fmt))
for key, value in fmt.items():
# encoded has unicode, want bytes
decoded = a2b_base64(encoded[key])
assert decoded == value
encoded2 = json_clean(encode_images(encoded))
assert encoded == encoded2
for key, value in fmt.items():
decoded = a2b_base64(encoded[key])
assert decoded == value
@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_lambda():
with pytest.raises(ValueError):
json_clean(lambda: 1)
@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_exception():
bad_dicts = [
{1: "number", "1": "string"},
{True: "bool", "True": "string"},
]
for d in bad_dicts:
with pytest.raises(ValueError):
json_clean(d)
@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op")
def test_unicode_dict():
data = {"üniço∂e": "üniço∂e"}
clean = jsonutil.json_clean(data)
assert data == clean

View File

@ -0,0 +1,575 @@
"""test the IPython Kernel"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import ast
import os.path
import platform
import signal
import subprocess
import sys
import time
from subprocess import Popen
from tempfile import TemporaryDirectory
import IPython
import psutil
import pytest
from flaky import flaky
from IPython.paths import locate_profile
from .utils import (
TIMEOUT,
assemble_output,
execute,
flush_channels,
get_reply,
kernel,
new_kernel,
wait_for_idle,
)
def _check_master(kc, expected=True, stream="stdout"):
execute(kc=kc, code="import sys")
flush_channels(kc)
msg_id, content = execute(kc=kc, code="print(sys.%s._is_master_process())" % stream)
stdout, stderr = assemble_output(kc.get_iopub_msg)
assert stdout.strip() == repr(expected)
def _check_status(content):
"""If status=error, show the traceback"""
if content["status"] == "error":
assert False, "".join(["\n"] + content["traceback"])
# printing tests
def test_simple_print():
"""simple print statement in kernel"""
with kernel() as kc:
msg_id, content = execute(kc=kc, code="print('hi')")
stdout, stderr = assemble_output(kc.get_iopub_msg)
assert stdout == "hi\n"
assert stderr == ""
_check_master(kc, expected=True)
@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing")
def test_capture_fd():
"""simple print statement in kernel"""
with kernel() as kc:
iopub = kc.iopub_channel
msg_id, content = execute(kc=kc, code="import os; os.system('echo capsys')")
stdout, stderr = assemble_output(iopub)
assert stdout == "capsys\n"
assert stderr == ""
_check_master(kc, expected=True)
@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing")
def test_subprocess_peek_at_stream_fileno():
""""""
with kernel() as kc:
iopub = kc.iopub_channel
msg_id, content = execute(
kc=kc,
code="import subprocess, sys; subprocess.run(['python', '-c', 'import os; os.system(\"echo CAP1\"); print(\"CAP2\")'], stderr=sys.stderr)",
)
stdout, stderr = assemble_output(iopub)
assert stdout == "CAP1\nCAP2\n"
assert stderr == ""
_check_master(kc, expected=True)
def test_sys_path():
"""test that sys.path doesn't get messed up by default"""
with kernel() as kc:
msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))")
stdout, stderr = assemble_output(kc.get_iopub_msg)
# for error-output on failure
sys.stderr.write(stderr)
sys_path = ast.literal_eval(stdout.strip())
assert "" in sys_path
def test_sys_path_profile_dir():
"""test that sys.path doesn't get messed up when `--profile-dir` is specified"""
with new_kernel(["--profile-dir", locate_profile("default")]) as kc:
msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))")
stdout, stderr = assemble_output(kc.get_iopub_msg)
# for error-output on failure
sys.stderr.write(stderr)
sys_path = ast.literal_eval(stdout.strip())
assert "" in sys_path
@flaky(max_runs=3)
@pytest.mark.skipif(
sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)),
reason="subprocess prints fail on Windows and MacOS Python 3.8+",
)
def test_subprocess_print():
"""printing from forked mp.Process"""
with new_kernel() as kc:
_check_master(kc, expected=True)
flush_channels(kc)
np = 5
code = "\n".join(
[
"import time",
"import multiprocessing as mp",
"pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np,
"for p in pool: p.start()",
"for p in pool: p.join()",
"time.sleep(0.5),",
]
)
msg_id, content = execute(kc=kc, code=code)
stdout, stderr = assemble_output(kc.get_iopub_msg)
assert stdout.count("hello") == np, stdout
for n in range(np):
assert stdout.count(str(n)) == 1, stdout
assert stderr == ""
_check_master(kc, expected=True)
_check_master(kc, expected=True, stream="stderr")
@flaky(max_runs=3)
def test_subprocess_noprint():
"""mp.Process without print doesn't trigger iostream mp_mode"""
with kernel() as kc:
np = 5
code = "\n".join(
[
"import multiprocessing as mp",
"pool = [mp.Process(target=range, args=(i,)) for i in range(%i)]" % np,
"for p in pool: p.start()",
"for p in pool: p.join()",
]
)
msg_id, content = execute(kc=kc, code=code)
stdout, stderr = assemble_output(kc.get_iopub_msg)
assert stdout == ""
assert stderr == ""
_check_master(kc, expected=True)
_check_master(kc, expected=True, stream="stderr")
@flaky(max_runs=3)
@pytest.mark.skipif(
sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)),
reason="subprocess prints fail on Windows and MacOS Python 3.8+",
)
def test_subprocess_error():
"""error in mp.Process doesn't crash"""
with new_kernel() as kc:
code = "\n".join(
[
"import multiprocessing as mp",
"p = mp.Process(target=int, args=('hi',))",
"p.start()",
"p.join()",
]
)
msg_id, content = execute(kc=kc, code=code)
stdout, stderr = assemble_output(kc.get_iopub_msg)
assert stdout == ""
assert "ValueError" in stderr
_check_master(kc, expected=True)
_check_master(kc, expected=True, stream="stderr")
# raw_input tests
def test_raw_input():
"""test input"""
with kernel() as kc:
iopub = kc.iopub_channel
input_f = "input"
theprompt = "prompt> "
code = 'print({input_f}("{theprompt}"))'.format(**locals())
msg_id = kc.execute(code, allow_stdin=True)
msg = kc.get_stdin_msg(timeout=TIMEOUT)
assert msg["header"]["msg_type"] == "input_request"
content = msg["content"]
assert content["prompt"] == theprompt
text = "some text"
kc.input(text)
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "ok"
stdout, stderr = assemble_output(kc.get_iopub_msg)
assert stdout == text + "\n"
def test_save_history():
# Saving history from the kernel with %hist -f was failing because of
# unicode problems on Python 2.
with kernel() as kc, TemporaryDirectory() as td:
file = os.path.join(td, "hist.out")
execute("a=1", kc=kc)
wait_for_idle(kc)
execute('b="abcþ"', kc=kc)
wait_for_idle(kc)
_, reply = execute("%hist -f " + file, kc=kc)
assert reply["status"] == "ok"
with open(file, encoding="utf-8") as f:
content = f.read()
assert "a=1" in content
assert 'b="abcþ"' in content
def test_smoke_faulthandler():
faulthadler = pytest.importorskip("faulthandler", reason="this test needs faulthandler")
with kernel() as kc:
# Note: faulthandler.register is not available on windows.
code = "\n".join(
[
"import sys",
"import faulthandler",
"import signal",
"faulthandler.enable()",
'if not sys.platform.startswith("win32"):',
" faulthandler.register(signal.SIGTERM)",
]
)
_, reply = execute(code, kc=kc)
assert reply["status"] == "ok", reply.get("traceback", "")
def test_help_output():
"""ipython kernel --help-all works"""
cmd = [sys.executable, "-m", "IPython", "kernel", "--help-all"]
proc = subprocess.run(cmd, timeout=30, capture_output=True)
assert proc.returncode == 0, proc.stderr
assert b"Traceback" not in proc.stderr
assert b"Options" in proc.stdout
assert b"Class" in proc.stdout
def test_is_complete():
with kernel() as kc:
# There are more test cases for this in core - here we just check
# that the kernel exposes the interface correctly.
kc.is_complete("2+2")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "complete"
# SyntaxError
kc.is_complete("raise = 2")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "invalid"
kc.is_complete("a = [1,\n2,")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "incomplete"
assert reply["content"]["indent"] == ""
# Cell magic ends on two blank lines for console UIs
kc.is_complete("%%timeit\na\n\n")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "complete"
@pytest.mark.skipif(sys.platform != "win32", reason="only run on Windows")
def test_complete():
with kernel() as kc:
execute("a = 1", kc=kc)
wait_for_idle(kc)
cell = "import IPython\nb = a."
kc.complete(cell)
reply = kc.get_shell_msg(timeout=TIMEOUT)
c = reply["content"]
assert c["status"] == "ok"
start = cell.find("a.")
end = start + 2
assert c["cursor_end"] == cell.find("a.") + 2
assert c["cursor_start"] <= end
# there are many right answers for cursor_start,
# so verify application of the completion
# rather than the value of cursor_start
matches = c["matches"]
assert matches
for m in matches:
completed = cell[: c["cursor_start"]] + m
assert completed.startswith(cell)
def test_matplotlib_inline_on_import():
pytest.importorskip("matplotlib", reason="this test requires matplotlib")
with kernel() as kc:
cell = "\n".join(
["import matplotlib, matplotlib.pyplot as plt", "backend = matplotlib.get_backend()"]
)
_, reply = execute(cell, user_expressions={"backend": "backend"}, kc=kc)
_check_status(reply)
backend_bundle = reply["user_expressions"]["backend"]
_check_status(backend_bundle)
assert "backend_inline" in backend_bundle["data"]["text/plain"]
def test_message_order():
N = 100 # number of messages to test
with kernel() as kc:
_, reply = execute("a = 1", kc=kc)
_check_status(reply)
offset = reply["execution_count"] + 1
cell = "a += 1\na"
msg_ids = []
# submit N executions as fast as we can
for _ in range(N):
msg_ids.append(kc.execute(cell))
# check message-handling order
for i, msg_id in enumerate(msg_ids, offset):
reply = kc.get_shell_msg(timeout=TIMEOUT)
_check_status(reply["content"])
assert reply["content"]["execution_count"] == i
assert reply["parent_header"]["msg_id"] == msg_id
@pytest.mark.skipif(
sys.platform.startswith("linux") or sys.platform.startswith("darwin"),
reason="test only on windows",
)
def test_unc_paths():
with kernel() as kc, TemporaryDirectory() as td:
drive_file_path = os.path.join(td, "unc.txt")
with open(drive_file_path, "w+") as f:
f.write("# UNC test")
unc_root = "\\\\localhost\\C$"
file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1]
unc_file_path = os.path.join(unc_root, file_path[1:])
kc.execute(f"cd {unc_file_path:s}")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "ok"
out, err = assemble_output(kc.get_iopub_msg)
assert unc_file_path in out
flush_channels(kc)
kc.execute(code="ls")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "ok"
out, err = assemble_output(kc.get_iopub_msg)
assert "unc.txt" in out
kc.execute(code="cd")
reply = kc.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "ok"
@pytest.mark.skipif(
platform.python_implementation() == "PyPy",
reason="does not work on PyPy",
)
def test_shutdown():
"""Kernel exits after polite shutdown_request"""
with new_kernel() as kc:
km = kc.parent
execute("a = 1", kc=kc)
wait_for_idle(kc)
kc.shutdown()
for _ in range(300): # 30s timeout
if km.is_alive():
time.sleep(0.1)
else:
break
assert not km.is_alive()
def test_interrupt_during_input():
"""
The kernel exits after being interrupted while waiting in input().
input() appears to have issues other functions don't, and it needs to be
interruptible in order for pdb to be interruptible.
"""
with new_kernel() as kc:
km = kc.parent
msg_id = kc.execute("input()")
time.sleep(1) # Make sure it's actually waiting for input.
km.interrupt_kernel()
from .test_message_spec import validate_message
# If we failed to interrupt interrupt, this will timeout:
reply = get_reply(kc, msg_id, TIMEOUT)
validate_message(reply, "execute_reply", msg_id)
@pytest.mark.skipif(os.name == "nt", reason="Message based interrupt not supported on Windows")
def test_interrupt_with_message():
""" """
with new_kernel() as kc:
km = kc.parent
km.kernel_spec.interrupt_mode = "message"
msg_id = kc.execute("input()")
time.sleep(1) # Make sure it's actually waiting for input.
km.interrupt_kernel()
from .test_message_spec import validate_message
# If we failed to interrupt interrupt, this will timeout:
reply = get_reply(kc, msg_id, TIMEOUT)
validate_message(reply, "execute_reply", msg_id)
@pytest.mark.skipif(
"__pypy__" in sys.builtin_module_names,
reason="fails on pypy",
)
def test_interrupt_during_pdb_set_trace():
"""
The kernel exits after being interrupted while waiting in pdb.set_trace().
Merely testing input() isn't enough, pdb has its own issues that need
to be handled in addition.
This test will fail with versions of IPython < 7.14.0.
"""
with new_kernel() as kc:
km = kc.parent
msg_id = kc.execute("import pdb; pdb.set_trace()")
msg_id2 = kc.execute("3 + 4")
time.sleep(1) # Make sure it's actually waiting for input.
km.interrupt_kernel()
from .test_message_spec import validate_message
# If we failed to interrupt interrupt, this will timeout:
reply = get_reply(kc, msg_id, TIMEOUT)
validate_message(reply, "execute_reply", msg_id)
# If we failed to interrupt interrupt, this will timeout:
reply = get_reply(kc, msg_id2, TIMEOUT)
validate_message(reply, "execute_reply", msg_id2)
def test_control_thread_priority():
N = 5
with new_kernel() as kc:
msg_id = kc.execute("pass")
get_reply(kc, msg_id)
sleep_msg_id = kc.execute("import asyncio; await asyncio.sleep(2)")
# submit N shell messages
shell_msg_ids = []
for i in range(N):
shell_msg_ids.append(kc.execute(f"i = {i}"))
# ensure all shell messages have arrived at the kernel before any control messages
time.sleep(0.5)
# at this point, shell messages should be waiting in msg_queue,
# rather than zmq while the kernel is still in the middle of processing
# the first execution
# now send N control messages
control_msg_ids = []
for _ in range(N):
msg = kc.session.msg("kernel_info_request", {})
kc.control_channel.send(msg)
control_msg_ids.append(msg["header"]["msg_id"])
# finally, collect the replies on both channels for comparison
get_reply(kc, sleep_msg_id)
shell_replies = []
for msg_id in shell_msg_ids:
shell_replies.append(get_reply(kc, msg_id))
control_replies = []
for msg_id in control_msg_ids:
control_replies.append(get_reply(kc, msg_id, channel="control"))
# verify that all control messages were handled before all shell messages
shell_dates = [msg["header"]["date"] for msg in shell_replies]
control_dates = [msg["header"]["date"] for msg in control_replies]
# comparing first to last ought to be enough, since queues preserve order
# use <= in case of very-fast handling and/or low resolution timers
assert control_dates[-1] <= shell_dates[0]
def _child():
print("in child", os.getpid())
def _print_and_exit(sig, frame):
print(f"Received signal {sig}")
# take some time so retries are triggered
time.sleep(0.5)
sys.exit(-sig)
signal.signal(signal.SIGTERM, _print_and_exit)
time.sleep(30)
def _start_children():
ip = IPython.get_ipython()
ns = ip.user_ns
cmd = [sys.executable, "-c", f"from {__name__} import _child; _child()"]
child_pg = Popen(cmd, start_new_session=False)
child_newpg = Popen(cmd, start_new_session=True)
ns["pid"] = os.getpid()
ns["child_pg"] = child_pg.pid
ns["child_newpg"] = child_newpg.pid
# give them time to start up and register signal handlers
time.sleep(1)
@pytest.mark.skipif(
platform.python_implementation() == "PyPy",
reason="does not work on PyPy",
)
def test_shutdown_subprocesses():
"""Kernel exits after polite shutdown_request"""
with new_kernel() as kc:
km = kc.parent
msg_id, reply = execute(
f"from {__name__} import _start_children\n_start_children()",
kc=kc,
user_expressions={
"pid": "pid",
"child_pg": "child_pg",
"child_newpg": "child_newpg",
},
)
print(reply)
expressions = reply["user_expressions"]
kernel_process = psutil.Process(int(expressions["pid"]["data"]["text/plain"]))
child_pg = psutil.Process(int(expressions["child_pg"]["data"]["text/plain"]))
child_newpg = psutil.Process(int(expressions["child_newpg"]["data"]["text/plain"]))
wait_for_idle(kc)
kc.shutdown()
for _ in range(300): # 30s timeout
if km.is_alive():
time.sleep(0.1)
else:
break
assert not km.is_alive()
assert not kernel_process.is_running()
# child in the process group shut down
assert not child_pg.is_running()
# child outside the process group was not shut down (unix only)
if os.name != "nt":
assert child_newpg.is_running()
try:
child_newpg.terminate()
except psutil.NoSuchProcess:
pass

View File

@ -0,0 +1,147 @@
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
import os
import shutil
import sys
import tempfile
from unittest import mock
import pytest
from jupyter_core.paths import jupyter_data_dir
from ipykernel.kernelspec import (
KERNEL_NAME,
RESOURCES,
InstallIPythonKernelSpecApp,
get_kernel_dict,
install,
make_ipkernel_cmd,
write_kernel_spec,
)
pjoin = os.path.join
def test_make_ipkernel_cmd():
cmd = make_ipkernel_cmd()
assert cmd == [sys.executable, "-m", "ipykernel_launcher", "-f", "{connection_file}"]
def assert_kernel_dict(d):
assert d["argv"] == make_ipkernel_cmd()
assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0]
assert d["language"] == "python"
def test_get_kernel_dict():
d = get_kernel_dict()
assert_kernel_dict(d)
def assert_kernel_dict_with_profile(d):
assert d["argv"] == make_ipkernel_cmd(extra_arguments=["--profile", "test"])
assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0]
assert d["language"] == "python"
def test_get_kernel_dict_with_profile():
d = get_kernel_dict(["--profile", "test"])
assert_kernel_dict_with_profile(d)
def assert_is_spec(path):
for fname in os.listdir(RESOURCES):
dst = pjoin(path, fname)
assert os.path.exists(dst)
kernel_json = pjoin(path, "kernel.json")
assert os.path.exists(kernel_json)
with open(kernel_json, encoding="utf8") as f:
json.load(f)
def test_write_kernel_spec():
path = write_kernel_spec()
assert_is_spec(path)
shutil.rmtree(path)
def test_write_kernel_spec_path():
path = os.path.join(tempfile.mkdtemp(), KERNEL_NAME)
path2 = write_kernel_spec(path)
assert path == path2
assert_is_spec(path)
shutil.rmtree(path)
def test_install_kernelspec():
path = tempfile.mkdtemp()
try:
InstallIPythonKernelSpecApp.launch_instance(argv=["--prefix", path])
assert_is_spec(os.path.join(path, "share", "jupyter", "kernels", KERNEL_NAME))
finally:
shutil.rmtree(path)
def test_install_user():
tmp = tempfile.mkdtemp()
with mock.patch.dict(os.environ, {"HOME": tmp}):
install(user=True)
data_dir = jupyter_data_dir()
assert_is_spec(os.path.join(data_dir, "kernels", KERNEL_NAME))
def test_install():
system_jupyter_dir = tempfile.mkdtemp()
with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]):
install()
assert_is_spec(os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME))
def test_install_profile():
system_jupyter_dir = tempfile.mkdtemp()
with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]):
install(profile="Test")
spec = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json")
with open(spec) as f:
spec = json.load(f)
assert spec["display_name"].endswith(" [profile=Test]")
assert spec["argv"][-2:] == ["--profile", "Test"]
def test_install_display_name_overrides_profile():
system_jupyter_dir = tempfile.mkdtemp()
with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]):
install(display_name="Display", profile="Test")
spec = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json")
with open(spec) as f:
spec = json.load(f)
assert spec["display_name"] == "Display"
@pytest.mark.parametrize("env", [None, dict(spam="spam"), dict(spam="spam", foo="bar")])
def test_install_env(tmp_path, env):
# python 3.5 // tmp_path must be converted to str
with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [str(tmp_path)]):
install(env=env)
spec = tmp_path / "kernels" / KERNEL_NAME / "kernel.json"
with spec.open() as f:
spec = json.load(f)
if env:
assert len(env) == len(spec["env"])
for k, v in env.items():
assert spec["env"][k] == v
else:
assert "env" not in spec

View File

@ -0,0 +1,620 @@
"""Test suite for our zeromq-based message specification."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import re
import sys
from queue import Empty
import jupyter_client
import pytest
from packaging.version import Version as V
from traitlets import (
Bool,
Dict,
Enum,
HasTraits,
Integer,
List,
TraitError,
Unicode,
observe,
)
from .utils import TIMEOUT, execute, flush_channels, get_reply, start_global_kernel
# -----------------------------------------------------------------------------
# Globals
# -----------------------------------------------------------------------------
KC = None
def setup():
global KC
KC = start_global_kernel()
# -----------------------------------------------------------------------------
# Message Spec References
# -----------------------------------------------------------------------------
class Reference(HasTraits):
"""
Base class for message spec specification testing.
This class is the core of the message specification test. The
idea is that child classes implement trait attributes for each
message keys, so that message keys can be tested against these
traits using :meth:`check` method.
"""
def check(self, d):
"""validate a dict against our traits"""
for key in self.trait_names():
assert key in d
# FIXME: always allow None, probably not a good idea
if d[key] is None:
continue
try:
setattr(self, key, d[key])
except TraitError as e:
assert False, str(e)
class Version(Unicode):
def __init__(self, *args, **kwargs):
self.min = kwargs.pop("min", None)
self.max = kwargs.pop("max", None)
kwargs["default_value"] = self.min
super().__init__(*args, **kwargs)
def validate(self, obj, value):
if self.min and V(value) < V(self.min):
raise TraitError("bad version: %s < %s" % (value, self.min))
if self.max and (V(value) > V(self.max)):
raise TraitError("bad version: %s > %s" % (value, self.max))
class RMessage(Reference):
msg_id = Unicode()
msg_type = Unicode()
header = Dict()
parent_header = Dict()
content = Dict()
def check(self, d):
super().check(d)
RHeader().check(self.header)
if self.parent_header:
RHeader().check(self.parent_header)
class RHeader(Reference):
msg_id = Unicode()
msg_type = Unicode()
session = Unicode()
username = Unicode()
version = Version(min="5.0")
mime_pat = re.compile(r"^[\w\-\+\.]+/[\w\-\+\.]+$")
class MimeBundle(Reference):
metadata = Dict()
data = Dict()
@observe("data")
def _on_data_changed(self, change):
for k, v in change["new"].items():
assert mime_pat.match(k)
assert isinstance(v, str)
# shell replies
class Reply(Reference):
status = Enum(("ok", "error"), default_value="ok")
class ExecuteReply(Reply):
execution_count = Integer()
def check(self, d):
Reference.check(self, d)
if d["status"] == "ok":
ExecuteReplyOkay().check(d)
elif d["status"] == "error":
ExecuteReplyError().check(d)
elif d["status"] == "aborted":
ExecuteReplyAborted().check(d)
class ExecuteReplyOkay(Reply):
status = Enum(("ok",))
user_expressions = Dict()
class ExecuteReplyError(Reply):
status = Enum(("error",))
ename = Unicode()
evalue = Unicode()
traceback = List(Unicode())
class ExecuteReplyAborted(Reply):
status = Enum(("aborted",))
class InspectReply(Reply, MimeBundle):
found = Bool()
class ArgSpec(Reference):
args = List(Unicode())
varargs = Unicode()
varkw = Unicode()
defaults = List()
class Status(Reference):
execution_state = Enum(("busy", "idle", "starting"), default_value="busy")
class CompleteReply(Reply):
matches = List(Unicode())
cursor_start = Integer()
cursor_end = Integer()
status = Unicode()
class LanguageInfo(Reference):
name = Unicode("python")
version = Unicode(sys.version.split()[0])
class KernelInfoReply(Reply):
protocol_version = Version(min="5.0")
implementation = Unicode("ipython")
implementation_version = Version(min="2.1")
language_info = Dict()
banner = Unicode()
def check(self, d):
Reference.check(self, d)
LanguageInfo().check(d["language_info"])
class ConnectReply(Reference):
shell_port = Integer()
control_port = Integer()
stdin_port = Integer()
iopub_port = Integer()
hb_port = Integer()
class CommInfoReply(Reply):
comms = Dict()
class IsCompleteReply(Reference):
status = Enum(("complete", "incomplete", "invalid", "unknown"), default_value="complete")
def check(self, d):
Reference.check(self, d)
if d["status"] == "incomplete":
IsCompleteReplyIncomplete().check(d)
class IsCompleteReplyIncomplete(Reference):
indent = Unicode()
# IOPub messages
class ExecuteInput(Reference):
code = Unicode()
execution_count = Integer()
class Error(ExecuteReplyError):
"""Errors are the same as ExecuteReply, but without status"""
status = None # no status field
class Stream(Reference):
name = Enum(("stdout", "stderr"), default_value="stdout")
text = Unicode()
class DisplayData(MimeBundle):
pass
class ExecuteResult(MimeBundle):
execution_count = Integer()
class HistoryReply(Reply):
history = List(List())
references = {
"execute_reply": ExecuteReply(),
"inspect_reply": InspectReply(),
"status": Status(),
"complete_reply": CompleteReply(),
"kernel_info_reply": KernelInfoReply(),
"connect_reply": ConnectReply(),
"comm_info_reply": CommInfoReply(),
"is_complete_reply": IsCompleteReply(),
"execute_input": ExecuteInput(),
"execute_result": ExecuteResult(),
"history_reply": HistoryReply(),
"error": Error(),
"stream": Stream(),
"display_data": DisplayData(),
"header": RHeader(),
}
"""
Specifications of `content` part of the reply messages.
"""
def validate_message(msg, msg_type=None, parent=None):
"""validate a message
This is a generator, and must be iterated through to actually
trigger each test.
If msg_type and/or parent are given, the msg_type and/or parent msg_id
are compared with the given values.
"""
RMessage().check(msg)
if msg_type:
assert msg["msg_type"] == msg_type
if parent:
assert msg["parent_header"]["msg_id"] == parent
content = msg["content"]
ref = references[msg["msg_type"]]
ref.check(content)
# -----------------------------------------------------------------------------
# Tests
# -----------------------------------------------------------------------------
# Shell channel
def test_execute():
flush_channels()
msg_id = KC.execute(code="x=1")
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "execute_reply", msg_id)
def test_execute_silent():
flush_channels()
msg_id, reply = execute(code="x=1", silent=True)
# flush status=idle
status = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(status, "status", msg_id)
assert status["content"]["execution_state"] == "idle"
with pytest.raises(Empty):
KC.get_iopub_msg(timeout=0.1)
count = reply["execution_count"]
msg_id, reply = execute(code="x=2", silent=True)
# flush status=idle
status = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(status, "status", msg_id)
assert status["content"]["execution_state"] == "idle"
with pytest.raises(Empty):
KC.get_iopub_msg(timeout=0.1)
count_2 = reply["execution_count"]
assert count_2 == count
def test_execute_error():
flush_channels()
msg_id, reply = execute(code="1/0")
assert reply["status"] == "error"
assert reply["ename"] == "ZeroDivisionError"
error = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(error, "error", msg_id)
def test_execute_inc():
"""execute request should increment execution_count"""
flush_channels()
_, reply = execute(code="x=1")
count = reply["execution_count"]
flush_channels()
_, reply = execute(code="x=2")
count_2 = reply["execution_count"]
assert count_2 == count + 1
def test_execute_stop_on_error():
"""execute request should not abort execution queue with stop_on_error False"""
flush_channels()
fail = "\n".join(
[
# sleep to ensure subsequent message is waiting in the queue to be aborted
# async sleep to ensure coroutines are processing while this happens
"import asyncio",
"await asyncio.sleep(1)",
"raise ValueError()",
]
)
KC.execute(code=fail)
KC.execute(code='print("Hello")')
KC.execute(code='print("world")')
reply = KC.get_shell_msg(timeout=TIMEOUT)
print(reply)
reply = KC.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "aborted"
# second message, too
reply = KC.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "aborted"
flush_channels()
KC.execute(code=fail, stop_on_error=False)
KC.execute(code='print("Hello")')
KC.get_shell_msg(timeout=TIMEOUT)
reply = KC.get_shell_msg(timeout=TIMEOUT)
assert reply["content"]["status"] == "ok"
def test_non_execute_stop_on_error():
"""test that non-execute_request's are not aborted after an error"""
flush_channels()
fail = "\n".join(
[
# sleep to ensure subsequent message is waiting in the queue to be aborted
"import time",
"time.sleep(0.5)",
"raise ValueError",
]
)
KC.execute(code=fail)
KC.kernel_info()
KC.comm_info()
KC.inspect(code="print")
reply = KC.get_shell_msg(timeout=TIMEOUT) # execute
assert reply["content"]["status"] == "error"
reply = KC.get_shell_msg(timeout=TIMEOUT) # kernel_info
assert reply["content"]["status"] == "ok"
reply = KC.get_shell_msg(timeout=TIMEOUT) # comm_info
assert reply["content"]["status"] == "ok"
reply = KC.get_shell_msg(timeout=TIMEOUT) # inspect
assert reply["content"]["status"] == "ok"
def test_user_expressions():
flush_channels()
msg_id, reply = execute(code="x=1", user_expressions=dict(foo="x+1"))
user_expressions = reply["user_expressions"]
assert user_expressions == {
"foo": {
"status": "ok",
"data": {"text/plain": "2"},
"metadata": {},
}
}
def test_user_expressions_fail():
flush_channels()
msg_id, reply = execute(code="x=0", user_expressions=dict(foo="nosuchname"))
user_expressions = reply["user_expressions"]
foo = user_expressions["foo"]
assert foo["status"] == "error"
assert foo["ename"] == "NameError"
def test_oinfo():
flush_channels()
msg_id = KC.inspect("a")
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "inspect_reply", msg_id)
def test_oinfo_found():
flush_channels()
msg_id, reply = execute(code="a=5")
msg_id = KC.inspect("a")
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "inspect_reply", msg_id)
content = reply["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "Type:" in text
assert "Docstring:" in text
def test_oinfo_detail():
flush_channels()
msg_id, reply = execute(code="ip=get_ipython()")
msg_id = KC.inspect("ip.object_inspect", cursor_pos=10, detail_level=1)
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "inspect_reply", msg_id)
content = reply["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "Signature:" in text
assert "Source:" in text
def test_oinfo_not_found():
flush_channels()
msg_id = KC.inspect("dne")
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "inspect_reply", msg_id)
content = reply["content"]
assert not content["found"]
def test_complete():
flush_channels()
msg_id, reply = execute(code="alpha = albert = 5")
msg_id = KC.complete("al", 2)
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "complete_reply", msg_id)
matches = reply["content"]["matches"]
for name in ("alpha", "albert"):
assert name in matches
def test_kernel_info_request():
flush_channels()
msg_id = KC.kernel_info()
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "kernel_info_reply", msg_id)
def test_connect_request():
flush_channels()
msg = KC.session.msg("connect_request")
KC.shell_channel.send(msg)
return msg["header"]["msg_id"]
msg_id = KC.kernel_info()
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "connect_reply", msg_id)
@pytest.mark.skipif(
jupyter_client.version_info < (5, 0),
reason="earlier Jupyter Client don't have comm_info",
)
def test_comm_info_request():
flush_channels()
msg_id = KC.comm_info()
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "comm_info_reply", msg_id)
def test_single_payload():
"""
We want to test the set_next_input is not triggered several time per cell.
This is (was ?) mostly due to the fact that `?` in a loop would trigger
several set_next_input.
I'm tempted to thing that we actually want to _allow_ multiple
set_next_input (that's users' choice). But that `?` itself (and ?'s
transform) should avoid setting multiple set_next_input).
"""
flush_channels()
msg_id, reply = execute(
code="ip = get_ipython()\nfor i in range(3):\n ip.set_next_input('Hello There')\n"
)
payload = reply["payload"]
next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
assert len(next_input_pls) == 1
def test_is_complete():
flush_channels()
msg_id = KC.is_complete("a = 1")
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "is_complete_reply", msg_id)
def test_history_range():
flush_channels()
KC.execute(code="x=1", store_history=True)
KC.get_shell_msg(timeout=TIMEOUT)
msg_id = KC.history(hist_access_type="range", raw=True, output=True, start=1, stop=2, session=0)
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "history_reply", msg_id)
content = reply["content"]
assert len(content["history"]) == 1
def test_history_tail():
flush_channels()
KC.execute(code="x=1", store_history=True)
KC.get_shell_msg(timeout=TIMEOUT)
msg_id = KC.history(hist_access_type="tail", raw=True, output=True, n=1, session=0)
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "history_reply", msg_id)
content = reply["content"]
assert len(content["history"]) == 1
def test_history_search():
flush_channels()
KC.execute(code="x=1", store_history=True)
KC.get_shell_msg(timeout=TIMEOUT)
msg_id = KC.history(
hist_access_type="search", raw=True, output=True, n=1, pattern="*", session=0
)
reply = get_reply(KC, msg_id, TIMEOUT)
validate_message(reply, "history_reply", msg_id)
content = reply["content"]
assert len(content["history"]) == 1
# IOPub channel
def test_stream():
flush_channels()
msg_id, reply = execute("print('hi')")
stdout = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(stdout, "stream", msg_id)
content = stdout["content"]
assert content["text"] == "hi\n"
def test_display_data():
flush_channels()
msg_id, reply = execute("from IPython.display import display; display(1)")
display = KC.get_iopub_msg(timeout=TIMEOUT)
validate_message(display, "display_data", parent=msg_id)
data = display["content"]["data"]
assert data["text/plain"] == "1"

View File

@ -0,0 +1,78 @@
import pickle
import warnings
with warnings.catch_warnings():
warnings.simplefilter("ignore")
from ipykernel.pickleutil import can, uncan
def interactive(f):
f.__module__ = "__main__"
return f
def dumps(obj):
return pickle.dumps(can(obj))
def loads(obj):
return uncan(pickle.loads(obj))
def test_no_closure():
@interactive
def foo():
a = 5
return a
pfoo = dumps(foo)
bar = loads(pfoo)
assert foo() == bar()
def test_generator_closure():
# this only creates a closure on Python 3
@interactive
def foo():
i = "i"
r = [i for j in (1, 2)]
return r
pfoo = dumps(foo)
bar = loads(pfoo)
assert foo() == bar()
def test_nested_closure():
@interactive
def foo():
i = "i"
def g():
return i
return g()
pfoo = dumps(foo)
bar = loads(pfoo)
assert foo() == bar()
def test_closure():
i = "i"
@interactive
def foo():
return i
pfoo = dumps(foo)
bar = loads(pfoo)
assert foo() == bar()
def test_uncan_bytes_buffer():
data = b"data"
canned = can(data)
canned.buffers = [memoryview(buf) for buf in canned.buffers]
out = uncan(canned)
assert out == data

View File

@ -0,0 +1,62 @@
from textwrap import dedent
from flaky import flaky
from .test_embed_kernel import setup_kernel
TIMEOUT = 15
@flaky(max_runs=3)
def test_ipython_start_kernel_userns():
cmd = dedent(
"""
from ipykernel.kernelapp import launch_new_instance
ns = {"tre": 123}
launch_new_instance(user_ns=ns)
"""
)
with setup_kernel(cmd) as client:
client.inspect("tre")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "123" in text
# user_module should be an instance of DummyMod
client.execute("usermod = get_ipython().user_module")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["status"] == "ok"
client.inspect("usermod")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "DummyMod" in text
@flaky(max_runs=3)
def test_ipython_start_kernel_no_userns():
# Issue #4188 - user_ns should be passed to shell as None, not {}
cmd = dedent(
"""
from ipykernel.kernelapp import launch_new_instance
launch_new_instance()
"""
)
with setup_kernel(cmd) as client:
# user_module should not be an instance of DummyMod
client.execute("usermod = get_ipython().user_module")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["status"] == "ok"
client.inspect("usermod")
msg = client.get_shell_msg(timeout=TIMEOUT)
content = msg["content"]
assert content["found"]
text = content["data"]["text/plain"]
assert "DummyMod" not in text

View File

@ -0,0 +1,205 @@
""" Tests for zmq shell / display publisher. """
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import unittest
from queue import Queue
from threading import Thread
import zmq
from jupyter_client.session import Session
from traitlets import Int
from ipykernel.zmqshell import ZMQDisplayPublisher
class NoReturnDisplayHook:
"""
A dummy DisplayHook which allows us to monitor
the number of times an object is called, but which
does *not* return a message when it is called.
"""
call_count = 0
def __call__(self, obj):
self.call_count += 1
class ReturnDisplayHook(NoReturnDisplayHook):
"""
A dummy DisplayHook with the same counting ability
as its base class, but which also returns the same
message when it is called.
"""
def __call__(self, obj):
super().__call__(obj)
return obj
class CounterSession(Session):
"""
This is a simple subclass to allow us to count
the calls made to the session object by the display
publisher.
"""
send_count = Int(0)
def send(self, *args, **kwargs):
"""
A trivial override to just augment the existing call
with an increment to the send counter.
"""
self.send_count += 1
super().send(*args, **kwargs)
class ZMQDisplayPublisherTests(unittest.TestCase):
"""
Tests the ZMQDisplayPublisher in zmqshell.py
"""
def setUp(self):
self.context = zmq.Context()
self.socket = self.context.socket(zmq.PUB)
self.session = CounterSession()
self.disp_pub = ZMQDisplayPublisher(session=self.session, pub_socket=self.socket)
def tearDown(self):
"""
We need to close the socket in order to proceed with the
tests.
TODO - There is still an open file handler to '/dev/null',
presumably created by zmq.
"""
self.disp_pub.clear_output()
self.socket.close()
self.context.term()
def test_display_publisher_creation(self):
"""
Since there's no explicit constructor, here we confirm
that keyword args get assigned correctly, and override
the defaults.
"""
assert self.disp_pub.session == self.session
assert self.disp_pub.pub_socket == self.socket
def test_thread_local_hooks(self):
"""
Confirms that the thread_local attribute is correctly
initialised with an empty list for the display hooks
"""
assert self.disp_pub._hooks == []
def hook(msg):
return msg
self.disp_pub.register_hook(hook)
assert self.disp_pub._hooks == [hook]
q = Queue()
def set_thread_hooks():
q.put(self.disp_pub._hooks)
t = Thread(target=set_thread_hooks)
t.start()
thread_hooks = q.get(timeout=10)
assert thread_hooks == []
def test_publish(self):
"""
Publish should prepare the message and eventually call
`send` by default.
"""
data = dict(a=1)
assert self.session.send_count == 0
self.disp_pub.publish(data)
assert self.session.send_count == 1
def test_display_hook_halts_send(self):
"""
If a hook is installed, and on calling the object
it does *not* return a message, then we assume that
the message has been consumed, and should not be
processed (`sent`) in the normal manner.
"""
data = dict(a=1)
hook = NoReturnDisplayHook()
self.disp_pub.register_hook(hook)
assert hook.call_count == 0
assert self.session.send_count == 0
self.disp_pub.publish(data)
assert hook.call_count == 1
assert self.session.send_count == 0
def test_display_hook_return_calls_send(self):
"""
If a hook is installed and on calling the object
it returns a new message, then we assume that this
is just a message transformation, and the message
should be sent in the usual manner.
"""
data = dict(a=1)
hook = ReturnDisplayHook()
self.disp_pub.register_hook(hook)
assert hook.call_count == 0
assert self.session.send_count == 0
self.disp_pub.publish(data)
assert hook.call_count == 1
assert self.session.send_count == 1
def test_unregister_hook(self):
"""
Once a hook is unregistered, it should not be called
during `publish`.
"""
data = dict(a=1)
hook = NoReturnDisplayHook()
self.disp_pub.register_hook(hook)
assert hook.call_count == 0
assert self.session.send_count == 0
self.disp_pub.publish(data)
assert hook.call_count == 1
assert self.session.send_count == 0
#
# After unregistering the `NoReturn` hook, any calls
# to publish should *not* got through the DisplayHook,
# but should instead hit the usual `session.send` call
# at the end.
#
# As a result, the hook call count should *not* increase,
# but the session send count *should* increase.
#
first = self.disp_pub.unregister_hook(hook)
self.disp_pub.publish(data)
self.assertTrue(first)
assert hook.call_count == 1
assert self.session.send_count == 1
#
# If a hook is not installed, `unregister_hook`
# should return false.
#
second = self.disp_pub.unregister_hook(hook)
self.assertFalse(second)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,213 @@
"""utilities for testing IPython kernels"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import atexit
import os
import sys
from contextlib import contextmanager
from queue import Empty
from subprocess import STDOUT
from tempfile import TemporaryDirectory
from time import time
from jupyter_client import manager
STARTUP_TIMEOUT = 60
TIMEOUT = 100
KM = None
KC = None
def start_new_kernel(**kwargs):
"""start a new kernel, and return its Manager and Client
Integrates with our output capturing for tests.
"""
kwargs["stderr"] = STDOUT
try:
import nose
kwargs["stdout"] = nose.iptest_stdstreams_fileno()
except (ImportError, AttributeError):
pass
return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs)
def flush_channels(kc=None):
"""flush any messages waiting on the queue"""
from .test_message_spec import validate_message
if kc is None:
kc = KC
for get_msg in (kc.get_shell_msg, kc.get_iopub_msg):
while True:
try:
msg = get_msg(timeout=0.1)
except Empty:
break
else:
validate_message(msg)
def get_reply(kc, msg_id, timeout=TIMEOUT, channel="shell"):
t0 = time()
while True:
get_msg = getattr(kc, f"get_{channel}_msg")
reply = get_msg(timeout=timeout)
if reply["parent_header"]["msg_id"] == msg_id:
break
# Allow debugging ignored replies
print(f"Ignoring reply not to {msg_id}: {reply}")
t1 = time()
timeout -= t1 - t0
t0 = t1
return reply
def execute(code="", kc=None, **kwargs):
"""wrapper for doing common steps for validating an execution request"""
from .test_message_spec import validate_message
if kc is None:
kc = KC
msg_id = kc.execute(code=code, **kwargs)
reply = get_reply(kc, msg_id, TIMEOUT)
validate_message(reply, "execute_reply", msg_id)
busy = kc.get_iopub_msg(timeout=TIMEOUT)
validate_message(busy, "status", msg_id)
assert busy["content"]["execution_state"] == "busy"
if not kwargs.get("silent"):
execute_input = kc.get_iopub_msg(timeout=TIMEOUT)
validate_message(execute_input, "execute_input", msg_id)
assert execute_input["content"]["code"] == code
# show tracebacks if present for debugging
if reply["content"].get("traceback"):
print("\n".join(reply["content"]["traceback"]), file=sys.stderr)
return msg_id, reply["content"]
def start_global_kernel():
"""start the global kernel (if it isn't running) and return its client"""
global KM, KC
if KM is None:
KM, KC = start_new_kernel()
atexit.register(stop_global_kernel)
else:
flush_channels(KC)
return KC
@contextmanager
def kernel():
"""Context manager for the global kernel instance
Should be used for most kernel tests
Returns
-------
kernel_client: connected KernelClient instance
"""
yield start_global_kernel()
def uses_kernel(test_f):
"""Decorator for tests that use the global kernel"""
def wrapped_test():
with kernel() as kc:
test_f(kc)
wrapped_test.__doc__ = test_f.__doc__
wrapped_test.__name__ = test_f.__name__
return wrapped_test
def stop_global_kernel():
"""Stop the global shared kernel instance, if it exists"""
global KM, KC
KC.stop_channels()
KC = None
if KM is None:
return
KM.shutdown_kernel(now=True)
KM = None
def new_kernel(argv=None):
"""Context manager for a new kernel in a subprocess
Should only be used for tests where the kernel must not be re-used.
Returns
-------
kernel_client: connected KernelClient instance
"""
kwargs = {"stderr": STDOUT}
try:
import nose
kwargs["stdout"] = nose.iptest_stdstreams_fileno()
except (ImportError, AttributeError):
pass
if argv is not None:
kwargs["extra_arguments"] = argv
return manager.run_kernel(**kwargs)
def assemble_output(get_msg):
"""assemble stdout/err from an execution"""
stdout = ""
stderr = ""
while True:
msg = get_msg(timeout=1)
msg_type = msg["msg_type"]
content = msg["content"]
if msg_type == "status" and content["execution_state"] == "idle":
# idle message signals end of output
break
elif msg["msg_type"] == "stream":
if content["name"] == "stdout":
stdout += content["text"]
elif content["name"] == "stderr":
stderr += content["text"]
else:
raise KeyError("bad stream: %r" % content["name"])
else:
# other output, ignored
pass
return stdout, stderr
def wait_for_idle(kc):
while True:
msg = kc.get_iopub_msg(timeout=1)
msg_type = msg["msg_type"]
content = msg["content"]
if msg_type == "status" and content["execution_state"] == "idle":
break
class TemporaryWorkingDirectory(TemporaryDirectory):
"""
Creates a temporary directory and sets the cwd to that directory.
Automatically reverts to previous cwd upon cleanup.
Usage example:
with TemporaryWorkingDirectory() as tmpdir:
...
"""
def __enter__(self):
self.old_wd = os.getcwd()
os.chdir(self.name)
return super().__enter__()
def __exit__(self, exc, value, tb):
os.chdir(self.old_wd)
return super().__exit__(exc, value, tb)