From 8d33e4ed059f471aa01550815546e195fcf11e84 Mon Sep 17 00:00:00 2001 From: Jonathan Jacobs Date: Sat, 6 Jun 2015 23:36:28 +0200 Subject: [PATCH] Improve test coverage. --- eliottree/render.py | 14 ++-- eliottree/test/tasks.py | 60 ++++++++++------ eliottree/test/test_render.py | 127 +++++++++++++++++++++++++++++++++- 3 files changed, 170 insertions(+), 31 deletions(-) diff --git a/eliottree/render.py b/eliottree/render.py index 9e2febd..7a0e215 100644 --- a/eliottree/render.py +++ b/eliottree/render.py @@ -10,13 +10,13 @@ def _format_value(value, encoding): Format a value for a task tree. """ if isinstance(value, datetime): - return value.isoformat(' ') + return value.isoformat(' ').encode(encoding) elif isinstance(value, unicode): return value.encode(encoding) - elif isinstance(value, dict): - return value - # XXX: This is probably wrong in a bunch of places. - return str(value) + elif isinstance(value, str): + # We guess bytes values are UTF-8. + return value.decode('utf-8', 'replace').encode(encoding) + return repr(value).decode('utf-8', 'replace').encode(encoding) def _indented_write(write): @@ -64,7 +64,6 @@ def _render_task(write, task, ignored_task_keys, field_limit, encoding): for i, (key, value) in enumerate(sorted(task.items()), 1): if key not in ignored_task_keys: tree_char = '`' if i == num_items else '|' - _value = _format_value(value, encoding) key = key.encode(encoding) if isinstance(value, dict): write( @@ -72,11 +71,12 @@ def _render_task(write, task, ignored_task_keys, field_limit, encoding): tree_char=tree_char, key=key)) _render_task(write=_write, - task=_value, + task=value, ignored_task_keys={}, field_limit=field_limit, encoding=encoding) else: + _value = _format_value(value, encoding) if field_limit: first_line = _truncate_value(_value, field_limit) else: diff --git a/eliottree/test/tasks.py b/eliottree/test/tasks.py index 64e1556..cbdac67 100644 --- a/eliottree/test/tasks.py +++ b/eliottree/test/tasks.py @@ -1,29 +1,45 @@ message_task = { - "task_uuid": "cdeb220d-7605-4d5f-8341-1a170222e308", - "error": False, - "timestamp": 1425356700, - "message": "Main loop terminated.", - "message_type": "twisted:log", - "action_type": "nope", - "task_level": [1]} + u"task_uuid": u"cdeb220d-7605-4d5f-8341-1a170222e308", + u"error": False, + u"timestamp": 1425356700, + u"message": u"Main loop terminated.", + u"message_type": u"twisted:log", + u"action_type": u"nope", + u"task_level": [1]} action_task = { - "timestamp": 1425356800, - "action_status": "started", - "task_uuid": "f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", - "action_type": "app:action", - "task_level": [1]} + u"timestamp": 1425356800, + u"action_status": u"started", + u"task_uuid": u"f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", + u"action_type": u"app:action", + u"task_level": [1]} nested_action_task = { - "timestamp": 1425356900, - "action_status": "started", - "task_uuid": "f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", - "action_type": "app:action:nested", - "task_level": [1, 1]} + u"timestamp": 1425356900, + u"action_status": u"started", + u"task_uuid": u"f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", + u"action_type": u"app:action:nested", + u"task_level": [1, 1]} action_task_end = { - "timestamp": 1425356800, - "action_status": "succeeded", - "task_uuid": "f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", - "action_type": "app:action", - "task_level": [2]} + u"timestamp": 1425356800, + u"action_status": u"succeeded", + u"task_uuid": u"f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", + u"action_type": u"app:action", + u"task_level": [2]} + +dict_action_task = { + u"timestamp": 1425356800, + u"action_status": u"started", + u"task_uuid": u"f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", + u"action_type": u"app:action", + u"task_level": [1], + u"some_data": {u"a": 42}} + +multiline_action_task = { + u"timestamp": 1425356800, + u"action_status": u"started", + u"task_uuid": u"f3a32bb3-ea6b-457c-aa99-08a3d0491ab4", + u"action_type": u"app:action", + u"task_level": [1], + u"message": u"this is a\nmany line message"} diff --git a/eliottree/test/test_render.py b/eliottree/test/test_render.py index 2a4c1ab..37f7e24 100644 --- a/eliottree/test/test_render.py +++ b/eliottree/test/test_render.py @@ -1,11 +1,73 @@ +from datetime import datetime from StringIO import StringIO from testtools import TestCase from testtools.matchers import Equals from eliottree import Tree, render_task_nodes +from eliottree.render import _format_value from eliottree.test.tasks import ( - action_task, action_task_end, message_task, nested_action_task) + action_task, action_task_end, dict_action_task, message_task, + multiline_action_task, nested_action_task) + + +class FormatValueTests(TestCase): + """ + Tests for ``eliottree.render._format_value``. + """ + def test_datetime(self): + """ + Format ``datetime`` values as ISO8601. + """ + now = datetime(2015, 6, 6, 22, 57, 12) + self.assertThat( + _format_value(now, 'utf-8'), + Equals('2015-06-06 22:57:12')) + self.assertThat( + _format_value(now, 'utf-16'), + Equals('\xff\xfe2\x000\x001\x005\x00-\x000\x006\x00-\x000\x006' + '\x00 \x002\x002\x00:\x005\x007\x00:\x001\x002\x00')) + + def test_unicode(self): + """ + Encode ``unicode`` values as the specified encoding. + """ + self.assertThat( + _format_value(u'\N{SNOWMAN}', 'utf-8'), + Equals('\xe2\x98\x83')) + self.assertThat( + _format_value(u'\N{SNOWMAN}', 'utf-16'), + Equals('\xff\xfe\x03&')) + + def test_str(self): + """ + Assume that ``str`` values are UTF-8. + """ + self.assertThat( + _format_value('foo', 'utf-8'), + Equals('foo')) + self.assertThat( + _format_value('\xe2\x98\x83', 'utf-8'), + Equals('\xe2\x98\x83')) + self.assertThat( + _format_value('\xff\xfe\x03&', 'utf-8'), + Equals('\xef\xbf\xbd\xef\xbf\xbd\x03&')) + + def test_other(self): + """ + Pass unknown values to ``repr`` and encode as ``utf-8`` while replacing + encoding errors. + """ + self.assertThat( + _format_value(42, 'utf-8'), + Equals('42')) + self.assertThat( + _format_value({u'a': u'\N{SNOWMAN}'}, 'utf-8'), + Equals("{u'a': u'\\u2603'}")) + self.assertThat( + _format_value({u'a': u'\N{SNOWMAN}'}, 'utf-16'), + Equals("\xff\xfe{\x00u\x00'\x00a\x00'\x00:\x00 \x00u\x00'" + "\x00\\\x00u\x002\x006\x000\x003\x00'\x00}\x00")) class RenderTaskNodesTests(TestCase): @@ -33,6 +95,47 @@ def test_tasks(self): ' +-- app:action@2/succeeded\n' ' `-- timestamp: 1425356800\n\n')) + def test_multiline_field(self): + """ + When no field limit is specified for task values, multiple lines are + output for multiline tasks. + """ + fd = StringIO() + tree = Tree() + tree.merge_tasks([multiline_action_task]) + render_task_nodes( + write=fd.write, + nodes=tree.nodes(), + field_limit=0) + self.assertThat( + fd.getvalue(), + Equals( + 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' + ' +-- app:action@1/started\n' + ' |-- message: this is a\n' + ' many line message\n' + ' `-- timestamp: 1425356800\n\n')) + + def test_multiline_field_limit(self): + """ + When a field limit is specified for task values, only the first of + multiple lines is output. + """ + fd = StringIO() + tree = Tree() + tree.merge_tasks([multiline_action_task]) + render_task_nodes( + write=fd.write, + nodes=tree.nodes(), + field_limit=1000) + self.assertThat( + fd.getvalue(), + Equals( + 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' + ' +-- app:action@1/started\n' + ' |-- message: this is a [...]\n' + ' `-- timestamp: 1425356800\n\n')) + def test_field_limit(self): """ Truncate task values that are longer than the field_limit if specified. @@ -78,7 +181,7 @@ def test_ignored_keys(self): def test_task_data(self): """ - Custom task data is rendered as tree elements. + Task data is rendered as tree elements. """ fd = StringIO() tree = Tree() @@ -97,6 +200,26 @@ def test_task_data(self): ' |-- message_type: twisted:log\n' ' `-- timestamp: 1425356700\n\n')) + def test_dict_data(self): + """ + Task values that are ``dict``s are rendered as tree elements. + """ + fd = StringIO() + tree = Tree() + tree.merge_tasks([dict_action_task]) + render_task_nodes( + write=fd.write, + nodes=tree.nodes(), + field_limit=0) + self.assertThat( + fd.getvalue(), + Equals( + 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' + ' +-- app:action@1/started\n' + ' |-- some_data:\n' + ' `-- a: 42\n' + ' `-- timestamp: 1425356800\n\n')) + def test_nested(self): """ Render nested tasks in a way that visually represents that nesting.