##// END OF EJS Templates
Merge pull request #1627 from minrk/msgspec...
Fernando Perez -
r6567:232fa81a merge
parent child Browse files
Show More
@@ -1456,11 +1456,13 b' class InteractiveShell(SingletonConfigurable, Magic):'
1456 print 'Object `%s` not found.' % oname
1456 print 'Object `%s` not found.' % oname
1457 return 'not found' # so callers can take other action
1457 return 'not found' # so callers can take other action
1458
1458
1459 def object_inspect(self, oname):
1459 def object_inspect(self, oname, detail_level=0):
1460 with self.builtin_trap:
1460 with self.builtin_trap:
1461 info = self._object_find(oname)
1461 info = self._object_find(oname)
1462 if info.found:
1462 if info.found:
1463 return self.inspector.info(info.obj, oname, info=info)
1463 return self.inspector.info(info.obj, oname, info=info,
1464 detail_level=detail_level
1465 )
1464 else:
1466 else:
1465 return oinspect.object_info(name=oname, found=False)
1467 return oinspect.object_info(name=oname, found=False)
1466
1468
@@ -233,6 +233,7 b' def make_exclude():'
233 # We do this unconditionally, so that the test suite doesn't import
233 # We do this unconditionally, so that the test suite doesn't import
234 # gtk, changing the default encoding and masking some unicode bugs.
234 # gtk, changing the default encoding and masking some unicode bugs.
235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
235 exclusions.append(ipjoin('lib', 'inputhookgtk'))
236 exclusions.append(ipjoin('zmq', 'gui', 'gtkembed'))
236
237
237 # These have to be skipped on win32 because the use echo, rm, cd, etc.
238 # These have to be skipped on win32 because the use echo, rm, cd, etc.
238 # See ticket https://github.com/ipython/ipython/issues/87
239 # See ticket https://github.com/ipython/ipython/issues/87
@@ -263,7 +264,9 b' def make_exclude():'
263
264
264 if not have['matplotlib']:
265 if not have['matplotlib']:
265 exclusions.extend([ipjoin('core', 'pylabtools'),
266 exclusions.extend([ipjoin('core', 'pylabtools'),
266 ipjoin('core', 'tests', 'test_pylabtools')])
267 ipjoin('core', 'tests', 'test_pylabtools'),
268 ipjoin('zmq', 'pylab'),
269 ])
267
270
268 if not have['tornado']:
271 if not have['tornado']:
269 exclusions.append(ipjoin('frontend', 'html'))
272 exclusions.append(ipjoin('frontend', 'html'))
@@ -385,6 +388,7 b' def make_runners():'
385 'scripts', 'testing', 'utils', 'nbformat' ]
388 'scripts', 'testing', 'utils', 'nbformat' ]
386
389
387 if have['zmq']:
390 if have['zmq']:
391 nose_pkg_names.append('zmq')
388 nose_pkg_names.append('parallel')
392 nose_pkg_names.append('parallel')
389
393
390 # For debugging this code, only load quick stuff
394 # For debugging this code, only load quick stuff
@@ -364,7 +364,10 b' class Kernel(Configurable):'
364 self.log.debug(str(completion_msg))
364 self.log.debug(str(completion_msg))
365
365
366 def object_info_request(self, ident, parent):
366 def object_info_request(self, ident, parent):
367 object_info = self.shell.object_inspect(parent['content']['oname'])
367 content = parent['content']
368 object_info = self.shell.object_inspect(content['oname'],
369 detail_level = content.get('detail_level', 0)
370 )
368 # Before we send this object over, we scrub it for JSON usage
371 # Before we send this object over, we scrub it for JSON usage
369 oinfo = json_clean(object_info)
372 oinfo = json_clean(object_info)
370 msg = self.session.send(self.shell_socket, 'object_info_reply',
373 msg = self.session.send(self.shell_socket, 'object_info_reply',
@@ -200,6 +200,10 b' class ShellSocketChannel(ZMQSocketChannel):'
200 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
200 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
201 self.stream.on_recv(self._handle_recv)
201 self.stream.on_recv(self._handle_recv)
202 self._run_loop()
202 self._run_loop()
203 try:
204 self.socket.close()
205 except:
206 pass
203
207
204 def stop(self):
208 def stop(self):
205 self.ioloop.stop()
209 self.ioloop.stop()
@@ -297,19 +301,21 b' class ShellSocketChannel(ZMQSocketChannel):'
297 self._queue_send(msg)
301 self._queue_send(msg)
298 return msg['header']['msg_id']
302 return msg['header']['msg_id']
299
303
300 def object_info(self, oname):
304 def object_info(self, oname, detail_level=0):
301 """Get metadata information about an object.
305 """Get metadata information about an object.
302
306
303 Parameters
307 Parameters
304 ----------
308 ----------
305 oname : str
309 oname : str
306 A string specifying the object name.
310 A string specifying the object name.
311 detail_level : int, optional
312 The level of detail for the introspection (0-2)
307
313
308 Returns
314 Returns
309 -------
315 -------
310 The msg_id of the message sent.
316 The msg_id of the message sent.
311 """
317 """
312 content = dict(oname=oname)
318 content = dict(oname=oname, detail_level=detail_level)
313 msg = self.session.msg('object_info_request', content)
319 msg = self.session.msg('object_info_request', content)
314 self._queue_send(msg)
320 self._queue_send(msg)
315 return msg['header']['msg_id']
321 return msg['header']['msg_id']
@@ -388,6 +394,10 b' class SubSocketChannel(ZMQSocketChannel):'
388 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
394 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
389 self.stream.on_recv(self._handle_recv)
395 self.stream.on_recv(self._handle_recv)
390 self._run_loop()
396 self._run_loop()
397 try:
398 self.socket.close()
399 except:
400 pass
391
401
392 def stop(self):
402 def stop(self):
393 self.ioloop.stop()
403 self.ioloop.stop()
@@ -450,6 +460,11 b' class StdInSocketChannel(ZMQSocketChannel):'
450 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
460 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
451 self.stream.on_recv(self._handle_recv)
461 self.stream.on_recv(self._handle_recv)
452 self._run_loop()
462 self._run_loop()
463 try:
464 self.socket.close()
465 except:
466 pass
467
453
468
454 def stop(self):
469 def stop(self):
455 self.ioloop.stop()
470 self.ioloop.stop()
@@ -573,6 +588,10 b' class HBSocketChannel(ZMQSocketChannel):'
573 # and close/reopen the socket, because the REQ/REP cycle has been broken
588 # and close/reopen the socket, because the REQ/REP cycle has been broken
574 self._create_socket()
589 self._create_socket()
575 continue
590 continue
591 try:
592 self.socket.close()
593 except:
594 pass
576
595
577 def pause(self):
596 def pause(self):
578 """Pause the heartbeat."""
597 """Pause the heartbeat."""
@@ -7,34 +7,424 b''
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import re
10 import sys
11 import sys
11 import time
12 import time
13 from subprocess import PIPE
14 from Queue import Empty
12
15
13 import nose.tools as nt
16 import nose.tools as nt
14
17
15 from ..blockingkernelmanager import BlockingKernelManager
18 from ..blockingkernelmanager import BlockingKernelManager
16
19
20
21 from IPython.testing import decorators as dec
17 from IPython.utils import io
22 from IPython.utils import io
23 from IPython.utils.traitlets import (
24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum,
25 )
26
27 #-----------------------------------------------------------------------------
28 # Global setup and utilities
29 #-----------------------------------------------------------------------------
18
30
19 def setup():
31 def setup():
20 global KM
32 global KM
21 KM = BlockingKernelManager()
33 KM = BlockingKernelManager()
22
34
23 KM.start_kernel()
35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
24 KM.start_channels()
36 KM.start_channels()
25 # Give the kernel a chance to come up.
37
26 time.sleep(1)
27
38
28 def teardown():
39 def teardown():
29 io.rprint('Entering teardown...') # dbg
30 io.rprint('Stopping channels and kernel...') # dbg
31 KM.stop_channels()
40 KM.stop_channels()
32 KM.kill_kernel()
41 KM.shutdown_kernel()
42
43
44 def flush_channels():
45 """flush any messages waiting on the queue"""
46 for channel in (KM.shell_channel, KM.sub_channel):
47 while True:
48 try:
49 msg = channel.get_msg(block=True, timeout=0.1)
50 except Empty:
51 break
52 else:
53 validate_message(msg)
54
55
56 def execute(code='', **kwargs):
57 """wrapper for doing common steps for validating an execution request"""
58 shell = KM.shell_channel
59 sub = KM.sub_channel
60
61 msg_id = shell.execute(code=code, **kwargs)
62 reply = shell.get_msg(timeout=2)
63 validate_message(reply, 'execute_reply', msg_id)
64 busy = sub.get_msg(timeout=2)
65 validate_message(busy, 'status', msg_id)
66 nt.assert_equals(busy['content']['execution_state'], 'busy')
67
68 if not kwargs.get('silent'):
69 pyin = sub.get_msg(timeout=2)
70 validate_message(pyin, 'pyin', msg_id)
71 nt.assert_equals(pyin['content']['code'], code)
72
73 return msg_id, reply['content']
74
75 #-----------------------------------------------------------------------------
76 # MSG Spec References
77 #-----------------------------------------------------------------------------
78
79
80 class Reference(HasTraits):
81
82 def check(self, d):
83 """validate a dict against our traits"""
84 for key in self.trait_names():
85 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
86 # FIXME: always allow None, probably not a good idea
87 if d[key] is None:
88 continue
89 try:
90 setattr(self, key, d[key])
91 except TraitError as e:
92 yield nt.assert_true(False, str(e))
93
94
95 class RMessage(Reference):
96 msg_id = Unicode()
97 msg_type = Unicode()
98 header = Dict()
99 parent_header = Dict()
100 content = Dict()
101
102 class RHeader(Reference):
103 msg_id = Unicode()
104 msg_type = Unicode()
105 session = Unicode()
106 username = Unicode()
107
108 class RContent(Reference):
109 status = Enum((u'ok', u'error'))
110
111
112 class ExecuteReply(Reference):
113 execution_count = Integer()
114 status = Enum((u'ok', u'error'))
115
116 def check(self, d):
117 for tst in Reference.check(self, d):
118 yield tst
119 if d['status'] == 'ok':
120 for tst in ExecuteReplyOkay().check(d):
121 yield tst
122 elif d['status'] == 'error':
123 for tst in ExecuteReplyError().check(d):
124 yield tst
125
126
127 class ExecuteReplyOkay(Reference):
128 payload = List(Dict)
129 user_variables = Dict()
130 user_expressions = Dict()
131
33
132
133 class ExecuteReplyError(Reference):
134 ename = Unicode()
135 evalue = Unicode()
136 traceback = List(Unicode)
34
137
35 # Actual tests
36
138
139 class OInfoReply(Reference):
140 name = Unicode()
141 found = Bool()
142 ismagic = Bool()
143 isalias = Bool()
144 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
145 type_name = Unicode()
146 string_form = Unicode()
147 base_class = Unicode()
148 length = Integer()
149 file = Unicode()
150 definition = Unicode()
151 argspec = Dict()
152 init_definition = Unicode()
153 docstring = Unicode()
154 init_docstring = Unicode()
155 class_docstring = Unicode()
156 call_def = Unicode()
157 call_docstring = Unicode()
158 source = Unicode()
159
160 def check(self, d):
161 for tst in Reference.check(self, d):
162 yield tst
163 if d['argspec'] is not None:
164 for tst in ArgSpec().check(d['argspec']):
165 yield tst
166
167
168 class ArgSpec(Reference):
169 args = List(Unicode)
170 varargs = Unicode()
171 varkw = Unicode()
172 defaults = List()
173
174
175 class Status(Reference):
176 execution_state = Enum((u'busy', u'idle'))
177
178
179 class CompleteReply(Reference):
180 matches = List(Unicode)
181
182
183 # IOPub messages
184
185 class PyIn(Reference):
186 code = Unicode()
187 execution_count = Integer()
188
189
190 PyErr = ExecuteReplyError
191
192
193 class Stream(Reference):
194 name = Enum((u'stdout', u'stderr'))
195 data = Unicode()
196
197
198 mime_pat = re.compile(r'\w+/\w+')
199
200 class DisplayData(Reference):
201 source = Unicode()
202 metadata = Dict()
203 data = Dict()
204 def _data_changed(self, name, old, new):
205 for k,v in new.iteritems():
206 nt.assert_true(mime_pat.match(k))
207 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
208
209
210 references = {
211 'execute_reply' : ExecuteReply(),
212 'object_info_reply' : OInfoReply(),
213 'status' : Status(),
214 'complete_reply' : CompleteReply(),
215 'pyin' : PyIn(),
216 'pyerr' : PyErr(),
217 'stream' : Stream(),
218 'display_data' : DisplayData(),
219 }
220
221
222 def validate_message(msg, msg_type=None, parent=None):
223 """validate a message"""
224 RMessage().check(msg)
225 if msg_type:
226 yield nt.assert_equals(msg['msg_type'], msg_type)
227 if parent:
228 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
229 content = msg['content']
230 ref = references[msg['msg_type']]
231 for tst in ref.check(content):
232 yield tst
233
234
235 #-----------------------------------------------------------------------------
236 # Tests
237 #-----------------------------------------------------------------------------
238
239 # Shell channel
240
241 @dec.parametric
37 def test_execute():
242 def test_execute():
38 KM.shell_channel.execute(code='x=1')
243 flush_channels()
39 KM.shell_channel.execute(code='print 1')
40
244
245 shell = KM.shell_channel
246 msg_id = shell.execute(code='x=1')
247 reply = shell.get_msg(timeout=2)
248 for tst in validate_message(reply, 'execute_reply', msg_id):
249 yield tst
250
251
252 @dec.parametric
253 def test_execute_silent():
254 flush_channels()
255 msg_id, reply = execute(code='x=1', silent=True)
256
257 # flush status=idle
258 status = KM.sub_channel.get_msg(timeout=2)
259 for tst in validate_message(status, 'status', msg_id):
260 yield tst
261 nt.assert_equals(status['content']['execution_state'], 'idle')
262
263 yield nt.assert_raises(Empty, KM.sub_channel.get_msg, timeout=0.1)
264 count = reply['execution_count']
265
266 msg_id, reply = execute(code='x=2', silent=True)
267
268 # flush status=idle
269 status = KM.sub_channel.get_msg(timeout=2)
270 for tst in validate_message(status, 'status', msg_id):
271 yield tst
272 yield nt.assert_equals(status['content']['execution_state'], 'idle')
273
274 yield nt.assert_raises(Empty, KM.sub_channel.get_msg, timeout=0.1)
275 count_2 = reply['execution_count']
276 yield nt.assert_equals(count_2, count)
277
278
279 @dec.parametric
280 def test_execute_error():
281 flush_channels()
282
283 msg_id, reply = execute(code='1/0')
284 yield nt.assert_equals(reply['status'], 'error')
285 yield nt.assert_equals(reply['ename'], 'ZeroDivisionError')
286
287 pyerr = KM.sub_channel.get_msg(timeout=2)
288 for tst in validate_message(pyerr, 'pyerr', msg_id):
289 yield tst
290
291
292 def test_execute_inc():
293 """execute request should increment execution_count"""
294 flush_channels()
295
296 msg_id, reply = execute(code='x=1')
297 count = reply['execution_count']
298
299 flush_channels()
300
301 msg_id, reply = execute(code='x=2')
302 count_2 = reply['execution_count']
303 nt.assert_equals(count_2, count+1)
304
305
306 def test_user_variables():
307 flush_channels()
308
309 msg_id, reply = execute(code='x=1', user_variables=['x'])
310 user_variables = reply['user_variables']
311 nt.assert_equals(user_variables, {u'x' : u'1'})
312
313
314 def test_user_expressions():
315 flush_channels()
316
317 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
318 user_expressions = reply['user_expressions']
319 nt.assert_equals(user_expressions, {u'foo' : u'2'})
320
321
322 @dec.parametric
323 def test_oinfo():
324 flush_channels()
325
326 shell = KM.shell_channel
327
328 msg_id = shell.object_info('a')
329 reply = shell.get_msg(timeout=2)
330 for tst in validate_message(reply, 'object_info_reply', msg_id):
331 yield tst
332
333
334 @dec.parametric
335 def test_oinfo_found():
336 flush_channels()
337
338 shell = KM.shell_channel
339
340 msg_id, reply = execute(code='a=5')
341
342 msg_id = shell.object_info('a')
343 reply = shell.get_msg(timeout=2)
344 for tst in validate_message(reply, 'object_info_reply', msg_id):
345 yield tst
346 content = reply['content']
347 yield nt.assert_true(content['found'])
348 argspec = content['argspec']
349 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
350
351
352 @dec.parametric
353 def test_oinfo_detail():
354 flush_channels()
355
356 shell = KM.shell_channel
357
358 msg_id, reply = execute(code='ip=get_ipython()')
359
360 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
361 reply = shell.get_msg(timeout=2)
362 for tst in validate_message(reply, 'object_info_reply', msg_id):
363 yield tst
364 content = reply['content']
365 yield nt.assert_true(content['found'])
366 argspec = content['argspec']
367 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
368 yield nt.assert_equals(argspec['defaults'], [0])
369
370
371 @dec.parametric
372 def test_oinfo_not_found():
373 flush_channels()
374
375 shell = KM.shell_channel
376
377 msg_id = shell.object_info('dne')
378 reply = shell.get_msg(timeout=2)
379 for tst in validate_message(reply, 'object_info_reply', msg_id):
380 yield tst
381 content = reply['content']
382 yield nt.assert_false(content['found'])
383
384
385 @dec.parametric
386 def test_complete():
387 flush_channels()
388
389 shell = KM.shell_channel
390
391 msg_id, reply = execute(code="alpha = albert = 5")
392
393 msg_id = shell.complete('al', 'al', 2)
394 reply = shell.get_msg(timeout=2)
395 for tst in validate_message(reply, 'complete_reply', msg_id):
396 yield tst
397 matches = reply['content']['matches']
398 for name in ('alpha', 'albert'):
399 yield nt.assert_true(name in matches, "Missing match: %r" % name)
400
401
402 # IOPub channel
403
404
405 @dec.parametric
406 def test_stream():
407 flush_channels()
408
409 msg_id, reply = execute("print('hi')")
410
411 stdout = KM.sub_channel.get_msg(timeout=2)
412 for tst in validate_message(stdout, 'stream', msg_id):
413 yield tst
414 content = stdout['content']
415 yield nt.assert_equals(content['name'], u'stdout')
416 yield nt.assert_equals(content['data'], u'hi\n')
417
418
419 @dec.parametric
420 def test_display_data():
421 flush_channels()
422
423 msg_id, reply = execute("from IPython.core.display import display; display(1)")
424
425 display = KM.sub_channel.get_msg(timeout=2)
426 for tst in validate_message(display, 'display_data', parent=msg_id):
427 yield tst
428 data = display['content']['data']
429 yield nt.assert_equals(data['text/plain'], u'1')
430
@@ -34,6 +34,7 b' from IPython.core.payloadpage import install_payload_page'
34 from IPython.lib.kernel import (
34 from IPython.lib.kernel import (
35 get_connection_file, get_connection_info, connect_qtconsole
35 get_connection_file, get_connection_info, connect_qtconsole
36 )
36 )
37 from IPython.testing.skipdoctest import skip_doctest
37 from IPython.utils import io
38 from IPython.utils import io
38 from IPython.utils.jsonutil import json_clean
39 from IPython.utils.jsonutil import json_clean
39 from IPython.utils.path import get_py_filename
40 from IPython.utils.path import get_py_filename
@@ -256,6 +257,7 b' class ZMQInteractiveShell(InteractiveShell):'
256 mode=dstore.mode)
257 mode=dstore.mode)
257 self.payload_manager.write_payload(payload)
258 self.payload_manager.write_payload(payload)
258
259
260 @skip_doctest
259 def magic_edit(self,parameter_s='',last_call=['','']):
261 def magic_edit(self,parameter_s='',last_call=['','']):
260 """Bring up an editor and execute the resulting code.
262 """Bring up an editor and execute the resulting code.
261
263
@@ -324,22 +324,17 b' Message type: ``execute_reply``::'
324 When status is 'ok', the following extra fields are present::
324 When status is 'ok', the following extra fields are present::
325
325
326 {
326 {
327 # The execution payload is a dict with string keys that may have been
327 # 'payload' will be a list of payload dicts.
328 # Each execution payload is a dict with string keys that may have been
328 # produced by the code being executed. It is retrieved by the kernel at
329 # produced by the code being executed. It is retrieved by the kernel at
329 # the end of the execution and sent back to the front end, which can take
330 # the end of the execution and sent back to the front end, which can take
330 # action on it as needed. See main text for further details.
331 # action on it as needed. See main text for further details.
331 'payload' : dict,
332 'payload' : list(dict),
332
333
333 # Results for the user_variables and user_expressions.
334 # Results for the user_variables and user_expressions.
334 'user_variables' : dict,
335 'user_variables' : dict,
335 'user_expressions' : dict,
336 'user_expressions' : dict,
336
337 }
337 # The kernel will often transform the input provided to it. If the
338 # '---->' transform had been applied, this is filled, otherwise it's the
339 # empty string. So transformations like magics don't appear here, only
340 # autocall ones.
341 'transformed_code' : str,
342 }
343
338
344 .. admonition:: Execution payloads
339 .. admonition:: Execution payloads
345
340
@@ -347,20 +342,19 b" When status is 'ok', the following extra fields are present::"
347 given set of code, which normally is just displayed on the pyout stream
342 given set of code, which normally is just displayed on the pyout stream
348 through the PUB socket. The idea of a payload is to allow special types of
343 through the PUB socket. The idea of a payload is to allow special types of
349 code, typically magics, to populate a data container in the IPython kernel
344 code, typically magics, to populate a data container in the IPython kernel
350 that will be shipped back to the caller via this channel. The kernel will
345 that will be shipped back to the caller via this channel. The kernel
351 have an API for this, probably something along the lines of::
346 has an API for this in the PayloadManager::
352
347
353 ip.exec_payload_add(key, value)
348 ip.payload_manager.write_payload(payload_dict)
354
349
355 though this API is still in the design stages. The data returned in this
350 which appends a dictionary to the list of payloads.
356 payload will allow frontends to present special views of what just happened.
357
351
358
352
359 When status is 'error', the following extra fields are present::
353 When status is 'error', the following extra fields are present::
360
354
361 {
355 {
362 'exc_name' : str, # Exception name, as a string
356 'ename' : str, # Exception name, as a string
363 'exc_value' : str, # Exception value, as a string
357 'evalue' : str, # Exception value, as a string
364
358
365 # The traceback will contain a list of frames, represented each as a
359 # The traceback will contain a list of frames, represented each as a
366 # string. For now we'll stick to the existing design of ultraTB, which
360 # string. For now we'll stick to the existing design of ultraTB, which
@@ -853,7 +847,7 b' Message type: ``crash``::'
853
847
854 content = {
848 content = {
855 # Similarly to the 'error' case for execute_reply messages, this will
849 # Similarly to the 'error' case for execute_reply messages, this will
856 # contain exc_name, exc_type and traceback fields.
850 # contain ename, etype and traceback fields.
857
851
858 # An additional field with supplementary information such as where to
852 # An additional field with supplementary information such as where to
859 # send the crash message
853 # send the crash message
General Comments 0
You need to be logged in to leave comments. Login now