# HG changeset patch # User cmlenz # Date 1116776962 0 # Node ID 0b2a3581c48d5463390a9aed0b4c0eefad38dfc7 Import initial ''bitten'' source. diff --git a/Makefile b/Makefile new file mode 100644 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +PYLINT_MSGS = C0101,E0201,E0213,W0103,W0704,R0921,R0923 + +all: pylint test + +pylint: + PYTHONPATH=.:../Trac/trunk pylint --parseable=y --disable-msg=$(PYLINT_MSGS) --ignore=tests bitten > build/pylint.txt + +#test: +# find . -name *.pyc | xargs rm +# PYTHONPATH=. trac/test.py diff --git a/bitten/__init__.py b/bitten/__init__.py new file mode 100644 --- /dev/null +++ b/bitten/__init__.py @@ -0,0 +1,2 @@ +class BuildError(Exception): + pass \ No newline at end of file diff --git a/bitten/distutils/__init__.py b/bitten/distutils/__init__.py new file mode 100644 diff --git a/bitten/distutils/testrunner.py b/bitten/distutils/testrunner.py new file mode 100644 --- /dev/null +++ b/bitten/distutils/testrunner.py @@ -0,0 +1,92 @@ +import sys +import time +from distutils.core import Command +from unittest import _TextTestResult, TextTestRunner + +from elementtree.ElementTree import Element, ElementTree, SubElement + + +class FullTestResult(_TextTestResult): + + def __init__(self, stream, descriptions, verbosity): + _TextTestResult.__init__(self, stream, descriptions, verbosity) + self.tests = [] + + def startTest(self, test): + _TextTestResult.startTest(self, test) + self.tests.append([test, + sys.modules[test.__module__].__file__, + time.time()]) + + def stopTest(self, test): + _TextTestResult.stopTest(self, test) + self.tests[-1][-1] = time.time() - self.tests[-1][-1] + + +class XMLTestRunner(TextTestRunner): + + def __init__(self, stream=sys.stderr, xml_stream=None): + TextTestRunner.__init__(self, stream, descriptions=0, verbosity=1) + self.xml_stream = xml_stream + + def _makeResult(self): + return FullTestResult(self.stream, self.descriptions, self.verbosity) + + def run(self, test): + result = TextTestRunner.run(self, test) + + if not self.xml_stream: + return result + + root = Element('unittest-results') + for testcase, filename, timetaken 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] + + test_elem = SubElement(root, 'test', file=filename, + name=str(testcase), status=status, + duration=str(timetaken)) + + description = testcase.shortDescription() + if description: + desc_elem = SubElement(test_elem, 'description') + desc_elem.test = description + + if tb: + tb_elem = SubElement(test_elem, 'traceback') + tb_elem.text = tb + + ElementTree(root).write(self.xml_stream) + return result + + +class test(Command): + description = "Runs the unit tests" + user_options = [('test-suite=', 's', + 'Name of the unittest suite to run'), + ('xml-output=', None, + 'Path of the XML file where test results are written to')] + + def initialize_options(self): + self.test_suite = None + self.xml_output = None + self.test_descriptions = None + + def finalize_options(self): + if self.xml_output is not None: + self.xml_output = open(self.xml_output, 'w') + + def run(self): + import unittest + suite = __import__(self.test_suite) + for comp in self.test_suite.split('.')[1:]: + suite = getattr(suite, comp) + runner = XMLTestRunner(stream=sys.stderr, xml_stream=self.xml_output) + runner.run(suite.suite()) diff --git a/bitten/general/__init__.py b/bitten/general/__init__.py new file mode 100644 diff --git a/bitten/general/cmd_make.py b/bitten/general/cmd_make.py new file mode 100644 --- /dev/null +++ b/bitten/general/cmd_make.py @@ -0,0 +1,18 @@ +import os + +from trac.core import * +from trac.util import NaivePopen +from bitten import BuildError +from bitten.recipe import ICommandExecutor + + +class MakeExecutor(Component): + implements(ICommandExecutor) + + def get_name(self): + return 'make' + + def execute(self, basedir, target='all'): + cmd = NaivePopen('make %s' % target, capturestderr=True) + for line in cmd.out.splitlines(): + print '[make] %s' % line diff --git a/bitten/python/__init__.py b/bitten/python/__init__.py new file mode 100644 diff --git a/bitten/python/cmd_distutils.py b/bitten/python/cmd_distutils.py new file mode 100644 --- /dev/null +++ b/bitten/python/cmd_distutils.py @@ -0,0 +1,21 @@ +import os + +from trac.core import * +from trac.util import NaivePopen +from bitten import BuildError +from bitten.recipe import ICommandExecutor + + +class DistutilsExecutor(Component): + implements(ICommandExecutor) + + def get_name(self): + return 'distutils' + + def execute(self, basedir, command='build'): + try: + cmd = NaivePopen('python setup.py %s' % command) + for line in cmd.out.splitlines(): + print '[distutils] %s' % line + except OSError, e: + raise BuildError, 'Executing distutils failed: %s' % e diff --git a/bitten/python/rep_pylint.py b/bitten/python/rep_pylint.py new file mode 100644 --- /dev/null +++ b/bitten/python/rep_pylint.py @@ -0,0 +1,30 @@ +import os +import re + +from elementtree import ElementTree +from trac.core import * +from bitten import BuildError +from bitten.recipe import IReportPreparator + + +_msg_re = re.compile(r'^(?P.+):(?P\d+): ' + r'\[(?P[A-Z])(?:, (?P[\w\.]+))?\] ' + r'(?P.*)$') + +class PylintReportPreparator(Component): + implements(IReportPreparator) + + def get_name(self): + return 'pylint' + + def execute(self, basedir, file=None): + assert file, 'Missing required attribute "file"' + + for line in open(file, 'r'): + match = _msg_re.search(line) + if match: + filename = match.group('file') + if filename.startswith(basedir): + filename = filename[len(basedir) + 1:] + lineno = int(match.group('line')) + print filename, lineno diff --git a/bitten/python/rep_unittest.py b/bitten/python/rep_unittest.py new file mode 100644 --- /dev/null +++ b/bitten/python/rep_unittest.py @@ -0,0 +1,30 @@ +import os +import re + +from elementtree import ElementTree +from trac.core import * +from bitten import BuildError +from bitten.recipe import IReportPreparator + + +_test_re = re.compile(r'^(?P\w+) \((?P\d+): ' + r'\[(?P[A-Z])(?:, (?P[\w\.]+))?\] ' + r'(?P.*)$') + +class PylintReportPreparator(Component): + implements(IReportPreparator) + + def get_name(self): + return 'pylint' + + def execute(self, basedir, file=None): + assert file, 'Missing required attribute "file"' + + for line in open(file, 'r'): + match = _msg_re.search(line) + if match: + filename = match.group('file') + if filename.startswith(basedir): + filename = filename[len(basedir) + 1:] + lineno = int(match.group('line')) + print filename, lineno diff --git a/bitten/recipe.py b/bitten/recipe.py new file mode 100644 --- /dev/null +++ b/bitten/recipe.py @@ -0,0 +1,62 @@ +import os +import os.path +from elementtree import ElementTree + +from trac.core import * + + +class ICommandExecutor(Interface): + + def get_name(): + """ + Return the name of the command as used in the XML file. + """ + + def execute(basedir, *attrs): + """ + """ + + +class IReportPreparator(Interface): + + def get_name(): + """ + Return the name of the command as used in the XML file. + """ + + def process(basedir, **attrs): + """ + """ + + +class RecipeExecutor(Component): + + commands = ExtensionPoint(ICommandExecutor) + reporters = ExtensionPoint(IReportPreparator) + + def execute(self, filename='recipe.xml', basedir=os.getcwd()): + path = os.path.join(basedir, filename) + recipe = ElementTree.parse(path).getroot() + for step in recipe: + print '---> %s' % step.attrib['title'] + for element in step: + if element.tag == 'reports': + for report in element: + reporter = self._get_reporter(report.tag) + reporter.execute(basedir, **report.attrib) + else: + cmd = self._get_command(element.tag) + cmd.execute(basedir, **element.attrib) + print + + def _get_command(self, name): + for command in self.commands: + if command.get_name() == name: + return command + raise Exception, "Unknown command <%s>" % name + + def _get_reporter(self, name): + for report in self.reporters: + if report.get_name() == name: + return report + raise Exception, "Unknown report <%s>" % name diff --git a/bitten/web_ui.py b/bitten/web_ui.py new file mode 100644 --- /dev/null +++ b/bitten/web_ui.py @@ -0,0 +1,42 @@ +from __future__ import generators +import re + +from trac.core import * +from trac.web.chrome import INavigationContributor +from trac.web.main import AbstractView, IRequestHandler + + +class BuildModule(AbstractView): + + implements(INavigationContributor, IRequestHandler) + + build_cs = """ + +
+ +

Build Status

+
+
+ +""" + + # INavigationContributor methods + + def get_active_navigation_item(self, req): + return 'build' + + def get_navigation_items(self, req): + yield 'mainnav', 'build', 'Builds' \ + % self.env.href.build() + + # IRequestHandler methods + + def match_request(self, req): + match = re.match(r'/build(:?/(\w+))?', req.path_info) + if match: + if match.group(1): + req.args['id'] = match.group(1) + return True + + def process_request(self, req): + return req.hdf.parse(self.build_cs), None diff --git a/recipe.xml b/recipe.xml new file mode 100644 --- /dev/null +++ b/recipe.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/build.py b/scripts/build.py new file mode 100755 --- /dev/null +++ b/scripts/build.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +from trac.core import ComponentManager + +from bitten.recipe import RecipeExecutor +from bitten.python import cmd_distutils, rep_pylint +from bitten.general import cmd_make + +if __name__ == '__main__': + mgr = ComponentManager() + executor = RecipeExecutor(mgr) + executor.execute() diff --git a/setup.py b/setup.py new file mode 100644 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +from distutils.core import setup, Command +from unittest import TextTestRunner + + +class test(Command): + description = "Runs the unit tests" + user_options = [('test-suite=', 's', "Name of the unittest suite to run")] + + def initialize_options(self): + self.test_suite = None + + def finalize_options(self): + pass + + def run(self): + print 'Hey yo' + print self.test_suite + suite = __import__(self.test_suite, locals(), globals()) + runner = unittest.TextTestRunner() + TextTestRunner.run(suite) + + +setup(name='bitten', version='1.0', + packages=['bitten', 'bitten.general', 'bitten.python'], + cmdclass={'test': test})