[Checkins] SVN: z3c.coverage/trunk/ Support .coverage files produced by coverage.py
Marius Gedminas
cvs-admin at zope.org
Thu Sep 6 13:23:53 UTC 2012
Log message for revision 127752:
Support .coverage files produced by coverage.py
Changed:
U z3c.coverage/trunk/CHANGES.txt
U z3c.coverage/trunk/buildout.cfg
U z3c.coverage/trunk/setup.py
U z3c.coverage/trunk/src/z3c/coverage/README.txt
U z3c.coverage/trunk/src/z3c/coverage/coveragereport.py
A z3c.coverage/trunk/src/z3c/coverage/sampleinput.pickle
U z3c.coverage/trunk/src/z3c/coverage/tests.py
-=-
Modified: z3c.coverage/trunk/CHANGES.txt
===================================================================
--- z3c.coverage/trunk/CHANGES.txt 2012-09-06 13:23:43 UTC (rev 127751)
+++ z3c.coverage/trunk/CHANGES.txt 2012-09-06 13:23:49 UTC (rev 127752)
@@ -2,15 +2,19 @@
CHANGES
=======
-1.2.1 (unreleased)
+1.3.0 (unreleased)
------------------
-- Bugfix: sorting by numbered of uncovered lines was broken.
+- ``coveragereport`` now accepts ``--help``, ``--verbose`` and ``--quiet``
+ options, with verbose being on by default.
-- The ``coveragereport`` script now accepts ``--help``, ``--verbose`` and
- ``--quiet`` options, with verbose being on by default.
+- ``coveragereport`` now can handle .coverage files produced by
+ http://pypi.python.org/pypi/coverage
+- Bugfix: sorting by numbered of uncovered lines was broken in the
+ ``all.html`` report.
+
1.2.0 (2010-02-11)
------------------
Modified: z3c.coverage/trunk/buildout.cfg
===================================================================
--- z3c.coverage/trunk/buildout.cfg 2012-09-06 13:23:43 UTC (rev 127751)
+++ z3c.coverage/trunk/buildout.cfg 2012-09-06 13:23:49 UTC (rev 127752)
@@ -30,7 +30,7 @@
recipe = zc.recipe.egg
eggs = z3c.coverage
scripts = coveragereport=coverage-report
-arguments = ['${buildout:directory}/parts/coverage-test', '${buildout:directory}/parts/coverage-test/report'] + sys.argv[1:]
+arguments = sys.argv[1:] or ['${buildout:directory}/parts/coverage-test', '${buildout:directory}/parts/coverage-test/report']
[coverage-diff]
recipe = zc.recipe.egg
@@ -40,4 +40,3 @@
[coverage]
recipe = zc.recipe.egg
eggs = coverage
- ${test:eggs}
Modified: z3c.coverage/trunk/setup.py
===================================================================
--- z3c.coverage/trunk/setup.py 2012-09-06 13:23:43 UTC (rev 127751)
+++ z3c.coverage/trunk/setup.py 2012-09-06 13:23:49 UTC (rev 127752)
@@ -23,7 +23,7 @@
setup(
name='z3c.coverage',
- version='1.2.1dev',
+ version='1.3.0dev',
author="Zope Community",
author_email="zope3-dev at zope.org",
description="A script to visualize coverage reports via HTML",
@@ -67,6 +67,7 @@
),
install_requires=[
'setuptools',
+ 'coverage',
],
entry_points="""
[console_scripts]
Modified: z3c.coverage/trunk/src/z3c/coverage/README.txt
===================================================================
--- z3c.coverage/trunk/src/z3c/coverage/README.txt 2012-09-06 13:23:43 UTC (rev 127751)
+++ z3c.coverage/trunk/src/z3c/coverage/README.txt 2012-09-06 13:23:49 UTC (rev 127752)
@@ -8,179 +8,60 @@
Luckily we already have the text input ready:
- >>> import os
- >>> import z3c.coverage
- >>> inputDir = os.path.join(
- ... os.path.dirname(z3c.coverage.__file__), 'sampleinput')
+ >>> import os
+ >>> import z3c.coverage
+ >>> inputDir = os.path.join(
+ ... os.path.dirname(z3c.coverage.__file__), 'sampleinput')
Let's create a temporary directory for the output
- >>> import tempfile
- >>> tempDir = tempfile.mkdtemp(prefix='tmp-z3c.coverage-report-')
+ >>> import tempfile, shutil
+ >>> tempDir = tempfile.mkdtemp(prefix='tmp-z3c.coverage-report-')
The output directory will be created if it doesn't exist already
- >>> outputDir = os.path.join(tempDir, 'report')
+ >>> outputDir = os.path.join(tempDir, 'report')
We can now create the coverage report as follows:
- >>> from z3c.coverage import coveragereport
- >>> coveragereport.main([inputDir, outputDir, '--quiet'])
+ >>> from z3c.coverage import coveragereport
+ >>> coveragereport.main([inputDir, outputDir, '--quiet'])
Looking at the output directory, we now see several files:
- >>> print '\n'.join(sorted(os.listdir(outputDir)))
- all.html
- z3c.coverage.__init__.html
- z3c.coverage.coveragediff.html
- z3c.coverage.coveragereport.html
- z3c.coverage.html
- z3c.html
+ >>> print '\n'.join(sorted(os.listdir(outputDir)))
+ all.html
+ z3c.coverage.__init__.html
+ z3c.coverage.coveragediff.html
+ z3c.coverage.coveragereport.html
+ z3c.coverage.html
+ z3c.html
+Let's clean up
-API Tests
----------
+ >>> shutil.rmtree(tempDir)
-``CoverageNode`` Class
-~~~~~~~~~~~~~~~~~~~~~~
-This class represents a node in the source tree. Simple modules are considered
-leaves and do not have children. Let's create a node for the `z3c` namespace
-first:
+coverage.py support
+~~~~~~~~~~~~~~~~~~~
- >>> z3cNode = coveragereport.CoverageNode()
+We also support coverage reports generated by coverage.py
-Before using the API, let's create a few more nodes and a tree from it:
+ >>> inputFile = os.path.join(
+ ... os.path.dirname(z3c.coverage.__file__), 'sampleinput.pickle')
- >>> coverageNode = coveragereport.CoverageNode()
- >>> z3cNode['coverage'] = coverageNode
+ >>> from z3c.coverage import coveragereport
+ >>> coveragereport.main([inputFile, outputDir, '-q',
+ ... '--strip-prefix=/home/mg/src/z3c.coverage/src'])
+ >>> print '\n'.join(sorted(os.listdir(outputDir)))
+ all.html
+ z3c.coverage.__init__.html
+ z3c.coverage.coveragediff.html
+ z3c.coverage.coveragereport.html
+ z3c.coverage.html
+ z3c.html
- >>> reportNode = coveragereport.CoverageNode()
- >>> reportNode._covered, reportNode._total = 40, 134
- >>> coverageNode['coveragereport'] = reportNode
- >>> diffNode = coveragereport.CoverageNode()
- >>> diffNode._covered, diffNode._total = 128, 128
- >>> coverageNode['coveragediff'] = diffNode
-
- >>> initNode = coveragereport.CoverageNode()
- >>> initNode._covered, initNode._total = 0, 0
- >>> coverageNode['__init__'] = initNode
-
-Let's now have a look at the coverage of the `z3c` namespace:
-
- >>> z3cNode.coverage
- (168, 262)
-
-We can also ask for the percentile:
-
- >>> z3cNode.percent
- 64
- >>> initNode.percent
- 100
-
-We can ask for the amount of uncovered lines:
-
- >>> z3cNode.uncovered
- 94
-
-Finally, we also can get a nice output:
-
- >>> print z3cNode
- 64% covered (94 of 262 lines uncovered)
-
-
-`index_to_filename()` function
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Takes an indexed Python path and produces the cover filename for it:
-
- >>> coveragereport.index_to_filename(('z3c', 'coverage', 'coveragereport'))
- 'z3c.coverage.coveragereport.cover'
-
- >>> coveragereport.index_to_filename(())
- ''
-
-`index_to_nice_name()` function
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Takes an indexed Python path and produces a nice "human-readable" string:
-
- >>> coveragereport.index_to_nice_name(('z3c', 'coverage', 'coveragereport'))
- ' coveragereport'
-
- >>> coveragereport.index_to_nice_name(())
- 'Everything'
-
-
-`index_to_name()` function
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Takes an indexed Python path and produces a "human-readable" string:
-
- >>> coveragereport.index_to_name(('z3c', 'coverage', 'coveragereport'))
- 'z3c.coverage.coveragereport'
-
- >>> coveragereport.index_to_name(())
- 'everything'
-
-
-`percent_to_colour()` function
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Given a coverage percentage, this function returns a color to represent the
-coverage:
-
- >>> coveragereport.percent_to_colour(100)
- 'green'
- >>> coveragereport.percent_to_colour(92)
- 'yellow'
- >>> coveragereport.percent_to_colour(85)
- 'orange'
- >>> coveragereport.percent_to_colour(50)
- 'red'
-
-
-`get_svn_revision()` function
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Given a path, the function tries to determine the revision number of the
-file. If it fails, "UNKNOWN" is returned:
-
- >>> path = os.path.dirname(z3c.coverage.__file__)
- >>> coveragereport.get_svn_revision(path) != 'UNKNOWN'
- True
-
- >>> coveragereport.get_svn_revision(path + '/__init__.py')
- 'UNKNOWN'
-
-
-`syntax_highlight()` function
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This function takes a cover file, converts it to a nicely colored HTML output:
-
- >>> filename = os.path.join(
- ... os.path.dirname(z3c.coverage.__file__), '__init__.py')
-
- >>> print coveragereport.syntax_highlight(filename)
- ... # this will fail if you don't have enscript in your $PATH
- <BLANKLINE>
- <I><FONT COLOR="#B22222"># Make a package.
- </FONT></I>
-
-If the highlighing command is not available, no coloration is done:
-
- >>> command_orig = coveragereport.HIGHLIGHT_COMMAND
- >>> coveragereport.HIGHLIGHT_COMMAND = ['aflkakhalkjdsjdhf']
-
- >>> print coveragereport.syntax_highlight(filename)
- # Make a package.
- <BLANKLINE>
-
- >>> coveragereport.HIGHLIGHT_COMMAND = command_orig
-
-
`coveragereport.py` is a script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -195,24 +76,7 @@
>>> execfile(script_file, dict(__name__='__main__'))
-Defaults are chosen when no input and output dir is specified:
-
- >>> def make_coverage_reports_stub(path, report_path, **kw):
- ... print path
- ... print report_path
-
- >>> make_coverage_reports_orig = coveragereport.make_coverage_reports
- >>> coveragereport.make_coverage_reports = make_coverage_reports_stub
-
- >>> sys.argv = ['coveragereport']
- >>> coveragereport.main()
- coverage
- coverage/reports
-
- >>> coveragereport.make_coverage_reports = make_coverage_reports_orig
-
Let's clean up
- >>> import shutil
>>> shutil.rmtree(tempDir)
Modified: z3c.coverage/trunk/src/z3c/coverage/coveragereport.py
===================================================================
--- z3c.coverage/trunk/src/z3c/coverage/coveragereport.py 2012-09-06 13:23:43 UTC (rev 127751)
+++ z3c.coverage/trunk/src/z3c/coverage/coveragereport.py 2012-09-06 13:23:49 UTC (rev 127752)
@@ -16,22 +16,27 @@
Convert trace.py coverage reports to HTML.
-Usage: coveragereport.py [report-directory [output-directory]]
+Usage: coveragereport.py [input-dir-or-file directory [output-directory]]
-Locates plain-text coverage reports (files named
-``dotted.package.name.cover``) in the report directory and produces HTML
-reports in the output directory. The format of plain-text coverage reports is
-as follows: the file name is a dotted Python module name with a ``.cover``
-suffix (e.g. ``zope.app.__init__.cover``). Each line corresponds to the
-source file line with a 7 character wide prefix. The prefix is one of
+Loads coverage data from plain-text coverage reports (files named
+``dotted.package.name.cover``) in the report directory *or* a .coverage
+file produced by http://pypi.python.org/pypi/coverage and produces HTML
+reports in the output directory.
+The format of plain-text coverage reports is as follows: the file name is
+a dotted Python module name with a ``.cover`` suffix (e.g.
+``zope.app.__init__.cover``). Each line corresponds to the source file
+line with a 7 character wide prefix. The prefix is one of
+
' ' if a line is not an executable code line
' NNN: ' where NNN is the number of times this line was executed
'>>>>>> ' if this line was never executed
You can produce such files with the Zope test runner by specifying
``--coverage`` on the command line, or, more generally, by using the
-``trace`` module in the standard library.
+``trace`` module in the standard library. Although you should consider
+using the above-mentioned coverage package instead, as it's much faster
+than trace.py.
$Id$
"""
@@ -43,12 +48,29 @@
import cgi
import subprocess
import optparse
+import tempfile
HIGHLIGHT_COMMAND = ['enscript', '-q', '--footer', '--header', '-h',
'--language=html', '--highlight=python', '--color',
'-o', '-']
+
+class Lazy(object):
+ """Descriptor for lazy evaluation"""
+
+ def __init__(self, func):
+ self.func = func
+ self.name = func.__name__
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ value = self.func(obj)
+ obj.__dict__[self.name] = value
+ return value
+
+
class CoverageNode(dict):
"""Tree node.
@@ -58,56 +80,124 @@
a dict. Item keys are non-qualified module names.
"""
- def __str__(self):
- covered, total = self.coverage
- uncovered = total - covered
- return '%s%% covered (%s of %s lines uncovered)' % \
- (self.percent, uncovered, total)
+ @Lazy
+ def covered(self):
+ return sum(child.covered for child in self.itervalues())
- @property
+ @Lazy
+ def total(self):
+ return sum(child.total for child in self.itervalues())
+
+ @Lazy
+ def uncovered(self):
+ return self.total - self.covered
+
+ @Lazy
def percent(self):
- """Compute the coverage percentage."""
- covered, total = self.coverage
- if total != 0:
- return int(100 * covered / total)
+ if self.total != 0:
+ return 100 * self.covered // self.total
else:
return 100
- @property
- def coverage(self):
- """Return (number_of_lines_covered, number_of_executable_lines).
+ @Lazy
+ def html_source(self):
+ return ''
- Computes the numbers recursively for the first time and caches the
- result.
+ def __str__(self):
+ return '%s%% covered (%s of %s lines uncovered)' % \
+ (self.percent, self.uncovered, self.total)
+
+ def get_at(self, path):
+ """Return a tree node for a given path.
+
+ The path is a sequence of child node names.
"""
- if not hasattr(self, '_total'): # first-time computation
- self._covered = self._total = 0
- for substats in self.values():
- covered_more, total_more = substats.coverage
- self._covered += covered_more
- self._total += total_more
- return self._covered, self._total
+ node = self
+ for name in path:
+ node = node[name]
+ return node
- @property
- def uncovered(self):
- """Compute the number of uncovered code lines."""
- covered, total = self.coverage
- return total - covered
+ def set_at(self, path, node):
+ """Create a tree node at a given path.
+ The path is a sequence of child node names.
-def parse_file(filename):
- """Parse a plain-text coverage report and return (covered, total)."""
- covered = 0
- total = 0
- for line in file(filename):
- if line.startswith(' '*7) or len(line) < 7:
- continue
- total += 1
- if not line.startswith('>>>>>>'):
- covered += 1
- return (covered, total)
+ Creates intermediate nodes if necessary.
+ """
+ parent = self
+ for name in path[:-1]:
+ parent = parent.setdefault(name, CoverageNode())
+ parent[path[-1]] = node
+class TraceCoverageNode(CoverageNode):
+ """Coverage node loaded from an annotated source file."""
+
+ def __init__(self, cover_filename):
+ self.cover_filename = cover_filename
+ self.covered, self.total = self._parse(cover_filename)
+
+ def _parse(self, filename):
+ """Parse a plain-text coverage report and return (covered, total)."""
+ covered = 0
+ total = 0
+ for line in file(filename):
+ if line.startswith(' '*7) or len(line) < 7:
+ continue
+ total += 1
+ if not line.startswith('>>>>>>'):
+ covered += 1
+ return (covered, total)
+
+ @Lazy
+ def html_source(self):
+ text = syntax_highlight(self.cover_filename)
+ text = highlight_uncovered_lines(text)
+ return '<pre>%s</pre>' % text
+
+
+class CoverageCoverageNode(CoverageNode):
+ """Coverage node loaded from a coverage.py data file."""
+
+ def __init__(self, cov, source_filename):
+ self.cov = cov
+ self.source_filename = source_filename
+ (filename_again, statements, excluded, missing,
+ missing_str) = cov.analysis2(source_filename)
+ self.covered = len(statements) - len(excluded) - len(missing)
+ self.total = len(statements) - len(excluded)
+ self._missing = set(missing)
+ self._statements = set(statements)
+ self._excluded = set(excluded)
+
+ @Lazy
+ def annotated_source(self):
+ MISSING = '>>>>>> '
+ STATEMENT = ' 1: '
+ EXCLUDED = ' # '
+ OTHER = ' '
+ lines = []
+ f = open(self.source_filename)
+ for n, line in enumerate(f):
+ n += 1 # workaround lack of enumerate(f, start=1) support in 2.4/5
+ if n in self._missing: prefix = MISSING
+ elif n in self._excluded: prefix = EXCLUDED
+ elif n in self._statements: prefix = STATEMENT
+ else: prefix = OTHER
+ lines.append(prefix + line)
+ return ''.join(lines)
+
+ @Lazy
+ def html_source(self):
+ tmpf = tempfile.NamedTemporaryFile(suffix='.py.cover')
+ tmpf.write(self.annotated_source)
+ tmpf.flush()
+ text = syntax_highlight(tmpf.name)
+ tmpf.close()
+ text = highlight_uncovered_lines(text)
+ return '<pre>%s</pre>' % text
+
+
def get_file_list(path, filter_fn=None):
"""Return a list of files in a directory.
@@ -129,20 +219,7 @@
return filename.split('.')[:-1]
-def get_tree_node(tree, index):
- """Return a tree node for a given path.
-
- The path is a sequence of child node names.
-
- Creates intermediate and leaf nodes if necessary.
- """
- node = tree
- for i in index:
- node = node.setdefault(i, CoverageNode())
- return node
-
-
-def create_tree(filelist, path):
+def create_tree_from_files(filelist, path):
"""Create a tree with coverage statistics.
Takes the directory for coverage reports and a list of filenames relative
@@ -151,15 +228,34 @@
Returns the root node of the tree.
"""
- tree = CoverageNode()
+ root = CoverageNode()
for filename in filelist:
tree_index = filename_to_list(filename)
- node = get_tree_node(tree, tree_index)
filepath = os.path.join(path, filename)
- node._covered, node._total = parse_file(filepath)
- return tree
+ root.set_at(tree_index, TraceCoverageNode(filepath))
+ return root
+def create_tree_from_coverage(cov, strip_prefix=None):
+ """Create a tree with coverage statistics.
+
+ Takes a coverage.coverage() instance.
+
+ Returns the root node of the tree.
+ """
+ root = CoverageNode()
+ for filename in cov.data.measured_files():
+ if strip_prefix and filename.startswith(strip_prefix):
+ short_name = filename[len(strip_prefix):].lstrip(os.path.sep)
+ else:
+ short_name = cov.file_locator.relative_filename(filename)
+ tree_index = filename_to_list(short_name.replace(os.path.sep, '.'))
+ if 'tests' in tree_index or 'ftests' in tree_index:
+ continue
+ root.set_at(tree_index, CoverageCoverageNode(cov, filename))
+ return root
+
+
def traverse_tree(tree, index, function):
"""Preorder traversal of a tree.
@@ -193,13 +289,6 @@
return 'index.html'
-def index_to_filename(index):
- """Construct the plain-text coverage report filename for a node."""
- if index:
- return '%s.cover' % '.'.join(index)
- return ''
-
-
def index_to_nice_name(index):
"""Construct an indented name for the node given its path."""
if index:
@@ -228,9 +317,6 @@
def print_table_row(html, node, file_index):
"""Generate a row for an HTML table."""
- covered, total = node.coverage
- uncovered = total - covered
- percent = node.percent
nice_name = index_to_nice_name(file_index)
if not node.keys():
nice_name += '.py'
@@ -239,9 +325,9 @@
print >> html, '<tr><td><a href="%s">%s</a></td>' % \
(index_to_url(file_index), nice_name),
print >> html, '<td style="background: %s"> </td>' % \
- (percent_to_colour(percent)),
+ (percent_to_colour(node.percent)),
print >> html, '<td>covered %s%% (%s of %s uncovered)</td></tr>' % \
- (percent, uncovered, total)
+ (node.percent, node.uncovered, node.total)
HEADER = """
@@ -284,7 +370,7 @@
"""
html = open(output_filename, 'w')
print >> html, HEADER % {'name': index_to_name(my_index)}
- info = [(get_tree_node(tree, node_path), node_path) for node_path in info]
+ info = [(tree.get_at(node_path), node_path) for node_path in info]
def key((node, node_path)):
return (len(node_path), -node.uncovered, node_path and node_path[-1])
info.sort(key=key)
@@ -293,18 +379,7 @@
continue # skip root node
print_table_row(html, node, file_index)
print >> html, '</table><hr/>'
- if not get_tree_node(tree, my_index):
- file_path = os.path.join(path, index_to_filename(my_index))
- text = syntax_highlight(file_path)
- def color_uncov(line):
- # The line must start with the missing line indicator or some HTML
- # was put in front of it.
- if line.startswith('>'*6) or '>'+'>'*6 in line:
- return ('<div class="notcovered">%s</div>'
- % line.rstrip('\n'))
- return line
- text = ''.join(map(color_uncov, text.splitlines(True)))
- print >> html, '<pre>%s</pre>' % text
+ print >> html, tree.get_at(my_index).html_source
print >> html, FOOTER % footer
html.close()
@@ -314,7 +389,7 @@
# TODO: use pygments instead
try:
pipe = subprocess.Popen(HIGHLIGHT_COMMAND + [filename],
- stdout=subprocess.PIPE)
+ stdout=subprocess.PIPE)
text, stderr = pipe.communicate()
if pipe.returncode != 0:
raise OSError
@@ -328,6 +403,19 @@
return text
+def highlight_uncovered_lines(text):
+ """Highlight lines beginning with '>>>>>>'."""
+ def color_uncov(line):
+ # The line must start with the missing line indicator or some HTML
+ # was put in front of it.
+ if line.startswith('>'*6) or '>'+'>'*6 in line:
+ return ('<div class="notcovered">%s</div>'
+ % line.rstrip('\n'))
+ return line
+ text = ''.join(map(color_uncov, text.splitlines(True)))
+ return text
+
+
def generate_htmls_from_tree(tree, path, report_path, footer=""):
"""Generate HTML files for all nodes in the tree.
@@ -404,22 +492,39 @@
'ftests' not in parts)
-def make_coverage_reports(path, report_path, verbose=True):
+def load_coverage(path, opts):
+ """Load coverage information from ``path``.
+
+ ``path`` can point to a directory full of files named *.cover, or it can
+ point to a single pickle file containing coverage information.
+ """
+ if os.path.isdir(path):
+ filelist = get_file_list(path, filter_fn)
+ tree = create_tree_from_files(filelist, path)
+ return tree
+ else:
+ import coverage
+ cov = coverage.coverage(data_file=path, config_file=False)
+ cov.load()
+ tree = create_tree_from_coverage(cov, strip_prefix=opts.strip_prefix)
+ return tree
+
+
+def make_coverage_reports(path, report_path, opts):
"""Convert reports from ``path`` into HTML files in ``report_path``."""
- create_report_path(report_path)
- filelist = get_file_list(path, filter_fn)
- if verbose:
+ if opts.verbose:
print "Loading coverage reports from %s" % path
- tree = create_tree(filelist, path)
- if verbose:
+ tree = load_coverage(path, opts=opts)
+ if opts.verbose:
print tree
rev = get_svn_revision(os.path.join(path, os.path.pardir))
timestamp = str(datetime.datetime.utcnow())+"Z"
footer = "Generated for revision %s on %s" % (rev, timestamp)
+ create_report_path(report_path)
generate_htmls_from_tree(tree, path, report_path, footer)
generate_overall_html_from_tree(
tree, os.path.join(report_path, 'all.html'), footer)
- if verbose:
+ if opts.verbose:
print "Generated HTML files in %s" % report_path
@@ -441,17 +546,20 @@
"""Process command line arguments and produce HTML coverage reports."""
parser = optparse.OptionParser(
- "usage: %prog [options] [inputdir [outputdir]]",
+ "usage: %prog [options] [inputpath [outputdir]]",
description=
- 'Converts trace.py coverage reports to HTML.'
- ' If the input directory is omitted, it defaults to ./coverage.'
- ' If the output directory is omitted, it defaults to'
- ' ./coverage/report.')
+ 'Converts coverage reports to HTML. If the input path is'
+ ' omitted, it defaults to coverage or .coverage, whichever'
+ ' exists. If the output directory is omitted, it defaults to'
+ ' inputpath + /report or ./coverage-reports, depending on whether'
+ ' the input path points to a directory or a file.')
parser.add_option('-q', '--quiet', help='be quiet',
action='store_const', const=0, dest='verbose')
parser.add_option('-v', '--verbose', help='be verbose (default)',
action='store_const', const=1, dest='verbose', default=1)
+ parser.add_option('--strip-prefix', metavar='PREFIX',
+ help='strip base directory from filenames loaded from .coverage')
if args is None:
args = sys.argv[1:]
@@ -460,17 +568,26 @@
if len(args) > 0:
path = args[0]
else:
- path = 'coverage'
+ if os.path.exists('coverage'):
+ # backward compat: default input path used to be 'coverage'
+ path = 'coverage'
+ else:
+ path = '.coverage'
if len(args) > 1:
report_path = args[1]
else:
- report_path = 'coverage/reports'
+ if os.path.isdir(path):
+ # backward compat: default input path is 'coverage', default output
+ # path is 'coverage/reports'
+ report_path = os.path.join(path, 'reports')
+ else:
+ report_path = 'coverage-reports'
if len(args) > 2:
parser.error("too many arguments")
- make_coverage_reports(path, report_path, verbose=opts.verbose)
+ make_coverage_reports(path, report_path, opts=opts)
if __name__ == '__main__':
Added: z3c.coverage/trunk/src/z3c/coverage/sampleinput.pickle
===================================================================
--- z3c.coverage/trunk/src/z3c/coverage/sampleinput.pickle (rev 0)
+++ z3c.coverage/trunk/src/z3c/coverage/sampleinput.pickle 2012-09-06 13:23:49 UTC (rev 127752)
@@ -0,0 +1,7 @@
+}q(U collectorqUcoverage v3.5.2qUlinesq}q(U:/home/mg/src/z3c.coverage/src/z3c/coverage/coveragediff.pyq]q(KKKKKKK!K"K6KAKDKZK[K\K]K^K_K`KaKdKjKkKnKxK{KKKKKKKKKKKKKKKKKKKKK¡K¢K£K¤K¥K¨KªK«K¬KK®K¯K²K½K¾K¿KÂKÜKÝKÞKáKâKäKæKçKèKêKîKïKðKñKòKóKôKõKöKùKúKüKýKÿMMMMMMM M
+MM
+MMMMMMMMMMMMMMMM!M"M#M$M'M)M*M+M,M-M.M/M0M1M3M4M5M6M7M8M9M;M<M=M>M?M@MBMCMDMEMFMIMJeU3/home/mg/src/z3c.coverage/src/z3c/coverage/tests.pyq]q (KKKKK K
+KKKKKKKKAKKKK°KÄKÕKæKùMMM"M#M%M&M'M(M*M+M,M.M/M0M1M2M5eU</home/mg/src/z3c.coverage/src/z3c/coverage/coveragereport.pyq
+]q(K*K+K-K.K/K0K1K2K3K6K7K8K;K<K>K?K at KBKCKDKEKFKGKJKQKSKUKWKYK[K]K_KaKbKdKfKhKjKkKlKnKsKtKuKvKxKKKKK
KKKKKKKKKKKKKKKKKKK K¢KK½KÈKÎKÑKÚKÝKæKçKèKéKêKëKîKÿMMMMMMMMMMMM M"M#M%M(M*M+M,M/M0M1M2M3M4M5M7M:M<M=M>M@MAMBMCMDMEMFMVM^MaMoMpMqMrMsMtMuMvMwMxMyMzM{M|MMMMM
MMMMMMMMMMMMMMMM¨M©MªM«M¬MM®M¯M°M±M²M³M´MµM¶M·M¸M»M½M¾M¿MÀMÁMÂMÃMÄMÅMÆMÇMÊMËMÌMÏMäMåMæMçMèMëMñMòMóMôMýMÿMMMMMMMM M
+MMMMMMMMMMMM M!M#M(M)M*M+M-M.M/M1M2M4M6M8M:M;M=M@MBMDMGMJMKeU6/home/mg/src/z3c.coverage/src/z3c/coverage/__init__.pyq]q
+Kauu.
\ No newline at end of file
Modified: z3c.coverage/trunk/src/z3c/coverage/tests.py
===================================================================
--- z3c.coverage/trunk/src/z3c/coverage/tests.py 2012-09-06 13:23:43 UTC (rev 127751)
+++ z3c.coverage/trunk/src/z3c/coverage/tests.py 2012-09-06 13:23:49 UTC (rev 127752)
@@ -3,15 +3,176 @@
Test suite for z3c.coverage
"""
-import unittest
import doctest
+import os
import re
+import shutil
+import sys
+import tempfile
+import unittest
from zope.testing import renormalizing
-from z3c.coverage.coveragereport import traverse_tree_in_order
+import z3c.coverage
+from z3c.coverage import coveragereport
+from z3c.coverage.coveragereport import (
+ Lazy, CoverageNode, index_to_nice_name, index_to_name, percent_to_colour,
+ traverse_tree_in_order, get_svn_revision, syntax_highlight)
+def doctest_Lazy():
+ """Test for Lazy
+
+ >>> class MyClass(object):
+ ... @Lazy
+ ... def foo(self):
+ ... print "computing foo"
+ ... return 42
+
+ This is basic lazy evaluation
+
+ >>> obj = MyClass()
+ >>> obj.foo
+ computing foo
+ 42
+
+ The difference from @propetry is that the value is only computed once
+
+ >>> obj.foo
+ 42
+
+ Another difference is that you can override it
+
+ >>> obj.foo = 25
+ >>> obj.foo
+ 25
+
+ To force recomputation, delete the cached value
+
+ >>> del obj.foo
+ >>> obj.foo
+ computing foo
+ 42
+
+ You can access the class attribute as well
+
+ >>> MyClass.foo # doctest: +ELLIPSIS
+ <z3c.coverage.coveragereport.Lazy object at ...>
+
+ """
+
+
+def doctest_CoverageNode():
+ """Test for CoverageNode
+
+ >>> root = CoverageNode()
+ >>> root['z3c'] = CoverageNode()
+ >>> root['z3c']['coverage'] = CoverageNode()
+ >>> root['z3c']['coverage']['coveragereport'] = CoverageNode()
+ >>> root['z3c']['coverage']['coveragereport'].covered = 40
+ >>> root['z3c']['coverage']['coveragereport'].total = 134
+ >>> root['z3c']['coverage']['coveragediff'] = CoverageNode()
+ >>> root['z3c']['coverage']['coveragediff'].covered = 128
+ >>> root['z3c']['coverage']['coveragediff'].total = 128
+ >>> root['z3c']['coverage']['__init__'] = CoverageNode()
+ >>> root['z3c']['coverage']['__init__'].covered = 0
+ >>> root['z3c']['coverage']['__init__'].total = 0
+
+ ``covered`` and ``total`` are lazily-computed properties that sum over
+ their children, recursively
+
+ >>> root.covered
+ 168
+ >>> root.total
+ 262
+
+ ``uncovered`` is also a lazily-computed property that computes the
+ difference
+
+ >>> root.uncovered
+ 94
+
+ We can also ask for the percentile
+
+ >>> root.percent
+ 64
+
+ and avoid division by zero
+
+ >>> root['z3c']['coverage']['__init__'].percent
+ 100
+
+ There are helpers for traversal
+
+ >>> root.get_at([]) is root
+ True
+ >>> root.get_at(['z3c', 'coverage']) is root['z3c']['coverage']
+ True
+ >>> root.get_at(['z3c', 'nosuch'])
+ Traceback (most recent call last):
+ ...
+ KeyError: 'nosuch'
+
+ and helpers for construction
+
+ >>> root.set_at(['z3c', 'somethingelse'], CoverageNode())
+ >>> root['z3c']['somethingelse']
+ {}
+
+ Finally, we also can get a nice output:
+
+ >>> print root['z3c']
+ 64% covered (94 of 262 lines uncovered)
+
+ """
+
+
+def doctest_index_to_nice_name():
+ """Test for index_to_nice_name
+
+ Takes an indexed Python path and produces a nice "human-readable" string:
+
+ >>> index_to_nice_name(['z3c', 'coverage', 'coveragereport'])
+ ' coveragereport'
+
+ >>> index_to_nice_name([])
+ 'Everything'
+
+ """
+
+
+def doctest_index_to_name():
+ """Test for index_to_name
+
+ Takes an indexed Python path and produces a "human-readable" string:
+
+ >>> index_to_name(['z3c', 'coverage', 'coveragereport'])
+ 'z3c.coverage.coveragereport'
+
+ >>> index_to_name([])
+ 'everything'
+
+ """
+
+
+def doctest_percent_to_colour():
+ """Test for percent_to_colour
+
+ Given a coverage percentage, this function returns a color to represent the
+ coverage:
+
+ >>> percent_to_colour(100)
+ 'green'
+ >>> percent_to_colour(92)
+ 'yellow'
+ >>> percent_to_colour(85)
+ 'orange'
+ >>> percent_to_colour(50)
+ 'red'
+
+ """
+
+
def doctest_traverse_tree_in_order():
"""Test for traverse_tree_in_order
@@ -32,6 +193,96 @@
"""
+def doctest_get_svn_revision():
+ """Test for get_svn_revision
+
+ Given a path, the function tries to determine the revision number of the
+ file. If it fails, "UNKNOWN" is returned:
+
+ >>> path = os.path.dirname(z3c.coverage.__file__)
+ >>> get_svn_revision(path) != 'UNKNOWN'
+ ... # This will fail if you don't have svnversion in your $PATH
+ True
+
+ >>> get_svn_revision(path + '/nosuchpath')
+ 'UNKNOWN'
+
+ """
+
+
+def doctest_syntax_highlight_with_enscript():
+ """Test for syntax_highlight
+
+ This function takes a cover file, converts it to a nicely colored HTML output:
+
+ >>> filename = os.path.join(
+ ... os.path.dirname(z3c.coverage.__file__), '__init__.py')
+
+ >>> print syntax_highlight(filename)
+ ... # this will fail if you don't have enscript in your $PATH
+ <BLANKLINE>
+ <I><FONT COLOR="#B22222"># Make a package.
+ </FONT></I>
+
+ """
+
+
+def doctest_syntax_highlight_without_enscript():
+ """Test for syntax_highlight
+
+ If the highlighing command is not available, no coloration is done:
+
+ >>> command_orig = coveragereport.HIGHLIGHT_COMMAND
+ >>> coveragereport.HIGHLIGHT_COMMAND = ['aflkakhalkjdsjdhf']
+
+ >>> filename = os.path.join(
+ ... os.path.dirname(z3c.coverage.__file__), '__init__.py')
+
+ >>> print syntax_highlight(filename)
+ # Make a package.
+ <BLANKLINE>
+
+ >>> coveragereport.HIGHLIGHT_COMMAND = command_orig
+
+ """
+
+def doctest_main_default_arguments():
+ """Test for main()
+
+ Defaults are chosen when no input and output dir is specified.
+
+ >>> def make_coverage_reports_stub(path, report_path, **kw):
+ ... print path
+ ... print report_path
+
+ >>> make_coverage_reports_orig = coveragereport.make_coverage_reports
+ >>> coveragereport.make_coverage_reports = make_coverage_reports_stub
+
+ >>> orig_argv = sys.argv
+ >>> sys.argv = ['coveragereport']
+
+ >>> tempDir = tempfile.mkdtemp(prefix='tmp-z3c.coverage-report-')
+ >>> orig_cwd = os.getcwd()
+ >>> os.chdir(tempDir)
+ >>> os.mkdir('coverage')
+
+ >>> coveragereport.main()
+ coverage
+ coverage/reports
+
+ >>> os.rmdir('coverage')
+ >>> coveragereport.main()
+ .coverage
+ coverage-reports
+
+ >>> coveragereport.make_coverage_reports = make_coverage_reports_orig
+ >>> sys.argv = orig_argv
+ >>> os.chdir(orig_cwd)
+ >>> shutil.rmtree(tempDir)
+
+ """
+
+
def test_suite():
checker = renormalizing.RENormalizing([
# optparse in Python 2.4 prints "usage:" and "options:",
More information about the checkins
mailing list