[Checkins] SVN: z3c.coverage/ Add a coverage package to generate test coverage reports. Most credit

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu May 24 07:13:56 EDT 2007


Log message for revision 75930:
  Add a coverage package to generate test coverage reports. Most credit 
  actually goes to Vidas Paz?\194?\190usiand Marius Gedminas of POV who wrote the 
  original script. Marius provided me with the latest version under ZPL 2.1 
  yesterday.
  
  I am playing with creating a nice script for this via setuptools.
  
  

Changed:
  A   z3c.coverage/
  A   z3c.coverage/branches/
  A   z3c.coverage/tags/
  A   z3c.coverage/trunk/
  A   z3c.coverage/trunk/AUTHORS.txt
  A   z3c.coverage/trunk/LICENSE.txt
  A   z3c.coverage/trunk/README.txt
  A   z3c.coverage/trunk/bootstrap.py
  A   z3c.coverage/trunk/buildout.cfg
  A   z3c.coverage/trunk/setup.py
  A   z3c.coverage/trunk/src/
  A   z3c.coverage/trunk/src/z3c/
  A   z3c.coverage/trunk/src/z3c/__init__.py
  A   z3c.coverage/trunk/src/z3c/coverage/
  A   z3c.coverage/trunk/src/z3c/coverage/__init__.py
  A   z3c.coverage/trunk/src/z3c/coverage/coveragereport.py

-=-
Added: z3c.coverage/trunk/AUTHORS.txt
===================================================================
--- z3c.coverage/trunk/AUTHORS.txt	                        (rev 0)
+++ z3c.coverage/trunk/AUTHORS.txt	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,3 @@
+- Vidas Pažusis
+- Marius Gedminas
+- Stephan Richter


Property changes on: z3c.coverage/trunk/AUTHORS.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.coverage/trunk/LICENSE.txt
===================================================================
--- z3c.coverage/trunk/LICENSE.txt	                        (rev 0)
+++ z3c.coverage/trunk/LICENSE.txt	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
+  AND ANY EXPRESSED 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 HOLDERS 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.


Property changes on: z3c.coverage/trunk/LICENSE.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.coverage/trunk/README.txt
===================================================================
--- z3c.coverage/trunk/README.txt	                        (rev 0)
+++ z3c.coverage/trunk/README.txt	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,2 @@
+This package produces a nice HTML representation of the coverage data
+generated by the Zope test runner.


Property changes on: z3c.coverage/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.coverage/trunk/bootstrap.py
===================================================================
--- z3c.coverage/trunk/bootstrap.py	                        (rev 0)
+++ z3c.coverage/trunk/bootstrap.py	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: z3c.coverage/trunk/bootstrap.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.coverage/trunk/buildout.cfg
===================================================================
--- z3c.coverage/trunk/buildout.cfg	                        (rev 0)
+++ z3c.coverage/trunk/buildout.cfg	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,7 @@
+[buildout]
+develop = .
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.coverage [test]

Added: z3c.coverage/trunk/setup.py
===================================================================
--- z3c.coverage/trunk/setup.py	                        (rev 0)
+++ z3c.coverage/trunk/setup.py	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup
+
+$Id$
+"""
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup (
+    name='z3c.coverage',
+    version='0.1.0c1',
+    author = "Zope Community",
+    author_email = "zope3-dev at zope.org",
+    description = "A script to visualize coverage reports via HTML",
+    long_description=(
+        read('README.txt')
+        ),
+    license = "ZPL 2.1",
+    keywords = "zope3 test coverage html",
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Zope Public License',
+        'Programming Language :: Python',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Framework :: Zope3'],
+    url = 'http://svn.zope.org/z3c.coverage',
+    packages = find_packages('src'),
+    include_package_data = True,
+    package_dir = {'':'src'},
+    namespace_packages = ['z3c'],
+    extras_require = dict(
+        test = ['zope.testing'],
+        ),
+    install_requires = [
+        'setuptools',
+        ],
+    entry_points = """
+        [console_scripts]
+        coverage = z3c.coverage.coveragereport:main
+        """,
+    dependency_links = ['http://download.zope.org/distribution'],
+    zip_safe = False,
+    )


Property changes on: z3c.coverage/trunk/setup.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.coverage/trunk/src/z3c/__init__.py
===================================================================
--- z3c.coverage/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.coverage/trunk/src/z3c/__init__.py	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,6 @@
+try:
+    # Declare this a namespace package if pkg_resources is available.
+    import pkg_resources
+    pkg_resources.declare_namespace('z3c')
+except ImportError:
+    pass


Property changes on: z3c.coverage/trunk/src/z3c/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.coverage/trunk/src/z3c/coverage/__init__.py
===================================================================
--- z3c.coverage/trunk/src/z3c/coverage/__init__.py	                        (rev 0)
+++ z3c.coverage/trunk/src/z3c/coverage/__init__.py	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: z3c.coverage/trunk/src/z3c/coverage/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id

Added: z3c.coverage/trunk/src/z3c/coverage/coveragereport.py
===================================================================
--- z3c.coverage/trunk/src/z3c/coverage/coveragereport.py	                        (rev 0)
+++ z3c.coverage/trunk/src/z3c/coverage/coveragereport.py	2007-05-24 11:13:56 UTC (rev 75930)
@@ -0,0 +1,401 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Coverage Report
+
+Convert unit test coverage reports to HTML.
+
+Usage: coverage_reports.py [report-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
+
+  '       ' 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.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import sys
+import os
+import datetime
+import cgi
+
+
+class CoverageNode(dict):
+    """Tree node.
+
+    Leaf nodes have no children (items() == []) and correspond to Python
+    modules.  Branches correspond to Python packages.  Child nodes are
+    accessible via the Python mapping protocol, as you would normally use
+    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)
+
+    @property
+    def percent(self):
+        """Compute the coverage percentage."""
+        covered, total = self.coverage
+        if total != 0:
+            return int(100 * covered / total)
+        else:
+            return 100
+
+    @property
+    def coverage(self):
+        """Return (number_of_lines_covered, number_of_executable_lines).
+
+        Computes the numbers recursively for the first time and caches the
+        result.
+        """
+        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
+
+    @property
+    def uncovered(self):
+        """Compute the number of uncovered code lines."""
+        covered, total = self.coverage
+        return total - covered
+
+
+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)
+
+
+def get_file_list(path, filter_fn=None):
+    """Return a list of files in a directory.
+
+    If you can specify a predicate (a callable), only file names matching it
+    will be returned.
+    """
+    return filter(filter_fn, os.listdir(path))
+
+
+def filename_to_list(filename):
+    """Return a list of package/module names from a filename.
+
+    One example is worth a thousand descriptions:
+
+        >>> filename_to_list('schooltool.app.__init__.cover')
+        ['schooltool', 'app', '__init__']
+
+    """
+    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):
+    """Create a tree with coverage statistics.
+
+    Takes the directory for coverage reports and a list of filenames relative
+    to that directory.  Parses all the files and constructs a module tree with
+    coverage statistics.
+
+    Returns the root node of the tree.
+    """
+    tree = 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
+
+
+def traverse_tree(tree, index, function):
+    """Preorder traversal of a tree.
+
+    ``index`` is the path of the root node (usually []).
+
+    ``function`` gets one argument: the path of a node.
+    """
+    function(tree, index)
+    for key, node in tree.items():
+        traverse_tree(node, index + [key], function)
+
+
+def traverse_tree_in_order(tree, index, function, order_by):
+    """Preorder traversal of a tree.
+
+    ``index`` is the path of the root node (usually []).
+
+    ``function`` gets one argument: the path of a node.
+
+    ``order_by`` gets one argument a tuple of (key, node).
+    """
+    function(tree, index)
+    for key, node in sorted(tree.items(), key=order_by):
+        traverse_tree(node, index + [key], function)
+
+
+def index_to_url(index):
+    """Construct a relative hyperlink to a tree node given its path."""
+    if index:
+        return '%s.html' % '.'.join(index)
+    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:
+        return '&nbsp;' * 4 * (len(index) - 1) + index[-1]
+    else:
+        return 'Everything'
+
+
+def index_to_name(index):
+    """Construct the full name for the node given its path."""
+    if index:
+        return '.'.join(index)
+    return 'everything'
+
+
+def percent_to_colour(percent):
+    if percent == 100:
+        return 'green'
+    elif percent >= 90:
+        return 'yellow'
+    elif percent >= 80:
+        return 'orange'
+    else:
+        return 'red'
+
+
+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'
+    else:
+        nice_name += '/'
+    print >> html, '<tr><td><a href="%s">%s</a></td>' % \
+                   (index_to_url(file_index), nice_name),
+    print >> html, '<td style="background: %s">&nbsp;&nbsp;&nbsp;&nbsp;</td>' % \
+                   (percent_to_colour(percent)),
+    print >> html, '<td>covered %s%% (%s of %s uncovered)</td></tr>' % \
+                   (percent, uncovered, total)
+
+
+HEADER = """
+    <html>
+      <head><title>Unit test coverage for %(name)s</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Unit test coverage for %(name)s</h1>
+      <table>
+    """
+
+
+FOOTER = """
+      <div class="footer">
+      %s
+      </div>
+    </body>
+    </html>"""
+
+
+def generate_html(output_filename, tree, my_index, info, path, footer=""):
+    """Generate HTML for a tree node.
+
+    ``output_filename`` is the output file name.
+
+    ``tree`` is the root node of the tree.
+
+    ``my_index`` is the path of the node for which you are generating this HTML
+    file.
+
+    ``info`` is a list of paths of child nodes.
+
+    ``path`` is the directory name for the plain-text report files.
+    """
+    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]
+    def key((node, node_path)):
+        return (len(node_path), -node.uncovered, node_path and node_path[-1])
+    info.sort(key=key)
+    for node, file_index in info:
+        if not file_index:
+            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):
+            if '&gt;&gt;&gt;&gt;&gt;&gt;' 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, FOOTER % footer
+    html.close()
+
+
+def syntax_highlight(filename):
+    """Return HTML with syntax-highlighted Python code from a file."""
+    # XXX can get painful if filenames contain unsafe characters
+    pipe = os.popen('enscript -q --footer --header -h --language=html'
+                    ' --highlight=python --color -o - "%s"' % filename,
+                    'r')
+    text = pipe.read()
+    if pipe.close():
+        # Failed to run enscript; maybe it is not installed?  Disable
+        # syntax highlighting then.
+        text = cgi.escape(file(filename).read())
+    else:
+        text = text[text.find('<PRE>')+len('<PRE>'):]
+        text = text[:text.find('</PRE>')]
+    return text
+
+
+def generate_htmls_from_tree(tree, path, report_path, footer=""):
+    """Generate HTML files for all nodes in the tree.
+
+    ``tree`` is the root node of the tree.
+
+    ``path`` is the directory name for the plain-text report files.
+
+    ``report_path`` is the directory name for the output files.
+    """
+    def make_html(node, my_index):
+        info = []
+        def list_parents_and_children(node, index):
+            position = len(index)
+            my_position = len(my_index)
+            if position <= my_position and index == my_index[:position]:
+                info.append(index)
+            elif (position == my_position + 1 and
+                  index[:my_position] == my_index):
+                info.append(index)
+            return
+        traverse_tree(tree, [], list_parents_and_children)
+        output_filename = os.path.join(report_path, index_to_url(my_index))
+        if not my_index:
+            return # skip root node
+        generate_html(output_filename, tree, my_index, info, path, footer)
+    traverse_tree(tree, [], make_html)
+
+
+def generate_overall_html_from_tree(tree, output_filename, footer=""):
+    """Generate an overall HTML file for all nodes in the tree."""
+    html = open(output_filename, 'w')
+    print >> html, HEADER % {'name': ', '.join(sorted(tree.keys()))}
+    def print_node(node, file_index):
+        if file_index: # skip root node
+            print_table_row(html, node, file_index)
+    def sort_by((key, node)):
+        return (-node.uncovered, key)
+    traverse_tree_in_order(tree, [], print_node, sort_by)
+    print >> html, '</table><hr/>'
+    print >> html, FOOTER % footer
+    html.close()
+
+
+def make_coverage_reports(path, report_path):
+    """Convert reports from ``path`` into HTML files in ``report_path``."""
+    def filter_fn(filename):
+        return (filename.endswith('.cover') and
+                'test' not in filename and
+                not filename.startswith('<'))
+    filelist = get_file_list(path, filter_fn)
+    tree = create_tree(filelist, path)
+    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)
+    generate_htmls_from_tree(tree, path, report_path, footer)
+    generate_overall_html_from_tree(tree, os.path.join(report_path,
+                                                       'all.html'), footer)
+
+
+def get_svn_revision(path):
+    """Return the Subversion revision number for a working directory."""
+    rev = os.popen('svnversion "%s"' % path, 'r').readline().strip()
+    if not rev:
+        rev = "UNKNOWN"
+    return rev
+
+
+def main():
+    """Process command line arguments and produce HTML coverage reports."""
+    if len(sys.argv) > 1:
+        path = sys.argv[1]
+    else:
+        path = 'coverage'
+    if len(sys.argv) > 2:
+        report_path = sys.argv[2]
+    else:
+        report_path = 'coverage/reports'
+    make_coverage_reports(path, report_path)
+
+
+if __name__ == '__main__':
+    main()


Property changes on: z3c.coverage/trunk/src/z3c/coverage/coveragereport.py
___________________________________________________________________
Name: svn:keywords
   + Id



More information about the Checkins mailing list