changeset 502:a3bcc4f98187

Bitten trunk is now trac-0.11 compatible.
author wbell
date Mon, 09 Mar 2009 00:41:57 +0000
parents 6a9268d10d09
children a7c795920c4a
files trac-0.11/COPYING trac-0.11/ChangeLog trac-0.11/MANIFEST.in trac-0.11/README.txt trac-0.11/bitten/__init__.py trac-0.11/bitten/admin.py trac-0.11/bitten/api.py trac-0.11/bitten/build/__init__.py trac-0.11/bitten/build/api.py trac-0.11/bitten/build/config.py trac-0.11/bitten/build/ctools.py trac-0.11/bitten/build/javatools.py trac-0.11/bitten/build/phptools.py trac-0.11/bitten/build/pythontools.py trac-0.11/bitten/build/shtools.py trac-0.11/bitten/build/svntools.py trac-0.11/bitten/build/tests/__init__.py trac-0.11/bitten/build/tests/api.py trac-0.11/bitten/build/tests/config.py trac-0.11/bitten/build/tests/ctools.py trac-0.11/bitten/build/tests/dummy.py trac-0.11/bitten/build/tests/javatools.py trac-0.11/bitten/build/tests/phptools.py trac-0.11/bitten/build/tests/pythontools.py trac-0.11/bitten/build/tests/xmltools.py trac-0.11/bitten/build/xmltools.py trac-0.11/bitten/htdocs/admin.css trac-0.11/bitten/htdocs/bitten.css trac-0.11/bitten/htdocs/bitten_build.png trac-0.11/bitten/htdocs/bitten_buildf.png trac-0.11/bitten/htdocs/bitten_coverage.css trac-0.11/bitten/htdocs/charts.swf trac-0.11/bitten/htdocs/charts_library/arno.swf trac-0.11/bitten/htdocs/charts_library/arst.swf trac-0.11/bitten/htdocs/charts_library/brfl.swf trac-0.11/bitten/htdocs/charts_library/brno.swf trac-0.11/bitten/htdocs/charts_library/brst.swf trac-0.11/bitten/htdocs/charts_library/cl3d.swf trac-0.11/bitten/htdocs/charts_library/clfl.swf trac-0.11/bitten/htdocs/charts_library/clno.swf trac-0.11/bitten/htdocs/charts_library/clp3.swf trac-0.11/bitten/htdocs/charts_library/cls3.swf trac-0.11/bitten/htdocs/charts_library/clst.swf trac-0.11/bitten/htdocs/charts_library/cnno.swf trac-0.11/bitten/htdocs/charts_library/lnno.swf trac-0.11/bitten/htdocs/charts_library/mxno.swf trac-0.11/bitten/htdocs/charts_library/pi3d.swf trac-0.11/bitten/htdocs/charts_library/pino.swf trac-0.11/bitten/htdocs/charts_library/scno.swf trac-0.11/bitten/htdocs/failure.png trac-0.11/bitten/htdocs/tabset.js trac-0.11/bitten/main.py trac-0.11/bitten/master.py trac-0.11/bitten/model.py trac-0.11/bitten/queue.py trac-0.11/bitten/recipe.py trac-0.11/bitten/report/__init__.py trac-0.11/bitten/report/coverage.py trac-0.11/bitten/report/testing.py trac-0.11/bitten/report/tests/__init__.py trac-0.11/bitten/report/tests/coverage.py trac-0.11/bitten/report/tests/testing.py trac-0.11/bitten/slave.py trac-0.11/bitten/templates/bitten_admin_configs.html trac-0.11/bitten/templates/bitten_admin_master.html trac-0.11/bitten/templates/bitten_build.html trac-0.11/bitten/templates/bitten_chart_coverage.html trac-0.11/bitten/templates/bitten_chart_tests.html trac-0.11/bitten/templates/bitten_config.html trac-0.11/bitten/templates/bitten_summary_coverage.html trac-0.11/bitten/templates/bitten_summary_tests.html trac-0.11/bitten/tests/__init__.py trac-0.11/bitten/tests/admin.py trac-0.11/bitten/tests/master.py trac-0.11/bitten/tests/model.py trac-0.11/bitten/tests/queue.py trac-0.11/bitten/tests/recipe.py trac-0.11/bitten/tests/slave.py trac-0.11/bitten/tests/web_ui.py trac-0.11/bitten/upgrades.py trac-0.11/bitten/util/__init__.py trac-0.11/bitten/util/loc.py trac-0.11/bitten/util/testrunner.py trac-0.11/bitten/util/tests/__init__.py trac-0.11/bitten/util/xmlio.py trac-0.11/bitten/web_ui.py trac-0.11/doc/commands.txt trac-0.11/doc/index.txt trac-0.11/doc/install.txt trac-0.11/doc/logo.pdf trac-0.11/doc/logo.png trac-0.11/doc/logo_small.png trac-0.11/doc/recipes.txt trac-0.11/scripts/proxy.py trac-0.11/setup.cfg trac-0.11/setup.py
diffstat 96 files changed, 14078 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/trac-0.11/COPYING
@@ -0,0 +1,36 @@
+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.
new file mode 100644
--- /dev/null
+++ b/trac-0.11/ChangeLog
@@ -0,0 +1,71 @@
+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 <java:ant> 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: <sh:pipe>, <c:configure>, <c:cppunit>,
+   <java:ant>, <java:junit>, and <x:transform>. 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.
new file mode 100644
--- /dev/null
+++ b/trac-0.11/MANIFEST.in
@@ -0,0 +1,2 @@
+include doc/api/*.*
+include doc/*.html
new file mode 100644
--- /dev/null
+++ b/trac-0.11/README.txt
@@ -0,0 +1,141 @@
+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:
+
+  <http://bitten.cmlenz.net/>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/__init__.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/admin.py
@@ -0,0 +1,343 @@
+# -*- 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/api.py
@@ -0,0 +1,120 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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`
+        """
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/__init__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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'
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/api.py
@@ -0,0 +1,290 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/config.py
@@ -0,0 +1,178 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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<ref>\w[\w.]*?\w)(?:\:(?P<def>.+))?\}')
+
+    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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/ctools.py
@@ -0,0 +1,365 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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<file>[^\']+)\'\s*$')
+    lines_re = re.compile(r'^Lines executed:(?P<cov>\d+\.\d+)\% of (?P<num>\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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/javatools.py
@@ -0,0 +1,225 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2006 Matthew Good <matt@matt-good.net>
+# 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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/phptools.py
@@ -0,0 +1,113 @@
+# -*- coding: UTF-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2007 Wei Zhuo <weizhuo@gmail.com>
+# 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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/pythontools.py
@@ -0,0 +1,466 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2008 Matt Good <matt@matt-good.net>
+# 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<file>.+):(?P<line>\d+): '
+                        r'\[(?P<type>[A-Z]\d*)(?:, (?P<tag>[\w\.]+))?\] '
+                        r'(?P<msg>.*)$')
+    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<module>.*?)\s+(?P<stmts>\d+)\s+'
+                                 r'(?P<exec>\d+)\s+(?P<cov>\d+)%\s+'
+                                 r'(?:(?P<missing>(?:\d+(?:-\d+)?(?:, )?)*)\s+)?'
+                                 r'(?P<file>.+)$')
+
+    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<lines>\d+)\s+(?P<cov>\d+)%\s+'
+                                 r'(?P<module>.*?)\s+\((?P<filename>.*?)\)')
+    coverage_line_re = re.compile(r'\s*(?:(?P<hits>\d+): )?(?P<line>.*)')
+
+    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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/shtools.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/svntools.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Christopher Lenz <cmlenz@gmx.de>
+# 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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/__init__.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/api.py
@@ -0,0 +1,234 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/config.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/ctools.py
@@ -0,0 +1,156 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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("""<?xml version="1.0" encoding='utf-8' ?>
+<TestRun>
+  <FailedTests>
+    <FailedTest id="2">
+      <Name>HelloTest::secondTest</Name>
+      <FailureType>Assertion</FailureType>
+      <Location>
+        <File>HelloTest.cxx</File>
+        <Line>95</Line>
+      </Location>
+      <Message>assertion failed
+- Expression: 2 == 3
+</Message>
+    </FailedTest>
+  </FailedTests>
+  <SuccessfulTests>
+    <Test id="1">
+      <Name>HelloTest::firstTest</Name>
+    </Test>
+    <Test id="3">
+      <Name>HelloTest::thirdTest</Name>
+    </Test>
+  </SuccessfulTests>
+  <Statistics>
+    <Tests>3</Tests>
+    <FailuresTotal>1</FailuresTotal>
+    <Errors>0</Errors>
+    <Failures>1</Failures>
+  </Statistics>
+</TestRun>""")
+        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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/dummy.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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())
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/javatools.py
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2006 Matthew Good <matt@matt-good.net>
+# 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="""<?xml version="1.0"?>
+<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-02.dtd">
+
+<coverage timestamp="1148533713840">
+  <sources>
+    <source>src</source>
+  </sources>
+  <packages>
+    <package name="test">
+      <classes>%s
+      </classes>
+    </package>
+  </packages>
+</coverage>"""
+
+    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 % """
+        <class name="test.TestClass" filename="test/TestClass.java">
+          <lines>
+            <line number="1" hits="0" branch="false"/>
+            <line number="2" hits="1" branch="false"/>
+            <line number="3" hits="0" branch="false"/>
+            <line number="4" hits="2" branch="false"/>
+          </lines>
+        </class>""")
+        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 % """
+        <class name="test.TestClass" filename="test/TestClass.java">
+          <lines>
+            <line number="1" hits="0" branch="false"/>
+            <line number="3" hits="1" branch="false"/>
+          </lines>
+        </class>""")
+        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 % """
+        <class name="test.TestInterface" filename="test/TestInterface.java">
+          <lines>
+          </lines>
+        </class>""")
+        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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/phptools.py
@@ -0,0 +1,131 @@
+# -*- coding: UTF-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2007 Wei Zhuo <weizhuo@gmail.com>
+# 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("""<?xml version="1.0" encoding="UTF-8"?>
+<testsuites>
+  <testsuite name="FooTest" file="FooTest.php" tests="2" failures="1" errors="0" time="0.147397">
+    <testcase name="testBar" class="FooTest" time="0.122265">
+      <failure message="expected same: &lt;1&gt; was not: &lt;2&gt;" type="PHPUnit2_Framework_AssertionFailedError">
+      ...
+</failure>
+    </testcase>
+    <testcase name="testBar2" class="FooTest" time="0.025132"/>
+  </testsuite>
+  <testsuite name="BarTest" file="BarTest.php" tests="1" failures="0" errors="0" time="0.050713">
+    <testcase name="testFoo" class="BarTest" time="0.026046"/>
+  </testsuite>
+</testsuites>""")
+        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("""<?xml version="1.0" encoding="UTF-8"?>
+<snapshot methodcount="4" methodscovered="2" statementcount="11" statementscovered="5" totalcount="15" totalcovered="7">
+  <package name="default" methodcount="4" methodscovered="2" statementcount="11" statementscovered="5" totalcount="15" totalcovered="7">
+    <class name="Foo" methodcount="1" methodscovered="1" statementcount="7" statementscovered="3" totalcount="8" totalcovered="4">
+      <sourcefile name="Foo.php" sourcefile="xxxx/Foo.php">
+	  ...
+      </sourcefile>
+    </class>
+    <class name="Foo2" methodcount="2" methodscovered="1" statementcount="4" statementscovered="2" totalcount="6" totalcovered="3">
+      <sourcefile name="Foo.php" sourcefile="xxxx/Foo.php">
+        ...
+      </sourcefile>
+    </class>
+    <class name="Bar" methodcount="1" methodscovered="0" statementcount="0" statementscovered="0" totalcount="1" totalcovered="0">
+      <sourcefile name="Bar.php" sourcefile="xxxx/Bar.php">
+        ...
+      </sourcefile>
+    </class>
+  </package>
+</snapshot>""")
+        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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/pythontools.py
@@ -0,0 +1,413 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2008 Matt Good <matt@matt-good.net>
+# 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('<?xml version="1.0"?>'
+                              '<unittest-results>'
+                              '</unittest-results>')
+        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('<?xml version="1.0"?>'
+                              '<unittest-results>'
+                              '<test duration="0.12" status="success"'
+                              '      file="%s"'
+                              '      name="test_foo (pkg.BarTestCase)"/>'
+                              '</unittest-results>'
+                              % 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('<?xml version="1.0"?>'
+                              '<unittest-results>'
+                              '<test duration="0.12" status="success"'
+                              '      file="%s"'
+                              '      name="test_foo (pkg.BarTestCase)"/>'
+                              '</unittest-results>'
+                              % 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('<?xml version="1.0"?>'
+                              '<unittest-results>'
+                              '<test duration="0.12" status="success"'
+                              '      name="test_foo (pkg.BarTestCase)"/>'
+                              '</unittest-results>')
+        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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/tests/xmltools.py
@@ -0,0 +1,127 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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("""<doc>
+<title>Document Title</title>
+<section>
+<title>Section Title</title>
+<para>This is a test.</para>
+<note>This is a note.</note>
+</section>
+</doc>
+""")
+        finally:
+            src_file.close()
+
+        style_file = file(self.ctxt.resolve('style.xsl'), 'w')
+        try:
+            style_file.write("""<xsl:stylesheet version="1.0"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns="http://www.w3.org/TR/xhtml1/strict">
+ <xsl:template match="doc">
+  <html>
+   <head>
+    <title><xsl:value-of select="title"/></title>
+   </head>
+   <body>
+    <xsl:apply-templates/>
+   </body>
+  </html>
+ </xsl:template>
+ <xsl:template match="doc/title">
+  <h1><xsl:apply-templates/></h1>
+ </xsl:template>
+ <xsl:template match="section/title">
+  <h2><xsl:apply-templates/></h2>
+ </xsl:template>
+ <xsl:template match="para">
+  <p><xsl:apply-templates/></p>
+ </xsl:template>
+ <xsl:template match="note">
+  <p class="note"><b>NOTE: </b><xsl:apply-templates/></p>
+ </xsl:template>
+</xsl:stylesheet>
+""")
+        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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/build/xmltools.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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?
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/htdocs/admin.css
@@ -0,0 +1,6 @@
+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; }
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/htdocs/bitten.css
@@ -0,0 +1,183 @@
+/* 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;
+}
new file mode 100755
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1e2f5669121b121d8119ca1ca05ff66663a41075
GIT binary patch
literal 300
zc%17D@N?(olHy`uVBq!ia0vp^JRr=%3?!FCJ6-`&%*9TgAsieWw;%dH0CH6Vd_r9R
z|NsB&*|Vd^PoG~B(Kl^QOH0e_8mF|hwBjhusHi9_D=SMa-YEH<-$7=T1o;Is{6_%J
zBLQkaan1sd$YKTtZXpn6ymYtj4^U9C#5JNMI6tkVJh3R1!8b9vC_gtfB{NaMEwd=K
zJijQrSiwZk;FX$sDNwN(NU?KKYGO%dex5=|W^O8jfw{hcp}v8s@ER``pb8657srr_
zImrnLAwek&Oo7(}7}?m4P0(;<6-c-A$O#MC$vB}aC1N7aBcp=tk_|6qZ|sy!=t^W}
ZIP#C{{ok5>vOqH!JYD@<);T3K0RZWzW`O_z
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a5b902f0caf9aa53a97b38b3cc0436f483f24d92
GIT binary patch
literal 289
zc%17D@N?(olHy`uVBq!ia0vp^JRr=$3?vg*uel1OSkfJR9T^y|-MHc(VFct$mbgZg
z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-$J4)6(ajf#pYj?%Q$;+<XN^m*E}
z^GhQ7rp>XkvO0SF^s{Hr?q_Cp^Ygc~wA^4|`2YWZT3Q;AJlGg74b(1J666>B9}O_5
zuAP|#lnnQDaSW-rWpXB3=zxHLOJK{4^e_LyXB!GGJ!LDumLWmuQAOaQw`RtEZ{{7@
zGI8&O<x?d09%JvmDl+jiqr%--{zdac4hcVYkJ;;%cV&0>YE#Cm5hi<>co>u_*nX^-
SuDb?k1%s!npUXO@geCxb9(I)g
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/htdocs/bitten_coverage.css
@@ -0,0 +1,4 @@
+/* Code coverage file annotations */
+table.code th.coverage { width: 4em; }
+table.code th.covered { background-color: #0f0; }
+table.code th.uncovered { background-color: #f00; }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e2821facdb1d55065baf6a435dafe72de5d8e777
GIT binary patch
literal 29521
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
AdH?_b
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cbd15765f453d2ac3725c7740c87be1593c3f5db
GIT binary patch
literal 6346
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
Mv=aaRKU-6&zehYvYXATM
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..268f8e3042f54d4fccbc9161c338c9f465412609
GIT binary patch
literal 6619
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
Z8NdQMxcV7<Jj>aRoxuS}0|4K2cKHYC%l`lX
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7b61a52c4cb18bafc176d2fc35def4b617037cbc
GIT binary patch
literal 6472
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..30d4aa2dea3c059fdc08b7900e756018f8fe1c01
GIT binary patch
literal 6508
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
SOJZ#oG@zyU_x}J8wfM^g_{1{+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c67ab32fafc00d2c3a3f90df799452111eec4d39
GIT binary patch
literal 6462
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
Y1GVJS;YYokTkDbtT8e-F7h~FU^*S0y&j0`b
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1c2f7ffbc7eef1402abcc7a381ad11e42c292cca
GIT binary patch
literal 9346
zc$@)(Bz@aMS5pSLvj6~ioa8-wd>qAjbElKE_MPz)3}~eQCXPu0*fJypaEvV5Ck1(>
zN(O8fIIZqhr^Tl`ad&#y;gJNWq0odTL=AD1fWaY@S3(}prXh}vAq`I36bR3>B_WP^
z81hTn{792p{pOo}%<RtIL$-|atNz&A{pOo*zWL^R%zQJu8Y7XP6B2!tkXVSch9@Q_
zNZrbs&wZ1S8?T$$5z<?8nc>0gC1$EPKoa>}u1M0YdeKPd@>_a_^rV?hlXQ=fH;qCf
ztq+lcQN-VLcW$&m26d>F&kbi&#3B@jvOYbJI3|$FWeTJKzXN){xLPmjB)!@gf=Yz}
zJ(U|xkOXN-r8@L2MxM2PhFvg52FN-}UhjkYEn{XOF`{RN4QO+W44PSsW+HDSi)6qs
z(*s2^lq;A;bHqsKnV|ul=sie_HMoUrCEb9#d^H(bMq2eulHN6w;+7$UAdyj&K{A(t
z9*vT8hnd}s91jf_21q)u4-F(N9<2vkE`?FEm>fU}<nr3^Akk?<7J~>R86%qp;;qAd
z1~hB2&iccc&SlAfnKJN_*lOf+BsT&w88lO=j6ssa`2tW$uQvvVSa*^cbBGj;v0?(R
z0?7nI20#jCI!ls9wg{9)tjE4wv6urEQ9?;%u3&Bj5#)+GveBQ*76B`hBCGTI=z2;j
zLy9C%`k)cUNAuF9XVc4B^s=0QT+>kVq!#;T6=})m^(`bxhVn+gIYv@O(j3$?i6k&-
zKY(5sLwa5>0>PeQ9uzInnj0L{NnyC3)+rQ?p#mN0L=pyz4m-Qiaq!le*>#`@tiKtv
zP$YT>y(TT)Ww7)r?CtsiGr2iy6l_II^yTs?kO`ATqUaR&XL3}`9Y%lAt&@Q#4kt|q
z5jsIf0Ze6ZH8iAWlLl$enpC@ztcA3zjZDg`_55Zd+h&Y0jj%XL=gm}NfVkv0ZX%$0
zR7*B&6p%*9hD{{B&KxsRV0(mhAyI^S1rnzUNeZJn(+=BUSmB_4MJ~^Ez|s*cGqVM%
zb-Cdp{xV%Lz?yrC&@j>0fMpkQ88c;3VO@bi5h!LsNj8Eq4TI*eGInoLY8xF8cH6aN
zrlxv_-e+V;4lF5aX<*9K)443D%kYqU4~Cp2U=qMLD{=0zM9-$dPP%f?<oaA!E(3Zt
zw4C4#eFiWYI*aJ2&dl!e=rW2q@M#0!aHvaR)(zfZ5R7kKZp1WNshR0Lo8jtc7L%Or
zDu})w?mC!->#bZj?JA_e6F`L`qstfvoES%&DLaCy;qVvpde-t0*<9A3ZBXeZskOlP
zxW%WZ%mR$o2&m_9p2-pi!k|W%0fS7h?dl;J^tS0WV@7hgXpqE+Sup!Rc&s&)5HpVg
zx;L`kZJv6B4kK+qt7-PjGsiIkeMBssxpo^F{jBxCLXP#odSs(zP$l|J1Dp_55{8-q
zv#>ln-ZF*4Th4;bOvXaOTg&!<Z|akomh=s0GDZ<=BKbBJJwPsC2aaCZ+!VTuJhe#s
zncf$`Qp^n4L_Q5_Gti}{QYjiK2imEyLBGg|0~>K*I<0DrotaMpCzP*NcFS~+jZI>V
zq&xfjA=YkSBB#wKw%9;0HD;dhV}N>sFT_hLx<~33=>?yRu_4eHs{G(G^d5An7_W^~
z3z%)1L1^V>tGJm4ZQ5IKZC$wn{&we_07D(R6DxpE=XMK8fvdO@xaw*nEmY3U-8xt=
zC=GOCBk5^@IFlu<EufMG(zA-Rt|C2g(i$i0*Af^Qvc8M7Y#=Svk7SLG91R^K$O#=3
z)1>Zn1HEjdmrX=xaZqQv(FcJiyA}o<N42N81tdz1czq55@B<@<<^6dY-*jutZRZW{
zwogxPCjB}%@|5k-A=VW;L9ao4M86pH2UMwptRouf-blJPk?svPxY@p02G&V^25r?<
z+ZI&FJji3X0KVRUSw*s#&vY{YRn!(oWm_G!W_Nm5MyFB$Z((LFCQy&pfmeo!0d_KH
zX-m#h5ca!?^HLkQ`;FZFCNP1)Ar8Qr({Wyuf&dB8Af*=u;AeD{B$F@{CeazYOm#ta
z`dX(8cx!Rq;dI?neFy3~&+Rj<=tao)W8)E`5GACJhlvu_#_LIa3g8x?Mx)n=79Fo;
z@L4emV1>0Bg@yncoki=>JEabb>bn~$T}2^k0Df3(Vut3n&<MnonTXg(%Vu&kqFUR$
zFNMh;!sAh*#OjFkhj=l0TUg<{VYh6SW?@?xagDX2jfY6MeI7->+D3;?9Ai~GM(f%J
z=$+$r1fa8_LY#u<#68sFR2E@ZhI6|YQZn;`E2y`HNi;k@izxG*9yud|qNn6$d1VpS
zVsUAY=_65NPQ|Dz9RSC1Xcm+ehCQOkBHnS`Zc_^p=XlKvIYHwG_C$m=Xt72w42aJl
zPEXsZz#9l<s!>^>vZ4bkX;G~4_Ar@E=vX&$V_im?<EOjC{WtZs{F5?YnQsXO2`Q>A
zwODj~Hc^^f%=cKzETF($4cxU-o@`PIkgA|6JU0TSLjmNiP~?+VpuWVQQ(d(Gi~g5B
z&V&x*<C}jhZyv8HlTxNeP;KR8ryq__aP_D`?3OE3rwgbur>sCyi-JkTt04vc=~U&t
zK<s2BGhd?uX|kT0@M*EK$S#AcjTfbw(@@_`3B8YYcMegOxGmKSEioQXLjvyyo2|~l
zr0c74FsIq*fez*a)jF6DmUb|Y)Ez|!<EccE+Fju3R35dV_Bm7%3(@k=pbgvPshZEQ
zz%ljcyDN~>nO-?9#o{wK=`hGLZKYOD`b;h|2e2ThY=Ojcaqa~=JA*=+S8%dkjtrfJ
z*R$K_c?&*-QYY;lAoOOkuo86@yflv1D>d@=KHLw5D~Y`D99npPNM866T3Ay9Lw-Yz
z=zoaHxeK)T1*+{X5fJ2&nKIYPTnW@VS2<TjL!*ubp3hUxFJWrv*B!qD@E@W9hz<zA
zw}vQq9(W?W1+RlYE36)WG*HGdqO4RdP+F8#N~<8AR@AH20&F$H)(EgQ8jUt<T{Vdd
z#c{0CCcxT|<zsB=aK5Cydm)U(A3~Ll1QYa}GVXiIWX`~cBlB~nkIc6bJI;MfITPU@
zLn;?qPZ#1-hxOEfPwT9wb@)`i-vFqS#5~Q8dAC!p+;GdY(Il|3*kuE*HXaU9gUgds
zbRjFk5DdRdc;@I$=|Id+hDP5JCPRmQw|zD)*Auu@hT3PW@;bI<{@LcWL_j)@^Rb;u
zm-1&ycYOpNR&@$!4UE6~i|FM-dRahg^aS8iS=O8Mass_9p}6ZsT<A)zhgYsB>K1Vw
z@Zhn?g%-odvH6;BMdqu~B-CiCpvGdM#^MTUG<c$GsTv=p^P?IZpcVE2K`8#HJ2)3z
zAfij7Bt?Q5a)&!Z?hxi}I9)SC0wF|ln{OrlQkV}0Gh)@B>7s;@iHlt(F7{8;pOrMD
zF<fh^2uODZBY6YrC7Ktq!3WvsgKXj;v%Flua7A33{PXU(xAvu4yps;}#ej|`Id6jb
zsSfsd%^|l2n|591%p=>Z`5%t}Lc0tLI|VRI#!ql$D;7a(m)Ck=VVv*TB79vmcdK=-
z+fjFFE7)3^*6twQ=8mt1)_*Vg^~%#O{mkxICrBfHc(fKg`1Z;N<F9Ou`6Q624N>|F
zeDo7kG&6|O%llR)p@eTne7O%In>9-Yw4MbPMd#<EjDgFPUX@|_m_ETOI>BFbyj65O
z6oC)u<4v!TYQs!%X~vc<NtHQARIJlb7JaI>7y@CZ5JVoEpiQJE-9)OQCY%o1IW=C<
z+N>N1j|3b<WGe5}w-RP)uNBpx6-BVnS8qlo?al~pcSgB(XL#G?mVlME2BI@8lzwbA
z?Pd-<5oDm7t7Hn5uBfb1*c;Sj<~OTz%JQTz)86cl-m>^Ci@>rN3$TneXQ#=oR#e~3
zs@pww-=N_TF2I%cpo3uL+&EZ+#!<*1g+UeTig+2Vkv$H&3$jKbZe~|3bOrCrdqnBd
zAm>eOIC9Ks<Bg><dx98Ls#<mX_Px6D$NTo}6N-j3&jkF+?(q|a)QC5A=PV^>i)dcK
z2dspgwBF%#rN95DEBoQEY*&19!Npp+WLt&3KO$WT6K@Y>3`_E-l@3CrBDV90Uhs$e
z--ZHxe;@Drp9(P>wF8VSz2pT|k%I!-MS;~*H9_W{j<ot91s`O!<`Z3$>KB&;r<7g~
zvvl>yk!Mz>7m+Hs4pan-WhXkT+#Gc@NH3lW)(c@$e^-bX$7o4$jQYee>J!JPAP!G@
zwxpd%EBN9{DT}cLH8x#4Q+^LYI^L@fjn@!mOW2;P2F9YTk~I6d4yMaXtuSeD^5QIe
zZhH-UNbwJL<+vCmKOPVyxjEG_nkQSb6*;(yZCjLgUjI`&rOwNNU5&77L~HUEU#nqi
z`8OeN0ZR~n!kb#=3qMH<ivfk7qJ{Sox|<N9Q~c|^LCNGcKhWh~@8#PbNw)zLh6^Y@
zLZlRZ6{CF3sL+V%&QB>t<M1h#XPXc@-yBhHaP>x<$vPxF68#IyX0KNrA1sRsh;v<O
z9NsKFt!(paim1KrrrVqVHJVdH8^6(A_z79iBd6oA)Vayc?%bq$+4)PDT~mHO5vKgy
zOoai^8}ZttM*Zjxqq7)6VIR#|1Nc}$PjWL)(8f(qPpjHCS*zN1#I33_isjj^(5kli
ztm+nzRoz^HRR!R9g56D^K^W|M#p449e@`EK%UJt1E$vy{+i;rU^MS8n3&dWvoSzTS
zMQd~My*6QPDQ{{PWhbU~Ru<wOiXEL0_g9)t-`U(}hgm-DMWEupS}0;|T;gkWgAcON
z2VrSjw|6BnwXDLWTD;K<`wU)hr51fDkr)0oExa`>FZ?VmOhn{`pF=NF2ff~|(7D~o
z{?iARtDqVjEf+JK{lwXLvZN#_YjZH}&~yOhh=U#92F4~oO@h=|Q4?3x<T^a>F4pJq
z9Kvm2A2WzD&h{s7Lv}VHqvGoQZ@Llh3q0PpkZnKSOrqSboT_}0-ztUcxOI=i{{`nk
z+&gvfns_|=AlXd)w958Nw+nmJ3+>rHw_m!Do87MwU~3SzmUb!PZkDn#akG@cJe#GQ
zK-;^i8C|7}&-Ex&MXECkRR>i;(2MAF&<iJ|xQ86Vhph!A2ENQ#|Lrsx>s#w4H`a4K
zZerT`0b#sr#qlob9Z-FznRmEg8hMAyBS&6i-bD`qb;pItt;aWP3ebURtt->TyEKO>
zr@XG<yuI?-h*W8&xpxWD1h2*fc_B_?7FDP*iw;X;whL#q-d&9$I8mM{taZ4<t&GT*
zG`6HUkuE=7<@mC4yUNakex+N?NuKjtx~#c_Th^34<-L|Wiy4?`yF=vDw*r%7Ts&3g
z&6(@1bSF!-Toaa|L%}*+<?QgS5dnBtP^t<`@Al-JsASqC#J0(ZZ7WIa7OioThvGe*
zuQY#>D3w3CmgY~s?afWx#iwx$Pa47y6B}dl#D?KdzTBnqL@{A;UWF}7s+P!g>_EWX
zD$7^Q$J1L}e*St@9z^BX*9BmE@*H=7{&h84dg$=F6D9ps&EBN;m=<q%cmDBRONiWA
zO+v(Q)gUZMh*ae{u8sy6ZeuiHp<ebi0depIU8KRM{Pf8)b^)0Qz7R7LZDDyKW+r}9
zBQM0v#6p^xSfT`^6UuhjSxJ=b`LtMbRFqS|lfU<P=7A-qekEf!BP%()>YTt9SD8B{
zQGKT*s_!fj)ptsw`p&XZot}GMbo3PAU0h1|2Ur}_Ne$!IrqM87S~uBY{L?Vw@k*xW
zhh^?Ze%V)<BRFT8XwZp~sn(!rW&ic0yhh1&pwgO?){3u9Bdv&pr#?JPPpyzEgZFK$
zG8HHrlhT0;rcrDm<cKUduJ|vvnsZRud)n+QZ0lYxn(sU_d)svY6BB0&b)3Uj!F9ZR
z{9O<q_j%DO;N!l@^O5%R@e6JZ04vT@O1|OGZM^q$+TlA)2-o{mxnbIjaK<e&Pcflz
zuWFfvr!yr<NKdD-s`vZlFfoO4;8=H#2KqU&J0tg3B?VFe)xg<R6*7|9&iz%{PX37K
ztju;~FERw%&LsGd_>v=OJAO|jTF-p}SsPjA&8EfKSb(zJt5&TS5??k|wiDnRy^bYF
zG}TcJG^omD6rra|&sDkOqz@nkcChzBc82Ap2#sM6aWU+8qI_NXhVn2|z;Aj-lhODV
z$NRUnm`7jhyQ9tlKKlF+@C=KU?<hGuyW$PIk07!4d)@&7_8+A$wM`{ITPx*ftIJR6
z)8T)Qb*-~Wv5>dGI-kUb;GXu9#b0cnONl&6rb+}6DgsIK-$6AjEtv@2<dD{ltB$Dn
z>Cm&>;d{pLKj8JdG)H#|kr&=W3!kAm^e>0XzwG-}QvO}sj}SWYvstJ95o`sXJ|H}u
z?><Gi_gx>v_gx<X!FgGIxSZf|Ug*3j+cE){RH!_wtUIN<Ty1w8E)KUlj$E)ipRYjj
znW^uN!_>zo*gT#;ZvQ<#cs%C!gfHXAFutyQ--Cj7@~{8|5zUg=;bYa>A^M<QmHMDv
zlk!2erG3yY+z<E(IqH2ss?O)-L)(wzKeidNzn&QhF=9VpA1S$*|5ix{-NJPad%oxq
z(E~COJy1$ShvlY!pvw5GP*j1vvt(s`T*LbOC?OZwJHe5lH)eJPzP-6ym_O}CMoNFB
zVz=-XEUQuaD;2wicVu_lv#GK*8iX1pzET19wZ|Dg`8&=oQ25N%)gf1Ok2?;~8Pm=Z
zn|6;|ZxWmx8b5;_yx0}+y5{2g4Pnf$d~kBP`@NF<%3eu+Wv`uIk$(hduO!8?w`_`~
zk?Ev;K;j9k@FZsydsuemJ@%Xc%x;gQf@tC2I7Ii#CBm;gYQhiuKBo2mNBF!TeY?$j
zx!D~vy&q~`$UpfYKk`AI@<H}-kX^pg1|MXx57Oj=FlRN>mD-OnE7-!l4#MR8&v^Y8
z?BaRc4*niTl<Z7U<N_QxuMq!Li^b`;JU&#S>N_igt9se_310sV**VUp2@3x-`^*Zo
zw1aEu=|H*cLG_{ikNfiwT&quu-*=hoZ&JV&EDi9Fl`!)-|5>DGT!rH3cv*VZ<K_LG
z@s$45Jv|gh#y>1-Cl^>sSN65btJ^c0%+O%m%Ce*R*l9Q%nm#W7)J#vNaP8LjEFerj
z=T3t`!*xunJr~&U(}HU7FN66c7nL;}WoNCP7Zl_<l|MZ8jEu_{1THrtmoHSWJQec$
zjKD8=@E0Y-==B8^UrAP864w%5Drm2#6X`6?#}FPK8?6+%yxs_1l#0d8E5?Jr=VSX4
zw6Wnj+2E!vM=qPb$~k`tLC@1yIHA*n)l@BQ8uD@7BX-q+^Wo@aZ#qSYRkZNb5b#<E
zQ@!Yr=><gZ<;&k#xg7r7FTXZco<?1&d8N*MLDl0jI@~jaVYZ-&SSPE)&icmL(^T9x
ze#TZd|L&|!_#uw6_T$crwrrvEAG8&z0&xTKpBlzwGgv&rq01e#2f~M}Db@~Eh>TH8
zeC)91olxrrR|fS}iH;q-bfps}j^yl>U`!alxD4O^s4Q#Yl_M%jH}LpmdTeM4bV$yG
zpu!cPI6`95@dTi$R5Z5NTow7BTW_ndHy|DSUuf~F7OErM2SDe$FY@E`=Xsp>H^}4`
zu|Dr<^Hj~JwxPdQPOep00=>^&0xc03sIB0;qA!7~W}EphaXR%hL<4_>aAId0%IREE
zX@s0Zc<=S18W+z4<bS_?1^@q*>g<0SV*Qg5{NEzne;Q#otIa_pTcA0sC+AQ%@~Y)V
z;P#j9ZRxD)Ng<XTeN_$0&Au&uIWg@2LM46JuLfhK?SstF(*6J(l*g}XLyeU*cA!LK
zZCqmqN;UQxZOlpxQ}{1cjMVQO5|YPH;_~%Zi1C_=v~Q#$s}0dE3!l;Jx2FhDc0fE=
zO^Y6u7PZl$yXh{*GW@vE>!LF4SM3ECs?o`o3|%mbHLokLGj@M8;UM_8iR}`2e4V~j
zg9KQzjv>9#9ka#(=b~RBOiuXlSrW+J?<OWD+KxVZ>?Isl;ed)W>Uq|%y9~tfjgO@3
zAn1o3nF<MmXOR+MbNq;D31WKQ9NdgkvGHC@JNL7<Dc)4^O^r>&)mvZZkL$Kqy<B~(
z;>i|JQpv`m2nUs3wNWxa2R&VKK71@GU`-vQMV9bl;^;jw;UIVy+Ko<0iP6Ijjo8im
z^_p*5syLHxnoYiJGiGmAwAD<JB1A$md?>hr1g=5eRB;TJqIB#ph`RdC?t9f(T+FV0
zZ^DazK&5kst1L@ox^&QLA{Gr^1XaYq!HOAp!<In_g+B?u9kbuH&0c0R0lMC+`V%}%
zL*J^V3f6cN`b!_*QcJ80Es^NN1--@mOp_=jZk#)r%~mytw*<)W9^#SDLEERqgihFl
z?C)#&&K;5QX0|nti;+)Em2I7G>lHt{`lRdjd2K&Ry}unl(0w!voBpsFjq^nqZ$91s
zDeaBu2`QJ$!sTspX^4r^5u%`J0cIAUdaMKdKKp(Zd---Xgxoz(o)VZstkm$jGj;3i
z0545veE^SdmmKi>CITz6k^h;cdcB+_20N->17{M`l~pLIylg+}|Jpm2*r=}Sz4P(R
zW5&kRZ36XU2HOF~5XT{q0EsbV#)%CElM?!&Qj|`?#iKa33r?C=T~z9(sOm1V?2Ic_
zDN>zP+D#W}x<FO4sw#C+smrRWsw}dI0h>}x+<VUby!-C^+Mc*nVl(sJx#x55x#!$_
z?)T{p)2E+D!}QZt?%A4SFZ=Xu4RP!<#G7`~35#@#oD*lN1DZybd_P9G^&<~of0k=7
z)ws@r1C0w2t-H)waO)n>3&gOTwYGa7uDY6d9zQ*q@YZlcOkwlFq}O9aDIb!6N^Ob=
zGy+>dzm;ZDu*05$$<Y9Z<V5*$?(+RMpnN|g4QOl{*ng$qTL7QnoX!qlkb+zOKX$Ho
z?XTzL_nZkyZv{;)X0~F6N+f$T=!R-zzbhi&ijA!L%iGwgE`{HYe;e<sU-H`l!s(ae
z&@TtmFX7D51>$qN{%!2gmoU*!ZaCetpi9B&?$>P}x!&V}?C++Sit9jNVMH+CulgE>
z%0>{IW?lQvxuvs?lr~KY`@4m^ZQAhgB1^l8X+^Om=%&DoI@`ou<&J~TrQvhlc`1(Q
z&-p>aNB+%AaSP`m3o0}6^z0owDjVv6oK~`;W(ADR-Rz=mQ2&PySME7i_p5#KG8RFE
z6?IL20K$C`nQ(2lk`j!Pe#S+v#v@1)i%>?9CKjcFq!3F6qe#T0(1au<NinXFR7?s@
zNSbEv+D2Lv#qTs&8OoB8+*2ia2ddRWGqTK5pDIiOJDVd|)TauQkQcjIeU#J70<F(W
zn1Z{r)yEoilL-7uXKdbSz8o{SCLxZ^XIdzFbED5N<{tXcLmvwBAq*y<3xw0I))p#4
zF!r3I0?zF7nid4583jW`mWx;d_TpM+e`s+JEiTyN7($+=nIYwg)l4<RO2Rva3|Sc?
zzMGU#Eqa8-p;@A#Qvv*g4onE(q7F<7U?<gpDFJ**2c`w^Egje-fWOp%83FtYY!k^R
zJiSu<?>RI2KT!|8yTge%3(r_wi(=vFq5Qa1yj;w$P(-mH6owofF^3!mF{?}ORh4Vd
z!jD7gW2xW-nXyCiA6<T;_u{_O9<OQLy~n2pbG~34$g~jQM-)%*Xp#zHk_scfeyvMt
z50$hH4s|fg2jj;f?a&G&H&d-EKu^w78>n|6^%h7LJx7k(K(_@Gay%g=uVvS~eQxf(
z<L}JP&Ee%>*U;18bLB&=jO!2X)`v;ujc<>#A+bfKCa-=2elEbzH%ayDwM)}uq&hTO
zyF4~cssp1_@XC*`R4bF?lkD&1vFfGC@A0HxZ=LBP+1~7_%<(d_@nQaVI&<UqpRQ)l
z;2%FTf&tqN_-6Q<I0|Q`VYOf*kS$nS`9slarKpK*)cA(Iiuy7bT1HSZNjpOr%m@Yy
z{R>>sIvLHzX5`D^5#CZdz6ge6wb5gbGM$F)T>@6t7b!nt)}a}v@|Ub&_9cO&$G=@B
z(jx+CMnjqjLYmc(W_^+JPEM^y0l?y+?bM|z$9Wu`v6LNV3Rj|1UIK^HQrMU2F^sj^
zH^SRfK|V5AO)r=>7sb()63V(BWJHY<F&|Sw8xT>B6tO<mEPnn#nNu0txb2)rJ5}?D
zEEuvWgIhZ(rh3M8AE2S~O=0^bemml~hvLIu9bXo>v?s{S7NE}788IY<%IcQb3xK^4
zC21aC%7&zQpq-B^sSMcVP)Q7NCsjbE$^t!yUfN8NM(;_a>W6Ta|92Mf;6w~z1paM`
zZ9yHvxL=XL^aV)&hz6$5L;4>Rf$3#P|7X%a9jyWo{*ekqXh(#P(o9+ZYSMe4;MO~;
z2MTamzosj07Zl)#pIaZ)N;0oP-=e;0dELBg>AUBYM?%*Gy9m<Z9eC`Hd(G<FxSBRW
zJwHkLswZ|~qtz5u*iEpmc%_l*o10t0cr=dsP9_^<T^m<d1D&v8>e-k0Upe3UTf23t
zC&SePS}~Ncn>Kg|@gTZbTn<4>VngU6-?^v{A^wg>NB$3o5Jfq?nuKRH`NX#$Y{S5!
z7nz+JfnoM83Rr`J{bgs-v=<bWJ!K%Ab?8s~&<~mPr-SK-J@f^y{9yw@qd((A?~#AT
zL*E2y0T50t{OvPmuHh>H*tg7y`|E9ZE`W{xDXD&K^x`xd(tkApTJ7LQ*M<+lQ!BZ%
z8gfA*2h5JRkc$$zs3DKLkXfcXsXGI5T{MUDu3gkEQ7*jl)W@h>gj{{)sk<p7N64z3
zhkQN8-|pkiK8CP|N{~S4QV2@=ZfZ+Wd&J{j$})|9nxGEjBus@%!aWLwx>10F3uoot
z8^$|DojfIB23!(~VreLy4oc4CEsKDQ<M$9R%h5ZXoHY;XGi>6Z+zMh@9JxzF)@Hy8
zBhE3p{(1))s9l||PF#e^ri3)N1M=Z1QpL&Ok~F+h48to)!z;xwyplA$Qk3B_tUFe>
z!V<LHc;GD{DX*m!^t6JD%uMp#4d9@+>F8}PPG%A=Hr%oKuMgY)`nO1>cIDzYpYNNz
zQoCH65N>~^da3rtRZ^{t*QUsKYUAU4f)AdaAD<ellJVNaD4!98C!PvVJhl4Lcy(fw
z{ICgfw2#8v-V{FXr`}C>tvKv{+*q^dp=y8rkicSH;wv~W2CY_3e}{qV6ErghtG%z-
zxUdELIGRD$y$<Wd_MU<B`984mUW%NPfbYyScvpFv`{3b8j(UK)yHJ2V&Xb<vfBQLx
z(|0o1L0%JY!r-!-Xpm`u)O5&O>Kmuaa4?Ov;LPL?>DAh!0)RuxZqR%WDeDWEFTd``
zF`Sbl&zIx5hI1sSj9t1%>{6Uv;YN;R-&Tgw7qr-w05d?d`4~UX`!-z4%j<j)m&%@|
z4#e^Gozc}+qGq-2O|SwSR6`S2tvyZ6MM7p;@Yf4Xw5c;MK?2q&UHDNIE@N0H8wq(!
zvNsn!e!1}At4#F7%>+|OXTqzH4Hh?!KI154W95yZPG5y=qj4<6tATK*YKTH&XdomP
z>s1I}$MYRKB!UMX5ZGJKKuh3WFiWj?h6eSQ$!XRjvCb{)7hi}atMg;Ei?2<LUS*w~
z=)1&{*7@&GjcUofM|&>|P0nGx$zktDjQ69)`?JRTbH;n0(S-ST==}b-WfF`b&$ltv
z9HW4PVO=)`416q6mcSo-s1{S{9CNsIa#$Nv^@|n}d+QTQKAJ_h?nm!xbN6RwT3iKX
zi8-jK13G<C3!dEc7<VznFc?s+(%_JZ>*W&?jIlnw6h>Sexn7P0>E#){Ty0tf`IYNg
zS+4bIV^J@w5qGYyti=%*M_$&W-m*?N-Uj073J8w#SnC8%*Ka2p=y}R_uQ1+wC=ivY
z#wd@4t*FSaOg-DuGd#$y=rN)BZc?Bs@<i~iKtlx2WhJLV`MKKni7QL#2Ovu}ao||j
z6;ao`VP<X8fYBxmC<Yi;{UYhA5ft2#(7nQmpQP;o<TpA5<ENvr!ROCZg7pIo*KJp$
z{sO1Ej=wi-!&*#R;#zf#(X>^=;Z!e`<=2u}FY>vvLygZ_ct(8k?Vq!E$b*_Q0*?4s
z;u^raD(0}VdKMof!N=`E_YmIIkRLJ7pEJ;nOdd7R`w*R%zYw;4LerLDE}E%~cB#34
z%jGVVYI%4zeOgL)sr>PSi>2c?fp4OF3Lu{N8eXZtO3txZCn)@RQ4Q+xQN})MFprNK
zREv{%fOIg)Rm#TprDRy|{Byi>M+CodZ7nQvEp3p;jF(5r&7%~_W7b=iHSu6o)VhgE
wi7wFQC?%pS%y0&|l@C$xl*>t;bc+8CQ)9{{WYqA$ELUV$>_o_a0f69uNCJ%*egFUf
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3d3da8e80ce449c0d0c9b2853073942e598dc209
GIT binary patch
literal 6404
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
STm0(0pcyU4fBzTIz#+=UR%}rK
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..edc00972484cc0591683f7f0d9d55b423d81c92c
GIT binary patch
literal 6487
zc$@)O8K~w%S5pQwfB*n^oaH@vd>qwzZ&zAr<drY^;uwXaaWMpgu>l8zPZ?Hj?2#>-
zaI+onj#gu@cIDmGVS+gl5}<}aDOGVO0pn1PP$)@AXoIn#fu`vZC=h5$Vhq8MhF{xX
ze!u1y+Q#a8-<#uIGdo(X6(IU!cILhBd+)p7_l_A2qsF%oN<M{9YYcV7M@L6d)AEKX
z*CBM%4DH`hx>PJyP;TY0T}}_0`2h>{*#%^-GRqdqb@ZC0O1flaDrh_Ewd`DP1@)Pg
zUSw`A<C|erDh?E~$m}TQ2l@&Z*;!nb>o6-;u2|Z>vER(tg&fLlv`V&BPUp;iRJJPc
zo9iwPm0i3PF4%0Apu)!Z#e5l+QEr_*Y-KSV>MNn#YO9~LT{8Q7(<qHrX0vO}?N*7B
zf0|Q1jCviKSDO_Rts|7Wy_bjVN~RaOES8LcKEg-I9^SOQ-$I*<!)OSV99)1{ZI-s8
zyj93mdf8%>w+oh8LYu9eT}YM8tZfx47ufkcRBWtl&tv%&2dzy-D8N5fAq(}6EUAcl
z?W~o)$|@C6f3a*=>|z1aP^lC#RdzOu+b9lH$`}S!iv1`vP%7h|bDONbekz7c-tI>g
zYq*j|Lm+c<&-IXolHRnLw{r!Q!9-$N4LXlzzQ5N*8Q`aEUu9t}R!ktGr&y?9O7dB>
zx?~P*B5hQNfWQLs2-o3WC^`KD<zCVTMjk@BwRT}E$b56Ll(kCfik;btl~~N%v?*L~
zp@j<;oJB5YlFK55y0H+8h2vMcH>=RfQpwzoGN`{~_1MEGYh~;{GoQ|2#@q+os?~3n
z%nGJqBi3Lcm+mO`^_i$V&_i&_6|28YY&e}U^BH2K^u|OT_-CD6SclD)$|!G_E67|+
zt{KN>X<2R+C}UGE=AvMg%dTiCbB?Og6|cG{UnB}zYxPuooIHMFV6yfg%FtGFs9@!=
zzz3}~RRT2v?~&RlGFO70yh^Gz50ySrpoNIot5xW<hAFN?aIR!$)7U&bDzH5<G_g>F
z#8Od-xHtomB94X1LngJnvg2=+5&@<`gQ~jE+)^wth>jIPom~K1rdDX-aIvw1yG7>t
zxDAR&`Nm4)+9+WAy9C?V05(cmNAGRY&Lv(E@axAfYWn6{bF-C4#S(E)YTQ}d%oPjR
zoCf;+`)qNjfCjA+rj*ur_fTVk4^XFFU(B1OO~v&^+&>i*D)$zL(j;C|v0y(T&cCiW
zXj>h`N6n2}@zpafDh|`P5pxs1TkAv_V8Mk&Wfl{R2@Rcrh8AKm+FmrNl+1z?KMF<c
zVc2qrfHK4(uyFnA`B}S+y>ZY&*?|%j2guSQewD{Qm%Cv7MwEw;nmd2k$_!L2lpeIp
z_U61h4uB}A2b3YEqD_AH;N(^g%!2;1#4&_mRshPR@!JK42;)3B;|P7A9Jye5&}xru
z;SfdiOhHMgb^HNtGhV1>ofJo52MBH{5TUyp_iYw(n9<Dx`MgyDOlZE74tZEEI3Rnb
z;ZjpxZ<QdhInU&tctQz}4XAg$nayU&_}9CJ$Qj!Z8uZEry)u(vIcl>@8B7Erse|59
zv!ZHA52IXHPY)hJFGlI@?)@dmTt~)heCdENN5UJqz>_uHkIe!%-H(Gbb~_RpVaT?!
zD@i1wB?Nh)Q@s$~J<0KUguXm$B#tzslWWW%*SIm#f|`)?*V)!~PaMrhBaKLFaNrsN
zE@AkXw4}*MweO0fk*L6mo|{o~4EL%vL$TH^g0vViQgX7~c56~-YEpMQp(^RXwL(`}
zQcnve1L71TIg+5|lUw~(ry8V`w1)dqwmVHr>H+X^zKiLG-$OfYOlt$cc2d{Iv@yfz
zB$8h>V<d^RnVvoXZl<~~K#0#ErL**r7>cg}<xK&7%*JIzJ8jMrZo|P#wL7ih@;Oec
zbBy&j;<~PpCWK3m!jyrwj)v=O{D7qp_oa7C_Z)(KtOTdu8AoQ@$YiAH9t)ldK-Q!c
zNrAc{ilfzNO{J7vlgFgq>a~^hC4`GT<^t?HM<g@^GHG|fK4Ca5xbks`QJG%N_DNLH
zAF*54;*J}yQ!uii0we-k;};%J028q_(}+;!0Cgu2Ex4j4M7KD_Gj*o5VA`cnvO*it
zkanW>dz!B4U~>tr0UBx7ljQM}#FaoU?fS?xq|I&m>HGh?{NY)<uV^Msb&O0!&7=6H
zc{RBZ4pxz(G<mwvFY;*neW2w8T`W1tSkB08o&+_|hf27Owjk}~<^<8jDNE;23nP`y
zz#V4pR5Ws;&#-*c+(2+YwR8>^<Z)=^7}PwEtB$cSq<tzG9j?<Dw>y=sCW2E!%@2f1
zQa_!hyS&kWDPu(gPx?G)r%`k&#M42D3m8F1G8JrrQ7|CHfw68+h<%;`*M&W#5~(W}
zk;?v<y7Ej?d1YK(c^0X>qCs7GHmTgyh*kV{BQfN~+F~7|X*;%j{QPO{966ria+N<M
zs5yXREO8v8i8u!SJ=f{`yg>Q1B5kR*Ok1uk)K+kWt}sXgE4i|jP_~LITV-I^(mH(Q
zuI8#&1Iqba+4<luQ@~UCLE?gLV)j357_UsmCyGTM9#MQRUI+8IJx0t!vJ;#%G(p`*
z)Jz_V)j>AE<~Z%;tPhV&?nGqhOlh6k8YbA|X8wTpj=3RG8&`jZKt~b+E@*P8iTTv5
z1%P!x6Y^-%d77$ttU>Y${M*HL?2e^khwGURXQ=asD2c-inmG8wEuTDG`a7CDq|{LJ
z1ED@{$-`weCl7X5@~}QEdAJ}fc{rOS56{=0Jbe5@-&3_`4Ii6y;alTO7gBDP;M+w_
zS;B|NEOytPC4Brc>+tj-VqV}_%EY7xb;}C~#!QT{o~%&3@jiKM!wtdX1MmBhR=vWB
zBkqk%l;2?78ApS#%+c)?jm$#L3#H-Kk(j)6#g;6gE1gPE%ukEY&&KXKcTa`I;WRAA
z<C^|u;#hVPKiCoKuSFctY-T{+q+O_eR=cRVk)$A99N4i=$^eTdpCOl-1mofWSfb2k
zn_P}27m|Bum+-JyVp9cLnW`Cn6~ll4M}-5<DvOF+ca<;_97ZAn#u7nK)iIicCABb&
z^GJ@-018^_Drhn;_LXz#3Lbo>BI^U7X6(HFGVxr-lBUvSVm1+>4m(%(yYE19EthtQ
zggE$<e2_R0ahXrVWegF{TJ@`nZVU?Rq+1whcQBA&K?C09hFEfiRC1+Ml9uvCh6=2%
zM&IB5usbDUn?|aODEcxC4&kG1l!nf^^zoX*Vl>(8Ek$=a%cd*<7TRX0>=N<fzD&;K
zaqy@d_XN-5VNx=QSQjQ_CXy@UPFnm<PKj}HacE|QHN#(1mzT)bO5L@LrLF&-_g~9T
zzWlc5S_$c?@wGMcvHx8DSnA+attxKCz<#VG+LmC^W`jhVC8EXiH;Wy8XrEa_q#`4g
zs21oIX4Fi_Q>|xdyGX!h&C)59M0DJ#ipy0kPE`x8!jUJ(I($2kMH|1bL^oV|;d&DF
z>61YgIbE#A<9`=7%sw+py0B~R!j9C1$Dq4k7pm~dpztXwg?sqUW?WA6-O1p)Q=n49
zn_dlXx`@Z@x2UO{E>VeD-{OF5g<kVs8{#fxUpIqP;E;;ZkXpr|Q00D;VG!tt*1|Ro
zlOzk@CT<XVEp8(TR(FeEKszn@vFZfGRpQo49BA24g;UNH!ec|OZ93^kZ>ZJ`fjS*>
z!HPfu?E4OIg@Xn$(^C}SB^CG(EgaScAk`&HLrJdpd)!#W0N@T<5eo&1s4)AztK$9&
znUF(4mXvT-%LIit`dg7z_UsdSlCH1iZ~UL-=I&p<E^N$QhuDR*0cc>@n{2Eina83<
zzgWHq{}ICZxy<%%ho69TvuI0XBIOCgpG*i<I#VUAkVS7nC?P4oup$;ovJ=v(#I0fx
z4qMq131w2P=z_)NeW~{S@npMLST=kUFT|D3R~K&z^&LSNfqs<GMe-9y{hUS+i^_on
zkdK)&$>OaYEs8scXeGuLZ&ui1y(0Sr#qr~>Yg@7ZhUQPy$s|kFO|n!6lZ>X#Q^#d7
zAFZhEqg@4PpOfctt?|I5JjW*GKZ{W#b2EQc+^#veyPdM@SU#AS?6R~^lj-!*QVI8J
z0T*(;1i}t>7>P1UF+^`O#M%{7$x5l@bCL!_JuZ^L1VzU&wk$0#a`UMMW-Vh!kik3%
zhH#BH83>-#U895L48|g8w-~71YxH2fUK^;_Um}r}AieI8^twaR>y9z$HGCu!4>4A6
zGXO7KmqUj~8jv<CUUgcwL2+(#u?Ok*IhDDe;n3}R9z59WgNu`D=VH=zUQFWYVP<c0
z{54csumL*09#d9z)ze!@3TwT^Y5nuKg_Q2L&93R)HySM_eyQn5$3$B81t`0LH+Hf5
ziw5jne>yH!(0$}Dk$vRlNL>lr)0KeA8%gEE#Hm_hWJ%{HF}W`=2X`UOxml#!-B$8D
zDA1$f-$BY2CB!6)ndCYt`0E*@MQi*mRwo;Yn?d~9aoWHi8KOLIsKdZ;vp(K20AFv?
zRpCRRcBgD_0po4wv;brLEbP+X;x89+TZZ-X6dV|C^#jALx=7BKjU1%rFF<O3o0I(A
zqDw+@!97AJ(+0U?<5i@7n=JK&k^6A^=FVDub7$RsGg^z)$&Tckw@bdcGrDgo6g+m{
z3~WU0o0z3ru^qzjbj)wKk>31(JX(Q~#^o<3EKIxQ^c@hP_yz+E-qNd$IRO^!oTQq|
z49ErFC}_*B3R7LthTDD36EocYvEjDw&FPN?qq;)`AmZP_Rna~!mwLTID!EcBp&OMx
zcOiR|6u~s)w^{xKu6L5Sa5+*}{wb-vCZ?_&A(fBD)s=rnDxYk?T<>ojnS`{vw3N0>
zUX7SePa!wYM~IvDf2G?2P&>L?oQ%587^l&@4NsBip(XF^(j7WIk#je9A}8ji`pk)(
zn49b~Cvs@nDuXPB_$P9J9iVMB2ge`Hp=H9+944E+pqm)YSH~m1`baIj7qU88MXqPn
zE?4aC9nf(dM7gmcM3mln_<BeTqkSLdbs>UTR!@Vww0;IRKOQ2bItnM$YL?Z(YA&sh
z)wIVZ*lMb})1ul1qdquTyKoQ;9Dyq~w2?nI=n#JlN$ULG@6qmZX4PL6RwU;AnrQG|
zW|*(~vg<l#J2r3<d@oO@l#wXrF#<~4wCN(!eRKlz#kdNbPzIy^ZYgKNh4+gDyQ5t@
zyAABl5s73v6^-pwHMTRfvD+z11lw#6d;u5#^y`Y(Z&RX%$P$#aL=km5lMb8CTuY`i
zPl{88``LVXi!dwL!p}2?)$@!k@?7YCooy8659dbgS%Dfj^{7C>Zk@&ia=n>-e}Wd>
z<P|B?|9i0+u5WkD(d&5+$n9%%emND19>)6LSN`v}Hcz;3Ec1jdzEb0gdBT_i$n8x5
z{yPs;b(aIJIDzV9_nHHD(QnVS_Jrm2Y$fFNnV7l~^7^CW>PpD#|07ObGHIaavmc=6
zv#Y0~?#^Sibds68Y@uu91W1`n-K9)2_b5iiJ&I9rPsHSXk79V-Q+0SG2O+NKW#md=
z%>4bu`1q5P=-OF&EY7jy<t#35PDWIt5*O7=iBdvbJI7DQR2-+qr_FINDkrzK^Y$hR
z*XEdcTQ)vRRp+O*^C!PV;s#D{eD|4c^pEwo8uSGxcvS&jEWzg0<(pU4S4ze`24D1n
zkEY!CbWVR`oCifP>C`6u{6`<8?5A@$-hO2;j!4I~L3BJMf(5ksFhj>f5$SkXrXw%Y
z@p|}*?pD|I<d%f7b{q#b@XteISlyKyd{9q@ZMi7VDG=trQ5*ADM__(@oQ{tzm5pCL
zCL8z8T12;Tg>9*9Ty?BE*v6~Tp|Ej<&)2BtVssl<C{;Fqgr`&)w5pmFA*4pvy|&J5
zEs?!4j8g@65c6=*V6o1eFn9!T=nm)C>F&W3jbu!Clo=C_LfT(yk7<unL-{Llx<K1_
zf}!LaMyp^~le)c6S^y7^VnMVZ?VH***fkKxv2S_fnAheu7<!*1ud*CTdNxF*XM;~q
z(0&jF{5Ie^yF#rovBKFIqAGY`O=$J5HE?F=DRiVn&`3m}=>D4!Fv2C%NS4!-xGCKe
z6`NkX${gUL1b+vvPm%fEiAY`f0I7U|EV_I?R{e<0U8wqdf1e_!R<)xjQ-8g54tYAq
ze`;jjrh6LRrh62NN+j;_YNBrE&g^uJl7+Nbb>b)k;Y?O%!0UIs40w_N25yWX-7Aj-
z10ESSkJ1F2J+<{W>`Bt^%JJ=u*hmxco~H#6u>QxnLM)WY3h(;vSiK7(%igGwWp7Q9
zB@rH3_QFEaOK8G*pU{x<#^JkXvmbqpxnFT@jM2uPp#$(`?DrMmV4b&l{$oKzkElfS
zNSKJm6?~u3RrEy^RUmhcten^LozLJRbfLSG&=_>ya&O>kaQnFT-1jkgSI`^f`?$AI
z_PL2oRg5JZM)-T~`?&Yq_c^&&AO`lY{i^pXtAAf|N6Z)9vtG#ablMfN+WV|?zU!>m
z$Z2#}Z!fzBoA7U!G+!Kt6^>Ks3J1kkum7r6>7Ij1e(mEqH;ZM~K4@9s+6S%X*FI=j
z=-wQ)Nq2+oZvo+Zj92WZYY@k|BRzJz{R#x$!=E@zwoDYlpE_Z}k4xWd`rQ#eD@bXZ
z;&~=#qptV7A(p%#mHeGl@_nh~@0pUlQf-q|vP3FLNF_8_P4cDo16ZzG$sE$7a{dRn
z{v)t>F0=Ks-xDR>Q*K-ViW7zSPeyBsyqAA=2<oqw2cwGQ{4-pC2;|Im+r$R{BK=SU
z?&*G}rxyd|vLDQc@Sl=bBbZ)a<i8LxUG9>D<vb0;&r%TcEc;=Vmwba_+L0F~_$V(k
zBh>zjzx$j5!WZ+_$qX#v$Wp(oxxHkl91ZKODmjKkPSbeWl&Jiuxt_}4z{Y378uiP5
z8ca@+!b;7{fgQidnFf1uPm)|x)p3$;xxd00$jdsr(fN{!%2zomw*r-~j@@`7()$ue
zFOJ|pQZz=cuj=q-i}o5nmk?XY`5()dvNVYxI72WL6}Y<Ha4agt;-7~|1;57S#uD(c
zfhLu5b50;vrLStrUxT7o$iaHdbl+G|!(BtI*Zn-JUO77)y)G`2aAOsz{A3K%T8<OD
zn5(jj7XSTM|MT<hRo-A_mP6+mqSTqsh6*8Lu1-Zp_rK=q+|H#lzlpnvjOO?8uXJYf
zV{dN4syIpe_g`V76P<r^wz*}N8HK!Iz?f_Yi-$XVvzu@L>CSsFQqu$2yLE14Ou|yf
zZYS?#R+9IME6LSF$DX~J%bvZNtGhG7R!)B@6}<9bPMt*Q4?L1gXT^?y4XKIXOgI9F
zCnPEzVE{UkhDHxtjj8#cI7b=icOE_XpBkw#O;iW@?PqSI`4!0fn^baZboQLR*F(p!
z`HcD9R$H%l3S{_Gpe~MpM1(Ex*Knw&Ti&lRHua)GKd8fx5X=i<L{3Xk)DdzTVx!lO
z$k@tGl1d-ky_Ee|UQO=*wQ>Fr4*dHY=AYrvo7HxoRVb4+t69^lubAEtVd;1S|HZVp
z{5?DJa6k`+%7I{t!0v$nn0BWTaWCyy2a8sDA3Y-in$1;t-&O-DzL|(g|CmEX_Q4Rc
zsXplde=v}2s6413;VnXfv&igOAN*&a!`xDI1}4LALi2Cw(C1BLl`#<$<k8vI<I;oc
z4)VwQdfe?)sj8DyeU0pNoDn0Y`8IEw2XuG(mUF&2<qFj)rX$ze+S`=eAB=hc^47Ck
zgCE}}r)Z!7y7WhZe%9^k2Qu4x5tS4?+<^kg`_<^^Xy?R}M~{S4go8Td=WUKT_yo|y
zkG+1lA)kDsrX_ufOH}AI88D{+%(c;%{B`Kua;)|M-Nt=KhvUDOBHx}R=Cy8j4$a^@
zh$lFO3M3bb036aRy<MT8Ljp_pxF2FwRM4&tkt#=ct-SVL81(?;^8{{9i1vV?LAQI^
zuGy}YhEsdjG?jDRm>%%$Y&b%O&=^y}<BBaPa7ObE{V>G>kdFIpLEpaVy`{IN__eyd
zqayr4on#_cJDx~w>5$U}`C`?$2sjj(fM2*W2r>BM;r-wPzHeI8X98^9(&gnMYN79r
z1qCqP!TgfPclD52rXvy)`9<&2IP(c!N{pVncDY{$jIP960%Z6g66ABpjVY}h6RwhF
zUaK=#7kyLTYGcO87mk!~o#)yWo2y;#hke%CCy4u9@WJ4TaBT8JGac`JU2%0@{<QCv
z5D5vDXT+=f<boI#r6)wr(gNHp!1O2s?5_O*9k_gNEDiZPsBBBv6<Wj8>*xK1t8;ki
zvtPq`FI3@Q9}PsLn*WJodKTk}!GY@Mm@<Xyst`hrm+o${f$8T*A~5Y7rJl_=_OBFU
zTU{J`9g^$T_-PSqk-MBX`~pHFuKrDgbn7~)U3HUVFx9Be0uzl(szi5}vf$_*FbYJl
zoUz8er$@OEPxGe*aaqH4F@=dsllmfLDNmySrLzD3HNb<_5m2>n?J%}jV$7Wl(2}IX
xUy|qV+X4LDis}$->NtKilCOln11m&v(m*5m{`Wy|0jy(LGg^Uv{~u|x0GOx=#@+w`
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a5e6b7973a525c8738b733036c3dda2330ea4b02
GIT binary patch
literal 9304
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
GZAkrbjtgx7
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..866116d4de72f211e1ff7a0bece0bd0f70222934
GIT binary patch
literal 9338
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..811e3d26d9e3056b2145b0bed6eabb16b5651ada
GIT binary patch
literal 6487
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a740afab2e9dcb9b94738b57cc63388e35f1cb60
GIT binary patch
literal 6727
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4d7dd27b1e90089a091d707f7ce747634ce2e7a2
GIT binary patch
literal 7016
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
GD?V&pkAuMg
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f682a88233e0675c5561588423ed6cc00e9d2004
GIT binary patch
literal 8566
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7d450c40f902be61743defaae782269e2984bf14
GIT binary patch
literal 8140
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f2f2d537b1fd0f96654f8f1005dc2ad0206f2eb9
GIT binary patch
literal 5498
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c452702490bd5c175c0716acc03c79bb5c0ede47
GIT binary patch
literal 6848
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..702258c3d72fb128148646894daed3c9facc0513
GIT binary patch
literal 206
zc%17D@N?(olHy`uVBq!ia0vp^93afW3?x5a^xFxfSkfJR9T^y|-MHc(VFct$mbgZg
z1m~xflqVLYGB~E>C#5QQ<|d}62BjvZR2H60wE-#;3h)VW{XA{j{mjg6e*XXe|KDI>
z0Fnm{m_SCdl?3?({|5nv&HI<^2Z|VYx;TbZ+;TZ($ala%fXPwe&Hw#xK5I_OEaN(2
t-}tumYMQ~{bx-Un99R8yFnY>iz@R9@@I-illP6FwgQu&X%Q~loCIE_oN?HH_
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/htdocs/tabset.js
@@ -0,0 +1,54 @@
+function makeTabSet(parentElement) {
+  var tabList = document.createElement("ul");
+  tabList.className = "tabs";
+  var contentDivs = document.createElement("div");
+
+  function makeTab(div) {
+    var title = div.firstChild;
+    while (title.nodeType != 1) title = title.nextSibling;
+    var tabItem = document.createElement("li");
+    if (!tabList.childNodes.length) tabItem.className = "active";
+    var link = document.createElement("a");
+    link.href = "#";
+    link.appendChild(title.firstChild);
+    tabItem.appendChild(link);
+
+    var contentDiv = document.createElement("div");
+    contentDiv.className = "tab-content";
+    while (div.childNodes.length) contentDiv.appendChild(div.firstChild);
+    if (tabList.childNodes.length) contentDiv.style.display = "none";
+
+    link.onclick = function() {
+      var child = contentDivs.firstChild;
+      while (child) {
+        if (child != contentDiv && child.nodeType == 1) {
+          child.style.display = "none";
+        }
+        child = child.nextSibling;
+      }
+      var item = tabList.firstChild;
+      while (item) {
+        if (item.nodeType == 1) {
+          item.className = item != tabItem ? "" : "active";
+        }
+        item = item.nextSibling;
+      }
+      contentDiv.style.display = "block";
+      return false;
+    }
+    contentDivs.appendChild(contentDiv);
+    tabList.appendChild(tabItem);
+  }
+
+  var divs = parentElement.getElementsByTagName("div");
+  for (var i = 0; i < divs.length; i++) {
+    var div = divs[i];
+    if (!/\btab\b/.test(div.className)) {
+      continue;
+    }
+    makeTab(div);
+  }
+
+  parentElement.appendChild(tabList);
+  parentElement.appendChild(contentDivs);
+}
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/main.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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 '<a class="build" href="%s" title="%s">%s</a>' \
+                       % (formatter.href.build(build.config, build.id), title,
+                          label)
+            return label
+        yield 'build', _format_link
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/master.py
@@ -0,0 +1,314 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/model.py
@@ -0,0 +1,940 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/queue.py
@@ -0,0 +1,314 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/recipe.py
@@ -0,0 +1,278 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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 <build>')
+        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 <step> 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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/report/__init__.py
@@ -0,0 +1,10 @@
+# -*- 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'
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/report/coverage.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
+        '<tr><th class="covered">5</th></tr>'
+        >>> annotate_row(2, '')
+        '<tr><th></th></tr>'
+        >>> annotate_row(3, 'y = x')
+        '<tr><th class="uncovered">0</th></tr>'
+        """
+        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'))
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/report/testing.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/report/tests/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/report/tests/coverage.py
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/report/tests/testing.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100755
--- /dev/null
+++ b/trac-0.11/bitten/slave.py
@@ -0,0 +1,410 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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())
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_admin_configs.html
@@ -0,0 +1,240 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+  <xi:include href="admin.html" />
+  <head>
+    <title>Manage Build Configurations</title>
+  </head>
+  <body>
+    <h2>Manage Build Configurations</h2>
+    <py:choose><form py:when="config" class="mod"
+         id="modconfig" method="post" action="">
+      <table class="form" summary=""><tr>
+        <td class="name"><label>Name:<br />
+          <input type="text" name="name"
+                 value="$config.name" />
+        </label></td>
+        <td class="label"><label>Label (for display):<br />
+          <input type="text" name="label" size="32"
+                 value="$config.label" />
+        </label></td>
+      </tr><tr>
+        <td colspan="2"><fieldset class="iefix">
+          <label for="description">
+            Description (you may use 
+            <a tabindex="42"
+               href="${href.wiki('WikiFormatting')}">
+              WikiFormatting</a> here):
+          </label>
+          <p>
+            <textarea id="description" name="description"
+                      class="wikitext" rows="3" cols="65">$config.description</textarea>
+          </p>
+          <script type="text/javascript" src="${chrome.htdocs_location}js/wikitoolbar.js"></script>
+        </fieldset></td>
+      </tr><tr>
+        <td colspan="2"><fieldset class="iefix">
+          <label for="recipe">Recipe:</label>
+          <p>
+            <textarea id="recipe" name="recipe" rows="8" cols="78">$config.recipe</textarea>
+          </p>
+        </fieldset></td>
+      </tr></table>
+      <fieldset id="repos">
+        <legend>Repository Mapping</legend>
+        <table class="form" summary=""><tr>
+          <th><label for="path">Path:</label></th>
+          <td colspan="3"><input id="path" type="text" name="path"
+              size="48" value="$config.path" /></td>
+        </tr><tr>
+          <th><label for="min_rev">
+            Oldest revision:
+          </label></th>
+          <td><input id="min_rev" type="text" name="min_rev" size="8"
+                     value="$config.min_rev" /></td>
+          <th><label for="max_rev">
+            Youngest revision:
+          </label></th>
+          <td><input id="max_rev" type="text" name="max_rev" size="8"
+                     value="$config.max_rev" /></td>
+        </tr></table>
+      </fieldset>
+      <div class="buttons">
+        <input type="submit" name="cancel"
+               value="Cancel" />
+        <input type="submit" name="save" value="Save" />
+      </div>
+      <div class="platforms">
+        <h3>Target Platforms</h3>
+        <table class="listing" id="platformlist">
+          <thead>
+            <tr><th class="sel">&nbsp;</th>
+            <th>Name</th><th>Rules</th></tr>
+          </thead>
+          <tbody>
+            <tr py:if="not config.platforms">
+              <td colspan="3"><em>(No Platforms)</em></td>
+            </tr>
+            <tr py:for="platform in config.platforms">
+              <td class="sel">
+                <input type="checkbox" name="sel"
+                       value="$platform.id" />
+              </td>
+              <td class="name"><a href="$platform.href">
+                $platform.name
+              </a></td>
+              <td class="rules">
+                <ul py:if="len(platform.rules)">
+                  <li py:for="rule in platform.rules">
+                    <code>
+                      <strong>$rule.property</strong> ~= 
+                      $rule.pattern
+                    </code>
+                  </li>
+                </ul>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+        <div class="buttons">
+          <input type="submit" name="new"
+                 value="Add platform" />
+          <input type="submit" name="remove"
+                 value="Delete selected platforms" />
+        </div>
+      </div>
+    </form>
+
+    <form py:when="platform" class="mod" id="modplatform"
+          method="post" action="">
+      <div class="field"><label>Target Platform:
+        <input type="text" name="name"
+               value="$platform.name" />
+      </label></div>
+      <fieldset>
+        <legend>Rules</legend>
+        <table><thead><tr>
+          <th>Property name</th><th>Match pattern</th>
+        </tr></thead><tbody>
+          <tr py:for="idx, rule in enumerate(platform.rules)">
+            <td><input type="text" name="property_${idx}"
+                       value="$rule.property" /></td>
+            <td><input type="text" name="pattern_${idx}" 
+                       value="$rule.pattern" /></td>
+            <td><input type="submit"
+                       name="add_rule_${idx}" value="+" />
+                <input type="submit" name="rm_rule_${idx}"
+                       value="-" />
+            </td>
+          </tr>
+        </tbody></table>
+      </fieldset>
+      <p class="help">
+        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:
+      </p>
+      <dl class="help">
+        <dt><code>os</code>:</dt>
+        <dd>The name of the operating system (for example
+            "Darwin")</dd>
+        <dt><code>family</code>:</dt>
+        <dd>The type of operating system (for example
+            "posix" or "nt")</dd>
+        <dt><code>version</code>:</dt>
+        <dd>The operating system version (for example
+            "8.10.1)</dd>
+        <dt><code>machine</code>:</dt>
+        <dd>The hardware architecture (for example "i386"</dd>
+        <dt><code>processor</code>:</dt>
+        <dd>The CPU model (for example "i386", this may be
+            empty or the same as for <code>machine</code>
+        </dd>
+        <dt><code>name</code>:</dt>
+        <dd>The name of the slave</dd>
+        <dt><code>ipnr</code>:</dt>
+        <dd>The IP address of the slave</dd>
+      </dl>
+      <p class="help">
+        The match pattern is a regular expression.
+      </p>
+      <div class="buttons">
+        <form method="get" action=""><div>
+          <input type="hidden"
+                 name="${platform.exists and 'edit'
+                         or 'new'}" value="" />
+          <input type="hidden" name="platform"
+                 value="$platform.id" />
+          <input type="submit" name="cancel"
+                 value="Cancel" /><py:choose>
+          <input py:when="platform.exists" type="submit"
+                 name="save" value="Save" />
+          <input py:otherwise="" type="submit"
+                 name="add" value="Add" /></py:choose>
+        </div></form>
+      </div>
+    </form>
+
+    <py:otherwise><form class="addnew" id="addcomp"
+                        method="post" action=""><fieldset>
+      <legend>Add Configuration:</legend>
+      <table summary=""><tr>
+        <td class="name"><div  
+            class="field"><label>Name:<br />
+          <input type="text" name="name" size="12" />
+        </label></div></td>
+        <td class="label"><div
+            class="field"><label>Label:<br />
+          <input type="text" name="label" size="22" />
+        </label></div></td>
+      </tr><tr>
+        <td class="path" colspan="2"><div class="field">
+          <label>Path:<br />
+            <input type="text" name="path" size="32" />
+          </label>
+        </div></td>
+      </tr></table>
+      <div class="buttons">
+        <input type="submit" name="add" value="Add" />
+      </div>
+    </fieldset></form>
+
+    <form method="post" action="">
+      <table class="listing" id="configlist">
+        <thead>
+          <tr><th class="sel">&nbsp;</th><th>Name</th>
+          <th>Path</th><th>Active</th></tr>
+        </thead><tbody>
+        <tr py:if="not configs">
+          <td colspan="4"><em>(No Build Configurations)</em></td>
+        </tr>
+        <tr py:for="config in configs">
+          <td class="sel">
+            <input type="checkbox" name="sel"
+                   value="$config.name" />
+          </td>
+          <td class="name">
+            <a href="$config.href">$config.label</a>
+          </td>
+          <td class="path"><code>$config.path</code></td>
+          <td class="active">
+            <input type="checkbox" name="active"
+                   value="$config.name"
+                   checked="${config.active and 'checked'
+                              or None}" />
+          </td>
+        </tr>
+      </tbody></table>
+      <div class="buttons">
+        <input type="submit" name="remove"
+               value="Remove selected items" />
+        <input type="submit" name="apply"
+               value="Apply changes" />
+      </div>
+    </form></py:otherwise></py:choose>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_admin_master.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+  <xi:include href="admin.html" />
+  <head>
+    <title>Manage Build Master</title>
+  </head>
+  <body>
+    <h2>Manage Build Master</h2>
+
+    <form class="mod" id="bitten" method="post" action="">
+      <fieldset id="config">
+        <legend>Configuration Options</legend>
+        <div class="field">
+          <label>
+            <input type="checkbox" id="build_all" name="build_all"
+                   checked="${master.build_all and 'checked' or None}" />
+            Build all revisions
+          </label>
+        </div>
+        <p class="hint">
+          Whether to build older revisions even when a more recent
+          revision has already been built.
+        </p>
+        <div class="field">
+          <label>
+            <input type="checkbox" id="adjust_timestamps"
+                   name="adjust_timestamps"
+                   checked="${master.adjust_timestamps and 'checked'
+                              or None}" />
+            Adjust build timestamps
+          </label>
+        </div>
+        <p class="hint">
+          Whether the timestamps of builds should be adjusted to be
+          close to the timestamps of the corresponding changesets.
+        </p>
+        <hr />
+        <div class="field">
+          <label>
+            Time to wait for stabilization:
+            <input type="text" id="stabilize_wait" name="stabilize_wait"
+                   value="$master.stabilize_wait" size="5" />
+          </label>
+        </div>
+        <p class="hint">
+          The time in seconds to wait for the repository to stabilize
+          after a check-in before initiating a build.
+        </p>
+        <hr />
+        <div class="field">
+          <label>
+            Connection timeout for build slaves:
+            <input type="text" id="slave_timeout" name="slave_timeout"
+                   value="$master.slave_timeout" size="5" />
+          </label>
+        </div>
+        <p class="hint">
+          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.
+        </p>
+      </fieldset>
+
+      <div class="buttons">
+        <input type="submit" value="Apply changes"/>
+      </div>
+    </form>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_build.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+  <xi:include href="layout.html" />
+  <head>
+    <title>$title</title>
+  </head>
+  <body>
+    <div id="content" class="build">
+      <h1>$title</h1>
+      <dl id="overview" py:with="slave = build.slave">
+        <dt class="config">Configuration:</dt>
+        <dd class="config">
+          <a href="$build.config.href">$build.config.name</a>
+        </dd>
+        <dt class="trigger">Triggered by:</dt>
+        <dd class="trigger">
+          Changeset <a href="$build.chgset_href">[$build.rev]</a> by 
+          $build.chgset_author
+        </dd>
+        <dt class="slave">Built by:</dt>
+        <dd class="slave">
+          <code>$slave.name</code> ($slave.ipnr)
+        </dd>
+        <dt class="os">Operating system:</dt>
+        <dd>$slave.os_name $slave.os_version ($slave.os_family)</dd>
+        <py:if test="slave.machine"><dt class="machine">Hardware:</dt>
+        <dd class="machine">
+          $slave.machine
+          <py:if test="slave.processor"> ($slave.processor)</py:if>
+        </dd></py:if>
+        <dt class="time">
+          ${build.stopped and 'Started:' or 'Building since:'}
+        </dt>
+        <dd class="time">$build.started ($build.started_delta ago)</dd>
+        <py:if test="build.stopped"><dt class="time">Stopped:</dt>
+        <dd class="time">$build.stopped ($build.stopped_delta ago)</dd></py:if>
+        <dt class="duration">Duration:</dt>
+        <dd class="duration">$build.duration</dd>
+      </dl>
+      <div py:if="build.can_delete" class="buttons">
+        <form method="post" action=""><div>
+          <input type="hidden" name="action" value="invalidate" />
+          <input type="submit" value="Invalidate build" />
+        </div></form>
+      </div><py:for each="step in build.steps">
+      <h2 class="step" id="step_${step.name}">$step.name ($step.duration)</h2>
+      <div py:if="step.errors" class="errors">
+        <h3>Errors</h3>
+        <ul>
+          <li py:for="error in step.errors">$error</li>
+        </ul>
+      </div>
+      <p>$step.description</p>
+      <div id="${step.name}_tabs">
+        <div class="tab">
+          <h3>Log</h3>
+          <div class="log"><py:for each="item in step.log"><code class="$item.level">$item.message</code><br /></py:for></div>
+        </div>
+        <div py:for="report in [r for r in step.reports if r.template]"
+             class="tab report $report.category">
+          <xi:include href="$report.template" py:with="data = report.data" />
+        </div>
+      </div>
+      <script type="text/javascript">
+        makeTabSet(document.getElementById("${step.name}_tabs"));
+      </script></py:for>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_chart_coverage.html
@@ -0,0 +1,38 @@
+<chart xmlns:py="http://genshi.edgewall.org/">
+ <chart_type>
+  <value>area</value>
+  <value>area</value>
+ </chart_type>
+
+ <axis_category size="10" orientation="diagonal_up"
+   skip="${len(data[0]) / 6}"/>
+ <axis_ticks value_ticks="false" category_ticks="true" major_thickness="1"
+   minor_thickness="0" major_color="000000" position="outside"/>
+
+ <chart_data>
+  <row py:for="idx, row in enumerate(data)">
+   <py:choose py:for="jdx, value in enumerate(row)">
+     <string py:when="not idx or not jdx">$value</string>
+     <number py:otherwise="">$value</number>
+   </py:choose>
+  </row>
+ </chart_data>
+
+ <chart_border color="999999" left_thickness="1" bottom_thickness="1"/>
+ <chart_grid_h alpha="5" color="666666" thickness="3"/>
+ <chart_pref line_thickness="2" point_shape="none"/>
+ <chart_value position="cursor"/>
+ <series_color>
+  <color>bbbbbb</color>
+  <color>9999ff</color>
+ </series_color>
+
+ <legend_label layout="vertical" alpha="60"/>
+ <legend_rect x="60" y="50" width="10"/>
+
+ <draw>
+  <text width="320" height="40" h_align="center" v_align="bottom"
+        size="12">$title</text>
+ </draw>
+
+</chart>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_chart_tests.html
@@ -0,0 +1,38 @@
+<chart xmlns:py="http://genshi.edgewall.org/">
+ <chart_type>
+  <value>area</value>
+  <value>column</value>
+ </chart_type>
+
+ <axis_category size="10" orientation="diagonal_up"
+   skip="${len(data[0]) / 6}"/>
+ <axis_ticks value_ticks="false" category_ticks="true" major_thickness="2"
+   minor_thickness="0" major_color="000000" position="outside"/>
+
+ <chart_data>
+  <row py:for="idx, row in enumerate(data)">
+   <py:choose py:for="jdx, value in enumerate(row)">
+     <string py:when="not idx or not jdx">$value</string>
+     <number py:otherwise="">$value</number>
+   </py:choose>
+  </row>
+ </chart_data>
+
+ <chart_border color="999999" left_thickness="1" bottom_thickness="1"/>
+ <chart_grid_h alpha="5" color="666666" thickness="3"/>
+ <chart_pref line_thickness="2" point_shape="none"/>
+ <chart_value position="cursor"/>
+ <series_color>
+  <color>99dd99</color>
+  <color>ff0000</color>
+ </series_color>
+
+ <legend_label layout="vertical" alpha="60"/>
+ <legend_rect x="60" y="50" width="10"/>
+
+ <draw>
+  <text width="320" height="40" h_align="center" v_align="bottom"
+        size="12">$title</text>
+ </draw>
+
+</chart>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_config.html
@@ -0,0 +1,178 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      xmlns:py="http://genshi.edgewall.org/">
+  <xi:include href="layout.html" />
+  <head>
+    <title>$title</title>
+  </head>
+  <body>
+    <strong py:def="build_status(status)" class="status">
+      <py:choose test="status">
+      <py:when test="'completed'">Success</py:when>
+      <py:when test="'failed'">Failed</py:when>
+      <py:when test="'in progress'">In-progress</py:when>
+      </py:choose>
+    </strong>
+    
+    <div py:def="slave_info(slave)" class="system">
+      <strong>$slave.name</strong> ($slave.ipnr)<br />
+      $slave.os_name $slave.os_version
+      <py:if test="slave.machine or slave.processor"> / </py:if>
+      ${slave.processor or slave.machine or ''}
+    </div>
+    
+    <ul py:def="build_steps(steps)" py:if="steps" class="steps">
+      <li py:for="step in steps"
+          class="${step.failed and 'failed' or 'success'}">
+        <span class="duration">$step.duration</span> 
+        <a href="$step.href" title="${step.description or None}">
+          $step.name
+        </a>
+        <ul py:if="step.failed and step.errors">
+          <li py:for="error in step.errors">$error</li>
+        </ul>
+      </li>
+    </ul>
+ 
+    <div id="content" class="build">
+      <h1>$title</h1><py:choose test="page_mode"><py:when test="'overview'">
+      <form id="prefs" method="get" action="">
+        <div>
+          <input type="checkbox" id="showall" name="show" value="all"
+                 checked="${show_all and 'checked' or None}" />
+          <label for="showall">Show deactivated configurations</label>
+        </div>
+        <div class="buttons">
+          <input type="submit" value="Update" />
+        </div>
+      </form><py:for each="config in configs">
+      <h2 class="config ${not config.active and 'deactivated' or ''}">
+        <a href="$config.href">$config.label</a>
+      </h2>
+      <div py:if="config.description" class="description">
+        $config.description
+      </div><py:if test="len(config.builds)">
+      <h3 class="builds"><a href="$config.href">Latest builds</a></h3>
+      <table class="builds"><tbody><tr>
+        <th py:with="youngest_rev = config.youngest_rev">
+          <a href="$youngest_rev.href">[$youngest_rev.id]</a>
+          by $youngest_rev.author<p class="date">$youngest_rev.date</p>
+          <p class="message">$youngest_rev.message</p>
+        </th>
+        <td py:for="build in config.builds" class="$build.cls"><py:choose>
+        <py:when test="build.status != 'pending'">
+          <a href="$build.href">$build.platform</a>
+          <p class="date">$build.stopped</p>
+          ${slave_info(build.slave)}
+          ${build_status(build.status)}
+        </py:when><py:otherwise>
+          <strong>$build.platform</strong>
+          <p class="nobuild">No build yet</p>
+        </py:otherwise></py:choose>
+        </td>
+      </tr></tbody></table></py:if></py:for>
+
+    </py:when><py:when test="'view_config'">
+      <form py:if="config.can_modify" id="prefs" method="post"
+            class="activation"><py:choose>
+        <div py:when="not config.active" class="help">
+          This build configuration is currently inactive.<br />
+          No builds will be initiated for this configuration<br />
+          until it is activated.
+        </div>
+        <div py:otherwise="" class="help">
+          This configuration is currently active.
+        </div></py:choose>
+        <div class="buttons" py:choose="">
+          <input type="hidden" name="action" value="edit" />
+          <input py:when="config.active" type="submit" name="deactivate"
+                 value="Deactivate" />
+          <input py:otherwise="" type="submit" name="activate"
+                 value="Activate" />
+        </div>
+      </form>
+      <p class="path">
+        Repository path: 
+        <a py:if="config.path" href="$config.browser_href">$config.path</a>
+        ${not config.path and '&mdash;' or ''}
+        <py:if test="config.min_rev or config.max_rev">
+        (<py:if test="config.min_rev">starting at 
+         <a href="$config.min_rev_href">[$config.min_rev]</a></py:if>
+         <py:if test="config.min_rev and config.max_rev">, </py:if>
+         <py:if test="config.max_rev">up to 
+         <a href="$config.max_rev_href">[$config.max_rev]</a></py:if>)
+        </py:if>
+      </p>
+      <div py:if="config.description" class="description">
+        $config.description
+      </div>
+      <div id="charts"><py:for each="chart in config.charts">
+        <object type="application/x-shockwave-flash"
+                width="320" height="240"
+                data="${href.chrome('bitten', 'charts.swf')}">
+          <param name="movie" value="${href.chrome('bitten', 'charts.swf')}" />
+          <param name="FlashVars"
+                 value="library_path=${href.chrome('bitten')}&amp;xml_source=$chart.href${config.charts_license and '&amp;license='+config.charts_license or ''}" />
+          <param name="wmode" value="transparent" />
+        </object><br /></py:for>
+      </div>
+
+      <table py:if="config.platforms and config.builds"
+             class="listing" id="builds">
+        <thead><tr>
+          <th class="chgset"><abbr title="Changeset">Chgset</abbr></th>
+          <th py:for="platform in config.platforms">$platform.name</th>
+        </tr></thead>
+        <tbody py:if="config.builds">
+          <tr py:for="rev_num in sorted(config.builds, reverse=True)"
+              py:with="rev = config.builds[rev_num]">
+            <th class="chgset" scope="row">
+              <a href="$rev.href" title="View Changeset">[$rev_num]</a>
+            </th><py:for each="platform in config.platforms"><py:choose>
+            <td py:when="platform.id in rev" py:with="build = rev[platform.id]"
+                class="$build.cls">
+              <div class="info">
+                <a href="$build.href" title="View build results">
+                  $build.id
+                  ${build_status(build.status)}
+                </a>
+                ${slave_info(build.slave)}
+              </div>
+              ${build_steps(build.steps)}
+            </td>
+            <td py:otherwise="">&mdash;</td></py:choose></py:for>
+          </tr>
+        </tbody>
+      </table>
+      <br style="clear: right"/>
+
+    </py:when><py:when test="'view-inprogress'">
+      <py:for each="config in configs">
+      <h2 class="config ${not config.active and 'deactivated' or ''}">
+        <a href="$config.href">$config.label</a>
+      </h2>
+      <table class="listing" id="builds">
+        <thead><tr>
+          <th class="chgset" abbrev="Changeset">Chgset</th><th>Build</th>
+        </tr></thead><tbody>
+        <tr py:for="build in config.builds">
+          <th class="chgset" scope="row">
+            <a href="$build.rev_href" title="View Changeset">[$build.rev]</a>
+          </th>
+          <td class="$build.cls">
+            <div class="info">
+              <a href="$build.href" title="View build results">
+                $build.id: <strong class="status">$build.platform</strong>
+              </a>
+              ${slave_info(build.slave)}
+            </div>
+            ${build_steps(build.steps)}
+          </td>
+        </tr></tbody>
+      </table></py:for></py:when>
+    </py:choose></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_summary_coverage.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/" py:strip="">
+ <body py:strip="">
+  <h3>Code Coverage</h3>
+  <table class="listing coverage">
+   <thead><tr>
+    <th class="name">Unit</th><th class="loc">Lines of Code</th>
+    <th class="cov">Coverage</th>
+   </tr></thead>
+   <tbody><tr py:for="item in data.units">
+    <td class="name" py:choose="">
+     <a py:when="item.href" href="$item.href">$item.name</a>
+     <py:otherwise>$item.name</py:otherwise>
+    </td>
+    <td class="loc">$item.loc</td>
+    <td class="cov">${item.cov}%</td>
+   </tr></tbody>
+   <tbody class="totals"><tr py:with="totals = data.totals">
+    <th>Total</th><td>$totals.loc</td>
+    <td>${totals.cov}%</td>
+   </tr></tbody>
+  </table>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/templates/bitten_summary_tests.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://genshi.edgewall.org/" py:strip="">
+ <body py:strip="">
+  <h3>Test Results</h3>
+  <table class="listing tests">
+   <thead><tr>
+    <th>Test Fixture</th><th>Total</th>
+    <th>Failures</th><th>Errors</th>
+   </tr></thead>
+   <tbody><tr py:for="item in data.fixtures"
+              class="${item.num_failure or item.num_error and 'failed' or None}">
+    <th py:choose="">
+     <a py:when="item.href" href="$item.href">$item.name</a>
+     <py:otherwise>$item.name</py:otherwise>
+    </th>
+    <td>${item.num_success + item.num_failure + item.num_error}</td>
+    <td>$item.num_failure</td>
+    <td>$item.num_error</td>
+   </tr></tbody>
+   <tbody class="totals"><tr py:with="totals = data.totals">
+    <th>Total</th>
+    <td>$totals.success</td>
+    <td>$totals.failure</td>
+    <td>$totals.error</td>
+   </tr></tbody>
+  </table>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/__init__.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/admin.py
@@ -0,0 +1,810 @@
+# -*- 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': '<build><step /></build>'})
+
+        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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/master.py
@@ -0,0 +1,748 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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("""<slave name="hal">
+  <platform>Power Macintosh</platform>
+  <os family="posix" version="8.1.0">Darwin</os>
+  <package name="java" version="2.4.3"/>
+</slave>""")
+        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('<slave></salve>')
+        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("""<slave name="hal">
+  <platform>Power Macintosh</platform>
+  <os family="posix" version="8.1.0">Darwin</os>
+</slave>""")
+        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='<build></build>')
+        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='<build></build>')
+        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('<build build="1" config="test"'
+                             ' path="somepath" revision="123"/>',
+                             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='<build></build>').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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result step="foo" status="success"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+</result>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result step="foo" status="success"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+    <log generator="http://bitten.cmlenz.net/tools/python#unittest">
+        <message level="info">Doing stuff</message>
+        <message level="error">Ouch that hurt</message>
+    </log>
+</result>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result step="foo" status="success"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+    <report category="test"
+            generator="http://bitten.cmlenz.net/tools/python#unittest">
+        <test fixture="my.Fixture" file="my/test/file.py">
+            <stdout>Doing my thing</stdout>
+        </test>
+    </report>
+</result>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result step="foo" status="success"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+    <log generator="http://bitten.cmlenz.net/tools/python#unittest">
+        <message level="info">Doing stuff</message>
+        <message level="error">Ouch that hurt</message>
+    </log>
+</result>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+  <step id="foo2">
+  </step>
+</build>"""
+        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("""<result step="foo" status="success"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+    <log generator="http://bitten.cmlenz.net/tools/python#unittest">
+        <message level="info">Doing stuff</message>
+        <message level="error">Ouch that hurt</message>
+    </log>
+</result>""")
+        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("""<result step="foo2" status="success"
+                                     time="2007-04-01T15:45:00.0000"
+                                     duration="4">
+    <log generator="http://bitten.cmlenz.net/tools/python#unittest">
+        <message level="info">This is a step after invalidation</message>
+    </log>
+</result>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result step="foo" status="failure"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+</result>""")
+        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 = """<build>
+  <step id="foo" onerror="ignore">
+  </step>
+</build>"""
+        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("""<result step="foo" status="failure"
+                                     time="2007-04-01T15:30:00.0000"
+                                     duration="3.45">
+</result>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result></rsleut>""")
+        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 = """<build>
+  <step id="foo">
+  </step>
+</build>"""
+        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("""<result step="foo" status="success"
+                                     time="sometime tomorrow maybe"
+                                     duration="3.45">
+</result>""")
+        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='<build></build>').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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/model.py
@@ -0,0 +1,733 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/queue.py
@@ -0,0 +1,358 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/recipe.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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('<build/>')
+        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('<build>'
+                          ' <step id="foo" description="Bar"></step>'
+                          '</build>')
+        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('<foo></foo>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_no_steps(self):
+        xml = xmlio.parse('<build></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_child_not_step(self):
+        xml = xmlio.parse('<build><foo/></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_child_not_step(self):
+        xml = xmlio.parse('<build><foo/></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_step_without_id(self):
+        xml = xmlio.parse('<build><step><cmd/></step></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_step_with_empty_id(self):
+        xml = xmlio.parse('<build><step id=""><cmd/></step></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_step_without_commands(self):
+        xml = xmlio.parse('<build><step id="test"/></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_step_with_command_children(self):
+        xml = xmlio.parse('<build><step id="test">'
+                          '<somecmd><child1/><child2/></somecmd>'
+                          '</step></build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_step_with_duplicate_id(self):
+        xml = xmlio.parse('<build>'
+                          '<step id="test"><somecmd></somecmd></step>'
+                          '<step id="test"><othercmd></othercmd></step>'
+                          '</build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        self.assertRaises(InvalidRecipeError, recipe.validate)
+
+    def test_validate_successful(self):
+        xml = xmlio.parse('<build>'
+                          '<step id="foo"><somecmd></somecmd></step>'
+                          '<step id="bar"><othercmd></othercmd></step>'
+                          '</build>')
+        recipe = Recipe(xml, basedir=self.basedir)
+        recipe.validate()
+
+def suite():
+    return unittest.makeSuite(RecipeTestCase, 'test')
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='suite')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/slave.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/tests/web_ui.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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 <a href="/trac/browser/trunk/foo/bar.c">'
+                         'foo/bar.c</a>: 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 <a href="/trac/browser/trunk/foo/bar.c#L123">'
+                         'foo/bar.c:123</a>: 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/upgrades.py
@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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]
+}
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/util/__init__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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'
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/util/loc.py
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 1998 Dinu C. Gherman <gherman@europemail.com>
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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 = ''
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/util/testrunner.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# Copyright (C) 2008 Matt Good <matt@matt-good.net>
+# 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)
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/util/tests/__init__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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')
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/util/xmlio.py
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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('"', '&#34;')
+
+
+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('<![CDATA[' + child + ']]>')
+                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')
+    <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)
+    <foo bar="42"/>
+    >>> print Element('foo', bar='1 < 2')
+    <foo bar="1 &lt; 2"/>
+    >>> print Element('foo', bar='"baz"')
+    <foo bar="&#34;baz&#34;"/>
+
+    The order in which attributes are rendered is undefined.
+
+    Elements can be using item access notation:
+
+    >>> print Element('foo')[Element('bar'), Element('baz')]
+    <foo><bar/><baz/></foo>
+
+    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']
+    <foo>Hello world</foo>
+    >>> print Element('foo')[42]
+    <foo>42</foo>
+    >>> print Element('foo')['1 < 2']
+    <foo>1 &lt; 2</foo>
+
+    This technique also allows mixed content:
+
+    >>> print Element('foo')['Hello ', Element('b')['world']]
+    <foo>Hello <b>world</b></foo>
+
+    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')['<bar a="3" b="4"><baz/></bar>']
+    <foo><![CDATA[<bar a="3" b="4"><baz/></bar>]]></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('</' + self.name + '>')
+        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('<root/>')
+    >>> print xml.name
+    root
+    
+    Parsed elements can be serialized to a string using the `write()` method:
+    
+    >>> import sys
+    >>> parse('<root></root>').write(sys.stdout)
+    <root/>
+    
+    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('<root></root>')
+    <root/>
+    
+    (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('<root foo="bar"/>').attr['foo']
+    bar
+    
+    Attributes can also be updated, added or removed:
+    
+    >>> xml = parse('<root foo="bar"/>')
+    >>> xml.attr['foo'] = 'baz'
+    >>> print xml
+    <root foo="baz"/>
+
+    >>> del xml.attr['foo']
+    >>> print xml
+    <root/>
+
+    >>> xml.attr['foo'] = 'bar'
+    >>> print xml
+    <root foo="bar"/>
+
+    CDATA sections are included in the text content of the element returned by
+    `gettext()`:
+    
+    >>> xml = parse('<root>foo<![CDATA[ <bar> ]]>baz</root>')
+    >>> xml.gettext()
+    'foo <bar> 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()
new file mode 100644
--- /dev/null
+++ b/trac-0.11/bitten/web_ui.py
@@ -0,0 +1,626 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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('</ul>')
+                            buf.write('<p>Step %s failed:</p><ul>' \
+                                      % escape(step))
+                            prev_step = step
+                        buf.write('<li>%s</li>' % escape(error))
+                    buf.write('</ul>')
+                    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('<em>%s</em>') % 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<path>[\w.-]+(?:/[\w.-]+)+)(?P<line>(:\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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/doc/commands.txt
@@ -0,0 +1,890 @@
+.. -*- 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.
+
+
+------------
+``<report>``
+------------
+
+Parse an XML file and send it to the master as a report with a given category.
+Use this command in conjunction with the ``<sh:pipe>`` or ``<x:transform>``
+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``
+
+
+-------------
+``<sh:exec>``
+-------------
+
+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
+
+
+-------------
+``<sh:pipe>``
+-------------
+
+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``
+
+
+------------------
+``<c:autoreconf>``
+------------------
+
+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
+
+  <c:autoreconf force="1" install="1" warnings="cross,syntax,error"/>
+
+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
+
+
+-----------------
+``<c:configure>``
+-----------------
+
+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
+
+  <c:configure enable="threadsafe" cflags="-O"/>
+
+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"
+
+
+------------
+``<c:gcov>``
+------------
+
+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                                               |
++--------------+------------------------------------------------------------+
+
+
+------------
+``<c:make>``
+------------
+
+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
+
+  <c:make target="compile" file="build/Makefile" />
+
+Runs the target "compile" of the ``Makefile`` located in the sub-directory
+``build``.
+
+.. code-block:: xml
+
+  <c:make target="compile" file="build/Makefile" directory="work" args="coverage=1" />
+
+Same as previous but execute the command in the ``work`` directory and call
+the makefile with the command line argument ``coverage=1``.
+
+---------------
+``<c:cppunit>``
+---------------
+
+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
+
+  <sh:exec executable="run_unit_tests" output="test_results.xml" />
+  <c:cppunit file="test_results.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``
+
+
+--------------
+``<java:ant>``
+--------------
+
+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
+
+  <java:ant target="compile" />
+
+Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the
+project source directory.
+
+
+--------------------
+``<java:cobertura>``
+--------------------
+
+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
+
+  <java:cobertura file="build/cobertura.xml" />
+
+Reads the specifid XML file, extracts the coverage data, and builds a coverage
+report to be sent to the build master.
+
+
+----------------
+``<java:junit>``
+----------------
+
+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
+
+  <java:junit file="build/tests/results/TEST-*.xml" srcdir="src/tests" />
+
+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/
+
+---------------
+``<php:phing>``
+---------------
+
+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
+
+  <php:phing target="compile" />
+
+Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the
+project source directory.
+
+
+-----------------
+``<php:phpunit>``
+-----------------
+
+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
+
+  <php:phpunit file="build/test-results.xml"/>
+
+Extracts the test results from the XML file located at
+``build/test-results.xml``.
+
+
+------------------
+``<php:coverage>``
+------------------
+
+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
+
+  <php:coverage file="build/coverage.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/
+
+
+-----------------
+``<python:exec>``
+-----------------
+
+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
+
+  <python:exec module="pylint.lint" output="pylint-report.txt" args="myproj" />
+
+Executes Pylint_ on the module/package ``myproj`` and stores the output into a
+file named ``pylint-report.txt``.
+
+
+----------------------
+``<python:distutils>``
+----------------------
+
+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
+
+  <python:distutils command="sdist" />
+
+Instructs `distutils` to produce a source distribution.
+
+.. code-block:: xml
+
+  <python:distutils command="unittest" options="
+      --xml-output build/test-results.xml
+      --coverage-summary build/test-coverage.txt
+      --coverage-dir build/coverage"/>
+
+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.
+
+
+---------------------
+``<python:unittest>``
+---------------------
+
+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
+
+  <python:unittest file="build/test-results.xml"/>
+
+Extracts the test results from the XML file located at
+``build/test-results.xml``.
+
+
+------------------
+``<python:trace>``
+------------------
+
+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
+
+  <python:trace summary="build/trace.out" coverdir="build/coverage" />
+
+-------------------
+``<python:pylint>``
+-------------------
+
+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
+
+  <python:pylint file="build/pylint.out" />
+
+
+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``
+
+
+------------------
+``<svn:checkout>``
+------------------
+
+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
+
+  <svn:checkout url="http://svn.example.org/repos/myproject/"
+      path="${path}" revision="${revision}"/>
+
+This checks out the a working copy into the current directory.
+
+
+----------------
+``<svn:export>``
+----------------
+
+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
+
+  <svn:export url="http://svn.example.org/repos/myproject/"
+      path="${path}" revision="${revision}"/>
+
+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.
+
+
+----------------
+``<svn:update>``
+----------------
+
+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
+
+  <svn:update revision="${revision}"/>
+
+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``
+
+
+-----------------
+``<x:transform>``
+-----------------
+
+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
+
+  <x:transform src="src.xml" dest="dest.xml" stylesheet="util/convert.xsl" />
+
+This applies the stylesheet in ``util/convert.xsl`` to the source file
+``src.xml``, and writes the resulting XML document to ``dest.xml``.
new file mode 100644
--- /dev/null
+++ b/trac-0.11/doc/index.txt
@@ -0,0 +1,25 @@
+.. -*- 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 <install.html>`_
+ * `Build Recipes <recipes.html>`_
+ * `Build Recipe Commands <commands.html>`_
+ * `Generated API Documentation <api/index.html>`_
new file mode 100644
--- /dev/null
+++ b/trac-0.11/doc/install.txt
@@ -0,0 +1,121 @@
+.. -*- 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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..665402ec268b69d884c17379346cf580f2e6355c
GIT binary patch
literal 41793
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bb4daf76efb72105cca5ea1e1b97fe70dbde9ad9
GIT binary patch
literal 20260
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..aba3415df5c2aebadd03ccf631cd9549f721648a
GIT binary patch
literal 5889
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
new file mode 100644
--- /dev/null
+++ b/trac-0.11/doc/recipes.txt
@@ -0,0 +1,68 @@
+.. -*- mode: rst; encoding: utf-8 -*-
+
+=============
+Build Recipes
+=============
+
+A build recipe tells a build slave how a project is to be built. It consists of
+multiple build steps, each defining a command to execute, and where artifacts
+can be found after that command has successfully completed.
+
+Build recipes are intended to supplement existing project build files (such as
+Makefiles), not to replace them. In general, a recipe will be much simpler than
+the build file itself, because it doesn't deal with all the details of the
+build. It just automates the execution of the build and lets the build slave
+locate any artifacts and metrics data generated in the course of the build.
+
+A recipe can and should split the build into multiple separate steps so that the
+build slave can provide better status reporting to the build master while the
+build is still in progress. This is important for builds that might take long to
+execute. In addition, build steps help organize the build results for a more
+structured presentation.
+
+.. contents:: Contents
+   :depth: 2
+.. sectnum::
+
+
+File Format
+===========
+
+Build recipes are stored internally in an XML-based format. Recipe documents
+have a single ``<build>`` root element with one or more ``<step>`` child
+elements. The steps are executed in the order they appear in the recipe.
+
+A ``<step>`` 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
+
+  <build xmlns:python="http://bitten.cmlenz.net/tools/python"
+         xmlns:svn="http://bitten.cmlenz.net/tools/svn">
+  
+    <step id="checkout" description="Checkout source from repository">
+      <svn:checkout url="http://svn.example.org/repos/foo"
+          path="${path}" revision="${revision}" />
+    </step>
+  
+    <step id="build" description="Compile to byte code">
+      <python:distutils command="build"/>
+    </step>
+  
+    <step id="test" description="Run unit tests">
+      <python:distutils command="unittest"/>
+      <python:unittest file="build/test-results.xml"/>
+      <python:trace summary="build/test-coverage.txt" 
+          coverdir="build/coverage" include="trac*" exclude="*.tests.*"/>
+    </step>
+  
+  </build>
+
+See `Build Recipe Commands`_ for a comprehensive reference of the commands
+available by default.
+
+.. _`build recipe commands`: commands.html
new file mode 100644
--- /dev/null
+++ b/trac-0.11/scripts/proxy.py
@@ -0,0 +1,93 @@
+# 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:', '<wait for incoming connections>'
+
+    def handle_connect(self):
+        print 'L:', '<open connection>'
+
+    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:', '<open connection>'
+
+    def handle_close(self):
+         print 'I:', '<close connection>'
+         self.sender.close()
+         self.close()
+
+
+if __name__ == '__main__':
+    if len(sys.argv) < 3:
+        print 'Usage: %s <server-host> <server-port>' % sys.argv[0]
+    else:
+        ps = proxy_server(sys.argv[1], int(sys.argv[2]))
+        asyncore.loop()
new file mode 100644
--- /dev/null
+++ b/trac-0.11/setup.cfg
@@ -0,0 +1,8 @@
+[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
new file mode 100755
--- /dev/null
+++ b/trac-0.11/setup.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Edgewall Software
+# Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de>
+# 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}
+)
Copyright (C) 2012-2017 Edgewall Software