Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(259)

Unified Diff: tools/code_coverage/croc_html.py

Issue 113980: Major refactoring of Croc.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/code_coverage/croc_html.py
===================================================================
--- tools/code_coverage/croc_html.py (revision 0)
+++ tools/code_coverage/croc_html.py (revision 0)
@@ -0,0 +1,453 @@
+#!/usr/bin/python2.4
John Grabowski 2009/05/29 00:34:43 /usr/bin/env python? Non-Google machines won't nec
+#
+# Copyright 2009, Google Inc.
John Grabowski 2009/05/29 00:34:43 (c) the Chromium authors?
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Crocodile HTML output."""
+
+import os
+import shutil
+import time
+import xml.dom
+
+
+class CrocHtmlError(Exception):
+ """Coverage HTML error."""
+
+
+class HtmlElement(object):
+ """Node in a HTML file."""
+
+ def __init__(self, doc, element):
+ """Constructor.
+
+ Args:
+ doc: XML document object.
+ element: XML element.
+ """
+ self.doc = doc
+ self.element = element
+
+ def E(self, name, **kwargs):
+ """Adds a child element.
+
+ Args:
+ name: Name of element.
+ kwargs: Attributes for element. To use an attribute which is a python
+ reserved word (i.e. 'class'), prefix the attribute name with 'e_'.
+
+ Returns:
+ The child element.
+ """
+ he = HtmlElement(self.doc, self.doc.createElement(name))
+ element = he.element
+ self.element.appendChild(element)
+
+ for k, v in kwargs.iteritems():
+ if k.startswith('e_'):
+ # Remove prefix
+ element.setAttribute(k[2:], str(v))
+ else:
+ element.setAttribute(k, str(v))
+
+ return he
+
+ def Text(self, text):
+ """Adds a text node.
+
+ Args:
+ text: Text to add.
+
+ Returns:
+ self.
+ """
+ t = self.doc.createTextNode(str(text))
+ self.element.appendChild(t)
+ return self
+
+
+class HtmlFile(object):
+ """HTML file."""
+
+ def __init__(self, xml_impl, filename):
+ """Constructor.
+
+ Args:
+ xml_impl: DOMImplementation to use to create document.
+ filename: Path to file.
+ """
+ self.xml_impl = xml_impl
+ doctype = xml_impl.createDocumentType(
+ 'HTML', '-//W3C//DTD HTML 4.01//EN',
+ 'http://www.w3.org/TR/html4/strict.dtd')
+ self.doc = xml_impl.createDocument(None, 'html', doctype)
+ self.filename = filename
+
+ # Create head and body elements
+ root = HtmlElement(self.doc, self.doc.documentElement)
+ self.head = root.E('head')
+ self.body = root.E('body')
+
+ def Write(self, cleanup=True):
+ """Writes the file.
+
+ Args:
+ cleanup: If True, calls unlink() on the internal xml document. This
+ frees up memory, but means that you can't use this file for anything
+ else.
+ """
+ f = open(self.filename, 'wt')
+ self.doc.writexml(f, encoding='UTF-8')
+ f.close()
+
+ if cleanup:
+ self.doc.unlink()
+ # Prevent future uses of the doc now that we've unlinked it
+ self.doc = None
+
+#------------------------------------------------------------------------------
+
+COV_TYPE_STRING = {None: 'm', 0: 'i', 1: 'E', 2: ' '}
+COV_TYPE_CLASS = {None: 'missing', 0: 'instr', 1: 'covered', 2: ''}
+
+
+class CrocHtml(object):
+ """Crocodile HTML output class."""
+
+ def __init__(self, cov, output_root):
+ """Constructor."""
+ self.cov = cov
+ self.output_root = output_root
+ self.xml_impl = xml.dom.getDOMImplementation()
+ self.time_string = 'Coverage information generated %s.' % time.asctime()
+
+ def CreateHtmlDoc(self, filename, title):
+ """Creates a new HTML document.
+
+ Args:
+ filename: Filename to write to, relative to self.output_root.
+ title: Title of page
+
+ Returns:
+ The document.
+ """
+ f = HtmlFile(self.xml_impl, self.output_root + '/' + filename)
+
+ f.head.E('title').Text(title)
+ f.head.E(
+ 'link', rel='stylesheet', type='text/css',
+ href='../' * (len(filename.split('/')) - 1) + 'croc.css')
+
+ return f
+
+ def AddCaptionForFile(self, body, path):
+ """Adds a caption for the file, with links to each parent dir.
+
+ Args:
+ body: Body elemement.
+ path: Path to file.
+ """
+ # This is slightly different that for subdir, because it needs to have a
+ # link to the current directory's index.html.
+ hdr = body.E('h2')
+ hdr.Text('Coverage for ')
+ dirs = [''] + path.split('/')
+ num_dirs = len(dirs)
+ for i in range(num_dirs - 1):
+ hdr.E('a', href=(
+ '../' * (num_dirs - i - 2) + 'index.html')).Text(dirs[i] + '/')
+ hdr.Text(dirs[-1])
+
+ def AddCaptionForSubdir(self, body, path):
+ """Adds a caption for the subdir, with links to each parent dir.
+
+ Args:
+ body: Body elemement.
+ path: Path to subdir.
+ """
+ # Link to parent dirs
+ hdr = body.E('h2')
+ hdr.Text('Coverage for ')
+ dirs = [''] + path.split('/')
+ num_dirs = len(dirs)
+ for i in range(num_dirs - 1):
+ hdr.E('a', href=(
+ '../' * (num_dirs - i - 1) + 'index.html')).Text(dirs[i] + '/')
+ hdr.Text(dirs[-1] + '/')
+
+ def AddSectionHeader(self, table, caption, itemtype, is_file=False):
+ """Adds a section header to the coverage table.
+
+ Args:
+ table: Table to add rows to.
+ caption: Caption for section, if not None.
+ itemtype: Type of items in this section, if not None.
+ is_file: Are items in this section files?
+ """
+
+ if caption is not None:
+ table.E('tr').E('td', e_class='secdesc', colspan=8).Text(caption)
+
+ sec_hdr = table.E('tr')
+
+ if itemtype is not None:
+ sec_hdr.E('td', e_class='section').Text(itemtype)
+
+ sec_hdr.E('td', e_class='section').Text('Coverage')
+ sec_hdr.E('td', e_class='section', colspan=3).Text(
+ 'Lines executed / instrumented / missing')
+
+ graph = sec_hdr.E('td', e_class='section')
+ graph.E('span', style='color:#00FF00').Text('exe')
+ graph.Text(' / ')
+ graph.E('span', style='color:#FFFF00').Text('inst')
+ graph.Text(' / ')
+ graph.E('span', style='color:#FF0000').Text('miss')
+
+ if is_file:
+ sec_hdr.E('td', e_class='section').Text('Language')
+ sec_hdr.E('td', e_class='section').Text('Group')
+ else:
+ sec_hdr.E('td', e_class='section', colspan=2)
+
+ def AddItem(self, table, itemname, stats, attrs, link=None):
+ """Adds a bar graph to the element. This is a series of <td> elements.
+
+ Args:
+ table: Table to add item to.
+ itemname: Name of item.
+ stats: Stats object.
+ attrs: Attributes dictionary; if None, no attributes will be printed.
+ link: Destination for itemname hyperlink, if not None.
+ """
+ row = table.E('tr')
+
+ # Add item name
+ if itemname is not None:
+ item_elem = row.E('td')
+ if link is not None:
+ item_elem = item_elem.E('a', href=link)
+ item_elem.Text(itemname)
+
+ # Get stats
+ stat_exe = stats.get('lines_executable', 0)
+ stat_ins = stats.get('lines_instrumented', 0)
+ stat_cov = stats.get('lines_covered', 0)
+
+ percent = row.E('td')
+
+ # Add text
+ row.E('td', e_class='number').Text(stat_cov)
+ row.E('td', e_class='number').Text(stat_ins)
+ row.E('td', e_class='number').Text(stat_exe - stat_ins)
+
+ # Add percent and graph; only fill in if there's something in there
+ graph = row.E('td', e_class='graph', width=100)
+ if stat_exe:
+ percent_cov = 100.0 * stat_cov / stat_exe
+ percent_ins = 100.0 * stat_ins / stat_exe
+
+ # Color percent based on thresholds
+ percent.Text('%.1f%%' % percent_cov)
+ if percent_cov >= 80:
+ percent.element.setAttribute('class', 'high_pct')
+ elif percent_cov >= 60:
+ percent.element.setAttribute('class', 'mid_pct')
+ else:
+ percent.element.setAttribute('class', 'low_pct')
+
+ # Graphs use integer values
+ percent_cov = int(percent_cov)
+ percent_ins = int(percent_ins)
+
+ graph.Text('.')
+ graph.E('span', style='padding-left:%dpx' % percent_cov,
+ e_class='g_covered')
+ graph.E('span', style='padding-left:%dpx' % (percent_ins - percent_cov),
+ e_class='g_instr')
+ graph.E('span', style='padding-left:%dpx' % (100 - percent_ins),
+ e_class='g_missing')
+
+ if attrs:
+ row.E('td', e_class='stat').Text(attrs.get('language'))
+ row.E('td', e_class='stat').Text(attrs.get('group'))
+ else:
+ row.E('td', colspan=2)
+
+ def WriteFile(self, cov_file):
+ """Writes the HTML for a file.
+
+ Args:
+ cov_file: croc.CoveredFile to write.
+ """
+ print ' ' + cov_file.filename
+ title = 'Coverage for ' + cov_file.filename
+
+ f = self.CreateHtmlDoc(cov_file.filename + '.html', title)
+ body = f.body
+
+ # Write header section
+ self.AddCaptionForFile(body, cov_file.filename)
+
+ # Summary for this file
+ table = body.E('table')
+ self.AddSectionHeader(table, None, None, is_file=True)
+ self.AddItem(table, None, cov_file.stats, cov_file.attrs)
+
+ body.E('h2').Text('Line-by-line coverage:')
+
+ # Print line-by-line coverage
+ if cov_file.local_path:
+ code_table = body.E('table').E('tr').E('td').E('pre')
+
+ flines = open(cov_file.local_path, 'rt')
+ lineno = 0
+
+ for line in flines:
+ lineno += 1
+ line_cov = cov_file.lines.get(lineno, 2)
+ e_class = COV_TYPE_CLASS.get(line_cov)
+
+ code_table.E('span', e_class=e_class).Text('%4d %s : %s\n' % (
+ lineno,
+ COV_TYPE_STRING.get(line_cov),
+ line.rstrip()
+ ))
+
+ else:
+ body.Text('Line-by-line coverage not available. Make sure the directory'
+ ' containing this file has been scanned via ')
+ body.E('B').Text('add_files')
+ body.Text(' in a configuration file, or the ')
+ body.E('B').Text('--addfiles')
+ body.Text(' command line option.')
+
+ # TODO: if file doesn't have a local path, try to find it by
+ # reverse-mapping roots and searching for the file.
+
+ body.E('p', e_class='time').Text(self.time_string)
+ f.Write()
+
+ def WriteSubdir(self, cov_dir):
+ """Writes the index.html for a subdirectory.
+
+ Args:
+ cov_dir: croc.CoveredDir to write.
+ """
+ print ' ' + cov_dir.dirpath + '/'
+
+ # Create the subdir if it doesn't already exist
+ subdir = self.output_root + '/' + cov_dir.dirpath
+ if not os.path.exists(subdir):
+ os.mkdir(subdir)
+
+ if cov_dir.dirpath:
+ title = 'Coverage for ' + cov_dir.dirpath + '/'
+ f = self.CreateHtmlDoc(cov_dir.dirpath + '/index.html', title)
+ else:
+ title = 'Coverage summary'
+ f = self.CreateHtmlDoc('index.html', title)
+
+ body = f.body
+
+ # Write header section
+ if cov_dir.dirpath:
+ self.AddCaptionForSubdir(body, cov_dir.dirpath)
+ else:
+ body.E('h2').Text(title)
+
+ table = body.E('table')
+
+ # Coverage by group
+ self.AddSectionHeader(table, 'Coverage by Group', 'Group')
+
+ for group in sorted(cov_dir.stats_by_group):
+ self.AddItem(table, group, cov_dir.stats_by_group[group], None)
+
+ # List subdirs
+ if cov_dir.subdirs:
+ self.AddSectionHeader(table, 'Subdirectories', 'Subdirectory')
+
+ for d in sorted(cov_dir.subdirs):
+ self.AddItem(table, d + '/', cov_dir.subdirs[d].stats_by_group['all'],
+ None, link=d + '/index.html')
+
+ # List files
+ if cov_dir.files:
+ self.AddSectionHeader(table, 'Files in This Directory', 'Filename',
+ is_file=True)
+
+ for filename in sorted(cov_dir.files):
+ cov_file = cov_dir.files[filename]
+ self.AddItem(table, filename, cov_file.stats, cov_file.attrs,
+ link=filename + '.html')
+
+ body.E('p', e_class='time').Text(self.time_string)
+ f.Write()
+
+ def WriteRoot(self):
+ """Writes the files in the output root."""
+ # Find ourselves
+ src_dir = os.path.split(self.WriteRoot.func_code.co_filename)[0]
+
+ # Files to copy into output root
+ copy_files = [
+ 'croc.css',
+ ]
+
+ # Copy files from our directory into the output directory
+ for copy_file in copy_files:
+ print ' Copying %s' % copy_file
+ shutil.copyfile(os.path.join(src_dir, copy_file),
+ os.path.join(self.output_root, copy_file))
+
+ def Write(self):
+ """Writes HTML output."""
+
+ print 'Writing HTML to %s...' % self.output_root
+
+ # Loop through the tree and write subdirs, breadth-first
+ # TODO: switch to depth-first and sort values - makes nicer output?
+ todo = [self.cov.tree]
+ while todo:
+ cov_dir = todo.pop(0)
+
+ # Append subdirs to todo list
+ todo += cov_dir.subdirs.values()
+
+ # Write this subdir
+ self.WriteSubdir(cov_dir)
+
+ # Write files in this subdir
+ for cov_file in cov_dir.files.itervalues():
+ self.WriteFile(cov_file)
+
+ # Write files in root directory
+ self.WriteRoot()
+

Powered by Google App Engine
This is Rietveld 408576698