##// END OF EJS Templates
Fix code_to_line when called with empty code...
Thomas Kluyver -
Show More
@@ -1,354 +1,356 b''
1 1 """Adapters for IPython msg spec versions."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import json
7 7
8 8 from IPython.core.release import kernel_protocol_version_info
9 9 from IPython.utils.tokenutil import token_at_cursor
10 10
11 11
12 12 def code_to_line(code, cursor_pos):
13 13 """Turn a multiline code block and cursor position into a single line
14 14 and new cursor position.
15 15
16 16 For adapting ``complete_`` and ``object_info_request``.
17 17 """
18 if not code:
19 return "", 0
18 20 for line in code.splitlines(True):
19 21 n = len(line)
20 22 if cursor_pos > n:
21 23 cursor_pos -= n
22 24 else:
23 25 break
24 26 return line, cursor_pos
25 27
26 28
27 29 class Adapter(object):
28 30 """Base class for adapting messages
29 31
30 32 Override message_type(msg) methods to create adapters.
31 33 """
32 34
33 35 msg_type_map = {}
34 36
35 37 def update_header(self, msg):
36 38 return msg
37 39
38 40 def update_metadata(self, msg):
39 41 return msg
40 42
41 43 def update_msg_type(self, msg):
42 44 header = msg['header']
43 45 msg_type = header['msg_type']
44 46 if msg_type in self.msg_type_map:
45 47 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
46 48 return msg
47 49
48 50 def handle_reply_status_error(self, msg):
49 51 """This will be called *instead of* the regular handler
50 52
51 53 on any reply with status != ok
52 54 """
53 55 return msg
54 56
55 57 def __call__(self, msg):
56 58 msg = self.update_header(msg)
57 59 msg = self.update_metadata(msg)
58 60 msg = self.update_msg_type(msg)
59 61 header = msg['header']
60 62
61 63 handler = getattr(self, header['msg_type'], None)
62 64 if handler is None:
63 65 return msg
64 66
65 67 # handle status=error replies separately (no change, at present)
66 68 if msg['content'].get('status', None) in {'error', 'aborted'}:
67 69 return self.handle_reply_status_error(msg)
68 70 return handler(msg)
69 71
70 72 def _version_str_to_list(version):
71 73 """convert a version string to a list of ints
72 74
73 75 non-int segments are excluded
74 76 """
75 77 v = []
76 78 for part in version.split('.'):
77 79 try:
78 80 v.append(int(part))
79 81 except ValueError:
80 82 pass
81 83 return v
82 84
83 85 class V5toV4(Adapter):
84 86 """Adapt msg protocol v5 to v4"""
85 87
86 88 version = '4.1'
87 89
88 90 msg_type_map = {
89 91 'execute_result' : 'pyout',
90 92 'execute_input' : 'pyin',
91 93 'error' : 'pyerr',
92 94 'inspect_request' : 'object_info_request',
93 95 'inspect_reply' : 'object_info_reply',
94 96 }
95 97
96 98 def update_header(self, msg):
97 99 msg['header'].pop('version', None)
98 100 return msg
99 101
100 102 # shell channel
101 103
102 104 def kernel_info_reply(self, msg):
103 105 content = msg['content']
104 106 content.pop('banner', None)
105 107 for key in ('language_version', 'protocol_version'):
106 108 if key in content:
107 109 content[key] = _version_str_to_list(content[key])
108 110 if content.pop('implementation', '') == 'ipython' \
109 111 and 'implementation_version' in content:
110 112 content['ipython_version'] = content.pop('implmentation_version')
111 113 content.pop('implementation_version', None)
112 114 content.setdefault("implmentation", content['language'])
113 115 return msg
114 116
115 117 def execute_request(self, msg):
116 118 content = msg['content']
117 119 content.setdefault('user_variables', [])
118 120 return msg
119 121
120 122 def execute_reply(self, msg):
121 123 content = msg['content']
122 124 content.setdefault('user_variables', {})
123 125 # TODO: handle payloads
124 126 return msg
125 127
126 128 def complete_request(self, msg):
127 129 content = msg['content']
128 130 code = content['code']
129 131 cursor_pos = content['cursor_pos']
130 132 line, cursor_pos = code_to_line(code, cursor_pos)
131 133
132 134 new_content = msg['content'] = {}
133 135 new_content['text'] = ''
134 136 new_content['line'] = line
135 137 new_content['block'] = None
136 138 new_content['cursor_pos'] = cursor_pos
137 139 return msg
138 140
139 141 def complete_reply(self, msg):
140 142 content = msg['content']
141 143 cursor_start = content.pop('cursor_start')
142 144 cursor_end = content.pop('cursor_end')
143 145 match_len = cursor_end - cursor_start
144 146 content['matched_text'] = content['matches'][0][:match_len]
145 147 content.pop('metadata', None)
146 148 return msg
147 149
148 150 def object_info_request(self, msg):
149 151 content = msg['content']
150 152 code = content['code']
151 153 cursor_pos = content['cursor_pos']
152 154 line, _ = code_to_line(code, cursor_pos)
153 155
154 156 new_content = msg['content'] = {}
155 157 new_content['oname'] = token_at_cursor(code, cursor_pos)
156 158 new_content['detail_level'] = content['detail_level']
157 159 return msg
158 160
159 161 def object_info_reply(self, msg):
160 162 """inspect_reply can't be easily backward compatible"""
161 163 msg['content'] = {'found' : False, 'name' : 'unknown'}
162 164 return msg
163 165
164 166 # iopub channel
165 167
166 168 def stream(self, msg):
167 169 content = msg['content']
168 170 content['data'] = content.pop('text')
169 171 return msg
170 172
171 173 def display_data(self, msg):
172 174 content = msg['content']
173 175 content.setdefault("source", "display")
174 176 data = content['data']
175 177 if 'application/json' in data:
176 178 try:
177 179 data['application/json'] = json.dumps(data['application/json'])
178 180 except Exception:
179 181 # warn?
180 182 pass
181 183 return msg
182 184
183 185 # stdin channel
184 186
185 187 def input_request(self, msg):
186 188 msg['content'].pop('password', None)
187 189 return msg
188 190
189 191
190 192 class V4toV5(Adapter):
191 193 """Convert msg spec V4 to V5"""
192 194 version = '5.0'
193 195
194 196 # invert message renames above
195 197 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
196 198
197 199 def update_header(self, msg):
198 200 msg['header']['version'] = self.version
199 201 return msg
200 202
201 203 # shell channel
202 204
203 205 def kernel_info_reply(self, msg):
204 206 content = msg['content']
205 207 for key in ('language_version', 'protocol_version', 'ipython_version'):
206 208 if key in content:
207 209 content[key] = ".".join(map(str, content[key]))
208 210
209 211 if content['language'].startswith('python') and 'ipython_version' in content:
210 212 content['implementation'] = 'ipython'
211 213 content['implementation_version'] = content.pop('ipython_version')
212 214
213 215 content['banner'] = ''
214 216 return msg
215 217
216 218 def execute_request(self, msg):
217 219 content = msg['content']
218 220 user_variables = content.pop('user_variables', [])
219 221 user_expressions = content.setdefault('user_expressions', {})
220 222 for v in user_variables:
221 223 user_expressions[v] = v
222 224 return msg
223 225
224 226 def execute_reply(self, msg):
225 227 content = msg['content']
226 228 user_expressions = content.setdefault('user_expressions', {})
227 229 user_variables = content.pop('user_variables', {})
228 230 if user_variables:
229 231 user_expressions.update(user_variables)
230 232
231 233 # Pager payloads became a mime bundle
232 234 for payload in content.get('payload', []):
233 235 if payload.get('source', None) == 'page' and ('text' in payload):
234 236 if 'data' not in payload:
235 237 payload['data'] = {}
236 238 payload['data']['text/plain'] = payload.pop('text')
237 239
238 240 return msg
239 241
240 242 def complete_request(self, msg):
241 243 old_content = msg['content']
242 244
243 245 new_content = msg['content'] = {}
244 246 new_content['code'] = old_content['line']
245 247 new_content['cursor_pos'] = old_content['cursor_pos']
246 248 return msg
247 249
248 250 def complete_reply(self, msg):
249 251 # complete_reply needs more context than we have to get cursor_start and end.
250 252 # use special value of `-1` to indicate to frontend that it should be at
251 253 # the current cursor position.
252 254 content = msg['content']
253 255 new_content = msg['content'] = {'status' : 'ok'}
254 256 new_content['matches'] = content['matches']
255 257 new_content['cursor_start'] = -len(content['matched_text'])
256 258 new_content['cursor_end'] = None
257 259 new_content['metadata'] = {}
258 260 return msg
259 261
260 262 def inspect_request(self, msg):
261 263 content = msg['content']
262 264 name = content['oname']
263 265
264 266 new_content = msg['content'] = {}
265 267 new_content['code'] = name
266 268 new_content['cursor_pos'] = len(name)
267 269 new_content['detail_level'] = content['detail_level']
268 270 return msg
269 271
270 272 def inspect_reply(self, msg):
271 273 """inspect_reply can't be easily backward compatible"""
272 274 content = msg['content']
273 275 new_content = msg['content'] = {'status' : 'ok'}
274 276 found = new_content['found'] = content['found']
275 277 new_content['name'] = content['name']
276 278 new_content['data'] = data = {}
277 279 new_content['metadata'] = {}
278 280 if found:
279 281 lines = []
280 282 for key in ('call_def', 'init_definition', 'definition'):
281 283 if content.get(key, False):
282 284 lines.append(content[key])
283 285 break
284 286 for key in ('call_docstring', 'init_docstring', 'docstring'):
285 287 if content.get(key, False):
286 288 lines.append(content[key])
287 289 break
288 290 if not lines:
289 291 lines.append("<empty docstring>")
290 292 data['text/plain'] = '\n'.join(lines)
291 293 return msg
292 294
293 295 # iopub channel
294 296
295 297 def stream(self, msg):
296 298 content = msg['content']
297 299 content['text'] = content.pop('data')
298 300 return msg
299 301
300 302 def display_data(self, msg):
301 303 content = msg['content']
302 304 content.pop("source", None)
303 305 data = content['data']
304 306 if 'application/json' in data:
305 307 try:
306 308 data['application/json'] = json.loads(data['application/json'])
307 309 except Exception:
308 310 # warn?
309 311 pass
310 312 return msg
311 313
312 314 # stdin channel
313 315
314 316 def input_request(self, msg):
315 317 msg['content'].setdefault('password', False)
316 318 return msg
317 319
318 320
319 321
320 322 def adapt(msg, to_version=kernel_protocol_version_info[0]):
321 323 """Adapt a single message to a target version
322 324
323 325 Parameters
324 326 ----------
325 327
326 328 msg : dict
327 329 An IPython message.
328 330 to_version : int, optional
329 331 The target major version.
330 332 If unspecified, adapt to the current version for IPython.
331 333
332 334 Returns
333 335 -------
334 336
335 337 msg : dict
336 338 An IPython message appropriate in the new version.
337 339 """
338 340 header = msg['header']
339 341 if 'version' in header:
340 342 from_version = int(header['version'].split('.')[0])
341 343 else:
342 344 # assume last version before adding the key to the header
343 345 from_version = 4
344 346 adapter = adapters.get((from_version, to_version), None)
345 347 if adapter is None:
346 348 return msg
347 349 return adapter(msg)
348 350
349 351
350 352 # one adapter per major version from,to
351 353 adapters = {
352 354 (5,4) : V5toV4(),
353 355 (4,5) : V4toV5(),
354 356 }
@@ -1,330 +1,334 b''
1 1 """Tests for adapting IPython msg spec versions"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import copy
7 7 import json
8 8 from unittest import TestCase
9 9 import nose.tools as nt
10 10
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4, code_to_line
12 12 from IPython.kernel.zmq.session import Session
13 13
14 14
15 15 def test_default_version():
16 16 s = Session()
17 17 msg = s.msg("msg_type")
18 18 msg['header'].pop('version')
19 19 original = copy.deepcopy(msg)
20 20 adapted = adapt(original)
21 21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
22 22
23 def test_code_to_line_no_code():
24 line, pos = code_to_line("", 0)
25 nt.assert_equal(line, "")
26 nt.assert_equal(pos, 0)
23 27
24 28 class AdapterTest(TestCase):
25 29
26 30 def setUp(self):
27 31 self.session = Session()
28 32
29 33 def adapt(self, msg, version=None):
30 34 original = copy.deepcopy(msg)
31 35 adapted = adapt(msg, version or self.to_version)
32 36 return original, adapted
33 37
34 38 def check_header(self, msg):
35 39 pass
36 40
37 41
38 42 class V4toV5TestCase(AdapterTest):
39 43 from_version = 4
40 44 to_version = 5
41 45
42 46 def msg(self, msg_type, content):
43 47 """Create a v4 msg (same as v5, minus version header)"""
44 48 msg = self.session.msg(msg_type, content)
45 49 msg['header'].pop('version')
46 50 return msg
47 51
48 52 def test_same_version(self):
49 53 msg = self.msg("execute_result",
50 54 content={'status' : 'ok'}
51 55 )
52 56 original, adapted = self.adapt(msg, self.from_version)
53 57
54 58 self.assertEqual(original, adapted)
55 59
56 60 def test_no_adapt(self):
57 61 msg = self.msg("input_reply", {'value' : 'some text'})
58 62 v4, v5 = self.adapt(msg)
59 63 self.assertEqual(v5['header']['version'], V4toV5.version)
60 64 v5['header'].pop('version')
61 65 self.assertEqual(v4, v5)
62 66
63 67 def test_rename_type(self):
64 68 for v5_type, v4_type in [
65 69 ('execute_result', 'pyout'),
66 70 ('execute_input', 'pyin'),
67 71 ('error', 'pyerr'),
68 72 ]:
69 73 msg = self.msg(v4_type, {'key' : 'value'})
70 74 v4, v5 = self.adapt(msg)
71 75 self.assertEqual(v5['header']['version'], V4toV5.version)
72 76 self.assertEqual(v5['header']['msg_type'], v5_type)
73 77 self.assertEqual(v4['content'], v5['content'])
74 78
75 79 def test_execute_request(self):
76 80 msg = self.msg("execute_request", {
77 81 'code' : 'a=5',
78 82 'silent' : False,
79 83 'user_expressions' : {'a' : 'apple'},
80 84 'user_variables' : ['b'],
81 85 })
82 86 v4, v5 = self.adapt(msg)
83 87 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
84 88 v4c = v4['content']
85 89 v5c = v5['content']
86 90 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
87 91 self.assertNotIn('user_variables', v5c)
88 92 self.assertEqual(v5c['code'], v4c['code'])
89 93
90 94 def test_execute_reply(self):
91 95 msg = self.msg("execute_reply", {
92 96 'status': 'ok',
93 97 'execution_count': 7,
94 98 'user_variables': {'a': 1},
95 99 'user_expressions': {'a+a': 2},
96 100 'payload': [{'source':'page', 'text':'blah'}]
97 101 })
98 102 v4, v5 = self.adapt(msg)
99 103 v5c = v5['content']
100 104 self.assertNotIn('user_variables', v5c)
101 105 self.assertEqual(v5c['user_expressions'], {'a': 1, 'a+a': 2})
102 106 self.assertEqual(v5c['payload'], [{'source': 'page',
103 107 'data': {'text/plain': 'blah'}}
104 108 ])
105 109
106 110 def test_complete_request(self):
107 111 msg = self.msg("complete_request", {
108 112 'text' : 'a.is',
109 113 'line' : 'foo = a.is',
110 114 'block' : None,
111 115 'cursor_pos' : 10,
112 116 })
113 117 v4, v5 = self.adapt(msg)
114 118 v4c = v4['content']
115 119 v5c = v5['content']
116 120 for key in ('text', 'line', 'block'):
117 121 self.assertNotIn(key, v5c)
118 122 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
119 123 self.assertEqual(v5c['code'], v4c['line'])
120 124
121 125 def test_complete_reply(self):
122 126 msg = self.msg("complete_reply", {
123 127 'matched_text' : 'a.is',
124 128 'matches' : ['a.isalnum',
125 129 'a.isalpha',
126 130 'a.isdigit',
127 131 'a.islower',
128 132 ],
129 133 })
130 134 v4, v5 = self.adapt(msg)
131 135 v4c = v4['content']
132 136 v5c = v5['content']
133 137
134 138 self.assertEqual(v5c['matches'], v4c['matches'])
135 139 self.assertEqual(v5c['metadata'], {})
136 140 self.assertEqual(v5c['cursor_start'], -4)
137 141 self.assertEqual(v5c['cursor_end'], None)
138 142
139 143 def test_object_info_request(self):
140 144 msg = self.msg("object_info_request", {
141 145 'oname' : 'foo',
142 146 'detail_level' : 1,
143 147 })
144 148 v4, v5 = self.adapt(msg)
145 149 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
146 150 v4c = v4['content']
147 151 v5c = v5['content']
148 152 self.assertEqual(v5c['code'], v4c['oname'])
149 153 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
150 154 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
151 155
152 156 def test_object_info_reply(self):
153 157 msg = self.msg("object_info_reply", {
154 158 'name' : 'foo',
155 159 'found' : True,
156 160 'status' : 'ok',
157 161 'definition' : 'foo(a=5)',
158 162 'docstring' : "the docstring",
159 163 })
160 164 v4, v5 = self.adapt(msg)
161 165 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
162 166 v4c = v4['content']
163 167 v5c = v5['content']
164 168 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
165 169 text = v5c['data']['text/plain']
166 170 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
167 171
168 172 # iopub channel
169 173
170 174 def test_display_data(self):
171 175 jsondata = dict(a=5)
172 176 msg = self.msg("display_data", {
173 177 'data' : {
174 178 'text/plain' : 'some text',
175 179 'application/json' : json.dumps(jsondata)
176 180 },
177 181 'metadata' : {'text/plain' : { 'key' : 'value' }},
178 182 })
179 183 v4, v5 = self.adapt(msg)
180 184 v4c = v4['content']
181 185 v5c = v5['content']
182 186 self.assertEqual(v5c['metadata'], v4c['metadata'])
183 187 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
184 188 self.assertEqual(v5c['data']['application/json'], jsondata)
185 189
186 190 # stdin channel
187 191
188 192 def test_input_request(self):
189 193 msg = self.msg('input_request', {'prompt': "$>"})
190 194 v4, v5 = self.adapt(msg)
191 195 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
192 196 self.assertFalse(v5['content']['password'])
193 197
194 198
195 199 class V5toV4TestCase(AdapterTest):
196 200 from_version = 5
197 201 to_version = 4
198 202
199 203 def msg(self, msg_type, content):
200 204 return self.session.msg(msg_type, content)
201 205
202 206 def test_same_version(self):
203 207 msg = self.msg("execute_result",
204 208 content={'status' : 'ok'}
205 209 )
206 210 original, adapted = self.adapt(msg, self.from_version)
207 211
208 212 self.assertEqual(original, adapted)
209 213
210 214 def test_no_adapt(self):
211 215 msg = self.msg("input_reply", {'value' : 'some text'})
212 216 v5, v4 = self.adapt(msg)
213 217 self.assertNotIn('version', v4['header'])
214 218 v5['header'].pop('version')
215 219 self.assertEqual(v4, v5)
216 220
217 221 def test_rename_type(self):
218 222 for v5_type, v4_type in [
219 223 ('execute_result', 'pyout'),
220 224 ('execute_input', 'pyin'),
221 225 ('error', 'pyerr'),
222 226 ]:
223 227 msg = self.msg(v5_type, {'key' : 'value'})
224 228 v5, v4 = self.adapt(msg)
225 229 self.assertEqual(v4['header']['msg_type'], v4_type)
226 230 nt.assert_not_in('version', v4['header'])
227 231 self.assertEqual(v4['content'], v5['content'])
228 232
229 233 def test_execute_request(self):
230 234 msg = self.msg("execute_request", {
231 235 'code' : 'a=5',
232 236 'silent' : False,
233 237 'user_expressions' : {'a' : 'apple'},
234 238 })
235 239 v5, v4 = self.adapt(msg)
236 240 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
237 241 v4c = v4['content']
238 242 v5c = v5['content']
239 243 self.assertEqual(v4c['user_variables'], [])
240 244 self.assertEqual(v5c['code'], v4c['code'])
241 245
242 246 def test_complete_request(self):
243 247 msg = self.msg("complete_request", {
244 248 'code' : 'def foo():\n'
245 249 ' a.is\n'
246 250 'foo()',
247 251 'cursor_pos': 19,
248 252 })
249 253 v5, v4 = self.adapt(msg)
250 254 v4c = v4['content']
251 255 v5c = v5['content']
252 256 self.assertNotIn('code', v4c)
253 257 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
254 258 self.assertEqual(v4c['cursor_pos'], 8)
255 259 self.assertEqual(v4c['text'], '')
256 260 self.assertEqual(v4c['block'], None)
257 261
258 262 def test_complete_reply(self):
259 263 msg = self.msg("complete_reply", {
260 264 'cursor_start' : 10,
261 265 'cursor_end' : 14,
262 266 'matches' : ['a.isalnum',
263 267 'a.isalpha',
264 268 'a.isdigit',
265 269 'a.islower',
266 270 ],
267 271 'metadata' : {},
268 272 })
269 273 v5, v4 = self.adapt(msg)
270 274 v4c = v4['content']
271 275 v5c = v5['content']
272 276 self.assertEqual(v4c['matched_text'], 'a.is')
273 277 self.assertEqual(v4c['matches'], v5c['matches'])
274 278
275 279 def test_inspect_request(self):
276 280 msg = self.msg("inspect_request", {
277 281 'code' : 'def foo():\n'
278 282 ' apple\n'
279 283 'bar()',
280 284 'cursor_pos': 18,
281 285 'detail_level' : 1,
282 286 })
283 287 v5, v4 = self.adapt(msg)
284 288 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
285 289 v4c = v4['content']
286 290 v5c = v5['content']
287 291 self.assertEqual(v4c['oname'], 'apple')
288 292 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
289 293
290 294 def test_inspect_reply(self):
291 295 msg = self.msg("inspect_reply", {
292 296 'name' : 'foo',
293 297 'found' : True,
294 298 'data' : {'text/plain' : 'some text'},
295 299 'metadata' : {},
296 300 })
297 301 v5, v4 = self.adapt(msg)
298 302 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
299 303 v4c = v4['content']
300 304 v5c = v5['content']
301 305 self.assertEqual(sorted(v4c), ['found', 'name'])
302 306 self.assertEqual(v4c['found'], False)
303 307
304 308 # iopub channel
305 309
306 310 def test_display_data(self):
307 311 jsondata = dict(a=5)
308 312 msg = self.msg("display_data", {
309 313 'data' : {
310 314 'text/plain' : 'some text',
311 315 'application/json' : jsondata,
312 316 },
313 317 'metadata' : {'text/plain' : { 'key' : 'value' }},
314 318 })
315 319 v5, v4 = self.adapt(msg)
316 320 v4c = v4['content']
317 321 v5c = v5['content']
318 322 self.assertEqual(v5c['metadata'], v4c['metadata'])
319 323 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
320 324 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
321 325
322 326 # stdin channel
323 327
324 328 def test_input_request(self):
325 329 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
326 330 v5, v4 = self.adapt(msg)
327 331 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
328 332 self.assertNotIn('password', v4['content'])
329 333
330 334
General Comments 0
You need to be logged in to leave comments. Login now