changeset 0:0b2a3581c48d

Import initial ''bitten'' source.
author cmlenz
date Sun, 22 May 2005 15:49:22 +0000
parents
children 32e9f0e94c69
files Makefile bitten/__init__.py bitten/distutils/__init__.py bitten/distutils/testrunner.py bitten/general/__init__.py bitten/general/cmd_make.py bitten/python/__init__.py bitten/python/cmd_distutils.py bitten/python/rep_pylint.py bitten/python/rep_unittest.py bitten/recipe.py bitten/web_ui.py recipe.xml scripts/build.py setup.py
diffstat 15 files changed, 369 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
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
new file mode 100644
--- /dev/null
+++ b/bitten/__init__.py
@@ -0,0 +1,2 @@
+class BuildError(Exception):
+    pass
\ No newline at end of file
new file mode 100644
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())
new file mode 100644
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
new file mode 100644
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
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<file>.+):(?P<line>\d+): '
+                     r'\[(?P<type>[A-Z])(?:, (?P<tag>[\w\.]+))?\] '
+                     r'(?P<msg>.*)$')
+
+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
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<testname>\w+) \((?P<testcase>\d+): '
+                      r'\[(?P<type>[A-Z])(?:, (?P<tag>[\w\.]+))?\] '
+                      r'(?P<msg>.*)$')
+
+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
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
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 = """
+<?cs include:"header.cs" ?>
+<div id="main">
+ <div id="ctxtnav" class="nav"></div>
+  <h1>Build Status</h1>
+ <div id="content"></div>
+</div>
+<?cs include:"footer.cs" ?>
+"""
+
+    # INavigationContributor methods
+
+    def get_active_navigation_item(self, req):
+        return 'build'
+
+    def get_navigation_items(self, req):
+        yield 'mainnav', 'build', '<a href="%s">Builds</a>' \
+                                  % 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
new file mode 100644
--- /dev/null
+++ b/recipe.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<recipe description="My project">
+
+    <step id="build" title="Let Distutils build the python code">
+        <distutils command="build"/>
+    </step>
+
+    <step id="lint" title="Run Pylint"
+          description="Run Pylint to check for bad style and potential errors">
+        <make target="pylint"/>
+        <reports>
+            <pylint file="build/pylint.txt"/>
+        </reports>
+    </step>
+
+    <step id="test" title="Unit tests"
+          description="Run unit tests and trace code coverage">
+        <make target="test"/>
+        <reports>
+            <unittest file="build/test-results.xml"/>
+            <trace.py dir="build/coverage" include="trac*" exclude="*.tests.*"/>
+        </reports>
+    </step>
+
+</recipe>
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()
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})
Copyright (C) 2012-2017 Edgewall Software