# HG changeset patch # User osimons # Date 1287189325 0 # Node ID d4b6e85bb5c84e41679fb335bcd01e9cb0044052 # Parent bd46b741029931a93b35b8605c49dd88dd7a4367 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). diff --git a/bitten/report/coverage.py b/bitten/report/coverage.py --- 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):