test_message_spec.py
446 lines
| 11.7 KiB
| text/x-python
|
PythonLexer
MinRK
|
r12414 | """Test suite for our zeromq-based message specification. | ||
Fernando Perez
|
r2926 | """ | ||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r12414 | # Copyright (C) 2010 The IPython Development Team | ||
Fernando Perez
|
r2926 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING.txt, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r6554 | import re | ||
from subprocess import PIPE | ||||
Thomas Kluyver
|
r13354 | try: | ||
from queue import Empty # Py 3 | ||||
except ImportError: | ||||
from Queue import Empty # Py 2 | ||||
Fernando Perez
|
r2926 | |||
import nose.tools as nt | ||||
MinRK
|
r10385 | from IPython.kernel import KernelManager | ||
Fernando Perez
|
r2926 | |||
MinRK
|
r6554 | from IPython.utils.traitlets import ( | ||
Takafumi Arakaki
|
r8865 | HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any, | ||
MinRK
|
r6554 | ) | ||
Thomas Kluyver
|
r13361 | from IPython.utils.py3compat import string_types, iteritems | ||
MinRK
|
r6554 | |||
MinRK
|
r12414 | from .utils import TIMEOUT, start_global_kernel, flush_channels, execute | ||
MinRK
|
r6554 | #----------------------------------------------------------------------------- | ||
MinRK
|
r12414 | # Globals | ||
MinRK
|
r6554 | #----------------------------------------------------------------------------- | ||
MinRK
|
r12414 | KC = None | ||
MinRK
|
r11802 | |||
Fernando Perez
|
r2926 | def setup(): | ||
MinRK
|
r12414 | global KC | ||
KC = start_global_kernel() | ||||
MinRK
|
r6554 | |||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r12414 | # Message Spec References | ||
MinRK
|
r6554 | #----------------------------------------------------------------------------- | ||
class Reference(HasTraits): | ||||
Takafumi Arakaki
|
r8829 | |||
""" | ||||
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. | ||||
""" | ||||
MinRK
|
r6554 | def check(self, d): | ||
"""validate a dict against our traits""" | ||||
for key in self.trait_names(): | ||||
Thomas Kluyver
|
r12374 | nt.assert_in(key, d) | ||
MinRK
|
r6554 | # 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: | ||||
Thomas Kluyver
|
r12374 | nt.assert_true(False, str(e)) | ||
MinRK
|
r6554 | |||
class RMessage(Reference): | ||||
msg_id = Unicode() | ||||
msg_type = Unicode() | ||||
header = Dict() | ||||
parent_header = Dict() | ||||
content = Dict() | ||||
class RHeader(Reference): | ||||
msg_id = Unicode() | ||||
msg_type = Unicode() | ||||
session = Unicode() | ||||
username = Unicode() | ||||
class RContent(Reference): | ||||
status = Enum((u'ok', u'error')) | ||||
class ExecuteReply(Reference): | ||||
execution_count = Integer() | ||||
status = Enum((u'ok', u'error')) | ||||
def check(self, d): | ||||
Thomas Kluyver
|
r12374 | Reference.check(self, d) | ||
MinRK
|
r6554 | if d['status'] == 'ok': | ||
Thomas Kluyver
|
r12374 | ExecuteReplyOkay().check(d) | ||
MinRK
|
r6554 | elif d['status'] == 'error': | ||
Thomas Kluyver
|
r12374 | ExecuteReplyError().check(d) | ||
MinRK
|
r6554 | |||
Fernando Perez
|
r2926 | |||
MinRK
|
r6554 | class ExecuteReplyOkay(Reference): | ||
payload = List(Dict) | ||||
user_variables = Dict() | ||||
user_expressions = Dict() | ||||
Fernando Perez
|
r2926 | |||
MinRK
|
r6554 | |||
class ExecuteReplyError(Reference): | ||||
ename = Unicode() | ||||
evalue = Unicode() | ||||
traceback = List(Unicode) | ||||
class OInfoReply(Reference): | ||||
name = Unicode() | ||||
found = Bool() | ||||
ismagic = Bool() | ||||
isalias = Bool() | ||||
namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive')) | ||||
type_name = Unicode() | ||||
string_form = Unicode() | ||||
base_class = Unicode() | ||||
length = Integer() | ||||
file = Unicode() | ||||
definition = Unicode() | ||||
argspec = Dict() | ||||
init_definition = Unicode() | ||||
docstring = Unicode() | ||||
init_docstring = Unicode() | ||||
class_docstring = Unicode() | ||||
call_def = Unicode() | ||||
call_docstring = Unicode() | ||||
source = Unicode() | ||||
def check(self, d): | ||||
Thomas Kluyver
|
r12374 | Reference.check(self, d) | ||
MinRK
|
r6554 | if d['argspec'] is not None: | ||
Thomas Kluyver
|
r12374 | ArgSpec().check(d['argspec']) | ||
MinRK
|
r6554 | |||
class ArgSpec(Reference): | ||||
args = List(Unicode) | ||||
varargs = Unicode() | ||||
varkw = Unicode() | ||||
MinRK
|
r6558 | defaults = List() | ||
MinRK
|
r6554 | |||
class Status(Reference): | ||||
MinRK
|
r10341 | execution_state = Enum((u'busy', u'idle', u'starting')) | ||
MinRK
|
r6554 | |||
class CompleteReply(Reference): | ||||
matches = List(Unicode) | ||||
Takafumi Arakaki
|
r8865 | def Version(num, trait=Integer): | ||
return List(trait, default_value=[0] * num, minlen=num, maxlen=num) | ||||
Takafumi Arakaki
|
r8879 | class KernelInfoReply(Reference): | ||
Takafumi Arakaki
|
r8865 | |||
protocol_version = Version(2) | ||||
ipython_version = Version(4, Any) | ||||
language_version = Version(3) | ||||
language = Unicode() | ||||
def _ipython_version_changed(self, name, old, new): | ||||
for v in new: | ||||
Thomas Kluyver
|
r13353 | assert isinstance(v, int) or isinstance(v, string_types), \ | ||
Thomas Kluyver
|
r12374 | 'expected int or string as version component, got {0!r}'.format(v) | ||
Takafumi Arakaki
|
r8831 | |||
MinRK
|
r6554 | # IOPub messages | ||
class PyIn(Reference): | ||||
code = Unicode() | ||||
MinRK
|
r6559 | execution_count = Integer() | ||
MinRK
|
r6554 | |||
PyErr = ExecuteReplyError | ||||
class Stream(Reference): | ||||
name = Enum((u'stdout', u'stderr')) | ||||
data = Unicode() | ||||
mime_pat = re.compile(r'\w+/\w+') | ||||
class DisplayData(Reference): | ||||
source = Unicode() | ||||
metadata = Dict() | ||||
data = Dict() | ||||
def _data_changed(self, name, old, new): | ||||
Thomas Kluyver
|
r13361 | for k,v in iteritems(new): | ||
Thomas Kluyver
|
r12374 | assert mime_pat.match(k) | ||
Thomas Kluyver
|
r13353 | nt.assert_is_instance(v, string_types) | ||
MinRK
|
r6554 | |||
MinRK
|
r6664 | class PyOut(Reference): | ||
execution_count = Integer() | ||||
data = Dict() | ||||
def _data_changed(self, name, old, new): | ||||
Thomas Kluyver
|
r13361 | for k,v in iteritems(new): | ||
Thomas Kluyver
|
r12374 | assert mime_pat.match(k) | ||
Thomas Kluyver
|
r13353 | nt.assert_is_instance(v, string_types) | ||
MinRK
|
r6664 | |||
MinRK
|
r6554 | references = { | ||
'execute_reply' : ExecuteReply(), | ||||
'object_info_reply' : OInfoReply(), | ||||
'status' : Status(), | ||||
'complete_reply' : CompleteReply(), | ||||
Takafumi Arakaki
|
r8879 | 'kernel_info_reply': KernelInfoReply(), | ||
MinRK
|
r6554 | 'pyin' : PyIn(), | ||
MinRK
|
r6664 | 'pyout' : PyOut(), | ||
MinRK
|
r6554 | 'pyerr' : PyErr(), | ||
'stream' : Stream(), | ||||
'display_data' : DisplayData(), | ||||
} | ||||
Takafumi Arakaki
|
r8829 | """ | ||
Specifications of `content` part of the reply messages. | ||||
""" | ||||
MinRK
|
r6554 | |||
def validate_message(msg, msg_type=None, parent=None): | ||||
MinRK
|
r6573 | """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. | ||||
""" | ||||
MinRK
|
r6554 | RMessage().check(msg) | ||
if msg_type: | ||||
Thomas Kluyver
|
r12374 | nt.assert_equal(msg['msg_type'], msg_type) | ||
MinRK
|
r6554 | if parent: | ||
Thomas Kluyver
|
r12374 | nt.assert_equal(msg['parent_header']['msg_id'], parent) | ||
MinRK
|
r6554 | content = msg['content'] | ||
ref = references[msg['msg_type']] | ||||
Thomas Kluyver
|
r12374 | ref.check(content) | ||
MinRK
|
r6554 | |||
#----------------------------------------------------------------------------- | ||||
# Tests | ||||
#----------------------------------------------------------------------------- | ||||
# Shell channel | ||||
Fernando Perez
|
r2926 | |||
def test_execute(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r10294 | msg_id = KC.execute(code='x=1') | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'execute_reply', msg_id) | ||
MinRK
|
r6554 | |||
def test_execute_silent(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | msg_id, reply = execute(code='x=1', silent=True) | ||
# flush status=idle | ||||
MinRK
|
r11802 | status = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(status, 'status', msg_id) | ||
Bradley M. Froehle
|
r7875 | nt.assert_equal(status['content']['execution_state'], 'idle') | ||
MinRK
|
r6554 | |||
Thomas Kluyver
|
r12374 | nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) | ||
MinRK
|
r6554 | count = reply['execution_count'] | ||
msg_id, reply = execute(code='x=2', silent=True) | ||||
# flush status=idle | ||||
MinRK
|
r11802 | status = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(status, 'status', msg_id) | ||
nt.assert_equal(status['content']['execution_state'], 'idle') | ||||
MinRK
|
r6554 | |||
Thomas Kluyver
|
r12374 | nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) | ||
MinRK
|
r6554 | count_2 = reply['execution_count'] | ||
Thomas Kluyver
|
r12374 | nt.assert_equal(count_2, count) | ||
MinRK
|
r6554 | |||
def test_execute_error(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | |||
msg_id, reply = execute(code='1/0') | ||||
Thomas Kluyver
|
r12374 | nt.assert_equal(reply['status'], 'error') | ||
nt.assert_equal(reply['ename'], 'ZeroDivisionError') | ||||
MinRK
|
r6554 | |||
MinRK
|
r11802 | pyerr = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(pyerr, 'pyerr', msg_id) | ||
MinRK
|
r6554 | |||
def test_execute_inc(): | ||||
"""execute request should increment execution_count""" | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | msg_id, reply = execute(code='x=1') | ||
count = reply['execution_count'] | ||||
flush_channels() | ||||
Fernando Perez
|
r2926 | |||
MinRK
|
r6554 | msg_id, reply = execute(code='x=2') | ||
count_2 = reply['execution_count'] | ||||
Bradley M. Froehle
|
r7875 | nt.assert_equal(count_2, count+1) | ||
MinRK
|
r6554 | |||
def test_user_variables(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | msg_id, reply = execute(code='x=1', user_variables=['x']) | ||
user_variables = reply['user_variables'] | ||||
MinRK
|
r10637 | nt.assert_equal(user_variables, {u'x': { | ||
u'status': u'ok', | ||||
u'data': {u'text/plain': u'1'}, | ||||
u'metadata': {}, | ||||
}}) | ||||
def test_user_variables_fail(): | ||||
flush_channels() | ||||
msg_id, reply = execute(code='x=1', user_variables=['nosuchname']) | ||||
user_variables = reply['user_variables'] | ||||
foo = user_variables['nosuchname'] | ||||
nt.assert_equal(foo['status'], 'error') | ||||
nt.assert_equal(foo['ename'], 'KeyError') | ||||
MinRK
|
r6554 | |||
def test_user_expressions(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1')) | ||
user_expressions = reply['user_expressions'] | ||||
MinRK
|
r10637 | nt.assert_equal(user_expressions, {u'foo': { | ||
u'status': u'ok', | ||||
u'data': {u'text/plain': u'2'}, | ||||
u'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'] | ||||
nt.assert_equal(foo['status'], 'error') | ||||
nt.assert_equal(foo['ename'], 'NameError') | ||||
MinRK
|
r6554 | |||
def test_oinfo(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r10294 | msg_id = KC.object_info('a') | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'object_info_reply', msg_id) | ||
MinRK
|
r6554 | |||
def test_oinfo_found(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | |||
msg_id, reply = execute(code='a=5') | ||||
MinRK
|
r10294 | msg_id = KC.object_info('a') | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'object_info_reply', msg_id) | ||
MinRK
|
r6554 | content = reply['content'] | ||
Thomas Kluyver
|
r12374 | assert content['found'] | ||
MinRK
|
r6560 | argspec = content['argspec'] | ||
Thomas Kluyver
|
r12374 | nt.assert_is(argspec, None) | ||
MinRK
|
r6554 | |||
MinRK
|
r6557 | def test_oinfo_detail(): | ||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6557 | |||
msg_id, reply = execute(code='ip=get_ipython()') | ||||
MinRK
|
r6558 | |||
MinRK
|
r10294 | msg_id = KC.object_info('ip.object_inspect', detail_level=2) | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'object_info_reply', msg_id) | ||
MinRK
|
r6557 | content = reply['content'] | ||
Thomas Kluyver
|
r12374 | assert content['found'] | ||
MinRK
|
r6560 | argspec = content['argspec'] | ||
Thomas Kluyver
|
r12374 | nt.assert_is_instance(argspec, dict, "expected non-empty argspec dict, got %r" % argspec) | ||
nt.assert_equal(argspec['defaults'], [0]) | ||||
MinRK
|
r6557 | |||
def test_oinfo_not_found(): | ||||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6557 | |||
MinRK
|
r10294 | msg_id = KC.object_info('dne') | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'object_info_reply', msg_id) | ||
MinRK
|
r6557 | content = reply['content'] | ||
Thomas Kluyver
|
r12374 | nt.assert_false(content['found']) | ||
MinRK
|
r6557 | |||
MinRK
|
r6554 | def test_complete(): | ||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | msg_id, reply = execute(code="alpha = albert = 5") | ||
MinRK
|
r10294 | msg_id = KC.complete('al', 'al', 2) | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'complete_reply', msg_id) | ||
MinRK
|
r6554 | matches = reply['content']['matches'] | ||
for name in ('alpha', 'albert'): | ||||
Thomas Kluyver
|
r12374 | nt.assert_in(name, matches) | ||
MinRK
|
r6554 | |||
Takafumi Arakaki
|
r8879 | def test_kernel_info_request(): | ||
Takafumi Arakaki
|
r8831 | flush_channels() | ||
MinRK
|
r10294 | msg_id = KC.kernel_info() | ||
MinRK
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(reply, 'kernel_info_reply', msg_id) | ||
Takafumi Arakaki
|
r8831 | |||
Pablo de Oliveira
|
r12953 | def test_single_payload(): | ||
flush_channels() | ||||
msg_id, reply = execute(code="for i in range(3):\n"+ | ||||
" x=range?\n") | ||||
payload = reply['payload'] | ||||
next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] | ||||
nt.assert_equal(len(next_input_pls), 1) | ||||
MinRK
|
r6561 | # IOPub channel | ||
MinRK
|
r6554 | def test_stream(): | ||
MinRK
|
r6558 | flush_channels() | ||
MinRK
|
r6554 | msg_id, reply = execute("print('hi')") | ||
MinRK
|
r11802 | stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(stdout, 'stream', msg_id) | ||
MinRK
|
r6554 | content = stdout['content'] | ||
Thomas Kluyver
|
r12374 | nt.assert_equal(content['name'], u'stdout') | ||
nt.assert_equal(content['data'], u'hi\n') | ||||
MinRK
|
r6554 | |||
MinRK
|
r6558 | def test_display_data(): | ||
flush_channels() | ||||
MinRK
|
r6554 | msg_id, reply = execute("from IPython.core.display import display; display(1)") | ||
MinRK
|
r11802 | display = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
Thomas Kluyver
|
r12374 | validate_message(display, 'display_data', parent=msg_id) | ||
MinRK
|
r6554 | data = display['content']['data'] | ||
Thomas Kluyver
|
r12374 | nt.assert_equal(data['text/plain'], u'1') | ||
MinRK
|
r6554 | |||