# HG changeset patch # User osimons # Date 1287189402 0 # Node ID 3ee5abad55ea11f87e56dc4bab30e7a3756240d1 # Parent 84aafcda9c9d5a8da1a0084bf795c6b879f3405c 0.6dev: Merged [933] from trunk. 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):