changeset 855:d4b6e85bb5c8

Reworked Coverage Annotator to be SQL based. It now looks for the most recent annotation information in the range of revisions from currently browsed and back to previous change version, displaying the most recent if any is found (and warnings if not). Fixes #466 (and more).
author osimons
date Sat, 16 Oct 2010 00:35:25 +0000
parents bd46b7410299
children fc9955ec8c67
files bitten/report/coverage.py
diffstat 1 files changed, 54 insertions(+), 32 deletions(-) [+]
line wrap: on
line diff
--- a/bitten/report/coverage.py
+++ b/bitten/report/coverage.py
@@ -8,11 +8,13 @@
 # you should have received as part of this distribution. The terms
 # are also available at http://bitten.edgewall.org/wiki/License.
 
+from genshi.builder import tag
 from trac.core import *
 from trac.mimeview.api import IHTMLPreviewAnnotator
 from trac.resource import Resource
+from trac.util.datefmt import to_timestamp
 from trac.web.api import IRequestFilter
-from trac.web.chrome import add_stylesheet, add_ctxtnav
+from trac.web.chrome import add_stylesheet, add_ctxtnav, add_warning
 from bitten.api import IReportChartGenerator, IReportSummarizer
 from bitten.model import BuildConfig, Build, Report
 
@@ -139,10 +141,13 @@
     >>> from genshi.builder import tag
     >>> from trac.test import Mock, MockPerm
     >>> from trac.mimeview import Context
+    >>> from trac.util.datefmt import to_datetime, utc
     >>> 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()
+    >>> repos = Mock(get_changeset=lambda x: Mock(date=to_datetime(12345, utc)))
+    >>> env.get_repository = lambda: repos
 
     >>> BuildConfig(env, name='trunk', path='trunk').insert()
     >>> Build(env, rev=123, config='trunk', rev_time=12345, platform=1).insert()
@@ -151,7 +156,8 @@
     >>> rpt.insert()
 
     >>> ann = TestCoverageAnnotator(env)
-    >>> req = Mock(href=Href('/'), perm=MockPerm(), chrome={}, args={})
+    >>> req = Mock(href=Href('/'), perm=MockPerm(),
+    ...                 chrome={'warnings': []}, args={})
 
     Version in the branch should not match:
     >>> context = Context.from_request(req, 'source', '/branches/blah/foo.py', 123)
@@ -190,11 +196,13 @@
         if resource and isinstance(resource, Resource) \
                     and resource.realm=='source' and data.get('file') \
                     and not req.args.get('annotate', '') == 'coverage':
-            add_ctxtnav(req, 'Coverage',
+            add_ctxtnav(req, tag.a('Coverage',
                     title='Annotate file with test coverage '
                           'data (if available)',
                     href=req.href.browser(resource.id, 
-                        annotate='coverage', rev=resource.version))
+                        annotate='coverage', rev=req.args.get('rev'),
+                        created=data.get('rev')),
+                    rel='nofollow'))
         return template, data, content_type
 
     # IHTMLPreviewAnnotator methods
@@ -209,35 +217,49 @@
 
         # attempt to use the version passed in with the request,
         # otherwise fall back to the latest version of this file.
-        try:
-            version = context.req.args['rev']
-        except KeyError:
-            version = resource.version
-
-        builds = Build.select(self.env, rev=version)
-
-        self.log.debug("Looking for coverage report for %s@%s [%s]..." % (
-                        resource.id, str(resource.version), version))
+        version = context.req.args.get('rev', resource.version)
+        # get the last change revision for the file so that we can
+        # pick coverage data as latest(version >= file_revision)
+        created = context.req.args.get('created', resource.version)
 
-        reports = []
-        for build in builds:
-            config = BuildConfig.fetch(self.env, build.config)
-            # Normalize paths and check (Trac version inconsistencies)
-            if not resource.id.lstrip('/').startswith(config.path.lstrip('/')):
-                continue
-            reports = Report.select(self.env, build=build.id,
-                                    category='coverage')
-            path_in_config = resource.id[len(config.path)+1:].lstrip('/')
-            for report in reports:
-                for item in report.items:
-                    if item.get('file') == path_in_config:
-                        coverage = item.get('line_hits', '').split()
-                        if coverage:
-                            # Return first result with line data
-                            self.log.debug(
-                                "Coverage annotate for %s@%s: %s" % \
-                                (resource.id, resource.version, coverage))
-                            return coverage
+        repos = self.env.get_repository()        
+        version_time = to_timestamp(repos.get_changeset(version).date)
+        if version != created:
+            created_time = to_timestamp(repos.get_changeset(created).date)
+        else:
+            created_time = version_time
+
+        self.log.debug("Looking for coverage report for %s@%s [%s:%s]..." % (
+                        resource.id, str(resource.version), created, version))
+
+        db = self.env.get_db_cnx()
+        cursor = db.cursor()
+        cursor.execute("""
+                SELECT b.id, b.rev, i2.value
+                FROM bitten_config AS c
+                    INNER JOIN bitten_build AS b ON c.name=b.config
+                    INNER JOIN bitten_report AS r ON b.id=r.build
+                    INNER JOIN bitten_report_item AS i1 ON r.id=i1.report
+                    INNER JOIN bitten_report_item AS i2 ON (i1.item=i2.item
+                                                    AND i1.report=i2.report)
+                WHERE i2.name='line_hits'
+                    AND b.rev_time>=%s
+                    AND b.rev_time<=%s
+                    AND i1.name='file'
+                    AND """ + db.concat('c.path', "'/'", 'i1.value') + """=%s
+                ORDER BY b.rev_time DESC LIMIT 1""" ,
+            (created_time, version_time, resource.id.lstrip('/')))
+
+        row = cursor.fetchone()
+        if row:
+            build_id, build_rev, line_hits = row
+            coverage = line_hits.split()
+            self.log.debug("Coverage annotate for %s@%s using build %d: %s",
+                            resource.id, build_rev, build_id, coverage)
+            return coverage
+        add_warning(context.req, "No coverage annotation found for "
+                                 "/%s for revision range [%s:%s]." % (
+                                 resource.id.lstrip('/'), version, created))
         return []
 
     def annotate_row(self, context, row, lineno, line, data):
Copyright (C) 2012-2017 Edgewall Software