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. |
|
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 |
|
345 | that will be shipped back to the caller via this channel. The kernel | |
351 |
ha |
|
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 |
'e |
|
356 | 'ename' : str, # Exception name, as a string | |
363 |
'e |
|
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 e |
|
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