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