changeset 354:2ffab7963b8d

add Java recipe command for parsing code coverage from [http://cobertura.sf.net Cobertura] XML reports
author mgood
date Fri, 26 May 2006 14:48:28 +0000
parents ad18a9a70266
children c67532c7d0d2
files bitten/build/javatools.py bitten/build/tests/javatools.py setup.py
diffstat 3 files changed, 201 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/build/javatools.py
+++ b/bitten/build/javatools.py
@@ -1,6 +1,7 @@
 # -*- coding: iso8859-1 -*-
 #
 # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2006 Matthew Good <matt@matt-good.net>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -141,3 +142,74 @@
         log.warning('Error opening JUnit results file (%s)', e)
     except xmlio.ParseError, e:
         log.warning('Error parsing JUnit results file (%s)', e)
+
+class _LineCounter(object):
+    def __init__(self):
+        self.lines = []
+        self.covered = 0
+        self.num_lines = 0
+
+    def __getitem__(self, idx):
+        if idx >= len(self.lines):
+            return 0
+        return self.lines[idx]
+
+    def __setitem__(self, idx, val):
+        idx = int(idx) - 1 # 1-indexed to 0-indexed
+        from itertools import repeat
+        if idx >= len(self.lines):
+            self.lines.extend(repeat('-', idx - len(self.lines) + 1))
+        self.lines[idx] = val
+        self.num_lines += 1
+        if val != '0':
+            self.covered += 1
+
+    def line_hits(self):
+        return ' '.join(self.lines)
+    line_hits = property(line_hits)
+
+    def percentage(self):
+        if self.num_lines == 0:
+            return 0
+        return int(round(self.covered * 100. / self.num_lines))
+    percentage = property(percentage)
+
+
+def cobertura(ctxt, file_=None):
+    assert file_, 'Missing required attribute "file"'
+
+    coverage = xmlio.Fragment()
+    doc = xmlio.parse(open(ctxt.resolve(file_)))
+    srcdir = [s.gettext().strip() for ss in doc.children('sources')
+                                  for s in ss.children('source')][0]
+
+    classes = [cls for pkgs in doc.children('packages')
+                   for pkg in pkgs.children('package')
+                   for clss in pkg.children('classes')
+                   for cls in clss.children('class')]
+
+    counters = {}
+    class_names = {}
+
+    for cls in classes:
+        filename = cls.attr['filename'].replace(os.sep, '/')
+        name = cls.attr['name']
+        if not '$' in name: # ignore internal classes
+            class_names[filename] = name
+        counter = counters.get(filename)
+        if counter is None:
+            counter = counters[filename] = _LineCounter()
+        lines = [l for ls in cls.children('lines')
+                   for l in ls.children('line')]
+        for line in lines:
+            counter[line.attr['number']] = line.attr['hits']
+
+    for filename, name in class_names.iteritems():
+        counter = counters[filename]
+        module = xmlio.Element('coverage', name=name,
+                               file=posixpath.join(srcdir, filename),
+                               lines=counter.num_lines,
+                               percentage=counter.percentage)
+        module.append(xmlio.Element('line_hits')[counter.line_hits])
+        coverage.append(module)
+    ctxt.report('coverage', coverage)
new file mode 100644
--- /dev/null
+++ b/bitten/build/tests/javatools.py
@@ -0,0 +1,128 @@
+# -*- coding: iso8859-1 -*-
+#
+# Copyright (C) 2006 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2006 Matthew Good <matt@matt-good.net>
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://bitten.cmlenz.net/wiki/License.
+
+import os.path
+import shutil
+import tempfile
+import unittest
+
+from bitten.build import javatools
+from bitten.recipe import Context
+
+class CoberturaTestCase(unittest.TestCase):
+    xml_template="""<?xml version="1.0"?>
+<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-02.dtd">
+
+<coverage timestamp="1148533713840">
+  <sources>
+    <source>src</source>
+  </sources>
+  <packages>
+    <package name="test">
+      <classes>%s
+      </classes>
+    </package>
+  </packages>
+</coverage>"""
+
+    def setUp(self):
+        self.basedir = os.path.realpath(tempfile.mkdtemp())
+        self.ctxt = Context(self.basedir)
+
+    def tearDown(self):
+        shutil.rmtree(self.basedir)
+
+    def _create_file(self, *path, **kw):
+        filename = os.path.join(self.basedir, *path)
+        dirname = os.path.dirname(filename)
+        if not os.path.exists(dirname):
+            os.makedirs(dirname)
+        fd = file(filename, 'w')
+        content = kw.get('content')
+        if content is not None:
+            fd.write(content)
+        fd.close()
+        return filename[len(self.basedir) + 1:]
+
+    def test_basic(self):
+        filename = self._create_file('coverage.xml', content=self.xml_template % """
+        <class name="test.TestClass" filename="test/TestClass.java">
+          <lines>
+            <line number="1" hits="0" branch="false"/>
+            <line number="2" hits="1" branch="false"/>
+            <line number="3" hits="0" branch="false"/>
+            <line number="4" hits="2" branch="false"/>
+          </lines>
+        </class>""")
+        javatools.cobertura(self.ctxt, file_=filename)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual('report', type)
+        self.assertEqual('coverage', category)
+        self.assertEqual(1, len(xml.children))
+
+        elem = xml.children[0]
+        self.assertEqual('coverage', elem.name)
+        self.assertEqual('src/test/TestClass.java', elem.attr['file'])
+        self.assertEqual('test.TestClass', elem.attr['name'])
+        self.assertEqual(4, elem.attr['lines'])
+        self.assertEqual(50, elem.attr['percentage'])
+
+    def test_skipped_lines(self):
+        filename = self._create_file('coverage.xml', content=self.xml_template % """
+        <class name="test.TestClass" filename="test/TestClass.java">
+          <lines>
+            <line number="1" hits="0" branch="false"/>
+            <line number="3" hits="1" branch="false"/>
+          </lines>
+        </class>""")
+        javatools.cobertura(self.ctxt, file_=filename)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual('report', type)
+        self.assertEqual('coverage', category)
+        self.assertEqual(1, len(xml.children))
+
+        elem = xml.children[0]
+        self.assertEqual('coverage', elem.name)
+        self.assertEqual('src/test/TestClass.java', elem.attr['file'])
+        self.assertEqual('test.TestClass', elem.attr['name'])
+        self.assertEqual(2, elem.attr['lines'])
+        self.assertEqual(50, elem.attr['percentage'])
+
+        line_hits = elem.children[0]
+        self.assertEqual('line_hits', line_hits.name)
+        self.assertEqual('0 - 1', line_hits.children[0])
+
+    def test_interface(self):
+        filename = self._create_file('coverage.xml', content=self.xml_template % """
+        <class name="test.TestInterface" filename="test/TestInterface.java">
+          <lines>
+          </lines>
+        </class>""")
+        javatools.cobertura(self.ctxt, file_=filename)
+        type, category, generator, xml = self.ctxt.output.pop()
+        self.assertEqual('report', type)
+        self.assertEqual('coverage', category)
+        self.assertEqual(1, len(xml.children))
+
+        elem = xml.children[0]
+        self.assertEqual('coverage', elem.name)
+        self.assertEqual('src/test/TestInterface.java', elem.attr['file'])
+        self.assertEqual('test.TestInterface', elem.attr['name'])
+        self.assertEqual(0, elem.attr['lines'])
+        self.assertEqual(0, elem.attr['percentage'])
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(CoberturaTestCase, 'test'))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
--- a/setup.py
+++ b/setup.py
@@ -50,6 +50,7 @@
             NS + 'c#make = bitten.build.ctools:make',
             NS + 'java#ant = bitten.build.javatools:ant',
             NS + 'java#junit = bitten.build.javatools:junit',
+            NS + 'java#cobertura = bitten.build.javatools:cobertura',
             NS + 'python#distutils = bitten.build.pythontools:distutils',
             NS + 'python#exec = bitten.build.pythontools:exec_',
             NS + 'python#pylint = bitten.build.pythontools:pylint',
Copyright (C) 2012-2017 Edgewall Software