def __init__(self, document_manager, registry=None, prune=False): """Constructor. Args: document_manager: [HtmlDocumentManager] Helps with look & feel, and structure. registry: [dict] Registry of processing methods keyed by record type in the journal. If not defined, then use the default. prune: [bool] If true, take liberties with pruning the information so that it is more concise and readable for a typical use case of verifying tests passed and investigating why they have not. """ if registry is None: registry = { 'JsonSnapshot': self.render_snapshot, 'JournalContextControl': self.handle_context_control, 'JournalMessage': self.render_message } super(HtmlRenderer, self).__init__(registry=registry) self.__entity_manager = ProcessedEntityManager() self.__document_manager = document_manager # The context stack is an array of array of strings, # Where each top level array is a context we've pushed, which # contains fragments of entities. If we've pushed a context, then # well render into the stack. Otherwise we'll render into the document. # When we pop a context we'll render it into the parent context until # we pop the root context, which will finally render into the document. self.__context_stack = [None] self.__context_stack_timestamp_prefix = [None] self.__prune = prune
def test_process_snapshot(self): """Test the conversion of a snapshot into HTML.""" tail = TestLinkedList(name='tail') head = TestLinkedList(name='head', next_elem=tail) snapshot = JsonSnapshot() snapshot.make_entity_for_object(head) json_snapshot = snapshot.to_json_object() entity_manager = ProcessedEntityManager() processor = ProcessToRenderInfo( HtmlDocumentManager('test_json'), entity_manager) processor.max_uncollapsable_json_lines = 20 processor.max_uncollapsable_metadata_rows = 20 processor.max_uncollapsable_entity_rows = 20 processor.default_force_top_level_collapse = False in_relation = None entity_manager.push_entity_map(json_snapshot['_entities']) html_info = processor.process_entity_id(json_snapshot['_subject_id'], json_snapshot, in_relation) entity_manager.pop_entity_map(json_snapshot['_entities']) expect = """<table> <tr> <th><i>metadata</i></th> <td> <table> <tr><th>_id</th><td>1</td></tr> <tr><th>class</th><td>type TestLinkedList</td></tr> <tr><th>name</th><td>head</td></tr> </table> </td> </tr> <tr> <th>Next</th> <td> <table> <tr> <th><i>metadata</i></th> <td> <table> <tr><th>_id</th><td>2</td></tr> <tr><th>class</th><td>type TestLinkedList</td></tr> <tr><th>name</th><td>tail</td></tr> </table> </td> </tr> </table> </td> </tr> </table> """ # Test without regard to whitespace formatting. self.assertEquals(''.join(expect.split()), ''.join(str(html_info.detail_block).split()))
def __handle_generic(self, entry): """Handles entries from the journal to update the overall summary. Args: entry: JSON entry from the journal """ # Update our running timestamps bounding overall processing time. timestamp = entry.get('_timestamp') if self.__first_timestamp is None: self.__first_timestamp = timestamp self.__last_timestamp = timestamp or self.__last_timestamp if entry.get('_type') == 'JsonSnapshot' and self.__depth == 0: # Consider root-level snapshots for overall status. entity_manager = ProcessedEntityManager() entity_manager.push_entity_map(entry.get('_entities', {})) relation = entity_manager.lookup_entity_with_id( entry.get('_subject_id')).get('_default_relation') self.__summary_status = self.__ingest_summary_status( self.__summary_status, relation) return # Look for top-level control objects that indicate tests. # TODO(ewiseblatt): 20160301 # This should be formalized since the concept of a test is reasonably # primitive. Maybe specialize the control by citing the name of the test # in an attribute whose semantics indicate the context block for a test. if entry.get('_type') != 'JournalContextControl': return if entry.get('control') == 'BEGIN': # pylint: disable=bad-indentation self.__depth += 1 if self.__depth == 1: self.__in_test = entry.get('_title', '').startswith('Test ') return if entry.get('control') == 'END': # pylint: disable=bad-indentation self.__depth -= 1 if self.__depth == 0: relation = entry.get('relation') if self.__in_test: self.__increment_relation_count(relation) elif relation is not None: self.__summary_status = self.__ingest_summary_status( self.__summary_status, relation) return
def test_cycle(self): tail = TestLinkedList(name='tail') head = TestLinkedList(name='head', next_elem=tail) tail.next = head snapshot = JsonSnapshot() entity_manager = ProcessedEntityManager() processor = ProcessToRenderInfo( HtmlDocumentManager('test_json'), entity_manager) snapshot.make_entity_for_object(head) json_snapshot = snapshot.to_json_object() self.assertEqual(1, json_snapshot.get('_subject_id')) entity_manager.push_entity_map(json_snapshot.get('_entities')) in_relation = None info = processor.process_entity_id(1, snapshot, in_relation)
def test_cycle(self): tail = TestLinkedList(name='tail') head = TestLinkedList(name='head', next_elem=tail) tail.next = head snapshot = JsonSnapshot() entity_manager = ProcessedEntityManager() processor = ProcessToRenderInfo(HtmlDocumentManager('test_json'), entity_manager) snapshot.make_entity_for_object(head) json_snapshot = snapshot.to_json_object() self.assertEqual(1, json_snapshot.get('_subject_id')) entity_manager.push_entity_map(json_snapshot.get('_entities')) in_relation = None info = processor.process_entity_id(1, snapshot, in_relation)
def test_yaml(self): """Test rendering literal json values""" processor = ProcessToRenderInfo(HtmlDocumentManager('test_yaml'), ProcessedEntityManager()) processor.max_uncollapsable_json_lines = 20 processor.max_uncollapsable_entity_rows = 20 # Numeric literals wont be treated as yaml for n in [-1, 0, 1, 3.14]: info = processor.process_yaml_html_if_possible(n) self.assertEquals('{0}'.format(n), info.detail_block) self.assertEquals(None, info.summary_block) # None of these strings are well-defined YAML documents # so should just be strings. for s in ['test', 'a phrase']: info = processor.process_yaml_html_if_possible(s) self.assertEquals('<pre>%s\n</pre>' % s, info.detail_block) self.assertEquals(None, info.summary_block) info = processor.process_yaml_html_if_possible('True') self.assertEquals('<pre>true\n</pre>', info.detail_block) # Boolean values wont be considered YAML for b in [True, False]: info = processor.process_yaml_html_if_possible(b) self.assertEquals('{0}'.format(str(b)), info.detail_block) self.assertEquals(None, info.summary_block) # Dictionaries and YAML dictionary strings normalize to YAML import yaml for d in [{'A': 'a', 'B': True}, 'A: a\nB: true\n']: info = processor.process_yaml_html_if_possible(d) # The eolns here show that it is being yaml formatted. self.assertEquals('<pre>A:a\nB:true\n</pre>', str(info.detail_block).replace(' ', '')) self.assertEquals(None, info.summary_block) self.assertEquals(None, info.summary_block) # Lists and YAML lists strings normalize to YAML. for l in [[123, 'abc', True, { 'A': 'a', 'B': 'b' }], '[123, "abc", true, {"A":"a", "B":"b"}]']: info = processor.process_yaml_html_if_possible(l) self.assertEquals( '<pre>-123\n-abc\n-true\n-A:a\nB:b\n</pre>', str(info.detail_block).replace(' ', '').replace('\n', '\n')) self.assertEquals(None, info.summary_block)
def test_process_snapshot(self): """Test the conversion of a snapshot into HTML.""" tail = TestLinkedList(name='tail') head = TestLinkedList(name='head', next_elem=tail) snapshot = JsonSnapshot() snapshot.make_entity_for_object(head) json_snapshot = snapshot.to_json_object() entity_manager = ProcessedEntityManager() processor = ProcessToRenderInfo(HtmlDocumentManager('test_json'), entity_manager) processor.max_uncollapsable_json_lines = 20 processor.max_uncollapsable_metadata_rows = 20 processor.max_uncollapsable_entity_rows = 20 processor.default_force_top_level_collapse = False in_relation = None entity_manager.push_entity_map(json_snapshot['_entities']) html_info = processor.process_entity_id(json_snapshot['_subject_id'], json_snapshot, in_relation) entity_manager.pop_entity_map(json_snapshot['_entities']) expect = """<table> <tr> <th><i>metadata</i></th> <td> <table> <tr><th>_id</th><td>1</td></tr> <tr><th>class</th><td>type TestLinkedList</td></tr> <tr><th>name</th><td>head</td></tr> </table> </td> </tr> <tr> <th>Next</th> <td> <table> <tr> <th><i>metadata</i></th> <td> <table> <tr><th>_id</th><td>2</td></tr> <tr><th>class</th><td>type TestLinkedList</td></tr> <tr><th>name</th><td>tail</td></tr> </table> </td> </tr> </table> </td> </tr> </table> """ # Test without regard to whitespace formatting. self.assertEquals(''.join(expect.split()), ''.join(str(html_info.detail_block).split()))
def test_json(self): """Test rendering literal json values""" processor = ProcessToRenderInfo(HtmlDocumentManager('test_json'), ProcessedEntityManager()) processor.max_uncollapsable_json_lines = 20 processor.max_uncollapsable_entity_rows = 20 # Numeric literals wont be treated as json. for n in [-1, 0, 1, 3.14]: info = processor.process_json_html_if_possible(n) self.assertEquals('{0}'.format(n), info.detail_block) self.assertEquals(None, info.summary_block) # None of these strings are well-defined JSON documents # so should just be strings. for s in ['test', 'a phrase', 'True']: info = processor.process_json_html_if_possible(s) self.assertEquals("'{0}'".format(s), info.detail_block) self.assertEquals(None, info.summary_block) # Boolean values wont be considered JSON. for b in [True, False]: info = processor.process_json_html_if_possible(b) self.assertEquals('{0}'.format(str(b)), info.detail_block) self.assertEquals(None, info.summary_block) # Dictionaries and JSON dictionary strings normalize to JSON. for d in [{'A': 'a', 'B': True}, '{"A":"a", "B":true}']: info = processor.process_json_html_if_possible(d) # The eolns here show that it is being json formatted. self.assertEquals('<pre>{\n"A":"a",\n"B":true\n}</pre>', str(info.detail_block).replace(' ', '')) self.assertEquals(None, info.summary_block) self.assertEquals(None, info.summary_block) # Lists and JSON lists strings normalize to JSON. for l in [[123, 'abc', True, { 'A': 'a', 'B': 'b' }], '[123, "abc", true, {"A":"a", "B":"b"}]']: info = processor.process_json_html_if_possible(l) self.assertEquals( '<pre>[123,"abc",true,{"A":"a","B":"b"}]</pre>', str(info.detail_block).replace(' ', '').replace('\n', '')) self.assertEquals(None, info.summary_block)
class HtmlRenderer(JournalProcessor): """Specialized JournalProcessor to produce HTML.""" def __init__(self, document_manager, registry=None, prune=False): """Constructor. Args: document_manager: [HtmlDocumentManager] Helps with look & feel, and structure. registry: [dict] Registry of processing methods keyed by record type in the journal. If not defined, then use the default. prune: [bool] If true, take liberties with pruning the information so that it is more concise and readable for a typical use case of verifying tests passed and investigating why they have not. """ if registry is None: registry = { 'JsonSnapshot': self.render_snapshot, 'JournalContextControl': self.handle_context_control, 'JournalMessage': self.render_message } super(HtmlRenderer, self).__init__(registry=registry) self.__entity_manager = ProcessedEntityManager() self.__document_manager = document_manager # The context stack is an array of array of strings, # Where each top level array is a context we've pushed, which # contains fragments of entities. If we've pushed a context, then # well render into the stack. Otherwise we'll render into the document. # When we pop a context we'll render it into the parent context until # we pop the root context, which will finally render into the document. self.__context_stack = [None] self.__context_stack_timestamp_prefix = [None] self.__prune = prune def has_context(self): return len(self.__context_stack) > 1 def terminate(self): """Implements JournalProcessor interface.""" if self.has_context(): raise ValueError('Still have {0} open contexts'.format( len(self.__context_stack))) def __render_context(self, end_control, rendered_context): """Render the context into HTML now that we've terminated it. Args: end_control: [dict] JournalContextControl for end context record. rendered_context: [RenderedContext] the context we just ended. """ begin_control = rendered_context.control if begin_control.get('_title') == 'Execute': # If this context is "Execute", then unwrap this top context and the # execute context to promote the actual thing executed to this level. # This is a hack, but makes the reporting more readable. if rendered_context.html: if self.has_context(): self.__context_stack[-1].html.extend(rendered_context.html) return # Relation here is used to indicate the test status. # Take that and turn it into a style. relation = end_control.get('relation', None) document_manager = self.__document_manager css = document_manager.determine_attribute_css_kwargs(relation)[0] title_html = document_manager.make_html_block( begin_control.get('_title', '')) if not rendered_context.html: title_html.append( document_manager.make_html_block(' (<i>empty</i>)')) self.render_log_tr(begin_control['_timestamp'], None, title_html, css=css) else: delta_time = end_control['_timestamp'] - begin_control['_timestamp'] if css: title_html = document_manager.make_tag_container( 'span', [title_html], **css) lvl = min(1, len(self.__context_stack)) title_html = document_manager.make_tag_container( 'context{n}'.format(n=lvl), [title_html]) summary = document_manager.make_html_block( '%s <small>+%.3fs</small>' % (title_html, delta_time)) table = document_manager.new_tag('table') for row in rendered_context.html: table.append(row) detail = document_manager.make_html_block(str(title_html)) detail.append(table) self.render_log_tr(begin_control['_timestamp'], summary, detail) def handle_context_control(self, control): """Begin or terminate contexts.""" direction = control['control'] if direction == 'BEGIN': self.__context_stack.append(RenderedContext(control, [])) self.__context_stack_timestamp_prefix.append( self.__context_stack_timestamp_prefix[-1]) elif direction == 'END': context = self.__context_stack[-1] self.__context_stack.pop() self.__context_stack_timestamp_prefix.pop() if (not context.html and context.control.get('_title', '') in ['setUp', 'tearDown']): return self.__render_context(control, context) else: raise ValueError( 'Invalid JournalContextControl control={0}'.format(direction)) def render_snapshot(self, snapshot): """Default method for rendering a JsonSnapshot into HTML.""" subject_id = snapshot.get('_subject_id') entities = snapshot.get('_entities', {}) self.__entity_manager.push_entity_map(entities) # This is only for the final relation. subject = self.__entity_manager.lookup_entity_with_id(subject_id) final_relation = subject.get('_default_relation') # Delegate the whole thing. document_manager = self.__document_manager processor = ProcessToRenderInfo(document_manager, self.__entity_manager, prune=self.__prune) try: info = processor.process_entity_id(subject_id, snapshot, None) finally: self.__entity_manager.pop_entity_map(entities) if not info.summary_block: self.render_log_tr(snapshot.get('_timestamp'), None, info.detail_block) return final_css = document_manager.determine_attribute_css_kwargs( final_relation)[0] title = snapshot.get('_title', '') if not title and info.summary_block: title = '"{html}" Snapshot'.format(html=info.summary_block) if title: title_html = document_manager.make_tag_text( 'padded', title, **final_css) self.render_log_tr(snapshot.get('_timestamp'), title_html, info.detail_block, collapse_decorator=title, css=final_css) return @staticmethod def timestamp_to_string(timestamp): """Return human-readable timestamp. Args: timestamp: [float] Seconds since epoch. """ millis = ('%.3f' % (timestamp - int(timestamp)))[2:] return datetime.datetime.fromtimestamp(timestamp).strftime( '%Y-%m-%d %H:%M:%S.{millis}'.format(millis=millis)) def render_log_tr(self, timestamp, summary, detail, **kwargs): """Add row to accumulated output. timestamp: [float] The timestamp for the entry. summary: [string] If provided, this is an abbreviation. detail: [string] This is the full detail for the entry. """ tag = self.render_log_tr_tag(timestamp, summary, detail, **kwargs) if self.has_context(): self.__context_stack[-1].html.append(tag) else: self.__document_manager.append_tag(tag) def render_log_tr_tag(self, timestamp, summary, detail, collapse_decorator='', css=None): """Render a top level entry into the log as a table row. Args: timestamp: [float] The timestamp for the entry. summary: [string] If provided, this is an abbreviation. detail: [string] This is the full detail for the entry. """ document_manager = self.__document_manager if self.__context_stack_timestamp_prefix: # If the last "yyyy-mm-dd " part of the date string is the # same as the previous for this scoped block, then strip it out. # otherwise include it. prev_date_prefix = self.__context_stack_timestamp_prefix[-1] date_str = self.timestamp_to_string(timestamp) if prev_date_prefix and date_str.startswith(prev_date_prefix): date_str = date_str[11:] else: self.__context_stack_timestamp_prefix[-1] = date_str[:11] else: date_str = self.timestamp_to_string(timestamp) css_class = None if css is None else css.get('class_') td_tag_kwargs = {} if summary else {'class': css_class} tr_tag = document_manager.new_tag('tr') th_tag = document_manager.make_tag_text('th', date_str, class_='rj') td_tag = document_manager.new_tag('td', **td_tag_kwargs) tr_tag.append(th_tag) tr_tag.append(td_tag) if not summary: td_tag.append(detail) return tr_tag # pylint: disable=bad-continuation collapse_html = ('collapse {decorator}'.format( decorator=collapse_decorator) if collapse_decorator else 'collapse') section_id = document_manager.new_section_id() show = document_manager.make_expandable_control_tag( section_id, 'expand') hide = document_manager.make_expandable_control_tag( section_id, collapse_html) detail_tag_attrs, summary_tag_attrs = ( document_manager.make_expandable_tag_attr_kwargs_pair( section_id=section_id, default_expanded=False)) show_span = document_manager.make_tag_container('span', [show, summary], class_=css_class, **summary_tag_attrs) hide_span = document_manager.make_tag_container('span', [hide, detail], class_=css_class, **detail_tag_attrs) td_tag.append(show_span) td_tag.append(hide_span) return tr_tag def render_message(self, message): """Default method for rendering a JournalMessage into HTML.""" text = message.get('_value').strip() document_manager = self.__document_manager processor = ProcessToRenderInfo(document_manager, self.__entity_manager, prune=self.__prune) html_info = HtmlInfo() html_format = message.get('format', None) if html_format == 'json': html_info = processor.process_json_html_if_possible(text) else: summary = None if text is None: html = document_manager.make_tag_text('i', 'Empty Message') elif html_format != 'pre': html = document_manager.make_text_block(text) else: last_offset = -1 # pylint: disable=bad-indentation # The ff tag here is a custom tag for "Fixed Format" # It is like 'pre' but inline rather than block to be more compact. num_lines = text.count('\n') if num_lines > processor.max_uncollapsable_message_lines: last_offset = text.find('\n') elif len(text) > processor.max_message_summary_length: last_offset = processor.max_message_summary_length - 2 * len( 'expand') if last_offset >= 0: summary = document_manager.make_tag_text( 'ff', '{0}...'.format(text[0:last_offset])) html = document_manager.make_tag_text('ff', text) html_info = HtmlInfo(detail=html, summary=summary) self.render_log_tr(message.get('_timestamp'), html_info.summary_block, html_info.detail_block)
class HtmlRenderer(JournalProcessor): """Specialized JournalProcessor to produce HTML.""" def __init__(self, document_manager, registry=None, prune=False): """Constructor. Args: document_manager: [HtmlDocumentManager] Helps with look & feel, and structure. registry: [dict] Registry of processing methods keyed by record type in the journal. If not defined, then use the default. prune: [bool] If true, take liberties with pruning the information so that it is more concise and readable for a typical use case of verifying tests passed and investigating why they have not. """ if registry is None: registry = { 'JsonSnapshot': self.render_snapshot, 'JournalContextControl': self.handle_context_control, 'JournalMessage': self.render_message } super(HtmlRenderer, self).__init__(registry=registry) self.__entity_manager = ProcessedEntityManager() self.__document_manager = document_manager # The context stack is an array of array of strings, # Where each top level array is a context we've pushed, which # contains fragments of entities. If we've pushed a context, then # well render into the stack. Otherwise we'll render into the document. # When we pop a context we'll render it into the parent context until # we pop the root context, which will finally render into the document. self.__context_stack = [None] self.__context_stack_timestamp_prefix = [None] self.__prune = prune def has_context(self): return len(self.__context_stack) > 1 def terminate(self): """Implements JournalProcessor interface.""" if self.has_context(): raise ValueError( 'Still have {0} open contexts'.format(len(self.__context_stack))) def __render_context(self, end_control, rendered_context): """Render the context into HTML now that we've terminated it. Args: end_control: [dict] JournalContextControl for end context record. rendered_context: [RenderedContext] the context we just ended. """ begin_control = rendered_context.control if begin_control.get('_title') == 'Execute': # If this context is "Execute", then unwrap this top context and the # execute context to promote the actual thing executed to this level. # This is a hack, but makes the reporting more readable. if rendered_context.html: if self.has_context(): self.__context_stack[-1].html.extend(rendered_context.html) return # Relation here is used to indicate the test status. # Take that and turn it into a style. relation = end_control.get('relation', None) document_manager = self.__document_manager css = document_manager.determine_attribute_css_kwargs(relation)[0] title_html = document_manager.make_html_block( begin_control.get('_title', '')) if not rendered_context.html: title_html.append(document_manager.make_html_block(' (<i>empty</i>)')) self.render_log_tr(begin_control['_timestamp'], None, title_html, css=css) else: delta_time = end_control['_timestamp'] - begin_control['_timestamp'] if css: title_html = document_manager.make_tag_container( 'span', [title_html], **css) lvl = min(1, len(self.__context_stack)) title_html = document_manager.make_tag_container( 'context{n}'.format(n=lvl), [title_html]) summary = document_manager.make_html_block( '%s <small>+%.3fs</small>' % (title_html, delta_time)) table = document_manager.new_tag('table') for row in rendered_context.html: table.append(row) detail = document_manager.make_html_block(str(title_html)) detail.append(table) self.render_log_tr(begin_control['_timestamp'], summary, detail) def handle_context_control(self, control): """Begin or terminate contexts.""" direction = control['control'] if direction == 'BEGIN': self.__context_stack.append(RenderedContext(control, [])) self.__context_stack_timestamp_prefix.append( self.__context_stack_timestamp_prefix[-1]) elif direction == 'END': context = self.__context_stack[-1] self.__context_stack.pop() self.__context_stack_timestamp_prefix.pop() if (not context.html and context.control.get('_title', '') in ['setUp', 'tearDown']): return self.__render_context(control, context) else: raise ValueError( 'Invalid JournalContextControl control={0}'.format(direction)) def render_snapshot(self, snapshot): """Default method for rendering a JsonSnapshot into HTML.""" subject_id = snapshot.get('_subject_id') entities = snapshot.get('_entities', {}) self.__entity_manager.push_entity_map(entities) # This is only for the final relation. subject = self.__entity_manager.lookup_entity_with_id(subject_id) final_relation = subject.get('_default_relation') # Delegate the whole thing. document_manager = self.__document_manager processor = ProcessToRenderInfo(document_manager, self.__entity_manager, prune=self.__prune) try: info = processor.process_entity_id(subject_id, snapshot, None) finally: self.__entity_manager.pop_entity_map(entities) if not info.summary_block: self.render_log_tr(snapshot.get('_timestamp'), None, info.detail_block) return final_css = document_manager.determine_attribute_css_kwargs( final_relation)[0] title = snapshot.get('_title', '') if not title and info.summary_block: title = '"{html}" Snapshot'.format(html=info.summary_block) if title: title_html = document_manager.make_tag_text('padded', title, **final_css) self.render_log_tr(snapshot.get('_timestamp'), title_html, info.detail_block, collapse_decorator=title, css=final_css) return @staticmethod def timestamp_to_string(timestamp): """Return human-readable timestamp. Args: timestamp: [float] Seconds since epoch. """ millis = ('%.3f' % (timestamp - int(timestamp)))[2:] return datetime.datetime.fromtimestamp(timestamp).strftime( '%Y-%m-%d %H:%M:%S.{millis}'.format(millis=millis)) def render_log_tr(self, timestamp, summary, detail, **kwargs): """Add row to accumulated output. timestamp: [float] The timestamp for the entry. summary: [string] If provided, this is an abbreviation. detail: [string] This is the full detail for the entry. """ tag = self.render_log_tr_tag(timestamp, summary, detail, **kwargs) if self.has_context(): self.__context_stack[-1].html.append(tag) else: self.__document_manager.append_tag(tag) def render_log_tr_tag(self, timestamp, summary, detail, collapse_decorator='', css=None): """Render a top level entry into the log as a table row. Args: timestamp: [float] The timestamp for the entry. summary: [string] If provided, this is an abbreviation. detail: [string] This is the full detail for the entry. """ document_manager = self.__document_manager if self.__context_stack_timestamp_prefix: # If the last "yyyy-mm-dd " part of the date string is the # same as the previous for this scoped block, then strip it out. # otherwise include it. prev_date_prefix = self.__context_stack_timestamp_prefix[-1] date_str = self.timestamp_to_string(timestamp) if prev_date_prefix and date_str.startswith(prev_date_prefix): date_str = date_str[11:] else: self.__context_stack_timestamp_prefix[-1] = date_str[:11] else: date_str = self.timestamp_to_string(timestamp) css_class = None if css is None else css.get('class_') td_tag_kwargs = {} if summary else {'class': css_class} tr_tag = document_manager.new_tag('tr') th_tag = document_manager.make_tag_text('th', date_str, class_='rj') td_tag = document_manager.new_tag('td', **td_tag_kwargs) tr_tag.append(th_tag) tr_tag.append(td_tag) if not summary: td_tag.append(detail) return tr_tag # pylint: disable=bad-continuation collapse_html = ('collapse {decorator}' .format(decorator=collapse_decorator) if collapse_decorator else 'collapse') section_id = document_manager.new_section_id() show = document_manager.make_expandable_control_tag( section_id, 'expand') hide = document_manager.make_expandable_control_tag( section_id, collapse_html) detail_tag_attrs, summary_tag_attrs = ( document_manager.make_expandable_tag_attr_kwargs_pair( section_id=section_id, default_expanded=False)) show_span = document_manager.make_tag_container( 'span', [show, summary], class_=css_class, **summary_tag_attrs) hide_span = document_manager.make_tag_container( 'span', [hide, detail], class_=css_class, **detail_tag_attrs) td_tag.append(show_span) td_tag.append(hide_span) return tr_tag def render_message(self, message): """Default method for rendering a JournalMessage into HTML.""" text = message.get('_value').strip() document_manager = self.__document_manager processor = ProcessToRenderInfo(document_manager, self.__entity_manager, prune=self.__prune) html_info = HtmlInfo() html_format = message.get('format', None) if html_format == 'json': html_info = processor.process_json_html_if_possible(text) elif html_format == 'yaml': html_info = processor.process_yaml_html_if_possible(text) else: summary = None if text is None: html = document_manager.make_tag_text('i', 'Empty Message') elif html_format != 'pre': html = document_manager.make_text_block(text) else: last_offset = -1 # pylint: disable=bad-indentation # The ff tag here is a custom tag for "Fixed Format" # It is like 'pre' but inline rather than block to be more compact. num_lines = text.count('\n') if num_lines > processor.max_uncollapsable_message_lines: last_offset = text.find('\n') elif len(text) > processor.max_message_summary_length: last_offset = processor.max_message_summary_length - 2*len('expand') if last_offset >= 0: summary = document_manager.make_tag_text( 'ff', '{0}...'.format(text[0:last_offset])) html = document_manager.make_tag_text('ff', text) html_info = HtmlInfo(detail=html, summary=summary) self.render_log_tr(message.get('_timestamp'), html_info.summary_block, html_info.detail_block)
def __handle_generic(self, entry): """Handles entries from the journal to update the overall summary. Args: entry: JSON entry from the journal """ if entry.get('_type') == 'JsonSnapshot' and self.__depth == 0: # Consider root-level snapshots for overall status. entity_manager = ProcessedEntityManager() entity_manager.push_entity_map(entry.get('_entities', {})) relation = entity_manager.lookup_entity_with_id( entry.get('_subject_id')).get('_default_relation') self.__summary_status = self.__ingest_summary_status( self.__summary_status, relation) return # Look for top-level control objects that indicate tests. # TODO(ewiseblatt): 20160301 # This should be formalized since the concept of a test is reasonably # primitive. Maybe specialize the control by citing the name of the test # in an attribute whose semantics indicate the context block for a test. if entry.get('_type') != 'JournalContextControl': return if entry.get('control') == 'BEGIN': # pylint: disable=bad-indentation self.__depth += 1 if self.__depth == 1: self.__in_test = entry.get('_title', '') if not self.__in_test.startswith('Test '): self.__in_test = None self.__start_timestamp = entry.get('_timestamp') return if entry.get('control') == 'END': # pylint: disable=bad-indentation self.__depth -= 1 if self.__depth == 0: relation = entry.get('relation') if not self.__in_test: if relation is not None: self.__summary_status = self.__ingest_summary_status( self.__summary_status, relation) return passed = 0 failed = 0 error = 0 end_timestamp = entry.get('_timestamp') secs = (end_timestamp - self.__start_timestamp if end_timestamp and self.__start_timestamp else 0) self.__summary_status = self.__ingest_summary_status( self.__summary_status, relation) if relation == 'VALID': passed = 1 elif relation == 'INVALID': failed = 1 elif relation == 'ERROR': error = 1 else: raise ValueError('Unhandled relation {0}'.format(relation)) test_stats = TestStats(passed, failed, error, secs) self.__stats.aggregate(test_stats) if self.__in_test: self.__detail_stats[self.__in_test] = test_stats self.__in_test = None return