# HG changeset patch # User wbell # Date 1236559574 0 # Node ID a7c795920c4ad9252f13a6f58f470b6d379cb349 # Parent a3bcc4f98187ef745127c15607ed7c69b9713c40 Merging trac-0.11 branch to trunk. This revision is equivalent to [571]. diff --git a/bitten/admin.py b/bitten/admin.py --- a/bitten/admin.py +++ b/bitten/admin.py @@ -13,12 +13,8 @@ import re from trac.core import * +from trac.admin import IAdminPanelProvider from trac.web.chrome import add_stylesheet -try: - require("TracWebAdmin") - from webadmin.web_ui import IAdminPageProvider -except DistributionNotFound, ImportError: - IAdminPageProvider = None from bitten.model import BuildConfig, TargetPlatform from bitten.recipe import Recipe, InvalidRecipeError @@ -28,15 +24,15 @@ class BuildMasterAdminPageProvider(Component): """Web administration panel for configuring the build master.""" - implements(IAdminPageProvider) + implements(IAdminPanelProvider) - # IAdminPageProvider methods + # IAdminPanelProvider methods - def get_admin_pages(self, req): + def get_admin_panels(self, req): if req.perm.has_permission('BUILD_ADMIN'): yield ('bitten', 'Builds', 'master', 'Master Settings') - def process_admin_request(self, req, cat, page, path_info): + def render_admin_panel(self, req, cat, page, path_info): from bitten.master import BuildMaster master = BuildMaster(self.env) @@ -44,14 +40,9 @@ self._save_config_changes(req, master) req.redirect(req.abs_href.admin(cat, page)) - req.hdf['admin.master'] = { - 'build_all': master.build_all, - 'adjust_timestamps': master.adjust_timestamps, - 'stabilize_wait': master.stabilize_wait, - 'slave_timeout': master.slave_timeout, - } + data = {'master': master} add_stylesheet(req, 'bitten/admin.css') - return 'bitten_admin_master.cs', None + return 'bitten_admin_master.html', data # Internal methods @@ -89,15 +80,15 @@ class BuildConfigurationsAdminPageProvider(Component): """Web administration panel for configuring the build master.""" - implements(IAdminPageProvider) + implements(IAdminPanelProvider) - # IAdminPageProvider methods + # IAdminPanelProvider methods - def get_admin_pages(self, req): + def get_admin_panels(self, req): if req.perm.has_permission('BUILD_MODIFY'): yield ('bitten', 'Builds', 'configs', 'Configurations') - def process_admin_request(self, req, cat, page, path_info): + def render_admin_panel(self, req, cat, page, path_info): data = {} # Analyze url @@ -108,7 +99,8 @@ platform_id = None if config_name: # Existing build config - if platform_id or ( # Editing or creating one of the config's target platforms + if platform_id or ( + # Editing or creating one of the config's target platforms req.method == 'POST' and 'new' in req.args): if platform_id: # Editing target platform @@ -202,9 +194,8 @@ }) data['configs'] = configs - req.hdf['admin'] = data add_stylesheet(req, 'bitten/admin.css') - return 'bitten_admin_configs.cs', None + return 'bitten_admin_configs.html', data # Internal methods diff --git a/bitten/api.py b/bitten/api.py --- a/bitten/api.py +++ b/bitten/api.py @@ -78,8 +78,11 @@ """ def render_summary(req, config, build, step, category): - """Render a summary for the given report and return the resulting HTML - as string. + """Render a summary for the given report. + + This function should return a tuple of the form `(template, data)`, + where `template` is the name of the template to use and `data` is the + data to be passed to the template. :param req: the request object :param config: the build configuration @@ -94,7 +97,7 @@ class IReportChartGenerator(Interface): - """Extension point interface for components that generator a chart for a + """Extension point interface for components that generate a chart for a set of reports.""" def get_supported_categories(): @@ -105,8 +108,9 @@ def generate_chart_data(req, config, category): """Generate the data for a report chart. - This method should store the data in the HDF of the request and return - the name of the template that should process the data. + This function should return a tuple of the form `(template, data)`, + where `template` is the name of the template to use and `data` is the + data to be passed to the template. :param req: the request object :param config: the build configuration diff --git a/bitten/build/ctools.py b/bitten/build/ctools.py --- a/bitten/build/ctools.py +++ b/bitten/build/ctools.py @@ -201,7 +201,82 @@ print e log.warning('Error parsing CppUnit results file (%s)', e) -def gcov(ctxt, include=None, exclude=None, prefix=None): +def cunit (ctxt, file_=None, srcdir=None): + """Collect CUnit XML data. + + :param ctxt: the build context + :type ctxt: `Context` + :param file\_: path of the file containing the CUnit results; may contain + globbing wildcards to match multiple files + :param srcdir: name of the directory containing the source files, used to + link the test results to the corresponding files + """ + assert file_, 'Missing required attribute "file"' + + try: + fileobj = file(ctxt.resolve(file_), 'r') + try: + total, failed = 0, 0 + results = xmlio.Fragment() + log_elem = xmlio.Fragment() + def info (msg): + log.info (msg) + log_elem.append (xmlio.Element ('message', level='info')[msg]) + def warning (msg): + log.warning (msg) + log_elem.append (xmlio.Element ('message', level='warning')[msg]) + def error (msg): + log.error (msg) + log_elem.append (xmlio.Element ('message', level='error')[msg]) + for node in xmlio.parse(fileobj): + if node.name != 'CUNIT_RESULT_LISTING': + continue + for suiteRun in node.children ('CUNIT_RUN_SUITE'): + for suite in suiteRun.children(): + if suite.name not in ('CUNIT_RUN_SUITE_SUCCESS', 'CUNIT_RUN_SUITE_FAILURE'): + warning ("Unknown node: %s" % suite.name) + continue + suiteName = suite.children ('SUITE_NAME').next().gettext() + info ("%s [%s]" % ("*" * (57 - len (suiteName)), suiteName)) + for record in suite.children ('CUNIT_RUN_TEST_RECORD'): + for result in record.children(): + if result.name not in ('CUNIT_RUN_TEST_SUCCESS', 'CUNIT_RUN_TEST_FAILURE'): + continue + testName = result.children ('TEST_NAME').next().gettext() + info ("Running %s..." % testName); + test = xmlio.Element('test') + test.attr['fixture'] = suiteName + test.attr['name'] = testName + if result.name == 'CUNIT_RUN_TEST_FAILURE': + error ("%s(%d): %s" + % (result.children ('FILE_NAME').next().gettext(), + int (result.children ('LINE_NUMBER').next().gettext()), + result.children ('CONDITION').next().gettext())) + test.attr['status'] = 'failure' + failed += 1 + else: + test.attr['status'] = 'success' + + results.append(test) + total += 1 + + if failed: + ctxt.error('%d of %d test%s failed' % (failed, total, + total != 1 and 's' or '')) + + ctxt.report('test', results) + ctxt.log (log_elem) + + finally: + fileobj.close() + + except IOError, e: + log.warning('Error opening CUnit results file (%s)', e) + except xmlio.ParseError, e: + print e + log.warning('Error parsing CUnit results file (%s)', e) + +def gcov(ctxt, include=None, exclude=None, prefix=None, root=""): """Run ``gcov`` to extract coverage data where available. :param ctxt: the build context @@ -210,8 +285,10 @@ :param exclude: patterns of files and directories that should be excluded :param prefix: optional prefix name that is added to object files by the build system + :param root: optional root path in which the build system puts the object + files """ - file_re = re.compile(r'^File \`(?P[^\']+)\'\s*$') + file_re = re.compile(r'^File (?:\'|\`)(?P[^\']+)\'\s*$') lines_re = re.compile(r'^Lines executed:(?P\d+\.\d+)\% of (?P\d+)\s*$') files = [] @@ -220,24 +297,35 @@ files.append(filename) coverage = xmlio.Fragment() + log_elem = xmlio.Fragment() + def info (msg): + log.info (msg) + log_elem.append (xmlio.Element ('message', level='info')[msg]) + def warning (msg): + log.warning (msg) + log_elem.append (xmlio.Element ('message', level='warning')[msg]) + def error (msg): + log.error (msg) + log_elem.append (xmlio.Element ('message', level='error')[msg]) for srcfile in files: # Determine the coverage for each source file by looking for a .gcno # and .gcda pair + info ("Getting coverage info for %s" % srcfile) filepath, filename = os.path.split(srcfile) stem = os.path.splitext(filename)[0] if prefix is not None: stem = prefix + '-' + stem - objfile = os.path.join(filepath, stem + '.o') + objfile = os.path.join (root, filepath, stem + '.o') if not os.path.isfile(ctxt.resolve(objfile)): - log.warn('No object file found for %s at %s', srcfile, objfile) + warning ('No object file found for %s at %s' % (srcfile, objfile)) continue - if not os.path.isfile(ctxt.resolve(stem + '.gcno')): - log.warn('No .gcno file found for %s', srcfile) + if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcno'))): + warning ('No .gcno file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcno'))) continue - if not os.path.isfile(ctxt.resolve(stem + '.gcda')): - log.warn('No .gcda file found for %s', srcfile) + if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcda'))): + warning ('No .gcda file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcda'))) continue num_lines, num_covered = 0, 0 @@ -274,3 +362,4 @@ coverage.append(module) ctxt.report('coverage', coverage) + ctxt.log (log_elem) diff --git a/bitten/build/tests/ctools.py b/bitten/build/tests/ctools.py --- a/bitten/build/tests/ctools.py +++ b/bitten/build/tests/ctools.py @@ -107,6 +107,8 @@ ctools.CommandLine = dummy.CommandLine() ctools.gcov(self.ctxt) type, category, generator, xml = self.ctxt.output.pop() + self.assertEqual('log', type) + type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(0, len(xml.children)) @@ -131,6 +133,8 @@ """) ctools.gcov(self.ctxt) type, category, generator, xml = self.ctxt.output.pop() + self.assertEqual('log', type) + type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(1, len(xml.children)) diff --git a/bitten/htdocs/bitten.css b/bitten/htdocs/bitten.css --- a/bitten/htdocs/bitten.css +++ b/bitten/htdocs/bitten.css @@ -157,6 +157,7 @@ } #content.build table.tests tr.failed { background: #d99; } #content.build table.tests tr.failed td { font-weight: bold; } +#content.build table.tests tr.failed:hover th, #content.build table.tests tr.failed:hover td, #content.build table.tests tr.failed:hover tr { background: #966; } #content.build table.tests tr.failed :link, diff --git a/bitten/report/coverage.py b/bitten/report/coverage.py --- a/bitten/report/coverage.py +++ b/bitten/report/coverage.py @@ -58,14 +58,14 @@ 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] - ] + data = {'title': 'Test Coverage', + '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' + return 'bitten_chart_coverage.html', data class TestCoverageSummarizer(Component): @@ -101,7 +101,7 @@ GROUP BY file, item_name.value ORDER BY item_name.value""", (build.id, step.name)) - data = [] + units = [] total_loc, total_cov = 0, 0 for unit, file, loc, cov in cursor: try: @@ -112,7 +112,7 @@ d = {'name': unit, 'loc': loc, 'cov': int(cov)} if file: d['href'] = req.href.browser(config.path, file, rev=build.rev, annotate='coverage') - data.append(d) + units.append(d) total_loc += loc total_cov += loc * cov @@ -120,10 +120,10 @@ 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') + return 'bitten_summary_coverage.html', { + 'units': units, + 'totals': {'loc': total_loc, 'cov': int(coverage)} + } # Coverage annotation requires the new interface from 0.11 diff --git a/bitten/report/testing.py b/bitten/report/testing.py --- a/bitten/report/testing.py +++ b/bitten/report/testing.py @@ -56,14 +56,14 @@ 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] - ] + data = {'title': 'Unit Tests', + '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' + return 'bitten_chart_tests.html', data class TestResultsSummarizer(Component): @@ -103,19 +103,20 @@ GROUP BY file, fixture ORDER BY fixture""", (build.id, step.name)) - data = [] + fixtures = [] 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}) + fixtures.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) + fixtures[-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') + data = {'fixtures': fixtures, + 'totals': {'success': total_success, 'failure': total_failure, + 'error': total_error} + } + return 'bitten_summary_tests.html', data diff --git a/bitten/report/tests/coverage.py b/bitten/report/tests/coverage.py --- a/bitten/report/tests/coverage.py +++ b/bitten/report/tests/coverage.py @@ -13,7 +13,6 @@ 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 @@ -39,15 +38,15 @@ self.assertEqual(['coverage'], generator.get_supported_categories()) def test_no_reports(self): - req = Mock(hdf=HDFWrapper()) + req = Mock() 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']) + template, data = generator.generate_chart_data(req, config, 'coverage') + self.assertEqual('bitten_chart_coverage.html', template) + self.assertEqual('Test Coverage', data['title']) + self.assertEqual('', data['data'][0][0]) + self.assertEqual('Lines of code', data['data'][1][0]) + self.assertEqual('Coverage', data['data'][2][0]) def test_single_platform(self): config = Mock(name='trunk') @@ -59,17 +58,17 @@ report.items += [{'lines': '12', 'percentage': '25'}] report.insert() - req = Mock(hdf=HDFWrapper()) + req = Mock() 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']) + template, data = generator.generate_chart_data(req, config, 'coverage') + self.assertEqual('bitten_chart_coverage.html', template) + self.assertEqual('Test Coverage', data['title']) + self.assertEqual('', data['data'][0][0]) + self.assertEqual('[123]', data['data'][0][1]) + self.assertEqual('Lines of code', data['data'][1][0]) + self.assertEqual(12, data['data'][1][1]) + self.assertEqual('Coverage', data['data'][2][0]) + self.assertEqual(3, data['data'][2][1]) def test_multi_platform(self): config = Mock(name='trunk') @@ -88,17 +87,17 @@ report.items += [{'lines': '12', 'percentage': '50'}] report.insert() - req = Mock(hdf=HDFWrapper()) + req = Mock() 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']) + template, data = generator.generate_chart_data(req, config, 'coverage') + self.assertEqual('bitten_chart_coverage.html', template) + self.assertEqual('Test Coverage', data['title']) + self.assertEqual('', data['data'][0][0]) + self.assertEqual('[123]', data['data'][0][1]) + self.assertEqual('Lines of code', data['data'][1][0]) + self.assertEqual(12, data['data'][1][1]) + self.assertEqual('Coverage', data['data'][2][0]) + self.assertEqual(6, data['data'][2][1]) def suite(): diff --git a/bitten/report/tests/testing.py b/bitten/report/tests/testing.py --- a/bitten/report/tests/testing.py +++ b/bitten/report/tests/testing.py @@ -12,7 +12,6 @@ from trac.db import DatabaseManager from trac.test import EnvironmentStub, Mock -from trac.web.clearsilver import HDFWrapper from bitten.model import * from bitten.report.testing import TestResultsChartGenerator @@ -35,15 +34,15 @@ self.assertEqual(['test'], generator.get_supported_categories()) def test_no_reports(self): - req = Mock(hdf=HDFWrapper()) + req = Mock() 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']) + template, data = generator.generate_chart_data(req, config, 'test') + self.assertEqual('bitten_chart_tests.html', template) + self.assertEqual('Unit Tests', data['title']) + self.assertEqual('', data['data'][0][0]) + self.assertEqual('Total', data['data'][1][0]) + self.assertEqual('Failures', data['data'][2][0]) def test_single_platform(self): config = Mock(name='trunk') @@ -55,17 +54,17 @@ {'status': 'success'}] report.insert() - req = Mock(hdf=HDFWrapper()) + req = Mock() 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']) + template, data = generator.generate_chart_data(req, config, 'test') + self.assertEqual('bitten_chart_tests.html', template) + self.assertEqual('Unit Tests', data['title']) + self.assertEqual('', data['data'][0][0]) + self.assertEqual('[123]', data['data'][0][1]) + self.assertEqual('Total', data['data'][1][0]) + self.assertEqual(3, data['data'][1][1]) + self.assertEqual('Failures', data['data'][2][0]) + self.assertEqual(1, data['data'][2][1]) def test_multi_platform(self): config = Mock(name='trunk') @@ -86,17 +85,17 @@ {'status': 'failure'}] report.insert() - req = Mock(hdf=HDFWrapper()) + req = Mock() 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']) + template, data = generator.generate_chart_data(req, config, 'test') + self.assertEqual('bitten_chart_tests.html', template) + self.assertEqual('Unit Tests', data['title']) + self.assertEqual('', data['data'][0][0]) + self.assertEqual('[123]', data['data'][0][1]) + self.assertEqual('Total', data['data'][1][0]) + self.assertEqual(3, data['data'][1][1]) + self.assertEqual('Failures', data['data'][2][0]) + self.assertEqual(2, data['data'][2][1]) def suite(): diff --git a/bitten/templates/bitten_admin_configs.cs b/bitten/templates/bitten_admin_configs.cs deleted file mode 100644 --- a/bitten/templates/bitten_admin_configs.cs +++ /dev/null @@ -1,181 +0,0 @@ -

Manage Build Configurations

-
- - - - - - - -
- -

- -
- -

-
-
- Repository Mapping - - - - - - - - -
-
-
- - -
-
-

Target Platforms

- - - - - - - - -
 NameRules
  • - ~= -
-
- - -
-
-
-
-
-
- Rules - - - - - - - -
Property nameMatch pattern
-
-
-

- The property name can be any of a set of standard default properties, or - custom properties defined in slave configuration files. The default - properties are: -

-
-
os:
-
The name of the operating system (for example "Darwin")
-
family:
-
The type of operating system (for example "posix" or "nt")
-
version:
-
The operating system version (for example "8.10.1)
-
machine:
-
The hardware architecture (for example "i386")
-
processor:
-
The CPU model (for example "i386", this may be empty or the same as for - machine)
-
name:
-
The name of the slave
-
ipnr:
-
The IP address of the slave
-
-

The match pattern is a regular expression.

-
-
- - - - - - - - -
-
- -
-
- Add Configuration: - - - - -
- -
-
-
- -
-
-
- -
- - - - - - - - - - - -
 NamePathActive
checked="checked" >
-
- - -
-
diff --git a/bitten/templates/bitten_admin_configs.html b/bitten/templates/bitten_admin_configs.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_admin_configs.html @@ -0,0 +1,240 @@ + + + + + Manage Build Configurations + + +

Manage Build Configurations

+
+ + + + + + + +
+ +

+ +

+ +
+ +

+ +

+
+
+ Repository Mapping + + + + + + + + +
+
+
+ + +
+
+

Target Platforms

+ + + + + + + + + + + + + + + +
 NameRules
(No Platforms)
+ + + $platform.name + +
    +
  • + + $rule.property ~= + $rule.pattern + +
  • +
+
+
+ + +
+
+
+ +
+
+
+ Rules + + + + + + + + +
Property nameMatch pattern
+ +
+
+

+ The property name can be any of a set of standard + default properties, or custom properties defined + in slave configuration files. The default + properties are: +

+
+
os:
+
The name of the operating system (for example + "Darwin")
+
family:
+
The type of operating system (for example + "posix" or "nt")
+
version:
+
The operating system version (for example + "8.10.1)
+
machine:
+
The hardware architecture (for example "i386"
+
processor:
+
The CPU model (for example "i386", this may be + empty or the same as for machine +
+
name:
+
The name of the slave
+
ipnr:
+
The IP address of the slave
+
+

+ The match pattern is a regular expression. +

+
+
+ + + + + +
+
+ + +
+ Add Configuration: + + + + + +
+ +
+
+ +
+
+ +
+ + + + + + + + + + + + + + +
 NamePathActive
(No Build Configurations)
+ + + $config.label + $config.path + +
+
+ + +
+
+ + diff --git a/bitten/templates/bitten_admin_master.cs b/bitten/templates/bitten_admin_master.cs deleted file mode 100644 --- a/bitten/templates/bitten_admin_master.cs +++ /dev/null @@ -1,59 +0,0 @@ -

Manage Build Master

- -
- -
- Configuration Options -
- -
-

- Whether to build older revisions even when a more recent revision has - already been built. -

-
- -
-

- Whether the timestamps of builds should be adjusted to be close to the - timestamps of the corresponding changesets. -

-
-
- -
-

- The time in seconds to wait for the repository to stabilize after a - check-in before initiating a build. -

-
-
- -
-

- The timeout in seconds after which a build started by a slave is - considered aborted, in case there has been no activity from that slave - in that time. -

-
- -
- -
-
diff --git a/bitten/templates/bitten_admin_master.html b/bitten/templates/bitten_admin_master.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_admin_master.html @@ -0,0 +1,73 @@ + + + + + Manage Build Master + + +

Manage Build Master

+ +
+
+ Configuration Options +
+ +
+

+ Whether to build older revisions even when a more recent + revision has already been built. +

+
+ +
+

+ Whether the timestamps of builds should be adjusted to be + close to the timestamps of the corresponding changesets. +

+
+
+ +
+

+ The time in seconds to wait for the repository to stabilize + after a check-in before initiating a build. +

+
+
+ +
+

+ The timeout in seconds after which a build started by a slave + is considered aborted, in case there has been no activity from + that slave in that time. +

+
+ +
+ +
+
+ + diff --git a/bitten/templates/bitten_build.cs b/bitten/templates/bitten_build.cs deleted file mode 100644 --- a/bitten/templates/bitten_build.cs +++ /dev/null @@ -1,84 +0,0 @@ - - - -
-

-
-
Configuration:
-
-
-
Triggered by:
-
Changeset [] by -
-
Built by:
-
() -
-
Operating system:
-
() -
-
Hardware:
-
() -
-
Started:Building since: -
( ago) -
- -
Stopped:
-
( ago)
- -
Duration:
-
-
-
-
-
- - -
-
-

()

-
-

Errors

-

-
-

Log


-
-
- -
-
- -
- diff --git a/bitten/templates/bitten_build.html b/bitten/templates/bitten_build.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_build.html @@ -0,0 +1,73 @@ + + + + + $title + + +
+

$title

+
+
Configuration:
+
+ $build.config.name +
+
Triggered by:
+
+ Changeset [$build.rev] by + $build.chgset_author +
+
Built by:
+
+ $slave.name ($slave.ipnr) +
+
Operating system:
+
$slave.os_name $slave.os_version ($slave.os_family)
+
Hardware:
+
+ $slave.machine + ($slave.processor) +
+
+ ${build.stopped and 'Started:' or 'Building since:'} +
+
$build.started ($build.started_delta ago)
+
Stopped:
+
$build.stopped ($build.stopped_delta ago)
+
Duration:
+
$build.duration
+
+
+
+ + +
+
+

$step.name ($step.duration)

+
+

Errors

+
    +
  • $error
  • +
+
+

$step.description

+
+
+

Log

+
$item.message
+
+
+ +
+
+
+
+ + diff --git a/bitten/templates/bitten_chart_coverage.cs b/bitten/templates/bitten_chart_coverage.cs deleted file mode 100644 --- a/bitten/templates/bitten_chart_coverage.cs +++ /dev/null @@ -1,40 +0,0 @@ - - - area - area - - - - - - - - - - - - - bbbbbb - 9999ff - - - - - - - - - - diff --git a/bitten/templates/bitten_chart_coverage.html b/bitten/templates/bitten_chart_coverage.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_chart_coverage.html @@ -0,0 +1,38 @@ + + + area + area + + + + + + + + + $value + $value + + + + + + + + + + bbbbbb + 9999ff + + + + + + + $title + + + diff --git a/bitten/templates/bitten_chart_tests.cs b/bitten/templates/bitten_chart_tests.cs deleted file mode 100644 --- a/bitten/templates/bitten_chart_tests.cs +++ /dev/null @@ -1,40 +0,0 @@ - - - area - column - - - - - - - - - - - - - 99dd99 - ff0000 - - - - - - - - - - diff --git a/bitten/templates/bitten_chart_tests.html b/bitten/templates/bitten_chart_tests.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_chart_tests.html @@ -0,0 +1,38 @@ + + + area + column + + + + + + + + + $value + $value + + + + + + + + + + 99dd99 + ff0000 + + + + + + + $title + + + diff --git a/bitten/templates/bitten_config.cs b/bitten/templates/bitten_config.cs deleted file mode 100644 --- a/bitten/templates/bitten_config.cs +++ /dev/null @@ -1,365 +0,0 @@ - - -
-

-
-
- checked="checked" /> - -
-
- -
-
-

-

Latest builds

- - -
[] by

-

- ()
/
SuccessFailedIn-progress -

No build yet

-
-
- - -
-
- - - - - -
- -

- -
-
- Build Recipe - -
-
- Repository Mapping - - - - - - - - -
-
-
- - - -
-
-
-

Target Platforms

    -
  • -
-
- - -
-
-
-

Are you sure you want to delete the build configuration "?

-

This will also delete all builds performed for that configuration.

-
- - - -
This build configuration is currently - inactive.
No builds will be initiated for this configuration
- until it is activated.
This configuration is currently active.
-
- - - -
-

- Repository path: (starting at [], up to []) -

-
- - - - -
-
- - -
- - -
- - -
Chgset -
[]

-

- - -
ChgsetBuild
- []
- : - -
- ()
/
- Building since: - ( ago) -
-
-
-
-

Rules

- - - - - - - -
Property nameMatch pattern
-
-
-
- - - - -
-
- - - diff --git a/bitten/templates/bitten_config.html b/bitten/templates/bitten_config.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_config.html @@ -0,0 +1,178 @@ + + + + + $title + + + + + Success + Failed + In-progress + + + +
+ $slave.name ($slave.ipnr)
+ $slave.os_name $slave.os_version + / + ${slave.processor or slave.machine or ''} +
+ + + +
+

$title

+
+
+ + +
+
+ +
+
+

+ $config.label +

+
+ $config.description +
+

Latest builds

+ + + +
+ [$youngest_rev.id] + by $youngest_rev.author

$youngest_rev.date

+

$youngest_rev.message

+
+ + $build.platform +

$build.stopped

+ ${slave_info(build.slave)} + ${build_status(build.status)} +
+ $build.platform +

No build yet

+
+
+ +
+
+
+ This build configuration is currently inactive.
+ No builds will be initiated for this configuration
+ until it is activated. +
+
+ This configuration is currently active. +
+
+ + + +
+
+

+ Repository path: + $config.path + ${not config.path and '—' or ''} + + (starting at + [$config.min_rev] + , + up to + [$config.max_rev]) + +

+
+ $config.description +
+
+ + + + +
+
+ + + + + + + + + + + + + +
Chgset$platform.name
+ [$rev_num] + +
+ + $build.id + ${build_status(build.status)} + + ${slave_info(build.slave)} +
+ ${build_steps(build.steps)} +
+
+ +
+ +

+ $config.label +

+ + + + + + + + +
ChgsetBuild
+ [$build.rev] + +
+ + $build.id: $build.platform + + ${slave_info(build.slave)} +
+ ${build_steps(build.steps)} +
+
+ + diff --git a/bitten/templates/bitten_summary_coverage.cs b/bitten/templates/bitten_summary_coverage.cs deleted file mode 100644 --- a/bitten/templates/bitten_summary_coverage.cs +++ /dev/null @@ -1,16 +0,0 @@ -

Code Coverage

- - - - - - - - -
UnitLines of CodeCoverage
%
Total%
diff --git a/bitten/templates/bitten_summary_coverage.html b/bitten/templates/bitten_summary_coverage.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_summary_coverage.html @@ -0,0 +1,27 @@ + + + +

Code Coverage

+ + + + + + + + + + + + + + +
UnitLines of CodeCoverage
+ $item.name + $item.name + $item.loc${item.cov}%
Total$totals.loc${totals.cov}%
+ + diff --git a/bitten/templates/bitten_summary_tests.cs b/bitten/templates/bitten_summary_tests.cs deleted file mode 100644 --- a/bitten/templates/bitten_summary_tests.cs +++ /dev/null @@ -1,23 +0,0 @@ -

Test Results

- - - - - - class="failed"> - - - - - -
Test FixtureTotalFailuresErrors
Total
diff --git a/bitten/templates/bitten_summary_tests.html b/bitten/templates/bitten_summary_tests.html new file mode 100755 --- /dev/null +++ b/bitten/templates/bitten_summary_tests.html @@ -0,0 +1,31 @@ + + + +

Test Results

+ + + + + + + + + + + + + + + + + +
Test FixtureTotalFailuresErrors
+ $item.name + $item.name + ${item.num_success + item.num_failure + item.num_error}$item.num_failure$item.num_error
Total$totals.success$totals.failure$totals.error
+ + diff --git a/bitten/tests/admin.py b/bitten/tests/admin.py --- a/bitten/tests/admin.py +++ b/bitten/tests/admin.py @@ -63,36 +63,33 @@ DefaultPermissionPolicy.CACHE_EXPIRY = self.old_perm_cache_expiry shutil.rmtree(self.env.path) - def test_get_admin_pages(self): + def test_get_admin_panels(self): provider = BuildMasterAdminPageProvider(self.env) req = Mock(perm=PermissionCache(self.env, 'joe')) self.assertEqual([('bitten', 'Builds', 'master', 'Master Settings')], - list(provider.get_admin_pages(req))) + list(provider.get_admin_panels(req))) PermissionSystem(self.env).revoke_permission('joe', 'BUILD_ADMIN') req = Mock(perm=PermissionCache(self.env, 'joe')) - self.assertEqual([], list(provider.get_admin_pages(req))) + self.assertEqual([], list(provider.get_admin_panels(req))) def test_process_get_request(self): - data = {} - req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildMasterAdminPageProvider(self.env) - template_name, content_type = provider.process_admin_request( + template_name, data = provider.render_admin_panel( req, 'bitten', 'master', '' ) - self.assertEqual('bitten_admin_master.cs', template_name) - self.assertEqual(None, content_type) - assert 'admin.master' in data - self.assertEqual({ - 'slave_timeout': 3600, - 'stabilize_wait': 0, - 'adjust_timestamps': False, - 'build_all': False, - }, data['admin.master']) + self.assertEqual('bitten_admin_master.html', template_name) + assert 'master' in data + master = data['master'] + self.assertEqual(3600, master.slave_timeout) + self.assertEqual(0, master.stabilize_wait) + assert not master.adjust_timestamps + assert not master.build_all def test_process_config_changes(self): redirected_to = [] @@ -105,7 +102,7 @@ provider = BuildMasterAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'master', '') + provider.render_admin_panel(req, 'bitten', 'master', '') self.fail('Expected RequestDone') except RequestDone: @@ -155,30 +152,28 @@ DefaultPermissionPolicy.CACHE_EXPIRY = self.old_perm_cache_expiry shutil.rmtree(self.env.path) - def test_get_admin_pages(self): + def test_get_admin_panels(self): provider = BuildConfigurationsAdminPageProvider(self.env) req = Mock(perm=PermissionCache(self.env, 'joe')) self.assertEqual([('bitten', 'Builds', 'configs', 'Configurations')], - list(provider.get_admin_pages(req))) + list(provider.get_admin_panels(req))) PermissionSystem(self.env).revoke_permission('joe', 'BUILD_MODIFY') req = Mock(perm=PermissionCache(self.env, 'joe')) - self.assertEqual([], list(provider.get_admin_pages(req))) + self.assertEqual([], list(provider.get_admin_panels(req))) def test_process_view_configs_empty(self): - data = {} - req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, content_type = provider.process_admin_request( + template_name, data = provider.render_admin_panel( req, 'bitten', 'configs', '' ) - self.assertEqual('bitten_admin_configs.cs', template_name) - self.assertEqual(None, content_type) - self.assertEqual([], data['admin']['configs']) + self.assertEqual('bitten_admin_configs.html', template_name) + self.assertEqual([], data['configs']) def test_process_view_configs(self): BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', @@ -186,18 +181,17 @@ BuildConfig(self.env, name='bar', label='Bar', path='branches/bar', min_rev='123', max_rev='456').insert() - data = {} - req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, content_type = provider.process_admin_request( + template_name, data = provider.render_admin_panel( req, 'bitten', 'configs', '' ) - self.assertEqual('bitten_admin_configs.cs', template_name) - self.assertEqual(None, content_type) - configs = data['admin']['configs'] + self.assertEqual('bitten_admin_configs.html', template_name) + assert 'configs' in data + configs = data['configs'] self.assertEqual(2, len(configs)) self.assertEqual({ 'name': 'bar', 'href': '/admin/bitten/configs/bar', @@ -215,18 +209,17 @@ active=True).insert() TargetPlatform(self.env, config='foo', name='any').insert() - data = {} - req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, content_type = provider.process_admin_request( + template_name, data = provider.render_admin_panel( req, 'bitten', 'configs', 'foo' ) - self.assertEqual('bitten_admin_configs.cs', template_name) - self.assertEqual(None, content_type) - config = data['admin']['config'] + self.assertEqual('bitten_admin_configs.html', template_name) + assert 'config' in data + config = data['config'] self.assertEqual({ 'name': 'foo', 'label': 'Foo', 'description': '', 'recipe': '', 'path': 'branches/foo', 'min_rev': None, 'max_rev': None, @@ -251,7 +244,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected RequestDone') except RequestDone: @@ -277,7 +270,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected RequestDone') except RequestDone: @@ -303,7 +296,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected RequestDone') except RequestDone: @@ -323,7 +316,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected RequestDone') except RequestDone: @@ -338,7 +331,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected TracError') except TracError, e: @@ -351,7 +344,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected TracError') except TracError, e: @@ -370,7 +363,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected TracError') except TracError, e: @@ -387,7 +380,7 @@ args={'add': '', 'name': 'bar', 'label': 'Bar'}) provider = BuildConfigurationsAdminPageProvider(self.env) - self.assertRaises(PermissionError, provider.process_admin_request, req, + self.assertRaises(PermissionError, provider.render_admin_panel, req, 'bitten', 'configs', '') def test_process_remove_config(self): @@ -406,7 +399,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected RequestDone') except RequestDone: @@ -430,7 +423,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected RequestDone') except RequestDone: @@ -448,7 +441,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected TracError') except TracError, e: @@ -463,7 +456,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', '') + provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected TracError') except TracError, e: @@ -479,7 +472,7 @@ args={'remove': '', 'sel': 'bar'}) provider = BuildConfigurationsAdminPageProvider(self.env) - self.assertRaises(PermissionError, provider.process_admin_request, req, + self.assertRaises(PermissionError, provider.render_admin_panel, req, 'bitten', 'configs', '') def test_process_update_config(self): @@ -499,7 +492,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected RequestDone') except RequestDone: @@ -518,7 +511,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: @@ -534,7 +527,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: @@ -556,7 +549,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: @@ -573,7 +566,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: @@ -592,7 +585,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: @@ -609,13 +602,13 @@ args={'new': ''}) provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, content_type = provider.process_admin_request( + template_name, data = provider.render_admin_panel( req, 'bitten', 'configs', 'foo' ) - self.assertEqual('bitten_admin_configs.cs', template_name) - self.assertEqual(None, content_type) - platform = data['admin']['platform'] + self.assertEqual('bitten_admin_configs.html', template_name) + assert 'platform' in data + platform = data['platform'] self.assertEqual({ 'id': None, 'exists': False, 'name': None, 'rules': [('', '')], }, platform) @@ -636,7 +629,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected RequestDone') except RequestDone: @@ -663,7 +656,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected RequestDone') except RequestDone: @@ -689,7 +682,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected RequestDone') except RequestDone: @@ -715,7 +708,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') + provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: @@ -727,18 +720,17 @@ platform = TargetPlatform(self.env, config='foo', name='any') platform.insert() - data = {} - req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, href=Href('/'), perm=PermissionCache(self.env, 'joe'), args={}) provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, content_type = provider.process_admin_request( + template_name, data = provider.render_admin_panel( req, 'bitten', 'configs', 'foo/%d' % platform.id ) - self.assertEqual('bitten_admin_configs.cs', template_name) - self.assertEqual(None, content_type) - platform = data['admin']['platform'] + self.assertEqual('bitten_admin_configs.html', template_name) + assert 'platform' in data + platform = data['platform'] self.assertEqual({ 'id': 1, 'exists': True, 'name': 'any', 'rules': [('', '')], }, platform) @@ -761,7 +753,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', + provider.render_admin_panel(req, 'bitten', 'configs', 'foo/%d' % platform.id) self.fail('Expected RequestDone') @@ -791,7 +783,7 @@ provider = BuildConfigurationsAdminPageProvider(self.env) try: - provider.process_admin_request(req, 'bitten', 'configs', + provider.render_admin_panel(req, 'bitten', 'configs', 'foo/%d' % platform.id) self.fail('Expected RequestDone') diff --git a/bitten/tests/web_ui.py b/bitten/tests/web_ui.py --- a/bitten/tests/web_ui.py +++ b/bitten/tests/web_ui.py @@ -17,7 +17,6 @@ from trac.perm import PermissionCache, PermissionSystem from trac.test import EnvironmentStub, Mock from trac.util.html import Markup -from trac.web.clearsilver import HDFWrapper from trac.web.href import Href from bitten.main import BuildSystem from bitten.model import Build, BuildConfig, BuildStep, TargetPlatform, schema @@ -58,14 +57,13 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build', href=Href('/trac'), args={}, chrome={}, - hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) + perm=PermissionCache(self.env, 'joe')) module = BuildConfigController(self.env) assert module.match_request(req) - module.process_request(req) + _, data, _ = module.process_request(req) - self.assertEqual('overview', req.hdf['page.mode']) - self.assertEqual('0', req.hdf.get('build.can_create', '0')) + self.assertEqual('overview', data['page_mode']) def test_view_config(self): config = BuildConfig(self.env, name='test', path='trunk') @@ -76,7 +74,7 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test', href=Href('/trac'), args={}, - chrome={}, hdf=HDFWrapper(), authname='joe', + chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], @@ -87,12 +85,10 @@ module = BuildConfigController(self.env) assert module.match_request(req) - module.process_request(req) + _, data, _ = module.process_request(req) - self.assertEqual('view_config', req.hdf['page.mode']) - self.assertEqual('0', req.hdf.get('build.config.can_delete', '0')) - self.assertEqual('0', req.hdf.get('build.config.can_modify', '0')) - self.assertEqual(None, req.hdf.get('chrome.links.next.0.href')) + self.assertEqual('view_config', data['page_mode']) + assert not 'next' in req.chrome['links'] def test_view_config_paging(self): config = BuildConfig(self.env, name='test', path='trunk') @@ -103,7 +99,7 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test', href=Href('/trac'), args={}, - chrome={}, hdf=HDFWrapper(), authname='joe', + chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], @@ -114,14 +110,11 @@ module = BuildConfigController(self.env) assert module.match_request(req) - module.process_request(req) + _, data, _ = module.process_request(req) - if req.chrome: # Trac 0.11 + if req.chrome: self.assertEqual('/trac/build/test?page=2', req.chrome['links']['next'][0]['href']) - else: - self.assertEqual('/trac/build/test?page=2', - req.hdf.get('chrome.links.next.0.href')) class SourceFileLinkFormatterTestCase(unittest.TestCase): diff --git a/bitten/util/testrunner.py b/bitten/util/testrunner.py --- a/bitten/util/testrunner.py +++ b/bitten/util/testrunner.py @@ -128,8 +128,9 @@ test.finalize_options(self) if self.xml_output is not None: - if not os.path.exists(os.path.dirname(self.xml_output)): - os.makedirs(os.path.dirname(self.xml_output)) + output_dir = os.path.dirname(self.xml_output) or '.' + if not os.path.exists(output_dir): + os.makedirs(output_dir) self.xml_output_file = open(self.xml_output, 'w') if self.coverage_method not in ('trace', 'coverage', 'figleaf'): diff --git a/bitten/util/xmlio.py b/bitten/util/xmlio.py --- a/bitten/util/xmlio.py +++ b/bitten/util/xmlio.py @@ -20,6 +20,17 @@ from StringIO import StringIO from UserDict import DictMixin +import cgi +import string + +__trans = string.maketrans ("", "") +__todel = "" +for c in range (0, 256): + c1 = chr (c) + if not c1 in string.printable: + __todel += c1 +del c, c1 + __all__ = ['Fragment', 'Element', 'ParsedElement', 'parse'] __docformat__ = 'restructuredtext en' @@ -27,8 +38,7 @@ """Escape special characters in the provided text so that it can be safely included in XML text nodes. """ - return str(text).replace('&', '&').replace('<', '<') \ - .replace('>', '>') + return cgi.escape (str(text)).translate (__trans, __todel) def _escape_attr(attr): """Escape special characters in the provided text so that it can be safely diff --git a/bitten/web_ui.py b/bitten/web_ui.py --- a/bitten/web_ui.py +++ b/bitten/web_ui.py @@ -16,65 +16,51 @@ from StringIO import StringIO import pkg_resources +from genshi.builder import tag from trac.core import * -try: - from trac.timeline import ITimelineEventProvider - have_trac_011 = True -except ImportError: - from trac.Timeline import ITimelineEventProvider - have_trac_011 = False +from trac.timeline import ITimelineEventProvider from trac.util import escape, pretty_timedelta, format_datetime, shorten_line, \ Markup from trac.util.html import html from trac.web import IRequestHandler from trac.web.chrome import INavigationContributor, ITemplateProvider, \ - add_link, add_stylesheet -from trac.wiki import wiki_to_html -from trac.wiki import wiki_to_oneliner as wiki_to_oneliner_ + add_link, add_stylesheet, add_ctxtnav, \ + prevnext_nav, add_script +from trac.wiki import wiki_to_html, wiki_to_oneliner from bitten.api import ILogFormatter, IReportChartGenerator, IReportSummarizer from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ BuildLog, Report from bitten.queue import collect_changes -def wiki_to_oneliner(wikitext, env, db=None, shorten=False, absurls=False, - req=None): - if have_trac_011: - return wiki_to_oneliner_(wikitext, env, db=db, shorten=shorten, - absurls=absurls, req=req) - else: - return wiki_to_oneliner_(wikitext, env, db=db, shorten=shorten, - absurls=absurls) - - _status_label = {Build.PENDING: 'pending', Build.IN_PROGRESS: 'in progress', Build.SUCCESS: 'completed', Build.FAILURE: 'failed'} -def _build_to_hdf(env, req, build): - hdf = {'id': build.id, 'name': build.slave, 'rev': build.rev, - 'status': _status_label[build.status], - 'cls': _status_label[build.status].replace(' ', '-'), - 'href': req.href.build(build.config, build.id), - 'chgset_href': req.href.changeset(build.rev)} +def _get_build_data(env, req, build): + data = {'id': build.id, 'name': build.slave, 'rev': build.rev, + 'status': _status_label[build.status], + 'cls': _status_label[build.status].replace(' ', '-'), + 'href': req.href.build(build.config, build.id), + 'chgset_href': req.href.changeset(build.rev)} if build.started: - hdf['started'] = format_datetime(build.started) - hdf['started_delta'] = pretty_timedelta(build.started) - hdf['duration'] = pretty_timedelta(build.started) + data['started'] = format_datetime(build.started) + data['started_delta'] = pretty_timedelta(build.started) + data['duration'] = pretty_timedelta(build.started) if build.stopped: - hdf['stopped'] = format_datetime(build.stopped) - hdf['stopped_delta'] = pretty_timedelta(build.stopped) - hdf['duration'] = pretty_timedelta(build.stopped, build.started) - hdf['slave'] = { + data['stopped'] = format_datetime(build.stopped) + data['stopped_delta'] = pretty_timedelta(build.stopped) + data['duration'] = pretty_timedelta(build.stopped, build.started) + data['slave'] = { 'name': build.slave, 'ipnr': build.slave_info.get(Build.IP_ADDRESS), - 'os.name': build.slave_info.get(Build.OS_NAME), - 'os.family': build.slave_info.get(Build.OS_FAMILY), - 'os.version': build.slave_info.get(Build.OS_VERSION), + 'os_name': build.slave_info.get(Build.OS_NAME), + 'os_family': build.slave_info.get(Build.OS_FAMILY), + 'os_version': build.slave_info.get(Build.OS_VERSION), 'machine': build.slave_info.get(Build.MACHINE), 'processor': build.slave_info.get(Build.PROCESSOR) } - return hdf + return data class BittenChrome(Component): @@ -95,11 +81,9 @@ def get_navigation_items(self, req): """Return the navigation item for access the build status overview from the Trac navigation bar.""" - if not req.perm.has_permission('BUILD_VIEW'): - return - yield ('mainnav', 'build', \ - Markup('Build Status') % - req.href.build()) + if 'BUILD_VIEW' in req.perm: + yield ('mainnav', 'build', + tag.a('Builds Status', href=req.href.build(), accesskey=5)) # ITemplatesProvider methods @@ -127,43 +111,44 @@ return True def process_request(self, req): - req.perm.assert_permission('BUILD_VIEW') + req.perm.require('BUILD_VIEW') action = req.args.get('action') view = req.args.get('view') config = req.args.get('config') if config: - self._render_config(req, config) + data = self._render_config(req, config) elif view == 'inprogress': - self._render_inprogress(req) + data = self._render_inprogress(req) else: - self._render_overview(req) + data = self._render_overview(req) add_stylesheet(req, 'bitten/bitten.css') - return 'bitten_config.cs', None + return 'bitten_config.html', data, None # Internal methods def _render_overview(self, req): - req.hdf['title'] = 'Build Status' + data = {'title': 'Build Status'} show_all = False if req.args.get('show') == 'all': show_all = True - req.hdf['config.show_all'] = show_all + data['show_all'] = show_all - configs = BuildConfig.select(self.env, include_inactive=show_all) - for idx, config in enumerate(configs): - prefix = 'configs.%d' % idx + configs = [] + for config in BuildConfig.select(self.env, include_inactive=show_all): description = config.description if description: description = wiki_to_html(description, self.env, req) - req.hdf[prefix] = { + config_data = { 'name': config.name, 'label': config.label or config.name, 'active': config.active, 'path': config.path, 'description': description, 'href': req.href.build(config.name), + 'builds': [] } + configs.append(config_data) if not config.active: continue @@ -176,69 +161,74 @@ if rev != prev_rev: if prev_rev is None: chgset = repos.get_changeset(rev) - req.hdf[prefix + '.youngest_rev'] = { + config_data['youngest_rev'] = { 'id': rev, 'href': req.href.changeset(rev), 'author': chgset.author or 'anonymous', 'date': format_datetime(chgset.date), 'message': wiki_to_oneliner( - shorten_line(chgset.message), self.env, req=req - ) + shorten_line(chgset.message), self.env, req=req) } else: break prev_rev = rev if build: - build_hdf = _build_to_hdf(self.env, req, build) - build_hdf['platform'] = platform.name - req.hdf[prefix + '.builds.%d' % platform.id] = build_hdf + build_data = _get_build_data(self.env, req, build) + build_data['platform'] = platform.name + config_data['builds'].append(build_data) else: - req.hdf[prefix + '.builds.%d' % platform.id] = { + config_data['builds'].append({ 'platform': platform.name, 'status': 'pending' - } + }) - req.hdf['page.mode'] = 'overview' + data['configs'] = configs + data['page_mode'] = 'overview' add_link(req, 'views', req.href.build(view='inprogress'), 'In Progress Builds') + add_ctxtnav(req, 'In Progress Builds', + req.href.build(view='inprogress')) + return data def _render_inprogress(self, req): - req.hdf['title'] = 'In Progress Builds' + data = {'title': 'In Progress Builds', + 'page_mode': 'view-inprogress'} db = self.env.get_db_cnx() - configs = BuildConfig.select(self.env, include_inactive=False) - for idx, config in enumerate(configs): + configs = [] + for config in BuildConfig.select(self.env, include_inactive=False): + self.log.debug(config.name) if not config.active: continue in_progress_builds = Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, db=db) - # sort correctly by revision. - builds = list(in_progress_builds) - builds.sort(lambda x, y: int(y.rev) - int(x.rev)) - prefix = 'configs.%d' % idx - current_builds = 0 - for idx2, build in enumerate(builds): - prefix2 = '%s.builds.%d' % (prefix, idx2) + builds = [] + # sort correctly by revision. + for build in sorted(in_progress_builds, + cmp=lambda x, y: int(y.rev) - int(x.rev)): rev = build.rev - req.hdf[prefix2] = _build_to_hdf(self.env, req, build) - req.hdf[prefix2 + '.rev'] = rev - req.hdf[prefix2 + '.rev_href'] = req.href.changeset(rev) + build_data = _get_build_data(self.env, req, build) + build_data['rev'] = rev + build_data['rev_href'] = req.href.changeset(rev) platform = TargetPlatform.fetch(self.env, build.platform) - req.hdf[prefix2 + '.platform'] = platform.name + build_data['platform'] = platform.name + build_data['steps'] = [] for step in BuildStep.select(self.env, build=build.id, db=db): - req.hdf['%s.steps.%s' % (prefix2, step.name)] = { - 'description': step.description, - 'duration': datetime.fromtimestamp(step.stopped) - \ - datetime.fromtimestamp(step.started), - 'failed': not step.successful, - 'errors': step.errors, - 'href': req.hdf[prefix2 + '.href'] + '#step_' + step.name - } + build_data['steps'].append({ + 'name': step.name, + 'description': step.description, + 'duration': datetime.fromtimestamp(step.stopped) - \ + datetime.fromtimestamp(step.started), + 'failed': not step.successful, + 'errors': step.errors, + 'href': build_data['href'] + '#step_' + step.name + }) - current_builds = current_builds + 1 + builds.append(build_data) + current_builds += 1 if current_builds == 0: continue @@ -246,26 +236,29 @@ description = config.description if description: description = wiki_to_html(description, self.env, req) - req.hdf[prefix] = { + configs.append({ 'name': config.name, 'label': config.label or config.name, 'active': config.active, 'path': config.path, 'description': description, 'href': req.href.build(config.name), - } + 'builds': builds + }) - req.hdf['page.mode'] = 'view_inprogress' + data['configs'] = configs + return data def _render_config(self, req, config_name): db = self.env.get_db_cnx() config = BuildConfig.fetch(self.env, config_name, db=db) - req.hdf['title'] = 'Build Configuration "%s"' \ - % config.label or config.name + data = {'title': 'Build Configuration "%s"' \ + % config.label or config.name, + 'page_mode': 'view_config'} add_link(req, 'up', req.href.build(), 'Build Status') description = config.description if description: description = wiki_to_html(description, self.env, req) - req.hdf['config'] = { + data['config'] = { 'name': config.name, 'label': config.label, 'path': config.path, 'min_rev': config.min_rev, 'min_rev_href': req.href.changeset(config.min_rev), @@ -274,12 +267,12 @@ 'active': config.active, 'description': description, 'browser_href': req.href.browser(config.path) } - req.hdf['page.mode'] = 'view_config' platforms = list(TargetPlatform.select(self.env, config=config_name, db=db)) - req.hdf['config.platforms'] = [ - {'name': platform.name, 'id': platform.id} for platform in platforms + data['config']['platforms'] = [ + {'name': platform.name, 'id': platform.id} + for platform in platforms ] has_reports = False @@ -294,14 +287,14 @@ chart_generators.append({ 'href': req.href.build(config.name, 'chart/' + category) }) - req.hdf['config.charts'] = chart_generators + data['config']['charts'] = chart_generators charts_license = self.config.get('bitten', 'charts_license') if charts_license: - req.hdf['config.charts_license'] = charts_license + data['config']['charts_license'] = charts_license page = max(1, int(req.args.get('page', 1))) more = False - req.hdf['page.number'] = page + data['page_number'] = page repos = self.env.get_repository(req.authname) if hasattr(repos, 'sync'): @@ -309,28 +302,31 @@ builds_per_page = 12 * len(platforms) idx = 0 + builds = {} for platform, rev, build in collect_changes(repos, config): if idx >= page * builds_per_page: more = True break elif idx >= (page - 1) * builds_per_page: - prefix = 'config.builds.%s' % rev - req.hdf[prefix + '.href'] = req.href.changeset(rev) + builds.setdefault(rev, {}) + builds[rev].setdefault('href', req.href.changeset(rev)) if build and build.status != Build.PENDING: - build_hdf = _build_to_hdf(self.env, req, build) - req.hdf['%s.%s' % (prefix, platform.id)] = build_hdf + build_data = _get_build_data(self.env, req, build) + build_data['steps'] = [] for step in BuildStep.select(self.env, build=build.id, db=db): - req.hdf['%s.%s.steps.%s' % (prefix, platform.id, - step.name)] = { + build_data['steps'].append({ + 'name': step.name, 'description': step.description, 'duration': datetime.fromtimestamp(step.stopped) - \ datetime.fromtimestamp(step.started), 'failed': not step.successful, 'errors': step.errors, - 'href': build_hdf['href'] + '#step_' + step.name, - } + 'href': build_data['href'] + '#step_' + step.name + }) + builds[rev][platform.id] = build_data idx += 1 + data['config']['builds'] = builds if page > 1: if page == 2: @@ -341,6 +337,8 @@ if more: next_href = req.href.build(config.name, page=page + 1) add_link(req, 'next', next_href, 'Next Page') + prevnext_nav(req, 'Page') + return data class BuildController(Component): @@ -370,7 +368,7 @@ return True def process_request(self, req): - req.perm.assert_permission('BUILD_VIEW') + req.perm.require('BUILD_VIEW') db = self.env.get_db_cnx() build_id = int(req.args.get('id')) @@ -386,11 +384,12 @@ 'Build Configuration') status2title = {Build.SUCCESS: 'Success', Build.FAILURE: 'Failure', Build.IN_PROGRESS: 'In Progress'} - req.hdf['title'] = 'Build %s - %s' % (build_id, - status2title[build.status]) - req.hdf['page.mode'] = 'view_build' + data = {'title': 'Build %s - %s' % (build_id, + status2title[build.status]), + 'page_mode': 'view_build', + 'build': {}} config = BuildConfig.fetch(self.env, build.config, db=db) - req.hdf['build.config'] = { + data['build']['config'] = { 'name': config.label, 'href': req.href.build(config.name) } @@ -404,7 +403,7 @@ categories = summarizer.get_supported_categories() summarizers.update(dict([(cat, summarizer) for cat in categories])) - req.hdf['build'] = _build_to_hdf(self.env, req, build) + data['build'].update(_get_build_data(self.env, req, build)) steps = [] for step in BuildStep.select(self.env, build=build.id, db=db): steps.append({ @@ -416,20 +415,21 @@ 'reports': self._render_reports(req, config, build, summarizers, step) }) - req.hdf['build.steps'] = steps - req.hdf['build.can_delete'] = req.perm.has_permission('BUILD_DELETE') + data['build']['steps'] = steps + data['build']['can_delete'] = ('BUILD_DELETE' in req.perm) repos = self.env.get_repository(req.authname) chgset = repos.get_changeset(build.rev) - req.hdf['build.chgset_author'] = chgset.author + data['build']['chgset_author'] = chgset.author + add_script(req, 'bitten/tabset.js') add_stylesheet(req, 'bitten/bitten.css') - return 'bitten_build.cs', None + return 'bitten_build.html', data, None # ITimelineEventProvider methods def get_timeline_filters(self, req): - if req.perm.has_permission('BUILD_VIEW'): + if 'BUILD_VIEW' in req.perm: yield ('build', 'Builds') def get_timeline_events(self, req, start, stop, filters): @@ -465,9 +465,8 @@ errors += [(step.name, error) for error in step.errors] - title = Markup('Build of %s [%s] on %s %s') % ( - label, rev, platform, _status_label[status] - ) + title = tag('Build of ', tag.em('%s [%s]' % (label, rev)), + ' on %s %s' % (platform, _status_label[status])) message = '' if req.args.get('format') == 'rss': href = req.abs_href.build(config, id) @@ -536,12 +535,12 @@ for report in Report.select(self.env, build=build.id, step=step.name): summarizer = summarizers.get(report.category) if summarizer: - summary = summarizer.render_summary(req, config, build, step, - report.category) + tmpl, data = summarizer.render_summary(req, config, build, + step, report.category) else: - summary = None + tmpl = data = None reports.append({'category': report.category, - 'summary': Markup(summary)}) + 'template': tmpl, 'data': data}) return reports @@ -565,13 +564,13 @@ for generator in self.generators: if category in generator.get_supported_categories(): - template = generator.generate_chart_data(req, config, - category) + tmpl, data = generator.generate_chart_data(req, config, + category) break else: raise TracError('Unknown report category "%s"' % category) - return template, 'text/xml' + return tmpl, data, 'text/xml' class SourceFileLinkFormatter(Component): @@ -609,7 +608,7 @@ link = href(config.path, filepath) if m.group('line'): link += '#L' + m.group('line')[1:] - return Markup(html.A(m.group(0), href=link)) + return Markup(tag.a(m.group(0), href=link)) def _formatter(step, type, level, message): buf = [] diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ package_data = { 'bitten': ['htdocs/*.*', 'htdocs/charts_library/*.swf', - 'templates/*.cs'] + 'templates/*.html'] }, test_suite = 'bitten.tests.suite', entry_points = { @@ -62,6 +62,7 @@ NS + 'c#configure = bitten.build.ctools:configure', NS + 'c#autoreconf = bitten.build.ctools:autoreconf', NS + 'c#cppunit = bitten.build.ctools:cppunit', + NS + 'c#cunit = bitten.build.ctools:cunit', NS + 'c#gcov = bitten.build.ctools:gcov', NS + 'c#make = bitten.build.ctools:make', NS + 'java#ant = bitten.build.javatools:ant', diff --git a/trac-0.11/COPYING b/trac-0.11/COPYING deleted file mode 100644 --- a/trac-0.11/COPYING +++ /dev/null @@ -1,36 +0,0 @@ -Copyright (C) 2007 Edgewall Software -Copyright (C) 2005-2007 Christopher Lenz -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - 3. The name of the author may not be used to endorse or promote - products derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -==================================================================== - -This software includes a copy of XML/SWF charts, which is -Copyright (C) 2004-2005 maani.us -See http://www.maani.us/xml_charts/index.php?menu=License for more -information. diff --git a/trac-0.11/ChangeLog b/trac-0.11/ChangeLog deleted file mode 100644 --- a/trac-0.11/ChangeLog +++ /dev/null @@ -1,71 +0,0 @@ -Version 0.6 -(?, from 0.6.x branch) -http://svn.edgewall.org/repos/bitten/tags/0.6.0 - - * Switch to using HTTP for communication between the build master and build - slaves. This means the `build-master` executable is no longer needed or - installed, the build simply runs in the scope of the Trac site. - * Build recipes now need to include instructions for performing the checkout - from the version control repository. The slave no longer receives a snapshot - archive of the code, but performs the checkout itself based on the - instructions in the build recipe. - * Many fixes for compatibility with more recent versions of Trac. - - -Version 0.5.3 -(18 April 2006, from 0.5.x branch) -http://svn.edgewall.org/repos/bitten/tags/0.5.3 - - * Fix double-escaping of report summaries. - * Fix build master error when build log contains no messages. - - -Version 0.5.2 -(17 January 2006, from 0.5.x branch) -http://svn.edgewall.org/repos/bitten/tags/0.5.2 - - * Fixes the main navigation tab that was broken in 0.5.1. - - -Version 0.5.1 -(10 January 2006, from 0.5.x branch) -http://svn.edgewall.org/repos/bitten/tags/0.5.1 - - * Fixes compatibility with Trac 0.9.3 release, as well as the current trunk. - This also means that Bitten now longer works with versions of Trac earlier - than 0.9.3. - * Improves PostgreSQL compatibility. - * Fixes encoding of non-ASCII characters in command output. - * Fix for missing log output when using on Windows. - - -Version 0.5 -(6 October 2005, from 0.5.x branch) -http://svn.edgewall.org/repos/bitten/tags/0.5 - - * BDB XML is no longer being used for report storage. Instead, - collected metrics data is stored in the Trac database. - * Snapshot archives created by the master are checked for integrity - prior to their transmission to the slaves. - * Improvements to the build status presentation in Trac. - * Changes to the build recipe format. See the documentation on the web - site for details. - * New recipe commands: , , , - , , and . Various improvements to - the existing commands. - * Recipe commands and command attributes in recipes can now reference - slave configuration values. - * The names of the master and slaves scripts have changed: `bittend` - is now `bitten-master`, `bitten` is now `bitten-slave`. - * The build master can now handle multiple Trac environments. - * The build slave now by default removes any working directories when - done. - * Build configurations can now be completely deleted. - * Build configurations can now have a minimum and maximum revision - specified. Any revisions outside that range will not be built. - * The build configuration editor now validates the supplied values. - * Fix management of target platforms when running under mod_python. - * Improved performance of the build log formatter that is responsible - for linking file references in build logs to the repository browser. - * Add paging to the build configuration view. - * Fix compatibility with PySQLite2. diff --git a/trac-0.11/MANIFEST.in b/trac-0.11/MANIFEST.in deleted file mode 100644 --- a/trac-0.11/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include doc/api/*.* -include doc/*.html diff --git a/trac-0.11/README.txt b/trac-0.11/README.txt deleted file mode 100644 --- a/trac-0.11/README.txt +++ /dev/null @@ -1,141 +0,0 @@ -About Bitten -============ - -Bitten is a simple distributed continuous integration system that not only -coordinates builds across multiple machines, but also collects software -metrics generated by builds, to enable feedback and reporting about -the progress of a software project. - -The Bitten software consists of three separate parts: - * The build slave, which executes builds on behalf of a local or remote - build master - * The build master, which orchestrates builds for a project across all - connected slaves, and stores the build status and results to the - database - * The web interface, which is implemented as an add-on to Trac - (http://trac.edgewall.com/) and provides a build management interface - as well as presentation of build results. - -Both the build master and the web interface depend on Trac 0.10, and need -to be installed on the same machine, together with the Subversion -repository. The build slave only requires Python (>= 2.3), setuptools -(>= 0.6a2), as well as any tools required by the build process itself. A -build slave may be run on any machine that can connect to the server -running the Bitten build master. - - -Installation ------------- - -Bitten is written in Python, so make sure that you have Python installed. -You'll need Python 2.3 or later. Also, make sure that setuptools -(http://peak.telecommunity.com/DevCenter/setuptools), version 0.6a2 or later, -is installed. - -If that's taken care of, you just need to download and unpack the Bitten -distribution, and execute the command: - - $ python setup.py install - -from the top of the directory where you unpacked (or checked out) the Bitten -code. Note that you may need administrator/root privileges for this step, as -it will by default attempt to install Bitten to the Python site-packages -directory on your system. - -It's also a good idea to run the unit tests at this point, to make sure that -the code works as expected on your platform: - - $ python setup.py test - - -What's left to do now depends on whether you want to use the build master and -web interface, or just the build slave. In the latter case, you're already -done. You might need to install software that the build of your project -requires, but the Bitten build slave itself doesn't require anything extra. - -For the build master and web interface, you'll need to install Trac 0.10 or -later. Please refer to the Trac documentation for information on how it is -installed. - - -Build Master Configuration --------------------------- - -Once both Bitten and Trac are installed and working, you'll have to introduce -Bitten to your Trac project environment. If you don't have a Trac project -set up yet, you'll need to do so in order to use Bitten. - -If you already have a Trac project environment, the Bitten plugin needs to be -explicitly enabled in the Trac configuration. This is done by adding it to the -[components] section in /path/to/projenv/conf/trac.ini: - - [components] - bitten.* = enabled - -The Trac web interface should now inform you with an error message that the -environment needs to be upgraded. To do this, run: - - $ trac-admin /path/to/projenv upgrade - -This will create the database tables and directories that Bitten requires. -You probably also want to grant permissions to someone (such as yourself) -to manage build configurations, and allow anonymous users to view the -status and results of builds: - - $ trac-admin /path/to/projenv permission add anonymous BUILD_EXEC - $ trac-admin /path/to/projenv permission add anonymous BUILD_VIEW - $ trac-admin /path/to/projenv permission add [yourname] BUILD_ADMIN - -You should now see an additional tab labeled "Build Status" in the Trac -navigation bar. This link will take you to the list of build configurations, -which at this point is of course empty. If you've set up permissions -correctly as described previously, you should see a button for adding new -build configurations. Click that button and fill out the form. Also, add -at least one target platform after saving the configuration. Last but not -least, you'll have to "activate" the build configuration. - - -Running the Build Master ------------------------- - -At this point, you're ready to start the Bitten build master. The -installation of Bitten should have put a `bitten-master` executable on your -path. If the script is not on your path, look for it in the `bin` or -`scripts` subdirectory of your Python installation. - -To find out about the options and arguments of the master, execute it with -the `--help` option as follows: - - $ bitten-master --help - -Most commonly, you'll want to specify the log level and log file, as well as -the path to the Trac environment: - - $ bitten-master --verbose --log=/var/log/bittend /var/trac/myproject - - -Running the Build Slave ------------------------ - -The build slave can be run on any machine that can connect to the machine -on which the build master is running. The installation of Bitten should have put -a `bitten-slave` executable on your path. If the script is not on your path, -look for it in the `bin` or `scripts` subdirectory of your Python installation. - -To get a list of options for the build slave, execute it with the `--help` -option: - - $ bitten-slave --help - -To run the build slave against a Bitten-enabled Trac site installed at -http://myproject.example.org/trac, you'd run: - - $ bitten-slave http://myproject.example.org/trac/builds - - -More Information ----------------- - -For further documentation, please see the Bitten website at: - - diff --git a/trac-0.11/bitten/__init__.py b/trac-0.11/bitten/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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. - -__docformat__ = 'restructuredtext en' -try: - __version__ = __import__('pkg_resources').get_distribution('Bitten').version -except ImportError: - pass diff --git a/trac-0.11/bitten/admin.py b/trac-0.11/bitten/admin.py deleted file mode 100644 --- a/trac-0.11/bitten/admin.py +++ /dev/null @@ -1,343 +0,0 @@ -# -*- 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. - -"""Implementation of the web administration interface.""" - -from pkg_resources import require, DistributionNotFound -import re - -from trac.core import * -from trac.admin import IAdminPanelProvider -from trac.web.chrome import add_stylesheet - -from bitten.model import BuildConfig, TargetPlatform -from bitten.recipe import Recipe, InvalidRecipeError -from bitten.util import xmlio - - -class BuildMasterAdminPageProvider(Component): - """Web administration panel for configuring the build master.""" - - implements(IAdminPanelProvider) - - # IAdminPanelProvider methods - - def get_admin_panels(self, req): - if req.perm.has_permission('BUILD_ADMIN'): - yield ('bitten', 'Builds', 'master', 'Master Settings') - - def render_admin_panel(self, req, cat, page, path_info): - from bitten.master import BuildMaster - master = BuildMaster(self.env) - - if req.method == 'POST': - self._save_config_changes(req, master) - req.redirect(req.abs_href.admin(cat, page)) - - data = {'master': master} - add_stylesheet(req, 'bitten/admin.css') - return 'bitten_admin_master.html', data - - # Internal methods - - def _save_config_changes(self, req, master): - changed = False - - build_all = 'build_all' in req.args - if build_all != master.build_all: - self.config['bitten'].set('build_all', - build_all and 'yes' or 'no') - changed = True - - adjust_timestamps = 'adjust_timestamps' in req.args - if adjust_timestamps != master.adjust_timestamps: - self.config['bitten'].set('adjust_timestamps', - adjust_timestamps and 'yes' or 'no') - changed = True - - stabilize_wait = int(req.args.get('stabilize_wait', 0)) - if stabilize_wait != master.stabilize_wait: - self.config['bitten'].set('stabilize_wait', str(stabilize_wait)) - changed = True - - slave_timeout = int(req.args.get('slave_timeout', 0)) - if slave_timeout != master.slave_timeout: - self.config['bitten'].set('slave_timeout', str(slave_timeout)) - changed = True - - if changed: - self.config.save() - - return master - - -class BuildConfigurationsAdminPageProvider(Component): - """Web administration panel for configuring the build master.""" - - implements(IAdminPanelProvider) - - # IAdminPanelProvider methods - - def get_admin_panels(self, req): - if req.perm.has_permission('BUILD_MODIFY'): - yield ('bitten', 'Builds', 'configs', 'Configurations') - - def render_admin_panel(self, req, cat, page, path_info): - data = {} - - # Analyze url - try: - config_name, platform_id = path_info.split('/', 1) - except: - config_name = path_info - platform_id = None - - if config_name: # Existing build config - if platform_id or ( - # Editing or creating one of the config's target platforms - req.method == 'POST' and 'new' in req.args): - - if platform_id: # Editing target platform - platform_id = int(platform_id) - platform = TargetPlatform.fetch(self.env, platform_id) - - if req.method == 'POST': - if 'cancel' in req.args or \ - self._update_platform(req, platform): - req.redirect(req.abs_href.admin(cat, page, - config_name)) - else: # creating target platform - if req.method == 'POST': - if 'add' in req.args: - self._create_platform(req, config_name) - req.redirect(req.abs_href.admin(cat, page, - config_name)) - elif 'cancel' in req.args: - req.redirect(req.abs_href.admin(cat, page, - config_name)) - - platform = TargetPlatform(self.env, config=config_name) - - # Set up template variables - data['platform'] = { - 'id': platform.id, 'name': platform.name, - 'exists': platform.exists, - 'rules': [ - {'property': propname, 'pattern': pattern} - for propname, pattern in platform.rules - ] or [('', '')] - } - - else: # Editing existing build config itself - config = BuildConfig.fetch(self.env, config_name) - platforms = list(TargetPlatform.select(self.env, - config=config.name)) - - if req.method == 'POST': - if 'add' in req.args: # Add target platform - platform = self._create_platform(req, config) - req.redirect(req.abs_href.admin(cat, page, config.name)) - - elif 'remove' in req.args: # Remove selected platforms - self._remove_platforms(req) - req.redirect(req.abs_href.admin(cat, page, config.name)) - - elif 'save' in req.args: # Save this build config - self._update_config(req, config) - - req.redirect(req.abs_href.admin(cat, page)) - - # Prepare template variables - data['config'] = { - 'name': config.name, 'label': config.label or config.name, - 'active': config.active, 'path': config.path, - 'min_rev': config.min_rev, 'max_rev': config.max_rev, - 'description': config.description, - 'recipe': config.recipe, - 'platforms': [{ - 'name': platform.name, - 'id': platform.id, - 'href': req.href.admin('bitten', 'configs', config.name, - platform.id), - 'rules': [{'property': propname, 'pattern': pattern} - for propname, pattern in platform.rules] - } for platform in platforms] - } - - else: # At the top level build config list - if req.method == 'POST': - if 'add' in req.args: # Add build config - config = self._create_config(req) - req.redirect(req.abs_href.admin(cat, page, config.name)) - - elif 'remove' in req.args: # Remove selected build configs - self._remove_configs(req) - - elif 'apply' in req.args: # Update active state of configs - self._activate_configs(req) - req.redirect(req.abs_href.admin(cat, page)) - - # Prepare template variables - configs = [] - for config in BuildConfig.select(self.env, include_inactive=True): - configs.append({ - 'name': config.name, 'label': config.label or config.name, - 'active': config.active, 'path': config.path, - 'min_rev': config.min_rev, 'max_rev': config.max_rev, - 'href': req.href.admin('bitten', 'configs', config.name), - }) - data['configs'] = configs - - add_stylesheet(req, 'bitten/admin.css') - return 'bitten_admin_configs.html', data - - # Internal methods - - def _activate_configs(self, req): - req.perm.assert_permission('BUILD_MODIFY') - - active = req.args.get('active') or [] - active = isinstance(active, list) and active or [active] - - db = self.env.get_db_cnx() - for config in list(BuildConfig.select(self.env, db=db, - include_inactive=True)): - config.active = config.name in active - config.update(db=db) - db.commit() - - def _create_config(self, req): - req.perm.assert_permission('BUILD_CREATE') - - config = BuildConfig(self.env) - self._update_config(req, config) - return config - - def _remove_configs(self, req): - req.perm.assert_permission('BUILD_DELETE') - - sel = req.args.get('sel') - if not sel: - raise TracError('No configuration selected') - sel = isinstance(sel, list) and sel or [sel] - - db = self.env.get_db_cnx() - for name in sel: - config = BuildConfig.fetch(self.env, name, db=db) - if not config: - raise TracError('Configuration %r not found' % name) - config.delete(db=db) - db.commit() - - def _update_config(self, req, config): - req.perm.assert_permission('BUILD_MODIFY') - - name = req.args.get('name') - if not name: - raise TracError('Missing required field "name"', 'Missing Field') - if not re.match(r'^[\w.-]+$', name): - raise TracError('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', 'Invalid Field') - - path = req.args.get('path', '') - repos = self.env.get_repository(req.authname) - max_rev = req.args.get('max_rev') or None - try: - node = repos.get_node(path, max_rev) - assert node.isdir, '%s is not a directory' % node.path - except (AssertionError, TracError), e: - raise TracError(unicode(e), 'Invalid Repository Path') - if req.args.get('min_rev'): - try: - repos.get_node(path, req.args.get('min_rev')) - except TracError, e: - raise TracError(unicode(e), 'Invalid Oldest Revision') - - recipe_xml = req.args.get('recipe', '') - if recipe_xml: - try: - Recipe(xmlio.parse(recipe_xml)).validate() - except xmlio.ParseError, e: - raise TracError('Failure parsing recipe: %s' % e, - 'Invalid Recipe') - except InvalidRecipeError, e: - raise TracError(unicode(e), 'Invalid Recipe') - - config.name = name - config.path = repos.normalize_path(path) - config.recipe = recipe_xml - config.min_rev = req.args.get('min_rev') - config.max_rev = req.args.get('max_rev') - config.label = req.args.get('label', config.name) - config.description = req.args.get('description', '') - - if config.exists: - config.update() - else: - config.insert() - - def _create_platform(self, req, config_name): - req.perm.assert_permission('BUILD_MODIFY') - - name = req.args.get('name') - if not name: - raise TracError('Missing required field "name"', 'Missing field') - - platform = TargetPlatform(self.env, config=config_name, name=name) - self._update_platform(req, platform) - return platform - - def _remove_platforms(self, req): - req.perm.assert_permission('BUILD_MODIFY') - - sel = req.args.get('sel') - if not sel: - raise TracError('No platform selected') - sel = isinstance(sel, list) and sel or [sel] - - db = self.env.get_db_cnx() - for platform_id in sel: - platform = TargetPlatform.fetch(self.env, platform_id, db=db) - if not platform: - raise TracError('Target platform %r not found' % platform_id) - platform.delete(db=db) - db.commit() - - def _update_platform(self, req, platform): - platform.name = req.args.get('name') - - properties = [int(key[9:]) for key in req.args.keys() - if key.startswith('property_')] - properties.sort() - patterns = [int(key[8:]) for key in req.args.keys() - if key.startswith('pattern_')] - patterns.sort() - platform.rules = [(req.args.get('property_%d' % property), - req.args.get('pattern_%d' % pattern)) - for property, pattern in zip(properties, patterns) - if req.args.get('property_%d' % property)] - - if platform.exists: - platform.update() - else: - platform.insert() - - add_rules = [int(key[9:]) for key in req.args.keys() - if key.startswith('add_rule_')] - if add_rules: - platform.rules.insert(add_rules[0] + 1, ('', '')) - return False - rm_rules = [int(key[8:]) for key in req.args.keys() - if key.startswith('rm_rule_')] - if rm_rules: - if rm_rules[0] < len(platform.rules): - del platform.rules[rm_rules[0]] - return False - - return True diff --git a/trac-0.11/bitten/api.py b/trac-0.11/bitten/api.py deleted file mode 100644 --- a/trac-0.11/bitten/api.py +++ /dev/null @@ -1,120 +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. - -"""Interfaces of extension points provided by the Bitten Trac plugin.""" - -from trac.core import * - -__all__ = ['IBuildListener', 'ILogFormatter', 'IReportChartGenerator', - 'IReportSummarizer'] -__docformat__ = 'restructuredtext en' - - -class IBuildListener(Interface): - """Extension point interface for components that need to be notified of - build events. - - Note that these will be notified in the process running the build master, - not the web interface. - """ - - def build_started(build): - """Called when a build slave has accepted a build initiation. - - :param build: the build that was started - :type build: `Build` - """ - - def build_aborted(build): - """Called when a build slave cancels a build or disconnects. - - :param build: the build that was aborted - :type build: `Build` - """ - - def build_completed(build): - """Called when a build slave has completed a build, regardless of the - outcome. - - :param build: the build that was aborted - :type build: `Build` - """ - - -class ILogFormatter(Interface): - """Extension point interface for components that format build log - messages.""" - - def get_formatter(req, build): - """Return a function that gets called for every log message. - - The function must take four positional arguments, ``step``, - ``generator``, ``level`` and ``message``, and return the formatted - message as a string. - - :param req: the request object - :param build: the build to which the logs belong that should be - formatted - :type build: `Build` - :return: the formatted log message - :rtype: `basestring` - """ - - -class IReportSummarizer(Interface): - """Extension point interface for components that render a summary of reports - of some kind.""" - - def get_supported_categories(): - """Return a list of strings identifying the types of reports this - component supports. - """ - - def render_summary(req, config, build, step, category): - """Render a summary for the given report. - - This function should return a tuple of the form `(template, data)`, - where `template` is the name of the template to use and `data` is the - data to be passed to the template. - - :param req: the request object - :param config: the build configuration - :type config: `BuildConfig` - :param build: the build - :type build: `Build` - :param step: the build step - :type step: `BuildStep` - :param category: the category of the report that should be summarized - :type category: `basestring` - """ - - -class IReportChartGenerator(Interface): - """Extension point interface for components that generate a chart for a - set of reports.""" - - def get_supported_categories(): - """Return a list of strings identifying the types of reports this - component supports. - """ - - def generate_chart_data(req, config, category): - """Generate the data for a report chart. - - This function should return a tuple of the form `(template, data)`, - where `template` is the name of the template to use and `data` is the - data to be passed to the template. - - :param req: the request object - :param config: the build configuration - :type config: `BuildConfig` - :param category: the category of reports to include in the chart - :type category: `basestring` - """ diff --git a/trac-0.11/bitten/build/__init__.py b/trac-0.11/bitten/build/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/build/__init__.py +++ /dev/null @@ -1,13 +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 bitten.build.api import * - -__docformat__ = 'restructuredtext en' diff --git a/trac-0.11/bitten/build/api.py b/trac-0.11/bitten/build/api.py deleted file mode 100644 --- a/trac-0.11/bitten/build/api.py +++ /dev/null @@ -1,290 +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. - -"""Functions and classes used to simplify the implementation recipe commands.""" - -import logging -import fnmatch -import os -import shlex -import time - -log = logging.getLogger('bitten.build.api') - -__docformat__ = 'restructuredtext en' - - -class BuildError(Exception): - """Exception raised when a build fails.""" - - -class TimeoutError(Exception): - """Exception raised when the execution of a command times out.""" - - -def _combine(*iterables): - iterables = [iter(iterable) for iterable in iterables] - size = len(iterables) - while True: - to_yield = [None] * size - for idx, iterable in enumerate(iterables): - if iterable is None: - continue - try: - to_yield[idx] = iterable.next() - except StopIteration: - iterables[idx] = None - if not [iterable for iterable in iterables if iterable is not None]: - break - yield tuple(to_yield) - - -class CommandLine(object): - """Simple helper for executing subprocesses.""" - - def __init__(self, executable, args, input=None, cwd=None): - """Initialize the CommandLine object. - - :param executable: the name of the program to execute - :param args: a list of arguments to pass to the executable - :param input: string or file-like object containing any input data for - the program - :param cwd: the working directory to change to before executing the - command - """ - self.executable = executable - self.arguments = [str(arg) for arg in args] - self.input = input - self.cwd = cwd - if self.cwd: - assert os.path.isdir(self.cwd) - self.returncode = None - - if os.name == 'nt': # windows - - def execute(self, timeout=None): - """Execute the command, and return a generator for iterating over - the output written to the standard output and error streams. - - :param timeout: number of seconds before the external process - should be aborted (not supported on Windows) - """ - args = [self.executable] + self.arguments - for idx, arg in enumerate(args): - if arg.find(' ') >= 0: - args[idx] = '"%s"' % arg - log.debug('Executing %s', args) - - if self.cwd: - old_cwd = os.getcwd() - os.chdir(self.cwd) - - import tempfile - in_name = None - if self.input: - if isinstance(self.input, basestring): - in_file, in_name = tempfile.mkstemp(prefix='bitten_', - suffix='.pipe') - os.write(in_file, self.input) - os.close(in_file) - in_redirect = '< "%s" ' % in_name - else: - in_redirect = '< "%s" ' % self.input.name - else: - in_redirect = '' - - out_file, out_name = tempfile.mkstemp(prefix='bitten_', - suffix='.pipe') - os.close(out_file) - err_file, err_name = tempfile.mkstemp(prefix='bitten_', - suffix='.pipe') - os.close(err_file) - - try: - cmd = '( %s ) > "%s" %s 2> "%s"' % (' '.join(args), out_name, - in_redirect, err_name) - self.returncode = os.system(cmd) - log.debug('Exited with code %s', self.returncode) - - out_file = file(out_name, 'r') - err_file = file(err_name, 'r') - out_lines = out_file.readlines() - err_lines = err_file.readlines() - out_file.close() - err_file.close() - finally: - if in_name: - os.unlink(in_name) - if out_name: - os.unlink(out_name) - if err_name: - os.unlink(err_name) - if self.cwd: - os.chdir(old_cwd) - - for out_line, err_line in _combine(out_lines, err_lines): - yield out_line and out_line.rstrip().replace('\x00', ''), \ - err_line and err_line.rstrip().replace('\x00', '') - - else: # posix - - def execute(self, timeout=None): - """Execute the command, and return a generator for iterating over - the output written to the standard output and error streams. - - :param timeout: number of seconds before the external process - should be aborted (not supported on Windows) - """ - import popen2, select - if self.cwd: - old_cwd = os.getcwd() - os.chdir(self.cwd) - - log.debug('Executing %s', [self.executable] + self.arguments) - pipe = popen2.Popen3([self.executable] + self.arguments, - capturestderr=True) - if self.input: - if isinstance(self.input, basestring): - in_data = self.input - else: - in_data = self.input.read() - else: - pipe.tochild.close() - in_data = '' - - out_data, err_data = [], [] - in_eof = out_eof = err_eof = False - if not in_data: - in_eof = True - while not out_eof or not err_eof: - readable = [pipe.fromchild] * (not out_eof) + \ - [pipe.childerr] * (not err_eof) - writable = [pipe.tochild] * (not in_eof) - ready = select.select(readable, writable, [], timeout) - if not (ready[0] or ready[1]): - raise TimeoutError('Command %s timed out' % self.executable) - if pipe.tochild in ready[1]: - sent = os.write(pipe.tochild.fileno(), in_data) - in_data = in_data[sent:] - if not in_data: - pipe.tochild.close() - in_eof = True - if pipe.fromchild in ready[0]: - data = os.read(pipe.fromchild.fileno(), 1024) - if data: - out_data.append(data) - else: - out_eof = True - if pipe.childerr in ready[0]: - data = os.read(pipe.childerr.fileno(), 1024) - if data: - err_data.append(data) - else: - err_eof = True - out_lines = self._extract_lines(out_data) - err_lines = self._extract_lines(err_data) - for out_line, err_line in _combine(out_lines, err_lines): - yield out_line, err_line - time.sleep(.1) - self.returncode = pipe.wait() - log.debug('%s exited with code %s', self.executable, - self.returncode) - - if self.cwd: - os.chdir(old_cwd) - - def _extract_lines(self, data): - extracted = [] - def _endswith_linesep(string): - for linesep in ('\n', '\r\n', '\r'): - if string.endswith(linesep): - return True - buf = ''.join(data) - lines = buf.splitlines(True) - if len(lines) > 1: - extracted += lines[:-1] - if _endswith_linesep(lines[-1]): - extracted.append(lines[-1]) - buf = '' - else: - buf = lines[-1] - elif _endswith_linesep(buf): - extracted.append(buf) - buf = '' - data[:] = [buf] * bool(buf) - - return [line.rstrip() for line in extracted] - - -class FileSet(object): - """Utility class for collecting a list of files in a directory that match - given name/path patterns.""" - - DEFAULT_EXCLUDES = ['CVS/*', '*/CVS/*', '.svn/*', '*/.svn/*', - '.DS_Store', 'Thumbs.db'] - - def __init__(self, basedir, include=None, exclude=None): - """Create a file set. - - :param basedir: the base directory for all files in the set - :param include: a list of patterns that define which files should be - included in the set - :param exclude: a list of patterns that define which files should be - excluded from the set - """ - self.files = [] - self.basedir = basedir - - self.include = [] - if include is not None: - self.include = shlex.split(include) - - self.exclude = self.DEFAULT_EXCLUDES[:] - if exclude is not None: - self.exclude += shlex.split(exclude) - - for dirpath, dirnames, filenames in os.walk(self.basedir): - dirpath = dirpath[len(self.basedir) + 1:] - - for filename in filenames: - filepath = nfilepath = os.path.join(dirpath, filename) - if os.sep != '/': - nfilepath = nfilepath.replace(os.sep, '/') - - if self.include: - included = False - for pattern in self.include: - if fnmatch.fnmatchcase(nfilepath, pattern) or \ - fnmatch.fnmatchcase(filename, pattern): - included = True - break - if not included: - continue - - excluded = False - for pattern in self.exclude: - if fnmatch.fnmatchcase(nfilepath, pattern) or \ - fnmatch.fnmatchcase(filename, pattern): - excluded = True - break - if not excluded: - self.files.append(filepath) - - def __iter__(self): - """Iterate over the names of all files in the set.""" - for filename in self.files: - yield filename - - def __contains__(self, filename): - """Return whether the given file name is in the set. - - :param filename: the name of the file to check - """ - return filename in self.files diff --git a/trac-0.11/bitten/build/config.py b/trac-0.11/bitten/build/config.py deleted file mode 100644 --- a/trac-0.11/bitten/build/config.py +++ /dev/null @@ -1,178 +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. - -"""Support for build slave configuration.""" - -from ConfigParser import SafeConfigParser -import logging -import os -import platform -import re - -log = logging.getLogger('bitten.config') - -__docformat__ = 'restructuredtext en' - - -class Configuration(object): - """Encapsulates the configuration of a build machine. - - Configuration values can be provided through a configuration file (in INI - format) or through command-line parameters (properties). In addition to - explicitly defined properties, this class automatically collects platform - information and stores them as properties. These defaults can be - overridden (useful for cross-compilation). - """ - # TODO: document mapping from config file to property names - - def __init__(self, filename=None, properties=None): - """Create the configuration object. - - :param filename: the path to the configuration file, if any - :param properties: a dictionary of the configuration properties - provided on the command-line - """ - self.properties = {} - self.packages = {} - parser = SafeConfigParser() - if filename: - parser.read(filename) - self._merge_sysinfo(parser, properties) - self._merge_packages(parser, properties) - - def _merge_sysinfo(self, parser, properties): - """Merge the platform information properties into the configuration.""" - system, _, release, version, machine, processor = platform.uname() - system, release, version = platform.system_alias(system, release, - version) - self.properties['machine'] = machine - self.properties['processor'] = processor - self.properties['os'] = system - self.properties['family'] = os.name - self.properties['version'] = release - - mapping = {'machine': ('machine', 'name'), - 'processor': ('machine', 'processor'), - 'os': ('os', 'name'), - 'family': ('os', 'family'), - 'version': ('os', 'version')} - for key, (section, option) in mapping.items(): - if parser.has_section(section): - value = parser.get(section, option) - if value is not None: - self.properties[key] = value - - if properties: - for key, value in properties.items(): - if key in mapping: - self.properties[key] = value - - def _merge_packages(self, parser, properties): - """Merge package information into the configuration.""" - for section in parser.sections(): - if section in ('os', 'machine', 'maintainer'): - continue - package = {} - for option in parser.options(section): - package[option] = parser.get(section, option) - self.packages[section] = package - - if properties: - for key, value in properties.items(): - if '.' in key: - package, propname = key.split('.', 1) - if package not in self.packages: - self.packages[package] = {} - self.packages[package][propname] = value - - def __contains__(self, key): - """Return whether the configuration contains a value for the specified - key. - - :param key: name of the configuration option using dotted notation - (for example, "python.path") - """ - if '.' in key: - package, propname = key.split('.', 1) - return propname in self.packages.get(package, {}) - return key in self.properties - - def __getitem__(self, key): - """Return the value for the specified configuration key. - - :param key: name of the configuration option using dotted notation - (for example, "python.path") - """ - if '.' in key: - package, propname = key.split('.', 1) - return self.packages.get(package, {}).get(propname) - return self.properties.get(key) - - def __str__(self): - return str({'properties': self.properties, 'packages': self.packages}) - - def get_dirpath(self, key): - """Return the value of the specified configuration key, but verify that - the value refers to the path of an existing directory. - - If the value does not exist, or is not a directory path, return `None`. - - :param key: name of the configuration option using dotted notation - (for example, "ant.home") - """ - dirpath = self[key] - if dirpath: - if os.path.isdir(dirpath): - return dirpath - log.warning('Invalid %s: %s is not a directory', key, dirpath) - return None - - def get_filepath(self, key): - """Return the value of the specified configuration key, but verify that - the value refers to the path of an existing file. - - If the value does not exist, or is not a file path, return `None`. - - :param key: name of the configuration option using dotted notation - (for example, "python.path") - """ - filepath = self[key] - if filepath: - if os.path.isfile(filepath): - return filepath - log.warning('Invalid %s: %s is not a file', key, filepath) - return None - - _VAR_RE = re.compile(r'\$\{(?P\w[\w.]*?\w)(?:\:(?P.+))?\}') - - def interpolate(self, text, **vars): - """Interpolate configuration properties into a string. - - Properties can be referenced in the text using the notation - ``${property.name}``. A default value can be provided by appending it to - the property name separated by a colon, for example - ``${property.name:defaultvalue}``. This value will be used when there's - no such property in the configuration. Otherwise, if no default is - provided, the reference is not replaced at all. - - :param text: the string containing variable references - :param vars: extra variables to use for the interpolation - """ - def _replace(m): - refname = m.group('ref') - if refname in self: - return self[refname] - elif refname in vars: - return vars[refname] - elif m.group('def'): - return m.group('def') - else: - return m.group(0) - return self._VAR_RE.sub(_replace, text) diff --git a/trac-0.11/bitten/build/ctools.py b/trac-0.11/bitten/build/ctools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/ctools.py +++ /dev/null @@ -1,365 +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. - -"""Recipe commands for build tasks commonly used for C/C++ projects.""" - -import logging -import re -import os -import posixpath -import shlex - -from bitten.build import CommandLine, FileSet -from bitten.util import xmlio - -log = logging.getLogger('bitten.build.ctools') - -__docformat__ = 'restructuredtext en' - -def configure(ctxt, file_='configure', enable=None, disable=None, with=None, - without=None, cflags=None, cxxflags=None): - """Run a ``configure`` script. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the configure script - :param enable: names of the features to enable, seperated by spaces - :param disable: names of the features to disable, separated by spaces - :param with: names of external packages to include - :param without: names of external packages to exclude - :param cflags: ``CFLAGS`` to pass to the configure script - :param cxxflags: ``CXXFLAGS`` to pass to the configure script - """ - args = [] - if enable: - args += ['--enable-%s' % feature for feature in enable.split()] - if disable: - args += ['--disable-%s' % feature for feature in disable.split()] - if with: - for pkg in with.split(): - pkg_path = pkg + '.path' - if pkg_path in ctxt.config: - args.append('--with-%s=%s' % (pkg, ctxt.config[pkg_path])) - else: - args.append('--with-%s' % pkg) - if without: - args += ['--without-%s' % pkg for pkg in without.split()] - if cflags: - args.append('CFLAGS=%s' % cflags) - if cxxflags: - args.append('CXXFLAGS=%s' % cxxflags) - - from bitten.build import shtools - returncode = shtools.execute(ctxt, file_=file_, args=args) - if returncode != 0: - ctxt.error('configure failed (%s)' % returncode) - -def autoreconf(ctxt, file_='configure', force=None, install=None, symlink=None, - warnings=None, prepend_include=None, include =None): - """Run the autotoll ``autoreconf``. - - :param ctxt: the build context - :type ctxt: `Context` - :param force: consider all files obsolete - :param install: copy missing auxiliary files - :param symlink: install symbolic links instead of copies - :param warnings: report the warnings falling in CATEGORY - :prepend_include: prepend directories to search path - :include: append directories to search path - - """ - args = [] - if install: - args.append('--install') - if symlink: - args.append('--symlink') - if force: - args.append('--force') - if warnings: - args.append('--warnings=%s' % warnings) - - if include: - args += ['--include=%s' % inc for inc in include.split()] - if prepend_include: - args += ['--prepend-include=%s' % pinc for pinc in prepend_include.split()] - - from bitten.build import shtools - returncode = shtools.execute(ctxt, 'autoreconf', args=args) - if returncode != 0: - ctxt.error('autoreconf failed (%s)' % returncode) - -def make(ctxt, target=None, file_=None, keep_going=False, directory=None, jobs=None, args=None): - """Execute a Makefile target. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the Makefile - :param keep_going: whether make should keep going when errors are - encountered - :param directory: directory in which to build; defaults to project source directory - :param jobs: number of concurrent jobs to run - :param args: command-line arguments to pass to the script - """ - executable = ctxt.config.get_filepath('make.path') or 'make' - - if directory is None: - directory = ctxt.basedir - - margs = ['--directory', directory] - - if file_: - margs += ['--file', ctxt.resolve(file_)] - if keep_going: - margs.append('--keep-going') - if target: - margs.append(target) - if jobs: - margs += ['--jobs', jobs] - - if args: - if isinstance(args, basestring): - margs += shlex.split(args) - - from bitten.build import shtools - returncode = shtools.execute(ctxt, executable=executable, args=margs) - if returncode != 0: - ctxt.error('make failed (%s)' % returncode) - -def cppunit(ctxt, file_=None, srcdir=None): - """Collect CppUnit XML data. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: path of the file containing the CppUnit results; may contain - globbing wildcards to match multiple files - :param srcdir: name of the directory containing the source files, used to - link the test results to the corresponding files - """ - assert file_, 'Missing required attribute "file"' - - try: - fileobj = file(ctxt.resolve(file_), 'r') - try: - total, failed = 0, 0 - results = xmlio.Fragment() - for group in xmlio.parse(fileobj): - if group.name not in ('FailedTests', 'SuccessfulTests'): - continue - for child in group.children(): - test = xmlio.Element('test') - name = child.children('Name').next().gettext() - if '::' in name: - parts = name.split('::') - test.attr['fixture'] = '::'.join(parts[:-1]) - name = parts[-1] - test.attr['name'] = name - - for location in child.children('Location'): - for file_elem in location.children('File'): - filepath = file_elem.gettext() - if srcdir is not None: - filepath = posixpath.join(srcdir, filepath) - test.attr['file'] = filepath - break - for line_elem in location.children('Line'): - test.attr['line'] = line_elem.gettext() - break - break - - if child.name == 'FailedTest': - for message in child.children('Message'): - test.append(xmlio.Element('traceback')[ - message.gettext() - ]) - test.attr['status'] = 'failure' - failed += 1 - else: - test.attr['status'] = 'success' - - results.append(test) - total += 1 - - if failed: - ctxt.error('%d of %d test%s failed' % (failed, total, - total != 1 and 's' or '')) - - ctxt.report('test', results) - - finally: - fileobj.close() - - except IOError, e: - log.warning('Error opening CppUnit results file (%s)', e) - except xmlio.ParseError, e: - print e - log.warning('Error parsing CppUnit results file (%s)', e) - -def cunit (ctxt, file_=None, srcdir=None): - """Collect CUnit XML data. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: path of the file containing the CUnit results; may contain - globbing wildcards to match multiple files - :param srcdir: name of the directory containing the source files, used to - link the test results to the corresponding files - """ - assert file_, 'Missing required attribute "file"' - - try: - fileobj = file(ctxt.resolve(file_), 'r') - try: - total, failed = 0, 0 - results = xmlio.Fragment() - log_elem = xmlio.Fragment() - def info (msg): - log.info (msg) - log_elem.append (xmlio.Element ('message', level='info')[msg]) - def warning (msg): - log.warning (msg) - log_elem.append (xmlio.Element ('message', level='warning')[msg]) - def error (msg): - log.error (msg) - log_elem.append (xmlio.Element ('message', level='error')[msg]) - for node in xmlio.parse(fileobj): - if node.name != 'CUNIT_RESULT_LISTING': - continue - for suiteRun in node.children ('CUNIT_RUN_SUITE'): - for suite in suiteRun.children(): - if suite.name not in ('CUNIT_RUN_SUITE_SUCCESS', 'CUNIT_RUN_SUITE_FAILURE'): - warning ("Unknown node: %s" % suite.name) - continue - suiteName = suite.children ('SUITE_NAME').next().gettext() - info ("%s [%s]" % ("*" * (57 - len (suiteName)), suiteName)) - for record in suite.children ('CUNIT_RUN_TEST_RECORD'): - for result in record.children(): - if result.name not in ('CUNIT_RUN_TEST_SUCCESS', 'CUNIT_RUN_TEST_FAILURE'): - continue - testName = result.children ('TEST_NAME').next().gettext() - info ("Running %s..." % testName); - test = xmlio.Element('test') - test.attr['fixture'] = suiteName - test.attr['name'] = testName - if result.name == 'CUNIT_RUN_TEST_FAILURE': - error ("%s(%d): %s" - % (result.children ('FILE_NAME').next().gettext(), - int (result.children ('LINE_NUMBER').next().gettext()), - result.children ('CONDITION').next().gettext())) - test.attr['status'] = 'failure' - failed += 1 - else: - test.attr['status'] = 'success' - - results.append(test) - total += 1 - - if failed: - ctxt.error('%d of %d test%s failed' % (failed, total, - total != 1 and 's' or '')) - - ctxt.report('test', results) - ctxt.log (log_elem) - - finally: - fileobj.close() - - except IOError, e: - log.warning('Error opening CUnit results file (%s)', e) - except xmlio.ParseError, e: - print e - log.warning('Error parsing CUnit results file (%s)', e) - -def gcov(ctxt, include=None, exclude=None, prefix=None, root=""): - """Run ``gcov`` to extract coverage data where available. - - :param ctxt: the build context - :type ctxt: `Context` - :param include: patterns of files and directories to include - :param exclude: patterns of files and directories that should be excluded - :param prefix: optional prefix name that is added to object files by the - build system - :param root: optional root path in which the build system puts the object - files - """ - file_re = re.compile(r'^File (?:\'|\`)(?P[^\']+)\'\s*$') - lines_re = re.compile(r'^Lines executed:(?P\d+\.\d+)\% of (?P\d+)\s*$') - - files = [] - for filename in FileSet(ctxt.basedir, include, exclude): - if os.path.splitext(filename)[1] in ('.c', '.cpp', '.cc', '.cxx'): - files.append(filename) - - coverage = xmlio.Fragment() - log_elem = xmlio.Fragment() - def info (msg): - log.info (msg) - log_elem.append (xmlio.Element ('message', level='info')[msg]) - def warning (msg): - log.warning (msg) - log_elem.append (xmlio.Element ('message', level='warning')[msg]) - def error (msg): - log.error (msg) - log_elem.append (xmlio.Element ('message', level='error')[msg]) - - for srcfile in files: - # Determine the coverage for each source file by looking for a .gcno - # and .gcda pair - info ("Getting coverage info for %s" % srcfile) - filepath, filename = os.path.split(srcfile) - stem = os.path.splitext(filename)[0] - if prefix is not None: - stem = prefix + '-' + stem - - objfile = os.path.join (root, filepath, stem + '.o') - if not os.path.isfile(ctxt.resolve(objfile)): - warning ('No object file found for %s at %s' % (srcfile, objfile)) - continue - if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcno'))): - warning ('No .gcno file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcno'))) - continue - if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcda'))): - warning ('No .gcda file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcda'))) - continue - - num_lines, num_covered = 0, 0 - skip_block = False - cmd = CommandLine('gcov', ['-b', '-n', '-o', objfile, srcfile], - cwd=ctxt.basedir) - for out, err in cmd.execute(): - if out == '': # catch blank lines, reset the block state... - skip_block = False - elif out and not skip_block: - # Check for a file name - match = file_re.match(out) - if match: - if os.path.isabs(match.group('file')): - skip_block = True - continue - else: - # check for a "Lines executed" message - match = lines_re.match(out) - if match: - lines = float(match.group('num')) - cov = float(match.group('cov')) - num_covered += int(lines * cov / 100) - num_lines += int(lines) - if cmd.returncode != 0: - continue - - module = xmlio.Element('coverage', name=os.path.basename(srcfile), - file=srcfile.replace(os.sep, '/'), - lines=num_lines, percentage=0) - if num_lines: - percent = int(round(num_covered * 100 / num_lines)) - module.attr['percentage'] = percent - coverage.append(module) - - ctxt.report('coverage', coverage) - ctxt.log (log_elem) diff --git a/trac-0.11/bitten/build/javatools.py b/trac-0.11/bitten/build/javatools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/javatools.py +++ /dev/null @@ -1,225 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2006 Matthew Good -# 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. - -"""Recipe commands for tools commonly used in Java projects.""" - -from glob import glob -import logging -import os -import posixpath -import shlex -import tempfile - -from bitten.build import CommandLine -from bitten.util import xmlio - -log = logging.getLogger('bitten.build.javatools') - -__docformat__ = 'restructuredtext en' - -def ant(ctxt, file_=None, target=None, keep_going=False, args=None): - """Run an Ant build. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the Ant build file - :param target: name of the target that should be executed (optional) - :param keep_going: whether Ant should keep going when errors are encountered - :param args: additional arguments to pass to Ant - """ - executable = 'ant' - ant_home = ctxt.config.get_dirpath('ant.home') - if ant_home: - executable = os.path.join(ant_home, 'bin', 'ant') - - java_home = ctxt.config.get_dirpath('java.home') - if java_home: - os.environ['JAVA_HOME'] = java_home - - logfile = tempfile.NamedTemporaryFile(prefix='ant_log', suffix='.xml') - logfile.close() - if args: - args = shlex.split(args) - else: - args = [] - args += ['-noinput', '-listener', 'org.apache.tools.ant.XmlLogger', - '-Dant.XmlLogger.stylesheet.uri', '""', - '-DXmlLogger.file', logfile.name] - if file_: - args += ['-buildfile', ctxt.resolve(file_)] - if keep_going: - args.append('-keep-going') - if target: - args.append(target) - - cmdline = CommandLine(executable, args, cwd=ctxt.basedir) - for out, err in cmdline.execute(): - if out is not None: - log.info(out) - if err is not None: - log.error(err) - - error_logged = False - log_elem = xmlio.Fragment() - try: - xml_log = xmlio.parse(file(logfile.name, 'r')) - def collect_log_messages(node): - for child in node.children(): - if child.name == 'message': - if child.attr['priority'] == 'debug': - continue - log_elem.append(xmlio.Element('message', - level=child.attr['priority'])[ - child.gettext().replace(ctxt.basedir + os.sep, '') - .replace(ctxt.basedir, '') - ]) - else: - collect_log_messages(child) - collect_log_messages(xml_log) - - if 'error' in xml_log.attr: - ctxt.error(xml_log.attr['error']) - error_logged = True - - except xmlio.ParseError, e: - log.warning('Error parsing Ant XML log file (%s)', e) - ctxt.log(log_elem) - - if not error_logged and cmdline.returncode != 0: - ctxt.error('Ant failed (%s)' % cmdline.returncode) - -def junit(ctxt, file_=None, srcdir=None): - """Extract test results from a JUnit XML report. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: path to the JUnit XML test results; may contain globbing - wildcards for matching multiple results files - :param srcdir: name of the directory containing the test sources, used to - link test results to the corresponding source files - """ - assert file_, 'Missing required attribute "file"' - try: - total, failed = 0, 0 - results = xmlio.Fragment() - for path in glob(ctxt.resolve(file_)): - fileobj = file(path, 'r') - try: - for testcase in xmlio.parse(fileobj).children('testcase'): - test = xmlio.Element('test') - test.attr['fixture'] = testcase.attr['classname'] - if 'time' in testcase.attr: - test.attr['duration'] = testcase.attr['time'] - if srcdir is not None: - cls = testcase.attr['classname'].split('.') - test.attr['file'] = posixpath.join(srcdir, *cls) + \ - '.java' - - result = list(testcase.children()) - if result: - test.attr['status'] = result[0].name - test.append(xmlio.Element('traceback')[ - result[0].gettext() - ]) - failed += 1 - else: - test.attr['status'] = 'success' - - results.append(test) - total += 1 - finally: - fileobj.close() - if failed: - ctxt.error('%d of %d test%s failed' % (failed, total, - total != 1 and 's' or '')) - ctxt.report('test', results) - except IOError, e: - log.warning('Error opening JUnit results file (%s)', e) - except xmlio.ParseError, e: - log.warning('Error parsing JUnit results file (%s)', e) - - -class _LineCounter(object): - def __init__(self): - self.lines = [] - self.covered = 0 - self.num_lines = 0 - - def __getitem__(self, idx): - if idx >= len(self.lines): - return 0 - return self.lines[idx] - - def __setitem__(self, idx, val): - idx = int(idx) - 1 # 1-indexed to 0-indexed - from itertools import repeat - if idx >= len(self.lines): - self.lines.extend(repeat('-', idx - len(self.lines) + 1)) - self.lines[idx] = val - self.num_lines += 1 - if val != '0': - self.covered += 1 - - def line_hits(self): - return ' '.join(self.lines) - line_hits = property(line_hits) - - def percentage(self): - if self.num_lines == 0: - return 0 - return int(round(self.covered * 100. / self.num_lines)) - percentage = property(percentage) - - -def cobertura(ctxt, file_=None): - """Extract test coverage information from a Cobertura XML report. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: path to the Cobertura XML output - """ - assert file_, 'Missing required attribute "file"' - - coverage = xmlio.Fragment() - doc = xmlio.parse(open(ctxt.resolve(file_))) - srcdir = [s.gettext().strip() for ss in doc.children('sources') - for s in ss.children('source')][0] - - classes = [cls for pkgs in doc.children('packages') - for pkg in pkgs.children('package') - for clss in pkg.children('classes') - for cls in clss.children('class')] - - counters = {} - class_names = {} - - for cls in classes: - filename = cls.attr['filename'].replace(os.sep, '/') - name = cls.attr['name'] - if not '$' in name: # ignore internal classes - class_names[filename] = name - counter = counters.get(filename) - if counter is None: - counter = counters[filename] = _LineCounter() - lines = [l for ls in cls.children('lines') - for l in ls.children('line')] - for line in lines: - counter[line.attr['number']] = line.attr['hits'] - - for filename, name in class_names.iteritems(): - counter = counters[filename] - module = xmlio.Element('coverage', name=name, - file=posixpath.join(srcdir, filename), - lines=counter.num_lines, - percentage=counter.percentage) - module.append(xmlio.Element('line_hits')[counter.line_hits]) - coverage.append(module) - ctxt.report('coverage', coverage) diff --git a/trac-0.11/bitten/build/phptools.py b/trac-0.11/bitten/build/phptools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/phptools.py +++ /dev/null @@ -1,113 +0,0 @@ -# -*- coding: UTF-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2007 Wei Zhuo -# 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.cmlenz.net/wiki/License. - -import logging -import os -import shlex - -from bitten.util import xmlio -from bitten.build import shtools - -log = logging.getLogger('bitten.build.phptools') - -def phing(ctxt, file_=None, target=None, executable=None, args=None): - """Run a phing build""" - if args: - args = shlex.split(args) - else: - args = [] - args += ['-logger', 'phing.listener.DefaultLogger', - '-buildfile', ctxt.resolve(file_ or 'build.xml')] - if target: - args.append(target) - - returncode = shtools.execute(ctxt, file_=executable or 'phing', args=args) - if returncode != 0: - ctxt.error('Phing failed (%s)' % returncode) - -def phpunit(ctxt, file_=None): - """Extract test results from a PHPUnit XML report.""" - assert file_, 'Missing required attribute "file"' - try: - total, failed = 0, 0 - results = xmlio.Fragment() - fileobj = file(ctxt.resolve(file_), 'r') - try: - for testsuit in xmlio.parse(fileobj).children('testsuite'): - total += int(testsuit.attr['tests']) - failed += int(testsuit.attr['failures']) + \ - int(testsuit.attr['errors']) - - for testcase in testsuit.children(): - test = xmlio.Element('test') - test.attr['fixture'] = testcase.attr['class'] - test.attr['name'] = testcase.attr['name'] - test.attr['duration'] = testcase.attr['time'] - result = list(testcase.children()) - if result: - test.append(xmlio.Element('traceback')[ - result[0].gettext() - ]) - test.attr['status'] = result[0].name - else: - test.attr['status'] = 'success' - if 'file' in testsuit.attr: - testfile = os.path.realpath(testsuit.attr['file']) - if testfile.startswith(ctxt.basedir): - testfile = testfile[len(ctxt.basedir) + 1:] - testfile = testfile.replace(os.sep, '/') - test.attr['file'] = testfile - results.append(test) - finally: - fileobj.close() - if failed: - ctxt.error('%d of %d test%s failed' % (failed, total, - total != 1 and 's' or '')) - ctxt.report('test', results) - except IOError, e: - ctxt.log('Error opening PHPUnit results file (%s)' % e) - except xmlio.ParseError, e: - ctxt.log('Error parsing PHPUnit results file (%s)' % e) - -def coverage(ctxt, file_=None): - """Extract data from a Phing code coverage report.""" - assert file_, 'Missing required attribute "file"' - try: - summary_file = file(ctxt.resolve(file_), 'r') - try: - coverage = xmlio.Fragment() - for package in xmlio.parse(summary_file).children('package'): - for cls in package.children('class'): - statements = float(cls.attr['statementcount']) - covered = float(cls.attr['statementscovered']) - if statements: - percentage = covered / statements * 100 - else: - percentage = 100 - class_coverage = xmlio.Element('coverage', - name=cls.attr['name'], - lines=int(statements), - percentage=percentage - ) - source = list(cls.children())[0] - if 'sourcefile' in source.attr: - sourcefile = os.path.realpath(source.attr['sourcefile']) - if sourcefile.startswith(ctxt.basedir): - sourcefile = sourcefile[len(ctxt.basedir) + 1:] - sourcefile = sourcefile.replace(os.sep, '/') - class_coverage.attr['file'] = sourcefile - coverage.append(class_coverage) - finally: - summary_file.close() - ctxt.report('coverage', coverage) - except IOError, e: - ctxt.log('Error opening coverage summary file (%s)' % e) - except xmlio.ParseError, e: - ctxt.log('Error parsing coverage summary file (%s)' % e) diff --git a/trac-0.11/bitten/build/pythontools.py b/trac-0.11/bitten/build/pythontools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/pythontools.py +++ /dev/null @@ -1,466 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2008 Matt Good -# Copyright (C) 2008 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. - -"""Recipe commands for tools commonly used by Python projects.""" - -from __future__ import division - -import logging -import os -import cPickle as pickle -import re -try: - set -except NameError: - from sets import Set as set -import shlex -import sys - -from bitten.build import CommandLine, FileSet -from bitten.util import loc, xmlio - -log = logging.getLogger('bitten.build.pythontools') - -__docformat__ = 'restructuredtext en' - -def _python_path(ctxt): - """Return the path to the Python interpreter. - - If the configuration has a ``python.path`` property, the value of that - option is returned; otherwise the path to the current Python interpreter is - returned. - """ - python_path = ctxt.config.get_filepath('python.path') - if python_path: - return python_path - return sys.executable - -def distutils(ctxt, file_='setup.py', command='build', options=None): - """Execute a ``distutils`` command. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the file defining the distutils setup - :param command: the setup command to execute - :param options: additional options to pass to the command - """ - if options: - if isinstance(options, basestring): - options = shlex.split(options) - else: - options = [] - - cmdline = CommandLine(_python_path(ctxt), - [ctxt.resolve(file_), command] + options, - cwd=ctxt.basedir) - log_elem = xmlio.Fragment() - error_logged = False - for out, err in cmdline.execute(): - if out is not None: - log.info(out) - log_elem.append(xmlio.Element('message', level='info')[out]) - if err is not None: - level = 'error' - if err.startswith('warning: '): - err = err[9:] - level = 'warning' - log.warning(err) - elif err.startswith('error: '): - ctxt.error(err[7:]) - error_logged = True - else: - log.error(err) - log_elem.append(xmlio.Element('message', level=level)[err]) - ctxt.log(log_elem) - - if not error_logged and cmdline.returncode != 0: - ctxt.error('distutils failed (%s)' % cmdline.returncode) - -def exec_(ctxt, file_=None, module=None, function=None, output=None, args=None): - """Execute a Python script. - - Either the `file_` or the `module` parameter must be provided. If - specified using the `file_` parameter, the file must be inside the project - directory. If specified as a module, the module must either be resolvable - to a file, or the `function` parameter must be provided - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the script file to execute - :param module: name of the Python module to execute - :param function: name of the Python function to run - :param output: name of the file to which output should be written - :param args: extra arguments to pass to the script - """ - assert file_ or module, 'Either "file" or "module" attribute required' - if function: - assert module and not file_, '"module" attribute required for use of ' \ - '"function" attribute' - - if module: - # Script specified as module name, need to resolve that to a file, - # or use the function name if provided - if function: - args = '-c "import sys; from %s import %s; %s(sys.argv)" %s' % ( - module, function, function, args) - else: - try: - mod = __import__(module, globals(), locals(), []) - components = module.split('.') - for comp in components[1:]: - mod = getattr(mod, comp) - file_ = mod.__file__.replace('\\', '/') - except ImportError, e: - ctxt.error('Cannot execute Python module %s: %s' % (module, e)) - return - - from bitten.build import shtools - returncode = shtools.execute(ctxt, executable=_python_path(ctxt), - file_=file_, output=output, args=args) - if returncode != 0: - ctxt.error('Executing %s failed (error code %s)' % (file_, returncode)) - -def pylint(ctxt, file_=None): - """Extract data from a ``pylint`` run written to a file. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the file containing the Pylint output - """ - assert file_, 'Missing required attribute "file"' - msg_re = re.compile(r'^(?P.+):(?P\d+): ' - r'\[(?P[A-Z]\d*)(?:, (?P[\w\.]+))?\] ' - r'(?P.*)$') - msg_categories = dict(W='warning', E='error', C='convention', R='refactor') - - problems = xmlio.Fragment() - try: - fd = open(ctxt.resolve(file_), 'r') - try: - for line in fd: - match = msg_re.search(line) - if match: - msg_type = match.group('type') - category = msg_categories.get(msg_type[0]) - if len(msg_type) == 1: - msg_type = None - filename = os.path.realpath(match.group('file')) - if filename.startswith(ctxt.basedir): - filename = filename[len(ctxt.basedir) + 1:] - filename = filename.replace(os.sep, '/') - lineno = int(match.group('line')) - tag = match.group('tag') - problems.append(xmlio.Element('problem', category=category, - type=msg_type, tag=tag, - line=lineno, file=filename)[ - match.group('msg') or '' - ]) - ctxt.report('lint', problems) - finally: - fd.close() - except IOError, e: - log.warning('Error opening pylint results file (%s)', e) - -def coverage(ctxt, summary=None, coverdir=None, include=None, exclude=None): - """Extract data from a ``coverage.py`` run. - - :param ctxt: the build context - :type ctxt: `Context` - :param summary: path to the file containing the coverage summary - :param coverdir: name of the directory containing the per-module coverage - details - :param include: patterns of files or directories to include in the report - :param exclude: patterns of files or directories to exclude from the report - """ - assert summary, 'Missing required attribute "summary"' - - summary_line_re = re.compile(r'^(?P.*?)\s+(?P\d+)\s+' - r'(?P\d+)\s+(?P\d+)%\s+' - r'(?:(?P(?:\d+(?:-\d+)?(?:, )?)*)\s+)?' - r'(?P.+)$') - - fileset = FileSet(ctxt.basedir, include, exclude) - missing_files = [] - for filename in fileset: - if os.path.splitext(filename)[1] != '.py': - continue - missing_files.append(filename) - covered_modules = set() - - try: - summary_file = open(ctxt.resolve(summary), 'r') - try: - coverage = xmlio.Fragment() - for summary_line in summary_file: - match = summary_line_re.search(summary_line) - if match: - modname = match.group(1) - filename = match.group(6) - if not os.path.isabs(filename): - filename = os.path.normpath(os.path.join(ctxt.basedir, - filename)) - else: - filename = os.path.realpath(filename) - if not filename.startswith(ctxt.basedir): - continue - filename = filename[len(ctxt.basedir) + 1:] - if not filename in fileset: - continue - - percentage = int(match.group(4).rstrip('%')) - num_lines = int(match.group(2)) - - missing_files.remove(filename) - covered_modules.add(modname) - module = xmlio.Element('coverage', name=modname, - file=filename.replace(os.sep, '/'), - percentage=percentage, - lines=num_lines) - coverage.append(module) - - for filename in missing_files: - modname = os.path.splitext(filename.replace(os.sep, '.'))[0] - if modname in covered_modules: - continue - covered_modules.add(modname) - module = xmlio.Element('coverage', name=modname, - file=filename.replace(os.sep, '/'), - percentage=0) - coverage.append(module) - - ctxt.report('coverage', coverage) - finally: - summary_file.close() - except IOError, e: - log.warning('Error opening coverage summary file (%s)', e) - -def trace(ctxt, summary=None, coverdir=None, include=None, exclude=None): - """Extract data from a ``trace.py`` run. - - :param ctxt: the build context - :type ctxt: `Context` - :param summary: path to the file containing the coverage summary - :param coverdir: name of the directory containing the per-module coverage - details - :param include: patterns of files or directories to include in the report - :param exclude: patterns of files or directories to exclude from the report - """ - assert summary, 'Missing required attribute "summary"' - assert coverdir, 'Missing required attribute "coverdir"' - - summary_line_re = re.compile(r'^\s*(?P\d+)\s+(?P\d+)%\s+' - r'(?P.*?)\s+\((?P.*?)\)') - coverage_line_re = re.compile(r'\s*(?:(?P\d+): )?(?P.*)') - - fileset = FileSet(ctxt.basedir, include, exclude) - missing_files = [] - for filename in fileset: - if os.path.splitext(filename)[1] != '.py': - continue - missing_files.append(filename) - covered_modules = set() - - def handle_file(elem, sourcefile, coverfile=None): - code_lines = set() - for lineno, linetype, line in loc.count(sourcefile): - if linetype == loc.CODE: - code_lines.add(lineno) - num_covered = 0 - lines = [] - - if coverfile: - prev_hits = '0' - for idx, coverline in enumerate(coverfile): - match = coverage_line_re.search(coverline) - if match: - hits = match.group(1) - if hits: # Line covered - if hits != '0': - num_covered += 1 - lines.append(hits) - prev_hits = hits - elif coverline.startswith('>'): # Line not covered - lines.append('0') - prev_hits = '0' - elif idx not in code_lines: # Not a code line - lines.append('-') - prev_hits = '0' - else: # A code line not flagged by trace.py - if prev_hits != '0': - num_covered += 1 - lines.append(prev_hits) - - elem.append(xmlio.Element('line_hits')[' '.join(lines)]) - - num_lines = len(code_lines) - if num_lines: - percentage = int(round(num_covered * 100 / num_lines)) - else: - percentage = 0 - elem.attr['percentage'] = percentage - elem.attr['lines'] = num_lines - - try: - summary_file = open(ctxt.resolve(summary), 'r') - try: - coverage = xmlio.Fragment() - for summary_line in summary_file: - match = summary_line_re.search(summary_line) - if match: - modname = match.group(3) - filename = match.group(4) - if not os.path.isabs(filename): - filename = os.path.normpath(os.path.join(ctxt.basedir, - filename)) - else: - filename = os.path.realpath(filename) - if not filename.startswith(ctxt.basedir): - continue - filename = filename[len(ctxt.basedir) + 1:] - if not filename in fileset: - continue - - missing_files.remove(filename) - covered_modules.add(modname) - module = xmlio.Element('coverage', name=modname, - file=filename.replace(os.sep, '/')) - sourcefile = file(ctxt.resolve(filename)) - try: - coverpath = ctxt.resolve(coverdir, modname + '.cover') - if os.path.isfile(coverpath): - coverfile = file(coverpath, 'r') - else: - log.warning('No coverage file for module %s at %s', - modname, coverpath) - coverfile = None - try: - handle_file(module, sourcefile, coverfile) - finally: - if coverfile: - coverfile.close() - finally: - sourcefile.close() - coverage.append(module) - - for filename in missing_files: - modname = os.path.splitext(filename.replace(os.sep, '.'))[0] - if modname in covered_modules: - continue - covered_modules.add(modname) - module = xmlio.Element('coverage', name=modname, - file=filename.replace(os.sep, '/'), - percentage=0) - filepath = ctxt.resolve(filename) - fileobj = file(filepath, 'r') - try: - handle_file(module, fileobj) - finally: - fileobj.close() - coverage.append(module) - - ctxt.report('coverage', coverage) - finally: - summary_file.close() - except IOError, e: - log.warning('Error opening coverage summary file (%s)', e) - -def figleaf(ctxt, summary=None, include=None, exclude=None): - from figleaf import get_lines - coverage = xmlio.Fragment() - try: - fileobj = open(ctxt.resolve(summary)) - except IOError, e: - log.warning('Error opening coverage summary file (%s)', e) - return - coverage_data = pickle.load(fileobj) - fileset = FileSet(ctxt.basedir, include, exclude) - for filename in fileset: - base, ext = os.path.splitext(filename) - if ext != '.py': - continue - modname = base.replace(os.path.sep, '.') - realfilename = ctxt.resolve(filename) - interesting_lines = get_lines(open(realfilename)) - covered_lines = coverage_data.get(realfilename, set()) - percentage = int(round(len(covered_lines) * 100 / len(interesting_lines))) - line_hits = [] - for lineno in xrange(1, max(interesting_lines)+1): - if lineno not in interesting_lines: - line_hits.append('-') - elif lineno in covered_lines: - line_hits.append('1') - else: - line_hits.append('0') - module = xmlio.Element('coverage', name=modname, - file=filename, - percentage=percentage, - lines=len(interesting_lines), - line_hits=' '.join(line_hits)) - coverage.append(module) - ctxt.report('coverage', coverage) - -def _normalize_filenames(ctxt, filenames, fileset): - for filename in filenames: - if not os.path.isabs(filename): - filename = os.path.normpath(os.path.join(ctxt.basedir, - filename)) - else: - filename = os.path.realpath(filename) - if not filename.startswith(ctxt.basedir): - continue - filename = filename[len(ctxt.basedir) + 1:] - if filename not in fileset: - continue - yield filename.replace(os.sep, '/') - -def unittest(ctxt, file_=None): - """Extract data from a unittest results file in XML format. - - :param ctxt: the build context - :type ctxt: `Context` - :param file\_: name of the file containing the test results - """ - assert file_, 'Missing required attribute "file"' - - try: - fileobj = file(ctxt.resolve(file_), 'r') - try: - total, failed = 0, 0 - results = xmlio.Fragment() - for child in xmlio.parse(fileobj).children(): - test = xmlio.Element('test') - for name, value in child.attr.items(): - if name == 'file': - value = os.path.realpath(value) - if value.startswith(ctxt.basedir): - value = value[len(ctxt.basedir) + 1:] - value = value.replace(os.sep, '/') - else: - continue - test.attr[name] = value - if name == 'status' and value in ('error', 'failure'): - failed += 1 - for grandchild in child.children(): - test.append(xmlio.Element(grandchild.name)[ - grandchild.gettext() - ]) - results.append(test) - total += 1 - if failed: - ctxt.error('%d of %d test%s failed' % (failed, total, - total != 1 and 's' or '')) - ctxt.report('test', results) - finally: - fileobj.close() - except IOError, e: - log.warning('Error opening unittest results file (%s)', e) - except xmlio.ParseError, e: - log.warning('Error parsing unittest results file (%s)', e) diff --git a/trac-0.11/bitten/build/shtools.py b/trac-0.11/bitten/build/shtools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/shtools.py +++ /dev/null @@ -1,158 +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. - -"""Generic recipe commands for executing external processes.""" - -import logging -import os -import shlex - -from bitten.build import CommandLine -from bitten.util import xmlio - -log = logging.getLogger('bitten.build.shtools') - -__docformat__ = 'restructuredtext en' - -def exec_(ctxt, executable=None, file_=None, output=None, args=None, dir_=None): - """Execute a program or shell script. - - :param ctxt: the build context - :type ctxt: `Context` - :param executable: name of the executable to run - :param file\_: name of the script file, relative to the project directory, - that should be run - :param output: name of the file to which the output of the script should be - written - :param args: command-line arguments to pass to the script - """ - assert executable or file_, \ - 'Either "executable" or "file" attribute required' - - returncode = execute(ctxt, executable=executable, file_=file_, - output=output, args=args, dir_=dir_) - if returncode != 0: - ctxt.error('Executing %s failed (error code %s)' % (executable or file_, - returncode)) - -def pipe(ctxt, executable=None, file_=None, input_=None, output=None, - args=None, dir_=None): - """Pipe the contents of a file through a program or shell script. - - :param ctxt: the build context - :type ctxt: `Context` - :param executable: name of the executable to run - :param file\_: name of the script file, relative to the project directory, - that should be run - :param input\_: name of the file containing the data that should be passed - to the shell script on its standard input stream - :param output: name of the file to which the output of the script should be - written - :param args: command-line arguments to pass to the script - """ - assert executable or file_, \ - 'Either "executable" or "file" attribute required' - assert input_, 'Missing required attribute "input"' - - returncode = execute(ctxt, executable=executable, file_=file_, - input_=input_, output=output, args=args, dir_=dir_) - if returncode != 0: - ctxt.error('Piping through %s failed (error code %s)' - % (executable or file_, returncode)) - -def execute(ctxt, executable=None, file_=None, input_=None, output=None, - args=None, dir_=None, filter_=None): - """Generic external program execution. - - This function is not itself bound to a recipe command, but rather used from - other commands. - - :param ctxt: the build context - :type ctxt: `Context` - :param executable: name of the executable to run - :param file\_: name of the script file, relative to the project directory, - that should be run - :param input\_: name of the file containing the data that should be passed - to the shell script on its standard input stream - :param output: name of the file to which the output of the script should be - written - :param args: command-line arguments to pass to the script - :param dirs: - :param filter\_: function to filter out messages from the executable stdout - """ - if args: - if isinstance(args, basestring): - args = shlex.split(args) - else: - args = [] - - if dir_: - def resolve(*args): - return ctxt.resolve(dir_, *args) - else: - resolve = ctxt.resolve - - if file_ and os.path.isfile(resolve(file_)): - file_ = resolve(file_) - - if executable is None: - executable = file_ - elif file_: - args[:0] = [file_] - - if input_: - input_file = file(resolve(input_), 'r') - else: - input_file = None - - if output: - output_file = file(resolve(output), 'w') - else: - output_file = None - - if dir_ and os.path.isdir(ctxt.resolve(dir_)): - dir_ = ctxt.resolve(dir_) - else: - dir_ = ctxt.basedir - - if not filter_: - filter_=lambda s: s - - try: - cmdline = CommandLine(executable, args, input=input_file, - cwd=dir_) - log_elem = xmlio.Fragment() - for out, err in cmdline.execute(): - if out is not None: - log.info(out) - info = filter_(out) - if info: - log_elem.append(xmlio.Element('message', level='info')[ - info.replace(ctxt.basedir + os.sep, '') - .replace(ctxt.basedir, '') - ]) - if output: - output_file.write(out + os.linesep) - if err is not None: - log.error(err) - log_elem.append(xmlio.Element('message', level='error')[ - err.replace(ctxt.basedir + os.sep, '') - .replace(ctxt.basedir, '') - ]) - if output: - output_file.write(err + os.linesep) - ctxt.log(log_elem) - finally: - if input_: - input_file.close() - if output: - output_file.close() - - return cmdline.returncode diff --git a/trac-0.11/bitten/build/svntools.py b/trac-0.11/bitten/build/svntools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/svntools.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 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. - -"""Recipe commands for Subversion.""" - -import logging -import posixpath -import re - -log = logging.getLogger('bitten.build.svntools') - -__docformat__ = 'restructuredtext en' - -def checkout(ctxt, url, path=None, revision=None, dir_='.', verbose=False): - """Perform a checkout from a Subversion repository. - - :param ctxt: the build context - :type ctxt: `Context` - :param url: the URL of the repository - :param path: the path inside the repository - :param revision: the revision to check out - :param dir_: the name of a local subdirectory to check out into - :param verbose: whether to log the list of checked out files - """ - args = ['checkout'] - if revision: - args += ['-r', revision] - if path: - url = posixpath.join(url, path.lstrip('/')) - args += [url, dir_] - - cofilter = None - if not verbose: - cre = re.compile(r'^[AU]\s.*$') - cofilter = lambda s: cre.sub('', s) - from bitten.build import shtools - returncode = shtools.execute(ctxt, file_='svn', args=args, - filter_=cofilter) - if returncode != 0: - ctxt.error('svn checkout failed (%s)' % returncode) - -def export(ctxt, url, path=None, revision=None, dir_='.'): - """Perform an export from a Subversion repository. - - :param ctxt: the build context - :type ctxt: `Context` - :param url: the URL of the repository - :param path: the path inside the repository - :param revision: the revision to check out - :param dir_: the name of a local subdirectory to export out into - """ - args = ['export', '--force'] - if revision: - args += ['-r', revision] - if path: - url = posixpath.join(url, path) - args += [url, dir_] - - from bitten.build import shtools - returncode = shtools.execute(ctxt, file_='svn', args=args) - if returncode != 0: - ctxt.error('svn export failed (%s)' % returncode) - -def update(ctxt, revision=None, dir_='.'): - """Update the local working copy from the Subversion repository. - - :param ctxt: the build context - :type ctxt: `Context` - :param revision: the revision to check out - :param dir_: the name of a local subdirectory containing the working copy - """ - args = ['update'] - if revision: - args += ['-r', revision] - args += [dir_] - - from bitten.build import shtools - returncode = shtools.execute(ctxt, file_='svn', args=args) - if returncode != 0: - ctxt.error('svn update failed (%s)' % returncode) diff --git a/trac-0.11/bitten/build/tests/__init__.py b/trac-0.11/bitten/build/tests/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/__init__.py +++ /dev/null @@ -1,27 +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 bitten.build.tests import api, config, ctools, phptools, pythontools, \ - xmltools - -def suite(): - suite = unittest.TestSuite() - suite.addTest(api.suite()) - suite.addTest(config.suite()) - suite.addTest(ctools.suite()) - suite.addTest(phptools.suite()) - suite.addTest(pythontools.suite()) - suite.addTest(xmltools.suite()) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/api.py b/trac-0.11/bitten/build/tests/api.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/api.py +++ /dev/null @@ -1,234 +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 os -import shutil -import sys -import tempfile -import unittest - -from bitten.build import CommandLine, FileSet, TimeoutError -from bitten.build.api import _combine - - -class CommandLineTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp(suffix='bitten_test')) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, name, content=None): - filename = os.path.join(self.basedir, name) - fd = file(filename, 'w') - if content: - fd.write(content) - fd.close() - return filename - - def test_extract_lines(self): - cmdline = CommandLine('test', []) - data = ['foo\n', 'bar\n'] - lines = cmdline._extract_lines(data) - self.assertEqual(['foo', 'bar'], lines) - self.assertEqual([], data) - - def test_extract_lines_spanned(self): - cmdline = CommandLine('test', []) - data = ['foo ', 'bar\n'] - lines = cmdline._extract_lines(data) - self.assertEqual(['foo bar'], lines) - self.assertEqual([], data) - - def test_extract_lines_trailing(self): - cmdline = CommandLine('test', []) - data = ['foo\n', 'bar'] - lines = cmdline._extract_lines(data) - self.assertEqual(['foo'], lines) - self.assertEqual(['bar'], data) - - def test_combine(self): - list1 = ['foo', 'bar'] - list2 = ['baz'] - combined = list(_combine(list1, list2)) - self.assertEqual([('foo', 'baz'), ('bar', None)], combined) - - def test_single_argument(self): - cmdline = CommandLine(sys.executable, ['-V']) - stdout = [] - stderr = [] - for out, err in cmdline.execute(timeout=5.0): - if out is not None: - stdout.append(out) - if err is not None: - stderr.append(err) - py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) - self.assertEqual(['Python %s' % py_version], stderr) - self.assertEqual([], stdout) - self.assertEqual(0, cmdline.returncode) - - def test_multiple_arguments(self): - script_file = self._create_file('test.py', content=""" -import sys -for arg in sys.argv[1:]: - print arg -""") - cmdline = CommandLine('python', [script_file, 'foo', 'bar', 'baz']) - stdout = [] - stderr = [] - for out, err in cmdline.execute(timeout=5.0): - stdout.append(out) - stderr.append(err) - py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) - self.assertEqual(['foo', 'bar', 'baz'], stdout) - self.assertEqual([None, None, None], stderr) - self.assertEqual(0, cmdline.returncode) - - def test_output_error_streams(self): - script_file = self._create_file('test.py', content=""" -import sys, time -print>>sys.stdout, 'Hello' -print>>sys.stdout, 'world!' -sys.stdout.flush() -time.sleep(.1) -print>>sys.stderr, 'Oops' -sys.stderr.flush() -""") - cmdline = CommandLine('python', [script_file]) - stdout = [] - stderr = [] - for out, err in cmdline.execute(timeout=5.0): - stdout.append(out) - stderr.append(err) - py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) - # nt doesn't properly split stderr and stdout. See ticket #256. - if os.name != "nt": - self.assertEqual(['Hello', 'world!', None], stdout) - self.assertEqual(0, cmdline.returncode) - - def test_input_stream_as_fileobj(self): - script_file = self._create_file('test.py', content=""" -import sys -data = sys.stdin.read() -if data == 'abcd': - print>>sys.stdout, 'Thanks' -""") - input_file = self._create_file('input.txt', content='abcd') - input_fileobj = file(input_file, 'r') - try: - cmdline = CommandLine('python', [script_file], input=input_fileobj) - stdout = [] - stderr = [] - for out, err in cmdline.execute(timeout=5.0): - stdout.append(out) - stderr.append(err) - py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) - self.assertEqual(['Thanks'], stdout) - self.assertEqual([None], stderr) - self.assertEqual(0, cmdline.returncode) - finally: - input_fileobj.close() - - def test_input_stream_as_string(self): - script_file = self._create_file('test.py', content=""" -import sys -data = sys.stdin.read() -if data == 'abcd': - print>>sys.stdout, 'Thanks' -""") - cmdline = CommandLine('python', [script_file], input='abcd') - stdout = [] - stderr = [] - for out, err in cmdline.execute(timeout=5.0): - stdout.append(out) - stderr.append(err) - py_version = '.'.join([str(v) for (v) in sys.version_info[:3]]) - self.assertEqual(['Thanks'], stdout) - self.assertEqual([None], stderr) - self.assertEqual(0, cmdline.returncode) - - def test_timeout(self): - script_file = self._create_file('test.py', content=""" -import time -time.sleep(2.0) -print 'Done' -""") - cmdline = CommandLine('python', [script_file]) - iterable = iter(cmdline.execute(timeout=.5)) - if os.name != "nt": - # commandline timeout not implemented on windows. See #257 - self.assertRaises(TimeoutError, iterable.next) - -class FileSetTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp(suffix='bitten_test')) - - def tearDown(self): - shutil.rmtree(self.basedir) - - # Convenience methods - - def _create_dir(self, *path): - cur = self.basedir - for part in path: - cur = os.path.join(cur, part) - os.mkdir(cur) - return cur[len(self.basedir) + 1:] - - def _create_file(self, *path): - filename = os.path.join(self.basedir, *path) - fd = file(filename, 'w') - fd.close() - return filename[len(self.basedir) + 1:] - - # Test methods - - def test_empty(self): - fileset = FileSet(self.basedir) - self.assertRaises(StopIteration, iter(fileset).next) - - def test_top_level_files(self): - foo_txt = self._create_file('foo.txt') - bar_txt = self._create_file('bar.txt') - fileset = FileSet(self.basedir) - assert foo_txt in fileset and bar_txt in fileset - - def test_files_in_subdir(self): - self._create_dir('tests') - foo_txt = self._create_file('tests', 'foo.txt') - bar_txt = self._create_file('tests', 'bar.txt') - fileset = FileSet(self.basedir) - assert foo_txt in fileset and bar_txt in fileset - - def test_files_in_subdir_with_include(self): - self._create_dir('tests') - foo_txt = self._create_file('tests', 'foo.txt') - bar_txt = self._create_file('tests', 'bar.txt') - fileset = FileSet(self.basedir, include='tests/*.txt') - assert foo_txt in fileset and bar_txt in fileset - - def test_files_in_subdir_with_exclude(self): - self._create_dir('tests') - foo_txt = self._create_file('tests', 'foo.txt') - bar_txt = self._create_file('tests', 'bar.txt') - fileset = FileSet(self.basedir, include='tests/*.txt', exclude='bar.*') - assert foo_txt in fileset and bar_txt not in fileset - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(CommandLineTestCase, 'test')) - suite.addTest(unittest.makeSuite(FileSetTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/config.py b/trac-0.11/bitten/build/tests/config.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/config.py +++ /dev/null @@ -1,154 +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 platform -import os -import tempfile -import unittest - -from bitten.build.config import Configuration - - -class ConfigurationTestCase(unittest.TestCase): - - def test_sysinfo_defaults(self): - config = Configuration() - - self.assertEqual(platform.machine(), config['machine']) - self.assertEqual(platform.processor(), config['processor']) - system, release, version = platform.system_alias(platform.system(), - platform.release(), - platform.version()) - self.assertEqual(system, config['os']) - self.assertEqual(os.name, config['family']) - self.assertEqual(release, config['version']) - - def test_sysinfo_properties_override(self): - config = Configuration(properties={ - 'machine': 'MACHINE', - 'processor': 'PROCESSOR', - 'os': 'OS', - 'family': 'FAMILY', - 'version': 'VERSION' - }) - self.assertEqual('MACHINE', config['machine']) - self.assertEqual('PROCESSOR', config['processor']) - self.assertEqual('OS', config['os']) - self.assertEqual('FAMILY', config['family']) - self.assertEqual('VERSION', config['version']) - - def test_sysinfo_configfile_override(self): - inifd, ininame = tempfile.mkstemp(prefix='bitten_test') - try: - os.write(inifd, """ -[machine] -name = MACHINE -processor = PROCESSOR - -[os] -name = OS -family = FAMILY -version = VERSION -""") - os.close(inifd) - config = Configuration(ininame) - - self.assertEqual('MACHINE', config['machine']) - self.assertEqual('PROCESSOR', config['processor']) - self.assertEqual('OS', config['os']) - self.assertEqual('FAMILY', config['family']) - self.assertEqual('VERSION', config['version']) - finally: - os.remove(ininame) - - def test_package_properties(self): - config = Configuration(properties={ - 'python.version': '2.3.5', - 'python.path': '/usr/local/bin/python2.3' - }) - self.assertEqual(True, 'python' in config.packages) - self.assertEqual('/usr/local/bin/python2.3', config['python.path']) - self.assertEqual('2.3.5', config['python.version']) - - def test_package_configfile(self): - inifd, ininame = tempfile.mkstemp(prefix='bitten_test') - try: - os.write(inifd, """ -[python] -path = /usr/local/bin/python2.3 -version = 2.3.5 -""") - os.close(inifd) - config = Configuration(ininame) - - self.assertEqual(True, 'python' in config.packages) - self.assertEqual('/usr/local/bin/python2.3', config['python.path']) - self.assertEqual('2.3.5', config['python.version']) - finally: - os.remove(ininame) - - def test_get_dirpath_non_existant(self): - tempdir = tempfile.mkdtemp() - os.rmdir(tempdir) - config = Configuration(properties={'somepkg.home': tempdir}) - self.assertEqual(None, config.get_dirpath('somepkg.home')) - - def test_get_dirpath(self): - tempdir = tempfile.mkdtemp() - try: - config = Configuration(properties={'somepkg.home': tempdir}) - self.assertEqual(tempdir, config.get_dirpath('somepkg.home')) - finally: - os.rmdir(tempdir) - - def test_get_filepath_non_existant(self): - testfile, testname = tempfile.mkstemp(prefix='bitten_test') - os.close(testfile) - os.remove(testname) - config = Configuration(properties={'somepkg.path': testname}) - self.assertEqual(None, config.get_filepath('somepkg.path')) - - def test_get_filepath(self): - testfile = tempfile.NamedTemporaryFile(prefix='bitten_test') - config = Configuration(properties={'somepkg.path': testfile.name}) - self.assertEqual(testfile.name, config.get_filepath('somepkg.path')) - - def test_interpolate(self): - config = Configuration(properties={ - 'python.version': '2.3.5', - 'python.path': '/usr/local/bin/python2.3' - }) - self.assertEqual('/usr/local/bin/python2.3', - config.interpolate('${python.path}')) - self.assertEqual('foo /usr/local/bin/python2.3 bar', - config.interpolate('foo ${python.path} bar')) - - def test_interpolate_default(self): - config = Configuration() - self.assertEqual('python2.3', - config.interpolate('${python.path:python2.3}')) - self.assertEqual('foo python2.3 bar', - config.interpolate('foo ${python.path:python2.3} bar')) - - def test_interpolate_missing(self): - config = Configuration() - self.assertEqual('${python.path}', - config.interpolate('${python.path}')) - self.assertEqual('foo ${python.path} bar', - config.interpolate('foo ${python.path} bar')) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(ConfigurationTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/ctools.py b/trac-0.11/bitten/build/tests/ctools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/ctools.py +++ /dev/null @@ -1,156 +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 os -import shutil -import tempfile -import unittest - -from bitten.build import ctools -from bitten.build.tests import dummy -from bitten.recipe import Context, Recipe - - -class CppUnitTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def test_missing_param_file(self): - self.assertRaises(AssertionError, ctools.cppunit, self.ctxt) - - def test_empty_summary(self): - cppunit_xml = file(self.ctxt.resolve('cppunit.xml'), 'w') - cppunit_xml.write(""" - - - - HelloTest::secondTest - Assertion - - HelloTest.cxx - 95 - - assertion failed -- Expression: 2 == 3 - - - - - - HelloTest::firstTest - - - HelloTest::thirdTest - - - - 3 - 1 - 0 - 1 - -""") - cppunit_xml.close() - ctools.cppunit(self.ctxt, file_='cppunit.xml') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('test', category) - - tests = list(xml.children) - self.assertEqual(3, len(tests)) - self.assertEqual('HelloTest', tests[0].attr['fixture']) - self.assertEqual('secondTest', tests[0].attr['name']) - self.assertEqual('failure', tests[0].attr['status']) - self.assertEqual('HelloTest.cxx', tests[0].attr['file']) - self.assertEqual('95', tests[0].attr['line']) - - self.assertEqual('HelloTest', tests[1].attr['fixture']) - self.assertEqual('firstTest', tests[1].attr['name']) - self.assertEqual('success', tests[1].attr['status']) - - self.assertEqual('HelloTest', tests[2].attr['fixture']) - self.assertEqual('thirdTest', tests[2].attr['name']) - self.assertEqual('success', tests[2].attr['status']) - - -class GCovTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, *path): - filename = os.path.join(self.basedir, *path) - dirname = os.path.dirname(filename) - if not os.path.isdir(dirname): - os.makedirs(dirname) - fd = file(filename, 'w') - fd.close() - return filename[len(self.basedir) + 1:] - - def test_no_file(self): - ctools.CommandLine = dummy.CommandLine() - ctools.gcov(self.ctxt) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('log', type) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('report', type) - self.assertEqual('coverage', category) - self.assertEqual(0, len(xml.children)) - - def test_single_file(self): - self._create_file('foo.c') - self._create_file('foo.o') - self._create_file('foo.gcno') - self._create_file('foo.gcda') - - ctools.CommandLine = dummy.CommandLine(stdout=""" -File `foo.c' -Lines executed:45.81% of 884 -Branches executed:54.27% of 398 -Taken at least once:36.68% of 398 -Calls executed:48.19% of 249 - -File `foo.h' -Lines executed:50.00% of 4 -No branches -Calls executed:100.00% of 1 -""") - ctools.gcov(self.ctxt) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('log', type) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('report', type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - elem = xml.children[0] - self.assertEqual('coverage', elem.name) - self.assertEqual('foo.c', elem.attr['file']) - self.assertEqual('foo.c', elem.attr['name']) - self.assertEqual(888, elem.attr['lines']) - self.assertEqual(45, elem.attr['percentage']) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(CppUnitTestCase, 'test')) - suite.addTest(unittest.makeSuite(GCovTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/dummy.py b/trac-0.11/bitten/build/tests/dummy.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/dummy.py +++ /dev/null @@ -1,27 +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 StringIO import StringIO - -from bitten.build import api - - -class CommandLine(api.CommandLine): - - def __init__(self, returncode=0, stdout='', stderr=''): - self.returncode = returncode - self.stdout = StringIO(stdout) - self.stderr = StringIO(stderr) - - def __call__(self, executable, args, input=None, cwd=None): - return self - - def execute(self): - return api._combine(self.stdout.readlines(), self.stderr.readlines()) diff --git a/trac-0.11/bitten/build/tests/javatools.py b/trac-0.11/bitten/build/tests/javatools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/javatools.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2006 Matthew Good -# 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 os.path -import shutil -import tempfile -import unittest - -from bitten.build import javatools -from bitten.recipe import Context - -class CoberturaTestCase(unittest.TestCase): - xml_template=""" - - - - - src - - - - %s - - - -""" - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, *path, **kw): - filename = os.path.join(self.basedir, *path) - dirname = os.path.dirname(filename) - if not os.path.exists(dirname): - os.makedirs(dirname) - fd = file(filename, 'w') - content = kw.get('content') - if content is not None: - fd.write(content) - fd.close() - return filename[len(self.basedir) + 1:] - - def test_basic(self): - filename = self._create_file('coverage.xml', content=self.xml_template % """ - - - - - - - - """) - javatools.cobertura(self.ctxt, file_=filename) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('report', type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - - elem = xml.children[0] - self.assertEqual('coverage', elem.name) - self.assertEqual('src/test/TestClass.java', elem.attr['file']) - self.assertEqual('test.TestClass', elem.attr['name']) - self.assertEqual(4, elem.attr['lines']) - self.assertEqual(50, elem.attr['percentage']) - - def test_skipped_lines(self): - filename = self._create_file('coverage.xml', content=self.xml_template % """ - - - - - - """) - javatools.cobertura(self.ctxt, file_=filename) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('report', type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - - elem = xml.children[0] - self.assertEqual('coverage', elem.name) - self.assertEqual('src/test/TestClass.java', elem.attr['file']) - self.assertEqual('test.TestClass', elem.attr['name']) - self.assertEqual(2, elem.attr['lines']) - self.assertEqual(50, elem.attr['percentage']) - - line_hits = elem.children[0] - self.assertEqual('line_hits', line_hits.name) - self.assertEqual('0 - 1', line_hits.children[0]) - - def test_interface(self): - filename = self._create_file('coverage.xml', content=self.xml_template % """ - - - - """) - javatools.cobertura(self.ctxt, file_=filename) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual('report', type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - - elem = xml.children[0] - self.assertEqual('coverage', elem.name) - self.assertEqual('src/test/TestInterface.java', elem.attr['file']) - self.assertEqual('test.TestInterface', elem.attr['name']) - self.assertEqual(0, elem.attr['lines']) - self.assertEqual(0, elem.attr['percentage']) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(CoberturaTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/phptools.py b/trac-0.11/bitten/build/tests/phptools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/phptools.py +++ /dev/null @@ -1,131 +0,0 @@ -# -*- coding: UTF-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2007 Wei Zhuo -# 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.cmlenz.net/wiki/License. - -import os -import shutil -import tempfile -import unittest - -from bitten.build import phptools -from bitten.recipe import Context, Recipe - -class PhpUnitTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def test_missing_param_file(self): - self.assertRaises(AssertionError, phptools.phpunit, self.ctxt) - - def test_sample_unit_test_result(self): - phpunit_xml = file(self.ctxt.resolve('phpunit.xml'), 'w') - phpunit_xml.write(""" - - - - - ... - - - - - - - -""") - phpunit_xml.close() - phptools.phpunit(self.ctxt, file_='phpunit.xml') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('test', category) - - tests = list(xml.children) - self.assertEqual(3, len(tests)) - self.assertEqual('FooTest', tests[0].attr['fixture']) - self.assertEqual('testBar', tests[0].attr['name']) - self.assertEqual('failure', tests[0].attr['status']) - self.assert_('FooTest.php' in tests[0].attr['file']) - - self.assertEqual('FooTest', tests[1].attr['fixture']) - self.assertEqual('testBar2', tests[1].attr['name']) - self.assertEqual('success', tests[1].attr['status']) - - self.assertEqual('BarTest', tests[2].attr['fixture']) - self.assertEqual('testFoo', tests[2].attr['name']) - self.assertEqual('success', tests[2].attr['status']) - -class PhpCodeCoverageTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def test_missing_param_file(self): - self.assertRaises(AssertionError, phptools.coverage, self.ctxt) - - def test_sample_code_coverage(self): - coverage_xml = file(self.ctxt.resolve('phpcoverage.xml'), 'w') - coverage_xml.write(""" - - - - - ... - - - - - ... - - - - - ... - - - -""") - coverage_xml.close() - phptools.coverage(self.ctxt, file_='phpcoverage.xml') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - - coverage = list(xml.children) - self.assertEqual(3, len(coverage)) - self.assertEqual(7, coverage[0].attr['lines']) - self.assertEqual('Foo', coverage[0].attr['name']) - self.assert_('xxxx/Foo.php' in coverage[0].attr['file']) - - self.assertEqual(4, coverage[1].attr['lines']) - self.assertEqual(50.0, coverage[1].attr['percentage']) - self.assertEqual('Foo2', coverage[1].attr['name']) - self.assert_('xxxx/Foo.php' in coverage[1].attr['file']) - - self.assertEqual(0, coverage[2].attr['lines']) - self.assertEqual(100.0, coverage[2].attr['percentage']) - self.assertEqual('Bar', coverage[2].attr['name']) - self.assert_('xxxx/Bar.php' in coverage[2].attr['file']) - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(PhpUnitTestCase, 'test')) - suite.addTest(unittest.makeSuite(PhpCodeCoverageTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/pythontools.py b/trac-0.11/bitten/build/tests/pythontools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/pythontools.py +++ /dev/null @@ -1,413 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2008 Matt Good -# Copyright (C) 2008 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 os -import cPickle as pickle -import shutil -import tempfile -import unittest - -from bitten.build import pythontools -from bitten.build import FileSet -from bitten.recipe import Context, Recipe - - -class CoverageTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - self.summary = open(os.path.join(self.basedir, 'test-coverage.txt'), - 'w') - self.coverdir = os.path.join(self.basedir, 'coverage') - os.mkdir(self.coverdir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, *path): - filename = os.path.join(self.basedir, *path) - dirname = os.path.dirname(filename) - os.makedirs(dirname) - fd = file(filename, 'w') - fd.close() - return filename[len(self.basedir) + 1:] - - def test_missing_param_summary(self): - self.summary.close() - self.assertRaises(AssertionError, pythontools.coverage, self.ctxt, - coverdir=self.coverdir) - - def test_empty_summary(self): - self.summary.write(""" -Name Stmts Exec Cover Missing -------------------------------------------- -""") - self.summary.close() - pythontools.coverage(self.ctxt, summary=self.summary.name, include='*.py') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(0, len(xml.children)) - - def test_summary_with_absolute_path(self): - self.summary.write(""" -Name Stmts Exec Cover Missing -------------------------------------------- -test.module 60 60 100%% %s/test/module.py -""" % self.ctxt.basedir) - self.summary.close() - self._create_file('test', 'module.py') - pythontools.coverage(self.ctxt, summary=self.summary.name, - include='test/*') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual('test/module.py', child.attr['file']) - self.assertEqual(100, child.attr['percentage']) - self.assertEqual(60, child.attr['lines']) - - def test_summary_with_relative_path(self): - self.summary.write(""" -Name Stmts Exec Cover Missing -------------------------------------------- -test.module 60 60 100% ./test/module.py -""") - self.summary.close() - self._create_file('test', 'module.py') - pythontools.coverage(self.ctxt, summary=self.summary.name, - include='test/*') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual('test/module.py', child.attr['file']) - self.assertEqual(100, child.attr['percentage']) - self.assertEqual(60, child.attr['lines']) - - def test_summary_with_missing_lines(self): - self.summary.write(""" -Name Stmts Exec Cover Missing -------------------------------------------- -test.module 28 26 92% 13-14 ./test/module.py -""") - self.summary.close() - self._create_file('test', 'module.py') - pythontools.coverage(self.ctxt, summary=self.summary.name, - include='test/*') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual('test/module.py', child.attr['file']) - self.assertEqual(92, child.attr['percentage']) - self.assertEqual(28, child.attr['lines']) - - -class TraceTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - self.summary = open(os.path.join(self.basedir, 'test-coverage.txt'), - 'w') - self.coverdir = os.path.join(self.basedir, 'coverage') - os.mkdir(self.coverdir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, *path): - filename = os.path.join(self.basedir, *path) - dirname = os.path.dirname(filename) - os.makedirs(dirname) - fd = file(filename, 'w') - fd.close() - return filename[len(self.basedir) + 1:] - - def test_missing_param_summary(self): - self.summary.close() - self.assertRaises(AssertionError, pythontools.trace, self.ctxt, - coverdir='coverage') - - def test_missing_param_coverdir(self): - self.summary.close() - self.assertRaises(AssertionError, pythontools.trace, self.ctxt, - summary='test-coverage.txt') - - def test_empty_summary(self): - self.summary.write('line cov% module (path)') - self.summary.close() - pythontools.trace(self.ctxt, summary=self.summary.name, include='*.py', - coverdir=self.coverdir) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(0, len(xml.children)) - - def test_summary_with_absolute_path(self): - self.summary.write(""" -lines cov%% module (path) - 60 100%% test.module (%s/test/module.py) -""" % self.ctxt.basedir) - self.summary.close() - self._create_file('test', 'module.py') - pythontools.trace(self.ctxt, summary=self.summary.name, - include='test/*', coverdir=self.coverdir) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual('test/module.py', child.attr['file']) - - def test_summary_with_relative_path(self): - self.summary.write(""" -lines cov% module (path) - 60 100% test.module (./test/module.py) -""") - self.summary.close() - self._create_file('test', 'module.py') - pythontools.trace(self.ctxt, summary=self.summary.name, - include='test/*', coverdir=self.coverdir) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual('test/module.py', child.attr['file']) - - -class FigleafTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - self.summary = open(os.path.join(self.basedir, '.figleaf'), 'w') - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, *path): - filename = os.path.join(self.basedir, *path) - dirname = os.path.dirname(filename) - os.makedirs(dirname) - fd = file(filename, 'w') - fd.close() - return filename[len(self.basedir) + 1:] - - def test_missing_param_summary(self): - self.summary.close() - self.assertRaises(AssertionError, pythontools.coverage, self.ctxt) - - def test_empty_summary(self): - pickle.dump({}, self.summary) - self.summary.close() - pythontools.figleaf(self.ctxt, summary=self.summary.name, include='*.py') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(0, len(xml.children)) - - def test_missing_coverage_file(self): - self.summary.close() - pythontools.figleaf(self.ctxt, summary='non-existant-file', include='*.py') - self.assertEqual([], self.ctxt.output) - - def test_summary_with_absolute_path(self): - filename = os.sep.join([self.ctxt.basedir, 'test', 'module.py']) - pickle.dump({ - filename: set([1, 4, 5]), - }, self.summary) - self.summary.close() - sourcefile = self.ctxt.resolve(self._create_file('test', 'module.py')) - open(sourcefile, 'w').write( - "if foo: # line 1\n" - " print 'uncovered' # line 2\n" - "else: # line 3 (uninteresting)\n" - " print 'covered' # line 4\n" - "print 'covered' # line 6\n" - ) - pythontools.figleaf(self.ctxt, summary=self.summary.name, - include='test/*') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual(os.path.join('test', 'module.py'), child.attr['file']) - self.assertEqual(75, child.attr['percentage']) - self.assertEqual(4, child.attr['lines']) - self.assertEqual('1 0 - 1 1', child.attr['line_hits']) - - def test_summary_with_non_covered_file(self): - pickle.dump({}, self.summary) - self.summary.close() - sourcefile = self.ctxt.resolve(self._create_file('test', 'module.py')) - open(sourcefile, 'w').write( - "print 'line 1'\n" - "print 'line 2'\n" - "print 'line 3'\n" - "print 'line 4'\n" - "print 'line 5'\n" - ) - pythontools.figleaf(self.ctxt, summary=self.summary.name, - include='test/*') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(1, len(xml.children)) - child = xml.children[0] - self.assertEqual('coverage', child.name) - self.assertEqual('test.module', child.attr['name']) - self.assertEqual(os.path.join('test', 'module.py'), child.attr['file']) - self.assertEqual(0, child.attr['percentage']) - self.assertEqual(5, child.attr['lines']) - - def test_summary_with_non_python_files(self): - "Figleaf coverage reports should not include files that do not end in .py" - pickle.dump({}, self.summary) - self.summary.close() - sourcefile = self.ctxt.resolve(self._create_file('test', 'document.txt')) - open(sourcefile, 'w').write("\n") - pythontools.figleaf(self.ctxt, summary=self.summary.name, - include='test/*') - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('coverage', category) - self.assertEqual(0, len(xml.children)) - - -class FilenameNormalizationTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def _create_file(self, *path): - filename = os.path.join(self.basedir, *path) - dirname = os.path.dirname(filename) - os.makedirs(dirname) - fd = file(filename, 'w') - fd.close() - return filename[len(self.basedir) + 1:] - - def test_absolute_path(self): - filename = os.sep.join([self.ctxt.basedir, 'test', 'module.py']) - self._create_file('test', 'module.py') - filenames = pythontools._normalize_filenames( - self.ctxt, [filename], - FileSet(self.ctxt.basedir, '**/*.py', None)) - self.assertEqual(['test/module.py'], list(filenames)) - - -class UnittestTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - self.results_xml = open(os.path.join(self.basedir, 'test-results.xml'), - 'w') - - def tearDown(self): - shutil.rmtree(self.basedir) - - def test_missing_file_param(self): - self.results_xml.close() - self.assertRaises(AssertionError, pythontools.unittest, self.ctxt) - - def test_empty_results(self): - self.results_xml.write('' - '' - '') - self.results_xml.close() - pythontools.unittest(self.ctxt, self.results_xml.name) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(Recipe.REPORT, type) - self.assertEqual('test', category) - self.assertEqual(0, len(xml.children)) - - def test_successful_test(self): - self.results_xml.write('' - '' - '' - '' - % os.path.join(self.ctxt.basedir, 'bar_test.py')) - self.results_xml.close() - pythontools.unittest(self.ctxt, self.results_xml.name) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(1, len(xml.children)) - test_elem = xml.children[0] - self.assertEqual('test', test_elem.name) - self.assertEqual('0.12', test_elem.attr['duration']) - self.assertEqual('success', test_elem.attr['status']) - self.assertEqual('bar_test.py', test_elem.attr['file']) - self.assertEqual('test_foo (pkg.BarTestCase)', test_elem.attr['name']) - - def test_file_path_normalization(self): - self.results_xml.write('' - '' - '' - '' - % os.path.join(self.ctxt.basedir, 'bar_test.py')) - self.results_xml.close() - pythontools.unittest(self.ctxt, self.results_xml.name) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(1, len(xml.children)) - self.assertEqual('bar_test.py', xml.children[0].attr['file']) - - def test_missing_file_attribute(self): - self.results_xml.write('' - '' - '' - '') - self.results_xml.close() - pythontools.unittest(self.ctxt, self.results_xml.name) - type, category, generator, xml = self.ctxt.output.pop() - self.assertEqual(1, len(xml.children)) - self.assertEqual(None, xml.children[0].attr.get('file')) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(CoverageTestCase, 'test')) - suite.addTest(unittest.makeSuite(TraceTestCase, 'test')) - suite.addTest(unittest.makeSuite(FigleafTestCase, 'test')) - suite.addTest(unittest.makeSuite(FilenameNormalizationTestCase, 'test')) - suite.addTest(unittest.makeSuite(UnittestTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/tests/xmltools.py b/trac-0.11/bitten/build/tests/xmltools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/tests/xmltools.py +++ /dev/null @@ -1,127 +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 os -import shutil -import tempfile -import unittest - -from bitten.build import xmltools -from bitten.recipe import Context -from bitten.util import xmlio - - -class TransformTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - self.ctxt = Context(self.basedir) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def test_transform_no_src(self): - self.assertRaises(AssertionError, xmltools.transform, self.ctxt) - - def test_transform_no_dest(self): - self.assertRaises(AssertionError, xmltools.transform, self.ctxt, - src='src.xml') - - def test_transform_no_stylesheet(self): - self.assertRaises(AssertionError, xmltools.transform, self.ctxt, - src='src.xml', dest='dest.xml') - - def test_transform(self): - src_file = file(self.ctxt.resolve('src.xml'), 'w') - try: - src_file.write(""" -Document Title -
-Section Title -This is a test. -This is a note. -
-
-""") - finally: - src_file.close() - - style_file = file(self.ctxt.resolve('style.xsl'), 'w') - try: - style_file.write(""" - - - - <xsl:value-of select="title"/> - - - - - - - -

-
- -

-
- -

-
- -

NOTE:

-
-
-""") - finally: - style_file.close() - - xmltools.transform(self.ctxt, src='src.xml', dest='dest.xml', - stylesheet='style.xsl') - - dest_file = file(self.ctxt.resolve('dest.xml')) - try: - dest = xmlio.parse(dest_file) - finally: - dest_file.close() - - self.assertEqual('html', dest.name) - self.assertEqual('http://www.w3.org/TR/xhtml1/strict', dest.namespace) - children = list(dest.children()) - self.assertEqual(2, len(children)) - self.assertEqual('head', children[0].name) - head_children = list(children[0].children()) - self.assertEqual(1, len(head_children)) - self.assertEqual('title', head_children[0].name) - self.assertEqual('Document Title', head_children[0].gettext()) - self.assertEqual('body', children[1].name) - body_children = list(children[1].children()) - self.assertEqual(4, len(body_children)) - self.assertEqual('h1', body_children[0].name) - self.assertEqual('Document Title', body_children[0].gettext()) - self.assertEqual('h2', body_children[1].name) - self.assertEqual('Section Title', body_children[1].gettext()) - self.assertEqual('p', body_children[2].name) - self.assertEqual('This is a test.', body_children[2].gettext()) - self.assertEqual('p', body_children[3].name) - self.assertEqual('note', body_children[3].attr['class']) - self.assertEqual('This is a note.', body_children[3].gettext()) - - -def suite(): - suite = unittest.TestSuite() - if xmltools.have_libxslt or xmltools.have_msxml: - suite.addTest(unittest.makeSuite(TransformTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/build/xmltools.py b/trac-0.11/bitten/build/xmltools.py deleted file mode 100644 --- a/trac-0.11/bitten/build/xmltools.py +++ /dev/null @@ -1,98 +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. - -"""Recipe commands for XML processing.""" - -import logging -import os - -try: - import libxml2 - import libxslt - have_libxslt = True -except ImportError: - have_libxslt = False - -if not have_libxslt and os.name == 'nt': - try: - import win32com.client - have_msxml = True - except ImportError: - have_msxml = False -else: - have_msxml = False - -log = logging.getLogger('bitten.build.xmltools') - -__docformat__ = 'restructuredtext en' - -def transform(ctxt, src=None, dest=None, stylesheet=None): - """Apply an XSLT stylesheet to a source XML document. - - This command requires either libxslt (with Python bindings), or MSXML to - be installed. - - :param ctxt: the build context - :type ctxt: `Context` - :param src: name of the XML input file - :param dest: name of the XML output file - :param stylesheet: name of the file containing the XSLT stylesheet - """ - assert src, 'Missing required attribute "src"' - assert dest, 'Missing required attribute "dest"' - assert stylesheet, 'Missing required attribute "stylesheet"' - - if have_libxslt: - log.debug('Using libxslt for XSLT transformation') - srcdoc, styledoc, result = None, None, None - try: - srcdoc = libxml2.parseFile(ctxt.resolve(src)) - styledoc = libxslt.parseStylesheetFile(ctxt.resolve(stylesheet)) - result = styledoc.applyStylesheet(srcdoc, None) - styledoc.saveResultToFilename(ctxt.resolve(dest), result, 0) - finally: - if styledoc: - styledoc.freeStylesheet() - if srcdoc: - srcdoc.freeDoc() - if result: - result.freeDoc() - - elif have_msxml: - log.debug('Using MSXML for XSLT transformation') - srcdoc = win32com.client.Dispatch('MSXML2.DOMDocument.3.0') - if not srcdoc.load(ctxt.resolve(src)): - err = styledoc.parseError - ctxt.error('Failed to parse XML source %s: %s', src, err.reason) - return - styledoc = win32com.client.Dispatch('MSXML2.DOMDocument.3.0') - if not styledoc.load(ctxt.resolve(stylesheet)): - err = styledoc.parseError - ctxt.error('Failed to parse XSLT stylesheet %s: %s', stylesheet, - err.reason) - return - result = srcdoc.transformNode(styledoc) - - # MSXML seems to always write produce the resulting XML document using - # UTF-16 encoding, regardless of the encoding specified in the - # stylesheet. For better interoperability, recode to UTF-8 here. - result = result.encode('utf-8').replace(' encoding="UTF-16"?>', '?>') - - dest_file = file(ctxt.resolve(dest), 'w') - try: - dest_file.write(result) - finally: - dest_file.close() - - else: - ctxt.error('No usable XSLT implementation found') - - # TODO: as a last resort, try to invoke 'xsltproc' to do the - # transformation? diff --git a/trac-0.11/bitten/htdocs/admin.css b/trac-0.11/bitten/htdocs/admin.css deleted file mode 100644 --- a/trac-0.11/bitten/htdocs/admin.css +++ /dev/null @@ -1,6 +0,0 @@ -table.form th { text-align: right; } -div.platforms h3 { margin-top: 3em; } -table#platformlist td ul { list-style: none; margin: 0; padding: 0; } - -dl.help { color: #666; font-size: 90%; margin: 1em .5em; } -dl.help dt { font-weight: bold; } diff --git a/trac-0.11/bitten/htdocs/bitten.css b/trac-0.11/bitten/htdocs/bitten.css deleted file mode 100644 --- a/trac-0.11/bitten/htdocs/bitten.css +++ /dev/null @@ -1,183 +0,0 @@ -/* Timeline styles */ -#content.timeline dt.successbuild, #content.timeline dt.successbuild a { - background-image: url(bitten_build.png) !important; -} -#content.timeline dt.failedbuild, #content.timeline dt.failedbuild a { - background-image: url(bitten_buildf.png) !important; -} - -#content.build h2.config, #content.build h2.step { background: #f7f7f7; - border-bottom: 1px solid #d7d7d7; margin: 2em 0 0; -} -#content.build h2.config :link, #content.build h2.config :visited { - color: #b00; display: block; border-bottom: none; -} -#content.build h2.deactivated { text-decoration: line-through; } -#content.build #prefs { line-height: 1.4em; } - -#content.build h3.builds { font-weight: bold; text-align: left; - margin: 2em 0 0 2em; -} -#content.build table.builds { border-collapse: separate; - border-top: 1px solid #666; margin-left: 2em; table-layout: fixed; -} -#content.build table.builds th { padding: 0 1em 0 .25em; text-align: left; - vertical-align: top; -} -#content.build table.builds th p { color: #666; font-size: smaller; - margin-top: 0; -} -#content.build table.builds th p.message { font-style: italic; } -#content.build table.builds td { color: #999; border: 1px solid; - padding: .25em .5em; vertical-align: top; -} -#content.build table.builds td :link, #content.build table.builds td :visited { - font-weight: bold; -} -#content.build table.builds td.completed { background: #9d9; border-color: #696; - color: #393; -} -#content.build table.builds td.failed { background: #d99; border-color: #966; - color: #933; -} -#content.build table.builds td.in-progress { background: #dd9; - border-color: #996; color: #993; -} -#content.build table.builds td p { font-size: smaller; margin-top: 0; } -#content.build table.builds .status { color: #000; } -#content.build table.builds .system { font-size: smaller; line-height: 1.2em; - margin: .5em 0; -} - -#content.build form.config { margin-top: 1em; } -#content.build form.config th { text-align: left; } -#content.build form.config fieldset { margin-bottom: 1em; } -#content.build div.platforms { margin-top: 2em; } -#content.build form.platforms ul { list-style-type: none; padding-left: 1em; } - -#content.build p.path { color: #999; font-size: smaller; margin-top: 0; } - -#content.build #charts { clear: right; float: right; width: 44%; } - -#content.build #builds { clear: none; margin-top: 2em; table-layout: fixed; - width: 54%; -} -#content.build #builds tbody th, #content.build #builds tbody td { - background: #fff; -} -#content.build #builds th.chgset { width: 6em; } -#content.build #builds td :link, #content.build #builds td :visited { - font-weight: bold; -} -#content.build #builds tbody td { background-position: 2px .5em; - background-repeat: no-repeat; -} -#content.build #builds td.completed { - background-color: #e8f6e8; background-image: url(bitten_build.png); -} -#content.build #builds td.failed { - background-color: #fbe8e7; background-image: url(bitten_buildf.png); -} -#content.build #builds td.in-progress { - background-color: #f6fae0; background-image: url(bitten_build.png); -} -#content.build #builds .info { margin-left: 16px; } -#content.build #builds :link, #content.build #builds :visited { - text-decoration: none; -} -#content.build #builds .info .status { color: #000; } -#content.build #builds .info .system { color: #999; font-size: smaller; - line-height: 1.2em; margin-top: .5em; -} -#content.build #builds ul.steps { - list-style-type: none; margin: .5em 0 0; padding: 0; -} -#content.build #builds ul.steps li.success, -#content.build #builds ul.steps li.failed { - border: 1px solid; margin: 1px 0; padding: 0 2px 0 12px; -} -#content.build #builds ul.steps li.success { - background: #9d9; border-color: #696; color: #393; -} -#content.build #builds ul.steps li.failed { - background: #d99 url(failure.png) 2px .3em no-repeat; border-color: #966; - color: #933; -} -#content.build #builds ul.steps li :link, -#content.build #builds ul.steps li :visited { border: none; color: inherit; - font-weight: bold; text-decoration: none; -} -#content.build #builds ul.steps li .duration { float: right; - font-size: smaller; -} -#content.build #builds ul.steps li.success .duration { color: #696; } -#content.build #builds ul.steps li.failed .duration { color: #966; } -#content.build #builds ul.steps li.failed ul { font-size: smaller; - line-height: 1.2em; list-style-type: square; margin: 0; - padding: 0 0 .5em 1.5em; -} - -#content.build #overview { line-height: 130%; margin-top: 1em; padding: .5em; } -#content.build #overview dt { font-weight: bold; padding-right: .25em; - position: absolute; left: 0; text-align: right; width: 11.5em; -} -#content.build #overview dd { margin-left: 12em; } -#content.build #overview .slave { margin-top: 1em; } -#content.build #overview .time { margin-top: 1em; } - -#content.build div.errors { background: #d99; border: 1px solid #966; - color: #933; float: right; margin: 1em; -} -#content.build div.errors h3 { background: #966; color: #fff; margin: 0; - padding: 0 .3em; -} -#content.build div.errors ul { list-style-image: url(failure.png); margin: 0; - padding: .5em 1.75em; -} - -#content.build .tabs { clear: right; list-style: none; float: left; width: 100%; - margin: 0 1em; padding: 0; -} -#content.build .tabs li { cursor: pointer; float: left; } -#content.build .tabs li a { background: #b9b9b9; color: #666; display: block; - margin: 2px 2px 0; padding: 3px 2em 0; -} -#content.build .tabs li a:hover { color: #333; text-decoration: none; } -#content.build .tabs li.active a { background: #d7d7d7; border: 1px outset; - border-bottom: none; color: #333; font-weight: bold; margin-top: 0; - padding-bottom: 1px; -} -#content.build .tab-content { background: #f4f4f4; border: 1px outset; - clear: both; margin: 0 2em 0 1em; padding: 5px; -} -#content.build .tab-content table { margin: 0; } - -#content.build tbody.totals td, #content.build tbody.totals th { - font-weight: bold; -} -#content.build table.tests tr.failed { background: #d99; } -#content.build table.tests tr.failed td { font-weight: bold; } -#content.build table.tests tr.failed:hover th, -#content.build table.tests tr.failed:hover td, -#content.build table.tests tr.failed:hover tr { background: #966; } -#content.build table.tests tr.failed :link, -#content.build table.tests tr.failed :visited { color: #933 } -#content.build table.tests tr.failed :link:hover, -#content.build table.tests tr.failed :visited:hover { color: #fff; } - -#content.build .log { background: #fff; border: 1px inset; font-size: 90%; - overflow: auto; max-height: 20em; width: 100%; white-space: pre; -} -#content.build .log code { padding: 0 5px; } -#content.build .log .warning { color: #660; font-weight: bold; } -#content.build .log .error { color: #900; font-weight: bold; } - -#content.build table.listing th, #content.build table.listing td { - font-size: 95%; -} -#content.build table.listing tbody th, #content.build table.listing tbody td { - background: #fff; padding: .1em .3em; -} -#content.build table.listing :link, #content.build table.listing :visited { - border: none; -} diff --git a/trac-0.11/bitten/htdocs/bitten_build.png b/trac-0.11/bitten/htdocs/bitten_build.png deleted file mode 100755 index 1e2f5669121b121d8119ca1ca05ff66663a41075..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@ -# 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 inspect -import os -import textwrap - -from trac.core import * -from trac.db import DatabaseManager -from trac.env import IEnvironmentSetupParticipant -from trac.perm import IPermissionRequestor -from trac.wiki import IWikiSyntaxProvider -from bitten.api import IBuildListener -from bitten.model import schema, schema_version, Build, BuildConfig - -__all__ = ['BuildSystem'] -__docformat__ = 'restructuredtext en' - - -class BuildSystem(Component): - - implements(IEnvironmentSetupParticipant, IPermissionRequestor, - IWikiSyntaxProvider) - - listeners = ExtensionPoint(IBuildListener) - - # IEnvironmentSetupParticipant methods - - def environment_created(self): - # Create the required tables - db = self.env.get_db_cnx() - connector, _ = DatabaseManager(self.env)._get_connector() - cursor = db.cursor() - for table in schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - - # Insert a global version flag - cursor.execute("INSERT INTO system (name,value) " - "VALUES ('bitten_version',%s)", (schema_version,)) - - # Create the directory for storing snapshot archives - snapshots_dir = os.path.join(self.env.path, 'snapshots') - os.mkdir(snapshots_dir) - - db.commit() - - def environment_needs_upgrade(self, db): - cursor = db.cursor() - cursor.execute("SELECT value FROM system WHERE name='bitten_version'") - row = cursor.fetchone() - if not row or int(row[0]) < schema_version: - return True - - def upgrade_environment(self, db): - cursor = db.cursor() - cursor.execute("SELECT value FROM system WHERE name='bitten_version'") - row = cursor.fetchone() - if not row: - self.environment_created() - else: - current_version = int(row[0]) - from bitten import upgrades - for version in range(current_version + 1, schema_version + 1): - for function in upgrades.map.get(version): - print textwrap.fill(inspect.getdoc(function)) - function(self.env, db) - print 'Done.' - cursor.execute("UPDATE system SET value=%s WHERE " - "name='bitten_version'", (schema_version,)) - self.log.info('Upgraded Bitten tables from version %d to %d', - current_version, schema_version) - - # IPermissionRequestor methods - - def get_permission_actions(self): - actions = ['BUILD_VIEW', 'BUILD_CREATE', 'BUILD_MODIFY', 'BUILD_DELETE', - 'BUILD_EXEC'] - return actions + [('BUILD_ADMIN', actions)] - - # IWikiSyntaxProvider methods - - def get_wiki_syntax(self): - return [] - - def get_link_resolvers(self): - def _format_link(formatter, ns, name, label): - build = Build.fetch(self.env, int(name)) - if build: - config = BuildConfig.fetch(self.env, build.config) - title = 'Build %d ([%s] of %s) by %s' % (build.id, build.rev, - config.label, build.slave) - return '%s' \ - % (formatter.href.build(build.config, build.id), title, - label) - return label - yield 'build', _format_link diff --git a/trac-0.11/bitten/master.py b/trac-0.11/bitten/master.py deleted file mode 100644 --- a/trac-0.11/bitten/master.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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. - -"""Build master implementation.""" - -import calendar -import re -import time - -from trac.config import BoolOption, IntOption -from trac.core import * -from trac.web import IRequestHandler, HTTPBadRequest, HTTPConflict, \ - HTTPForbidden, HTTPMethodNotAllowed, HTTPNotFound, \ - RequestDone - -from bitten.model import BuildConfig, Build, BuildStep, BuildLog, Report -from bitten.main import BuildSystem -from bitten.queue import BuildQueue -from bitten.recipe import Recipe -from bitten.util import xmlio - -__all__ = ['BuildMaster'] -__docformat__ = 'restructuredtext en' - - -class BuildMaster(Component): - """BEEP listener implementation for the build master.""" - - implements(IRequestHandler) - - # Configuration options - - adjust_timestamps = BoolOption('bitten', 'adjust_timestamps', False, doc= - """Whether the timestamps of builds should be adjusted to be close - to the timestamps of the corresponding changesets.""") - - build_all = BoolOption('bitten', 'build_all', False, doc= - """Whether to request builds of older revisions even if a younger - revision has already been built.""") - - stabilize_wait = IntOption('bitten', 'stabilize_wait', 0, doc= - """The time in seconds to wait for the repository to stabilize before - queuing up a new build. This allows time for developers to check in - a group of related changes back to back without spawning multiple - builds.""") - - slave_timeout = IntOption('bitten', 'slave_timeout', 3600, doc= - """The time in seconds after which a build is cancelled if the slave - does not report progress.""") - - # IRequestHandler methods - - def match_request(self, req): - match = re.match(r'/builds(?:/(\d+)(?:/(\w+)/([^/]+)?)?)?$', - req.path_info) - if match: - if match.group(1): - req.args['id'] = match.group(1) - req.args['collection'] = match.group(2) - req.args['member'] = match.group(3) - return True - - def process_request(self, req): - req.perm.assert_permission('BUILD_EXEC') - - if 'id' not in req.args: - if req.method != 'POST': - raise HTTPMethodNotAllowed('Method not allowed') - return self._process_build_creation(req) - - build = Build.fetch(self.env, req.args['id']) - if not build: - raise HTTPNotFound('No such build') - config = BuildConfig.fetch(self.env, build.config) - - if not req.args['collection']: - if req.method == 'DELETE': - return self._process_build_cancellation(req, config, build) - else: - return self._process_build_initiation(req, config, build) - - if req.method != 'POST': - raise HTTPMethodNotAllowed('Method not allowed') - - if req.args['collection'] == 'steps': - return self._process_build_step(req, config, build) - else: - raise HTTPNotFound('No such collection') - - def _process_build_creation(self, req): - queue = BuildQueue(self.env, build_all=self.build_all, - stabilize_wait=self.stabilize_wait, - timeout=self.slave_timeout) - queue.populate() - - try: - elem = xmlio.parse(req.read()) - except xmlio.ParseError, e: - self.log.error('Error parsing build initialization request: %s', e, - exc_info=True) - raise HTTPBadRequest('XML parser error') - - slavename = elem.attr['name'] - properties = {'name': slavename, Build.IP_ADDRESS: req.remote_addr} - self.log.info('Build slave %r connected from %s', slavename, - req.remote_addr) - - for child in elem.children(): - if child.name == 'platform': - properties[Build.MACHINE] = child.gettext() - properties[Build.PROCESSOR] = child.attr.get('processor') - elif child.name == 'os': - properties[Build.OS_NAME] = child.gettext() - properties[Build.OS_FAMILY] = child.attr.get('family') - properties[Build.OS_VERSION] = child.attr.get('version') - elif child.name == 'package': - for name, value in child.attr.items(): - if name == 'name': - continue - properties[child.attr['name'] + '.' + name] = value - - self.log.debug('Build slave configuration: %r', properties) - - build = queue.get_build_for_slave(slavename, properties) - if not build: - req.send_response(204) - req.write('') - raise RequestDone - - req.send_response(201) - req.send_header('Content-Type', 'text/plain') - req.send_header('Location', req.abs_href.builds(build.id)) - req.write('Build pending') - raise RequestDone - - def _process_build_cancellation(self, req, config, build): - self.log.info('Build slave %r cancelled build %d', build.slave, - build.id) - build.status = Build.PENDING - build.slave = None - build.slave_info = {} - build.started = 0 - db = self.env.get_db_cnx() - for step in list(BuildStep.select(self.env, build=build.id, db=db)): - step.delete(db=db) - build.update(db=db) - db.commit() - - for listener in BuildSystem(self.env).listeners: - listener.build_aborted(build) - - req.send_response(204) - req.write('') - raise RequestDone - - def _process_build_initiation(self, req, config, build): - self.log.info('Build slave %r initiated build %d', build.slave, - build.id) - build.started = int(time.time()) - build.update() - - for listener in BuildSystem(self.env).listeners: - listener.build_started(build) - - xml = xmlio.parse(config.recipe) - xml.attr['path'] = config.path - xml.attr['revision'] = build.rev - xml.attr['config'] = config.name - xml.attr['build'] = str(build.id) - body = str(xml) - - self.log.info('Build slave %r initiated build %d', build.slave, - build.id) - - req.send_response(200) - req.send_header('Content-Type', 'application/x-bitten+xml') - req.send_header('Content-Length', str(len(body))) - req.send_header('Content-Disposition', - 'attachment; filename=recipe_%s_r%s.xml' % - (config.name, build.rev)) - req.write(body) - raise RequestDone - - def _process_build_step(self, req, config, build): - try: - elem = xmlio.parse(req.read()) - except xmlio.ParseError, e: - self.log.error('Error parsing build step result: %s', e, - exc_info=True) - raise HTTPBadRequest('XML parser error') - stepname = elem.attr['step'] - - # make sure it's the right slave. - if build.status != Build.IN_PROGRESS or \ - build.slave_info.get(Build.IP_ADDRESS) != req.remote_addr: - raise HTTPForbidden('Build %s has been invalidated for host %s.' - % (build.id, req.remote_addr)) - - step = BuildStep.fetch(self.env, build=build.id, name=stepname) - if step: - raise HTTPConflict('Build step already exists') - - recipe = Recipe(xmlio.parse(config.recipe)) - index = None - current_step = None - for num, recipe_step in enumerate(recipe): - if recipe_step.id == stepname: - index = num - current_step = recipe_step - if index is None: - raise HTTPForbidden('No such build step') - last_step = index == num - - self.log.debug('Slave %s (build %d) completed step %d (%s) with ' - 'status %s', build.slave, build.id, index, stepname, - elem.attr['status']) - - db = self.env.get_db_cnx() - - step = BuildStep(self.env, build=build.id, name=stepname) - try: - step.started = int(_parse_iso_datetime(elem.attr['time'])) - step.stopped = step.started + float(elem.attr['duration']) - except ValueError, e: - self.log.error('Error parsing build step timestamp: %s', e, - exc_info=True) - raise HTTPBadRequest(e.args[0]) - if elem.attr['status'] == 'failure': - self.log.warning('Build %s step %s failed', build.id, stepname) - step.status = BuildStep.FAILURE - if current_step.onerror == 'fail': - last_step = True - else: - step.status = BuildStep.SUCCESS - step.errors += [error.gettext() for error in elem.children('error')] - step.insert(db=db) - - # Collect log messages from the request body - for idx, log_elem in enumerate(elem.children('log')): - build_log = BuildLog(self.env, build=build.id, step=stepname, - generator=log_elem.attr.get('generator'), - orderno=idx) - for message_elem in log_elem.children('message'): - build_log.messages.append((message_elem.attr['level'], - message_elem.gettext())) - build_log.insert(db=db) - - # Collect report data from the request body - for report_elem in elem.children('report'): - report = Report(self.env, build=build.id, step=stepname, - category=report_elem.attr.get('category'), - generator=report_elem.attr.get('generator')) - for item_elem in report_elem.children(): - item = {'type': item_elem.name} - item.update(item_elem.attr) - for child_elem in item_elem.children(): - item[child_elem.name] = child_elem.gettext() - report.items.append(item) - report.insert(db=db) - - # If this was the last step in the recipe we mark the build as - # completed - if last_step: - self.log.info('Slave %s completed build %d ("%s" as of [%s])', - build.slave, build.id, build.config, build.rev) - build.stopped = step.stopped - - # Determine overall outcome of the build by checking the outcome - # of the individual steps against the "onerror" specification of - # each step in the recipe - for num, recipe_step in enumerate(recipe): - step = BuildStep.fetch(self.env, build.id, recipe_step.id) - if step.status == BuildStep.FAILURE: - if recipe_step.onerror != 'ignore': - build.status = Build.FAILURE - break - else: - build.status = Build.SUCCESS - - build.update(db=db) - - db.commit() - - if last_step: - for listener in BuildSystem(self.env).listeners: - listener.build_completed(build) - - body = 'Build step processed' - req.send_response(201) - req.send_header('Content-Type', 'text/plain') - req.send_header('Content-Length', str(len(body))) - req.send_header('Location', req.abs_href.builds(build.id, 'steps', - stepname)) - req.write(body) - raise RequestDone - - -def _parse_iso_datetime(string): - """Minimal parser for ISO date-time strings. - - Return the time as floating point number. Only handles UTC timestamps - without time zone information.""" - try: - string = string.split('.', 1)[0] # strip out microseconds - return calendar.timegm(time.strptime(string, '%Y-%m-%dT%H:%M:%S')) - except ValueError, e: - raise ValueError('Invalid ISO date/time %r' % string) diff --git a/trac-0.11/bitten/model.py b/trac-0.11/bitten/model.py deleted file mode 100644 --- a/trac-0.11/bitten/model.py +++ /dev/null @@ -1,940 +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. - -"""Model classes for objects persisted in the database.""" - -from trac.db import Table, Column, Index - -__docformat__ = 'restructuredtext en' - - -class BuildConfig(object): - """Representation of a build configuration.""" - - _schema = [ - Table('bitten_config', key='name')[ - Column('name'), Column('path'), Column('active', type='int'), - Column('recipe'), Column('min_rev'), Column('max_rev'), - Column('label'), Column('description') - ] - ] - - def __init__(self, env, name=None, path=None, active=False, recipe=None, - min_rev=None, max_rev=None, label=None, description=None): - """Initialize a new build configuration with the specified attributes. - - To actually create this configuration in the database, the `insert` - method needs to be called. - """ - self.env = env - self._old_name = None - self.name = name - self.path = path or '' - self.active = bool(active) - self.recipe = recipe or '' - self.min_rev = min_rev or None - self.max_rev = max_rev or None - self.label = label or '' - self.description = description or '' - - def __repr__(self): - return '<%s %r>' % (type(self).__name__, self.name) - - exists = property(fget=lambda self: self._old_name is not None, - doc='Whether this configuration exists in the database') - - def delete(self, db=None): - """Remove a build configuration and all dependent objects from the - database.""" - assert self.exists, 'Cannot delete non-existing configuration' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - for platform in list(TargetPlatform.select(self.env, self.name, db=db)): - platform.delete(db=db) - - for build in list(Build.select(self.env, config=self.name, db=db)): - build.delete(db=db) - - cursor = db.cursor() - cursor.execute("DELETE FROM bitten_config WHERE name=%s", (self.name,)) - - if handle_ta: - db.commit() - self._old_name = None - - def insert(self, db=None): - """Insert a new configuration into the database.""" - assert not self.exists, 'Cannot insert existing configuration' - assert self.name, 'Configuration requires a name' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,active," - "recipe,min_rev,max_rev,label,description) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", - (self.name, self.path, int(self.active or 0), - self.recipe or '', self.min_rev, self.max_rev, - self.label or '', self.description or '')) - - if handle_ta: - db.commit() - self._old_name = self.name - - def update(self, db=None): - """Save changes to an existing build configuration.""" - assert self.exists, 'Cannot update a non-existing configuration' - assert self.name, 'Configuration requires a name' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - cursor = db.cursor() - cursor.execute("UPDATE bitten_config SET name=%s,path=%s,active=%s," - "recipe=%s,min_rev=%s,max_rev=%s,label=%s," - "description=%s WHERE name=%s", - (self.name, self.path, int(self.active or 0), - self.recipe, self.min_rev, self.max_rev, - self.label, self.description, self._old_name)) - if self.name != self._old_name: - cursor.execute("UPDATE bitten_platform SET config=%s " - "WHERE config=%s", (self.name, self._old_name)) - cursor.execute("UPDATE bitten_build SET config=%s " - "WHERE config=%s", (self.name, self._old_name)) - - if handle_ta: - db.commit() - self._old_name = self.name - - def fetch(cls, env, name, db=None): - """Retrieve an existing build configuration from the database by - name. - """ - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - cursor.execute("SELECT path,active,recipe,min_rev,max_rev,label," - "description FROM bitten_config WHERE name=%s", (name,)) - row = cursor.fetchone() - if not row: - return None - - config = BuildConfig(env) - config.name = config._old_name = name - config.path = row[0] or '' - config.active = bool(row[1]) - config.recipe = row[2] or '' - config.min_rev = row[3] or None - config.max_rev = row[4] or None - config.label = row[5] or '' - config.description = row[6] or '' - return config - - fetch = classmethod(fetch) - - def select(cls, env, include_inactive=False, db=None): - """Retrieve existing build configurations from the database that match - the specified criteria. - """ - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - if include_inactive: - cursor.execute("SELECT name,path,active,recipe,min_rev,max_rev," - "label,description FROM bitten_config ORDER BY name") - else: - cursor.execute("SELECT name,path,active,recipe,min_rev,max_rev," - "label,description FROM bitten_config " - "WHERE active=1 ORDER BY name") - for name, path, active, recipe, min_rev, max_rev, label, description \ - in cursor: - config = BuildConfig(env, name=name, path=path or '', - active=bool(active), recipe=recipe or '', - min_rev=min_rev or None, - max_rev=max_rev or None, label=label or '', - description=description or '') - config._old_name = name - yield config - - select = classmethod(select) - - -class TargetPlatform(object): - """Target platform for a build configuration.""" - - _schema = [ - Table('bitten_platform', key='id')[ - Column('id', auto_increment=True), Column('config'), Column('name') - ], - Table('bitten_rule', key=('id', 'propname'))[ - Column('id'), Column('propname'), Column('pattern'), - Column('orderno', type='int') - ] - ] - - def __init__(self, env, config=None, name=None): - """Initialize a new target platform with the specified attributes. - - To actually create this platform in the database, the `insert` method - needs to be called. - """ - self.env = env - self.id = None - self.config = config - self.name = name - self.rules = [] - - def __repr__(self): - return '<%s %r>' % (type(self).__name__, self.id) - - exists = property(fget=lambda self: self.id is not None, - doc='Whether this target platform exists in the database') - - def delete(self, db=None): - """Remove the target platform from the database.""" - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - cursor = db.cursor() - cursor.execute("DELETE FROM bitten_rule WHERE id=%s", (self.id,)) - cursor.execute("DELETE FROM bitten_platform WHERE id=%s", (self.id,)) - if handle_ta: - db.commit() - - def insert(self, db=None): - """Insert a new target platform into the database.""" - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - assert not self.exists, 'Cannot insert existing target platform' - assert self.config, 'Target platform needs to be associated with a ' \ - 'configuration' - assert self.name, 'Target platform requires a name' - - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_platform (config,name) " - "VALUES (%s,%s)", (self.config, self.name)) - self.id = db.get_last_id(cursor, 'bitten_platform') - if self.rules: - cursor.executemany("INSERT INTO bitten_rule VALUES (%s,%s,%s,%s)", - [(self.id, propname, pattern, idx) - for idx, (propname, pattern) - in enumerate(self.rules)]) - - if handle_ta: - db.commit() - - def update(self, db=None): - """Save changes to an existing target platform.""" - assert self.exists, 'Cannot update a non-existing platform' - assert self.config, 'Target platform needs to be associated with a ' \ - 'configuration' - assert self.name, 'Target platform requires a name' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - cursor = db.cursor() - cursor.execute("UPDATE bitten_platform SET name=%s WHERE id=%s", - (self.name, self.id)) - cursor.execute("DELETE FROM bitten_rule WHERE id=%s", (self.id,)) - if self.rules: - cursor.executemany("INSERT INTO bitten_rule VALUES (%s,%s,%s,%s)", - [(self.id, propname, pattern, idx) - for idx, (propname, pattern) - in enumerate(self.rules)]) - - if handle_ta: - db.commit() - - def fetch(cls, env, id, db=None): - """Retrieve an existing target platform from the database by ID.""" - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - cursor.execute("SELECT config,name FROM bitten_platform " - "WHERE id=%s", (id,)) - row = cursor.fetchone() - if not row: - return None - - platform = TargetPlatform(env, config=row[0], name=row[1]) - platform.id = id - cursor.execute("SELECT propname,pattern FROM bitten_rule " - "WHERE id=%s ORDER BY orderno", (id,)) - for propname, pattern in cursor: - platform.rules.append((propname, pattern)) - return platform - - fetch = classmethod(fetch) - - def select(cls, env, config=None, db=None): - """Retrieve existing target platforms from the database that match the - specified criteria. - """ - if not db: - db = env.get_db_cnx() - - where_clauses = [] - if config is not None: - where_clauses.append(("config=%s", config)) - if where_clauses: - where = "WHERE " + " AND ".join([wc[0] for wc in where_clauses]) - else: - where = "" - - cursor = db.cursor() - cursor.execute("SELECT id FROM bitten_platform %s ORDER BY name" - % where, [wc[1] for wc in where_clauses]) - for (id,) in cursor: - yield TargetPlatform.fetch(env, id) - - select = classmethod(select) - - -class Build(object): - """Representation of a build.""" - - _schema = [ - Table('bitten_build', key='id')[ - Column('id', auto_increment=True), Column('config'), Column('rev'), - Column('rev_time', type='int'), Column('platform', type='int'), - Column('slave'), Column('started', type='int'), - Column('stopped', type='int'), Column('status', size=1), - Index(['config', 'rev', 'slave']) - ], - Table('bitten_slave', key=('build', 'propname'))[ - Column('build', type='int'), Column('propname'), Column('propvalue') - ] - ] - - # Build status codes - PENDING = 'P' - IN_PROGRESS = 'I' - SUCCESS = 'S' - FAILURE = 'F' - - # Standard slave properties - IP_ADDRESS = 'ipnr' - MAINTAINER = 'owner' - OS_NAME = 'os' - OS_FAMILY = 'family' - OS_VERSION = 'version' - MACHINE = 'machine' - PROCESSOR = 'processor' - - def __init__(self, env, config=None, rev=None, platform=None, slave=None, - started=0, stopped=0, rev_time=0, status=PENDING): - """Initialize a new build with the specified attributes. - - To actually create this build in the database, the `insert` method needs - to be called. - """ - self.env = env - self.id = None - self.config = config - self.rev = rev and str(rev) or None - self.platform = platform - self.slave = slave - self.started = started or 0 - self.stopped = stopped or 0 - self.rev_time = rev_time - self.status = status - self.slave_info = {} - - def __repr__(self): - return '<%s %r>' % (type(self).__name__, self.id) - - exists = property(fget=lambda self: self.id is not None, - doc='Whether this build exists in the database') - completed = property(fget=lambda self: self.status != Build.IN_PROGRESS, - doc='Whether the build has been completed') - successful = property(fget=lambda self: self.status == Build.SUCCESS, - doc='Whether the build was successful') - - def delete(self, db=None): - """Remove the build from the database.""" - assert self.exists, 'Cannot delete a non-existing build' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - for step in list(BuildStep.select(self.env, build=self.id)): - step.delete(db=db) - - cursor = db.cursor() - cursor.execute("DELETE FROM bitten_slave WHERE build=%s", (self.id,)) - cursor.execute("DELETE FROM bitten_build WHERE id=%s", (self.id,)) - - if handle_ta: - db.commit() - - def insert(self, db=None): - """Insert a new build into the database.""" - assert not self.exists, 'Cannot insert an existing build' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - assert self.config and self.rev and self.rev_time and self.platform - assert self.status in (self.PENDING, self.IN_PROGRESS, self.SUCCESS, - self.FAILURE) - if not self.slave: - assert self.status == self.PENDING - - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_build (config,rev,rev_time,platform," - "slave,started,stopped,status) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", - (self.config, self.rev, int(self.rev_time), - self.platform, self.slave or '', self.started or 0, - self.stopped or 0, self.status)) - self.id = db.get_last_id(cursor, 'bitten_build') - if self.slave_info: - cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", - [(self.id, name, value) for name, value - in self.slave_info.items()]) - - if handle_ta: - db.commit() - - def update(self, db=None): - """Save changes to an existing build.""" - assert self.exists, 'Cannot update a non-existing build' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - assert self.config and self.rev - assert self.status in (self.PENDING, self.IN_PROGRESS, self.SUCCESS, - self.FAILURE) - if not self.slave: - assert self.status == self.PENDING - - cursor = db.cursor() - cursor.execute("UPDATE bitten_build SET slave=%s,started=%s," - "stopped=%s,status=%s WHERE id=%s", - (self.slave or '', self.started or 0, - self.stopped or 0, self.status, self.id)) - cursor.execute("DELETE FROM bitten_slave WHERE build=%s", (self.id,)) - if self.slave_info: - cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", - [(self.id, name, value) for name, value - in self.slave_info.items()]) - if handle_ta: - db.commit() - - def fetch(cls, env, id, db=None): - """Retrieve an existing build from the database by ID.""" - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - cursor.execute("SELECT config,rev,rev_time,platform,slave,started," - "stopped,status FROM bitten_build WHERE id=%s", (id,)) - row = cursor.fetchone() - if not row: - return None - - build = Build(env, config=row[0], rev=row[1], rev_time=int(row[2]), - platform=int(row[3]), slave=row[4], - started=row[5] and int(row[5]) or 0, - stopped=row[6] and int(row[6]) or 0, status=row[7]) - build.id = int(id) - cursor.execute("SELECT propname,propvalue FROM bitten_slave " - "WHERE build=%s", (id,)) - for propname, propvalue in cursor: - build.slave_info[propname] = propvalue - return build - - fetch = classmethod(fetch) - - def select(cls, env, config=None, rev=None, platform=None, slave=None, - status=None, db=None): - """Retrieve existing builds from the database that match the specified - criteria. - """ - if not db: - db = env.get_db_cnx() - - where_clauses = [] - if config is not None: - where_clauses.append(("config=%s", config)) - if rev is not None: - where_clauses.append(("rev=%s", rev)) - if platform is not None: - where_clauses.append(("platform=%s", platform)) - if slave is not None: - where_clauses.append(("slave=%s", slave)) - if status is not None: - where_clauses.append(("status=%s", status)) - if where_clauses: - where = "WHERE " + " AND ".join([wc[0] for wc in where_clauses]) - else: - where = "" - - cursor = db.cursor() - cursor.execute("SELECT id FROM bitten_build %s " - "ORDER BY config,rev_time DESC,slave" - % where, [wc[1] for wc in where_clauses]) - for (id,) in cursor: - yield Build.fetch(env, id) - select = classmethod(select) - - -class BuildStep(object): - """Represents an individual step of an executed build.""" - - _schema = [ - Table('bitten_step', key=('build', 'name'))[ - Column('build', type='int'), Column('name'), Column('description'), - Column('status', size=1), Column('started', type='int'), - Column('stopped', type='int') - ], - Table('bitten_error', key=('build', 'step', 'orderno'))[ - Column('build', type='int'), Column('step'), Column('message'), - Column('orderno', type='int') - ] - ] - - # Step status codes - SUCCESS = 'S' - FAILURE = 'F' - - def __init__(self, env, build=None, name=None, description=None, - status=None, started=None, stopped=None): - """Initialize a new build step with the specified attributes. - - To actually create this build step in the database, the `insert` method - needs to be called. - """ - self.env = env - self.build = build - self.name = name - self.description = description - self.status = status - self.started = started - self.stopped = stopped - self.errors = [] - self._exists = False - - exists = property(fget=lambda self: self._exists, - doc='Whether this build step exists in the database') - successful = property(fget=lambda self: self.status == BuildStep.SUCCESS, - doc='Whether the build step was successful') - - def delete(self, db=None): - """Remove the build step from the database.""" - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - for log in list(BuildLog.select(self.env, build=self.build, - step=self.name, db=db)): - log.delete(db=db) - for report in list(Report.select(self.env, build=self.build, - step=self.name, db=db)): - report.delete(db=db) - - cursor = db.cursor() - cursor.execute("DELETE FROM bitten_step WHERE build=%s AND name=%s", - (self.build, self.name)) - cursor.execute("DELETE FROM bitten_error WHERE build=%s AND step=%s", - (self.build, self.name)) - - if handle_ta: - db.commit() - self._exists = False - - def insert(self, db=None): - """Insert a new build step into the database.""" - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - assert self.build and self.name - assert self.status in (self.SUCCESS, self.FAILURE) - - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_step (build,name,description,status," - "started,stopped) VALUES (%s,%s,%s,%s,%s,%s)", - (self.build, self.name, self.description or '', - self.status, self.started or 0, self.stopped or 0)) - if self.errors: - cursor.executemany("INSERT INTO bitten_error (build,step,message," - "orderno) VALUES (%s,%s,%s,%s)", - [(self.build, self.name, message, idx) - for idx, message in enumerate(self.errors)]) - - if handle_ta: - db.commit() - self._exists = True - - def fetch(cls, env, build, name, db=None): - """Retrieve an existing build from the database by build ID and step - name.""" - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - cursor.execute("SELECT description,status,started,stopped " - "FROM bitten_step WHERE build=%s AND name=%s", - (build, name)) - row = cursor.fetchone() - if not row: - return None - step = BuildStep(env, build, name, row[0] or '', row[1], - row[2] and int(row[2]), row[3] and int(row[3])) - step._exists = True - - cursor.execute("SELECT message FROM bitten_error WHERE build=%s " - "AND step=%s ORDER BY orderno", (build, name)) - for row in cursor: - step.errors.append(row[0] or '') - return step - - fetch = classmethod(fetch) - - def select(cls, env, build=None, name=None, status=None, db=None): - """Retrieve existing build steps from the database that match the - specified criteria. - """ - if not db: - db = env.get_db_cnx() - - assert status in (None, BuildStep.SUCCESS, BuildStep.FAILURE) - - where_clauses = [] - if build is not None: - where_clauses.append(("build=%s", build)) - if name is not None: - where_clauses.append(("name=%s", name)) - if status is not None: - where_clauses.append(("status=%s", status)) - if where_clauses: - where = "WHERE " + " AND ".join([wc[0] for wc in where_clauses]) - else: - where = "" - - cursor = db.cursor() - cursor.execute("SELECT build,name FROM bitten_step %s ORDER BY stopped" - % where, [wc[1] for wc in where_clauses]) - for build, name in cursor: - yield BuildStep.fetch(env, build, name, db=db) - - select = classmethod(select) - - -class BuildLog(object): - """Represents a build log.""" - - _schema = [ - Table('bitten_log', key='id')[ - Column('id', auto_increment=True), Column('build', type='int'), - Column('step'), Column('generator'), Column('orderno', type='int'), - Index(['build', 'step']) - ], - Table('bitten_log_message', key=('log', 'line'))[ - Column('log', type='int'), Column('line', type='int'), - Column('level', size=1), Column('message') - ] - ] - - # Message levels - DEBUG = 'D' - INFO = 'I' - WARNING = 'W' - ERROR = 'E' - - def __init__(self, env, build=None, step=None, generator=None, - orderno=None): - """Initialize a new build log with the specified attributes. - - To actually create this build log in the database, the `insert` method - needs to be called. - """ - self.env = env - self.id = None - self.build = build - self.step = step - self.generator = generator or '' - self.orderno = orderno and int(orderno) or 0 - self.messages = [] - - exists = property(fget=lambda self: self.id is not None, - doc='Whether this build log exists in the database') - - def delete(self, db=None): - """Remove the build log from the database.""" - assert self.exists, 'Cannot delete a non-existing build log' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - cursor = db.cursor() - cursor.execute("DELETE FROM bitten_log_message WHERE log=%s", - (self.id,)) - cursor.execute("DELETE FROM bitten_log WHERE id=%s", (self.id,)) - - if handle_ta: - db.commit() - self.id = None - - def insert(self, db=None): - """Insert a new build log into the database.""" - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - assert self.build and self.step - - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_log (build,step,generator,orderno) " - "VALUES (%s,%s,%s,%s)", (self.build, self.step, - self.generator, self.orderno)) - id = db.get_last_id(cursor, 'bitten_log') - if self.messages: - cursor.executemany("INSERT INTO bitten_log_message " - "(log,line,level,message) VALUES (%s,%s,%s,%s)", - [(id, idx, msg[0], msg[1]) for idx, msg in - enumerate(self.messages)]) - - if handle_ta: - db.commit() - self.id = id - - def fetch(cls, env, id, db=None): - """Retrieve an existing build from the database by ID.""" - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - cursor.execute("SELECT build,step,generator,orderno FROM bitten_log " - "WHERE id=%s", (id,)) - row = cursor.fetchone() - if not row: - return None - log = BuildLog(env, int(row[0]), row[1], row[2], row[3]) - log.id = id - cursor.execute("SELECT level,message FROM bitten_log_message " - "WHERE log=%s ORDER BY line", (id,)) - log.messages = cursor.fetchall() or [] - - return log - - fetch = classmethod(fetch) - - def select(cls, env, build=None, step=None, generator=None, db=None): - """Retrieve existing build logs from the database that match the - specified criteria. - """ - if not db: - db = env.get_db_cnx() - - where_clauses = [] - if build is not None: - where_clauses.append(("build=%s", build)) - if step is not None: - where_clauses.append(("step=%s", step)) - if generator is not None: - where_clauses.append(("generator=%s", generator)) - if where_clauses: - where = "WHERE " + " AND ".join([wc[0] for wc in where_clauses]) - else: - where = "" - - cursor = db.cursor() - cursor.execute("SELECT id FROM bitten_log %s ORDER BY orderno" - % where, [wc[1] for wc in where_clauses]) - for (id, ) in cursor: - yield BuildLog.fetch(env, id, db=db) - - select = classmethod(select) - - -class Report(object): - """Represents a generated report.""" - - _schema = [ - Table('bitten_report', key='id')[ - Column('id', auto_increment=True), Column('build', type='int'), - Column('step'), Column('category'), Column('generator'), - Index(['build', 'step', 'category']) - ], - Table('bitten_report_item', key=('report', 'item', 'name'))[ - Column('report', type='int'), Column('item', type='int'), - Column('name'), Column('value') - ] - ] - - def __init__(self, env, build=None, step=None, category=None, - generator=None): - """Initialize a new report with the specified attributes. - - To actually create this build log in the database, the `insert` method - needs to be called. - """ - self.env = env - self.id = None - self.build = build - self.step = step - self.category = category - self.generator = generator or '' - self.items = [] - - exists = property(fget=lambda self: self.id is not None, - doc='Whether this report exists in the database') - - def delete(self, db=None): - """Remove the report from the database.""" - assert self.exists, 'Cannot delete a non-existing report' - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - cursor = db.cursor() - cursor.execute("DELETE FROM bitten_report_item WHERE report=%s", - (self.id,)) - cursor.execute("DELETE FROM bitten_report WHERE id=%s", (self.id,)) - - if handle_ta: - db.commit() - self.id = None - - def insert(self, db=None): - """Insert a new build log into the database.""" - if not db: - db = self.env.get_db_cnx() - handle_ta = True - else: - handle_ta = False - - assert self.build and self.step and self.category - - # Enforce uniqueness of build-step-category. - # This should be done by the database, but the DB schema helpers in Trac - # currently don't support UNIQUE() constraints - assert not list(Report.select(self.env, build=self.build, - step=self.step, category=self.category, - db=db)), 'Report already exists' - - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_report " - "(build,step,category,generator) VALUES (%s,%s,%s,%s)", - (self.build, self.step, self.category, self.generator)) - id = db.get_last_id(cursor, 'bitten_report') - for idx, item in enumerate([item for item in self.items if item]): - cursor.executemany("INSERT INTO bitten_report_item " - "(report,item,name,value) VALUES (%s,%s,%s,%s)", - [(id, idx, key, value) for key, value - in item.items()]) - - if handle_ta: - db.commit() - self.id = id - - def fetch(cls, env, id, db=None): - """Retrieve an existing build from the database by ID.""" - if not db: - db = env.get_db_cnx() - - cursor = db.cursor() - cursor.execute("SELECT build,step,category,generator " - "FROM bitten_report WHERE id=%s", (id,)) - row = cursor.fetchone() - if not row: - return None - report = Report(env, int(row[0]), row[1], row[2] or '', row[3] or '') - report.id = id - - cursor.execute("SELECT item,name,value FROM bitten_report_item " - "WHERE report=%s ORDER BY item", (id,)) - items = {} - for item, name, value in cursor: - items.setdefault(item, {})[name] = value - report.items = items.values() - - return report - - fetch = classmethod(fetch) - - def select(cls, env, config=None, build=None, step=None, category=None, - db=None): - """Retrieve existing reports from the database that match the specified - criteria. - """ - where_clauses = [] - joins = [] - if config is not None: - where_clauses.append(("config=%s", config)) - joins.append("INNER JOIN bitten_build ON (bitten_build.id=build)") - if build is not None: - where_clauses.append(("build=%s", build)) - if step is not None: - where_clauses.append(("step=%s", step)) - if category is not None: - where_clauses.append(("category=%s", category)) - - if where_clauses: - where = "WHERE " + " AND ".join([wc[0] for wc in where_clauses]) - else: - where = "" - - if not db: - db = env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT bitten_report.id FROM bitten_report %s %s " - "ORDER BY category" % (' '.join(joins), where), - [wc[1] for wc in where_clauses]) - for (id, ) in cursor: - yield Report.fetch(env, id, db=db) - - select = classmethod(select) - - -schema = BuildConfig._schema + TargetPlatform._schema + Build._schema + \ - BuildStep._schema + BuildLog._schema + Report._schema -schema_version = 7 diff --git a/trac-0.11/bitten/queue.py b/trac-0.11/bitten/queue.py deleted file mode 100644 --- a/trac-0.11/bitten/queue.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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. - -"""Implements the scheduling of builds for a project. - -This module provides the functionality for scheduling builds for a specific -Trac environment. It is used by both the build master and the web interface to -get the list of required builds (revisions not built yet). - -Furthermore, the `BuildQueue` class is used by the build master to determine -the next pending build, and to match build slaves against configured target -platforms. -""" - -from datetime import datetime -from itertools import ifilter -import logging -import re -import time - -from trac.versioncontrol import NoSuchNode -from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep - -__docformat__ = 'restructuredtext en' - -log = logging.getLogger('bitten.queue') - - -def collect_changes(repos, config, db=None): - """Collect all changes for a build configuration that either have already - been built, or still need to be built. - - This function is a generator that yields ``(platform, rev, build)`` tuples, - where ``platform`` is a `TargetPlatform` object, ``rev`` is the identifier - of the changeset, and ``build`` is a `Build` object or `None`. - - :param repos: the version control repository - :param config: the build configuration - :param db: a database connection (optional) - """ - env = config.env - if not db: - db = env.get_db_cnx() - try: - node = repos.get_node(config.path) - except NoSuchNode, e: - env.log.warn('Node for configuration %r not found', config.name, - exc_info=True) - return - - for path, rev, chg in node.get_history(): - - # Don't follow moves/copies - if path != repos.normalize_path(config.path): - break - - # Stay within the limits of the build config - if config.min_rev and repos.rev_older_than(rev, config.min_rev): - break - if config.max_rev and repos.rev_older_than(config.max_rev, rev): - continue - - # Make sure the repository directory isn't empty at this - # revision - old_node = repos.get_node(path, rev) - is_empty = True - for entry in old_node.get_entries(): - is_empty = False - break - if is_empty: - continue - - # For every target platform, check whether there's a build - # of this revision - for platform in TargetPlatform.select(env, config.name, db=db): - builds = list(Build.select(env, config.name, rev, platform.id, - db=db)) - if builds: - build = builds[0] - else: - build = None - - yield platform, rev, build - - -class BuildQueue(object): - """Enapsulates the build queue of an environment. - - A build queue manages the the registration of build slaves and detection of - repository revisions that need to be built. - """ - - def __init__(self, env, build_all=False, stabilize_wait=0, timeout=0): - """Create the build queue. - - :param env: the Trac environment - :param build_all: whether older revisions should be built - :param stabilize_wait: The time in seconds to wait before considering - the repository stable to create a build in the queue. - :param timeout: the time in seconds after which an in-progress build - should be considered orphaned, and reset to pending - state - """ - self.env = env - self.log = env.log - self.build_all = build_all - self.stabilize_wait = stabilize_wait - self.timeout = timeout - - # Build scheduling - - def get_build_for_slave(self, name, properties): - """Check whether one of the pending builds can be built by the build - slave. - - :param name: the name of the slave - :type name: `basestring` - :param properties: the slave configuration - :type properties: `dict` - :return: the allocated build, or `None` if no build was found - :rtype: `Build` - """ - log.debug('Checking for pending builds...') - - db = self.env.get_db_cnx() - repos = self.env.get_repository() - - self.reset_orphaned_builds() - - # Iterate through pending builds by descending revision timestamp, to - # avoid the first configuration/platform getting all the builds - platforms = [p.id for p in self.match_slave(name, properties)] - build = None - builds_to_delete = [] - for build in Build.select(self.env, status=Build.PENDING, db=db): - if self.should_delete_build(build, repos): - self.log.info('Scheduling build %d for deletion', build.id) - builds_to_delete.append(build) - elif build.platform in platforms: - break - else: - self.log.debug('No pending builds.') - build = None - - # delete any obsolete builds - for build_to_delete in builds_to_delete: - build_to_delete.delete(db=db) - - if build: - build.slave = name - build.slave_info.update(properties) - build.status = Build.IN_PROGRESS - build.update(db=db) - - if build or builds_to_delete: - db.commit() - - return build - - def match_slave(self, name, properties): - """Match a build slave against available target platforms. - - :param name: the name of the slave - :type name: `basestring` - :param properties: the slave configuration - :type properties: `dict` - :return: the list of platforms the slave matched - """ - platforms = [] - - for config in BuildConfig.select(self.env): - for platform in TargetPlatform.select(self.env, config=config.name): - match = True - for propname, pattern in ifilter(None, platform.rules): - try: - propvalue = properties.get(propname) - if not propvalue or not re.match(pattern, propvalue): - match = False - break - except re.error: - self.log.error('Invalid platform matching pattern "%s"', - pattern, exc_info=True) - match = False - break - if match: - self.log.debug('Slave %r matched target platform %r of ' - 'build configuration %r', name, - platform.name, config.name) - platforms.append(platform) - - if not platforms: - self.log.warning('Slave %r matched none of the target platforms', - name) - - return platforms - - def populate(self): - """Add a build for the next change on each build configuration to the - queue. - - The next change is the latest repository check-in for which there isn't - a corresponding build on each target platform. Repeatedly calling this - method will eventually result in the entire change history of the build - configuration being in the build queue. - """ - repos = self.env.get_repository() - if hasattr(repos, 'sync'): - repos.sync() - - db = self.env.get_db_cnx() - builds = [] - - for config in BuildConfig.select(self.env, db=db): - platforms = [] - for platform, rev, build in collect_changes(repos, config, db): - - if not self.build_all and platform.id in platforms: - # We've seen this platform already, so these are older - # builds that should only be built if built_all=True - self.log.debug('Ignoring older revisions for configuration ' - '%r on %r', config.name, platform.name) - break - - platforms.append(platform.id) - - if build is None: - self.log.info('Enqueuing build of configuration "%s" at ' - 'revision [%s] on %s', config.name, rev, - platform.name) - - rev_time = repos.get_changeset(rev).date - if isinstance(rev_time, datetime): # Trac>=0.11 - from trac.util.datefmt import to_timestamp - rev_time = to_timestamp(rev_time) - age = int(time.time()) - rev_time - if self.stabilize_wait and age < self.stabilize_wait: - self.log.info('Delaying build of revision %s until %s ' - 'seconds pass. Current age is: %s ' - 'seconds' % (rev, self.stabilize_wait, - age)) - continue - - build = Build(self.env, config=config.name, - platform=platform.id, rev=str(rev), - rev_time=rev_time) - builds.append(build) - - for build in builds: - build.insert(db=db) - - db.commit() - - def reset_orphaned_builds(self): - """Reset all in-progress builds to ``PENDING`` state if they've been - running so long that the configured timeout has been reached. - - This is used to cleanup after slaves that have unexpectedly cancelled - a build without notifying the master, or are for some other reason not - reporting back status updates. - """ - if not self.timeout: - # If no timeout is set, none of the in-progress builds can be - # considered orphaned - return - - db = self.env.get_db_cnx() - now = int(time.time()) - for build in Build.select(self.env, status=Build.IN_PROGRESS, db=db): - if now - build.started < self.timeout: - # This build has not reached the timeout yet, assume it's still - # being executed - # FIXME: ideally, we'd base this check on the last activity on - # the build, not the start time - continue - build.status = Build.PENDING - build.slave = None - build.slave_info = {} - build.started = 0 - for step in list(BuildStep.select(self.env, build=build.id, db=db)): - step.delete(db=db) - build.update(db=db) - db.commit() - - def should_delete_build(self, build, repos): - # Ignore pending builds for deactived build configs - config = BuildConfig.fetch(self.env, build.config) - if not config.active: - log.info('Dropping build of configuration "%s" at ' - 'revision [%s] on "%s" because the configuration is ' - 'deactivated', config.name, build.rev, - TargetPlatform.fetch(self.env, build.platform).name) - return True - - # Stay within the revision limits of the build config - if (config.min_rev and repos.rev_older_than(build.rev, - config.min_rev)) \ - or (config.max_rev and repos.rev_older_than(config.max_rev, - build.rev)): - # This minimum and/or maximum revision has changed since - # this build was enqueued, so drop it - log.info('Dropping build of configuration "%s" at revision [%s] on ' - '"%s" because it is outside of the revision range of the ' - 'configuration', config.name, build.rev, - TargetPlatform.fetch(self.env, build.platform).name) - return True - - return False diff --git a/trac-0.11/bitten/recipe.py b/trac-0.11/bitten/recipe.py deleted file mode 100644 --- a/trac-0.11/bitten/recipe.py +++ /dev/null @@ -1,278 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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. - -"""Execution of build recipes. - -This module provides various classes that can be used to process build recipes, -most importantly the `Recipe` class. -""" - -import keyword -import logging -import os -try: - set -except NameError: - from sets import Set as set - -from pkg_resources import WorkingSet -from bitten.build import BuildError -from bitten.build.config import Configuration -from bitten.util import xmlio - -__all__ = ['Context', 'Recipe', 'Step', 'InvalidRecipeError'] -__docformat__ = 'restructuredtext en' - -log = logging.getLogger('bitten.recipe') - - -class InvalidRecipeError(Exception): - """Exception raised when a recipe is not valid.""" - - -class Context(object): - """The context in which a build is executed.""" - - step = None # The current step - generator = None # The current generator (namespace#name) - - def __init__(self, basedir, config=None, vars=None): - """Initialize the context. - - :param basedir: a string containing the working directory for the build. - (may be a pattern for replacement ex: 'build_${build}' - :param config: the build slave configuration - :type config: `Configuration` - """ - self.config = config or Configuration() - self.vars = vars or {} - self.output = [] - self.basedir = os.path.realpath(self.config.interpolate(basedir, - **self.vars)) - - def run(self, step, namespace, name, attr): - """Run the specified recipe command. - - :param step: the build step that the command belongs to - :param namespace: the namespace URI of the command - :param name: the local tag name of the command - :param attr: a dictionary containing the attributes defined on the - command element - """ - self.step = step - - try: - function = None - qname = '#'.join(filter(None, [namespace, name])) - if namespace: - group = 'bitten.recipe_commands' - for entry_point in WorkingSet().iter_entry_points(group, qname): - function = entry_point.load() - break - elif name == 'report': - function = Context.report_file - if not function: - raise InvalidRecipeError('Unknown recipe command %s' % qname) - - def escape(name): - name = name.replace('-', '_') - if keyword.iskeyword(name) or name in __builtins__: - name = name + '_' - return name - args = dict([(escape(name), - self.config.interpolate(attr[name], **self.vars)) - for name in attr]) - - self.generator = qname - log.debug('Executing %s with arguments: %s', function, args) - function(self, **args) - - finally: - self.generator = None - self.step = None - - def error(self, message): - """Record an error message. - - :param message: a string containing the error message. - """ - self.output.append((Recipe.ERROR, None, self.generator, message)) - - def log(self, xml): - """Record log output. - - :param xml: an XML fragment containing the log messages - """ - self.output.append((Recipe.LOG, None, self.generator, xml)) - - def report(self, category, xml): - """Record report data. - - :param category: the name of category of the report - :param xml: an XML fragment containing the report data - """ - self.output.append((Recipe.REPORT, category, self.generator, xml)) - - def report_file(self, category=None, file_=None): - """Read report data from a file and record it. - - :param category: the name of the category of the report - :param file\_: the path to the file containing the report data, relative - to the base directory - """ - filename = self.resolve(file_) - try: - fileobj = file(filename, 'r') - try: - xml_elem = xmlio.Fragment() - for child in xmlio.parse(fileobj).children(): - child_elem = xmlio.Element(child.name, **dict([ - (name, value) for name, value in child.attr.items() - if value is not None - ])) - xml_elem.append(child_elem[ - [xmlio.Element(grandchild.name)[grandchild.gettext()] - for grandchild in child.children()] - ]) - self.output.append((Recipe.REPORT, category, None, xml_elem)) - finally: - fileobj.close() - except xmlio.ParseError, e: - self.error('Failed to parse %s report at %s: %s' - % (category, filename, e)) - except IOError, e: - self.error('Failed to read %s report at %s: %s' - % (category, filename, e)) - - def resolve(self, *path): - """Return the path of a file relative to the base directory. - - Accepts any number of positional arguments, which are joined using the - system path separator to form the path. - """ - return os.path.normpath(os.path.join(self.basedir, *path)) - - -class Step(object): - """Represents a single step of a build recipe. - - Iterate over an object of this class to get the commands to execute, and - their keyword arguments. - """ - - def __init__(self, elem): - """Create the step. - - :param elem: the XML element representing the step - :type elem: `ParsedElement` - """ - self._elem = elem - self.id = elem.attr['id'] - self.description = elem.attr.get('description') - self.onerror = elem.attr.get('onerror', 'fail') - - def __repr__(self): - return '<%s %r>' % (type(self).__name__, self.id) - - def execute(self, ctxt): - """Execute this step in the given context. - - :param ctxt: the build context - :type ctxt: `Context` - """ - for child in self._elem: - ctxt.run(self, child.namespace, child.name, child.attr) - - errors = [] - while ctxt.output: - type, category, generator, output = ctxt.output.pop(0) - yield type, category, generator, output - if type == Recipe.ERROR: - errors.append((generator, output)) - if errors: - if self.onerror != 'ignore': - raise BuildError('Build step %s failed' % self.id) - log.warning('Continuing despite errors in step %s (%s)', self.id, - ', '.join([error[1] for error in errors])) - - -class Recipe(object): - """A build recipe. - - Iterate over this object to get the individual build steps in the order - they have been defined in the recipe file. - """ - - ERROR = 'error' - LOG = 'log' - REPORT = 'report' - - def __init__(self, xml, basedir=os.getcwd(), config=None): - """Create the recipe. - - :param xml: the XML document representing the recipe - :type xml: `ParsedElement` - :param basedir: the base directory for the build - :param config: the slave configuration (optional) - :type config: `Configuration` - """ - assert isinstance(xml, xmlio.ParsedElement) - vars = dict([(name, value) for name, value in xml.attr.items() - if not name.startswith('xmlns')]) - self.ctxt = Context(basedir, config, vars) - self._root = xml - - def __iter__(self): - """Iterate over the individual steps of the recipe.""" - for child in self._root.children('step'): - yield Step(child) - - def validate(self): - """Validate the recipe. - - This method checks a number of constraints: - - the name of the root element must be "build" - - the only permitted child elements or the root element with the name - "step" - - the recipe must contain at least one step - - step elements must have a unique "id" attribute - - a step must contain at least one nested command - - commands must not have nested content - - :raise InvalidRecipeError: in case any of the above contraints is - violated - """ - if self._root.name != 'build': - raise InvalidRecipeError('Root element must be ') - steps = list(self._root.children()) - if not steps: - raise InvalidRecipeError('Recipe defines no build steps') - - step_ids = set() - for step in steps: - if step.name != 'step': - raise InvalidRecipeError('Only elements allowed at ' - 'top level of recipe') - if not step.attr.get('id'): - raise InvalidRecipeError('Steps must have an "id" attribute') - - if step.attr['id'] in step_ids: - raise InvalidRecipeError('Duplicate step ID "%s"' % - step.attr['id']) - step_ids.add(step.attr['id']) - - cmds = list(step.children()) - if not cmds: - raise InvalidRecipeError('Step "%s" has no recipe commands' % - step.attr['id']) - for cmd in cmds: - if len(list(cmd.children())): - raise InvalidRecipeError('Recipe command <%s> has nested ' - 'content' % cmd.name) diff --git a/trac-0.11/bitten/report/__init__.py b/trac-0.11/bitten/report/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/report/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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. - -__docformat__ = 'restructuredtext en' diff --git a/trac-0.11/bitten/report/coverage.py b/trac-0.11/bitten/report/coverage.py deleted file mode 100644 --- a/trac-0.11/bitten/report/coverage.py +++ /dev/null @@ -1,213 +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.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' - - -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 - - data = {'title': 'Test Coverage', - '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.html', data - - -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)) - - units = [] - 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, rev=build.rev, annotate='coverage') - units.append(d) - total_loc += loc - total_cov += loc * cov - - coverage = 0 - if total_loc != 0: - coverage = total_cov // total_loc - - return 'bitten_summary_coverage.html', { - 'units': units, - 'totals': {'loc': total_loc, 'cov': int(coverage)} - } - - -# 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') - '5' - >>> annotate_row(2, '') - '' - >>> annotate_row(3, 'y = x') - '0' - """ - 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')) diff --git a/trac-0.11/bitten/report/testing.py b/trac-0.11/bitten/report/testing.py deleted file mode 100644 --- a/trac-0.11/bitten/report/testing.py +++ /dev/null @@ -1,122 +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.api import IReportChartGenerator, IReportSummarizer - -__docformat__ = 'restructuredtext en' - - -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]) - - data = {'title': 'Unit Tests', - '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.html', data - - -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)) - - fixtures = [] - total_success, total_failure, total_error = 0, 0, 0 - for fixture, file, num_success, num_failure, num_error in cursor: - fixtures.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: - fixtures[-1]['href'] = req.href.browser(config.path, file) - - data = {'fixtures': fixtures, - 'totals': {'success': total_success, 'failure': total_failure, - 'error': total_error} - } - return 'bitten_summary_tests.html', data diff --git a/trac-0.11/bitten/report/tests/__init__.py b/trac-0.11/bitten/report/tests/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/report/tests/__init__.py +++ /dev/null @@ -1,22 +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 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/trac-0.11/bitten/report/tests/coverage.py b/trac-0.11/bitten/report/tests/coverage.py deleted file mode 100644 --- a/trac-0.11/bitten/report/tests/coverage.py +++ /dev/null @@ -1,110 +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 doctest -import unittest - -from trac.db import DatabaseManager -from trac.test import EnvironmentStub, Mock -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 = env_stub_with_tables() - self.env.path = '' - - def test_supported_categories(self): - generator = TestCoverageChartGenerator(self.env) - self.assertEqual(['coverage'], generator.get_supported_categories()) - - def test_no_reports(self): - req = Mock() - config = Mock(name='trunk') - generator = TestCoverageChartGenerator(self.env) - template, data = generator.generate_chart_data(req, config, 'coverage') - self.assertEqual('bitten_chart_coverage.html', template) - self.assertEqual('Test Coverage', data['title']) - self.assertEqual('', data['data'][0][0]) - self.assertEqual('Lines of code', data['data'][1][0]) - self.assertEqual('Coverage', data['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() - generator = TestCoverageChartGenerator(self.env) - template, data = generator.generate_chart_data(req, config, 'coverage') - self.assertEqual('bitten_chart_coverage.html', template) - self.assertEqual('Test Coverage', data['title']) - self.assertEqual('', data['data'][0][0]) - self.assertEqual('[123]', data['data'][0][1]) - self.assertEqual('Lines of code', data['data'][1][0]) - self.assertEqual(12, data['data'][1][1]) - self.assertEqual('Coverage', data['data'][2][0]) - self.assertEqual(3, data['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() - generator = TestCoverageChartGenerator(self.env) - template, data = generator.generate_chart_data(req, config, 'coverage') - self.assertEqual('bitten_chart_coverage.html', template) - self.assertEqual('Test Coverage', data['title']) - self.assertEqual('', data['data'][0][0]) - self.assertEqual('[123]', data['data'][0][1]) - self.assertEqual('Lines of code', data['data'][1][0]) - self.assertEqual(12, data['data'][1][1]) - self.assertEqual('Coverage', data['data'][2][0]) - self.assertEqual(6, data['data'][2][1]) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestCoverageChartGeneratorTestCase)) - suite.addTest(doctest.DocTestSuite(coverage)) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/report/tests/testing.py b/trac-0.11/bitten/report/tests/testing.py deleted file mode 100644 --- a/trac-0.11/bitten/report/tests/testing.py +++ /dev/null @@ -1,107 +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.db import DatabaseManager -from trac.test import EnvironmentStub, Mock -from bitten.model import * -from bitten.report.testing import TestResultsChartGenerator - - -class TestResultsChartGeneratorTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - 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 = TestResultsChartGenerator(self.env) - self.assertEqual(['test'], generator.get_supported_categories()) - - def test_no_reports(self): - req = Mock() - config = Mock(name='trunk') - generator = TestResultsChartGenerator(self.env) - template, data = generator.generate_chart_data(req, config, 'test') - self.assertEqual('bitten_chart_tests.html', template) - self.assertEqual('Unit Tests', data['title']) - self.assertEqual('', data['data'][0][0]) - self.assertEqual('Total', data['data'][1][0]) - self.assertEqual('Failures', data['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() - generator = TestResultsChartGenerator(self.env) - template, data = generator.generate_chart_data(req, config, 'test') - self.assertEqual('bitten_chart_tests.html', template) - self.assertEqual('Unit Tests', data['title']) - self.assertEqual('', data['data'][0][0]) - self.assertEqual('[123]', data['data'][0][1]) - self.assertEqual('Total', data['data'][1][0]) - self.assertEqual(3, data['data'][1][1]) - self.assertEqual('Failures', data['data'][2][0]) - self.assertEqual(1, data['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() - generator = TestResultsChartGenerator(self.env) - template, data = generator.generate_chart_data(req, config, 'test') - self.assertEqual('bitten_chart_tests.html', template) - self.assertEqual('Unit Tests', data['title']) - self.assertEqual('', data['data'][0][0]) - self.assertEqual('[123]', data['data'][0][1]) - self.assertEqual('Total', data['data'][1][0]) - self.assertEqual(3, data['data'][1][1]) - self.assertEqual('Failures', data['data'][2][0]) - self.assertEqual(2, data['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/trac-0.11/bitten/slave.py b/trac-0.11/bitten/slave.py deleted file mode 100755 --- a/trac-0.11/bitten/slave.py +++ /dev/null @@ -1,410 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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. - -"""Implementation of the build slave.""" - -from datetime import datetime -import errno -import urllib2 -import logging -import os -import platform -import shutil -import socket -import tempfile -import time - -from bitten.build import BuildError -from bitten.build.config import Configuration -from bitten.recipe import Recipe -from bitten.util import xmlio - -EX_OK = getattr(os, "EX_OK", 0) -EX_UNAVAILABLE = getattr(os, "EX_UNAVAILABLE", 69) -EX_PROTOCOL = getattr(os, "EX_PROTOCOL", 76) - -__all__ = ['BuildSlave', 'ExitSlave'] -__docformat__ = 'restructuredtext en' - -log = logging.getLogger('bitten.slave') - -# List of network errors which are usually temporary and non critical. -temp_net_errors = [errno.ENETUNREACH, errno.ENETDOWN, errno.ETIMEDOUT, - errno.ECONNREFUSED] - -def _rmtree(root): - """Catch shutil.rmtree failures on Windows when files are read-only.""" - def _handle_error(fn, path, excinfo): - os.chmod(path, 0666) - fn(path) - return shutil.rmtree(root, onerror=_handle_error) - -class SaneHTTPErrorProcessor(urllib2.HTTPErrorProcessor): - "The HTTPErrorProcessor defined in urllib needs some love." - - def http_response(self, request, response): - code, msg, hdrs = response.code, response.msg, response.info() - if code >= 300: - response = self.parent.error( - 'http', request, response, code, msg, hdrs) - return response - - -class SaneHTTPRequest(urllib2.Request): - - def __init__(self, method, url, data=None, headers={}): - urllib2.Request.__init__(self, url, data, headers) - self.method = method - - def get_method(self): - if self.method is None: - self.method = self.has_data() and 'POST' or 'GET' - return self.method - - -class BuildSlave(object): - """BEEP initiator implementation for the build slave.""" - - def __init__(self, urls, name=None, config=None, dry_run=False, - work_dir=None, build_dir="build_${build}", - keep_files=False, single_build=False, - poll_interval=300, username=None, password=None, - dump_reports=False): - """Create the build slave instance. - - :param urls: a list of URLs of the build masters to connect to, or a - single-element list containing the path to a build recipe - file - :param name: the name with which this slave should identify itself - :param config: the path to the slave configuration file - :param dry_run: wether the build outcome should not be reported back - to the master - :param work_dir: the working directory to use for build execution - :param build_dir: the pattern to use for naming the build subdir - :param keep_files: whether files and directories created for build - execution should be kept when done - :param single_build: whether this slave should exit after completing a - single build, or continue processing builds forever - :param poll_interval: the time in seconds to wait between requesting - builds from the build master (default is five - minutes) - :param username: the username to use when authentication against the - build master is requested - :param password: the password to use when authentication is needed - :param dump_reports: whether report data should be written to the - standard output, in addition to being transmitted - to the build master - """ - self.urls = urls - self.local = len(urls) == 1 and not urls[0].startswith('http://') \ - and not urls[0].startswith('https://') - if name is None: - name = platform.node().split('.', 1)[0].lower() - self.name = name - self.config = Configuration(config) - self.dry_run = dry_run - if not work_dir: - work_dir = tempfile.mkdtemp(prefix='bitten') - elif not os.path.exists(work_dir): - os.makedirs(work_dir) - self.work_dir = work_dir - self.build_dir = build_dir - self.keep_files = keep_files - self.single_build = single_build - self.poll_interval = poll_interval - self.dump_reports = dump_reports - - if not self.local: - self.opener = urllib2.build_opener(SaneHTTPErrorProcessor) - password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() - if username and password: - log.debug('Enabling authentication with username %r', username) - password_mgr.add_password(None, urls, username, password) - self.opener.add_handler(urllib2.HTTPBasicAuthHandler(password_mgr)) - self.opener.add_handler(urllib2.HTTPDigestAuthHandler(password_mgr)) - - def request(self, method, url, body=None, headers=None): - log.debug('Sending %s request to %r', method, url) - req = SaneHTTPRequest(method, url, body, headers or {}) - try: - return self.opener.open(req) - except urllib2.HTTPError, e: - if e.code >= 300: - log.warning('Server returned error %d: %s', e.code, e.msg) - raise - return e - - def run(self): - if self.local: - fileobj = open(self.urls[0]) - try: - self._execute_build(None, fileobj) - finally: - fileobj.close() - return EX_OK - - urls = [] - while True: - if not urls: - urls[:] = self.urls - url = urls.pop(0) - try: - try: - job_done = self._create_build(url) - if job_done: - continue - except urllib2.HTTPError, e: - # HTTPError doesn't have the "reason" attribute of URLError - log.error(e) - raise ExitSlave(EX_UNAVAILABLE) - except urllib2.URLError, e: - # Is this a temporary network glitch or something a bit - # more severe? - if isinstance(e.reason, socket.error) and \ - e.reason.args[0] in temp_net_errors: - log.warning(e) - else: - log.error(e) - raise ExitSlave(EX_UNAVAILABLE) - except ExitSlave, e: - return e.exit_code - time.sleep(self.poll_interval) - - def quit(self): - log.info('Shutting down') - raise ExitSlave(EX_OK) - - def _create_build(self, url): - xml = xmlio.Element('slave', name=self.name)[ - xmlio.Element('platform', processor=self.config['processor'])[ - self.config['machine'] - ], - xmlio.Element('os', family=self.config['family'], - version=self.config['version'])[ - self.config['os'] - ], - ] - - log.debug('Configured packages: %s', self.config.packages) - for package, properties in self.config.packages.items(): - xml.append(xmlio.Element('package', name=package, **properties)) - - body = str(xml) - log.debug('Sending slave configuration: %s', body) - resp = self.request('POST', url, body, { - 'Content-Length': len(body), - 'Content-Type': 'application/x-bitten+xml' - }) - - if resp.code == 201: - self._initiate_build(resp.info().get('location')) - return True - elif resp.code == 204: - log.info('No pending builds') - return False - else: - log.error('Unexpected response (%d %s)', resp.code, resp.msg) - raise ExitSlave(EX_PROTOCOL) - - def _initiate_build(self, build_url): - log.info('Build pending at %s', build_url) - try: - resp = self.request('GET', build_url) - if resp.code == 200: - self._execute_build(build_url, resp) - else: - log.error('Unexpected response (%d): %s', resp.code, resp.msg) - self._cancel_build(build_url, exit_code=EX_PROTOCOL) - except KeyboardInterrupt: - log.warning('Build interrupted') - self._cancel_build(build_url) - - def _execute_build(self, build_url, fileobj): - build_id = build_url and int(build_url.split('/')[-1]) or 0 - xml = xmlio.parse(fileobj) - try: - recipe = Recipe(xml, os.path.join(self.work_dir, self.build_dir), - self.config) - basedir = recipe.ctxt.basedir - log.debug('Running build in directory %s' % basedir) - if not os.path.exists(basedir): - os.mkdir(basedir) - - for step in recipe: - log.info('Executing build step %r', step.id) - if not self._execute_step(build_url, recipe, step): - log.warning('Stopping build due to failure') - break - else: - log.info('Build completed') - if self.dry_run: - self._cancel_build(build_url) - finally: - if not self.keep_files: - log.debug('Removing build directory %s' % basedir) - _rmtree(basedir) - if self.single_build: - log.info('Exiting after single build completed.') - raise ExitSlave(EX_OK) - - def _execute_step(self, build_url, recipe, step): - failed = False - started = datetime.utcnow() - xml = xmlio.Element('result', step=step.id, time=started.isoformat()) - try: - for type, category, generator, output in \ - step.execute(recipe.ctxt): - if type == Recipe.ERROR: - failed = True - if type == Recipe.REPORT and self.dump_reports: - print output - xml.append(xmlio.Element(type, category=category, - generator=generator)[ - output - ]) - except KeyboardInterrupt: - log.warning('Build interrupted') - self._cancel_build(build_url) - except BuildError, e: - log.error('Build step %r failed (%s)', step.id, e) - failed = True - except Exception, e: - log.error('Internal error in build step %r', step.id, exc_info=True) - failed = True - xml.attr['duration'] = (datetime.utcnow() - started).seconds - if failed: - xml.attr['status'] = 'failure' - log.warning('Build step %r failed', step.id) - else: - xml.attr['status'] = 'success' - log.info('Build step %s completed successfully', step.id) - - if not self.local and not self.dry_run: - try: - resp = self.request('POST', build_url + '/steps/', str(xml), { - 'Content-Type': 'application/x-bitten+xml' - }) - if resp.code != 201: - log.error('Unexpected response (%d): %s', resp.code, - resp.msg) - except KeyboardInterrupt: - log.warning('Build interrupted') - self._cancel_build(build_url) - - return not failed or step.onerror != 'fail' - - def _cancel_build(self, build_url, exit_code=EX_OK): - log.info('Cancelling build at %s', build_url) - if not self.local: - resp = self.request('DELETE', build_url) - if resp.code not in (200, 204): - log.error('Unexpected response (%d): %s', resp.code, resp.msg) - raise ExitSlave(exit_code) - - -class ExitSlave(Exception): - """Exception used internally by the slave to signal that the slave process - should be stopped. - """ - def __init__(self, exit_code): - self.exit_code = exit_code - Exception.__init__(self) - - -def main(): - """Main entry point for running the build slave.""" - from bitten import __version__ as VERSION - from optparse import OptionParser - - parser = OptionParser(usage='usage: %prog [options] url1 [url2] ...', - version='%%prog %s' % VERSION) - parser.add_option('--name', action='store', dest='name', - help='name of this slave (defaults to host name)') - parser.add_option('-f', '--config', action='store', dest='config', - metavar='FILE', help='path to configuration file') - parser.add_option('-u', '--user', dest='username', - help='the username to use for authentication') - parser.add_option('-p', '--password', dest='password', - help='the password to use when authenticating') - - group = parser.add_option_group('building') - group.add_option('-d', '--work-dir', action='store', dest='work_dir', - metavar='DIR', help='working directory for builds') - group.add_option('--build-dir', action='store', dest='build_dir', - default = 'build_${config}_${build}', - help='name pattern for the build dir to use inside the ' - 'working dir ["%default"]') - group.add_option('-k', '--keep-files', action='store_true', - dest='keep_files', - help='don\'t delete files after builds') - group.add_option('-s', '--single', action='store_true', - dest='single_build', - help='exit after completing a single build') - group.add_option('-n', '--dry-run', action='store_true', dest='dry_run', - help='don\'t report results back to master') - group.add_option('-i', '--interval', dest='interval', metavar='SECONDS', - type='int', help='time to wait between requesting builds') - group = parser.add_option_group('logging') - group.add_option('-l', '--log', dest='logfile', metavar='FILENAME', - help='write log messages to FILENAME') - group.add_option('-v', '--verbose', action='store_const', dest='loglevel', - const=logging.DEBUG, help='print as much as possible') - group.add_option('-q', '--quiet', action='store_const', dest='loglevel', - const=logging.WARN, help='print as little as possible') - group.add_option('--dump-reports', action='store_true', dest='dump_reports', - help='whether report data should be printed') - - parser.set_defaults(dry_run=False, keep_files=False, - loglevel=logging.INFO, single_build=False, - dump_reports=False, interval=300) - options, args = parser.parse_args() - - if len(args) < 1: - parser.error('incorrect number of arguments') - urls = args - - logger = logging.getLogger('bitten') - logger.setLevel(options.loglevel) - handler = logging.StreamHandler() - handler.setLevel(options.loglevel) - formatter = logging.Formatter('[%(levelname)-8s] %(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - if options.logfile: - handler = logging.FileHandler(options.logfile) - handler.setLevel(options.loglevel) - formatter = logging.Formatter('%(asctime)s [%(name)s] %(levelname)s: ' - '%(message)s') - handler.setFormatter(formatter) - logger.addHandler(handler) - - slave = BuildSlave(urls, name=options.name, config=options.config, - dry_run=options.dry_run, work_dir=options.work_dir, - build_dir=options.build_dir, - keep_files=options.keep_files, - single_build=options.single_build, - poll_interval=options.interval, - username=options.username, password=options.password, - dump_reports=options.dump_reports) - try: - try: - exit_code = slave.run() - except KeyboardInterrupt: - slave.quit() - except ExitSlave, e: - exit_code = e.exit_code - - if not options.work_dir: - log.debug('Removing temporary directory %s' % slave.work_dir) - _rmtree(slave.work_dir) - return exit_code - -if __name__ == '__main__': - sys.exit(main()) diff --git a/trac-0.11/bitten/templates/bitten_admin_configs.html b/trac-0.11/bitten/templates/bitten_admin_configs.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_admin_configs.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - Manage Build Configurations - - -

Manage Build Configurations

-
- - - - - - - -
- -

- -

- -
- -

- -

-
-
- Repository Mapping - - - - - - - - -
-
-
- - -
-
-

Target Platforms

- - - - - - - - - - - - - - - -
 NameRules
(No Platforms)
- - - $platform.name - -
    -
  • - - $rule.property ~= - $rule.pattern - -
  • -
-
-
- - -
-
-
- -
-
-
- Rules - - - - - - - - -
Property nameMatch pattern
- -
-
-

- The property name can be any of a set of standard - default properties, or custom properties defined - in slave configuration files. The default - properties are: -

-
-
os:
-
The name of the operating system (for example - "Darwin")
-
family:
-
The type of operating system (for example - "posix" or "nt")
-
version:
-
The operating system version (for example - "8.10.1)
-
machine:
-
The hardware architecture (for example "i386"
-
processor:
-
The CPU model (for example "i386", this may be - empty or the same as for machine -
-
name:
-
The name of the slave
-
ipnr:
-
The IP address of the slave
-
-

- The match pattern is a regular expression. -

-
-
- - - - - -
-
- - -
- Add Configuration: - - - - - -
- -
-
- -
-
- -
- - - - - - - - - - - - - - -
 NamePathActive
(No Build Configurations)
- - - $config.label - $config.path - -
-
- - -
-
- - diff --git a/trac-0.11/bitten/templates/bitten_admin_master.html b/trac-0.11/bitten/templates/bitten_admin_master.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_admin_master.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - Manage Build Master - - -

Manage Build Master

- -
-
- Configuration Options -
- -
-

- Whether to build older revisions even when a more recent - revision has already been built. -

-
- -
-

- Whether the timestamps of builds should be adjusted to be - close to the timestamps of the corresponding changesets. -

-
-
- -
-

- The time in seconds to wait for the repository to stabilize - after a check-in before initiating a build. -

-
-
- -
-

- The timeout in seconds after which a build started by a slave - is considered aborted, in case there has been no activity from - that slave in that time. -

-
- -
- -
-
- - diff --git a/trac-0.11/bitten/templates/bitten_build.html b/trac-0.11/bitten/templates/bitten_build.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_build.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - $title - - -
-

$title

-
-
Configuration:
-
- $build.config.name -
-
Triggered by:
-
- Changeset [$build.rev] by - $build.chgset_author -
-
Built by:
-
- $slave.name ($slave.ipnr) -
-
Operating system:
-
$slave.os_name $slave.os_version ($slave.os_family)
-
Hardware:
-
- $slave.machine - ($slave.processor) -
-
- ${build.stopped and 'Started:' or 'Building since:'} -
-
$build.started ($build.started_delta ago)
-
Stopped:
-
$build.stopped ($build.stopped_delta ago)
-
Duration:
-
$build.duration
-
-
-
- - -
-
-

$step.name ($step.duration)

-
-

Errors

-
    -
  • $error
  • -
-
-

$step.description

-
-
-

Log

-
$item.message
-
-
- -
-
-
-
- - diff --git a/trac-0.11/bitten/templates/bitten_chart_coverage.html b/trac-0.11/bitten/templates/bitten_chart_coverage.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_chart_coverage.html +++ /dev/null @@ -1,38 +0,0 @@ - - - area - area - - - - - - - - - $value - $value - - - - - - - - - - bbbbbb - 9999ff - - - - - - - $title - - - diff --git a/trac-0.11/bitten/templates/bitten_chart_tests.html b/trac-0.11/bitten/templates/bitten_chart_tests.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_chart_tests.html +++ /dev/null @@ -1,38 +0,0 @@ - - - area - column - - - - - - - - - $value - $value - - - - - - - - - - 99dd99 - ff0000 - - - - - - - $title - - - diff --git a/trac-0.11/bitten/templates/bitten_config.html b/trac-0.11/bitten/templates/bitten_config.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_config.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - $title - - - - - Success - Failed - In-progress - - - -
- $slave.name ($slave.ipnr)
- $slave.os_name $slave.os_version - / - ${slave.processor or slave.machine or ''} -
- - - -
-

$title

-
-
- - -
-
- -
-
-

- $config.label -

-
- $config.description -
-

Latest builds

- - - -
- [$youngest_rev.id] - by $youngest_rev.author

$youngest_rev.date

-

$youngest_rev.message

-
- - $build.platform -

$build.stopped

- ${slave_info(build.slave)} - ${build_status(build.status)} -
- $build.platform -

No build yet

-
-
- -
-
-
- This build configuration is currently inactive.
- No builds will be initiated for this configuration
- until it is activated. -
-
- This configuration is currently active. -
-
- - - -
-
-

- Repository path: - $config.path - ${not config.path and '—' or ''} - - (starting at - [$config.min_rev] - , - up to - [$config.max_rev]) - -

-
- $config.description -
-
- - - - -
-
- - - - - - - - - - - - - -
Chgset$platform.name
- [$rev_num] - -
- - $build.id - ${build_status(build.status)} - - ${slave_info(build.slave)} -
- ${build_steps(build.steps)} -
-
- -
- -

- $config.label -

- - - - - - - - -
ChgsetBuild
- [$build.rev] - -
- - $build.id: $build.platform - - ${slave_info(build.slave)} -
- ${build_steps(build.steps)} -
-
- - diff --git a/trac-0.11/bitten/templates/bitten_summary_coverage.html b/trac-0.11/bitten/templates/bitten_summary_coverage.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_summary_coverage.html +++ /dev/null @@ -1,27 +0,0 @@ - - - -

Code Coverage

- - - - - - - - - - - - - - -
UnitLines of CodeCoverage
- $item.name - $item.name - $item.loc${item.cov}%
Total$totals.loc${totals.cov}%
- - diff --git a/trac-0.11/bitten/templates/bitten_summary_tests.html b/trac-0.11/bitten/templates/bitten_summary_tests.html deleted file mode 100644 --- a/trac-0.11/bitten/templates/bitten_summary_tests.html +++ /dev/null @@ -1,31 +0,0 @@ - - - -

Test Results

- - - - - - - - - - - - - - - - - -
Test FixtureTotalFailuresErrors
- $item.name - $item.name - ${item.num_success + item.num_failure + item.num_error}$item.num_failure$item.num_error
Total$totals.success$totals.failure$totals.error
- - diff --git a/trac-0.11/bitten/tests/__init__.py b/trac-0.11/bitten/tests/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/__init__.py +++ /dev/null @@ -1,33 +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 bitten.tests import admin, master, model, recipe, queue, slave, web_ui -from bitten.build import tests as build -from bitten.report import tests as report -from bitten.util import tests as util - -def suite(): - suite = unittest.TestSuite() - suite.addTest(admin.suite()) - suite.addTest(master.suite()) - suite.addTest(model.suite()) - suite.addTest(recipe.suite()) - suite.addTest(queue.suite()) - suite.addTest(slave.suite()) - suite.addTest(web_ui.suite()) - suite.addTest(build.suite()) - suite.addTest(report.suite()) - suite.addTest(util.suite()) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/admin.py b/trac-0.11/bitten/tests/admin.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/admin.py +++ /dev/null @@ -1,810 +0,0 @@ -# -*- 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. - -import shutil -import tempfile -import unittest - -from trac.core import TracError -from trac.db import DatabaseManager -from trac.perm import PermissionCache, PermissionError, PermissionSystem -from trac.test import EnvironmentStub, Mock -from trac.web.href import Href -from trac.web.main import RequestDone -from bitten.main import BuildSystem -from bitten.model import BuildConfig, TargetPlatform, schema -from bitten.admin import BuildMasterAdminPageProvider, \ - BuildConfigurationsAdminPageProvider - -try: - from trac.perm import DefaultPermissionPolicy -except ImportError: - DefaultPermissionPolicy = None - -class BuildMasterAdminPageProviderTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) - self.env.path = tempfile.mkdtemp() - - # Create tables - 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) - - # Set up permissions - self.env.config.set('trac', 'permission_store', - 'DefaultPermissionStore') - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - if DefaultPermissionPolicy is not None and hasattr(DefaultPermissionPolicy, "CACHE_EXPIRY"): - self.old_perm_cache_expiry = DefaultPermissionPolicy.CACHE_EXPIRY - DefaultPermissionPolicy.CACHE_EXPIRY = -1 - - # Hook up a dummy repository - self.repos = Mock( - get_node=lambda path, rev=None: Mock(get_history=lambda: [], - isdir=True), - normalize_path=lambda path: path, - sync=lambda: None - ) - self.env.get_repository = lambda authname=None: self.repos - - def tearDown(self): - if DefaultPermissionPolicy is not None and hasattr(DefaultPermissionPolicy, "CACHE_EXPIRY"): - DefaultPermissionPolicy.CACHE_EXPIRY = self.old_perm_cache_expiry - shutil.rmtree(self.env.path) - - def test_get_admin_panels(self): - provider = BuildMasterAdminPageProvider(self.env) - - req = Mock(perm=PermissionCache(self.env, 'joe')) - self.assertEqual([('bitten', 'Builds', 'master', 'Master Settings')], - list(provider.get_admin_panels(req))) - - PermissionSystem(self.env).revoke_permission('joe', 'BUILD_ADMIN') - req = Mock(perm=PermissionCache(self.env, 'joe')) - self.assertEqual([], list(provider.get_admin_panels(req))) - - def test_process_get_request(self): - req = Mock(method='GET', chrome={}, href=Href('/'), - perm=PermissionCache(self.env, 'joe')) - - provider = BuildMasterAdminPageProvider(self.env) - template_name, data = provider.render_admin_panel( - req, 'bitten', 'master', '' - ) - - self.assertEqual('bitten_admin_master.html', template_name) - assert 'master' in data - master = data['master'] - self.assertEqual(3600, master.slave_timeout) - self.assertEqual(0, master.stabilize_wait) - assert not master.adjust_timestamps - assert not master.build_all - - def test_process_config_changes(self): - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - args={'slave_timeout': '60', 'adjust_timestamps': ''}) - - provider = BuildMasterAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'master', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/master', - redirected_to[0]) - section = self.env.config['bitten'] - self.assertEqual(60, section.getint('slave_timeout')) - self.assertEqual(True, section.getbool('adjust_timestamps')) - self.assertEqual(False, section.getbool('build_all')) - - -class BuildConfigurationsAdminPageProviderTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) - self.env.path = tempfile.mkdtemp() - - # Create tables - 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) - - # Set up permissions - self.env.config.set('trac', 'permission_store', - 'DefaultPermissionStore') - PermissionSystem(self.env).grant_permission('joe', 'BUILD_CREATE') - PermissionSystem(self.env).grant_permission('joe', 'BUILD_DELETE') - PermissionSystem(self.env).grant_permission('joe', 'BUILD_MODIFY') - if DefaultPermissionPolicy is not None and hasattr(DefaultPermissionPolicy, "CACHE_EXPIRY"): - self.old_perm_cache_expiry = DefaultPermissionPolicy.CACHE_EXPIRY - DefaultPermissionPolicy.CACHE_EXPIRY = -1 - - # Hook up a dummy repository - self.repos = Mock( - get_node=lambda path, rev=None: Mock(get_history=lambda: [], - isdir=True), - normalize_path=lambda path: path, - sync=lambda: None - ) - self.env.get_repository = lambda authname=None: self.repos - - def tearDown(self): - if DefaultPermissionPolicy is not None and hasattr(DefaultPermissionPolicy, "CACHE_EXPIRY"): - DefaultPermissionPolicy.CACHE_EXPIRY = self.old_perm_cache_expiry - shutil.rmtree(self.env.path) - - def test_get_admin_panels(self): - provider = BuildConfigurationsAdminPageProvider(self.env) - - req = Mock(perm=PermissionCache(self.env, 'joe')) - self.assertEqual([('bitten', 'Builds', 'configs', 'Configurations')], - list(provider.get_admin_panels(req))) - - PermissionSystem(self.env).revoke_permission('joe', 'BUILD_MODIFY') - req = Mock(perm=PermissionCache(self.env, 'joe')) - self.assertEqual([], list(provider.get_admin_panels(req))) - - def test_process_view_configs_empty(self): - req = Mock(method='GET', chrome={}, href=Href('/'), - perm=PermissionCache(self.env, 'joe')) - - provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, data = provider.render_admin_panel( - req, 'bitten', 'configs', '' - ) - - self.assertEqual('bitten_admin_configs.html', template_name) - self.assertEqual([], data['configs']) - - def test_process_view_configs(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - BuildConfig(self.env, name='bar', label='Bar', path='branches/bar', - min_rev='123', max_rev='456').insert() - - req = Mock(method='GET', chrome={}, href=Href('/'), - perm=PermissionCache(self.env, 'joe')) - - provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, data = provider.render_admin_panel( - req, 'bitten', 'configs', '' - ) - - self.assertEqual('bitten_admin_configs.html', template_name) - assert 'configs' in data - configs = data['configs'] - self.assertEqual(2, len(configs)) - self.assertEqual({ - 'name': 'bar', 'href': '/admin/bitten/configs/bar', - 'label': 'Bar', 'min_rev': '123', 'max_rev': '456', - 'path': 'branches/bar', 'active': False - }, configs[0]) - self.assertEqual({ - 'name': 'foo', 'href': '/admin/bitten/configs/foo', - 'label': 'Foo', 'min_rev': None, 'max_rev': None, - 'path': 'branches/foo', 'active': True - }, configs[1]) - - def test_process_view_config(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - TargetPlatform(self.env, config='foo', name='any').insert() - - req = Mock(method='GET', chrome={}, href=Href('/'), - perm=PermissionCache(self.env, 'joe')) - - provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, data = provider.render_admin_panel( - req, 'bitten', 'configs', 'foo' - ) - - self.assertEqual('bitten_admin_configs.html', template_name) - assert 'config' in data - config = data['config'] - self.assertEqual({ - 'name': 'foo', 'label': 'Foo', 'description': '', 'recipe': '', - 'path': 'branches/foo', 'min_rev': None, 'max_rev': None, - 'active': True, 'platforms': [{ - 'href': '/admin/bitten/configs/foo/1', - 'name': 'any', 'id': 1, 'rules': [] - }] - }, config) - - def test_process_activate_config(self): - BuildConfig(self.env, name='foo', path='branches/foo').insert() - BuildConfig(self.env, name='bar', path='branches/bar').insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'apply': '', 'active': ['foo']}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs', - redirected_to[0]) - config = BuildConfig.fetch(self.env, name='foo') - self.assertEqual(True, config.active) - - def test_process_deactivate_config(self): - BuildConfig(self.env, name='foo', path='branches/foo', - active=True).insert() - BuildConfig(self.env, name='bar', path='branches/bar', - active=True).insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'apply': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs', - redirected_to[0]) - config = BuildConfig.fetch(self.env, name='foo') - self.assertEqual(False, config.active) - config = BuildConfig.fetch(self.env, name='bar') - self.assertEqual(False, config.active) - - def test_process_add_config(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'add': '', 'name': 'bar', 'label': 'Bar'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs/bar', - redirected_to[0]) - config = BuildConfig.fetch(self.env, name='bar') - self.assertEqual('Bar', config.label) - - def test_process_add_config_cancel(self): - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - args={'cancel': '', 'name': 'bar', 'label': 'Bar'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs', - redirected_to[0]) - configs = list(BuildConfig.select(self.env, include_inactive=True)) - self.assertEqual(0, len(configs)) - - def test_process_add_config_no_name(self): - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'add': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('Missing required field "name"', e.message) - self.assertEqual('Missing Field', e.title) - - def test_process_add_config_invalid_name(self): - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'add': '', 'name': 'no spaces allowed'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', e.message) - self.assertEqual('Invalid Field', e.title) - - def test_new_config_submit_with_invalid_path(self): - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - authname='joe', - args={'add': '', 'name': 'foo', 'path': 'invalid/path'}) - - def get_node(path, rev=None): - raise TracError('No such node') - self.repos = Mock(get_node=get_node) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('No such node', e.message) - self.assertEqual('Invalid Repository Path', e.title) - - def test_process_add_config_no_perms(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - PermissionSystem(self.env).revoke_permission('joe', 'BUILD_CREATE') - - req = Mock(method='POST', - perm=PermissionCache(self.env, 'joe'), - args={'add': '', 'name': 'bar', 'label': 'Bar'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - self.assertRaises(PermissionError, provider.render_admin_panel, req, - 'bitten', 'configs', '') - - def test_process_remove_config(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - BuildConfig(self.env, name='bar', label='Bar', path='branches/bar', - min_rev='123', max_rev='456').insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - args={'remove': '', 'sel': 'bar'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs', - redirected_to[0]) - assert not BuildConfig.fetch(self.env, name='bar') - - def test_process_remove_config_cancel(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - BuildConfig(self.env, name='bar', label='Bar', path='branches/bar', - min_rev='123', max_rev='456').insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - args={'cancel': '', 'sel': 'bar'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs', - redirected_to[0]) - configs = list(BuildConfig.select(self.env, include_inactive=True)) - self.assertEqual(2, len(configs)) - - def test_process_remove_config_no_selection(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'remove': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('No configuration selected', e.message) - - def test_process_remove_config_bad_selection(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'remove': '', 'sel': 'baz'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', '') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual("Configuration 'baz' not found", e.message) - - def test_process_remove_config_no_perms(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - PermissionSystem(self.env).revoke_permission('joe', 'BUILD_DELETE') - - req = Mock(method='POST', - perm=PermissionCache(self.env, 'joe'), - args={'remove': '', 'sel': 'bar'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - self.assertRaises(PermissionError, provider.render_admin_panel, req, - 'bitten', 'configs', '') - - def test_process_update_config(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', args={ - 'save': '', 'name': 'foo', 'label': 'Foobar', - 'description': 'Thanks for all the fish!' - }) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs', - redirected_to[0]) - config = BuildConfig.fetch(self.env, name='foo') - self.assertEqual('Foobar', config.label) - self.assertEqual('Thanks for all the fish!', config.description) - - def test_process_update_config_no_name(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'save': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('Missing required field "name"', e.message) - self.assertEqual('Missing Field', e.title) - - def test_process_update_config_invalid_name(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'save': '', 'name': 'no spaces allowed'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', e.message) - self.assertEqual('Invalid Field', e.title) - - def test_process_update_config_invalid_path(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - authname='joe', - args={'save': '', 'name': 'foo', 'path': 'invalid/path'}) - - def get_node(path, rev=None): - raise TracError('No such node') - self.repos = Mock(get_node=get_node) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('No such node', e.message) - self.assertEqual('Invalid Repository Path', e.title) - - def test_process_update_config_non_wellformed_recipe(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - authname='joe', - args={'save': '', 'name': 'foo', 'recipe': 'not_xml'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('Failure parsing recipe: syntax error: line 1, ' - 'column 0', e.message) - self.assertEqual('Invalid Recipe', e.title) - - def test_process_update_config_invalid_recipe(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - authname='joe', - args={'save': '', 'name': 'foo', - 'recipe': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('Steps must have an "id" attribute', e.message) - self.assertEqual('Invalid Recipe', e.title) - - def test_process_new_platform(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - data = {} - req = Mock(method='POST', chrome={}, hdf=data, href=Href('/'), - perm=PermissionCache(self.env, 'joe'), - args={'new': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, data = provider.render_admin_panel( - req, 'bitten', 'configs', 'foo' - ) - - self.assertEqual('bitten_admin_configs.html', template_name) - assert 'platform' in data - platform = data['platform'] - self.assertEqual({ - 'id': None, 'exists': False, 'name': None, 'rules': [('', '')], - }, platform) - - def test_process_add_platform(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'add': '', 'new': '', 'name': 'Test', - 'property_0': 'family', 'pattern_0': 'posix'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs/foo', - redirected_to[0]) - platforms = list(TargetPlatform.select(self.env, config='foo')) - self.assertEqual(1, len(platforms)) - self.assertEqual('Test', platforms[0].name) - self.assertEqual([('family', 'posix')], platforms[0].rules) - - def test_process_add_platform_cancel(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'cancel': '', 'new': '', 'name': 'Test', - 'property_0': 'family', 'pattern_0': 'posix'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs/foo', - redirected_to[0]) - platforms = list(TargetPlatform.select(self.env, config='foo')) - self.assertEqual(0, len(platforms)) - - def test_process_remove_platforms(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - platform = TargetPlatform(self.env, config='foo', name='any') - platform.insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'remove': '', 'sel': str(platform.id)}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs/foo', - redirected_to[0]) - platforms = list(TargetPlatform.select(self.env, config='foo')) - self.assertEqual(0, len(platforms)) - - def test_process_remove_platforms_no_selection(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - platform = TargetPlatform(self.env, config='foo', name='any') - platform.insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'remove': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('No platform selected', e.message) - - def test_process_edit_platform(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - platform = TargetPlatform(self.env, config='foo', name='any') - platform.insert() - - req = Mock(method='GET', chrome={}, href=Href('/'), - perm=PermissionCache(self.env, 'joe'), args={}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - template_name, data = provider.render_admin_panel( - req, 'bitten', 'configs', 'foo/%d' % platform.id - ) - - self.assertEqual('bitten_admin_configs.html', template_name) - assert 'platform' in data - platform = data['platform'] - self.assertEqual({ - 'id': 1, 'exists': True, 'name': 'any', 'rules': [('', '')], - }, platform) - - def test_process_update_platform(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - platform = TargetPlatform(self.env, config='foo', name='any') - platform.insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'save': '', 'edit': '', 'name': 'Test', - 'property_0': 'family', 'pattern_0': 'posix'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', - 'foo/%d' % platform.id) - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs/foo', - redirected_to[0]) - platforms = list(TargetPlatform.select(self.env, config='foo')) - self.assertEqual(1, len(platforms)) - self.assertEqual('Test', platforms[0].name) - self.assertEqual([('family', 'posix')], platforms[0].rules) - - def test_process_update_platform_cancel(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - platform = TargetPlatform(self.env, config='foo', name='any') - platform.insert() - - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - abs_href=Href('http://example.org/'), redirect=redirect, - authname='joe', - args={'cancel': '', 'edit': '', 'name': 'Changed', - 'property_0': 'family', 'pattern_0': 'posix'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.render_admin_panel(req, 'bitten', 'configs', - 'foo/%d' % platform.id) - self.fail('Expected RequestDone') - - except RequestDone: - self.assertEqual('http://example.org/admin/bitten/configs/foo', - redirected_to[0]) - platforms = list(TargetPlatform.select(self.env, config='foo')) - self.assertEqual(1, len(platforms)) - self.assertEqual('any', platforms[0].name) - self.assertEqual([], platforms[0].rules) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite( - BuildMasterAdminPageProviderTestCase, 'test' - )) - suite.addTest(unittest.makeSuite( - BuildConfigurationsAdminPageProviderTestCase, 'test' - )) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/master.py b/trac-0.11/bitten/tests/master.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/master.py +++ /dev/null @@ -1,748 +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 -import shutil -from StringIO import StringIO -import tempfile -import unittest - -from trac.db import DatabaseManager -from trac.perm import PermissionCache, PermissionSystem -from trac.test import EnvironmentStub, Mock -from trac.web.api import HTTPBadRequest, HTTPMethodNotAllowed, HTTPNotFound, \ - HTTPForbidden, RequestDone -from trac.web.href import Href - -from bitten.master import BuildMaster -from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ - BuildLog, Report, schema - - -class BuildMasterTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) - self.env.path = tempfile.mkdtemp() - - PermissionSystem(self.env).grant_permission('hal', 'BUILD_EXEC') - - # Create tables - 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) - - self.repos = Mock() - self.env.get_repository = lambda authname=None: self.repos - - def tearDown(self): - shutil.rmtree(self.env.path) - - def test_create_build(self): - BuildConfig(self.env, 'test', path='somepath', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('family', 'posix')) - platform.insert() - - self.repos = Mock( - get_node=lambda path, rev=None: Mock( - get_entries=lambda: [Mock(), Mock()], - get_history=lambda: [('somepath', 123, 'edit'), - ('somepath', 121, 'edit'), - ('somepath', 120, 'edit')] - ), - get_changeset=lambda rev: Mock(date=42), - normalize_path=lambda path: path, - rev_older_than=lambda rev1, rev2: rev1 < rev2 - ) - - inheaders = {'Content-Type': 'application/x-bitten+xml'} - inbody = StringIO(""" - Power Macintosh - Darwin - -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', path_info='/builds', - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - get_header=lambda x: inheaders.get(x), read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(201, outheaders['Status']) - self.assertEqual('text/plain', outheaders['Content-Type']) - location = outheaders['Location'] - mo = re.match('http://example.org/trac/builds/(\d+)', location) - assert mo, 'Location was %r' % location - self.assertEqual('Build pending', outbody.getvalue()) - build = Build.fetch(self.env, int(mo.group(1))) - self.assertEqual(Build.IN_PROGRESS, build.status) - self.assertEqual('hal', build.slave) - - def test_create_build_invalid_xml(self): - inheaders = {'Content-Type': 'application/x-bitten+xml'} - inbody = StringIO('') - req = Mock(method='POST', base_path='', path_info='/builds', - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - get_header=lambda x: inheaders.get(x), read=inbody.read) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPBadRequest') - except HTTPBadRequest, e: - self.assertEqual('XML parser error', e.detail) - - def test_create_build_no_post(self): - req = Mock(method='GET', base_path='', path_info='/builds', - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal')) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPMethodNotAllowed') - except HTTPMethodNotAllowed, e: - self.assertEqual('Method not allowed', e.detail) - - def test_create_build_no_match(self): - inheaders = {'Content-Type': 'application/x-bitten+xml'} - inbody = StringIO(""" - Power Macintosh - Darwin -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', path_info='/builds', - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - get_header=lambda x: inheaders.get(x), read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(204, outheaders['Status']) - self.assertEqual('', outbody.getvalue()) - - def test_cancel_build(self): - config = BuildConfig(self.env, 'test', path='somepath', active=True, - recipe='') - config.insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - status=Build.IN_PROGRESS, started=42) - build.insert() - - outheaders = {} - outbody = StringIO() - req = Mock(method='DELETE', base_path='', - path_info='/builds/%d' % build.id, - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(204, outheaders['Status']) - self.assertEqual('', outbody.getvalue()) - - # Make sure the started timestamp has been set - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.PENDING, build.status) - assert not build.started - - def test_initiate_build(self): - config = BuildConfig(self.env, 'test', path='somepath', active=True, - recipe='') - config.insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('family', 'posix')) - platform.insert() - build = Build(self.env, 'test', '123', platform.id, slave='hal', - rev_time=42) - build.insert() - - outheaders = {} - outbody = StringIO() - - req = Mock(method='GET', base_path='', - path_info='/builds/%d' % build.id, - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(200, outheaders['Status']) - self.assertEqual('63', outheaders['Content-Length']) - self.assertEqual('application/x-bitten+xml', - outheaders['Content-Type']) - self.assertEqual('attachment; filename=recipe_test_r123.xml', - outheaders['Content-Disposition']) - self.assertEqual('', - outbody.getvalue()) - - # Make sure the started timestamp has been set - build = Build.fetch(self.env, build.id) - assert build.started - - def test_initiate_build_no_such_build(self): - req = Mock(method='GET', base_path='', - path_info='/builds/123', href=Href('/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal')) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPNotFound') - except HTTPNotFound, e: - self.assertEqual('No such build', e.detail) - - def test_process_unknown_collection(self): - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe='').insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42) - build.insert() - - req = Mock(method='POST', base_path='', - path_info='/builds/%d/files/' % build.id, - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal')) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPNotFound') - except HTTPNotFound, e: - self.assertEqual('No such collection', e.detail) - - def test_process_build_step_success(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - build.insert() - - inbody = StringIO(""" -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(201, outheaders['Status']) - self.assertEqual('20', outheaders['Content-Length']) - self.assertEqual('text/plain', outheaders['Content-Type']) - self.assertEqual('Build step processed', outbody.getvalue()) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.SUCCESS, build.status) - assert build.stopped - assert build.stopped > build.started - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(1, len(steps)) - self.assertEqual('foo', steps[0].name) - self.assertEqual(BuildStep.SUCCESS, steps[0].status) - - def test_process_build_step_success_with_log(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - build.insert() - - inbody = StringIO(""" - - Doing stuff - Ouch that hurt - -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(201, outheaders['Status']) - self.assertEqual('20', outheaders['Content-Length']) - self.assertEqual('text/plain', outheaders['Content-Type']) - self.assertEqual('Build step processed', outbody.getvalue()) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.SUCCESS, build.status) - assert build.stopped - assert build.stopped > build.started - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(1, len(steps)) - self.assertEqual('foo', steps[0].name) - self.assertEqual(BuildStep.SUCCESS, steps[0].status) - - logs = list(BuildLog.select(self.env, build=build.id, step='foo')) - self.assertEqual(1, len(logs)) - self.assertEqual('http://bitten.cmlenz.net/tools/python#unittest', - logs[0].generator) - self.assertEqual(2, len(logs[0].messages)) - self.assertEqual((u'info', u'Doing stuff'), logs[0].messages[0]) - self.assertEqual((u'error', u'Ouch that hurt'), logs[0].messages[1]) - - def test_process_build_step_success_with_report(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - build.insert() - - inbody = StringIO(""" - - - Doing my thing - - -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(201, outheaders['Status']) - self.assertEqual('20', outheaders['Content-Length']) - self.assertEqual('text/plain', outheaders['Content-Type']) - self.assertEqual('Build step processed', outbody.getvalue()) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.SUCCESS, build.status) - assert build.stopped - assert build.stopped > build.started - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(1, len(steps)) - self.assertEqual('foo', steps[0].name) - self.assertEqual(BuildStep.SUCCESS, steps[0].status) - - reports = list(Report.select(self.env, build=build.id, step='foo')) - self.assertEqual(1, len(reports)) - self.assertEqual('test', reports[0].category) - self.assertEqual('http://bitten.cmlenz.net/tools/python#unittest', - reports[0].generator) - self.assertEqual(1, len(reports[0].items)) - self.assertEqual({ - 'fixture': 'my.Fixture', - 'file': 'my/test/file.py', - 'stdout': 'Doing my thing', - 'type': 'test', - }, reports[0].items[0]) - - def test_process_build_step_wrong_slave(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '192.168.1.1'; - build.insert() - - inbody = StringIO(""" - - Doing stuff - Ouch that hurt - -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPForbidden') - except HTTPForbidden, e: - self.assertEqual('Build 1 has been invalidated for host 127.0.0.1.', e.detail) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.IN_PROGRESS, build.status) - assert not build.stopped - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(0, len(steps)) - - - def test_process_build_step_invalidated_build(self): - recipe = """ - - - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - build.insert() - - inbody = StringIO(""" - - Doing stuff - Ouch that hurt - -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.IN_PROGRESS, build.status) - assert not build.stopped - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(1, len(steps)) - - # invalidate the build. - - build = Build.fetch(self.env, build.id) - build.slave = None - build.status = Build.PENDING - build.update() - - # have this slave submit more data. - inbody = StringIO(""" - - This is a step after invalidation - -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Build was invalidated. Should fail.'); - except HTTPForbidden, e: - self.assertEqual('Build 1 has been invalidated for host 127.0.0.1.', e.detail) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.PENDING, build.status) - - def test_process_build_step_failure(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - build.insert() - - inbody = StringIO(""" -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(201, outheaders['Status']) - self.assertEqual('20', outheaders['Content-Length']) - self.assertEqual('text/plain', outheaders['Content-Type']) - self.assertEqual('Build step processed', outbody.getvalue()) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.FAILURE, build.status) - assert build.stopped - assert build.stopped > build.started - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(1, len(steps)) - self.assertEqual('foo', steps[0].name) - self.assertEqual(BuildStep.FAILURE, steps[0].status) - - def test_process_build_step_failure_ignored(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - - build.insert() - - inbody = StringIO(""" -""") - outheaders = {} - outbody = StringIO() - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), abs_href=Href('http://example.org/trac'), - remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read, - send_response=lambda x: outheaders.setdefault('Status', x), - send_header=lambda x, y: outheaders.setdefault(x, y), - write=outbody.write) - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected RequestDone') - except RequestDone: - self.assertEqual(201, outheaders['Status']) - self.assertEqual('20', outheaders['Content-Length']) - self.assertEqual('text/plain', outheaders['Content-Type']) - self.assertEqual('Build step processed', outbody.getvalue()) - - build = Build.fetch(self.env, build.id) - self.assertEqual(Build.SUCCESS, build.status) - assert build.stopped - assert build.stopped > build.started - - steps = list(BuildStep.select(self.env, build.id)) - self.assertEqual(1, len(steps)) - self.assertEqual('foo', steps[0].name) - self.assertEqual(BuildStep.FAILURE, steps[0].status) - - def test_process_build_step_invalid_xml(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42) - build.insert() - - inbody = StringIO("""""") - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPBadRequest') - except HTTPBadRequest, e: - self.assertEqual('XML parser error', e.detail) - - def test_process_build_step_invalid_datetime(self): - recipe = """ - - -""" - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe=recipe).insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42, status=Build.IN_PROGRESS) - build.slave_info[Build.IP_ADDRESS] = '127.0.0.1'; - build.insert() - - inbody = StringIO(""" -""") - req = Mock(method='POST', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal'), - read=inbody.read) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPBadRequest') - except HTTPBadRequest, e: - self.assertEqual("Invalid ISO date/time 'sometime tomorrow maybe'", - e.detail) - - def test_process_build_step_no_post(self): - BuildConfig(self.env, 'test', path='somepath', active=True, - recipe='').insert() - build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, - started=42) - build.insert() - - req = Mock(method='GET', base_path='', - path_info='/builds/%d/steps/' % build.id, - href=Href('/trac'), remote_addr='127.0.0.1', args={}, - perm=PermissionCache(self.env, 'hal')) - - module = BuildMaster(self.env) - assert module.match_request(req) - try: - module.process_request(req) - self.fail('Expected HTTPMethodNotAllowed') - except HTTPMethodNotAllowed, e: - self.assertEqual('Method not allowed', e.detail) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(BuildMasterTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/model.py b/trac-0.11/bitten/tests/model.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/model.py +++ /dev/null @@ -1,733 +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.db import DatabaseManager -from trac.test import EnvironmentStub -from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ - BuildLog, Report, schema - - -class BuildConfigTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - 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) - db.commit() - - def test_new(self): - config = BuildConfig(self.env, name='test') - assert not config.exists - - def test_fetch(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,label,active) " - "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) - config = BuildConfig.fetch(self.env, name='test') - assert config.exists - self.assertEqual('test', config.name) - self.assertEqual('trunk', config.path) - self.assertEqual('Test', config.label) - self.assertEqual(False, config.active) - - def test_fetch_none(self): - config = BuildConfig.fetch(self.env, name='test') - self.assertEqual(None, config) - - def test_select_none(self): - configs = BuildConfig.select(self.env) - self.assertRaises(StopIteration, configs.next) - - def test_select_none(self): - configs = BuildConfig.select(self.env) - self.assertRaises(StopIteration, configs.next) - - def test_insert(self): - config = BuildConfig(self.env, name='test', path='trunk', label='Test') - config.insert() - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT name,path,label,active,description " - "FROM bitten_config") - self.assertEqual(('test', 'trunk', 'Test', 0, ''), cursor.fetchone()) - - def test_insert_no_name(self): - config = BuildConfig(self.env) - self.assertRaises(AssertionError, config.insert) - - def test_update(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,label,active) " - "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) - - config = BuildConfig.fetch(self.env, 'test') - config.path = 'some_branch' - config.label = 'Updated' - config.active = True - config.description = 'Bla bla bla' - config.update() - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT name,path,label,active,description " - "FROM bitten_config") - self.assertEqual(('test', 'some_branch', 'Updated', 1, 'Bla bla bla'), - cursor.fetchone()) - self.assertEqual(None, cursor.fetchone()) - - def test_update_name(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,label,active) " - "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) - - config = BuildConfig.fetch(self.env, 'test') - config.name = 'foobar' - config.update() - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT name,path,label,active,description " - "FROM bitten_config") - self.assertEqual(('foobar', 'trunk', 'Test', 0, ''), cursor.fetchone()) - self.assertEqual(None, cursor.fetchone()) - - def test_update_no_name(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,label,active) " - "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) - - config = BuildConfig.fetch(self.env, 'test') - config.name = None - self.assertRaises(AssertionError, config.update) - - def test_update_name_with_platform(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,label,active) " - "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) - cursor.execute("INSERT INTO bitten_platform (config,name) " - "VALUES (%s,%s)", ('test', 'NetBSD')) - - config = BuildConfig.fetch(self.env, 'test') - config.name = 'foobar' - config.update() - - cursor.execute("SELECT config,name FROM bitten_platform") - self.assertEqual(('foobar', 'NetBSD'), cursor.fetchone()) - self.assertEqual(None, cursor.fetchone()) - - def test_delete(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_config (name,path,label,active) " - "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) - - config = BuildConfig.fetch(self.env, 'test') - config.delete() - self.assertEqual(False, config.exists) - - cursor.execute("SELECT * FROM bitten_config WHERE name=%s", ('test',)) - self.assertEqual(None, cursor.fetchone()) - - def test_delete_non_existing(self): - config = BuildConfig(self.env, 'test') - self.assertRaises(AssertionError, config.delete) - - -class TargetPlatformTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - - db = self.env.get_db_cnx() - cursor = db.cursor() - connector, _ = DatabaseManager(self.env)._get_connector() - for table in TargetPlatform._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - db.commit() - - def test_new(self): - platform = TargetPlatform(self.env) - self.assertEqual(False, platform.exists) - self.assertEqual([], platform.rules) - - def test_insert(self): - platform = TargetPlatform(self.env, config='test', name='Windows XP') - platform.rules += [(Build.OS_NAME, 'Windows'), (Build.OS_VERSION, 'XP')] - platform.insert() - - assert platform.exists - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT config,name FROM bitten_platform " - "WHERE id=%s", (platform.id,)) - self.assertEqual(('test', 'Windows XP'), cursor.fetchone()) - - cursor.execute("SELECT propname,pattern,orderno FROM bitten_rule " - "WHERE id=%s", (platform.id,)) - self.assertEqual((Build.OS_NAME, 'Windows', 0), cursor.fetchone()) - self.assertEqual((Build.OS_VERSION, 'XP', 1), cursor.fetchone()) - - def test_fetch(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_platform (config,name) " - "VALUES (%s,%s)", ('test', 'Windows')) - id = db.get_last_id(cursor, 'bitten_platform') - platform = TargetPlatform.fetch(self.env, id) - assert platform.exists - self.assertEqual('test', platform.config) - self.assertEqual('Windows', platform.name) - - def test_select(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.executemany("INSERT INTO bitten_platform (config,name) " - "VALUES (%s,%s)", [('test', 'Windows'), - ('test', 'Mac OS X')]) - platforms = list(TargetPlatform.select(self.env, config='test')) - self.assertEqual(2, len(platforms)) - - -class BuildTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - - db = self.env.get_db_cnx() - cursor = db.cursor() - connector, _ = DatabaseManager(self.env)._get_connector() - for table in Build._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - db.commit() - - def test_new(self): - build = Build(self.env) - self.assertEqual(None, build.id) - self.assertEqual(Build.PENDING, build.status) - self.assertEqual(0, build.stopped) - self.assertEqual(0, build.started) - - def test_insert(self): - build = Build(self.env, config='test', rev='42', rev_time=12039, - platform=1) - build.slave_info.update({Build.IP_ADDRESS: '127.0.0.1', - Build.MAINTAINER: 'joe@example.org'}) - build.insert() - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT config,rev,platform,slave,started,stopped,status" - " FROM bitten_build WHERE id=%s" % build.id) - self.assertEqual(('test', '42', 1, '', 0, 0, 'P'), cursor.fetchone()) - - cursor.execute("SELECT propname,propvalue FROM bitten_slave") - expected = {Build.IP_ADDRESS: '127.0.0.1', - Build.MAINTAINER: 'joe@example.org'} - for propname, propvalue in cursor: - self.assertEqual(expected[propname], propvalue) - - def test_insert_no_config_or_rev_or_rev_time_or_platform(self): - build = Build(self.env) - self.assertRaises(AssertionError, build.insert) - - build = Build(self.env, rev='42', rev_time=12039, platform=1) - self.assertRaises(AssertionError, build.insert) # No config - - build = Build(self.env, config='test', rev_time=12039, platform=1) - self.assertRaises(AssertionError, build.insert) # No rev - - build = Build(self.env, config='test', rev='42', platform=1) - self.assertRaises(AssertionError, build.insert) # No rev time - - build = Build(self.env, config='test', rev='42', rev_time=12039) - self.assertRaises(AssertionError, build.insert) # No platform - - def test_insert_no_slave(self): - build = Build(self.env, config='test', rev='42', rev_time=12039, - platform=1) - build.status = Build.SUCCESS - self.assertRaises(AssertionError, build.insert) - build.status = Build.FAILURE - self.assertRaises(AssertionError, build.insert) - build.status = Build.IN_PROGRESS - self.assertRaises(AssertionError, build.insert) - build.status = Build.PENDING - build.insert() - - def test_insert_invalid_status(self): - build = Build(self.env, config='test', rev='42', rev_time=12039, - status='DUNNO') - self.assertRaises(AssertionError, build.insert) - - def test_fetch(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_build (config,rev,rev_time,platform," - "slave,started,stopped,status) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", - ('test', '42', 12039, 1, 'tehbox', 15006, 16007, - Build.SUCCESS)) - build_id = db.get_last_id(cursor, 'bitten_build') - cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", - [(build_id, Build.IP_ADDRESS, '127.0.0.1'), - (build_id, Build.MAINTAINER, 'joe@example.org')]) - - build = Build.fetch(self.env, build_id) - self.assertEquals(build_id, build.id) - self.assertEquals('127.0.0.1', build.slave_info[Build.IP_ADDRESS]) - self.assertEquals('joe@example.org', build.slave_info[Build.MAINTAINER]) - - def test_update(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_build (config,rev,rev_time,platform," - "slave,started,stopped,status) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", - ('test', '42', 12039, 1, 'tehbox', 15006, 16007, - Build.SUCCESS)) - build_id = db.get_last_id(cursor, 'bitten_build') - cursor.executemany("INSERT INTO bitten_slave VALUES (%s,%s,%s)", - [(build_id, Build.IP_ADDRESS, '127.0.0.1'), - (build_id, Build.MAINTAINER, 'joe@example.org')]) - - build = Build.fetch(self.env, build_id) - build.status = Build.FAILURE - build.update() - - -class BuildStepTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - - db = self.env.get_db_cnx() - cursor = db.cursor() - connector, _ = DatabaseManager(self.env)._get_connector() - for table in BuildStep._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - db.commit() - - def test_new(self): - step = BuildStep(self.env) - self.assertEqual(False, step.exists) - self.assertEqual(None, step.build) - self.assertEqual(None, step.name) - - def test_insert(self): - step = BuildStep(self.env, build=1, name='test', description='Foo bar', - status=BuildStep.SUCCESS) - step.insert() - self.assertEqual(True, step.exists) - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT build,name,description,status,started,stopped " - "FROM bitten_step") - self.assertEqual((1, 'test', 'Foo bar', BuildStep.SUCCESS, 0, 0), - cursor.fetchone()) - - def test_insert_with_errors(self): - step = BuildStep(self.env, build=1, name='test', description='Foo bar', - status=BuildStep.SUCCESS) - step.errors += ['Foo', 'Bar'] - step.insert() - self.assertEqual(True, step.exists) - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT build,name,description,status,started,stopped " - "FROM bitten_step") - self.assertEqual((1, 'test', 'Foo bar', BuildStep.SUCCESS, 0, 0), - cursor.fetchone()) - cursor.execute("SELECT message FROM bitten_error ORDER BY orderno") - self.assertEqual(('Foo',), cursor.fetchone()) - self.assertEqual(('Bar',), cursor.fetchone()) - - def test_insert_no_build_or_name(self): - step = BuildStep(self.env, name='test') - self.assertRaises(AssertionError, step.insert) # No build - - step = BuildStep(self.env, build=1) - self.assertRaises(AssertionError, step.insert) # No name - - def test_fetch(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_step VALUES (%s,%s,%s,%s,%s,%s)", - (1, 'test', 'Foo bar', BuildStep.SUCCESS, 0, 0)) - - step = BuildStep.fetch(self.env, build=1, name='test') - self.assertEqual(1, step.build) - self.assertEqual('test', step.name) - self.assertEqual('Foo bar', step.description) - self.assertEqual(BuildStep.SUCCESS, step.status) - - def test_fetch_with_errors(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_step VALUES (%s,%s,%s,%s,%s,%s)", - (1, 'test', 'Foo bar', BuildStep.SUCCESS, 0, 0)) - cursor.executemany("INSERT INTO bitten_error VALUES (%s,%s,%s,%s)", - [(1, 'test', 'Foo', 0), (1, 'test', 'Bar', 1)]) - - step = BuildStep.fetch(self.env, build=1, name='test') - self.assertEqual(1, step.build) - self.assertEqual('test', step.name) - self.assertEqual('Foo bar', step.description) - self.assertEqual(BuildStep.SUCCESS, step.status) - self.assertEqual(['Foo', 'Bar'], step.errors) - - def test_select(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.executemany("INSERT INTO bitten_step VALUES (%s,%s,%s,%s,%s,%s)", - [(1, 'test', 'Foo bar', BuildStep.SUCCESS, 1, 2), - (1, 'dist', 'Foo baz', BuildStep.FAILURE, 2, 3)]) - - steps = list(BuildStep.select(self.env, build=1)) - self.assertEqual(1, steps[0].build) - self.assertEqual('test', steps[0].name) - self.assertEqual('Foo bar', steps[0].description) - self.assertEqual(BuildStep.SUCCESS, steps[0].status) - self.assertEqual(1, steps[1].build) - self.assertEqual('dist', steps[1].name) - self.assertEqual('Foo baz', steps[1].description) - self.assertEqual(BuildStep.FAILURE, steps[1].status) - - -class BuildLogTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - - db = self.env.get_db_cnx() - cursor = db.cursor() - connector, _ = DatabaseManager(self.env)._get_connector() - for table in BuildLog._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - db.commit() - - def test_new(self): - log = BuildLog(self.env) - self.assertEqual(False, log.exists) - self.assertEqual(None, log.id) - self.assertEqual(None, log.build) - self.assertEqual(None, log.step) - self.assertEqual('', log.generator) - self.assertEqual([], log.messages) - - def test_insert(self): - log = BuildLog(self.env, build=1, step='test', generator='distutils') - log.messages = [ - (BuildLog.INFO, 'running tests'), - (BuildLog.ERROR, 'tests failed') - ] - log.insert() - self.assertNotEqual(None, log.id) - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT build,step,generator FROM bitten_log " - "WHERE id=%s", (log.id,)) - self.assertEqual((1, 'test', 'distutils'), cursor.fetchone()) - cursor.execute("SELECT level,message FROM bitten_log_message " - "WHERE log=%s ORDER BY line", (log.id,)) - self.assertEqual((BuildLog.INFO, 'running tests'), cursor.fetchone()) - self.assertEqual((BuildLog.ERROR, 'tests failed'), cursor.fetchone()) - - def test_insert_empty(self): - log = BuildLog(self.env, build=1, step='test', generator='distutils') - log.messages = [] - log.insert() - self.assertNotEqual(None, log.id) - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT build,step,generator FROM bitten_log " - "WHERE id=%s", (log.id,)) - self.assertEqual((1, 'test', 'distutils'), cursor.fetchone()) - cursor.execute("SELECT COUNT(*) FROM bitten_log_message " - "WHERE log=%s", (log.id,)) - self.assertEqual(0, cursor.fetchone()[0]) - - def test_insert_no_build_or_step(self): - log = BuildLog(self.env, step='test') - self.assertRaises(AssertionError, log.insert) # No build - - step = BuildStep(self.env, build=1) - self.assertRaises(AssertionError, log.insert) # No step - - def test_delete(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_log (build,step,generator) " - "VALUES (%s,%s,%s)", (1, 'test', 'distutils')) - id = db.get_last_id(cursor, 'bitten_log') - cursor.executemany("INSERT INTO bitten_log_message " - "VALUES (%s,%s,%s,%s)", - [(id, 1, BuildLog.INFO, 'running tests'), - (id, 2, BuildLog.ERROR, 'tests failed')]) - - log = BuildLog.fetch(self.env, id=id, db=db) - self.assertEqual(True, log.exists) - log.delete() - self.assertEqual(False, log.exists) - - cursor.execute("SELECT * FROM bitten_log WHERE id=%s", (id,)) - self.assertEqual(True, not cursor.fetchall()) - cursor.execute("SELECT * FROM bitten_log_message WHERE log=%s", (id,)) - self.assertEqual(True, not cursor.fetchall()) - - def test_delete_new(self): - log = BuildLog(self.env, build=1, step='test', generator='foo') - self.assertRaises(AssertionError, log.delete) - - def test_fetch(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_log (build,step,generator) " - "VALUES (%s,%s,%s)", (1, 'test', 'distutils')) - id = db.get_last_id(cursor, 'bitten_log') - cursor.executemany("INSERT INTO bitten_log_message " - "VALUES (%s,%s,%s,%s)", - [(id, 1, BuildLog.INFO, 'running tests'), - (id, 2, BuildLog.ERROR, 'tests failed')]) - - log = BuildLog.fetch(self.env, id=id, db=db) - self.assertEqual(True, log.exists) - self.assertEqual(id, log.id) - self.assertEqual(1, log.build) - self.assertEqual('test', log.step) - self.assertEqual('distutils', log.generator) - self.assertEqual((BuildLog.INFO, 'running tests'), log.messages[0]) - self.assertEqual((BuildLog.ERROR, 'tests failed'), log.messages[1]) - - def test_select(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_log (build,step,generator) " - "VALUES (%s,%s,%s)", (1, 'test', 'distutils')) - id = db.get_last_id(cursor, 'bitten_log') - cursor.executemany("INSERT INTO bitten_log_message " - "VALUES (%s,%s,%s,%s)", - [(id, 1, BuildLog.INFO, 'running tests'), - (id, 2, BuildLog.ERROR, 'tests failed')]) - - logs = BuildLog.select(self.env, build=1, step='test', db=db) - log = logs.next() - self.assertEqual(True, log.exists) - self.assertEqual(id, log.id) - self.assertEqual(1, log.build) - self.assertEqual('test', log.step) - self.assertEqual('distutils', log.generator) - self.assertEqual((BuildLog.INFO, 'running tests'), log.messages[0]) - self.assertEqual((BuildLog.ERROR, 'tests failed'), log.messages[1]) - self.assertRaises(StopIteration, logs.next) - - -class ReportTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = '' - - db = self.env.get_db_cnx() - cursor = db.cursor() - connector, _ = DatabaseManager(self.env)._get_connector() - for table in Report._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - db.commit() - - def test_delete(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_report " - "(build,step,category,generator) VALUES (%s,%s,%s,%s)", - (1, 'test', 'test', 'unittest')) - report_id = db.get_last_id(cursor, 'bitten_report') - cursor.executemany("INSERT INTO bitten_report_item " - "(report,item,name,value) VALUES (%s,%s,%s,%s)", - [(report_id, 0, 'file', 'tests/foo.c'), - (report_id, 0, 'result', 'failure'), - (report_id, 1, 'file', 'tests/bar.c'), - (report_id, 1, 'result', 'success')]) - - report = Report.fetch(self.env, report_id, db=db) - report.delete(db=db) - self.assertEqual(False, report.exists) - report = Report.fetch(self.env, report_id, db=db) - self.assertEqual(None, report) - - def test_insert(self): - report = Report(self.env, build=1, step='test', category='test', - generator='unittest') - report.items = [ - {'file': 'tests/foo.c', 'status': 'failure'}, - {'file': 'tests/bar.c', 'status': 'success'} - ] - report.insert() - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT build,step,category,generator " - "FROM bitten_report WHERE id=%s", (report.id,)) - self.assertEqual((1, 'test', 'test', 'unittest'), - cursor.fetchone()) - cursor.execute("SELECT item,name,value FROM bitten_report_item " - "WHERE report=%s ORDER BY item", (report.id,)) - items = [] - prev_item = None - for item, name, value in cursor: - if item != prev_item: - items.append({name: value}) - prev_item = item - else: - items[-1][name] = value - self.assertEquals(2, len(items)) - seen_foo, seen_bar = False, False - for item in items: - if item['file'] == 'tests/foo.c': - self.assertEqual('failure', item['status']) - seen_foo = True - if item['file'] == 'tests/bar.c': - self.assertEqual('success', item['status']) - seen_bar = True - self.assertEquals((True, True), (seen_foo, seen_bar)) - - def test_insert_dupe(self): - report = Report(self.env, build=1, step='test', category='test', - generator='unittest') - report.insert() - - report = Report(self.env, build=1, step='test', category='test', - generator='unittest') - self.assertRaises(AssertionError, report.insert) - - def test_insert_empty_items(self): - report = Report(self.env, build=1, step='test', category='test', - generator='unittest') - report.items = [{}, {}] - report.insert() - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT build,step,category,generator " - "FROM bitten_report WHERE id=%s", (report.id,)) - self.assertEqual((1, 'test', 'test', 'unittest'), - cursor.fetchone()) - cursor.execute("SELECT COUNT(*) FROM bitten_report_item " - "WHERE report=%s", (report.id,)) - self.assertEqual(0, cursor.fetchone()[0]) - - def test_fetch(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_report " - "(build,step,category,generator) VALUES (%s,%s,%s,%s)", - (1, 'test', 'test', 'unittest')) - report_id = db.get_last_id(cursor, 'bitten_report') - cursor.executemany("INSERT INTO bitten_report_item " - "(report,item,name,value) VALUES (%s,%s,%s,%s)", - [(report_id, 0, 'file', 'tests/foo.c'), - (report_id, 0, 'result', 'failure'), - (report_id, 1, 'file', 'tests/bar.c'), - (report_id, 1, 'result', 'success')]) - - report = Report.fetch(self.env, report_id) - self.assertEquals(report_id, report.id) - self.assertEquals('test', report.step) - self.assertEquals('test', report.category) - self.assertEquals('unittest', report.generator) - self.assertEquals(2, len(report.items)) - assert {'file': 'tests/foo.c', 'result': 'failure'} in report.items - assert {'file': 'tests/bar.c', 'result': 'success'} in report.items - - def test_select(self): - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("INSERT INTO bitten_report " - "(build,step,category,generator) VALUES (%s,%s,%s,%s)", - (1, 'test', 'test', 'unittest')) - report1_id = db.get_last_id(cursor, 'bitten_report') - cursor.execute("INSERT INTO bitten_report " - "(build,step,category,generator) VALUES (%s,%s,%s,%s)", - (1, 'test', 'coverage', 'trace')) - report2_id = db.get_last_id(cursor, 'bitten_report') - cursor.executemany("INSERT INTO bitten_report_item " - "(report,item,name,value) VALUES (%s,%s,%s,%s)", - [(report1_id, 0, 'file', 'tests/foo.c'), - (report1_id, 0, 'result', 'failure'), - (report1_id, 1, 'file', 'tests/bar.c'), - (report1_id, 1, 'result', 'success'), - (report2_id, 0, 'file', 'tests/foo.c'), - (report2_id, 0, 'loc', '12'), - (report2_id, 0, 'cov', '50'), - (report2_id, 1, 'file', 'tests/bar.c'), - (report2_id, 1, 'loc', '20'), - (report2_id, 1, 'cov', '25')]) - - reports = Report.select(self.env, build=1, step='test') - for idx, report in enumerate(reports): - if report.id == report1_id: - self.assertEquals('test', report.step) - self.assertEquals('test', report.category) - self.assertEquals('unittest', report.generator) - self.assertEquals(2, len(report.items)) - assert {'file': 'tests/foo.c', 'result': 'failure'} \ - in report.items - assert {'file': 'tests/bar.c', 'result': 'success'} \ - in report.items - elif report.id == report1_id: - self.assertEquals('test', report.step) - self.assertEquals('coverage', report.category) - self.assertEquals('trace', report.generator) - self.assertEquals(2, len(report.items)) - assert {'file': 'tests/foo.c', 'loc': '12', 'cov': '50'} \ - in report.items - assert {'file': 'tests/bar.c', 'loc': '20', 'cov': '25'} \ - in report.items - self.assertEqual(1, idx) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(BuildConfigTestCase, 'test')) - suite.addTest(unittest.makeSuite(TargetPlatformTestCase, 'test')) - suite.addTest(unittest.makeSuite(BuildTestCase, 'test')) - suite.addTest(unittest.makeSuite(BuildStepTestCase, 'test')) - suite.addTest(unittest.makeSuite(BuildLogTestCase, 'test')) - suite.addTest(unittest.makeSuite(ReportTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/queue.py b/trac-0.11/bitten/tests/queue.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/queue.py +++ /dev/null @@ -1,358 +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 os -import shutil -import tempfile -import time -import unittest - -from trac.db import DatabaseManager -from trac.test import EnvironmentStub, Mock -from bitten.model import BuildConfig, TargetPlatform, Build, schema -from bitten.queue import BuildQueue, collect_changes - - -class CollectChangesTestCase(unittest.TestCase): - """ - Unit tests for the `bitten.queue.collect_changes` function. - """ - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = tempfile.mkdtemp() - - 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) - - self.config = BuildConfig(self.env, name='test', path='somepath') - self.config.insert(db=db) - self.platform = TargetPlatform(self.env, config='test', name='Foo') - self.platform.insert(db=db) - db.commit() - - def tearDown(self): - shutil.rmtree(self.env.path) - - def test_stop_on_copy(self): - self.env.get_repository = lambda authname=None: Mock( - get_node=lambda path, rev=None: Mock( - get_history=lambda: [('otherpath', 123, 'copy')] - ), - normalize_path=lambda path: path - ) - - retval = list(collect_changes(self.env.get_repository(), self.config)) - self.assertEqual(0, len(retval)) - - def test_stop_on_minrev(self): - self.env.get_repository = lambda authname=None: Mock( - get_node=lambda path, rev=None: Mock( - get_entries=lambda: [Mock(), Mock()], - get_history=lambda: [('somepath', 123, 'edit'), - ('somepath', 121, 'edit'), - ('somepath', 120, 'edit')] - ), - normalize_path=lambda path: path, - rev_older_than=lambda rev1, rev2: rev1 < rev2 - ) - - self.config.min_rev = 123 - self.config.update() - - retval = list(collect_changes(self.env.get_repository(), self.config)) - self.assertEqual(1, len(retval)) - self.assertEqual(123, retval[0][1]) - - def test_skip_until_maxrev(self): - self.env.get_repository = lambda authname=None: Mock( - get_node=lambda path, rev=None: Mock( - get_entries=lambda: [Mock(), Mock()], - get_history=lambda: [('somepath', 123, 'edit'), - ('somepath', 121, 'edit'), - ('somepath', 120, 'edit')] - ), - normalize_path=lambda path: path, - rev_older_than=lambda rev1, rev2: rev1 < rev2 - ) - - self.config.max_rev=121 - self.config.update() - - retval = list(collect_changes(self.env.get_repository(), self.config)) - self.assertEqual(2, len(retval)) - self.assertEqual(121, retval[0][1]) - self.assertEqual(120, retval[1][1]) - - def test_skip_empty_dir(self): - def _mock_get_node(path, rev=None): - if rev and rev == 121: - return Mock( - get_entries=lambda: [] - ) - else: - return Mock( - get_entries=lambda: [Mock(), Mock()], - get_history=lambda: [('somepath', 123, 'edit'), - ('somepath', 121, 'edit'), - ('somepath', 120, 'edit')] - ) - - self.env.get_repository = lambda authname=None: Mock( - get_node=_mock_get_node, - normalize_path=lambda path: path, - rev_older_than=lambda rev1, rev2: rev1 < rev2 - ) - - retval = list(collect_changes(self.env.get_repository(), self.config)) - self.assertEqual(2, len(retval)) - self.assertEqual(123, retval[0][1]) - self.assertEqual(120, retval[1][1]) - - -class BuildQueueTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub() - self.env.path = tempfile.mkdtemp() - os.mkdir(os.path.join(self.env.path, 'snapshots')) - - 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) - db.commit() - - # Hook up a dummy repository - self.repos = Mock() - self.env.get_repository = lambda authname=None: self.repos - - def tearDown(self): - shutil.rmtree(self.env.path) - - def test_get_build_for_slave(self): - """ - Make sure that a pending build of an activated configuration is - scheduled for a slave that matches the target platform. - """ - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name='Foo') - platform.insert() - build = Build(self.env, config='test', platform=platform.id, rev=123, - rev_time=42, status=Build.PENDING) - build.insert() - build_id = build.id - - queue = BuildQueue(self.env) - build = queue.get_build_for_slave('foobar', {}) - self.assertEqual(build_id, build.id) - - def test_next_pending_build_no_matching_slave(self): - """ - Make sure that builds for which there is no slave matching the target - platform are not scheduled. - """ - BuildConfig(self.env, 'test', active=True).insert() - build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, - status=Build.PENDING) - build.insert() - build_id = build.id - - queue = BuildQueue(self.env) - build = queue.get_build_for_slave('foobar', {}) - self.assertEqual(None, build) - - def test_next_pending_build_inactive_config(self): - """ - Make sure that builds for a deactived build config are not scheduled. - """ - BuildConfig(self.env, 'test').insert() - platform = TargetPlatform(self.env, config='test', name='Foo') - platform.insert() - build = Build(self.env, config='test', platform=platform.id, rev=123, - rev_time=42, status=Build.PENDING) - build.insert() - - queue = BuildQueue(self.env) - build = queue.get_build_for_slave('foobar', {}) - self.assertEqual(None, build) - - def test_populate_not_build_all(self): - self.env.get_repository = lambda authname=None: Mock( - get_changeset=lambda rev: Mock(date=rev * 1000), - get_node=lambda path, rev=None: Mock( - get_entries=lambda: [Mock(), Mock()], - get_history=lambda: [('somepath', 123, 'edit'), - ('somepath', 121, 'edit'), - ('somepath', 120, 'edit')] - ), - normalize_path=lambda path: path, - rev_older_than=lambda rev1, rev2: rev1 < rev2 - ) - BuildConfig(self.env, 'test', path='somepath', active=True).insert() - platform1 = TargetPlatform(self.env, config='test', name='P1') - platform1.insert() - platform2 = TargetPlatform(self.env, config='test', name='P2') - platform2.insert() - - queue = BuildQueue(self.env) - queue.populate() - queue.populate() - queue.populate() - - builds = list(Build.select(self.env, config='test')) - builds.sort(lambda a, b: cmp(a.platform, b.platform)) - self.assertEqual(2, len(builds)) - self.assertEqual(platform1.id, builds[0].platform) - self.assertEqual('123', builds[0].rev) - self.assertEqual(platform2.id, builds[1].platform) - self.assertEqual('123', builds[1].rev) - - def test_populate_build_all(self): - self.env.get_repository = lambda authname=None: Mock( - get_changeset=lambda rev: Mock(date=rev * 1000), - get_node=lambda path, rev=None: Mock( - get_entries=lambda: [Mock(), Mock()], - get_history=lambda: [('somepath', 123, 'edit'), - ('somepath', 121, 'edit'), - ('somepath', 120, 'edit')] - ), - normalize_path=lambda path: path, - rev_older_than=lambda rev1, rev2: rev1 < rev2 - ) - BuildConfig(self.env, 'test', path='somepath', active=True).insert() - platform1 = TargetPlatform(self.env, config='test', name='P1') - platform1.insert() - platform2 = TargetPlatform(self.env, config='test', name='P2') - platform2.insert() - - queue = BuildQueue(self.env, build_all=True) - queue.populate() - queue.populate() - queue.populate() - - builds = list(Build.select(self.env, config='test')) - builds.sort(lambda a, b: cmp(a.platform, b.platform)) - self.assertEqual(6, len(builds)) - self.assertEqual(platform1.id, builds[0].platform) - self.assertEqual('123', builds[0].rev) - self.assertEqual(platform1.id, builds[1].platform) - self.assertEqual('121', builds[1].rev) - self.assertEqual(platform1.id, builds[2].platform) - self.assertEqual('120', builds[2].rev) - self.assertEqual(platform2.id, builds[3].platform) - self.assertEqual('123', builds[3].rev) - self.assertEqual(platform2.id, builds[4].platform) - self.assertEqual('121', builds[4].rev) - self.assertEqual(platform2.id, builds[5].platform) - self.assertEqual('120', builds[5].rev) - - def test_reset_orphaned_builds(self): - BuildConfig(self.env, 'test').insert() - platform = TargetPlatform(self.env, config='test', name='Foo') - platform.insert() - build1 = Build(self.env, config='test', platform=platform.id, rev=123, - rev_time=42, status=Build.IN_PROGRESS, slave='heinz', - started=time.time() - 600) # Started ten minutes ago - build1.insert() - - build2 = Build(self.env, config='test', platform=platform.id, rev=124, - rev_time=42, status=Build.IN_PROGRESS, slave='heinz', - started=time.time() - 60) # Started a minute ago - build2.insert() - - queue = BuildQueue(self.env, timeout=300) # 5 minutes timeout - build = queue.reset_orphaned_builds() - self.assertEqual(Build.PENDING, Build.fetch(self.env, build1.id).status) - self.assertEqual(Build.IN_PROGRESS, - Build.fetch(self.env, build2.id).status) - - def test_match_slave_match(self): - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('family', 'posix')) - platform.insert() - platform_id = platform.id - - queue = BuildQueue(self.env) - platforms = queue.match_slave('foo', {'family': 'posix'}) - self.assertEqual(1, len(platforms)) - self.assertEqual(platform_id, platforms[0].id) - - def test_register_slave_match_simple_fail(self): - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('family', 'posix')) - platform.insert() - - queue = BuildQueue(self.env) - platforms = queue.match_slave('foo', {'family': 'nt'}) - self.assertEqual([], platforms) - - def test_register_slave_match_regexp(self): - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('version', '8\.\d\.\d')) - platform.insert() - platform_id = platform.id - - queue = BuildQueue(self.env) - platforms = queue.match_slave('foo', {'version': '8.2.0'}) - self.assertEqual(1, len(platforms)) - self.assertEqual(platform_id, platforms[0].id) - - def test_register_slave_match_regexp_multi(self): - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('os', '^Linux')) - platform.rules.append(('processor', '^[xi]\d?86$')) - platform.insert() - platform_id = platform.id - - queue = BuildQueue(self.env) - platforms = queue.match_slave('foo', {'os': 'Linux', 'processor': 'i686'}) - self.assertEqual(1, len(platforms)) - self.assertEqual(platform_id, platforms[0].id) - - def test_register_slave_match_regexp_fail(self): - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('version', '8\.\d\.\d')) - platform.insert() - - queue = BuildQueue(self.env) - platforms = queue.match_slave('foo', {'version': '7.8.1'}) - self.assertEqual([], platforms) - - def test_register_slave_match_regexp_invalid(self): - BuildConfig(self.env, 'test', active=True).insert() - platform = TargetPlatform(self.env, config='test', name="Unix") - platform.rules.append(('version', '8(\.\d')) - platform.insert() - - queue = BuildQueue(self.env) - platforms = queue.match_slave('foo', {'version': '7.8.1'}) - self.assertEqual([], platforms) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(CollectChangesTestCase, 'test')) - suite.addTest(unittest.makeSuite(BuildQueueTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/recipe.py b/trac-0.11/bitten/tests/recipe.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/recipe.py +++ /dev/null @@ -1,108 +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 os -import shutil -import tempfile -import unittest - -from bitten.recipe import Recipe, InvalidRecipeError -from bitten.util import xmlio - - -class RecipeTestCase(unittest.TestCase): - - def setUp(self): - self.basedir = os.path.realpath(tempfile.mkdtemp()) - - def tearDown(self): - shutil.rmtree(self.basedir) - - def test_empty_recipe(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertEqual(self.basedir, recipe.ctxt.basedir) - steps = list(recipe) - self.assertEqual(0, len(steps)) - - def test_empty_step(self): - xml = xmlio.parse('' - ' ' - '') - recipe = Recipe(xml, basedir=self.basedir) - steps = list(recipe) - self.assertEqual(1, len(steps)) - self.assertEqual('foo', steps[0].id) - self.assertEqual('Bar', steps[0].description) - self.assertEqual('fail', steps[0].onerror) - - def test_validate_bad_root(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_no_steps(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_child_not_step(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_child_not_step(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_step_without_id(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_step_with_empty_id(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_step_without_commands(self): - xml = xmlio.parse('') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_step_with_command_children(self): - xml = xmlio.parse('' - '' - '') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_step_with_duplicate_id(self): - xml = xmlio.parse('' - '' - '' - '') - recipe = Recipe(xml, basedir=self.basedir) - self.assertRaises(InvalidRecipeError, recipe.validate) - - def test_validate_successful(self): - xml = xmlio.parse('' - '' - '' - '') - recipe = Recipe(xml, basedir=self.basedir) - recipe.validate() - -def suite(): - return unittest.makeSuite(RecipeTestCase, 'test') - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/slave.py b/trac-0.11/bitten/tests/slave.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/slave.py +++ /dev/null @@ -1,42 +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 os -import shutil -import tempfile -import unittest - -from trac.test import Mock -from bitten.slave import BuildSlave - - -class BuildSlaveTestCase(unittest.TestCase): - - def setUp(self): - self.work_dir = tempfile.mkdtemp(prefix='bitten_test') - self.slave = BuildSlave(None, work_dir=self.work_dir) - - def tearDown(self): - shutil.rmtree(self.work_dir) - - def _create_file(self, *path): - filename = os.path.join(self.work_dir, *path) - fd = file(filename, 'w') - fd.close() - return filename - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(BuildSlaveTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/tests/web_ui.py b/trac-0.11/bitten/tests/web_ui.py deleted file mode 100644 --- a/trac-0.11/bitten/tests/web_ui.py +++ /dev/null @@ -1,235 +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 shutil -import tempfile -import unittest - -from trac.core import TracError -from trac.db import DatabaseManager -from trac.perm import PermissionCache, PermissionSystem -from trac.test import EnvironmentStub, Mock -from trac.util.html import Markup -from trac.web.href import Href -from bitten.main import BuildSystem -from bitten.model import Build, BuildConfig, BuildStep, TargetPlatform, schema -from bitten.web_ui import BuildConfigController, SourceFileLinkFormatter - - -class BuildConfigControllerTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) - self.env.path = tempfile.mkdtemp() - - # Create tables - 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) - - # Set up permissions - self.env.config.set('trac', 'permission_store', - 'DefaultPermissionStore') - - # Hook up a dummy repository - self.repos = Mock( - get_node=lambda path, rev=None: Mock(get_history=lambda: [], - isdir=True), - normalize_path=lambda path: path, - sync=lambda: None - ) - self.env.get_repository = lambda authname=None: self.repos - - def tearDown(self): - shutil.rmtree(self.env.path) - - def test_overview(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), args={}, chrome={}, - perm=PermissionCache(self.env, 'joe')) - - module = BuildConfigController(self.env) - assert module.match_request(req) - _, data, _ = module.process_request(req) - - self.assertEqual('overview', data['page_mode']) - - def test_view_config(self): - config = BuildConfig(self.env, name='test', path='trunk') - config.insert() - platform = TargetPlatform(self.env, config='test', name='any') - platform.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), args={}, - chrome={}, authname='joe', - perm=PermissionCache(self.env, 'joe')) - - root = Mock(get_entries=lambda: ['foo'], - get_history=lambda: [('trunk', rev, 'edit') for rev in - range(123, 111, -1)]) - self.repos = Mock(get_node=lambda path, rev=None: root, - sync=lambda: None, normalize_path=lambda path: path) - - module = BuildConfigController(self.env) - assert module.match_request(req) - _, data, _ = module.process_request(req) - - self.assertEqual('view_config', data['page_mode']) - assert not 'next' in req.chrome['links'] - - def test_view_config_paging(self): - config = BuildConfig(self.env, name='test', path='trunk') - config.insert() - platform = TargetPlatform(self.env, config='test', name='any') - platform.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), args={}, - chrome={}, authname='joe', - perm=PermissionCache(self.env, 'joe')) - - root = Mock(get_entries=lambda: ['foo'], - get_history=lambda: [('trunk', rev, 'edit') for rev in - range(123, 110, -1)]) - self.repos = Mock(get_node=lambda path, rev=None: root, - sync=lambda: None, normalize_path=lambda path: path) - - module = BuildConfigController(self.env) - assert module.match_request(req) - _, data, _ = module.process_request(req) - - if req.chrome: - self.assertEqual('/trac/build/test?page=2', - req.chrome['links']['next'][0]['href']) - - -class SourceFileLinkFormatterTestCase(unittest.TestCase): - - def setUp(self): - self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) - - # Create tables - 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) - - # Hook up a dummy repository - self.repos = Mock( - get_node=lambda path, rev=None: Mock(get_history=lambda: [], - isdir=True), - normalize_path=lambda path: path, - sync=lambda: None - ) - self.env.get_repository = lambda authname=None: self.repos - - def tearDown(self): - pass - - def test_format_simple_link_in_repos(self): - BuildConfig(self.env, name='test', path='trunk').insert() - build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, - status=Build.SUCCESS, slave='hal') - build.insert() - step = BuildStep(self.env, build=build.id, name='foo', - status=BuildStep.SUCCESS) - step.insert() - - self.repos.get_node = lambda path, rev: (path, rev) - - req = Mock(method='GET', href=Href('/trac'), authname='hal') - comp = SourceFileLinkFormatter(self.env) - formatter = comp.get_formatter(req, build) - - output = formatter(step, None, None, u'error in foo/bar.c: bad') - self.assertEqual(Markup, type(output)) - self.assertEqual('error in ' - 'foo/bar.c: bad', output) - - def test_format_simple_link_not_in_repos(self): - BuildConfig(self.env, name='test', path='trunk').insert() - build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, - status=Build.SUCCESS, slave='hal') - build.insert() - step = BuildStep(self.env, build=build.id, name='foo', - status=BuildStep.SUCCESS) - step.insert() - - def _raise(): - raise TracError('No such node') - self.repos.get_node = lambda path, rev: _raise() - - req = Mock(method='GET', href=Href('/trac'), authname='hal') - comp = SourceFileLinkFormatter(self.env) - formatter = comp.get_formatter(req, build) - - output = formatter(step, None, None, u'error in foo/bar.c: bad') - self.assertEqual(Markup, type(output)) - self.assertEqual('error in foo/bar.c: bad', output) - - def test_format_link_in_repos_with_line(self): - BuildConfig(self.env, name='test', path='trunk').insert() - build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, - status=Build.SUCCESS, slave='hal') - build.insert() - step = BuildStep(self.env, build=build.id, name='foo', - status=BuildStep.SUCCESS) - step.insert() - - self.repos.get_node = lambda path, rev: (path, rev) - - req = Mock(method='GET', href=Href('/trac'), authname='hal') - comp = SourceFileLinkFormatter(self.env) - formatter = comp.get_formatter(req, build) - - output = formatter(step, None, None, u'error in foo/bar.c:123: bad') - self.assertEqual(Markup, type(output)) - self.assertEqual('error in ' - 'foo/bar.c:123: bad', output) - - def test_format_link_not_in_repos_with_line(self): - BuildConfig(self.env, name='test', path='trunk').insert() - build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, - status=Build.SUCCESS, slave='hal') - build.insert() - step = BuildStep(self.env, build=build.id, name='foo', - status=BuildStep.SUCCESS) - step.insert() - - def _raise(): - raise TracError('No such node') - self.repos.get_node = lambda path, rev: _raise() - - req = Mock(method='GET', href=Href('/trac'), authname='hal') - comp = SourceFileLinkFormatter(self.env) - formatter = comp.get_formatter(req, build) - - output = formatter(step, None, None, u'error in foo/bar.c:123: bad') - self.assertEqual(Markup, type(output)) - self.assertEqual('error in foo/bar.c:123: bad', output) - - -def suite(): - suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(BuildConfigControllerTestCase, 'test')) - suite.addTest(unittest.makeSuite(SourceFileLinkFormatterTestCase, 'test')) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/upgrades.py b/trac-0.11/bitten/upgrades.py deleted file mode 100644 --- a/trac-0.11/bitten/upgrades.py +++ /dev/null @@ -1,291 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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. - -"""Automated upgrades for the Bitten database tables, and other data stored -in the Trac environment.""" - -import os -import sys - -from trac.db import DatabaseManager - -__docformat__ = 'restructuredtext en' - -def add_log_table(env, db): - """Add a table for storing the builds logs.""" - from bitten.model import BuildLog, BuildStep - cursor = db.cursor() - - connector, _ = DatabaseManager(env)._get_connector() - for table in BuildLog._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - - cursor.execute("SELECT build,name,log FROM bitten_step " - "WHERE log IS NOT NULL") - for build, step, log in cursor: - build_log = BuildLog(env, build, step) - build_log.messages = [(BuildLog.INFO, msg) for msg in log.splitlines()] - build_log.insert(db) - - cursor.execute("CREATE TEMP TABLE old_step AS SELECT * FROM bitten_step") - cursor.execute("DROP TABLE bitten_step") - for table in BuildStep._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - cursor.execute("INSERT INTO bitten_step (build,name,description,status," - "started,stopped) SELECT build,name,description,status," - "started,stopped FROM old_step") - -def add_recipe_to_config(env, db): - """Add a column for storing the build recipe to the build configuration - table.""" - from bitten.model import BuildConfig - cursor = db.cursor() - - cursor.execute("CREATE TEMP TABLE old_config AS " - "SELECT * FROM bitten_config") - cursor.execute("DROP TABLE bitten_config") - - connector, _ = DatabaseManager(env)._get_connector() - for table in BuildConfig._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - - cursor.execute("INSERT INTO bitten_config (name,path,active,recipe,min_rev," - "max_rev,label,description) SELECT name,path,0,'',NULL," - "NULL,label,description FROM old_config") - -def add_config_to_reports(env, db): - """Add the name of the build configuration as metadata to report documents - stored in the BDB XML database.""" - - from bitten.model import Build - try: - from bsddb3 import db as bdb - import dbxml - except ImportError: - return - - dbfile = os.path.join(env.path, 'db', 'bitten.dbxml') - if not os.path.isfile(dbfile): - return - - dbenv = bdb.DBEnv() - dbenv.open(os.path.dirname(dbfile), - bdb.DB_CREATE | bdb.DB_INIT_LOCK | bdb.DB_INIT_LOG | - bdb.DB_INIT_MPOOL | bdb.DB_INIT_TXN, 0) - - mgr = dbxml.XmlManager(dbenv, 0) - xtn = mgr.createTransaction() - container = mgr.openContainer(dbfile, dbxml.DBXML_TRANSACTIONAL) - uc = mgr.createUpdateContext() - - container.addIndex(xtn, '', 'config', 'node-metadata-equality-string', uc) - - qc = mgr.createQueryContext() - for value in mgr.query(xtn, 'collection("%s")/report' % dbfile, qc): - doc = value.asDocument() - metaval = dbxml.XmlValue() - if doc.getMetaData('', 'build', metaval): - build_id = int(metaval.asNumber()) - build = Build.fetch(env, id=build_id, db=db) - if build: - doc.setMetaData('', 'config', dbxml.XmlValue(build.config)) - container.updateDocument(xtn, doc, uc) - else: - # an orphaned report, for whatever reason... just remove it - container.deleteDocument(xtn, doc, uc) - - xtn.commit() - container.close() - dbenv.close(0) - -def add_order_to_log(env, db): - """Add order column to log table to make sure that build logs are displayed - in the order they were generated.""" - from bitten.model import BuildLog - cursor = db.cursor() - - cursor.execute("CREATE TEMP TABLE old_log AS " - "SELECT * FROM bitten_log") - cursor.execute("DROP TABLE bitten_log") - - connector, _ = DatabaseManager(env)._get_connector() - for stmt in connector.to_sql(BuildLog._schema[0]): - cursor.execute(stmt) - - cursor.execute("INSERT INTO bitten_log (id,build,step,generator,orderno) " - "SELECT id,build,step,type,0 FROM old_log") - -def add_report_tables(env, db): - """Add database tables for report storage.""" - from bitten.model import Report - cursor = db.cursor() - - connector, _ = DatabaseManager(env)._get_connector() - for table in Report._schema: - for stmt in connector.to_sql(table): - cursor.execute(stmt) - -def xmldb_to_db(env, db): - """Migrate report data from Berkeley DB XML to SQL database. - - Depending on the number of reports stored, this might take rather long. - After the upgrade is done, the bitten.dbxml file (and any BDB XML log files) - may be deleted. BDB XML is no longer used by Bitten. - """ - from bitten.model import Report - from bitten.util import xmlio - try: - from bsddb3 import db as bdb - import dbxml - except ImportError: - return - - dbfile = os.path.join(env.path, 'db', 'bitten.dbxml') - if not os.path.isfile(dbfile): - return - - dbenv = bdb.DBEnv() - dbenv.open(os.path.dirname(dbfile), - bdb.DB_CREATE | bdb.DB_INIT_LOCK | bdb.DB_INIT_LOG | - bdb.DB_INIT_MPOOL | bdb.DB_INIT_TXN, 0) - - mgr = dbxml.XmlManager(dbenv, 0) - xtn = mgr.createTransaction() - container = mgr.openContainer(dbfile, dbxml.DBXML_TRANSACTIONAL) - - def get_pylint_items(xml): - for problems_elem in xml.children('problems'): - for problem_elem in problems_elem.children('problem'): - item = {'type': 'problem'} - item.update(problem_elem.attr) - yield item - - def get_trace_items(xml): - for cov_elem in xml.children('coverage'): - item = {'type': 'coverage', 'name': cov_elem.attr['module'], - 'file': cov_elem.attr['file'], - 'percentage': cov_elem.attr['percentage']} - lines = 0 - line_hits = [] - for line_elem in cov_elem.children('line'): - lines += 1 - line_hits.append(line_elem.attr['hits']) - item['lines'] = lines - item['line_hits'] = ' '.join(line_hits) - yield item - - def get_unittest_items(xml): - for test_elem in xml.children('test'): - item = {'type': 'test'} - item.update(test_elem.attr) - for child_elem in test_elem.children(): - item[child_elem.name] = child_elem.gettext() - yield item - - qc = mgr.createQueryContext() - for value in mgr.query(xtn, 'collection("%s")/report' % dbfile, qc, 0): - doc = value.asDocument() - metaval = dbxml.XmlValue() - build, step = None, None - if doc.getMetaData('', 'build', metaval): - build = metaval.asNumber() - if doc.getMetaData('', 'step', metaval): - step = metaval.asString() - - report_types = {'pylint': ('lint', get_pylint_items), - 'trace': ('coverage', get_trace_items), - 'unittest': ('test', get_unittest_items)} - xml = xmlio.parse(value.asString()) - report_type = xml.attr['type'] - category, get_items = report_types[report_type] - sys.stderr.write('.') - sys.stderr.flush() - report = Report(env, build, step, category=category, - generator=report_type) - report.items = list(get_items(xml)) - try: - report.insert(db=db) - except AssertionError: - # Duplicate report, skip - pass - sys.stderr.write('\n') - sys.stderr.flush() - - xtn.abort() - container.close() - dbenv.close(0) - -def normalize_file_paths(env, db): - """Normalize the file separator in file names in reports.""" - cursor = db.cursor() - cursor.execute("SELECT report,item,value FROM bitten_report_item " - "WHERE name='file'") - rows = cursor.fetchall() or [] - for report, item, value in rows: - if '\\' in value: - cursor.execute("UPDATE bitten_report_item SET value=%s " - "WHERE report=%s AND item=%s AND name='file'", - (value.replace('\\', '/'), report, item)) - -def fixup_generators(env, db): - """Upgrade the identifiers for the recipe commands that generated log - messages and report data.""" - - mapping = { - 'pipe': 'http://bitten.cmlenz.net/tools/sh#pipe', - 'make': 'http://bitten.cmlenz.net/tools/c#make', - 'distutils': 'http://bitten.cmlenz.net/tools/python#distutils', - 'exec_': 'http://bitten.cmlenz.net/tools/python#exec' # Ambigious - } - cursor = db.cursor() - cursor.execute("SELECT id,generator FROM bitten_log " - "WHERE generator IN (%s)" - % ','.join([repr(key) for key in mapping.keys()])) - for log_id, generator in cursor: - cursor.execute("UPDATE bitten_log SET generator=%s " - "WHERE id=%s", (mapping[generator], log_id)) - - mapping = { - 'unittest': 'http://bitten.cmlenz.net/tools/python#unittest', - 'trace': 'http://bitten.cmlenz.net/tools/python#trace', - 'pylint': 'http://bitten.cmlenz.net/tools/python#pylint' - } - cursor.execute("SELECT id,generator FROM bitten_report " - "WHERE generator IN (%s)" - % ','.join([repr(key) for key in mapping.keys()])) - for report_id, generator in cursor: - cursor.execute("UPDATE bitten_report SET generator=%s " - "WHERE id=%s", (mapping[generator], report_id)) - -def add_error_table(env, db): - """Add the bitten_error table for recording step failure reasons.""" - from trac.db import Table, Column - - table = Table('bitten_error', key=('build', 'step', 'orderno'))[ - Column('build', type='int'), Column('step'), Column('message'), - Column('orderno', type='int') - ] - cursor = db.cursor() - - connector, _ = DatabaseManager(env)._get_connector() - for stmt in connector.to_sql(table): - cursor.execute(stmt) - -map = { - 2: [add_log_table], - 3: [add_recipe_to_config], - 4: [add_config_to_reports], - 5: [add_order_to_log, add_report_tables, xmldb_to_db], - 6: [normalize_file_paths, fixup_generators], - 7: [add_error_table] -} diff --git a/trac-0.11/bitten/util/__init__.py b/trac-0.11/bitten/util/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/util/__init__.py +++ /dev/null @@ -1,17 +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. - -"""Generic utility functions and classes. - -Functionality in these modules have no dependencies on modules outside of this -package, so that they could theoretically be used in other projects. -""" - -__docformat__ = 'restructuredtext en' diff --git a/trac-0.11/bitten/util/loc.py b/trac-0.11/bitten/util/loc.py deleted file mode 100644 --- a/trac-0.11/bitten/util/loc.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 1998 Dinu C. Gherman -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2007 Edgewall Software -# -# 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. -# -# This module is based on the pycount.py script written by Dinu C. -# Gherman, and is used here under the following license: -# -# Permission to use, copy, modify, and distribute this software -# and its documentation without fee and for any purpose, except -# direct commerial advantage, is hereby granted, provided that -# the above copyright notice appear in all copies and that both -# that copyright notice and this permission notice appear in -# supporting documentation. - -"""Support for counting the lines of code in Python programs.""" - -import re - -__all__ = ['BLANK', 'CODE', 'COMMENT', 'DOC', 'count'] -__docformat__ = 'restructuredtext en' - -# Reg. exps. to find the end of a triple quote, given that -# we know we're in one; use the "match" method; .span()[1] -# will be the index of the character following the final -# quote. -_squote3_finder = re.compile( - r"([^\']|" - r"\.|" - r"'[^\']|" - r"'\.|" - r"''[^\']|" - r"''\.)*'''") - -_dquote3_finder = re.compile( - r'([^\"]|' - r'\.|' - r'"[^\"]|' - r'"\.|' - r'""[^\"]|' - r'""\.)*"""') - -# Reg. exps. to find the leftmost one-quoted string; use the -# "search" method; .span()[0] bounds the string found. -_dquote1_finder = re.compile(r'"([^"]|\.)*"') -_squote1_finder = re.compile(r"'([^']|\.)*'") - -# _is_comment matches pure comment line. -_is_comment = re.compile(r"^[ \t]*#").match - -# _is_blank matches empty line. -_is_blank = re.compile(r"^[ \t]*$").match - -# find leftmost splat or quote. -_has_nightmare = re.compile(r"""[\"'#]""").search - -# _is_doc_candidate matches lines that start with a triple quote. -_is_doc_candidate = re.compile(r"^[ \t]*('''|\"\"\")") - -BLANK, CODE, COMMENT, DOC = 0, 1, 2, 3 - -def count(source): - """Parse the given file-like object as Python source code. - - For every line in the code, this function yields a ``(lineno, type, line)`` - tuple, where ``lineno`` is the line number (starting at 0), ``type`` is - one of `BLANK`, `CODE`, `COMMENT` or `DOC`, and ``line`` is the actual - content of the line. - - :param source: a file-like object containing Python code - """ - - quote3_finder = {'"': _dquote3_finder, "'": _squote3_finder} - quote1_finder = {'"': _dquote1_finder, "'": _squote1_finder } - - in_doc = False - in_triple_quote = None - - for lineno, line in enumerate(source): - classified = False - - if in_triple_quote: - if in_doc: - yield lineno, DOC, line - else: - yield lineno, CODE, line - classified = True - m = in_triple_quote.match(line) - if m == None: - continue - # Get rid of everything through the end of the triple. - end = m.span()[1] - line = line[end:] - in_doc = in_triple_quote = False - - if _is_blank(line): - if not classified: - yield lineno, BLANK, line - continue - - if _is_comment(line): - if not classified: - yield lineno, COMMENT, line - continue - - # Now we have a code line, a doc start line, or crap left - # over following the close of a multi-line triple quote; in - # (& only in) the last case, classified==1. - if not classified: - if _is_doc_candidate.match(line): - yield lineno, DOC, line - in_doc = True - else: - yield lineno, CODE, line - - # The only reason to continue parsing is to make sure the - # start of a multi-line triple quote isn't missed. - while True: - m = _has_nightmare(line) - if not m: - break - else: - i = m.span()[0] - - ch = line[i] # splat or quote - if ch == '#': - # Chop off comment; and there are no quotes - # remaining because splat was leftmost. - break - # A quote is leftmost. - elif ch * 3 == line[i:i + 3]: - # at the start of a triple quote - in_triple_quote = quote3_finder[ch] - m = in_triple_quote.match(line, i + 3) - if m: - # Remove the string & continue. - end = m.span()[1] - line = line[:i] + line[end:] - in_doc = in_triple_quote = False - else: - # Triple quote doesn't end on this line. - break - else: - # At a single quote; remove the string & continue. - prev_line = line[:] - line = re.sub(quote1_finder[ch], ' ', line, 1) - # No more change detected, so be quiet or give up. - if prev_line == line: - # Let's be quiet and hope only one line is affected. - line = '' diff --git a/trac-0.11/bitten/util/testrunner.py b/trac-0.11/bitten/util/testrunner.py deleted file mode 100644 --- a/trac-0.11/bitten/util/testrunner.py +++ /dev/null @@ -1,280 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2005-2007 Christopher Lenz -# Copyright (C) 2008 Matt Good -# Copyright (C) 2008 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 distutils import log -from distutils.errors import DistutilsOptionError -import os -import re -from StringIO import StringIO -import sys -import time -from pkg_resources import Distribution, EntryPoint, PathMetadata, \ - normalize_path, require, working_set -from setuptools.command.test import test -from unittest import _TextTestResult, TextTestRunner - -from bitten import __version__ as VERSION -from bitten.util import xmlio - -__docformat__ = 'restructuredtext en' - - -class XMLTestResult(_TextTestResult): - - def __init__(self, stream, descriptions, verbosity): - _TextTestResult.__init__(self, stream, descriptions, verbosity) - self.tests = [] - - def startTest(self, test): - _TextTestResult.startTest(self, test) - filename = sys.modules[test.__module__].__file__ - if filename.endswith('.pyc') or filename.endswith('.pyo'): - filename = filename[:-1] - self.tests.append([test, filename, time.time(), None, None]) - - def stopTest(self, test): - self.tests[-1][2] = time.time() - self.tests[-1][2] - _TextTestResult.stopTest(self, test) - - -class XMLTestRunner(TextTestRunner): - - def __init__(self, stream=sys.stdout, xml_stream=None): - TextTestRunner.__init__(self, stream, descriptions=0, verbosity=2) - self.xml_stream = xml_stream - - def _makeResult(self): - return XMLTestResult(self.stream, self.descriptions, self.verbosity) - - def run(self, test): - result = TextTestRunner.run(self, test) - if not self.xml_stream: - return result - - root = xmlio.Element('unittest-results') - for testcase, filename, timetaken, stdout, stderr in result.tests: - status = 'success' - tb = None - - if testcase in [e[0] for e in result.errors]: - status = 'error' - tb = [e[1] for e in result.errors if e[0] is testcase][0] - elif testcase in [f[0] for f in result.failures]: - status = 'failure' - tb = [f[1] for f in result.failures if f[0] is testcase][0] - - name = str(testcase) - fixture = None - description = testcase.shortDescription() or '' - if description.startswith('doctest of '): - name = 'doctest' - fixture = description[11:] - description = None - else: - match = re.match('(\w+)\s+\(([\w.]+)\)', name) - if match: - name = match.group(1) - fixture = match.group(2) - - test_elem = xmlio.Element('test', file=filename, name=name, - fixture=fixture, status=status, - duration=timetaken) - if description: - test_elem.append(xmlio.Element('description')[description]) - if stdout: - test_elem.append(xmlio.Element('stdout')[stdout]) - if stderr: - test_elem.append(xmlio.Element('stdout')[stderr]) - if tb: - test_elem.append(xmlio.Element('traceback')[tb]) - root.append(test_elem) - - root.write(self.xml_stream, newlines=True) - return result - - -class unittest(test): - description = test.description + ', and optionally record code coverage' - - user_options = test.user_options + [ - ('xml-output=', None, - "Path to the XML file where test results are written to"), - ('coverage-dir=', None, - "Directory where coverage files are to be stored"), - ('coverage-summary=', None, - "Path to the file where the coverage summary should be stored"), - ('coverage-method=', None, - "Whether to use trace.py or coverage.py to collect code coverage. " - "Valid options are 'trace' (the default) or 'coverage'.") - ] - - def initialize_options(self): - test.initialize_options(self) - self.xml_output = None - self.xml_output_file = None - self.coverage_summary = None - self.coverage_dir = None - self.coverage_method = 'trace' - - def finalize_options(self): - test.finalize_options(self) - - if self.xml_output is not None: - output_dir = os.path.dirname(self.xml_output) or '.' - if not os.path.exists(output_dir): - os.makedirs(output_dir) - self.xml_output_file = open(self.xml_output, 'w') - - if self.coverage_method not in ('trace', 'coverage', 'figleaf'): - raise DistutilsOptionError('Unknown coverage method %r' % - self.coverage_method) - - def run_tests(self): - if self.coverage_summary: - if self.coverage_method == 'coverage': - self._run_with_coverage() - elif self.coverage_method == 'figleaf': - self._run_with_figleaf() - else: - self._run_with_trace() - else: - self._run_tests() - - def _run_with_figleaf(self): - import figleaf - figleaf.start() - try: - self._run_tests() - finally: - figleaf.stop() - figleaf.write_coverage(self.coverage_summary) - - def _run_with_coverage(self): - import coverage - coverage.use_cache(False) - coverage.start() - try: - self._run_tests() - finally: - coverage.stop() - - modules = [m for _, m in sys.modules.items() - if m is not None and hasattr(m, '__file__') - and os.path.splitext(m.__file__)[-1] in ('.py', '.pyc')] - - # Generate summary file - buf = StringIO() - coverage.report(modules, file=buf) - buf.seek(0) - fileobj = open(self.coverage_summary, 'w') - try: - filter_coverage(buf, fileobj) - finally: - fileobj.close() - - if self.coverage_dir: - if not os.path.exists(self.coverage_dir): - os.makedirs(self.coverage_dir) - coverage.annotate(modules, directory=self.coverage_dir, - ignore_errors=True) - - def _run_with_trace(self): - from trace import Trace - trace = Trace(ignoredirs=[sys.prefix, sys.exec_prefix], trace=False, - count=True) - try: - trace.runfunc(self._run_tests) - finally: - results = trace.results() - real_stdout = sys.stdout - sys.stdout = open(self.coverage_summary, 'w') - try: - results.write_results(show_missing=True, summary=True, - coverdir=self.coverage_dir) - finally: - sys.stdout.close() - sys.stdout = real_stdout - - def _run_tests(self): - old_path = sys.path[:] - ei_cmd = self.get_finalized_command("egg_info") - path_item = normalize_path(ei_cmd.egg_base) - metadata = PathMetadata( - path_item, normalize_path(ei_cmd.egg_info) - ) - dist = Distribution(path_item, metadata, project_name=ei_cmd.egg_name) - working_set.add(dist) - require(str(dist.as_requirement())) - loader_ep = EntryPoint.parse("x=" + self.test_loader) - loader_class = loader_ep.load(require=False) - - try: - import unittest - unittest.main( - None, None, [unittest.__file__] + self.test_args, - testRunner=XMLTestRunner(stream=sys.stdout, - xml_stream=self.xml_output_file), - testLoader=loader_class() - ) - except SystemExit, e: - return e.code - - -def filter_coverage(infile, outfile): - for idx, line in enumerate(infile): - if idx < 2 or line.startswith('--'): - outfile.write(line) - continue - parts = line.split() - name = parts[0] - if name == 'TOTAL': - continue - if name not in sys.modules: - outfile.write(line) - continue - filename = os.path.normpath(sys.modules[name].__file__) - if filename.endswith('.pyc') or filename.endswith('.pyo'): - filename = filename[:-1] - outfile.write(line.rstrip() + ' ' + filename + '\n') - - -def main(): - from distutils.dist import Distribution - from optparse import OptionParser - - parser = OptionParser(usage='usage: %prog [options] test_suite ...', - version='%%prog %s' % VERSION) - parser.add_option('-o', '--xml-output', action='store', dest='xml_output', - metavar='FILE', help='write XML test results to FILE') - parser.add_option('-d', '--coverage-dir', action='store', - dest='coverage_dir', metavar='DIR', - help='store coverage results in DIR') - parser.add_option('-s', '--coverage-summary', action='store', - dest='coverage_summary', metavar='FILE', - help='write coverage summary to FILE') - options, args = parser.parse_args() - if len(args) < 1: - parser.error('incorrect number of arguments') - - cmd = unittest(Distribution()) - cmd.initialize_options() - cmd.test_suite = args[0] - if hasattr(options, 'xml_output'): - cmd.xml_output = options.xml_output - if hasattr(options, 'coverage_summary'): - cmd.coverage_summary = options.coverage_summary - if hasattr(options, 'coverage_dir'): - cmd.coverage_dir = options.coverage_dir - cmd.finalize_options() - cmd.run() - -if __name__ == '__main__': - main(sys.argv) diff --git a/trac-0.11/bitten/util/tests/__init__.py b/trac-0.11/bitten/util/tests/__init__.py deleted file mode 100644 --- a/trac-0.11/bitten/util/tests/__init__.py +++ /dev/null @@ -1,22 +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 doctest -import unittest - -from bitten.util import xmlio - -def suite(): - suite = unittest.TestSuite() - suite.addTest(doctest.DocTestSuite(xmlio)) - return suite - -if __name__ == '__main__': - unittest.main(defaultTest='suite') diff --git a/trac-0.11/bitten/util/xmlio.py b/trac-0.11/bitten/util/xmlio.py deleted file mode 100644 --- a/trac-0.11/bitten/util/xmlio.py +++ /dev/null @@ -1,318 +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. - -"""Utility code for easy input and output of XML. - -The current implementation uses `xml.dom.minidom` under the hood for parsing. -""" - -import os -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO -from UserDict import DictMixin - -import cgi -import string - -__trans = string.maketrans ("", "") -__todel = "" -for c in range (0, 256): - c1 = chr (c) - if not c1 in string.printable: - __todel += c1 -del c, c1 - -__all__ = ['Fragment', 'Element', 'ParsedElement', 'parse'] -__docformat__ = 'restructuredtext en' - -def _escape_text(text): - """Escape special characters in the provided text so that it can be safely - included in XML text nodes. - """ - return cgi.escape (str(text)).translate (__trans, __todel) - -def _escape_attr(attr): - """Escape special characters in the provided text so that it can be safely - included in XML attribute values. - """ - return _escape_text(attr).replace('"', '"') - - -class Fragment(object): - """A collection of XML elements.""" - __slots__ = ['children'] - - def __init__(self): - """Create an XML fragment.""" - self.children = [] - - def __getitem__(self, nodes): - """Add nodes to the fragment.""" - if not isinstance(nodes, (list, tuple)): - nodes = [nodes] - for node in nodes: - self.append(node) - return self - - def __str__(self): - """Return a string representation of the XML fragment.""" - buf = StringIO() - self.write(buf) - return buf.getvalue() - - def append(self, node): - """Append an element or fragment as child.""" - if isinstance(node, Element): - self.children.append(node) - elif isinstance(node, Fragment): - self.children += node.children - elif node is not None and node != '': - self.children.append(str(node)) - - def write(self, out, newlines=False): - """Serializes the element and writes the XML to the given output - stream. - """ - for child in self.children: - if isinstance(child, (Element, ParsedElement)): - child.write(out, newlines=newlines) - else: - if child.startswith('<'): - out.write('') - else: - out.write(_escape_text(child)) - - -class Element(Fragment): - """Simple XML output generator based on the builder pattern. - - Construct XML elements by passing the tag name to the constructor: - - >>> print Element('foo') - - - Attributes can be specified using keyword arguments. The values of the - arguments will be converted to strings and any special XML characters - escaped: - - >>> print Element('foo', bar=42) - - >>> print Element('foo', bar='1 < 2') - - >>> print Element('foo', bar='"baz"') - - - The order in which attributes are rendered is undefined. - - Elements can be using item access notation: - - >>> print Element('foo')[Element('bar'), Element('baz')] - - - Text nodes can be nested in an element by using strings instead of elements - in item access. Any special characters in the strings are escaped - automatically: - - >>> print Element('foo')['Hello world'] - Hello world - >>> print Element('foo')[42] - 42 - >>> print Element('foo')['1 < 2'] - 1 < 2 - - This technique also allows mixed content: - - >>> print Element('foo')['Hello ', Element('b')['world']] - Hello world - - Finally, text starting with an opening angle bracket is treated specially: - under the assumption that the text actually contains XML itself, the whole - thing is wrapped in a CDATA block instead of escaping all special characters - individually: - - >>> print Element('foo')[''] - ]]> - """ - __slots__ = ['name', 'attr'] - - def __init__(self, name_, **attr): - """Create an XML element using the specified tag name. - - The tag name must be supplied as the first positional argument. All - keyword arguments following it are handled as attributes of the element. - """ - Fragment.__init__(self) - self.name = name_ - self.attr = dict([(name, value) for name, value in attr.items() - if value is not None]) - - def write(self, out, newlines=False): - """Serializes the element and writes the XML to the given output - stream. - """ - out.write('<') - out.write(self.name) - for name, value in self.attr.items(): - out.write(' %s="%s"' % (name, _escape_attr(value))) - if self.children: - out.write('>') - Fragment.write(self, out, newlines) - out.write('') - else: - out.write('/>') - if newlines: - out.write(os.linesep) - - -class ParseError(Exception): - """Exception thrown when there's an error parsing an XML document.""" - - -def parse(text_or_file): - """Parse an XML document provided as string or file-like object. - - Returns an instance of `ParsedElement` that can be used to traverse the - parsed document. - """ - from xml.dom import minidom - from xml.parsers import expat - try: - if isinstance(text_or_file, (str, unicode)): - dom = minidom.parseString(text_or_file) - else: - dom = minidom.parse(text_or_file) - return ParsedElement(dom.documentElement) - except expat.error, e: - raise ParseError(e) - - -class ParsedElement(object): - """Representation of an XML element that was parsed from a string or - file. - - This class should not be used directly. Rather, XML text parsed using - `xmlio.parse()` will return an instance of this class. - - >>> xml = parse('') - >>> print xml.name - root - - Parsed elements can be serialized to a string using the `write()` method: - - >>> import sys - >>> parse('').write(sys.stdout) - - - For convenience, this is also done when coercing the object to a string - using the builtin ``str()`` function, which is used when printing an - object: - - >>> print parse('') - - - (Note that serializing the element will produce a normalized representation - that may not excatly match the input string.) - - Attributes are accessed via the `attr` member: - - >>> print parse('').attr['foo'] - bar - - Attributes can also be updated, added or removed: - - >>> xml = parse('') - >>> xml.attr['foo'] = 'baz' - >>> print xml - - - >>> del xml.attr['foo'] - >>> print xml - - - >>> xml.attr['foo'] = 'bar' - >>> print xml - - - CDATA sections are included in the text content of the element returned by - `gettext()`: - - >>> xml = parse('foo ]]>baz') - >>> xml.gettext() - 'foo baz' - """ - __slots__ = ['_node', 'attr'] - - class _Attrs(DictMixin): - """Simple wrapper around the element attributes to provide a dictionary - interface.""" - def __init__(self, node): - self._node = node - def __getitem__(self, name): - attr = self._node.getAttributeNode(name) - if not attr: - raise KeyError(name) - return attr.value.encode('utf-8') - def __setitem__(self, name, value): - self._node.setAttribute(name, value) - def __delitem__(self, name): - self._node.removeAttribute(name) - def keys(self): - return [key.encode('utf-8') for key in self._node.attributes.keys()] - - def __init__(self, node): - self._node = node - self.attr = ParsedElement._Attrs(node) - - name = property(fget=lambda self: self._node.localName, - doc='Local name of the element') - namespace = property(fget=lambda self: self._node.namespaceURI, - doc='Namespace URI of the element') - - def children(self, name=None): - """Iterate over the child elements of this element. - - If the parameter `name` is provided, only include elements with a - matching local name. Otherwise, include all elements. - """ - for child in [c for c in self._node.childNodes if c.nodeType == 1]: - if name in (None, child.tagName): - yield ParsedElement(child) - - def __iter__(self): - return self.children() - - def gettext(self): - """Return the text content of this element. - - This concatenates the values of all text and CDATA nodes that are - immediate children of this element. - """ - return ''.join([c.nodeValue.encode('utf-8') - for c in self._node.childNodes - if c.nodeType in (3, 4)]) - - def write(self, out, newlines=False): - """Serializes the element and writes the XML to the given output - stream. - """ - self._node.writexml(out, newl=newlines and '\n' or '') - - def __str__(self): - """Return a string representation of the XML element.""" - buf = StringIO() - self.write(buf) - return buf.getvalue() - - -if __name__ == '__main__': - import doctest - doctest.testmod() diff --git a/trac-0.11/bitten/web_ui.py b/trac-0.11/bitten/web_ui.py deleted file mode 100644 --- a/trac-0.11/bitten/web_ui.py +++ /dev/null @@ -1,626 +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. - -"""Implementation of the Bitten web interface.""" - -from datetime import datetime -import posixpath -import re -from StringIO import StringIO - -import pkg_resources -from genshi.builder import tag -from trac.core import * -from trac.timeline import ITimelineEventProvider -from trac.util import escape, pretty_timedelta, format_datetime, shorten_line, \ - Markup -from trac.util.html import html -from trac.web import IRequestHandler -from trac.web.chrome import INavigationContributor, ITemplateProvider, \ - add_link, add_stylesheet, add_ctxtnav, \ - prevnext_nav, add_script -from trac.wiki import wiki_to_html, wiki_to_oneliner -from bitten.api import ILogFormatter, IReportChartGenerator, IReportSummarizer -from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ - BuildLog, Report -from bitten.queue import collect_changes - -_status_label = {Build.PENDING: 'pending', - Build.IN_PROGRESS: 'in progress', - Build.SUCCESS: 'completed', - Build.FAILURE: 'failed'} - -def _get_build_data(env, req, build): - data = {'id': build.id, 'name': build.slave, 'rev': build.rev, - 'status': _status_label[build.status], - 'cls': _status_label[build.status].replace(' ', '-'), - 'href': req.href.build(build.config, build.id), - 'chgset_href': req.href.changeset(build.rev)} - if build.started: - data['started'] = format_datetime(build.started) - data['started_delta'] = pretty_timedelta(build.started) - data['duration'] = pretty_timedelta(build.started) - if build.stopped: - data['stopped'] = format_datetime(build.stopped) - data['stopped_delta'] = pretty_timedelta(build.stopped) - data['duration'] = pretty_timedelta(build.stopped, build.started) - data['slave'] = { - 'name': build.slave, - 'ipnr': build.slave_info.get(Build.IP_ADDRESS), - 'os_name': build.slave_info.get(Build.OS_NAME), - 'os_family': build.slave_info.get(Build.OS_FAMILY), - 'os_version': build.slave_info.get(Build.OS_VERSION), - 'machine': build.slave_info.get(Build.MACHINE), - 'processor': build.slave_info.get(Build.PROCESSOR) - } - return data - - -class BittenChrome(Component): - """Provides the Bitten templates and static resources.""" - - implements(INavigationContributor, ITemplateProvider) - - # INavigationContributor methods - - def get_active_navigation_item(self, req): - """Called by Trac to determine which navigation item should be marked - as active. - - :param req: the request object - """ - return 'build' - - def get_navigation_items(self, req): - """Return the navigation item for access the build status overview from - the Trac navigation bar.""" - if 'BUILD_VIEW' in req.perm: - yield ('mainnav', 'build', - tag.a('Builds Status', href=req.href.build(), accesskey=5)) - - # ITemplatesProvider methods - - def get_htdocs_dirs(self): - """Return the directories containing static resources.""" - return [('bitten', pkg_resources.resource_filename(__name__, 'htdocs'))] - - def get_templates_dirs(self): - """Return the directories containing templates.""" - return [pkg_resources.resource_filename(__name__, 'templates')] - - -class BuildConfigController(Component): - """Implements the web interface for build configurations.""" - - implements(IRequestHandler) - - # IRequestHandler methods - - def match_request(self, req): - match = re.match(r'/build(?:/([\w.-]+))?/?$', req.path_info) - if match: - if match.group(1): - req.args['config'] = match.group(1) - return True - - def process_request(self, req): - req.perm.require('BUILD_VIEW') - - action = req.args.get('action') - view = req.args.get('view') - config = req.args.get('config') - - if config: - data = self._render_config(req, config) - elif view == 'inprogress': - data = self._render_inprogress(req) - else: - data = self._render_overview(req) - - add_stylesheet(req, 'bitten/bitten.css') - return 'bitten_config.html', data, None - - # Internal methods - - def _render_overview(self, req): - data = {'title': 'Build Status'} - show_all = False - if req.args.get('show') == 'all': - show_all = True - data['show_all'] = show_all - - configs = [] - for config in BuildConfig.select(self.env, include_inactive=show_all): - description = config.description - if description: - description = wiki_to_html(description, self.env, req) - config_data = { - 'name': config.name, 'label': config.label or config.name, - 'active': config.active, 'path': config.path, - 'description': description, - 'href': req.href.build(config.name), - 'builds': [] - } - configs.append(config_data) - if not config.active: - continue - - repos = self.env.get_repository(req.authname) - if hasattr(repos, 'sync'): - repos.sync() - - prev_rev = None - for platform, rev, build in collect_changes(repos, config): - if rev != prev_rev: - if prev_rev is None: - chgset = repos.get_changeset(rev) - config_data['youngest_rev'] = { - 'id': rev, 'href': req.href.changeset(rev), - 'author': chgset.author or 'anonymous', - 'date': format_datetime(chgset.date), - 'message': wiki_to_oneliner( - shorten_line(chgset.message), self.env, req=req) - } - else: - break - prev_rev = rev - if build: - build_data = _get_build_data(self.env, req, build) - build_data['platform'] = platform.name - config_data['builds'].append(build_data) - else: - config_data['builds'].append({ - 'platform': platform.name, 'status': 'pending' - }) - - data['configs'] = configs - data['page_mode'] = 'overview' - add_link(req, 'views', req.href.build(view='inprogress'), - 'In Progress Builds') - add_ctxtnav(req, 'In Progress Builds', - req.href.build(view='inprogress')) - return data - - def _render_inprogress(self, req): - data = {'title': 'In Progress Builds', - 'page_mode': 'view-inprogress'} - - db = self.env.get_db_cnx() - - configs = [] - for config in BuildConfig.select(self.env, include_inactive=False): - self.log.debug(config.name) - if not config.active: - continue - - in_progress_builds = Build.select(self.env, config=config.name, - status=Build.IN_PROGRESS, db=db) - - current_builds = 0 - builds = [] - # sort correctly by revision. - for build in sorted(in_progress_builds, - cmp=lambda x, y: int(y.rev) - int(x.rev)): - rev = build.rev - build_data = _get_build_data(self.env, req, build) - build_data['rev'] = rev - build_data['rev_href'] = req.href.changeset(rev) - platform = TargetPlatform.fetch(self.env, build.platform) - build_data['platform'] = platform.name - build_data['steps'] = [] - - for step in BuildStep.select(self.env, build=build.id, db=db): - build_data['steps'].append({ - 'name': step.name, - 'description': step.description, - 'duration': datetime.fromtimestamp(step.stopped) - \ - datetime.fromtimestamp(step.started), - 'failed': not step.successful, - 'errors': step.errors, - 'href': build_data['href'] + '#step_' + step.name - }) - - builds.append(build_data) - current_builds += 1 - - if current_builds == 0: - continue - - description = config.description - if description: - description = wiki_to_html(description, self.env, req) - configs.append({ - 'name': config.name, 'label': config.label or config.name, - 'active': config.active, 'path': config.path, - 'description': description, - 'href': req.href.build(config.name), - 'builds': builds - }) - - data['configs'] = configs - return data - - def _render_config(self, req, config_name): - db = self.env.get_db_cnx() - - config = BuildConfig.fetch(self.env, config_name, db=db) - data = {'title': 'Build Configuration "%s"' \ - % config.label or config.name, - 'page_mode': 'view_config'} - add_link(req, 'up', req.href.build(), 'Build Status') - description = config.description - if description: - description = wiki_to_html(description, self.env, req) - data['config'] = { - 'name': config.name, 'label': config.label, 'path': config.path, - 'min_rev': config.min_rev, - 'min_rev_href': req.href.changeset(config.min_rev), - 'max_rev': config.max_rev, - 'max_rev_href': req.href.changeset(config.max_rev), - 'active': config.active, 'description': description, - 'browser_href': req.href.browser(config.path) - } - - platforms = list(TargetPlatform.select(self.env, config=config_name, - db=db)) - data['config']['platforms'] = [ - {'name': platform.name, 'id': platform.id} - for platform in platforms - ] - - has_reports = False - for report in Report.select(self.env, config=config.name, db=db): - has_reports = True - break - - if has_reports: - chart_generators = [] - for generator in ReportChartController(self.env).generators: - for category in generator.get_supported_categories(): - chart_generators.append({ - 'href': req.href.build(config.name, 'chart/' + category) - }) - data['config']['charts'] = chart_generators - charts_license = self.config.get('bitten', 'charts_license') - if charts_license: - data['config']['charts_license'] = charts_license - - page = max(1, int(req.args.get('page', 1))) - more = False - data['page_number'] = page - - repos = self.env.get_repository(req.authname) - if hasattr(repos, 'sync'): - repos.sync() - - builds_per_page = 12 * len(platforms) - idx = 0 - builds = {} - for platform, rev, build in collect_changes(repos, config): - if idx >= page * builds_per_page: - more = True - break - elif idx >= (page - 1) * builds_per_page: - builds.setdefault(rev, {}) - builds[rev].setdefault('href', req.href.changeset(rev)) - if build and build.status != Build.PENDING: - build_data = _get_build_data(self.env, req, build) - build_data['steps'] = [] - for step in BuildStep.select(self.env, build=build.id, - db=db): - build_data['steps'].append({ - 'name': step.name, - 'description': step.description, - 'duration': datetime.fromtimestamp(step.stopped) - \ - datetime.fromtimestamp(step.started), - 'failed': not step.successful, - 'errors': step.errors, - 'href': build_data['href'] + '#step_' + step.name - }) - builds[rev][platform.id] = build_data - idx += 1 - data['config']['builds'] = builds - - if page > 1: - if page == 2: - prev_href = req.href.build(config.name) - else: - prev_href = req.href.build(config.name, page=page - 1) - add_link(req, 'prev', prev_href, 'Previous Page') - if more: - next_href = req.href.build(config.name, page=page + 1) - add_link(req, 'next', next_href, 'Next Page') - prevnext_nav(req, 'Page') - return data - - -class BuildController(Component): - """Renders the build page.""" - implements(INavigationContributor, IRequestHandler, ITimelineEventProvider) - - log_formatters = ExtensionPoint(ILogFormatter) - report_summarizers = ExtensionPoint(IReportSummarizer) - - # INavigationContributor methods - - def get_active_navigation_item(self, req): - return 'build' - - def get_navigation_items(self, req): - return [] - - # IRequestHandler methods - - def match_request(self, req): - match = re.match(r'/build/([\w.-]+)/(\d+)', req.path_info) - if match: - if match.group(1): - req.args['config'] = match.group(1) - if match.group(2): - req.args['id'] = match.group(2) - return True - - def process_request(self, req): - req.perm.require('BUILD_VIEW') - - db = self.env.get_db_cnx() - build_id = int(req.args.get('id')) - build = Build.fetch(self.env, build_id, db=db) - assert build, 'Build %s does not exist' % build_id - - if req.method == 'POST': - if req.args.get('action') == 'invalidate': - self._do_invalidate(req, build, db) - req.redirect(req.href.build(build.config, build.id)) - - add_link(req, 'up', req.href.build(build.config), - 'Build Configuration') - status2title = {Build.SUCCESS: 'Success', Build.FAILURE: 'Failure', - Build.IN_PROGRESS: 'In Progress'} - data = {'title': 'Build %s - %s' % (build_id, - status2title[build.status]), - 'page_mode': 'view_build', - 'build': {}} - config = BuildConfig.fetch(self.env, build.config, db=db) - data['build']['config'] = { - 'name': config.label, - 'href': req.href.build(config.name) - } - - formatters = [] - for formatter in self.log_formatters: - formatters.append(formatter.get_formatter(req, build)) - - summarizers = {} # keyed by report type - for summarizer in self.report_summarizers: - categories = summarizer.get_supported_categories() - summarizers.update(dict([(cat, summarizer) for cat in categories])) - - data['build'].update(_get_build_data(self.env, req, build)) - steps = [] - for step in BuildStep.select(self.env, build=build.id, db=db): - steps.append({ - 'name': step.name, 'description': step.description, - 'duration': pretty_timedelta(step.started, step.stopped), - 'failed': step.status == BuildStep.FAILURE, - 'errors': step.errors, - 'log': self._render_log(req, build, formatters, step), - 'reports': self._render_reports(req, config, build, summarizers, - step) - }) - data['build']['steps'] = steps - data['build']['can_delete'] = ('BUILD_DELETE' in req.perm) - - repos = self.env.get_repository(req.authname) - chgset = repos.get_changeset(build.rev) - data['build']['chgset_author'] = chgset.author - - add_script(req, 'bitten/tabset.js') - add_stylesheet(req, 'bitten/bitten.css') - return 'bitten_build.html', data, None - - # ITimelineEventProvider methods - - def get_timeline_filters(self, req): - if 'BUILD_VIEW' in req.perm: - yield ('build', 'Builds') - - def get_timeline_events(self, req, start, stop, filters): - if 'build' not in filters: - return - - if isinstance(start, datetime): # Trac>=0.11 - from trac.util.datefmt import to_timestamp - start = to_timestamp(start) - stop = to_timestamp(stop) - - add_stylesheet(req, 'bitten/bitten.css') - - db = self.env.get_db_cnx() - cursor = db.cursor() - cursor.execute("SELECT b.id,b.config,c.label,b.rev,p.name," - "b.stopped,b.status FROM bitten_build AS b" - " INNER JOIN bitten_config AS c ON (c.name=b.config) " - " INNER JOIN bitten_platform AS p ON (p.id=b.platform) " - "WHERE b.stopped>=%s AND b.stopped<=%s " - "AND b.status IN (%s, %s) ORDER BY b.stopped", - (start, stop, Build.SUCCESS, Build.FAILURE)) - - event_kinds = {Build.SUCCESS: 'successbuild', - Build.FAILURE: 'failedbuild'} - for id, config, label, rev, platform, stopped, status in cursor: - - errors = [] - if status == Build.FAILURE: - for step in BuildStep.select(self.env, build=id, - status=BuildStep.FAILURE, - db=db): - errors += [(step.name, error) for error - in step.errors] - - title = tag('Build of ', tag.em('%s [%s]' % (label, rev)), - ' on %s %s' % (platform, _status_label[status])) - message = '' - if req.args.get('format') == 'rss': - href = req.abs_href.build(config, id) - if errors: - buf = StringIO() - prev_step = None - for step, error in errors: - if step != prev_step: - if prev_step is not None: - buf.write('') - buf.write('

Step %s failed:

    ' \ - % escape(step)) - prev_step = step - buf.write('
  • %s
  • ' % escape(error)) - buf.write('
') - message = Markup(buf.getvalue()) - else: - href = req.href.build(config, id) - if errors: - steps = [] - for step, error in errors: - if step not in steps: - steps.append(step) - steps = [Markup('%s') % step for step in steps] - if len(steps) < 2: - message = steps[0] - elif len(steps) == 2: - message = Markup(' and ').join(steps) - elif len(steps) > 2: - message = Markup(', ').join(steps[:-1]) + ', and ' + \ - steps[-1] - message = Markup('Step%s %s failed') % ( - len(steps) != 1 and 's' or '', message - ) - yield event_kinds[status], href, title, stopped, None, message - - # Internal methods - - def _do_invalidate(self, req, build, db): - self.log.info('Invalidating build %d', build.id) - - for step in BuildStep.select(self.env, build=build.id, db=db): - step.delete(db=db) - - build.slave = None - build.started = build.stopped = 0 - build.status = Build.PENDING - build.slave_info = {} - build.update() - - db.commit() - - req.redirect(req.href.build(build.config)) - - def _render_log(self, req, build, formatters, step): - items = [] - for log in BuildLog.select(self.env, build=build.id, step=step.name): - for level, message in log.messages: - for format in formatters: - message = format(step, log.generator, level, message) - items.append({'level': level, 'message': message}) - return items - - def _render_reports(self, req, config, build, summarizers, step): - reports = [] - for report in Report.select(self.env, build=build.id, step=step.name): - summarizer = summarizers.get(report.category) - if summarizer: - tmpl, data = summarizer.render_summary(req, config, build, - step, report.category) - else: - tmpl = data = None - reports.append({'category': report.category, - 'template': tmpl, 'data': data}) - 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(): - tmpl, data = generator.generate_chart_data(req, config, - category) - break - else: - raise TracError('Unknown report category "%s"' % category) - - return tmpl, data, 'text/xml' - - -class SourceFileLinkFormatter(Component): - """Detects references to files in the build log and renders them as links - to the repository browser. - """ - - implements(ILogFormatter) - - _fileref_re = re.compile('(?P[\w.-]+(?:/[\w.-]+)+)(?P(:\d+))?') - - def get_formatter(self, req, build): - """Return the log message formatter function.""" - config = BuildConfig.fetch(self.env, name=build.config) - repos = self.env.get_repository(req.authname) - href = req.href.browser - cache = {} - - def _replace(m): - filepath = posixpath.normpath(m.group('path').replace('\\', '/')) - if not cache.get(filepath) is True: - parts = filepath.split('/') - path = '' - for part in parts: - path = posixpath.join(path, part) - if path not in cache: - try: - repos.get_node(posixpath.join(config.path, path), - build.rev) - cache[path] = True - except TracError: - cache[path] = False - if cache[path] is False: - return m.group(0) - link = href(config.path, filepath) - if m.group('line'): - link += '#L' + m.group('line')[1:] - return Markup(tag.a(m.group(0), href=link)) - - def _formatter(step, type, level, message): - buf = [] - offset = 0 - for mo in self._fileref_re.finditer(message): - start, end = mo.span() - if start > offset: - buf.append(message[offset:start]) - buf.append(_replace(mo)) - offset = end - if offset < len(message): - buf.append(message[offset:]) - return Markup("").join(buf) - - return _formatter diff --git a/trac-0.11/doc/commands.txt b/trac-0.11/doc/commands.txt deleted file mode 100644 --- a/trac-0.11/doc/commands.txt +++ /dev/null @@ -1,890 +0,0 @@ -.. -*- mode: rst; encoding: utf-8 -*- - -===================== -Build Recipe Commands -===================== - -`Build recipes`_ are represented by XML documents. This page describes what -commands are generally available in recipes. Please note, though, that -third-party packages can add additional commands, which would then be -documented by that third party. - -.. _`build recipes`: recipes.html - -.. contents:: Contents - :depth: 2 -.. sectnum:: - - -Generic Commands -================ - -These are commands that are used without a namespace prefix. - - ------------- -```` ------------- - -Parse an XML file and send it to the master as a report with a given category. -Use this command in conjunction with the ```` or ```` -commands to send custom reports to the build master. - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``category`` | Category of the report (for example "test" or "coverage"). | -+--------------+-------------------------------------------------------------+ -| ``file`` | Path to the XML file containing the report data, relative | -| | to the project directory. | -+--------------+-------------------------------------------------------------+ - -Both parameters must be specified. - - -Shell Tools -=========== - -A bundle of generic tools that are not specific to any programming language or -tool-chain. - -:Namespace: ``http://bitten.cmlenz.net/tools/sh`` -:Common prefix: ``sh`` - - -------------- -```` -------------- - -Executes a program or script. - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``executable`` | The name of the executable program. | -+----------------+-----------------------------------------------------------+ -| ``file`` | Path to the script to execute, relative to the project | -| | directory | -+----------------+-----------------------------------------------------------+ -| ``output`` | Path to the output file | -+----------------+-----------------------------------------------------------+ -| ``args`` | Any arguments to pass to the executable or script | -+----------------+-----------------------------------------------------------+ - -Either ``executable`` or ``file`` must be specified. - -Examples --------- - -TODO - - -------------- -```` -------------- - -Pipes the content of a file through a program or script. - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``executable`` | The name of the executable program. | -+----------------+-----------------------------------------------------------+ -| ``file`` | Path to the script to execute, relative to the project | -| | directory | -+----------------+-----------------------------------------------------------+ -| ``input`` | Path to the input file | -+----------------+-----------------------------------------------------------+ -| ``output`` | Path to the output file | -+----------------+-----------------------------------------------------------+ -| ``args`` | Any arguments to pass to the executable or script | -+----------------+-----------------------------------------------------------+ - -Either ``executable`` or ``file`` must be specified. - -Examples --------- - -TODO - - -C/Unix Tools -============ - -These commands provide support for tools commonly used for development of C/C++ -applications on Unix platforms, such as ``make``. - -:Namespace: ``http://bitten.cmlenz.net/tools/c`` -:Common prefix: ``c`` - - ------------------- -```` ------------------- - -Executes ths autotool autoreconf. - -Parameters ----------- - - :param force: consider all files obsolete - :param install: copy missing auxiliary files - :param symlink: install symbolic links instead of copies - :param warnings: report the warnings falling in CATEGORY - :prepend_include: prepend directories to search path - :include: append directories to search path - - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``force`` | Consider all files obsolete | -+--------------+-------------------------------------------------------------+ -| ``install`` | Copy missing auxiliary files | -+--------------+-------------------------------------------------------------+ -| ``symlink`` | Install symbolic links instead of copies | -+--------------+-------------------------------------------------------------+ -| ``warnings`` | Report the warnings related to category | -| | (which can actually be a comma separated list) | -+--------------+-------------------------------------------------------------+ -| ``prepend_include`` | Prepend directories to search path | -+--------------+-------------------------------------------------------------+ -| ``include`` | Append directories to search path | -+--------------+-------------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Runs the ``autoreconf`` tool in the base directory with the option: force, install -and 3 warning categories active: cross,syntax,error. This is equivalent to:: - - autoreconf --force --install --warnings=cross,syntax,error - - ------------------ -```` ------------------ - -Executes a configure script as generated by Autoconf. - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``file`` | Name of the configure script (defaults to "configure") | -+--------------+-------------------------------------------------------------+ -| ``enable`` | List of features to enable, separated by spaces. | -+--------------+-------------------------------------------------------------+ -| ``disable`` | List of features to disable, separated by spaces. | -+--------------+-------------------------------------------------------------+ -| ``with`` | List of packages to include, separated by spaces. | -+--------------+-------------------------------------------------------------+ -| ``without`` | List of packages to exclude, separated by spaces. | -+--------------+-------------------------------------------------------------+ -| ``cflags`` | Value of the `CFLAGS` variable to pass to the script. | -+--------------+-------------------------------------------------------------+ -| ``cxxflags`` | Value of the `CXXFLAGS` variable to pass to the script. | -+--------------+-------------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Runs the ``configure`` script in the base directory, enable the ``threadsafe`` -feature, and passing ``-O`` as ``CFLAGS``. This is equivalent to:: - - ./configure --enable-threadsafe CFLAGS="-O" - - ------------- -```` ------------- - -Run gcov_ to extract coverage data where available. - -.. _gcov: http://gcc.gnu.org/onlinedocs/gcc/Gcov-Intro.html - -Parameters ----------- - -+--------------+------------------------------------------------------------+ -| Name | Description | -+==============+============================================================+ -| ``include`` | List of glob patterns (separated by space) that specify | -| | which source files should be included in the coverage | -| | report | -+--------------+------------------------------------------------------------+ -| ``exclude`` | List of glob patterns (separated by space) that specify | -| | which source files should be excluded from the coverage | -| | report | -+--------------+------------------------------------------------------------+ -| ``prefix`` | Optional prefix name that is added to object files by the | -| | build system | -+--------------+------------------------------------------------------------+ - - ------------- -```` ------------- - -Executes a Makefile. - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``target`` | Name of the target to execute (defaults to "all") | -+----------------+-----------------------------------------------------------+ -| ``file`` | Path to the Makefile that should be used. | -+----------------+-----------------------------------------------------------+ -| ``keep-going`` | Whether `make` should try to continue even after | -| | encountering errors. | -+----------------+-----------------------------------------------------------+ -| ``jobs`` | Number of parallel jobs used by make. | -+----------------+-----------------------------------------------------------+ -| ``directory`` | Path of the directory in which make should be called. | -+----------------+-----------------------------------------------------------+ -| ``args`` | Any space separated arguments to pass to the makefile. | -| | Usually in the form: | -| | ``"parameter1=value1 parameter2=value2"``. | -+----------------+-----------------------------------------------------------+ - - -Examples --------- - -.. code-block:: xml - - - -Runs the target "compile" of the ``Makefile`` located in the sub-directory -``build``. - -.. code-block:: xml - - - -Same as previous but execute the command in the ``work`` directory and call -the makefile with the command line argument ``coverage=1``. - ---------------- -```` ---------------- - -Report the test output generated by the CppUnit_ unit testing framework. The -output from CppUnit must be in XML format and in already, specified by the -``file`` argument of this recipe. - -.. _cppunit: http://cppunit.sourceforge.net - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path to the cppunit XML output file. | -+----------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - - -Runs the program ``run_unit_tests`` to gather the data output by CppUnit in the -``test_results.xml`` file and then reports it. - - -Java Tools -========== - -A bundle of recipe commands that support tools commonly used by Java projects. - -:Namespace: ``http://bitten.cmlenz.net/tools/java`` -:Common prefix: ``java`` - - --------------- -```` --------------- - -Runs an Ant_ build. - -.. _ant: http://ant.apache.org/ - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path of the build file, relative to the project source | -| | directory (default is ``build.xml``). | -+----------------+-----------------------------------------------------------+ -| ``target`` | Name of the build target(s) to execute. | -+----------------+-----------------------------------------------------------+ -| ``args`` | Additional arguments to pass to Ant, separated by | -| | whitespace. | -+----------------+-----------------------------------------------------------+ -| ``keep_going`` | Tell Ant to continue even when errors are in encountered | -| | in the build. | -+----------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the -project source directory. - - --------------------- -```` --------------------- - -Extract code coverage data from a Cobertura_ XML file. - -.. _cobertura: http://cobertura.sourceforge.net/ - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path to the XML file generated by Cobertura | -+----------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Reads the specifid XML file, extracts the coverage data, and builds a coverage -report to be sent to the build master. - - ----------------- -```` ----------------- - -Extracts information about unit test results from a file in JUnit_ XML format. - -.. _junit: http://junit.org/index.htm - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path to the JUnit XML test results file. This can include | -| | wildcards, in which case all the file matching the | -| | pattern will be included. | -+----------------+-----------------------------------------------------------+ -| ``srcdir`` | Path of the directory unit test sources. Used to link the | -| | test cases to files. | -+----------------+-----------------------------------------------------------+ - -The ``file`` attribute is required. - -Examples --------- - -.. code-block:: xml - - - -Collects the test results from all files in the `build/tests/results` directory -that match the pattern `TEST-*.xml`. Also, maps the class names in the results -files to Java source files in the directory `src/tests`. - - -PHP Tools -========= - -A bundle of recipe commands for PHP_ projects. - -:Namespace: ``http://bitten.cmlenz.net/tools/php`` -:Common prefix: ``php`` - -.. _php: http://php.net/ - ---------------- -```` ---------------- - -Runs a Phing_ build. - -.. _phing: http://phing.info/ - -Parameters ----------- - -+-------------------+-------------------------------------------------------+ -| Name | Description | -+===================+=======================================================+ -| ``file`` | Path of the build file, relative to the project | -| | source directory (default is ``build.xml``). | -+-------------------+-------------------------------------------------------+ -| ``target`` | Name of the build target(s) to execute. | -+-------------------+-------------------------------------------------------+ -| ``args`` | Additional arguments to pass to Phing, separated by | -| | whitespace. | -+-------------------+-------------------------------------------------------+ -| ``executable`` | Phing executable program (default is ``phing``). | -+-------------------+-------------------------------------------------------+ - - -Examples --------- - -.. code-block:: xml - - - -Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the -project source directory. - - ------------------ -```` ------------------ - -Extracts information from PHPUnit_ test results recorded in an XML file. - -.. _phpunit: http://www.phpunit.de/ - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path to the XML results file, relative to the project | -| | source directory. | -+----------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Extracts the test results from the XML file located at -``build/test-results.xml``. - - ------------------- -```` ------------------- - -Extracts coverage information Phing_'s code coverage task recorded in an XML -file. - -Parameters ----------- - -+---------------+-----------------------------------------------------------+ -| Name | Description | -+===============+===========================================================+ -| ``file`` | Path to the XML coverage file, relative to the project | -| | source directory. | -+---------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - - -Python Tools -============ - -A bundle of recipe commands that support tools commonly used by Python_ -projects. - -:Namespace: ``http://bitten.cmlenz.net/tools/python`` -:Common prefix: ``python`` - -.. _python: http://www.python.org/ - - ------------------ -```` ------------------ - -Executes a Python script. - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path of the script to execute, relative to the project | -| | source directory. | -+----------------+-----------------------------------------------------------+ -| ``module`` | Name of the Python module to execute. | -+----------------+-----------------------------------------------------------+ -| ``function`` | Name of the function in the Python module to run. Only | -| | works when also specifying the `module` attribute. | -+----------------+-----------------------------------------------------------+ -| ``args`` | Any arguments that should be passed to the script. | -+----------------+-----------------------------------------------------------+ -| ``output`` | Path to a file where any output by the script should be | -| | recorded. | -+----------------+-----------------------------------------------------------+ - -Either `file` or `module` must be specified. - -Examples --------- - -.. code-block:: xml - - - -Executes Pylint_ on the module/package ``myproj`` and stores the output into a -file named ``pylint-report.txt``. - - ----------------------- -```` ----------------------- - -Executes a distutils_ script. - -.. _distutils: http://docs.python.org/lib/module-distutils.html - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| `command` | The name of the `distutils` command that should be run | -+----------------+-----------------------------------------------------------+ -| `options` | Additional options to pass to the command, separated by | -| | spaces | -+----------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Instructs `distutils` to produce a source distribution. - -.. code-block:: xml - - - -Instructs `distutils` to run the ``unittest`` command (which is provided by -Bitten), and passes the options needed to determine the output paths for test -results and code coverage reports. - - ---------------------- -```` ---------------------- - -Extracts information from unittest_ results recorded in an XML file. - -.. _unittest: http://docs.python.org/lib/module-unittest.html -.. note:: This report must be used in conjunction with the ``distutils`` command - "unittest" that comes with Bitten. - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``file`` | Path to the XML results file, relative to the project | -| | source directory. | -+----------------+-----------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -Extracts the test results from the XML file located at -``build/test-results.xml``. - - ------------------- -```` ------------------- - -Extracts coverage information recorded by the built-in Python module -``trace.py``. - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``summary`` | Path to the summary file written by ``trace.py``, | -| | relative to the project source directory. | -+--------------+-------------------------------------------------------------+ -| ``coverdir`` | Path to the directory containing the coverage files written | -| | by ``trace.py``, relative to the project source directory. | -+--------------+-------------------------------------------------------------+ -| ``include`` | List of glob patterns (separated by space) that specify | -| | which Python file should be included in the coverage report | -+--------------+-------------------------------------------------------------+ -| ``exclude`` | List of glob patterns (separated by space) that specify | -| | which Python file should be excluded from the coverage | -| | report | -+--------------+-------------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -------------------- -```` -------------------- - -Extracts information from Pylint_ reports. - -.. _pylint: http://www.logilab.org/projects/pylint - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``file`` | Path to the file containing the Pylint output, relative to | -| | the project source directory. | -+--------------+-------------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - - -Subversion Tools -================ - -A collection of recipe commands for working with the Subversion_ version -control system. This commands are commonly used as the first step of a build -recipe to actually pull the code that should be built from the repository. - -.. _subversion: http://subversion.tigris.org/ - -:Namespace: ``http://bitten.cmlenz.net/tools/svn`` -:Common prefix: ``svn`` - - ------------------- -```` ------------------- - -Check out a working copy from a Subversion repository. - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``url`` | URL of the repository. | -+--------------+-------------------------------------------------------------+ -| ``path`` | The path inside the repository that should be checked out. | -| | You should normally set this to ``${path}`` so that the | -| | path of the build configuration is used. | -+--------------+-------------------------------------------------------------+ -| ``revision`` | The revision that should be checked out. You should | -| | normally set this to ``${revision}`` so that the revision | -| | of the build is used. | -+--------------+-------------------------------------------------------------+ -| ``dir`` | Path specifying which directory the sources should be | -| | checked out to (defaults to '.'). | -+--------------+-------------------------------------------------------------+ -| ``verbose`` | Whether to log the list of checked out files (defaults to | -| | False). | -+--------------+-------------------------------------------------------------+ - - -Examples --------- - -.. code-block:: xml - - - -This checks out the a working copy into the current directory. - - ----------------- -```` ----------------- - -Download a file or directory from a Subversion repository. This is similar to -performing a checkout, but will not include the meta-data Subversion uses to -connect the local working copy to the repository (i.e. it does not include the -``.svn`` directories.) - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``url`` | URL of the repository. | -+--------------+-------------------------------------------------------------+ -| ``path`` | The path inside the repository that should be checked out. | -| | You should normally set this to ``${path}`` so that the | -| | path of the build configuration is used. | -+--------------+-------------------------------------------------------------+ -| ``revision`` | The revision that should be checked out. You should | -| | normally set this to ``${revision}`` so that the revision | -| | of the build is used. | -+--------------+-------------------------------------------------------------+ -| ``dir`` | Path specifying which directory the sources should be | -| | exported to (defaults to '.') | -+--------------+-------------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -This downloads the file or directory at ``${path}`` from the Subversion -repository at ``http://svn.example.org/repos/myproject/``. Variables are used -for the ``path`` and ``revision`` attributes so they are populated from the -properties of the build and build configuration. - - ----------------- -```` ----------------- - -Update an existing working copy from a Subversion repository to a specific -revision. - -Parameters ----------- - -+--------------+-------------------------------------------------------------+ -| Name | Description | -+==============+=============================================================+ -| ``revision`` | The revision that should be checked out. You should | -| | normally set this to ``${revision}`` so that the revision | -| | of the build is used. | -+--------------+-------------------------------------------------------------+ -| ``dir`` | Path specifying the directory containing the sources to be | -| | updated (defaults to '.') | -+--------------+-------------------------------------------------------------+ - -Examples --------- - -.. code-block:: xml - - - -This updates the working copy in the current directory. The revision is -specified as a variable so that it is populated from the properties of the -build. - - -XML Tools -========= - -A collection of recipe commands for XML processing. - -:Namespace: ``http://bitten.cmlenz.net/tools/xml`` -:Common prefix: ``x`` - - ------------------ -```` ------------------ - -Apply an XSLT stylesheet . - -.. note:: that this command requires either libxslt_ (with `Python bindings`_) - or, on Windows platforms, MSXML (version 3 or later) to be installed - on the slave machine. - -.. _libxslt: http://xmlsoft.org/XSLT/ -.. _`python bindings`: http://xmlsoft.org/XSLT/python.html - -Parameters ----------- - -+----------------+-----------------------------------------------------------+ -| Name | Description | -+================+===========================================================+ -| ``src`` | Path of the source XML file. | -+----------------+-----------------------------------------------------------+ -| ``dest`` | Path of the destition XML file. | -+----------------+-----------------------------------------------------------+ -| ``stylesheet`` | Path to the XSLT stylesheet file. | -+----------------+-----------------------------------------------------------+ - -All these are interpreted relative to the project source directory. - -Examples --------- - -.. code-block:: xml - - - -This applies the stylesheet in ``util/convert.xsl`` to the source file -``src.xml``, and writes the resulting XML document to ``dest.xml``. diff --git a/trac-0.11/doc/index.txt b/trac-0.11/doc/index.txt deleted file mode 100644 --- a/trac-0.11/doc/index.txt +++ /dev/null @@ -1,25 +0,0 @@ -.. -*- mode: rst; encoding: utf-8 -*- - -======= -Preface -======= - -.. image:: logo.png - :width: 538 - :height: 298 - :align: center - :alt: Bitten - :class: logo - -------------------------------- -Continuous Integration for Trac -------------------------------- - -Bitten is a Python-based framework for collecting various software metrics via -continuous integration. It builds on Trac to provide an integrated web-based -user interface. - - * `Installation `_ - * `Build Recipes `_ - * `Build Recipe Commands `_ - * `Generated API Documentation `_ diff --git a/trac-0.11/doc/install.txt b/trac-0.11/doc/install.txt deleted file mode 100644 --- a/trac-0.11/doc/install.txt +++ /dev/null @@ -1,121 +0,0 @@ -.. -*- mode: rst; encoding: utf-8 -*- - -============ -Installation -============ - -.. contents:: Contents - :depth: 2 -.. sectnum:: - - -Prerequisites -============= - -Bitten is written in Python, so make sure that you have Python installed. -You'll need Python 2.3 or later. Also, make sure that setuptools_, version 0.6a2 -or later, is installed. - -.. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools - -If that's taken care of, you just need to download and unpack the Bitten -distribution, and execute the command:: - - $ python setup.py install - -from the top of the directory where you unpacked (or checked out) the Bitten -code. Note that you may need administrator/root privileges for this step, as -it will by default attempt to install Bitten to the Python site-packages -directory on your system. - -It's also a good idea to run the unit tests at this point, to make sure that -the code works as expected on your platform:: - - $ python setup.py test - - -What's left to do now depends on whether you want to use the build master and -web interface, or just the build slave. In the latter case, you're already -done. You might need to install software that the build of your project -requires, but the Bitten build slave itself doesn't require anything extra. - -For the build master and web interface, you'll need to install Trac_ 0.10 or -later and the TracWebAdmin_ plugin. Please refer to the Trac documentation for -information on how it is installed. - -.. _trac: http://trac.edgewall.org/ -.. _tracwebadmin: http://trac.edgewall.org/wiki/WebAdmin - - -Build Master Configuration -========================== - -Once both Bitten and Trac are installed and working, you'll have to introduce -Bitten to your Trac project environment. If you don't have a Trac project -set up yet, you'll need to do so in order to use Bitten. - -If you already have a Trac project environment, the Bitten plugin needs to be -explicitly enabled in the Trac configuration. This is done by adding it to the -``[components]`` section in ``/path/to/projenv/conf/trac.ini``: - -.. code-block:: ini - - [components] - bitten.* = enabled - -The Trac web interface should now inform you with an error message that the -environment needs to be upgraded. To do this, run:: - - $ trac-admin /path/to/projenv upgrade - -This will create the database tables and directories that Bitten requires. -You probably also want to grant permissions to someone (such as yourself) -to manage build configurations, and allow anonymous users to view the -status and results of builds:: - - $ trac-admin /path/to/projenv permission add anonymous BUILD_EXEC - $ trac-admin /path/to/projenv permission add anonymous BUILD_VIEW - $ trac-admin /path/to/projenv permission add [yourname] BUILD_ADMIN - -You should now see an additional tab labeled "Build Status" in the Trac -navigation bar. This link will take you to the list of build configurations, -which at this point is of course empty. - -To add build configurations, you need to have the TracWebAdmin_ plugin -installed. - -.. warning:: The TracWebAdmin needs to be installed even if you're using Trac - 0.11 or later, which basically provides a builtin web - administration interface. Make sure that you disable the plugin in - that case, though (``webadmin.* = disabled``). While somewhat - counterintuitive, this process allows the Bitten administration UI - to neatly integrate into the new web administration interface added - in Trac 0.11. - -If both TracWebAdmin_ and Bitten are installed, and you are logged in as a user -with the required permissions, you should see additional administration pages -inside the “Admin” area, under a group named “Builds”. These pages allow you to -set options of the build master, and manage build configurations. - -Add a new build configuration and fill out the form. Also, add at least one -target platform after saving the configuration. Last but not least, you'll have -to "activate" your new build configuration. - - -Running the Build Slave -======================= - -The build slave can be run on any machine that can connect to the machine -on which the build master is running. The installation of Bitten should have put -a `bitten-slave` executable on your path. If the script is not on your path, -look for it in the `bin` or `scripts` subdirectory of your Python installation. - -To get a list of options for the build slave, execute it with the `--help` -option:: - - $ bitten-slave --help - -To run the build slave against a Bitten-enabled Trac site installed at -http://myproject.example.org/trac, you'd run:: - - $ bitten-slave http://myproject.example.org/trac/builds diff --git a/trac-0.11/doc/logo.pdf b/trac-0.11/doc/logo.pdf deleted file mode 100644 index 665402ec268b69d884c17379346cf580f2e6355c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 GIT binary patch literal 0 Hc$@`` root element with one or more ```` child -elements. The steps are executed in the order they appear in the recipe. - -A ```` element will consist of any number of commands and reports. Most of -these elements are declared in XML namespaces, where the namespace URI defines -a collection of related commands. - -Commonly, the first step of any build recipe will perform the checkout from the -repository. - -.. code-block:: xml - - - - - - - - - - - - - - - - - - - -See `Build Recipe Commands`_ for a comprehensive reference of the commands -available by default. - -.. _`build recipe commands`: commands.html diff --git a/trac-0.11/scripts/proxy.py b/trac-0.11/scripts/proxy.py deleted file mode 100644 --- a/trac-0.11/scripts/proxy.py +++ /dev/null @@ -1,93 +0,0 @@ -# Based on the proxy module from the Medusa project -# Used for inspecting the communication between two BEEP peers - -import asynchat -import asyncore -import socket -import sys - - -class proxy_server(asyncore.dispatcher): - - def __init__(self, host, port): - asyncore.dispatcher.__init__ (self) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.set_reuse_addr() - self.there = (host, port) - here = ('', port + 1) - self.bind(here) - self.listen(5) - - def handle_accept(self): - proxy_receiver(self, self.accept()) - - -class proxy_sender(asynchat.async_chat): - - def __init__(self, receiver, address): - asynchat.async_chat.__init__(self) - self.receiver = receiver - self.set_terminator(None) - self.create_socket(socket.AF_INET, socket.SOCK_STREAM) - self.buffer = '' - self.set_terminator('\r\n') - self.connect(address) - print 'L:', '' - - def handle_connect(self): - print 'L:', '' - - def collect_incoming_data(self, data): - self.buffer = self.buffer + data - - def found_terminator(self): - data = self.buffer - self.buffer = '' - for line in data.splitlines(): - print 'L:', '\x1b[35m' + line + '\x1b[0m' - self.receiver.push(data + '\r\n') - - def handle_close(self): - self.receiver.close() - self.close() - - -class proxy_receiver(asynchat.async_chat): - - channel_counter = 0 - - def __init__(self, server, (conn, addr)): - asynchat.async_chat.__init__(self, conn) - self.set_terminator('\r\n') - self.server = server - self.id = self.channel_counter - self.channel_counter = self.channel_counter + 1 - self.sender = proxy_sender (self, server.there) - self.sender.id = self.id - self.buffer = '' - - def collect_incoming_data (self, data): - self.buffer = self.buffer + data - - def found_terminator(self): - data = self.buffer - self.buffer = '' - for line in data.splitlines(): - print 'I:', '\x1b[34m' + line + '\x1b[0m' - self.sender.push (data + '\r\n') - - def handle_connect(self): - print 'I:', '' - - def handle_close(self): - print 'I:', '' - self.sender.close() - self.close() - - -if __name__ == '__main__': - if len(sys.argv) < 3: - print 'Usage: %s ' % sys.argv[0] - else: - ps = proxy_server(sys.argv[1], int(sys.argv[2])) - asyncore.loop() diff --git a/trac-0.11/setup.cfg b/trac-0.11/setup.cfg deleted file mode 100644 --- a/trac-0.11/setup.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[egg_info] -tag_build = dev -tag_svn_revision = true - -[unittest] -xml_output = build/test-results.xml -coverage_summary = build/test-coverage.txt -coverage_dir = build/coverage diff --git a/trac-0.11/setup.py b/trac-0.11/setup.py deleted file mode 100755 --- a/trac-0.11/setup.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2007 Edgewall Software -# Copyright (C) 2005-2007 Christopher Lenz -# 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 os -from setuptools import setup, find_packages -import sys - -sys.path.append(os.path.join('doc', 'common')) -try: - from doctools import build_doc, test_doc -except ImportError: - build_doc = test_doc = None - -NS = 'http://bitten.cmlenz.net/tools/' - -setup( - name = 'Bitten', - version = '0.6', - description = 'Continuous integration for Trac', - long_description = \ -"""A Trac plugin for collecting software metrics via continuous integration.""", - author = 'Edgewall Software', - author_email = 'info@edgewall.org', - license = 'BSD', - url = 'http://bitten.edgewall.org/', - download_url = 'http://bitten.edgewall.org/wiki/Download', - zip_safe = False, - - packages = find_packages(exclude=['*.tests*']), - package_data = { - 'bitten': ['htdocs/*.*', - 'htdocs/charts_library/*.swf', - 'templates/*.html'] - }, - test_suite = 'bitten.tests.suite', - entry_points = { - 'console_scripts': [ - 'bitten-slave = bitten.slave:main' - ], - 'distutils.commands': [ - 'unittest = bitten.util.testrunner:unittest' - ], - 'trac.plugins': [ - 'bitten.admin = bitten.admin', - 'bitten.main = bitten.main', - 'bitten.master = bitten.master', - 'bitten.web_ui = bitten.web_ui', - 'bitten.testing = bitten.report.testing', - 'bitten.coverage = bitten.report.coverage' - ], - 'bitten.recipe_commands': [ - NS + 'sh#exec = bitten.build.shtools:exec_', - NS + 'sh#pipe = bitten.build.shtools:pipe', - NS + 'c#configure = bitten.build.ctools:configure', - NS + 'c#autoreconf = bitten.build.ctools:autoreconf', - NS + 'c#cppunit = bitten.build.ctools:cppunit', - NS + 'c#cunit = bitten.build.ctools:cunit', - NS + 'c#gcov = bitten.build.ctools:gcov', - NS + 'c#make = bitten.build.ctools:make', - NS + 'java#ant = bitten.build.javatools:ant', - NS + 'java#junit = bitten.build.javatools:junit', - NS + 'java#cobertura = bitten.build.javatools:cobertura', - NS + 'php#phing = bitten.build.phptools:phing', - NS + 'php#phpunit = bitten.build.phptools:phpunit', - NS + 'php#coverage = bitten.build.phptools:coverage', - NS + 'python#coverage = bitten.build.pythontools:coverage', - NS + 'python#distutils = bitten.build.pythontools:distutils', - NS + 'python#exec = bitten.build.pythontools:exec_', - NS + 'python#figleaf = bitten.build.pythontools:figleaf', - NS + 'python#pylint = bitten.build.pythontools:pylint', - NS + 'python#trace = bitten.build.pythontools:trace', - NS + 'python#unittest = bitten.build.pythontools:unittest', - NS + 'svn#checkout = bitten.build.svntools:checkout', - NS + 'svn#export = bitten.build.svntools:export', - NS + 'svn#update = bitten.build.svntools:update', - NS + 'xml#transform = bitten.build.xmltools:transform' - ] - }, - - cmdclass = {'build_doc': build_doc, 'test_doc': test_doc} -)