test_message_spec.py
496 lines
| 13.6 KiB
| text/x-python
|
PythonLexer
|
r16566 | """Test suite for our zeromq-based message specification.""" | ||
# Copyright (c) IPython Development Team. | ||||
# Distributed under the terms of the Modified BSD License. | ||||
|
r2926 | |||
|
r6554 | import re | ||
|
r19046 | import sys | ||
|
r16566 | from distutils.version import LooseVersion as V | ||
|
r13354 | try: | ||
from queue import Empty # Py 3 | ||||
except ImportError: | ||||
from Queue import Empty # Py 2 | ||||
|
r2926 | |||
import nose.tools as nt | ||||
|
r6554 | from IPython.utils.traitlets import ( | ||
|
r19046 | HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, | ||
|
r6554 | ) | ||
|
r13361 | from IPython.utils.py3compat import string_types, iteritems | ||
|
r6554 | |||
|
r12414 | from .utils import TIMEOUT, start_global_kernel, flush_channels, execute | ||
|
r6554 | #----------------------------------------------------------------------------- | ||
|
r12414 | # Globals | ||
|
r6554 | #----------------------------------------------------------------------------- | ||
|
r12414 | KC = None | ||
|
r11802 | |||
|
r2926 | def setup(): | ||
|
r12414 | global KC | ||
KC = start_global_kernel() | ||||
|
r6554 | |||
#----------------------------------------------------------------------------- | ||||
|
r12414 | # Message Spec References | ||
|
r6554 | #----------------------------------------------------------------------------- | ||
class Reference(HasTraits): | ||||
|
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. | ||||
""" | ||||
|
r6554 | def check(self, d): | ||
"""validate a dict against our traits""" | ||||
for key in self.trait_names(): | ||||
|
r12374 | nt.assert_in(key, d) | ||
|
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: | ||||
|
r16566 | assert False, str(e) | ||
|
r6554 | |||
|
r16665 | |||
|
r16566 | class Version(Unicode): | ||
|
r16665 | def __init__(self, *args, **kwargs): | ||
self.min = kwargs.pop('min', None) | ||||
self.max = kwargs.pop('max', None) | ||||
kwargs['default_value'] = self.min | ||||
super(Version, self).__init__(*args, **kwargs) | ||||
|
r16566 | def validate(self, obj, value): | ||
|
r16665 | 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)) | ||||
|
r6554 | |||
class RMessage(Reference): | ||||
msg_id = Unicode() | ||||
msg_type = Unicode() | ||||
header = Dict() | ||||
parent_header = Dict() | ||||
content = Dict() | ||||
|
r16566 | |||
def check(self, d): | ||||
super(RMessage, self).check(d) | ||||
RHeader().check(self.header) | ||||
|
r16570 | if self.parent_header: | ||
RHeader().check(self.parent_header) | ||||
|
r6554 | |||
class RHeader(Reference): | ||||
msg_id = Unicode() | ||||
msg_type = Unicode() | ||||
session = Unicode() | ||||
username = Unicode() | ||||
|
r16665 | version = Version(min='5.0') | ||
|
r6554 | |||
|
r16665 | mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$') | ||
|
r16580 | |||
class MimeBundle(Reference): | ||||
metadata = Dict() | ||||
data = Dict() | ||||
def _data_changed(self, name, old, new): | ||||
for k,v in iteritems(new): | ||||
assert mime_pat.match(k) | ||||
nt.assert_is_instance(v, string_types) | ||||
# shell replies | ||||
|
r6554 | |||
class ExecuteReply(Reference): | ||||
execution_count = Integer() | ||||
|
r20481 | status = Enum((u'ok', u'error'), default_value=u'ok') | ||
|
r6554 | |||
def check(self, d): | ||||
|
r12374 | Reference.check(self, d) | ||
|
r6554 | if d['status'] == 'ok': | ||
|
r12374 | ExecuteReplyOkay().check(d) | ||
|
r6554 | elif d['status'] == 'error': | ||
|
r12374 | ExecuteReplyError().check(d) | ||
|
r6554 | |||
|
r2926 | |||
|
r6554 | class ExecuteReplyOkay(Reference): | ||
payload = List(Dict) | ||||
user_expressions = Dict() | ||||
|
r2926 | |||
|
r6554 | |||
class ExecuteReplyError(Reference): | ||||
ename = Unicode() | ||||
evalue = Unicode() | ||||
traceback = List(Unicode) | ||||
|
r16587 | class InspectReply(MimeBundle): | ||
|
r6554 | found = Bool() | ||
class ArgSpec(Reference): | ||||
args = List(Unicode) | ||||
varargs = Unicode() | ||||
varkw = Unicode() | ||||
|
r6558 | defaults = List() | ||
|
r6554 | |||
class Status(Reference): | ||||
|
r20481 | execution_state = Enum((u'busy', u'idle', u'starting'), default_value=u'busy') | ||
|
r6554 | |||
class CompleteReply(Reference): | ||||
matches = List(Unicode) | ||||
|
r16588 | cursor_start = Integer() | ||
cursor_end = Integer() | ||||
status = Unicode() | ||||
|
r6554 | |||
|
r19046 | class LanguageInfo(Reference): | ||
name = Unicode('python') | ||||
version = Unicode(sys.version.split()[0]) | ||||
|
r6554 | |||
|
r8879 | class KernelInfoReply(Reference): | ||
|
r16665 | protocol_version = Version(min='5.0') | ||
|
r16584 | implementation = Unicode('ipython') | ||
|
r16665 | implementation_version = Version(min='2.1') | ||
|
r18474 | language_info = Dict() | ||
|
r16584 | banner = Unicode() | ||
|
r19046 | |||
def check(self, d): | ||||
Reference.check(self, d) | ||||
LanguageInfo().check(d['language_info']) | ||||
|
r8865 | |||
|
r8831 | |||
|
r17622 | class IsCompleteReply(Reference): | ||
|
r20481 | status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'), default_value=u'complete') | ||
|
r17804 | |||
def check(self, d): | ||||
Reference.check(self, d) | ||||
if d['status'] == 'incomplete': | ||||
IsCompleteReplyIncomplete().check(d) | ||||
class IsCompleteReplyIncomplete(Reference): | ||||
indent = Unicode() | ||||
|
r17622 | |||
|
r6554 | # IOPub messages | ||
|
r16567 | class ExecuteInput(Reference): | ||
|
r6554 | code = Unicode() | ||
|
r6559 | execution_count = Integer() | ||
|
r6554 | |||
|
r16569 | Error = ExecuteReplyError | ||
|
r6554 | |||
class Stream(Reference): | ||||
|
r20481 | name = Enum((u'stdout', u'stderr'), default_value=u'stdout') | ||
|
r18104 | text = Unicode() | ||
|
r6554 | |||
|
r16580 | class DisplayData(MimeBundle): | ||
|
r16585 | pass | ||
|
r6554 | |||
|
r16580 | class ExecuteResult(MimeBundle): | ||
|
r6664 | execution_count = Integer() | ||
|
r19537 | class HistoryReply(Reference): | ||
|
r19538 | history = List(List()) | ||
|
r19536 | |||
|
r6664 | |||
|
r6554 | references = { | ||
'execute_reply' : ExecuteReply(), | ||||
|
r16587 | 'inspect_reply' : InspectReply(), | ||
|
r6554 | 'status' : Status(), | ||
'complete_reply' : CompleteReply(), | ||||
|
r8879 | 'kernel_info_reply': KernelInfoReply(), | ||
|
r17622 | 'is_complete_reply': IsCompleteReply(), | ||
|
r16567 | 'execute_input' : ExecuteInput(), | ||
|
r16568 | 'execute_result' : ExecuteResult(), | ||
|
r19536 | 'history_reply' : HistoryReply(), | ||
|
r16569 | 'error' : Error(), | ||
|
r6554 | 'stream' : Stream(), | ||
'display_data' : DisplayData(), | ||||
|
r16566 | 'header' : RHeader(), | ||
|
r6554 | } | ||
|
r8829 | """ | ||
Specifications of `content` part of the reply messages. | ||||
""" | ||||
|
r6554 | |||
def validate_message(msg, msg_type=None, parent=None): | ||||
|
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. | ||||
""" | ||||
|
r6554 | RMessage().check(msg) | ||
if msg_type: | ||||
|
r12374 | nt.assert_equal(msg['msg_type'], msg_type) | ||
|
r6554 | if parent: | ||
|
r12374 | nt.assert_equal(msg['parent_header']['msg_id'], parent) | ||
|
r6554 | content = msg['content'] | ||
ref = references[msg['msg_type']] | ||||
|
r12374 | ref.check(content) | ||
|
r6554 | |||
#----------------------------------------------------------------------------- | ||||
# Tests | ||||
#----------------------------------------------------------------------------- | ||||
# Shell channel | ||||
|
r2926 | |||
def test_execute(): | ||||
|
r6558 | flush_channels() | ||
|
r10294 | msg_id = KC.execute(code='x=1') | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(reply, 'execute_reply', msg_id) | ||
|
r6554 | |||
def test_execute_silent(): | ||||
|
r6558 | flush_channels() | ||
|
r6554 | msg_id, reply = execute(code='x=1', silent=True) | ||
# flush status=idle | ||||
|
r11802 | status = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(status, 'status', msg_id) | ||
|
r7875 | nt.assert_equal(status['content']['execution_state'], 'idle') | ||
|
r6554 | |||
|
r12374 | nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) | ||
|
r6554 | count = reply['execution_count'] | ||
msg_id, reply = execute(code='x=2', silent=True) | ||||
# flush status=idle | ||||
|
r11802 | status = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(status, 'status', msg_id) | ||
nt.assert_equal(status['content']['execution_state'], 'idle') | ||||
|
r6554 | |||
|
r12374 | nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) | ||
|
r6554 | count_2 = reply['execution_count'] | ||
|
r12374 | nt.assert_equal(count_2, count) | ||
|
r6554 | |||
def test_execute_error(): | ||||
|
r6558 | flush_channels() | ||
|
r6554 | |||
msg_id, reply = execute(code='1/0') | ||||
|
r12374 | nt.assert_equal(reply['status'], 'error') | ||
nt.assert_equal(reply['ename'], 'ZeroDivisionError') | ||||
|
r6554 | |||
|
r16569 | error = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
validate_message(error, 'error', msg_id) | ||||
|
r6554 | |||
def test_execute_inc(): | ||||
"""execute request should increment execution_count""" | ||||
|
r6558 | flush_channels() | ||
|
r6554 | msg_id, reply = execute(code='x=1') | ||
count = reply['execution_count'] | ||||
flush_channels() | ||||
|
r2926 | |||
|
r6554 | msg_id, reply = execute(code='x=2') | ||
count_2 = reply['execution_count'] | ||||
|
r7875 | nt.assert_equal(count_2, count+1) | ||
|
r6554 | |||
|
r19986 | def test_execute_stop_on_error(): | ||
"""execute request should not abort execution queue with stop_on_error False""" | ||||
|
r19985 | flush_channels() | ||
|
r20125 | |||
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) | ||||
|
r19986 | msg_id = KC.execute(code='print("Hello")') | ||
|
r19985 | KC.get_shell_msg(timeout=TIMEOUT) | ||
reply = KC.get_shell_msg(timeout=TIMEOUT) | ||||
nt.assert_equal(reply['content']['status'], 'aborted') | ||||
flush_channels() | ||||
|
r20125 | KC.execute(code=fail, stop_on_error=False) | ||
|
r19986 | msg_id = KC.execute(code='print("Hello")') | ||
|
r19985 | KC.get_shell_msg(timeout=TIMEOUT) | ||
reply = KC.get_shell_msg(timeout=TIMEOUT) | ||||
nt.assert_equal(reply['content']['status'], 'ok') | ||||
|
r6554 | |||
def test_user_expressions(): | ||||
|
r6558 | flush_channels() | ||
|
r6554 | msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1')) | ||
user_expressions = reply['user_expressions'] | ||||
|
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') | ||||
|
r6554 | |||
def test_oinfo(): | ||||
|
r6558 | flush_channels() | ||
|
r16587 | msg_id = KC.inspect('a') | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r16587 | validate_message(reply, 'inspect_reply', msg_id) | ||
|
r6554 | |||
def test_oinfo_found(): | ||||
|
r6558 | flush_channels() | ||
|
r6554 | |||
msg_id, reply = execute(code='a=5') | ||||
|
r16587 | msg_id = KC.inspect('a') | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r16587 | validate_message(reply, 'inspect_reply', msg_id) | ||
|
r6554 | content = reply['content'] | ||
|
r12374 | assert content['found'] | ||
|
r16580 | text = content['data']['text/plain'] | ||
nt.assert_in('Type:', text) | ||||
nt.assert_in('Docstring:', text) | ||||
|
r6554 | |||
|
r6557 | def test_oinfo_detail(): | ||
|
r6558 | flush_channels() | ||
|
r6557 | |||
msg_id, reply = execute(code='ip=get_ipython()') | ||||
|
r6558 | |||
|
r16587 | msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1) | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r16587 | validate_message(reply, 'inspect_reply', msg_id) | ||
|
r6557 | content = reply['content'] | ||
|
r12374 | assert content['found'] | ||
|
r16580 | text = content['data']['text/plain'] | ||
|
r20578 | nt.assert_in('Signature:', text) | ||
|
r16580 | nt.assert_in('Source:', text) | ||
|
r6557 | |||
def test_oinfo_not_found(): | ||||
|
r6558 | flush_channels() | ||
|
r6557 | |||
|
r16587 | msg_id = KC.inspect('dne') | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r16587 | validate_message(reply, 'inspect_reply', msg_id) | ||
|
r6557 | content = reply['content'] | ||
|
r12374 | nt.assert_false(content['found']) | ||
|
r6557 | |||
|
r6554 | def test_complete(): | ||
|
r6558 | flush_channels() | ||
|
r6554 | msg_id, reply = execute(code="alpha = albert = 5") | ||
|
r16580 | msg_id = KC.complete('al', 2) | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(reply, 'complete_reply', msg_id) | ||
|
r6554 | matches = reply['content']['matches'] | ||
for name in ('alpha', 'albert'): | ||||
|
r12374 | nt.assert_in(name, matches) | ||
|
r6554 | |||
|
r8879 | def test_kernel_info_request(): | ||
|
r8831 | flush_channels() | ||
|
r10294 | msg_id = KC.kernel_info() | ||
|
r11802 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(reply, 'kernel_info_reply', msg_id) | ||
|
r8831 | |||
|
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) | ||||
|
r17622 | def test_is_complete(): | ||
flush_channels() | ||||
|
r19544 | |||
|
r17622 | msg_id = KC.is_complete("a = 1") | ||
reply = KC.get_shell_msg(timeout=TIMEOUT) | ||||
validate_message(reply, 'is_complete_reply', msg_id) | ||||
|
r12953 | |||
|
r19537 | def test_history_range(): | ||
|
r19536 | flush_channels() | ||
|
r19544 | msg_id_exec = KC.execute(code='x=1', store_history = True) | ||
reply_exec = KC.get_shell_msg(timeout=TIMEOUT) | ||||
|
r19538 | msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0) | ||
|
r19536 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
validate_message(reply, 'history_reply', msg_id) | ||||
|
r19538 | content = reply['content'] | ||
nt.assert_equal(len(content['history']), 1) | ||||
|
r19536 | |||
|
r19537 | def test_history_tail(): | ||
flush_channels() | ||||
|
r19544 | msg_id_exec = KC.execute(code='x=1', store_history = True) | ||
reply_exec = KC.get_shell_msg(timeout=TIMEOUT) | ||||
|
r19538 | msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0) | ||
|
r19537 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
validate_message(reply, 'history_reply', msg_id) | ||||
|
r19538 | content = reply['content'] | ||
nt.assert_equal(len(content['history']), 1) | ||||
|
r19537 | |||
def test_history_search(): | ||||
flush_channels() | ||||
|
r19544 | msg_id_exec = KC.execute(code='x=1', store_history = True) | ||
reply_exec = KC.get_shell_msg(timeout=TIMEOUT) | ||||
|
r19538 | msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0) | ||
|
r19537 | reply = KC.get_shell_msg(timeout=TIMEOUT) | ||
validate_message(reply, 'history_reply', msg_id) | ||||
|
r19538 | content = reply['content'] | ||
nt.assert_equal(len(content['history']), 1) | ||||
|
r19537 | |||
|
r6561 | # IOPub channel | ||
|
r6554 | def test_stream(): | ||
|
r6558 | flush_channels() | ||
|
r6554 | msg_id, reply = execute("print('hi')") | ||
|
r11802 | stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(stdout, 'stream', msg_id) | ||
|
r6554 | content = stdout['content'] | ||
|
r18104 | nt.assert_equal(content['text'], u'hi\n') | ||
|
r6554 | |||
|
r6558 | def test_display_data(): | ||
flush_channels() | ||||
|
r6554 | msg_id, reply = execute("from IPython.core.display import display; display(1)") | ||
|
r11802 | display = KC.iopub_channel.get_msg(timeout=TIMEOUT) | ||
|
r12374 | validate_message(display, 'display_data', parent=msg_id) | ||
|
r6554 | data = display['content']['data'] | ||
|
r12374 | nt.assert_equal(data['text/plain'], u'1') | ||
|
r6554 | |||