# HG changeset patch # User cmlenz # Date 1186478250 0 # Node ID 5786700df0c765cfccf0e2e0fc9fe6150dcba25f # Parent 933105ab516bdc17e53e9b937b198b88e906fc2d Moved/restructured the modules implementing report chart generators and report summarizers. diff --git a/bitten/report/__init__.py b/bitten/report/__init__.py new file mode 100644 --- /dev/null +++ b/bitten/report/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# 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.edgewall.org/wiki/License. diff --git a/bitten/report/coverage.py b/bitten/report/coverage.py new file mode 100644 --- /dev/null +++ b/bitten/report/coverage.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2007 Christopher Lenz +# Copyright (C) 2007 Edgewall Software +# 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.edgewall.org/wiki/License. + +from trac.core import * +from trac.web.chrome import Chrome +from trac.web.clearsilver import HDFWrapper +from bitten.trac_ext.api import IReportChartGenerator, IReportSummarizer + + +class TestCoverageChartGenerator(Component): + implements(IReportChartGenerator) + + # IReportChartGenerator methods + + def get_supported_categories(self): + return ['coverage'] + + def generate_chart_data(self, req, config, category): + assert category == 'coverage' + + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute(""" +SELECT build.rev, SUM(%s) AS loc, SUM(%s * %s / 100) AS cov +FROM bitten_build AS build + LEFT OUTER JOIN bitten_report AS report ON (report.build=build.id) + LEFT OUTER JOIN bitten_report_item AS item_lines + ON (item_lines.report=report.id AND item_lines.name='lines') + LEFT OUTER JOIN bitten_report_item AS item_percentage + ON (item_percentage.report=report.id AND item_percentage.name='percentage' AND + item_percentage.item=item_lines.item) +WHERE build.config=%%s AND report.category='coverage' +GROUP BY build.rev_time, build.rev, build.platform +ORDER BY build.rev_time""" % (db.cast('item_lines.value', 'int'), + db.cast('item_lines.value', 'int'), + db.cast('item_percentage.value', 'int')), + (config.name,)) + + prev_rev = None + coverage = [] + for rev, loc, cov in cursor: + if rev != prev_rev: + coverage.append([rev, 0, 0]) + if loc > coverage[-1][1]: + coverage[-1][1] = int(loc) + if cov > coverage[-1][2]: + coverage[-1][2] = int(cov) + prev_rev = rev + + req.hdf['chart.title'] = 'Test Coverage' + req.hdf['chart.data'] = [ + [''] + ['[%s]' % item[0] for item in coverage], + ['Lines of code'] + [item[1] for item in coverage], + ['Coverage'] + [int(item[2]) for item in coverage] + ] + + return 'bitten_chart_coverage.cs' + + +class TestCoverageSummarizer(Component): + implements(IReportSummarizer) + + # IReportSummarizer methods + + def get_supported_categories(self): + return ['coverage'] + + def render_summary(self, req, config, build, step, category): + assert category == 'coverage' + + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute(""" +SELECT item_name.value AS unit, item_file.value AS file, + max(item_lines.value) AS loc, max(item_percentage.value) AS cov +FROM bitten_report AS report + LEFT OUTER JOIN bitten_report_item AS item_name + ON (item_name.report=report.id AND item_name.name='name') + LEFT OUTER JOIN bitten_report_item AS item_file + ON (item_file.report=report.id AND item_file.item=item_name.item AND + item_file.name='file') + LEFT OUTER JOIN bitten_report_item AS item_lines + ON (item_lines.report=report.id AND item_lines.item=item_name.item AND + item_lines.name='lines') + LEFT OUTER JOIN bitten_report_item AS item_percentage + ON (item_percentage.report=report.id AND + item_percentage.item=item_name.item AND + item_percentage.name='percentage') +WHERE category='coverage' AND build=%s AND step=%s +GROUP BY file, item_name.value +ORDER BY item_name.value""", (build.id, step.name)) + + data = [] + total_loc, total_cov = 0, 0 + for unit, file, loc, cov in cursor: + try: + loc, cov = int(loc), float(cov) + except TypeError: + continue # no rows + if loc: + d = {'name': unit, 'loc': loc, 'cov': int(cov)} + if file: + d['href'] = req.href.browser(config.path, file) + data.append(d) + total_loc += loc + total_cov += loc * cov + + coverage = 0 + if total_loc != 0: + coverage = total_cov // total_loc + + hdf = HDFWrapper(loadpaths=Chrome(self.env).get_all_templates_dirs()) + hdf['data'] = data + hdf['totals'] = {'loc': total_loc, 'cov': int(coverage)} + return hdf.render('bitten_summary_coverage.cs') diff --git a/bitten/report/testing.py b/bitten/report/testing.py new file mode 100644 --- /dev/null +++ b/bitten/report/testing.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2007 Christopher Lenz +# Copyright (C) 2007 Edgewall Software +# 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.edgewall.org/wiki/License. + +from trac.core import * +from trac.web.chrome import Chrome +from trac.web.clearsilver import HDFWrapper +from bitten.trac_ext.api import IReportChartGenerator, IReportSummarizer + + +class TestResultsChartGenerator(Component): + implements(IReportChartGenerator) + + # IReportChartGenerator methods + + def get_supported_categories(self): + return ['test'] + + def generate_chart_data(self, req, config, category): + assert category == 'test' + + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute(""" +SELECT build.rev, build.platform, item_status.value AS status, COUNT(*) AS num +FROM bitten_build AS build + LEFT OUTER JOIN bitten_report AS report ON (report.build=build.id) + LEFT OUTER JOIN bitten_report_item AS item_status + ON (item_status.report=report.id AND item_status.name='status') +WHERE build.config=%s AND report.category='test' +GROUP BY build.rev_time, build.rev, build.platform, item_status.value +ORDER BY build.rev_time, build.platform""", (config.name,)) + + prev_rev = None + prev_platform, platform_total = None, 0 + tests = [] + for rev, platform, status, num in cursor: + if rev != prev_rev: + tests.append([rev, 0, 0]) + prev_rev = rev + platform_total = 0 + if platform != prev_platform: + prev_platform = platform + platform_total = 0 + + platform_total += num + tests[-1][1] = max(platform_total, tests[-1][1]) + if status != 'success': + tests[-1][2] = max(num, tests[-1][2]) + + req.hdf['chart.title'] = 'Unit Tests' + req.hdf['chart.data'] = [ + [''] + ['[%s]' % item[0] for item in tests], + ['Total'] + [item[1] for item in tests], + ['Failures'] + [item[2] for item in tests] + ] + + return 'bitten_chart_tests.cs' + + +class TestResultsSummarizer(Component): + implements(IReportSummarizer) + + # IReportSummarizer methods + + def get_supported_categories(self): + return ['test'] + + def render_summary(self, req, config, build, step, category): + assert category == 'test' + + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute(""" +SELECT item_fixture.value AS fixture, item_file.value AS file, + COUNT(item_success.value) AS num_success, + COUNT(item_failure.value) AS num_failure, + COUNT(item_error.value) AS num_error +FROM bitten_report AS report + LEFT OUTER JOIN bitten_report_item AS item_fixture + ON (item_fixture.report=report.id AND item_fixture.name='fixture') + LEFT OUTER JOIN bitten_report_item AS item_file + ON (item_file.report=report.id AND item_file.item=item_fixture.item AND + item_file.name='file') + LEFT OUTER JOIN bitten_report_item AS item_success + ON (item_success.report=report.id AND item_success.item=item_fixture.item AND + item_success.name='status' AND item_success.value='success') + LEFT OUTER JOIN bitten_report_item AS item_failure + ON (item_failure.report=report.id AND item_failure.item=item_fixture.item AND + item_failure.name='status' AND item_failure.value='failure') + LEFT OUTER JOIN bitten_report_item AS item_error + ON (item_error.report=report.id AND item_error.item=item_fixture.item AND + item_error.name='status' AND item_error.value='error') +WHERE category='test' AND build=%s AND step=%s +GROUP BY file, fixture +ORDER BY fixture""", (build.id, step.name)) + + data = [] + total_success, total_failure, total_error = 0, 0, 0 + for fixture, file, num_success, num_failure, num_error in cursor: + data.append({'name': fixture, 'num_success': num_success, + 'num_error': num_error, 'num_failure': num_failure}) + total_success += num_success + total_failure += num_failure + total_error += num_error + if file: + data[-1]['href'] = req.href.browser(config.path, file) + + hdf = HDFWrapper(loadpaths=Chrome(self.env).get_all_templates_dirs()) + hdf['data'] = data + hdf['totals'] = {'success': total_success, 'failure': total_failure, + 'error': total_error} + return hdf.render('bitten_summary_tests.cs') diff --git a/bitten/report/tests/__init__.py b/bitten/report/tests/__init__.py new file mode 100644 --- /dev/null +++ b/bitten/report/tests/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2007 Christopher Lenz +# Copyright (C) 2007 Edgewall Software +# 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.edgewall.org/wiki/License. + +import unittest + +from bitten.report.tests import coverage, testing + +def suite(): + suite = unittest.TestSuite() + suite.addTest(coverage.suite()) + suite.addTest(testing.suite()) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/bitten/report/tests/coverage.py b/bitten/report/tests/coverage.py new file mode 100644 --- /dev/null +++ b/bitten/report/tests/coverage.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2007 Christopher Lenz +# Copyright (C) 2007 Edgewall Software +# 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.edgewall.org/wiki/License. + +import unittest + +from trac.test import EnvironmentStub, Mock +from trac.web.clearsilver import HDFWrapper +from bitten.model import * +from bitten.report.coverage import TestCoverageChartGenerator +from bitten.trac_ext.compat import schema_to_sql + + +class TestCoverageChartGeneratorTestCase(unittest.TestCase): + + def setUp(self): + self.env = EnvironmentStub() + self.env.path = '' + db = self.env.get_db_cnx() + cursor = db.cursor() + for table in schema: + for stmt in schema_to_sql(self.env, db, table): + cursor.execute(stmt) + + def test_supported_categories(self): + generator = TestCoverageChartGenerator(self.env) + self.assertEqual(['coverage'], generator.get_supported_categories()) + + def test_no_reports(self): + req = Mock(hdf=HDFWrapper()) + config = Mock(name='trunk') + generator = TestCoverageChartGenerator(self.env) + template = generator.generate_chart_data(req, config, 'coverage') + self.assertEqual('bitten_chart_coverage.cs', template) + self.assertEqual('Test Coverage', req.hdf['chart.title']) + self.assertEqual('', req.hdf['chart.data.0.0']) + self.assertEqual('Lines of code', req.hdf['chart.data.1.0']) + self.assertEqual('Coverage', req.hdf['chart.data.2.0']) + + def test_single_platform(self): + config = Mock(name='trunk') + build = Build(self.env, config='trunk', platform=1, rev=123, + rev_time=42) + build.insert() + report = Report(self.env, build=build.id, step='foo', + category='coverage') + report.items += [{'lines': '12', 'percentage': '25'}] + report.insert() + + req = Mock(hdf=HDFWrapper()) + generator = TestCoverageChartGenerator(self.env) + template = generator.generate_chart_data(req, config, 'coverage') + self.assertEqual('bitten_chart_coverage.cs', template) + self.assertEqual('Test Coverage', req.hdf['chart.title']) + self.assertEqual('', req.hdf['chart.data.0.0']) + self.assertEqual('[123]', req.hdf['chart.data.0.1']) + self.assertEqual('Lines of code', req.hdf['chart.data.1.0']) + self.assertEqual('12', req.hdf['chart.data.1.1']) + self.assertEqual('Coverage', req.hdf['chart.data.2.0']) + self.assertEqual('3', req.hdf['chart.data.2.1']) + + def test_multi_platform(self): + config = Mock(name='trunk') + build = Build(self.env, config='trunk', platform=1, rev=123, + rev_time=42) + build.insert() + report = Report(self.env, build=build.id, step='foo', + category='coverage') + report.items += [{'lines': '12', 'percentage': '25'}] + report.insert() + build = Build(self.env, config='trunk', platform=2, rev=123, + rev_time=42) + build.insert() + report = Report(self.env, build=build.id, step='foo', + category='coverage') + report.items += [{'lines': '12', 'percentage': '50'}] + report.insert() + + req = Mock(hdf=HDFWrapper()) + generator = TestCoverageChartGenerator(self.env) + template = generator.generate_chart_data(req, config, 'coverage') + self.assertEqual('bitten_chart_coverage.cs', template) + self.assertEqual('Test Coverage', req.hdf['chart.title']) + self.assertEqual('', req.hdf['chart.data.0.0']) + self.assertEqual('[123]', req.hdf['chart.data.0.1']) + self.assertEqual('Lines of code', req.hdf['chart.data.1.0']) + self.assertEqual('12', req.hdf['chart.data.1.1']) + self.assertEqual('Coverage', req.hdf['chart.data.2.0']) + self.assertEqual('6', req.hdf['chart.data.2.1']) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestCoverageChartGeneratorTestCase)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/bitten/report/tests/testing.py b/bitten/report/tests/testing.py new file mode 100644 --- /dev/null +++ b/bitten/report/tests/testing.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2007 Christopher Lenz +# Copyright (C) 2007 Edgewall Software +# 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.edgewall.org/wiki/License. + +import unittest + +from trac.test import EnvironmentStub, Mock +from trac.web.clearsilver import HDFWrapper +from bitten.model import * +from bitten.report.testing import TestResultsChartGenerator +from bitten.trac_ext.compat import schema_to_sql + + +class TestResultsChartGeneratorTestCase(unittest.TestCase): + + def setUp(self): + self.env = EnvironmentStub() + self.env.path = '' + db = self.env.get_db_cnx() + cursor = db.cursor() + for table in schema: + for stmt in schema_to_sql(self.env, db, table): + cursor.execute(stmt) + + def test_supported_categories(self): + generator = TestResultsChartGenerator(self.env) + self.assertEqual(['test'], generator.get_supported_categories()) + + def test_no_reports(self): + req = Mock(hdf=HDFWrapper()) + config = Mock(name='trunk') + generator = TestResultsChartGenerator(self.env) + template = generator.generate_chart_data(req, config, 'test') + self.assertEqual('bitten_chart_tests.cs', template) + self.assertEqual('Unit Tests', req.hdf['chart.title']) + self.assertEqual('', req.hdf['chart.data.0.0']) + self.assertEqual('Total', req.hdf['chart.data.1.0']) + self.assertEqual('Failures', req.hdf['chart.data.2.0']) + + def test_single_platform(self): + config = Mock(name='trunk') + build = Build(self.env, config='trunk', platform=1, rev=123, + rev_time=42) + build.insert() + report = Report(self.env, build=build.id, step='foo', category='test') + report.items += [{'status': 'success'}, {'status': 'failure'}, + {'status': 'success'}] + report.insert() + + req = Mock(hdf=HDFWrapper()) + generator = TestResultsChartGenerator(self.env) + template = generator.generate_chart_data(req, config, 'test') + self.assertEqual('bitten_chart_tests.cs', template) + self.assertEqual('Unit Tests', req.hdf['chart.title']) + self.assertEqual('', req.hdf['chart.data.0.0']) + self.assertEqual('[123]', req.hdf['chart.data.0.1']) + self.assertEqual('Total', req.hdf['chart.data.1.0']) + self.assertEqual('3', req.hdf['chart.data.1.1']) + self.assertEqual('Failures', req.hdf['chart.data.2.0']) + self.assertEqual('1', req.hdf['chart.data.2.1']) + + def test_multi_platform(self): + config = Mock(name='trunk') + + build = Build(self.env, config='trunk', platform=1, rev=123, + rev_time=42) + build.insert() + report = Report(self.env, build=build.id, step='foo', category='test') + report.items += [{'status': 'success'}, {'status': 'failure'}, + {'status': 'success'}] + report.insert() + + build = Build(self.env, config='trunk', platform=2, rev=123, + rev_time=42) + build.insert() + report = Report(self.env, build=build.id, step='foo', category='test') + report.items += [{'status': 'success'}, {'status': 'failure'}, + {'status': 'failure'}] + report.insert() + + req = Mock(hdf=HDFWrapper()) + generator = TestResultsChartGenerator(self.env) + template = generator.generate_chart_data(req, config, 'test') + self.assertEqual('bitten_chart_tests.cs', template) + self.assertEqual('Unit Tests', req.hdf['chart.title']) + self.assertEqual('', req.hdf['chart.data.0.0']) + self.assertEqual('[123]', req.hdf['chart.data.0.1']) + self.assertEqual('Total', req.hdf['chart.data.1.0']) + self.assertEqual('3', req.hdf['chart.data.1.1']) + self.assertEqual('Failures', req.hdf['chart.data.2.0']) + self.assertEqual('2', req.hdf['chart.data.2.1']) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(TestResultsChartGeneratorTestCase)) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/bitten/tests/__init__.py b/bitten/tests/__init__.py --- a/bitten/tests/__init__.py +++ b/bitten/tests/__init__.py @@ -12,8 +12,9 @@ from bitten.tests import master, model, recipe, queue, slave from bitten.build import tests as build +from bitten.report import tests as report +from bitten.trac_ext import tests as trac_ext from bitten.util import tests as util -from bitten.trac_ext import tests as trac_ext def suite(): suite = unittest.TestSuite() @@ -23,6 +24,7 @@ suite.addTest(queue.suite()) suite.addTest(slave.suite()) suite.addTest(build.suite()) + suite.addTest(report.suite()) suite.addTest(trac_ext.suite()) suite.addTest(util.suite()) return suite diff --git a/bitten/trac_ext/charts.py b/bitten/trac_ext/charts.py deleted file mode 100644 --- a/bitten/trac_ext/charts.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2007 Edgewall Software -# 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.edgewall.org/wiki/License. - -import re - -from trac.core import * -from trac.web import IRequestHandler -from bitten.model import BuildConfig -from bitten.trac_ext.api import IReportChartGenerator - - -class ReportChartController(Component): - implements(IRequestHandler) - - generators = ExtensionPoint(IReportChartGenerator) - - # IRequestHandler methods - - def match_request(self, req): - match = re.match(r'/build/([\w.-]+)/chart/(\w+)', req.path_info) - if match: - req.args['config'] = match.group(1) - req.args['category'] = match.group(2) - return True - - def process_request(self, req): - category = req.args.get('category') - config = BuildConfig.fetch(self.env, name=req.args.get('config')) - - for generator in self.generators: - if category in generator.get_supported_categories(): - template = generator.generate_chart_data(req, config, - category) - break - else: - raise TracError('Unknown report category "%s"' % category) - - return template, 'text/xml' - - -class TestResultsChartGenerator(Component): - implements(IReportChartGenerator) - - # IReportChartGenerator methods - - def get_supported_categories(self): - return ['test'] - - def generate_chart_data(self, req, config, category): - assert category == 'test' - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute(""" -SELECT build.rev, build.platform, item_status.value AS status, COUNT(*) AS num -FROM bitten_build AS build - LEFT OUTER JOIN bitten_report AS report ON (report.build=build.id) - LEFT OUTER JOIN bitten_report_item AS item_status - ON (item_status.report=report.id AND item_status.name='status') -WHERE build.config=%s AND report.category='test' -GROUP BY build.rev_time, build.rev, build.platform, item_status.value -ORDER BY build.rev_time, build.platform""", (config.name,)) - - prev_rev = None - prev_platform, platform_total = None, 0 - tests = [] - for rev, platform, status, num in cursor: - if rev != prev_rev: - tests.append([rev, 0, 0]) - prev_rev = rev - platform_total = 0 - if platform != prev_platform: - prev_platform = platform - platform_total = 0 - - platform_total += num - tests[-1][1] = max(platform_total, tests[-1][1]) - if status != 'success': - tests[-1][2] = max(num, tests[-1][2]) - - req.hdf['chart.title'] = 'Unit Tests' - req.hdf['chart.data'] = [ - [''] + ['[%s]' % item[0] for item in tests], - ['Total'] + [item[1] for item in tests], - ['Failures'] + [item[2] for item in tests] - ] - - return 'bitten_chart_tests.cs' - - -class TestCoverageChartGenerator(Component): - implements(IReportChartGenerator) - - # IReportChartGenerator methods - - def get_supported_categories(self): - return ['coverage'] - - def generate_chart_data(self, req, config, category): - assert category == 'coverage' - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute(""" -SELECT build.rev, SUM(%s) AS loc, SUM(%s * %s / 100) AS cov -FROM bitten_build AS build - LEFT OUTER JOIN bitten_report AS report ON (report.build=build.id) - LEFT OUTER JOIN bitten_report_item AS item_lines - ON (item_lines.report=report.id AND item_lines.name='lines') - LEFT OUTER JOIN bitten_report_item AS item_percentage - ON (item_percentage.report=report.id AND item_percentage.name='percentage' AND - item_percentage.item=item_lines.item) -WHERE build.config=%%s AND report.category='coverage' -GROUP BY build.rev_time, build.rev, build.platform -ORDER BY build.rev_time""" % (db.cast('item_lines.value', 'int'), - db.cast('item_lines.value', 'int'), - db.cast('item_percentage.value', 'int')), - (config.name,)) - - prev_rev = None - coverage = [] - for rev, loc, cov in cursor: - if rev != prev_rev: - coverage.append([rev, 0, 0]) - if loc > coverage[-1][1]: - coverage[-1][1] = int(loc) - if cov > coverage[-1][2]: - coverage[-1][2] = int(cov) - prev_rev = rev - - req.hdf['chart.title'] = 'Test Coverage' - req.hdf['chart.data'] = [ - [''] + ['[%s]' % item[0] for item in coverage], - ['Lines of code'] + [item[1] for item in coverage], - ['Coverage'] + [int(item[2]) for item in coverage] - ] - - return 'bitten_chart_coverage.cs' diff --git a/bitten/trac_ext/summarizers.py b/bitten/trac_ext/summarizers.py deleted file mode 100644 --- a/bitten/trac_ext/summarizers.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2007 Edgewall Software -# 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.edgewall.org/wiki/License. - -from trac.core import * -from trac.web.chrome import Chrome -from trac.web.clearsilver import HDFWrapper -from bitten.trac_ext.api import IReportSummarizer - - -class TestResultsSummarizer(Component): - implements(IReportSummarizer) - - def get_supported_categories(self): - return ['test'] - - def render_summary(self, req, config, build, step, category): - assert category == 'test' - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute(""" -SELECT item_fixture.value AS fixture, item_file.value AS file, - COUNT(item_success.value) AS num_success, - COUNT(item_failure.value) AS num_failure, - COUNT(item_error.value) AS num_error -FROM bitten_report AS report - LEFT OUTER JOIN bitten_report_item AS item_fixture - ON (item_fixture.report=report.id AND item_fixture.name='fixture') - LEFT OUTER JOIN bitten_report_item AS item_file - ON (item_file.report=report.id AND item_file.item=item_fixture.item AND - item_file.name='file') - LEFT OUTER JOIN bitten_report_item AS item_success - ON (item_success.report=report.id AND item_success.item=item_fixture.item AND - item_success.name='status' AND item_success.value='success') - LEFT OUTER JOIN bitten_report_item AS item_failure - ON (item_failure.report=report.id AND item_failure.item=item_fixture.item AND - item_failure.name='status' AND item_failure.value='failure') - LEFT OUTER JOIN bitten_report_item AS item_error - ON (item_error.report=report.id AND item_error.item=item_fixture.item AND - item_error.name='status' AND item_error.value='error') -WHERE category='test' AND build=%s AND step=%s -GROUP BY file, fixture -ORDER BY fixture""", (build.id, step.name)) - - data = [] - total_success, total_failure, total_error = 0, 0, 0 - for fixture, file, num_success, num_failure, num_error in cursor: - data.append({'name': fixture, 'num_success': num_success, - 'num_error': num_error, 'num_failure': num_failure}) - total_success += num_success - total_failure += num_failure - total_error += num_error - if file: - data[-1]['href'] = req.href.browser(config.path, file) - - hdf = HDFWrapper(loadpaths=Chrome(self.env).get_all_templates_dirs()) - hdf['data'] = data - hdf['totals'] = {'success': total_success, 'failure': total_failure, - 'error': total_error} - return hdf.render('bitten_summary_tests.cs') - - -class TestCoverageSummarizer(Component): - implements(IReportSummarizer) - - def get_supported_categories(self): - return ['coverage'] - - def render_summary(self, req, config, build, step, category): - assert category == 'coverage' - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute(""" -SELECT item_name.value AS unit, item_file.value AS file, - max(item_lines.value) AS loc, max(item_percentage.value) AS cov -FROM bitten_report AS report - LEFT OUTER JOIN bitten_report_item AS item_name - ON (item_name.report=report.id AND item_name.name='name') - LEFT OUTER JOIN bitten_report_item AS item_file - ON (item_file.report=report.id AND item_file.item=item_name.item AND - item_file.name='file') - LEFT OUTER JOIN bitten_report_item AS item_lines - ON (item_lines.report=report.id AND item_lines.item=item_name.item AND - item_lines.name='lines') - LEFT OUTER JOIN bitten_report_item AS item_percentage - ON (item_percentage.report=report.id AND - item_percentage.item=item_name.item AND - item_percentage.name='percentage') -WHERE category='coverage' AND build=%s AND step=%s -GROUP BY file, item_name.value -ORDER BY item_name.value""", (build.id, step.name)) - - data = [] - total_loc, total_cov = 0, 0 - for unit, file, loc, cov in cursor: - try: - loc, cov = int(loc), float(cov) - except TypeError: - continue # no rows - if loc: - d = {'name': unit, 'loc': loc, 'cov': int(cov)} - if file: - d['href'] = req.href.browser(config.path, file) - data.append(d) - total_loc += loc - total_cov += loc * cov - - coverage = 0 - if total_loc != 0: - coverage = total_cov // total_loc - - hdf = HDFWrapper(loadpaths=Chrome(self.env).get_all_templates_dirs()) - hdf['data'] = data - hdf['totals'] = {'loc': total_loc, 'cov': int(coverage)} - return hdf.render('bitten_summary_coverage.cs') diff --git a/bitten/trac_ext/tests/__init__.py b/bitten/trac_ext/tests/__init__.py --- a/bitten/trac_ext/tests/__init__.py +++ b/bitten/trac_ext/tests/__init__.py @@ -10,11 +10,10 @@ import unittest -from bitten.trac_ext.tests import charts, web_ui +from bitten.trac_ext.tests import web_ui def suite(): suite = unittest.TestSuite() - suite.addTest(charts.suite()) suite.addTest(web_ui.suite()) return suite diff --git a/bitten/trac_ext/tests/charts.py b/bitten/trac_ext/tests/charts.py deleted file mode 100644 --- a/bitten/trac_ext/tests/charts.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2007 Edgewall Software -# 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.edgewall.org/wiki/License. - -import unittest - -from trac.test import EnvironmentStub, Mock -from trac.web.clearsilver import HDFWrapper -from bitten.model import * -from bitten.trac_ext.charts import * -from bitten.trac_ext.compat import schema_to_sql - - -class TestResultsChartGeneratorTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - db = self.env.get_db_cnx() - cursor = db.cursor() - for table in schema: - for stmt in schema_to_sql(self.env, db, table): - cursor.execute(stmt) - - def test_supported_categories(self): - generator = TestResultsChartGenerator(self.env) - self.assertEqual(['test'], generator.get_supported_categories()) - - def test_no_reports(self): - req = Mock(hdf=HDFWrapper()) - config = Mock(name='trunk') - generator = TestResultsChartGenerator(self.env) - template = generator.generate_chart_data(req, config, 'test') - self.assertEqual('bitten_chart_tests.cs', template) - self.assertEqual('Unit Tests', req.hdf['chart.title']) - self.assertEqual('', req.hdf['chart.data.0.0']) - self.assertEqual('Total', req.hdf['chart.data.1.0']) - self.assertEqual('Failures', req.hdf['chart.data.2.0']) - - def test_single_platform(self): - config = Mock(name='trunk') - build = Build(self.env, config='trunk', platform=1, rev=123, - rev_time=42) - build.insert() - report = Report(self.env, build=build.id, step='foo', category='test') - report.items += [{'status': 'success'}, {'status': 'failure'}, - {'status': 'success'}] - report.insert() - - req = Mock(hdf=HDFWrapper()) - generator = TestResultsChartGenerator(self.env) - template = generator.generate_chart_data(req, config, 'test') - self.assertEqual('bitten_chart_tests.cs', template) - self.assertEqual('Unit Tests', req.hdf['chart.title']) - self.assertEqual('', req.hdf['chart.data.0.0']) - self.assertEqual('[123]', req.hdf['chart.data.0.1']) - self.assertEqual('Total', req.hdf['chart.data.1.0']) - self.assertEqual('3', req.hdf['chart.data.1.1']) - self.assertEqual('Failures', req.hdf['chart.data.2.0']) - self.assertEqual('1', req.hdf['chart.data.2.1']) - - def test_multi_platform(self): - config = Mock(name='trunk') - - build = Build(self.env, config='trunk', platform=1, rev=123, - rev_time=42) - build.insert() - report = Report(self.env, build=build.id, step='foo', category='test') - report.items += [{'status': 'success'}, {'status': 'failure'}, - {'status': 'success'}] - report.insert() - - build = Build(self.env, config='trunk', platform=2, rev=123, - rev_time=42) - build.insert() - report = Report(self.env, build=build.id, step='foo', category='test') - report.items += [{'status': 'success'}, {'status': 'failure'}, - {'status': 'failure'}] - report.insert() - - req = Mock(hdf=HDFWrapper()) - generator = TestResultsChartGenerator(self.env) - template = generator.generate_chart_data(req, config, 'test') - self.assertEqual('bitten_chart_tests.cs', template) - self.assertEqual('Unit Tests', req.hdf['chart.title']) - self.assertEqual('', req.hdf['chart.data.0.0']) - self.assertEqual('[123]', req.hdf['chart.data.0.1']) - self.assertEqual('Total', req.hdf['chart.data.1.0']) - self.assertEqual('3', req.hdf['chart.data.1.1']) - self.assertEqual('Failures', req.hdf['chart.data.2.0']) - self.assertEqual('2', req.hdf['chart.data.2.1']) - - -class TestCoverageChartGeneratorTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - db = self.env.get_db_cnx() - cursor = db.cursor() - for table in schema: - for stmt in schema_to_sql(self.env, db, table): - cursor.execute(stmt) - - def test_supported_categories(self): - generator = TestCoverageChartGenerator(self.env) - self.assertEqual(['coverage'], generator.get_supported_categories()) - - def test_no_reports(self): - req = Mock(hdf=HDFWrapper()) - config = Mock(name='trunk') - generator = TestCoverageChartGenerator(self.env) - template = generator.generate_chart_data(req, config, 'coverage') - self.assertEqual('bitten_chart_coverage.cs', template) - self.assertEqual('Test Coverage', req.hdf['chart.title']) - self.assertEqual('', req.hdf['chart.data.0.0']) - self.assertEqual('Lines of code', req.hdf['chart.data.1.0']) - self.assertEqual('Coverage', req.hdf['chart.data.2.0']) - - def test_single_platform(self): - config = Mock(name='trunk') - build = Build(self.env, config='trunk', platform=1, rev=123, - rev_time=42) - build.insert() - report = Report(self.env, build=build.id, step='foo', - category='coverage') - report.items += [{'lines': '12', 'percentage': '25'}] - report.insert() - - req = Mock(hdf=HDFWrapper()) - generator = TestCoverageChartGenerator(self.env) - template = generator.generate_chart_data(req, config, 'coverage') - self.assertEqual('bitten_chart_coverage.cs', template) - self.assertEqual('Test Coverage', req.hdf['chart.title']) - self.assertEqual('', req.hdf['chart.data.0.0']) - self.assertEqual('[123]', req.hdf['chart.data.0.1']) - self.assertEqual('Lines of code', req.hdf['chart.data.1.0']) - self.assertEqual('12', req.hdf['chart.data.1.1']) - self.assertEqual('Coverage', req.hdf['chart.data.2.0']) - self.assertEqual('3', req.hdf['chart.data.2.1']) - - def test_multi_platform(self): - config = Mock(name='trunk') - build = Build(self.env, config='trunk', platform=1, rev=123, - rev_time=42) - build.insert() - report = Report(self.env, build=build.id, step='foo', - category='coverage') - report.items += [{'lines': '12', 'percentage': '25'}] - report.insert() - build = Build(self.env, config='trunk', platform=2, rev=123, - rev_time=42) - build.insert() - report = Report(self.env, build=build.id, step='foo', - category='coverage') - report.items += [{'lines': '12', 'percentage': '50'}] - report.insert() - - req = Mock(hdf=HDFWrapper()) - generator = TestCoverageChartGenerator(self.env) - template = generator.generate_chart_data(req, config, 'coverage') - self.assertEqual('bitten_chart_coverage.cs', template) - self.assertEqual('Test Coverage', req.hdf['chart.title']) - self.assertEqual('', req.hdf['chart.data.0.0']) - self.assertEqual('[123]', req.hdf['chart.data.0.1']) - self.assertEqual('Lines of code', req.hdf['chart.data.1.0']) - self.assertEqual('12', req.hdf['chart.data.1.1']) - self.assertEqual('Coverage', req.hdf['chart.data.2.0']) - self.assertEqual('6', req.hdf['chart.data.2.1']) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestResultsChartGeneratorTestCase)) - suite.addTest(unittest.makeSuite(TestCoverageChartGeneratorTestCase)) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/bitten/trac_ext/web_ui.py b/bitten/trac_ext/web_ui.py --- a/bitten/trac_ext/web_ui.py +++ b/bitten/trac_ext/web_ui.py @@ -35,8 +35,8 @@ BuildLog, Report from bitten.queue import collect_changes from bitten.recipe import Recipe, InvalidRecipeError -from bitten.trac_ext.api import ILogFormatter, IReportSummarizer -from bitten.trac_ext.charts import ReportChartController +from bitten.trac_ext.api import ILogFormatter, IReportChartGenerator, \ + IReportSummarizer from bitten.util import xmlio _status_label = {Build.PENDING: 'pending', @@ -796,6 +796,35 @@ return reports +class ReportChartController(Component): + implements(IRequestHandler) + + generators = ExtensionPoint(IReportChartGenerator) + + # IRequestHandler methods + + def match_request(self, req): + match = re.match(r'/build/([\w.-]+)/chart/(\w+)', req.path_info) + if match: + req.args['config'] = match.group(1) + req.args['category'] = match.group(2) + return True + + def process_request(self, req): + category = req.args.get('category') + config = BuildConfig.fetch(self.env, name=req.args.get('config')) + + for generator in self.generators: + if category in generator.get_supported_categories(): + template = generator.generate_chart_data(req, config, + category) + break + else: + raise TracError('Unknown report category "%s"' % category) + + return template, 'text/xml' + + class SourceFileLinkFormatter(Component): """Detects references to files in the build log and renders them as links to the repository browser.""" diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -46,8 +46,8 @@ 'bitten.main = bitten.trac_ext.main', 'bitten.master = bitten.master', 'bitten.web_ui = bitten.trac_ext.web_ui', - 'bitten.summarizers = bitten.trac_ext.summarizers', - 'bitten.charts = bitten.trac_ext.charts' + 'bitten.testing = bitten.report.testing', + 'bitten.coverage = bitten.report.coverage' ], 'bitten.recipe_commands': [ NS + 'sh#exec = bitten.build.shtools:exec_',