changeset 484:3386b22da07b

code coverage annotator for source browser
author mgood
date Thu, 20 Mar 2008 05:59:30 +0000
parents 9514fad39d60
children 28123a0c14db
files bitten/htdocs/bitten_coverage.css bitten/report/coverage.py bitten/report/tests/coverage.py
diffstat 3 files changed, 108 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/bitten/htdocs/bitten_coverage.css
@@ -0,0 +1,4 @@
+/* Code coverage file annotations */
+table.code th.coverage { width: 4em; }
+table.code th.covered { background-color: #0f0; }
+table.code th.uncovered { background-color: #f00; }
--- a/bitten/report/coverage.py
+++ b/bitten/report/coverage.py
@@ -9,9 +9,11 @@
 # are also available at http://bitten.edgewall.org/wiki/License.
 
 from trac.core import *
-from trac.web.chrome import Chrome
+from trac.mimeview.api import IHTMLPreviewAnnotator
+from trac.web.chrome import Chrome, add_stylesheet
 from trac.web.clearsilver import HDFWrapper
 from bitten.api import IReportChartGenerator, IReportSummarizer
+from bitten.model import BuildConfig, Build, Report
 
 __docformat__ = 'restructuredtext en'
 
@@ -109,7 +111,7 @@
             if loc:
                 d = {'name': unit, 'loc': loc, 'cov': int(cov)}
                 if file:
-                    d['href'] = req.href.browser(config.path, file)
+                    d['href'] = req.href.browser(config.path, file, rev=build.rev, annotate='coverage')
                 data.append(d)
                 total_loc += loc
                 total_cov += loc * cov
@@ -122,3 +124,90 @@
         hdf['data'] = data
         hdf['totals'] = {'loc': total_loc, 'cov': int(coverage)}
         return hdf.render('bitten_summary_coverage.cs')
+
+
+# Coverage annotation requires the new interface from 0.11
+if hasattr(IHTMLPreviewAnnotator, 'get_annotation_data'):
+    class TestCoverageAnnotator(Component):
+        """
+        >>> from genshi.builder import tag
+        >>> from trac.test import Mock, MockPerm
+        >>> from trac.mimeview import Context
+        >>> from trac.web.href import Href
+        >>> from bitten.model import BuildConfig, Build, Report
+        >>> from bitten.report.tests.coverage import env_stub_with_tables
+        >>> env = env_stub_with_tables()
+
+        >>> BuildConfig(env, name='trunk', path='trunk').insert()
+        >>> Build(env, rev=123, config='trunk', rev_time=12345, platform=1).insert()
+        >>> rpt = Report(env, build=1, step='test', category='coverage')
+        >>> rpt.items.append({'file': 'foo.py', 'line_hits': '5 - 0'})
+        >>> rpt.insert()
+
+        >>> ann = TestCoverageAnnotator(env)
+        >>> req = Mock(href=Href('/'), perm=MockPerm(), chrome={})
+
+        Version in the branch should not match:
+        >>> context = Context.from_request(req, 'source', 'branches/blah/foo.py', 123)
+        >>> ann.get_annotation_data(context)
+        []
+
+        Version in the trunk should match:
+        >>> context = Context.from_request(req, 'source', 'trunk/foo.py', 123)
+        >>> data = ann.get_annotation_data(context)
+        >>> print data
+        [u'5', u'-', u'0']
+
+        >>> def annotate_row(lineno, line):
+        ...     row = tag.tr()
+        ...     ann.annotate_row(context, row, lineno, line, data)
+        ...     return row.generate().render('html')
+
+        >>> annotate_row(1, 'x = 1')
+        '<tr><th class="covered">5</th></tr>'
+        >>> annotate_row(2, '')
+        '<tr><th></th></tr>'
+        >>> annotate_row(3, 'y = x')
+        '<tr><th class="uncovered">0</th></tr>'
+        """
+        implements(IHTMLPreviewAnnotator)
+
+        # IHTMLPreviewAnnotator methods
+
+        def get_annotation_type(self):
+            return 'coverage', 'Cov', 'Code coverage'
+
+        def get_annotation_data(self, context):
+            add_stylesheet(context.req, 'bitten/bitten_coverage.css')
+
+            resource = context.resource
+            builds = Build.select(self.env, rev=resource.version)
+            reports = []
+            for build in builds:
+                config = BuildConfig.fetch(self.env, build.config)
+                if not resource.id.startswith(config.path):
+                    continue
+                reports = Report.select(self.env, build=build.id,
+                                        category='coverage')
+                path_in_config = resource.id[len(config.path):].lstrip('/')
+                for report in reports:
+                    for item in report.items:
+                        if item.get('file') == path_in_config:
+                            # TODO should aggregate coverage across builds
+                            return item.get('line_hits', '').split()
+            return []
+
+        def annotate_row(self, context, row, lineno, line, data):
+            self.log.debug('%s', data)
+            from genshi.builder import tag
+            lineno -= 1 # 0-based index for data
+            if lineno >= len(data):
+                row.append(tag.th())
+                return
+            row_data = data[lineno]
+            if row_data == '-':
+                row.append(tag.th())
+            elif row_data == '0':
+                row.append(tag.th(row_data, class_='uncovered'))
+            else:
+                row.append(tag.th(row_data, class_='covered'))
--- a/bitten/report/tests/coverage.py
+++ b/bitten/report/tests/coverage.py
@@ -8,27 +8,31 @@
 # you should have received as part of this distribution. The terms
 # are also available at http://bitten.edgewall.org/wiki/License.
 
+import doctest
 import unittest
 
 from trac.db import DatabaseManager
 from trac.test import EnvironmentStub, Mock
 from trac.web.clearsilver import HDFWrapper
 from bitten.model import *
+from bitten.report import coverage
 from bitten.report.coverage import TestCoverageChartGenerator
 
+def env_stub_with_tables():
+    env = EnvironmentStub()
+    db = env.get_db_cnx()
+    cursor = db.cursor()
+    connector, _ = DatabaseManager(env)._get_connector()
+    for table in schema:
+        for stmt in connector.to_sql(table):
+            cursor.execute(stmt)
+    return env
 
 class TestCoverageChartGeneratorTestCase(unittest.TestCase):
 
     def setUp(self):
-        self.env = EnvironmentStub()
+        self.env = env_stub_with_tables()
         self.env.path = ''
-        db = self.env.get_db_cnx()
-        cursor = db.cursor()
-
-        connector, _ = DatabaseManager(self.env)._get_connector()
-        for table in schema:
-            for stmt in connector.to_sql(table):
-                cursor.execute(stmt)
 
     def test_supported_categories(self):
         generator = TestCoverageChartGenerator(self.env)
@@ -100,6 +104,7 @@
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(TestCoverageChartGeneratorTestCase))
+    suite.addTest(doctest.DocTestSuite(coverage))
     return suite
 
 if __name__ == '__main__':
Copyright (C) 2012-2017 Edgewall Software