##// END OF EJS Templates
allow utilities in test_message_spec to be reused...
MinRK -
Show More
@@ -1,497 +1,501 b''
1 1 """Test suite for our zeromq-based messaging specification.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 import re
11 11 import sys
12 12 import time
13 13 from subprocess import PIPE
14 14 from Queue import Empty
15 15
16 16 import nose.tools as nt
17 17
18 18 from ..blockingkernelmanager import BlockingKernelManager
19 19
20 20
21 21 from IPython.testing import decorators as dec
22 22 from IPython.utils import io
23 23 from IPython.utils.traitlets import (
24 24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
25 25 )
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Global setup and utilities
29 29 #-----------------------------------------------------------------------------
30 30
31 31 def setup():
32 32 global KM
33 33 KM = BlockingKernelManager()
34 34
35 35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
36 36 KM.start_channels()
37 37
38 38 # wait for kernel to be ready
39 39 KM.shell_channel.execute("pass")
40 40 KM.shell_channel.get_msg(block=True, timeout=5)
41 41 flush_channels()
42 42
43 43
44 44 def teardown():
45 45 KM.stop_channels()
46 46 KM.shutdown_kernel()
47 47
48 48
49 def flush_channels():
49 def flush_channels(km=None):
50 if km is None:
51 km = KM
50 52 """flush any messages waiting on the queue"""
51 53 for channel in (KM.shell_channel, KM.iopub_channel):
52 54 while True:
53 55 try:
54 56 msg = channel.get_msg(block=True, timeout=0.1)
55 57 except Empty:
56 58 break
57 59 else:
58 60 list(validate_message(msg))
59 61
60 62
61 def execute(code='', **kwargs):
63 def execute(code='', km=None, **kwargs):
62 64 """wrapper for doing common steps for validating an execution request"""
63 shell = KM.shell_channel
64 sub = KM.iopub_channel
65 if km is None:
66 km = KM
67 shell = km.shell_channel
68 sub = km.iopub_channel
65 69
66 70 msg_id = shell.execute(code=code, **kwargs)
67 71 reply = shell.get_msg(timeout=2)
68 72 list(validate_message(reply, 'execute_reply', msg_id))
69 73 busy = sub.get_msg(timeout=2)
70 74 list(validate_message(busy, 'status', msg_id))
71 75 nt.assert_equal(busy['content']['execution_state'], 'busy')
72 76
73 77 if not kwargs.get('silent'):
74 78 pyin = sub.get_msg(timeout=2)
75 79 list(validate_message(pyin, 'pyin', msg_id))
76 80 nt.assert_equal(pyin['content']['code'], code)
77 81
78 82 return msg_id, reply['content']
79 83
80 84 #-----------------------------------------------------------------------------
81 85 # MSG Spec References
82 86 #-----------------------------------------------------------------------------
83 87
84 88
85 89 class Reference(HasTraits):
86 90
87 91 """
88 92 Base class for message spec specification testing.
89 93
90 94 This class is the core of the message specification test. The
91 95 idea is that child classes implement trait attributes for each
92 96 message keys, so that message keys can be tested against these
93 97 traits using :meth:`check` method.
94 98
95 99 """
96 100
97 101 def check(self, d):
98 102 """validate a dict against our traits"""
99 103 for key in self.trait_names():
100 104 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
101 105 # FIXME: always allow None, probably not a good idea
102 106 if d[key] is None:
103 107 continue
104 108 try:
105 109 setattr(self, key, d[key])
106 110 except TraitError as e:
107 111 yield nt.assert_true(False, str(e))
108 112
109 113
110 114 class RMessage(Reference):
111 115 msg_id = Unicode()
112 116 msg_type = Unicode()
113 117 header = Dict()
114 118 parent_header = Dict()
115 119 content = Dict()
116 120
117 121 class RHeader(Reference):
118 122 msg_id = Unicode()
119 123 msg_type = Unicode()
120 124 session = Unicode()
121 125 username = Unicode()
122 126
123 127 class RContent(Reference):
124 128 status = Enum((u'ok', u'error'))
125 129
126 130
127 131 class ExecuteReply(Reference):
128 132 execution_count = Integer()
129 133 status = Enum((u'ok', u'error'))
130 134
131 135 def check(self, d):
132 136 for tst in Reference.check(self, d):
133 137 yield tst
134 138 if d['status'] == 'ok':
135 139 for tst in ExecuteReplyOkay().check(d):
136 140 yield tst
137 141 elif d['status'] == 'error':
138 142 for tst in ExecuteReplyError().check(d):
139 143 yield tst
140 144
141 145
142 146 class ExecuteReplyOkay(Reference):
143 147 payload = List(Dict)
144 148 user_variables = Dict()
145 149 user_expressions = Dict()
146 150
147 151
148 152 class ExecuteReplyError(Reference):
149 153 ename = Unicode()
150 154 evalue = Unicode()
151 155 traceback = List(Unicode)
152 156
153 157
154 158 class OInfoReply(Reference):
155 159 name = Unicode()
156 160 found = Bool()
157 161 ismagic = Bool()
158 162 isalias = Bool()
159 163 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
160 164 type_name = Unicode()
161 165 string_form = Unicode()
162 166 base_class = Unicode()
163 167 length = Integer()
164 168 file = Unicode()
165 169 definition = Unicode()
166 170 argspec = Dict()
167 171 init_definition = Unicode()
168 172 docstring = Unicode()
169 173 init_docstring = Unicode()
170 174 class_docstring = Unicode()
171 175 call_def = Unicode()
172 176 call_docstring = Unicode()
173 177 source = Unicode()
174 178
175 179 def check(self, d):
176 180 for tst in Reference.check(self, d):
177 181 yield tst
178 182 if d['argspec'] is not None:
179 183 for tst in ArgSpec().check(d['argspec']):
180 184 yield tst
181 185
182 186
183 187 class ArgSpec(Reference):
184 188 args = List(Unicode)
185 189 varargs = Unicode()
186 190 varkw = Unicode()
187 191 defaults = List()
188 192
189 193
190 194 class Status(Reference):
191 195 execution_state = Enum((u'busy', u'idle'))
192 196
193 197
194 198 class CompleteReply(Reference):
195 199 matches = List(Unicode)
196 200
197 201
198 202 def Version(num, trait=Integer):
199 203 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
200 204
201 205
202 206 class KernelInfoReply(Reference):
203 207
204 208 protocol_version = Version(2)
205 209 ipython_version = Version(4, Any)
206 210 language_version = Version(3)
207 211 language = Unicode()
208 212
209 213 def _ipython_version_changed(self, name, old, new):
210 214 for v in new:
211 215 nt.assert_true(
212 216 isinstance(v, int) or isinstance(v, basestring),
213 217 'expected int or string as version component, got {0!r}'
214 218 .format(v))
215 219
216 220
217 221 # IOPub messages
218 222
219 223 class PyIn(Reference):
220 224 code = Unicode()
221 225 execution_count = Integer()
222 226
223 227
224 228 PyErr = ExecuteReplyError
225 229
226 230
227 231 class Stream(Reference):
228 232 name = Enum((u'stdout', u'stderr'))
229 233 data = Unicode()
230 234
231 235
232 236 mime_pat = re.compile(r'\w+/\w+')
233 237
234 238 class DisplayData(Reference):
235 239 source = Unicode()
236 240 metadata = Dict()
237 241 data = Dict()
238 242 def _data_changed(self, name, old, new):
239 243 for k,v in new.iteritems():
240 244 nt.assert_true(mime_pat.match(k))
241 245 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
242 246
243 247
244 248 class PyOut(Reference):
245 249 execution_count = Integer()
246 250 data = Dict()
247 251 def _data_changed(self, name, old, new):
248 252 for k,v in new.iteritems():
249 253 nt.assert_true(mime_pat.match(k))
250 254 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
251 255
252 256
253 257 references = {
254 258 'execute_reply' : ExecuteReply(),
255 259 'object_info_reply' : OInfoReply(),
256 260 'status' : Status(),
257 261 'complete_reply' : CompleteReply(),
258 262 'kernel_info_reply': KernelInfoReply(),
259 263 'pyin' : PyIn(),
260 264 'pyout' : PyOut(),
261 265 'pyerr' : PyErr(),
262 266 'stream' : Stream(),
263 267 'display_data' : DisplayData(),
264 268 }
265 269 """
266 270 Specifications of `content` part of the reply messages.
267 271 """
268 272
269 273
270 274 def validate_message(msg, msg_type=None, parent=None):
271 275 """validate a message
272 276
273 277 This is a generator, and must be iterated through to actually
274 278 trigger each test.
275 279
276 280 If msg_type and/or parent are given, the msg_type and/or parent msg_id
277 281 are compared with the given values.
278 282 """
279 283 RMessage().check(msg)
280 284 if msg_type:
281 285 yield nt.assert_equal(msg['msg_type'], msg_type)
282 286 if parent:
283 287 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
284 288 content = msg['content']
285 289 ref = references[msg['msg_type']]
286 290 for tst in ref.check(content):
287 291 yield tst
288 292
289 293
290 294 #-----------------------------------------------------------------------------
291 295 # Tests
292 296 #-----------------------------------------------------------------------------
293 297
294 298 # Shell channel
295 299
296 300 @dec.parametric
297 301 def test_execute():
298 302 flush_channels()
299 303
300 304 shell = KM.shell_channel
301 305 msg_id = shell.execute(code='x=1')
302 306 reply = shell.get_msg(timeout=2)
303 307 for tst in validate_message(reply, 'execute_reply', msg_id):
304 308 yield tst
305 309
306 310
307 311 @dec.parametric
308 312 def test_execute_silent():
309 313 flush_channels()
310 314 msg_id, reply = execute(code='x=1', silent=True)
311 315
312 316 # flush status=idle
313 317 status = KM.iopub_channel.get_msg(timeout=2)
314 318 for tst in validate_message(status, 'status', msg_id):
315 319 yield tst
316 320 nt.assert_equal(status['content']['execution_state'], 'idle')
317 321
318 322 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
319 323 count = reply['execution_count']
320 324
321 325 msg_id, reply = execute(code='x=2', silent=True)
322 326
323 327 # flush status=idle
324 328 status = KM.iopub_channel.get_msg(timeout=2)
325 329 for tst in validate_message(status, 'status', msg_id):
326 330 yield tst
327 331 yield nt.assert_equal(status['content']['execution_state'], 'idle')
328 332
329 333 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
330 334 count_2 = reply['execution_count']
331 335 yield nt.assert_equal(count_2, count)
332 336
333 337
334 338 @dec.parametric
335 339 def test_execute_error():
336 340 flush_channels()
337 341
338 342 msg_id, reply = execute(code='1/0')
339 343 yield nt.assert_equal(reply['status'], 'error')
340 344 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
341 345
342 346 pyerr = KM.iopub_channel.get_msg(timeout=2)
343 347 for tst in validate_message(pyerr, 'pyerr', msg_id):
344 348 yield tst
345 349
346 350
347 351 def test_execute_inc():
348 352 """execute request should increment execution_count"""
349 353 flush_channels()
350 354
351 355 msg_id, reply = execute(code='x=1')
352 356 count = reply['execution_count']
353 357
354 358 flush_channels()
355 359
356 360 msg_id, reply = execute(code='x=2')
357 361 count_2 = reply['execution_count']
358 362 nt.assert_equal(count_2, count+1)
359 363
360 364
361 365 def test_user_variables():
362 366 flush_channels()
363 367
364 368 msg_id, reply = execute(code='x=1', user_variables=['x'])
365 369 user_variables = reply['user_variables']
366 370 nt.assert_equal(user_variables, {u'x' : u'1'})
367 371
368 372
369 373 def test_user_expressions():
370 374 flush_channels()
371 375
372 376 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
373 377 user_expressions = reply['user_expressions']
374 378 nt.assert_equal(user_expressions, {u'foo' : u'2'})
375 379
376 380
377 381 @dec.parametric
378 382 def test_oinfo():
379 383 flush_channels()
380 384
381 385 shell = KM.shell_channel
382 386
383 387 msg_id = shell.object_info('a')
384 388 reply = shell.get_msg(timeout=2)
385 389 for tst in validate_message(reply, 'object_info_reply', msg_id):
386 390 yield tst
387 391
388 392
389 393 @dec.parametric
390 394 def test_oinfo_found():
391 395 flush_channels()
392 396
393 397 shell = KM.shell_channel
394 398
395 399 msg_id, reply = execute(code='a=5')
396 400
397 401 msg_id = shell.object_info('a')
398 402 reply = shell.get_msg(timeout=2)
399 403 for tst in validate_message(reply, 'object_info_reply', msg_id):
400 404 yield tst
401 405 content = reply['content']
402 406 yield nt.assert_true(content['found'])
403 407 argspec = content['argspec']
404 408 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
405 409
406 410
407 411 @dec.parametric
408 412 def test_oinfo_detail():
409 413 flush_channels()
410 414
411 415 shell = KM.shell_channel
412 416
413 417 msg_id, reply = execute(code='ip=get_ipython()')
414 418
415 419 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
416 420 reply = shell.get_msg(timeout=2)
417 421 for tst in validate_message(reply, 'object_info_reply', msg_id):
418 422 yield tst
419 423 content = reply['content']
420 424 yield nt.assert_true(content['found'])
421 425 argspec = content['argspec']
422 426 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
423 427 yield nt.assert_equal(argspec['defaults'], [0])
424 428
425 429
426 430 @dec.parametric
427 431 def test_oinfo_not_found():
428 432 flush_channels()
429 433
430 434 shell = KM.shell_channel
431 435
432 436 msg_id = shell.object_info('dne')
433 437 reply = shell.get_msg(timeout=2)
434 438 for tst in validate_message(reply, 'object_info_reply', msg_id):
435 439 yield tst
436 440 content = reply['content']
437 441 yield nt.assert_false(content['found'])
438 442
439 443
440 444 @dec.parametric
441 445 def test_complete():
442 446 flush_channels()
443 447
444 448 shell = KM.shell_channel
445 449
446 450 msg_id, reply = execute(code="alpha = albert = 5")
447 451
448 452 msg_id = shell.complete('al', 'al', 2)
449 453 reply = shell.get_msg(timeout=2)
450 454 for tst in validate_message(reply, 'complete_reply', msg_id):
451 455 yield tst
452 456 matches = reply['content']['matches']
453 457 for name in ('alpha', 'albert'):
454 458 yield nt.assert_true(name in matches, "Missing match: %r" % name)
455 459
456 460
457 461 @dec.parametric
458 462 def test_kernel_info_request():
459 463 flush_channels()
460 464
461 465 shell = KM.shell_channel
462 466
463 467 msg_id = shell.kernel_info()
464 468 reply = shell.get_msg(timeout=2)
465 469 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
466 470 yield tst
467 471
468 472
469 473 # IOPub channel
470 474
471 475
472 476 @dec.parametric
473 477 def test_stream():
474 478 flush_channels()
475 479
476 480 msg_id, reply = execute("print('hi')")
477 481
478 482 stdout = KM.iopub_channel.get_msg(timeout=2)
479 483 for tst in validate_message(stdout, 'stream', msg_id):
480 484 yield tst
481 485 content = stdout['content']
482 486 yield nt.assert_equal(content['name'], u'stdout')
483 487 yield nt.assert_equal(content['data'], u'hi\n')
484 488
485 489
486 490 @dec.parametric
487 491 def test_display_data():
488 492 flush_channels()
489 493
490 494 msg_id, reply = execute("from IPython.core.display import display; display(1)")
491 495
492 496 display = KM.iopub_channel.get_msg(timeout=2)
493 497 for tst in validate_message(display, 'display_data', parent=msg_id):
494 498 yield tst
495 499 data = display['content']['data']
496 500 yield nt.assert_equal(data['text/plain'], u'1')
497 501
General Comments 0
You need to be logged in to leave comments. Login now