From fb9c5ce35512200cf9b67eb971fe76c10d04af91 2014-05-09 19:04:07 From: MinRK Date: 2014-05-09 19:04:07 Subject: [PATCH] further adapter tests, and associated fixes --- diff --git a/IPython/kernel/adapter.py b/IPython/kernel/adapter.py index 615ac47..5d33e97 100644 --- a/IPython/kernel/adapter.py +++ b/IPython/kernel/adapter.py @@ -54,6 +54,9 @@ class Adapter(object): handler = getattr(self, header['msg_type'], None) if handler is None: return msg + # no transform for status=error + if msg['content'].get('status', None) in {'error', 'aborted'}: + return msg return handler(msg) def _version_str_to_list(version): @@ -62,7 +65,7 @@ def _version_str_to_list(version): non-int segments are excluded """ v = [] - for part in content[key].split('.'): + for part in version.split('.'): try: v.append(int(part)) except ValueError: @@ -121,7 +124,7 @@ class V5toV4(Adapter): new_content = msg['content'] = {} new_content['text'] = '' new_content['line'] = line - new_content['blob'] = None + new_content['block'] = None new_content['cursor_pos'] = cursor_pos return msg @@ -138,10 +141,10 @@ class V5toV4(Adapter): content = msg['content'] code = content['code'] cursor_pos = content['cursor_pos'] - line, cursor_pos = code_to_line(code, cursor_pos) + line, _ = code_to_line(code, cursor_pos) - new_content = msg['content'] = {} - new_content['name'] = token_at_cursor(code, cursor_pos) + new_content = msg['content'] = {'status' : 'ok'} + new_content['oname'] = token_at_cursor(code, cursor_pos) new_content['detail_level'] = content['detail_level'] return msg @@ -155,6 +158,13 @@ class V5toV4(Adapter): def display_data(self, msg): content = msg['content'] content.setdefault("source", "display") + data = content['data'] + if 'application/json' in data: + try: + data['application/json'] = json.dumps(data['application/json']) + except Exception: + # warn? + pass return msg # stdin channel @@ -219,25 +229,45 @@ class V4toV5(Adapter): def complete_reply(self, msg): # TODO: complete_reply needs more context than we have # Maybe strip common prefix and give magic cursor_start = cursor_end = 0? - content = msg['content'] = {} - content['matches'] = [] - content['cursor_start'] = content['cursor_end'] = 0 - content['metadata'] = {} + content = msg['content'] + new_content = msg['content'] = {'status' : 'ok'} + n = len(content['matched_text']) + new_content['matches'] = [ m[n:] for m in content['matches'] ] + new_content['cursor_start'] = new_content['cursor_end'] = 0 + new_content['metadata'] = {} return msg def inspect_request(self, msg): content = msg['content'] - name = content['name'] + name = content['oname'] new_content = msg['content'] = {} new_content['code'] = name - new_content['cursor_pos'] = len(name) - 1 + new_content['cursor_pos'] = len(name) new_content['detail_level'] = content['detail_level'] return msg def inspect_reply(self, msg): """inspect_reply can't be easily backward compatible""" - msg['content'] = {'found' : False, 'name' : 'unknown'} + content = msg['content'] + new_content = msg['content'] = {'status' : 'ok'} + found = new_content['found'] = content['found'] + new_content['name'] = content['name'] + new_content['data'] = data = {} + new_content['metadata'] = {} + if found: + lines = [] + for key in ('call_def', 'init_definition', 'definition'): + if content.get(key, False): + lines.append(content[key]) + break + for key in ('call_docstring', 'init_docstring', 'docstring'): + if content.get(key, False): + lines.append(content[key]) + break + if not lines: + lines.append("") + data['text/plain'] = '\n'.join(lines) return msg # iopub channel @@ -247,7 +277,11 @@ class V4toV5(Adapter): content.pop("source", None) data = content['data'] if 'application/json' in data: - data['application/json'] = json.dumps(data['application/json']) + try: + data['application/json'] = json.loads(data['application/json']) + except Exception: + # warn? + pass return msg # stdin channel diff --git a/IPython/kernel/tests/test_adapter.py b/IPython/kernel/tests/test_adapter.py index 64f5f6a..46873c7 100644 --- a/IPython/kernel/tests/test_adapter.py +++ b/IPython/kernel/tests/test_adapter.py @@ -4,7 +4,7 @@ # Distributed under the terms of the Modified BSD License. import copy - +import json from unittest import TestCase import nose.tools as nt @@ -18,13 +18,12 @@ def test_default_version(): msg['header'].pop('version') original = copy.deepcopy(msg) adapted = adapt(original) - nt.assert_equal(adapted['header'].version, V4toV5.version) + nt.assert_equal(adapted['header']['version'], V4toV5.version) class AdapterTest(TestCase): def setUp(self): - print("hi") self.session = Session() def adapt(self, msg, version=None): @@ -85,25 +84,90 @@ class V4toV5TestCase(AdapterTest): v4c = v4['content'] v5c = v5['content'] self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'}) - nt.assert_not_in('user_variables', v5c) + self.assertNotIn('user_variables', v5c) self.assertEqual(v5c['code'], v4c['code']) def test_complete_request(self): - pass + msg = self.msg("complete_request", { + 'text' : 'a.is', + 'line' : 'foo = a.is', + 'block' : None, + 'cursor_pos' : 10, + }) + v4, v5 = self.adapt(msg) + v4c = v4['content'] + v5c = v5['content'] + for key in ('text', 'line', 'block'): + self.assertNotIn(key, v5c) + self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos']) + self.assertEqual(v5c['code'], v4c['line']) def test_complete_reply(self): - pass + msg = self.msg("complete_reply", { + 'matched_text' : 'a.is', + 'matches' : ['a.isalnum', + 'a.isalpha', + 'a.isdigit', + 'a.islower', + ], + }) + v4, v5 = self.adapt(msg) + v4c = v4['content'] + v5c = v5['content'] + n = len(v4c['matched_text']) + expected_matches = [ m[n:] for m in v4c['matches'] ] + + self.assertEqual(v5c['matches'], expected_matches) + self.assertEqual(v5c['metadata'], {}) + self.assertEqual(v5c['cursor_start'], 0) + self.assertEqual(v5c['cursor_end'], 0) def test_object_info_request(self): - pass + msg = self.msg("object_info_request", { + 'oname' : 'foo', + 'detail_level' : 1, + }) + v4, v5 = self.adapt(msg) + self.assertEqual(v5['header']['msg_type'], 'inspect_request') + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(v5c['code'], v4c['oname']) + self.assertEqual(v5c['cursor_pos'], len(v4c['oname'])) + self.assertEqual(v5c['detail_level'], v4c['detail_level']) def test_object_info_reply(self): - pass + msg = self.msg("object_info_reply", { + 'name' : 'foo', + 'found' : True, + 'status' : 'ok', + 'definition' : 'foo(a=5)', + 'docstring' : "the docstring", + }) + v4, v5 = self.adapt(msg) + self.assertEqual(v5['header']['msg_type'], 'inspect_reply') + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status']) + text = v5c['data']['text/plain'] + self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']])) # iopub channel def test_display_data(self): - pass + jsondata = dict(a=5) + msg = self.msg("display_data", { + 'data' : { + 'text/plain' : 'some text', + 'application/json' : json.dumps(jsondata) + }, + 'metadata' : {'text/plain' : { 'key' : 'value' }}, + }) + v4, v5 = self.adapt(msg) + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(v5c['metadata'], v4c['metadata']) + self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain']) + self.assertEqual(v5c['data']['application/json'], jsondata) # stdin channel @@ -162,21 +226,84 @@ class V5toV4TestCase(AdapterTest): self.assertEqual(v5c['code'], v4c['code']) def test_complete_request(self): - pass + msg = self.msg("complete_request", { + 'code' : 'def foo():\n' + ' a.is\n' + 'foo()', + 'cursor_pos': 19, + }) + v5, v4 = self.adapt(msg) + v4c = v4['content'] + v5c = v5['content'] + self.assertNotIn('code', v4c) + self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1]) + self.assertEqual(v4c['cursor_pos'], 8) + self.assertEqual(v4c['text'], '') + self.assertEqual(v4c['block'], None) def test_complete_reply(self): - pass + msg = self.msg("complete_reply", { + 'cursor_start' : 10, + 'cursor_end' : 14, + 'matches' : ['a.isalnum', + 'a.isalpha', + 'a.isdigit', + 'a.islower', + ], + 'metadata' : {}, + }) + v5, v4 = self.adapt(msg) + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(v4c['matched_text'], 'a.is') + self.assertEqual(v4c['matches'], v5c['matches']) def test_inspect_request(self): - pass + msg = self.msg("inspect_request", { + 'code' : 'def foo():\n' + ' apple\n' + 'bar()', + 'cursor_pos': 18, + 'detail_level' : 1, + }) + v5, v4 = self.adapt(msg) + self.assertEqual(v4['header']['msg_type'], 'object_info_request') + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(v4c['oname'], 'apple') + self.assertEqual(v5c['detail_level'], v4c['detail_level']) def test_inspect_reply(self): - pass + msg = self.msg("inspect_reply", { + 'name' : 'foo', + 'found' : True, + 'data' : {'text/plain' : 'some text'}, + 'metadata' : {}, + }) + v5, v4 = self.adapt(msg) + self.assertEqual(v4['header']['msg_type'], 'object_info_reply') + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(sorted(v4c), ['found', 'name']) + self.assertEqual(v4c['found'], False) # iopub channel def test_display_data(self): - pass + jsondata = dict(a=5) + msg = self.msg("display_data", { + 'data' : { + 'text/plain' : 'some text', + 'application/json' : jsondata, + }, + 'metadata' : {'text/plain' : { 'key' : 'value' }}, + }) + v5, v4 = self.adapt(msg) + v4c = v4['content'] + v5c = v5['content'] + self.assertEqual(v5c['metadata'], v4c['metadata']) + self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain']) + self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata)) # stdin channel