Bitten-0.6/000755 000765 000120 00000000000 11536424600 013057 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/000755 000765 000120 00000000000 11536424600 014344 5ustar00simonadmin000000 000000 Bitten-0.6/Bitten.egg-info/000755 000765 000120 00000000000 11536424600 015776 5ustar00simonadmin000000 000000 Bitten-0.6/ChangeLog000644 000765 000120 00000020134 11536403656 014641 0ustar00simonadmin000000 000000 Version 0.6 (11 March 2011, from 0.6.x branch) http://svn.edgewall.org/repos/bitten/tags/0.6 * Compatibility fix for Python 2.6 HTTPBasicAuthHandler issues. * When checking to delete pending build, handle case where config may not exist. * Set 'Content-Length' header on keep-alive requests. * Make sure slave reads attachments as binary files. * Fixed handling of configurations that point to deleted branches. * Fix hg:pull command for Trac 0.12. Version 0.6b3 (21 October 2010, from 0.6.x branch) http://svn.edgewall.org/repos/bitten/tags/0.6b3 * Basic support for Trac 0.12, supporting just a '(default)' repository. * For Trac 0.12+ and DVCS repository connectors, shortened revision numbers will be displayed. * Attachments via `` command is completely redone to eliminate serious performance issues. * Removed sorting of revisions received from Trac as they are already sorted chronologically. Use `rev_time` if any other sorting is needed. * Moved Report Format from wiki to distributable documentation. * Use Genshi `NewTextTemplate` for uniform notification template syntax. * Fixed issue in use of drop index during database upgrade on MySQL. * Added upgrade script to fix sequences on PostgreSQL tables. * Fixed missing field in group by clause that caused lint report submission to fail. * Coverage context menu links correctly, and Coverage from browsing will locate the most recent annotation between currently browsed revision and previous change for file. * Set 'Content-Length' header on master-slave communication. * Fix for an issue where a renamed/deleted config would crash timeline if a build with this config was in range. * Shell output encoding improvements. * Redirect after login will now redirect to same /builds url. * Improved command-line calls on Windows, using shell for built-in commands and scripts (like `java:ant`). * Improvements to the `hg:pull` command. * Support linking to individual steps on builds. * Safer parsing of `java:junit` xml in case an optional attribute is missing. * Delete attachments properly in some corner cases where incomplete builds are cancelled or invalidated. * Improved error messages for failed Python recipe commands specified via a module or function. * Added timeout limiting for shell and python recipe commands. * `onerror` attribute can now be specified on `` elements as well as `` elements. * Charts now only show if there's applicable reports, and restrict themselves to the versions of the active configuration. * Times shown in the ui are now consistently times from the master, and not inter-mixed times between the server and slaves, which caused skew issues. * Steps are now shown while in-progress, and builds are considered aborted based on the time since the last interaction with the serve, not since starting. Slaves now send keepalive messages to the server to avoid timing out during long build steps. * Fix reference to database field in pylint report generation to solve issue running query on PostGreSQL. * Report charting re-implemented using Flot. * Svn commands gained username, password and no_auth_cache options. Verbose flag now behaves less cryptically. * No longer create 'snapshots' directory in environments of new projects (`initenv`). * Added page on upgrades to distributed documentation. * New utility script for removing duplicate builds encountered when upgrading. * Add 'Platform' to Build web display and notifications. * Other minor fixes. Version 0.6b2 (12 September 2010, from 0.6.x branch) http://svn.edgewall.org/repos/bitten/tags/0.6b2 * Slave-only install changed to `./setup.py --without-master install` (or any other valid distribution command). * Tools namespace changed to `http://bitten.edgewall.org/tools/`. Both new and old namespace will work, but Admin will issue a deprecation notice when using the old namespace. Version 0.6b1 (10 September 2009, from 0.6.x branch) http://svn.edgewall.org/repos/bitten/tags/0.6b1 * Python 2.4 is now required for slave, while master should still work using Python 2.3 (as is also Trac 0.11 minimum). * 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, and Bitten now requires Trac 0.11. * The administration interface is now properly integrated with the Trac web administration component. * Unicode (non-ascii) support for recipes and command-line input and output. * Improved authentication support in bitten-slave, including support for the popular AccountManager plugin. * New command-line execute() using Python subprocess module that in particular improves the situation for slaves executing commands on Windows. * Build logs are moved from database and into project log/bitten directory as text files. * Slave-only install now possible by running 'python setup-slave.py install'. * Fixes related to creating and deleting builds correctly, including database upgrade to avoid thread race conditions. * Some improvements to the Admin user interface for creating/editing configurations and platforms. * Attachment support for configurations and builds, including a new general command to use in recipes. * command added. * Many fixes and improvements for pre-existing commands. * Improvements for coverage, lint and test summary display. Version 0.5.3 (18 April 2006, from 0.5.x branch) http://svn.edgewall.org/repos/bitten/tags/0.5.3 * Fix double-escaping of report summaries. * Fix build master error when build log contains no messages. Version 0.5.2 (17 January 2006, from 0.5.x branch) http://svn.edgewall.org/repos/bitten/tags/0.5.2 * Fixes the main navigation tab that was broken in 0.5.1. Version 0.5.1 (10 January 2006, from 0.5.x branch) http://svn.edgewall.org/repos/bitten/tags/0.5.1 * Fixes compatibility with Trac 0.9.3 release, as well as the current trunk. This also means that Bitten now longer works with versions of Trac earlier than 0.9.3. * Improves PostgreSQL compatibility. * Fixes encoding of non-ASCII characters in command output. * Fix for missing log output when using on Windows. Version 0.5 (6 October 2005, from 0.5.x branch) http://svn.edgewall.org/repos/bitten/tags/0.5 * BDB XML is no longer being used for report storage. Instead, collected metrics data is stored in the Trac database. * Snapshot archives created by the master are checked for integrity prior to their transmission to the slaves. * Improvements to the build status presentation in Trac. * Changes to the build recipe format. See the documentation on the web site for details. * New recipe commands: , , , , , and . Various improvements to the existing commands. * Recipe commands and command attributes in recipes can now reference slave configuration values. * The names of the master and slaves scripts have changed: `bittend` is now `bitten-master`, `bitten` is now `bitten-slave`. * The build master can now handle multiple Trac environments. * The build slave now by default removes any working directories when done. * Build configurations can now be completely deleted. * Build configurations can now have a minimum and maximum revision specified. Any revisions outside that range will not be built. * The build configuration editor now validates the supplied values. * Fix management of target platforms when running under mod_python. * Improved performance of the build log formatter that is responsible for linking file references in build logs to the repository browser. * Add paging to the build configuration view. * Fix compatibility with PySQLite2. Bitten-0.6/COPYING000644 000765 000120 00000003173 11453043212 014110 0ustar00simonadmin000000 000000 Copyright (C) 2007-2010 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 copies of external libraries: - jquery.flot.js (MIT license) - excanvas.js (Apache License, Version 2.0) Bitten-0.6/doc/000755 000765 000120 00000000000 11536424600 013624 5ustar00simonadmin000000 000000 Bitten-0.6/MANIFEST-SLAVE.in000644 000765 000120 00000000763 11310206140 015456 0ustar00simonadmin000000 000000 prune bitten/htdocs prune bitten/templates prune doc exclude MANIFEST.in include setup.py include ChangeLog include COPYING include MANIFEST-SLAVE.in include README.txt exclude bitten/*.py exclude bitten/report/*.py exclude bitten/report/tests/*.py exclude bitten/tests/*.py include bitten/__init__.py include bitten/build/*.py include bitten/build/tests/*.py include bitten/recipe.py include bitten/slave.py include bitten/tests_slave/*.py include bitten/util/*.py include bitten/util/tests/*.py Bitten-0.6/MANIFEST.in000644 000765 000120 00000000513 11375466351 014626 0ustar00simonadmin000000 000000 include ChangeLog include COPYING include MANIFEST-SLAVE.in include README.txt include bitten/htdocs/*.* include bitten/templates/*.* include doc/*.* include doc/api/*.* include doc/common/*.html include doc/common/*.py include doc/common/*.txt include doc/common/COPYING include doc/common/conf/*.ini include doc/common/style/*.* Bitten-0.6/PKG-INFO000644 000765 000120 00000000602 11536424600 014152 0ustar00simonadmin000000 000000 Metadata-Version: 1.0 Name: Bitten Version: 0.6 Summary: Continuous integration for Trac Home-page: http://bitten.edgewall.org/ Author: Edgewall Software Author-email: info@edgewall.org License: BSD Download-URL: http://bitten.edgewall.org/wiki/Download Description: A slave for running builds and submitting them to Bitten, the continuous integration system for Trac Platform: UNKNOWN Bitten-0.6/README.txt000644 000765 000120 00000011404 11252540751 014556 0ustar00simonadmin000000 000000 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 two separate parts: * The build slave, which executes builds on behalf of a build master. * 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. The build master is a plugin for Trac 0.11, while the build slave can be installed without other dependencies than Python >= 2.4 and setuptools >= 0.6a2 in addition to 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.4 or later for running build slave, while the Trac plugin (master) should still work using Python 2.3. 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 It is also possible to install only the build slave, installing only the parts of Bitten that are needed by clients to run builds: $ python setup.py --without-master install 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.11 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 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: Bitten-0.6/setup.cfg000644 000765 000120 00000000264 11536424600 014702 0ustar00simonadmin000000 000000 [unittest] xml_output = build/test-results.xml coverage_summary = build/test-coverage.txt coverage_dir = build/coverage [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 Bitten-0.6/setup.py000755 000765 000120 00000014043 11536421561 014601 0ustar00simonadmin000000 000000 #!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. import os import sys from setuptools import setup, find_packages, Feature from setuptools.command import egg_info sys.path.append(os.path.join('doc', 'common')) try: from doctools import build_doc, test_doc except ImportError: build_doc = test_doc = None # Turn off multiprocessing logging # Bug in setuptools/distutils test runner using Python 2.6.2+? import logging if hasattr(logging, 'logMultiprocessing'): logging.logMultiprocessing = 0 NS_old = 'http://bitten.cmlenz.net/tools/' NS_new = 'http://bitten.edgewall.org/tools/' tools = [ 'sh#exec = bitten.build.shtools:exec_', 'sh#pipe = bitten.build.shtools:pipe', 'c#configure = bitten.build.ctools:configure', 'c#autoreconf = bitten.build.ctools:autoreconf', 'c#cppunit = bitten.build.ctools:cppunit', 'c#cunit = bitten.build.ctools:cunit', 'c#gcov = bitten.build.ctools:gcov', 'c#make = bitten.build.ctools:make', 'mono#nunit = bitten.build.monotools:nunit', 'java#ant = bitten.build.javatools:ant', 'java#junit = bitten.build.javatools:junit', 'java#cobertura = bitten.build.javatools:cobertura', 'php#phing = bitten.build.phptools:phing', 'php#phpunit = bitten.build.phptools:phpunit', 'php#coverage = bitten.build.phptools:coverage', 'python#coverage = bitten.build.pythontools:coverage', 'python#distutils = bitten.build.pythontools:distutils', 'python#exec = bitten.build.pythontools:exec_', 'python#figleaf = bitten.build.pythontools:figleaf', 'python#pylint = bitten.build.pythontools:pylint', 'python#trace = bitten.build.pythontools:trace', 'python#unittest = bitten.build.pythontools:unittest', 'svn#checkout = bitten.build.svntools:checkout', 'svn#export = bitten.build.svntools:export', 'svn#update = bitten.build.svntools:update', 'hg#pull = bitten.build.hgtools:pull', 'xml#transform = bitten.build.xmltools:transform' ] recipe_commands = [NS_old + tool for tool in tools] \ + [NS_new + tool for tool in tools] class MasterFeature(Feature): def exclude_from(self, dist): # Called when master is disabled (--without-master) pass def include_in(self, dist): # Called when master is enabled (default, or --with-master) dist.metadata.name = 'Bitten' dist.metadata.description = 'Continuous integration for Trac' dist.long_description = "A Trac plugin for collecting software " \ "metrics via continuous integration.""" # Use full manifest when master is included egg_info.manifest_maker.template = "MANIFEST.in" # Include tests in source distribution if 'sdist' in dist.commands: dist.packages = find_packages() else: dist.packages = find_packages(exclude=['*tests*']) dist.test_suite = 'bitten.tests.suite' dist.package_data = { 'bitten': ['htdocs/*.*', 'templates/*.html', 'templates/*.txt']} dist.entry_points['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.lint = bitten.report.lint', 'bitten.notify = bitten.notify'] master = MasterFeature( description = "Bitten Master Trac plugin", standard = True, py_modules = []) egg_info.manifest_maker.template = "MANIFEST-SLAVE.in" if os.path.exists(os.path.join(os.path.dirname(__file__), 'MANIFEST.in')): available_features = {"master": master} else: # Building from a slave distribution available_features = {} setup( name = 'BittenSlave', version = '0.6', 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, description = 'Continuous integration build slave for Trac', long_description = "A slave for running builds and submitting them to " \ "Bitten, the continuous integration system for Trac", packages = {}, py_modules = ["bitten.__init__", "bitten.build.__init__", "bitten.build.api", "bitten.build.config", "bitten.build.ctools", "bitten.build.hgtools", "bitten.build.javatools", "bitten.build.monotools", "bitten.build.phptools", "bitten.build.pythontools", "bitten.build.shtools", "bitten.build.svntools", "bitten.build.xmltools", "bitten.recipe", "bitten.slave", "bitten.util.__init__", "bitten.util.compat", "bitten.util.loc", "bitten.util.testrunner", "bitten.util.xmlio", ], test_suite = 'bitten.tests_slave.suite', tests_require = [ 'figleaf', ], entry_points = { 'console_scripts': [ 'bitten-slave = bitten.slave:main' ], 'distutils.commands': [ 'unittest = bitten.util.testrunner:unittest' ], 'bitten.recipe_commands': recipe_commands }, features = available_features, cmdclass = {'build_doc': build_doc, 'test_doc': test_doc} ) Bitten-0.6/doc/api/000755 000765 000120 00000000000 11536424600 014375 5ustar00simonadmin000000 000000 Bitten-0.6/doc/commands.html000644 000765 000120 00000176715 11536424526 016343 0ustar00simonadmin000000 000000 Bitten: Build Recipe Commands

Build Recipe Commands

Build recipes are represented by XML documents. This page describes what commands are generally available in recipes, and any runtime configuration supported by these commands. Please note, though, that third-party packages can add additional commands, which would then be documented by that third party.

1   Generic Commands

These are commands that are used without a namespace prefix.

1.1   <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.

1.1.1   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.

See also Report Formats.

1.2   <attach>

Attach a file to the build or configuration as regular attachment. An already existing attachment on the same resource with same base filename will be replaced.

Note: Unless consistently building latest only, overwriting files on config level may lead to unexpected results.

1.2.1   Parameters

Name Description
file Path to the file to attach, relative to the project directory.
resource The resource to attach the file to. Either 'build' (default) or 'config'. Optional.
description Attachment description. Optional.

2   Shell Tools

A bundle of generic tools that are not specific to any programming language or tool-chain.

Namespace:http://bitten.edgewall.org/tools/sh
Common prefix:sh

2.1   <sh:exec>

Executes a program or script.

2.1.1   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
dir Directory to change to before executing the command
timeout Limits the runtime of this command to the specified number of seconds, after which it will be terminated.

Either executable or file must be specified.

2.1.2   Examples

TODO

2.2   <sh:pipe>

Pipes the content of a file through a program or script.

2.2.1   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
dir Directory to change to before executing the command

Either executable or file must be specified.

2.2.2   Examples

TODO

3   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.edgewall.org/tools/c
Common prefix:c

3.1   <c:autoreconf>

Executes ths autotool autoreconf.

3.1.1   Parameters

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

3.1.2   Examples

<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

3.2   <c:configure>

Executes a configure script as generated by Autoconf.

3.2.1   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.

3.2.2   Examples

<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"

3.2.3   Configuration

Parameter with will expand any package found in slave configuration:

[mylib]
path = /path/to/mylib

3.3   <c:gcov>

Run gcov to extract coverage data where available.

3.3.1   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

3.4   <c:make>

Executes a Makefile.

3.4.1   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".

3.4.2   Examples

<c:make target="compile" file="build/Makefile" />

Runs the target "compile" of the Makefile located in the sub-directory build.

<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.

3.4.3   Configuration

[make]
path = /path/to/(c|n)make

3.5   <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.

3.5.1   Parameters

Name Description
file Path to the cppunit XML output file.

3.5.2   Examples

<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.

4   Java Tools

A bundle of recipe commands that support tools commonly used by Java projects.

Namespace:http://bitten.edgewall.org/tools/java
Common prefix:java

4.1   <java:ant>

Runs an Ant build.

4.1.1   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.

4.1.2   Examples

<java:ant target="compile" />

Executes the target compile of the build.xml buildfile at the top of the project source directory.

4.1.3   Configuration

[ant]
home = /path/to/ant/dir

[java]
home = /path/to/java/dir

4.2   <java:cobertura>

Extract code coverage data from a Cobertura XML file.

4.2.1   Parameters

Name Description
file Path to the XML file generated by Cobertura

4.2.2   Examples

<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.

4.3   <java:junit>

Extracts information about unit test results from a file in JUnit XML format.

4.3.1   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.

4.3.2   Examples

<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.

5   Mono Tools

A bundle of recipe commands that support tools commonly used by Mono/.NET projects.

Namespace:http://bitten.edgewall.org/tools/mono
Common prefix:mono

5.1   <mono:nunit>

Extracts information about unit test results from a files in NUnit XML format.

5.1.1   Parameters

Name Description
file Path to the NUnit XML test results file. This can include wildcards, in which case all the file matching the pattern will be included.

The file attribute is required.

5.1.2   Examples

<mono:nunit file="build/tests/TestResult.xml" />

6   PHP Tools

A bundle of recipe commands for PHP projects.

Namespace:http://bitten.edgewall.org/tools/php
Common prefix:php

6.1   <php:phing>

Runs a Phing build.

6.1.1   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).

6.1.2   Examples

<php:phing target="compile" />

Executes the target compile of the build.xml buildfile at the top of the project source directory.

6.2   <php:phpunit>

Extracts information from PHPUnit test results recorded in an XML file.

6.2.1   Parameters

Name Description
file Path to the XML results file, relative to the project source directory.

6.2.2   Examples

<php:phpunit file="build/test-results.xml"/>

Extracts the test results from the XML file located at build/test-results.xml.

6.3   <php:coverage>

Extracts coverage information from Phing's code coverage task XML file or from PHPUnit coverage-clover XML file.

6.3.1   Parameters

Name Description
file Path to the XML coverage file, relative to the project source directory.

6.3.2   Examples

<php:coverage file="build/coverage.xml" />

7   Python Tools

A bundle of recipe commands that support tools commonly used by Python projects.

Namespace:http://bitten.edgewall.org/tools/python
Common prefix:python

7.1   <python:exec>

Executes a Python script.

7.1.1   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.
timeout Limits the runtime of this command to the specified number of seconds, after which it will be terminated.

Either file or module must be specified.

7.1.2   Examples

<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.

7.1.3   Configuration

[python]
path = /path/to/python

7.2   <python:distutils>

Executes a distutils script.

7.2.1   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
timeout Limits the runtime of this command to the specified number of seconds, after which it will be terminated.

7.2.2   Examples

<python:distutils command="sdist" />

Instructs distutils to produce a source distribution.

<python:distutils command="unittest" options="
    --xml-output build/test-results.xml
    --coverage-summary build/test-coverage.txt
    --coverage-dir build/coverage
    --coverage-method trace"/>

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.

Option --coverage-method is one of trace, coverage or figleaf.

7.2.3   Configuration

[python]
path = /path/to/python

7.3   <python:unittest>

Extracts information from unittest results recorded in an XML file.

Note: This report must be used in conjunction with the distutils command "unittest" that comes with Bitten.

7.3.1   Parameters

Name Description
file Path to the XML results file, relative to the project source directory.

7.3.2   Examples

<python:unittest file="build/test-results.xml"/>

Extracts the test results from the XML file located at build/test-results.xml.

7.4   <python:trace>

Extracts coverage information recorded by the built-in Python module trace.py.

7.4.1   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

7.4.2   Examples

<python:trace summary="build/trace.out" coverdir="build/coverage" />

7.5   <python:coverage>

Extract data from a coverage.py run.

7.5.1   Parameters

Name Description
summary Path to the summary file with coverage.py information, relative to the project source directory.
coverdir Path to the directory containing per-module coverage details, 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

7.5.2   Examples

<step id="test" description="Unittests with coverage.py information">
  <python:distutils command="unittest"
      options="--xml-output build/test-results.xml
               --coverage-summary build/test-coverage.txt
               --coverage-dir build/coverage
               --coverage-method coverage" />
  <python:unittest file="build/test-results.xml"/>
  <python:coverage summary="build/test-coverage.txt"
      coverdir="build/coverage"
      include="mypackage/*" exclude="*/tests/*" />
</step>

7.6   <python:figleaf>

Extracts coverage information recorded by Figleaf.

7.6.1   Parameters

Name Description
summary Path to the summary file written by figleaf, 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

7.6.2   Examples

<step id="test" description="Unittests with Figleaf coverage">
  <python:distutils command="unittest"
      options="--xml-output build/test-results.xml
               --coverage-summary build/test-coverage.txt
               --coverage-dir build/coverage
               --coverage-method figleaf" />
  <python:unittest file="build/test-results.xml"/>
  <python:figleaf summary="build/test-coverage.txt"
      include="mypackage/*" exclude="*/tests/*" />
</step>

7.7   <python:pylint>

Extracts information from Pylint reports.

7.7.1   Parameters

Name Description
file Path to the file containing the Pylint output, relative to the project source directory.

7.7.2   Examples

<python:pylint file="build/pylint.out" />

8   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.

Namespace:http://bitten.edgewall.org/tools/svn
Common prefix:svn

8.1   <svn:checkout>

Check out a working copy from a Subversion repository.

8.1.1   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).
shared_path An optional shared directory to check the sources out in, which will be reused for each subsequent build. This is relative to the project directory, so for standard usage set it to something like ../trunk
username Username to pass for authentication (optional)
password Password to pass for authentication (optional)

8.1.2   Examples

<svn:checkout url="http://svn.example.org/repos/myproject/"
    path="${path}" revision="${revision}"/>

This checks out the a working copy into the current directory.

8.2   <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.)

8.2.1   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 '.')
username Username to pass for authentication (optional)
password Password to pass for authentication (optional)

8.2.2   Examples

<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.

8.3   <svn:update>

Update an existing working copy from a Subversion repository to a specific revision.

8.3.1   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 '.')

8.3.2   Examples

<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.

9   XML Tools

A collection of recipe commands for XML processing.

Namespace:http://bitten.edgewall.org/tools/xml
Common prefix:x

9.1   <x:transform>

Apply an XSLT stylesheet .

Note: This command requires either libxslt (with Python bindings) or, on Windows platforms, MSXML (version 3 or later) to be installed on the slave machine.

9.1.1   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.

9.1.2   Examples

<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.

10   Mercurial Tools

A collection of recipe commands for working with Mercurial (hg) repositories.

Namespace:http://bitten.edgewall.org/tools/hg
Common prefix:hg

10.1   <hg:pull>

Pull changesets and updates a local Mercurial repository.

As the command depends on a pre-existing repository, bitten-slave must be started with --build-dir= option for locating and working with the repository. Also, seeing no remote location can be specified the repository .hg/hgrc configuration file needs a [paths] default = .... location defined.

10.1.1   Parameters

Name Description
revision The revision to update to (optional, defaults to tip).
dir Local subdirectory with repository (optional, defaults to '.').

Paths are interpreted relative to the project source directory.

10.1.2   Examples

<hg:pull revision="${revision}" dir="src" />

This updates the repository in src to the revision of the current build.

Bitten-0.6/doc/commands.txt000644 000765 000120 00000123613 11446640647 016207 0ustar00simonadmin000000 000000 .. -*- 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, and any `runtime configuration`_ supported by these commands. Please note, though, that third-party packages can add additional commands, which would then be documented by that third party. .. _`build recipes`: recipes.html .. _`runtime configuration`: configure.html .. contents:: Contents :depth: 2 .. sectnum:: Generic Commands ================ These are commands that are used without a namespace prefix. ------------ ```` ------------ Parse an XML file and send it to the master as a report with a given category. Use this command in conjunction with the ```` or ```` commands to send custom reports to the build master. Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``category`` | Category of the report (for example "test" or "coverage"). | +--------------+-------------------------------------------------------------+ | ``file`` | Path to the XML file containing the report data, relative | | | to the project directory. | +--------------+-------------------------------------------------------------+ Both parameters must be specified. See also `Report Formats `_. ------------ ```` ------------ Attach a file to the build or configuration as regular attachment. An already existing attachment on the same resource with same base filename will be replaced. **Note:** Unless consistently building latest only, overwriting files on config level may lead to unexpected results. Parameters ---------- +-----------------+----------------------------------------------------------+ | Name | Description | +=================+==========================================================+ | ``file`` | Path to the file to attach, relative to the project | | | directory. | +-----------------+----------------------------------------------------------+ | ``resource`` | The resource to attach the file to. Either | | | 'build' (default) or 'config'. Optional. | +-----------------+----------------------------------------------------------+ | ``description`` | Attachment description. Optional. | +-----------------+----------------------------------------------------------+ Shell Tools =========== A bundle of generic tools that are not specific to any programming language or tool-chain. :Namespace: ``http://bitten.edgewall.org/tools/sh`` :Common prefix: ``sh`` ------------- ```` ------------- Executes a program or script. Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``executable`` | The name of the executable program. | +----------------+-----------------------------------------------------------+ | ``file`` | Path to the script to execute, relative to the project | | | directory | +----------------+-----------------------------------------------------------+ | ``output`` | Path to the output file | +----------------+-----------------------------------------------------------+ | ``args`` | Any arguments to pass to the executable or script | +----------------+-----------------------------------------------------------+ | ``dir`` | Directory to change to before executing the command | +----------------+-----------------------------------------------------------+ | ``timeout`` | Limits the runtime of this command to the specified | | | number of seconds, after which it will be terminated. | +----------------+-----------------------------------------------------------+ Either ``executable`` or ``file`` must be specified. Examples -------- TODO ------------- ```` ------------- Pipes the content of a file through a program or script. Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``executable`` | The name of the executable program. | +----------------+-----------------------------------------------------------+ | ``file`` | Path to the script to execute, relative to the project | | | directory | +----------------+-----------------------------------------------------------+ | ``input`` | Path to the input file | +----------------+-----------------------------------------------------------+ | ``output`` | Path to the output file | +----------------+-----------------------------------------------------------+ | ``args`` | Any arguments to pass to the executable or script | +----------------+-----------------------------------------------------------+ | ``dir`` | Directory to change to before executing the command | +----------------+-----------------------------------------------------------+ 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.edgewall.org/tools/c`` :Common prefix: ``c`` ------------------ ```` ------------------ Executes ths autotool autoreconf. Parameters ---------- +----------------------+-----------------------------------------------------+ | Name | Description | +======================+=====================================================+ | ``force`` | Consider all files obsolete | +----------------------+-----------------------------------------------------+ | ``install`` | Copy missing auxiliary files | +----------------------+-----------------------------------------------------+ | ``symlink`` | Install symbolic links instead of copies | +----------------------+-----------------------------------------------------+ | ``warnings`` | Report the warnings related to category | | | (which can actually be a comma separated list) | +----------------------+-----------------------------------------------------+ | ``prepend_include`` | Prepend directories to search path | +----------------------+-----------------------------------------------------+ | ``include`` | Append directories to search path | +----------------------+-----------------------------------------------------+ Examples -------- .. code-block:: xml Runs the ``autoreconf`` tool in the base directory with the option: force, install and 3 warning categories active: cross,syntax,error. This is equivalent to:: autoreconf --force --install --warnings=cross,syntax,error ----------------- ```` ----------------- Executes a configure script as generated by Autoconf. Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``file`` | Name of the configure script (defaults to "configure") | +--------------+-------------------------------------------------------------+ | ``enable`` | List of features to enable, separated by spaces. | +--------------+-------------------------------------------------------------+ | ``disable`` | List of features to disable, separated by spaces. | +--------------+-------------------------------------------------------------+ | ``with`` | List of packages to include, separated by spaces. | +--------------+-------------------------------------------------------------+ | ``without`` | List of packages to exclude, separated by spaces. | +--------------+-------------------------------------------------------------+ | ``cflags`` | Value of the `CFLAGS` variable to pass to the script. | +--------------+-------------------------------------------------------------+ | ``cxxflags`` | Value of the `CXXFLAGS` variable to pass to the script. | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml Runs the ``configure`` script in the base directory, enable the ``threadsafe`` feature, and passing ``-O`` as ``CFLAGS``. This is equivalent to:: ./configure --enable-threadsafe CFLAGS="-O" Configuration ------------- Parameter ``with`` will expand any package found in slave configuration: .. code-block:: ini [mylib] path = /path/to/mylib ------------ ```` ------------ Run gcov_ to extract coverage data where available. .. _gcov: http://gcc.gnu.org/onlinedocs/gcc/Gcov-Intro.html Parameters ---------- +--------------+------------------------------------------------------------+ | Name | Description | +==============+============================================================+ | ``include`` | List of glob patterns (separated by space) that specify | | | which source files should be included in the coverage | | | report | +--------------+------------------------------------------------------------+ | ``exclude`` | List of glob patterns (separated by space) that specify | | | which source files should be excluded from the coverage | | | report | +--------------+------------------------------------------------------------+ | ``prefix`` | Optional prefix name that is added to object files by the | | | build system | +--------------+------------------------------------------------------------+ ------------ ```` ------------ Executes a Makefile. Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``target`` | Name of the target to execute (defaults to "all") | +----------------+-----------------------------------------------------------+ | ``file`` | Path to the Makefile that should be used. | +----------------+-----------------------------------------------------------+ | ``keep-going`` | Whether `make` should try to continue even after | | | encountering errors. | +----------------+-----------------------------------------------------------+ | ``jobs`` | Number of parallel jobs used by make. | +----------------+-----------------------------------------------------------+ | ``directory`` | Path of the directory in which make should be called. | +----------------+-----------------------------------------------------------+ | ``args`` | Any space separated arguments to pass to the makefile. | | | Usually in the form: | | | ``"parameter1=value1 parameter2=value2"``. | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Runs the target "compile" of the ``Makefile`` located in the sub-directory ``build``. .. code-block:: xml Same as previous but execute the command in the ``work`` directory and call the makefile with the command line argument ``coverage=1``. Configuration ------------- .. code-block:: ini [make] path = /path/to/(c|n)make --------------- ```` --------------- Report the test output generated by the CppUnit_ unit testing framework. The output from CppUnit must be in XML format and in already, specified by the ``file`` argument of this recipe. .. _cppunit: http://cppunit.sourceforge.net Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path to the cppunit XML output file. | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Runs the program ``run_unit_tests`` to gather the data output by CppUnit in the ``test_results.xml`` file and then reports it. Java Tools ========== A bundle of recipe commands that support tools commonly used by Java projects. :Namespace: ``http://bitten.edgewall.org/tools/java`` :Common prefix: ``java`` -------------- ```` -------------- Runs an Ant_ build. .. _ant: http://ant.apache.org/ Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path of the build file, relative to the project source | | | directory (default is ``build.xml``). | +----------------+-----------------------------------------------------------+ | ``target`` | Name of the build target(s) to execute. | +----------------+-----------------------------------------------------------+ | ``args`` | Additional arguments to pass to Ant, separated by | | | whitespace. | +----------------+-----------------------------------------------------------+ | ``keep_going`` | Tell Ant to continue even when errors are in encountered | | | in the build. | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the project source directory. Configuration ------------- .. code-block:: ini [ant] home = /path/to/ant/dir [java] home = /path/to/java/dir -------------------- ```` -------------------- Extract code coverage data from a Cobertura_ XML file. .. _cobertura: http://cobertura.sourceforge.net/ Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path to the XML file generated by Cobertura | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Reads the specifid XML file, extracts the coverage data, and builds a coverage report to be sent to the build master. ---------------- ```` ---------------- Extracts information about unit test results from a file in JUnit_ XML format. .. _junit: http://junit.org/index.htm Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path to the JUnit XML test results file. This can include | | | wildcards, in which case all the file matching the | | | pattern will be included. | +----------------+-----------------------------------------------------------+ | ``srcdir`` | Path of the directory unit test sources. Used to link the | | | test cases to files. | +----------------+-----------------------------------------------------------+ The ``file`` attribute is required. Examples -------- .. code-block:: xml Collects the test results from all files in the `build/tests/results` directory that match the pattern `TEST-*.xml`. Also, maps the class names in the results files to Java source files in the directory `src/tests`. Mono Tools ========== A bundle of recipe commands that support tools commonly used by Mono/.NET projects. :Namespace: ``http://bitten.edgewall.org/tools/mono`` :Common prefix: ``mono`` ---------------- ```` ---------------- Extracts information about unit test results from a files in NUnit_ XML format. .. _nunit: http://nunit.org/ Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path to the NUnit XML test results file. This can include | | | wildcards, in which case all the file matching the | | | pattern will be included. | +----------------+-----------------------------------------------------------+ The ``file`` attribute is required. Examples -------- .. code-block:: xml PHP Tools ========= A bundle of recipe commands for PHP_ projects. :Namespace: ``http://bitten.edgewall.org/tools/php`` :Common prefix: ``php`` .. _php: http://php.net/ --------------- ```` --------------- Runs a Phing_ build. .. _phing: http://phing.info/ Parameters ---------- +-------------------+-------------------------------------------------------+ | Name | Description | +===================+=======================================================+ | ``file`` | Path of the build file, relative to the project | | | source directory (default is ``build.xml``). | +-------------------+-------------------------------------------------------+ | ``target`` | Name of the build target(s) to execute. | +-------------------+-------------------------------------------------------+ | ``args`` | Additional arguments to pass to Phing, separated by | | | whitespace. | +-------------------+-------------------------------------------------------+ | ``executable`` | Phing executable program (default is ``phing``). | +-------------------+-------------------------------------------------------+ Examples -------- .. code-block:: xml Executes the target ``compile`` of the ``build.xml`` buildfile at the top of the project source directory. ----------------- ```` ----------------- Extracts information from PHPUnit_ test results recorded in an XML file. .. _phpunit: http://www.phpunit.de/ Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path to the XML results file, relative to the project | | | source directory. | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Extracts the test results from the XML file located at ``build/test-results.xml``. ------------------ ```` ------------------ Extracts coverage information from Phing_'s code coverage task XML file or from PHPUnit_ coverage-clover XML file. Parameters ---------- +---------------+-----------------------------------------------------------+ | Name | Description | +===============+===========================================================+ | ``file`` | Path to the XML coverage file, relative to the project | | | source directory. | +---------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Python Tools ============ A bundle of recipe commands that support tools commonly used by Python_ projects. :Namespace: ``http://bitten.edgewall.org/tools/python`` :Common prefix: ``python`` .. _python: http://www.python.org/ ----------------- ```` ----------------- Executes a Python script. Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path of the script to execute, relative to the project | | | source directory. | +----------------+-----------------------------------------------------------+ | ``module`` | Name of the Python module to execute. | +----------------+-----------------------------------------------------------+ | ``function`` | Name of the function in the Python module to run. Only | | | works when also specifying the `module` attribute. | +----------------+-----------------------------------------------------------+ | ``args`` | Any arguments that should be passed to the script. | +----------------+-----------------------------------------------------------+ | ``output`` | Path to a file where any output by the script should be | | | recorded. | +----------------+-----------------------------------------------------------+ | ``timeout`` | Limits the runtime of this command to the specified | | | number of seconds, after which it will be terminated. | +----------------+-----------------------------------------------------------+ Either `file` or `module` must be specified. Examples -------- .. code-block:: xml Executes Pylint_ on the module/package ``myproj`` and stores the output into a file named ``pylint-report.txt``. Configuration ------------- .. code-block:: ini [python] path = /path/to/python ---------------------- ```` ---------------------- 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 | +----------------+-----------------------------------------------------------+ | `timeout` | Limits the runtime of this command to the specified | | | number of seconds, after which it will be terminated. | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Instructs `distutils` to produce a source distribution. .. code-block:: xml Instructs `distutils` to run the ``unittest`` command (which is provided by Bitten), and passes the options needed to determine the output paths for test results and code coverage reports. Option ``--coverage-method`` is one of ``trace``, ``coverage`` or ``figleaf``. Configuration ------------- .. code-block:: ini [python] path = /path/to/python --------------------- ```` --------------------- Extracts information from unittest_ results recorded in an XML file. .. _unittest: http://docs.python.org/lib/module-unittest.html **Note:** This report must be used in conjunction with the ``distutils`` command "unittest" that comes with Bitten. Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``file`` | Path to the XML results file, relative to the project | | | source directory. | +----------------+-----------------------------------------------------------+ Examples -------- .. code-block:: xml Extracts the test results from the XML file located at ``build/test-results.xml``. ------------------ ```` ------------------ Extracts coverage information recorded by the built-in Python module ``trace.py``. Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``summary`` | Path to the summary file written by ``trace.py``, | | | relative to the project source directory. | +--------------+-------------------------------------------------------------+ | ``coverdir`` | Path to the directory containing the coverage files written | | | by ``trace.py``, relative to the project source directory. | +--------------+-------------------------------------------------------------+ | ``include`` | List of glob patterns (separated by space) that specify | | | which Python file should be included in the coverage report | +--------------+-------------------------------------------------------------+ | ``exclude`` | List of glob patterns (separated by space) that specify | | | which Python file should be excluded from the coverage | | | report | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml --------------------- ```` --------------------- Extract data from a coverage.py_ run. .. _coverage.py: http://nedbatchelder.com/code/coverage/ Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``summary`` | Path to the summary file with ``coverage.py`` information, | | | relative to the project source directory. | +--------------+-------------------------------------------------------------+ | ``coverdir`` | Path to the directory containing per-module coverage | | | details, relative to the project source directory. | +--------------+-------------------------------------------------------------+ | ``include`` | List of glob patterns (separated by space) that specify | | | which Python file should be included in the coverage report | +--------------+-------------------------------------------------------------+ | ``exclude`` | List of glob patterns (separated by space) that specify | | | which Python file should be excluded from the coverage | | | report | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml -------------------- ```` -------------------- Extracts coverage information recorded by Figleaf_. .. _figleaf: http://darcs.idyll.org/~t/projects/figleaf/doc/ Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``summary`` | Path to the summary file written by ``figleaf``, | | | relative to the project source directory. | +--------------+-------------------------------------------------------------+ | ``include`` | List of glob patterns (separated by space) that specify | | | which Python file should be included in the coverage report | +--------------+-------------------------------------------------------------+ | ``exclude`` | List of glob patterns (separated by space) that specify | | | which Python file should be excluded from the coverage | | | report | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml ------------------- ```` ------------------- Extracts information from Pylint_ reports. .. _pylint: http://www.logilab.org/projects/pylint Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``file`` | Path to the file containing the Pylint output, relative to | | | the project source directory. | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml Subversion Tools ================ A collection of recipe commands for working with the Subversion_ version control system. This commands are commonly used as the first step of a build recipe to actually pull the code that should be built from the repository. .. _subversion: http://subversion.tigris.org/ :Namespace: ``http://bitten.edgewall.org/tools/svn`` :Common prefix: ``svn`` ------------------ ```` ------------------ Check out a working copy from a Subversion repository. Parameters ---------- +-----------------+-------------------------------------------------------------+ | Name | Description | +=================+=============================================================+ | ``url`` | URL of the repository. | +-----------------+-------------------------------------------------------------+ | ``path`` | The path inside the repository that should be checked out. | | | You should normally set this to ``${path}`` so that the | | | path of the build configuration is used. | +-----------------+-------------------------------------------------------------+ | ``revision`` | The revision that should be checked out. You should | | | normally set this to ``${revision}`` so that the revision | | | of the build is used. | +-----------------+-------------------------------------------------------------+ | ``dir`` | Path specifying which directory the sources should be | | | checked out to (defaults to '.'). | +-----------------+-------------------------------------------------------------+ | ``verbose`` | Whether to log the list of checked out files (defaults to | | | False). | +-----------------+-------------------------------------------------------------+ | ``shared_path`` | An optional shared directory to check the sources out in, | | | which will be reused for each subsequent build. This is | | | relative to the project directory, so for standard usage | | | set it to something like ``../trunk`` | +-----------------+-------------------------------------------------------------+ | ``username`` | Username to pass for authentication (optional) | +-----------------+-------------------------------------------------------------+ | ``password`` | Password to pass for authentication (optional) | +-----------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml This checks out the a working copy into the current directory. ---------------- ```` ---------------- Download a file or directory from a Subversion repository. This is similar to performing a checkout, but will not include the meta-data Subversion uses to connect the local working copy to the repository (i.e. it does not include the ``.svn`` directories.) Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``url`` | URL of the repository. | +--------------+-------------------------------------------------------------+ | ``path`` | The path inside the repository that should be checked out. | | | You should normally set this to ``${path}`` so that the | | | path of the build configuration is used. | +--------------+-------------------------------------------------------------+ | ``revision`` | The revision that should be checked out. You should | | | normally set this to ``${revision}`` so that the revision | | | of the build is used. | +--------------+-------------------------------------------------------------+ | ``dir`` | Path specifying which directory the sources should be | | | exported to (defaults to '.') | +--------------+-------------------------------------------------------------+ | ``username`` | Username to pass for authentication (optional) | +--------------+-------------------------------------------------------------+ | ``password`` | Password to pass for authentication (optional) | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml This downloads the file or directory at ``${path}`` from the Subversion repository at ``http://svn.example.org/repos/myproject/``. Variables are used for the ``path`` and ``revision`` attributes so they are populated from the properties of the build and build configuration. ---------------- ```` ---------------- Update an existing working copy from a Subversion repository to a specific revision. Parameters ---------- +--------------+-------------------------------------------------------------+ | Name | Description | +==============+=============================================================+ | ``revision`` | The revision that should be checked out. You should | | | normally set this to ``${revision}`` so that the revision | | | of the build is used. | +--------------+-------------------------------------------------------------+ | ``dir`` | Path specifying the directory containing the sources to be | | | updated (defaults to '.') | +--------------+-------------------------------------------------------------+ Examples -------- .. code-block:: xml This updates the working copy in the current directory. The revision is specified as a variable so that it is populated from the properties of the build. XML Tools ========= A collection of recipe commands for XML processing. :Namespace: ``http://bitten.edgewall.org/tools/xml`` :Common prefix: ``x`` ----------------- ```` ----------------- Apply an XSLT stylesheet . **Note:** This command requires either libxslt_ (with `Python bindings`_) or, on Windows platforms, MSXML (version 3 or later) to be installed on the slave machine. .. _libxslt: http://xmlsoft.org/XSLT/ .. _`python bindings`: http://xmlsoft.org/XSLT/python.html Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``src`` | Path of the source XML file. | +----------------+-----------------------------------------------------------+ | ``dest`` | Path of the destition XML file. | +----------------+-----------------------------------------------------------+ | ``stylesheet`` | Path to the XSLT stylesheet file. | +----------------+-----------------------------------------------------------+ All these are interpreted relative to the project source directory. Examples -------- .. code-block:: xml This applies the stylesheet in ``util/convert.xsl`` to the source file ``src.xml``, and writes the resulting XML document to ``dest.xml``. Mercurial Tools =============== A collection of recipe commands for working with Mercurial_ (hg) repositories. .. _mercurial: http://mercurial.selenic.com/ :Namespace: ``http://bitten.edgewall.org/tools/hg`` :Common prefix: ``hg`` ------------- ```` ------------- Pull changesets and updates a local Mercurial repository. As the command depends on a pre-existing repository, bitten-slave must be started with ``--build-dir=`` option for locating and working with the repository. Also, seeing no remote location can be specified the repository ``.hg/hgrc`` configuration file needs a ``[paths] default = ....`` location defined. Parameters ---------- +----------------+-----------------------------------------------------------+ | Name | Description | +================+===========================================================+ | ``revision`` | The revision to update to (optional, defaults to tip). | +----------------+-----------------------------------------------------------+ | ``dir`` | Local subdirectory with repository (optional, | | | defaults to '.'). | +----------------+-----------------------------------------------------------+ Paths are interpreted relative to the project source directory. Examples -------- .. code-block:: xml This updates the repository in ``src`` to the revision of the current build. Bitten-0.6/doc/common/000755 000765 000120 00000000000 11536424600 015114 5ustar00simonadmin000000 000000 Bitten-0.6/doc/configure.html000644 000765 000120 00000034431 11536424527 016510 0ustar00simonadmin000000 000000 Bitten: Configuration

Configuration

While the Recipe contains the instructions for how to build, configurations are used to determine what gets built and any runtime parameters used when building.

1   Target platforms

A target platform is something like 'NetBSD x86' or 'Win32 Java 1.4'.

Technically, a target platform is a named set of rules against which the properties of build slaves are matched. Each rule is a regular expression matching a particular slave property, such as the operating system or the processor. When a slave connects to the build master, it sends a registration message that includes information about the slave. A slave will only be sent builds for a given platform if the slave's properties satisfy all of the rules associated with that platform.

A rule's regular expression is matched against the value of the slave property using Python's re.match function so, for example, x86 will match a value of x86_64. Use ^x86$ to match only the value x86.

A build configuration must have at least one target platform assigned to it before it becomes fully active.

2   Slave Properties

By default, the following properties are included:

family:The basic type of operating system, typically “posix” for Unix-like systems and “nt” for Win32 systems.
os:The name of the operating system (for example “Darwin”, “Linux” or “Windows”).
version:The operating system version.
machine:The hardware platform (for example “i686” or “Power Macintosh”).
processor:The processor architecture (for example “athlon” or “powerpc”).
name:The name of the slave.
ipnr:The IP address of the slave.

Note that not all of these properties may be available for all platforms, depending on OS and Python version.

2.1   Examples

To set up a target platform, create rules that are checked against the properties of the slave. For example, a target platform that matches slave running Linux on x86 would look like this:

Property Expression
os ^Linux
machine ^[xi]d?86$

A target platform that matches any slaves running on Windows might look like this:

Property Expression
family ^nt$

The build master will request a build from at most one slave for every target platform. So, for example, if there are three slaves connected that are matching 'NetBSD x86', only one of them will perform the build of a specific revision. Slaves that match a particular target platform are treated as if they were completely interchangable.

If a slave connects that doesn't match any of the configured target platforms, the build master will reject its registration.

3   Slave Configuration

When a build slave registers with a build master, it sends information about the machine the slave is running on, and what software it has available. While some of this information can be automatically discovered by the slave, other information may need to be configured explicitly. Also, a slave instance may want to override some of the automatically computed attributes, for example to enable cross-compilation.

There are three categories of information that can be configured for a slave:

os:Properties of the operating system
machine:Properties of the underlying hardware
packages:Various pieces of software, like a language runtime or a library

3.1   Configuration File Format

For simple manual editing, the slave configuration file will be based on the 'INI' file format known from Windows, which is also frequently used by Python applications.

The file is included at runtime using a slave command-line option:

bitten-slave -f config.ini

A configuration file is partitioned into named sections. There are two predefined sections named [machine] and [os]. If you supply them in your configuration file they should include the following sections.

[os]
name = Darwin
version = 8.1.0
family = posix

[machine]
name = levi
processor = Power Macintosh

There may be any number of additional sections, where each section corresponds to a software package. For example:

[dbxml]
version = 2.1.8

[python]
version = 2.3.5
path = /usr/bin/python2.3

Note: Options called name is not allowed in custom sections (will be skipped).

The build slave sends this package information as part of the build initiation, which when using verbose logging (bitten-slave -v) will display a debug message 'Sending slave configuration:' followed by:

<slave name="host.domain">
  <platform processor="Power Macintosh">levi</platform>
  <os version="8.1.0" family="posix">Darwin</os>
  <package name="dbxml" version="2.1.8" />
  <package name="python" version="2.3.5" path="/usr/bin/python23" />
</slave>

The name of the slave can only be set as command-line option:

bitten-slave --name=myhost

3.2   Commands using Properties

A number of commands support runtime settings using a slave configuration file. The example of python.path above is one such example, where all Python commands will use the specified executable for running commands.

The documentation for commands should include information about all runtime settings.

3.3   Properties in Build Configurations

Defined properties can be used in a build configuration to match slaves against target platforms. For example, the following rule would match any slave providing 'Berkeley DB XML' version 2.x:

dbxml.version ~= /^2\.\d\.\d.*/

The properties are accessible in dotted notation, where the part before the dot is the package name, and the part after the dot is the name of the property.

3.4   Property Interpolation in Build Recipes

Property values can be interpolated into build recipes as well, so individual slaves can parameterize how their build is perfomed. For example, given the following build recipe excerpt:

<svn:checkout url="http://svn.example.org/repos/myproject/"
  path="${repository.branch}" revision="${revision}"/>

Slaves may control which part of the repository is checked out and tested with a configuration file excerpt like this one:

[repository]
branch = /branches/0.3-testing

Default slave properties are also available for use in recipes:

<sh:exec executable="echo"
  args="Slave: ${family} ${os} ${version} ${machine} ${processor}"/>

Additionally, environment variables are also interpolated, supporting the common notations of $VAR and ${VAR}.

<sh:exec executable="${PROGRAMFILES}/SomeDir/MyProg.exe" />

4   Authentication

Authentication information can also be included in slave configuration file:

[authentication]
username = myusername
password = mypassword

The authentication information will be removed as soon as it is read by the slave, and will not be passed to the master as active configuration.

Bitten-0.6/doc/configure.txt000644 000765 000120 00000017426 11513271761 016363 0ustar00simonadmin000000 000000 .. -*- mode: rst; encoding: utf-8 -*- ============= Configuration ============= While the `Recipe`_ contains the instructions for how to build, configurations are used to determine what gets built and any runtime parameters used when building. .. _recipe: recipe.html .. contents:: Contents :depth: 2 .. sectnum:: Target platforms ================ A target platform is something like 'NetBSD x86' or 'Win32 Java 1.4'. Technically, a target platform is a named set of rules against which the properties of build slaves are matched. Each rule is a regular expression matching a particular slave property, such as the operating system or the processor. When a slave connects to the build master, it sends a registration message that includes information about the slave. A slave will only be sent builds for a given platform if the slave's properties satisfy all of the rules associated with that platform. A rule's regular expression is matched against the value of the slave property using Python's `re.match`_ function so, for example, `x86` will match a value of `x86_64`. Use `^x86$` to match only the value `x86`. .. _re.match: http://docs.python.org/library/re.html#re.match A build configuration must have at least one target platform assigned to it before it becomes fully active. Slave Properties ================ By default, the following properties are included: :`family`: The basic type of operating system, typically “posix” for Unix-like systems and “nt” for Win32 systems. :`os`: The name of the operating system (for example “Darwin”, “Linux” or “Windows”). :`version`: The operating system version. :`machine`: The hardware platform (for example “i686” or “Power Macintosh”). :`processor`: The processor architecture (for example “athlon” or “powerpc”). :`name`: The name of the slave. :`ipnr`: The IP address of the slave. Note that not all of these properties may be available for all platforms, depending on OS and Python version. Examples -------- To set up a target platform, create rules that are checked against the properties of the slave. For example, a target platform that matches slave running Linux on x86 would look like this: +------------+------------------------------------+ + Property | Expression | +============+====================================+ | `os` | `^Linux` | +------------+------------------------------------+ | `machine` | `^[xi]\d?86$` | +------------+------------------------------------+ A target platform that matches any slaves running on Windows might look like this: +------------+------------------------------------+ + Property | Expression | +============+====================================+ | `family` | `^nt$` | +------------+------------------------------------+ The build master will request a build from at most one slave for every target platform. So, for example, if there are three slaves connected that are matching 'NetBSD x86', only one of them will perform the build of a specific revision. Slaves that match a particular target platform are treated as if they were completely interchangable. If a slave connects that doesn't match any of the configured target platforms, the build master will reject its registration. Slave Configuration =================== When a build slave registers with a build master, it sends information about the machine the slave is running on, and what software it has available. While some of this information can be automatically discovered by the slave, other information may need to be configured explicitly. Also, a slave instance may want to override some of the automatically computed attributes, for example to enable cross-compilation. There are three categories of information that can be configured for a slave: :`os`: Properties of the operating system :`machine`: Properties of the underlying hardware :`packages`: Various pieces of software, like a language runtime or a library Configuration File Format ------------------------- For simple manual editing, the slave configuration file will be based on the ``'INI'`` file format known from Windows, which is also frequently used by Python applications. The file is included at runtime using a slave command-line option:: bitten-slave -f config.ini A configuration file is partitioned into named sections. There are two predefined sections named ``[machine]`` and ``[os]``. If you supply them in your configuration file they should include the following sections. .. code-block:: ini [os] name = Darwin version = 8.1.0 family = posix [machine] name = levi processor = Power Macintosh There may be any number of additional sections, where each section corresponds to a software package. For example: .. code-block:: ini [dbxml] version = 2.1.8 [python] version = 2.3.5 path = /usr/bin/python2.3 *Note:* Options called ``name`` is not allowed in custom sections (will be skipped). The build slave sends this package information as part of the build initiation, which when using verbose logging (``bitten-slave -v``) will display a debug message 'Sending slave configuration:' followed by: .. code-block:: xml levi Darwin The name of the slave can only be set as command-line option:: bitten-slave --name=myhost Commands using Properties ------------------------- A number of commands_ support runtime settings using a slave configuration file. The example of ``python.path`` above is one such example, where all Python commands will use the specified executable for running commands. The documentation for commands_ should include information about all runtime settings. .. _commands: commands.html Properties in Build Configurations ---------------------------------- Defined properties can be used in a build configuration to match slaves against target platforms. For example, the following rule would match any slave providing 'Berkeley DB XML' version 2.x:: dbxml.version ~= /^2\.\d\.\d.*/ The properties are accessible in dotted notation, where the part before the dot is the package name, and the part after the dot is the name of the property. Property Interpolation in Build Recipes --------------------------------------- Property values can be interpolated into build recipes_ as well, so individual slaves can parameterize how their build is perfomed. For example, given the following build recipe excerpt: .. code-block:: xml .. _recipes: recipes.html Slaves may control which part of the repository is checked out and tested with a configuration file excerpt like this one: .. code-block:: ini [repository] branch = /branches/0.3-testing Default slave properties are also available for use in recipes: .. code-block:: xml Additionally, environment variables are also interpolated, supporting the common notations of ``$VAR`` and ``${VAR}``. .. code-block:: xml Authentication ============== Authentication information can also be included in slave configuration file: .. code-block:: ini [authentication] username = myusername password = mypassword The authentication information will be removed as soon as it is read by the slave, and will not be passed to the master as active configuration. Bitten-0.6/doc/index.html000644 000765 000120 00000003543 11536424527 015636 0ustar00simonadmin000000 000000 Bitten: Preface

Preface

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.

Bitten-0.6/doc/index.txt000644 000765 000120 00000001411 11457032362 015473 0ustar00simonadmin000000 000000 .. -*- mode: rst; encoding: utf-8 -*- ======= Preface ======= .. image:: logo.png :width: 538 :height: 298 :align: center :alt: Bitten :class: logo ------------------------------- Continuous Integration for Trac ------------------------------- Bitten is a Python-based framework for collecting various software metrics via continuous integration. It builds on Trac to provide an integrated web-based user interface. * `Installation `_ * `Build Recipes `_ * `Build Recipe Commands `_ * `Configuration `_ * `Build Links `_ * `Notification `_ * `Report Formats `_ * `Upgrading Bitten `_ * `Generated API Documentation `_ Bitten-0.6/doc/install.html000644 000765 000120 00000014746 11536424527 016204 0ustar00simonadmin000000 000000 Bitten: Installation

Installation

1   Prerequisites

Bitten is written in Python, so make sure that you have Python installed. You'll need Python 2.4 or later for running build slave. The build master is a plugin for Trac 0.11, and should still work with Python 2.3. Also, make sure that 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 (please note that Trac needs to be installed for the tests to run; see below):

$ python setup.py test

It is also possible to install only the build slave, installing only the parts of Bitten that are needed by clients to run builds:

$ python setup.py --without-master install

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.11 or later. Please refer to the Trac documentation for information on how it is installed.

2   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 [yourname] BUILD_ADMIN
$ trac-admin /path/to/projenv permission add anonymous BUILD_VIEW

Build slaves (see next section) will need permission to download build configurations and submit results. You can grant the permission using:

$ trac-admin /path/to/projenv permission add [slavegroup] BUILD_EXEC

Alternatively you may allow anyone to submit builds by giving anonymous users this permission.

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 Bitten is 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.

3   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
Bitten-0.6/doc/install.txt000644 000765 000120 00000010741 11363375354 016047 0ustar00simonadmin000000 000000 .. -*- 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.4 or later for running build slave. The build master is a plugin for Trac_ 0.11, and should still work with Python 2.3. Also, make sure that setuptools_, version 0.6a2 or later, is installed. .. _trac: http://trac.edgewall.org .. _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 (please note that Trac_ needs to be installed for the tests to run; see below):: $ python setup.py test It is also possible to install only the build slave, installing only the parts of Bitten that are needed by clients to run builds:: $ python setup.py --without-master install 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.11 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``: .. 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 [yourname] BUILD_ADMIN $ trac-admin /path/to/projenv permission add anonymous BUILD_VIEW Build slaves (see next section) will need permission to download build configurations and submit results. You can grant the permission using:: $ trac-admin /path/to/projenv permission add [slavegroup] BUILD_EXEC Alternatively you may allow anyone to submit builds by giving anonymous users this permission. 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 Bitten is 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 Bitten-0.6/doc/links.html000644 000765 000120 00000002506 11536424527 015645 0ustar00simonadmin000000 000000 Bitten: Build Links Bitten-0.6/doc/links.txt000644 000765 000120 00000000713 11335040672 015506 0ustar00simonadmin000000 000000 .. -*- mode: rst; encoding: utf-8 -*- =========== Build Links =========== Bitten supports the ability to link between any wiki-enabled objects in Trac. To link to a particular build, use the TracLink syntax ``build:N`` (where ``N`` is the number of the build). Linking to a particular step of the build is also supported by adding an anchor reference, like ``build:N#Test``. If the step ID contains spaces, the link must be quoted: ``build:"N#My Step"``. Bitten-0.6/doc/logo.pdf000644 000765 000120 00000121501 10654567501 015267 0ustar00simonadmin000000 000000 %PDF-1.3 % 2 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xڽ]WeqUwu)HD@+y`] 5K0BꝙSN ?SG?5L+s-=Rg䵷ݶ6>Z3=j}w7=}~ū幗{.yuN/7yjzÐӣa^SN_=Kws5^Y]ߖ=r߼^"=WmV9{{=A_l8a6pAQ~qJl^*v}xb%3޽NL<[:A˃9Oa}^:qzm9м$bL>E[\q=eɻ B;{_3 ǹUX[-+?f^EFxz,)4@W]6A0啵.%͂AGA G5`.5I8ؼVCɥ4[DmZZse}0; X=QAF}qcs_r<1<ϥDp"cCOQBsQZV9(ȓsoj,]5vj̙!$33v @g*om(جM$w+AR>9&!M&!tUgɌ*"ހMo"3!xM'֘8+h7G2# sx^򵯂c^m#ZVpbSvJFsP9ʼnT mCoN@%̅[F {nk*P5?+X%^#wW:Np]8ъ)C=ـa!2EG PڍC}!wA0414yv\[fR nYwBz<1^tfJf1= R2W` W6 E|GQu)Peם:5ddiv Ih]F: n{ hT8ݡAn1 jfDc ɓzZStJ;mm(wޚHE|dݬccN+:P$;O0=*-l,'*뢴uu=s$yỌ֜89X|0VKf.r556P*sһ&L550K28fF5\3Q"0= 쀧0(7B?K"!rCj M# J.}j4NF{otner[d'2tT7> ]}pb(;Lgo*PQp ĩcL$̯RTFZPN&4֤$P+ȍMS/@fQPG7;<љU tEZi@XSuD !:0#YU/P^Au(E1P\ z80]$DnFxګ DT'Vl.)BgVhcP&gL)1X , pT2}QNȉ &k״pdc4d?qE~ͤ}!t PQ>>JMQ kHUI_6p-nEZiIXؙ=<`'&>Z+Inf G߰F& )c4P$vNYYP4Ki[%X+g4*O..jIxZ Igk~D!QM7 PyVv rCuZ;Y 2f=[$ VwA :E1]o: x?h"*M8FHҚ[@GZȽ ܴ 4|#lx)EZ+ ֭Nm&0]  1&QSEB ^52h/M_) z{D9:34#.bP\h6/RT-̶ܦf%4~ӈFKzdbm|]t,ʳl,dy!Ї$ V@dn/ ށ:ק%YY ]q!ZqDIJۖt@SuBq5. bƒ̶c҅#,  a†յeXuK!^/{zW_oW=oۋcQ)6T߯K|wqطw~k/vŵ:Y??oǜS @ H^/7lz=tm^^n'(SsLih>dLH >8\w_LY''Ng endstream endobj 4 0 obj 3174 endobj 1 0 obj << /Type /Page /Parent 35 0 R /Resources 3 0 R /Contents 2 0 R /MediaBox [0 0 548.87909 302.19348] >> endobj 3 0 obj << /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] /ColorSpace << /Cs1 5 0 R >> /Font << /F1.0 6 0 R >> /XObject << /Im4 19 0 R /Im1 7 0 R /Im5 23 0 R /Im7 31 0 R /Im3 15 0 R /Im6 27 0 R /Im2 11 0 R /Fm4 21 0 R /Fm7 33 0 R /Fm2 13 0 R /Fm3 17 0 R /Fm6 29 0 R /Fm1 9 0 R /Fm5 25 0 R >> /Properties << /Pl1 36 0 R >> >> endobj 21 0 obj << /Length 37 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [131 117 183 253] /Resources 22 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xu ~ ĕ(Qι(CC_km6(eQp8t8=8R|nRBVB g5僽+6c8&eJñUA|$@CZTjd{T%Fu!29QRńƽ G?[2Ѵ*&yeIkD^ ysJHG]}P ~?9Ɯ$jڞLH7ͻܩOϱ7rY%]B-S*&=b-{؝z"z+(7ˆV0VK!βIGLcA/Y7t}$ᚬD0Iӱc{vx-= 隆oXL]_\J@c*ws.ZE#&қ<\ `fa׃&FfͤHd-| VBtW%V98gCjN;sp]*ay^81:Y.}mHxE?5.ƅUQͣަ.W$}Җbkl]m^1*A4SW#;&~I+,S>Ҿ7zIN&c4{2 =*-Nd#NjM<5c*OdB\"rfLJ̎AC^: Mid D;|0-.fڹ?IL2vBr="$?iai :ꌴFT֥0[^ScvqBuV#egoh!K H.W]P],D3"&QTk ď#O{$n$pVTmk(aO}dkA`-1+l."VV:K #A&MD=JRߺRFsAC⯩܁wP Nt<Xtkg-L{*{"{g]OJdi卨V[E3\CDA2K f9XVwmJDST|Qd?F|F 7%Bi%Z&*!AхtMOrz S98 endstream endobj 37 0 obj 2953 endobj 22 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 5 0 R >> >> endobj 33 0 obj << /Length 38 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [8 119 86 258] /Resources 34 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xu4 ~ ܖ(˜0lc+tq,j,Kkwg?_~mvUV;moo^b]u}U>ۚ\\gz6⴨-ʌ;Xk*sc F9!e5mz?]:j^mg>>m+nVTÝmks^[i}X87fv?[V<ͺi s,Ѧ'f\ux4V} &vψzՇ \q/s֋">Zsv\U-+ZT݊h{u"{qX3`Nى'QO#eMOtz4A,c޺XLq[:uaS+Liv+_gnLu85I nvEb* bI 6Nn3JF dIlGOaC%OLI[K !s=lN2Y⸷Ɵ.FcDlиq6'1`bd3eAaP?gE~6Y;UBz`˚xd涙#GwE,8kC}.q*> = !!a=|xDʢD $-͵3Z%ulVm`)2E%Iå?Q~}ڠt6ƚr~bCSꘗ|Pv bJܺ8"ˬI̱d9[_fY"59NSLzbU*ЛAtĕj6&h.]Q1mM 'ܩ+l0,uƘ'2X.Sg\8IH]=BT#A1lhJ2Hz`O[\1~i>/cF^0v=D (':~чS"ks)J0ִKlȿ[5J NL2Zie r*YM;v(ŀ&k=YHu'rl*K+ڦ= @&ü#Z;. ~JP=C%s4ѽd_W4P&6񃮋4=DA>uC^-5+D""rp|6\Cd yvqdEgVqaȉA&)Q;5Avʻ%Dd{lc VD:? I)2IUG#ZIG=U_۟V;~Gş[[f4{"E~ģ²ߧMRcHϏmxt zl+~ KtӳJ_YK΋BCC5 Ɩ;:-lo[DMG©dA9@:|݋l k@, YeN]l‰* <7#Y|`]oF@M6#&Uɀ-;ޤIUN(DCPG=^yi?FRepfD'pH'Gٓ.,f,eY(E We7v_yJ;5پn,\*yG8' KA}Ȭ’̄KqVc[^|r}T!Pm\LNHl)zn}0 %5A~-C8fS%bl˂d0Cqtr#P@:df裄Fʷ)N">QA저ll"tt…p SUl1Yki)>^F6=atI=FZezHS!QgϪ*Ҥ#n;nQ+a' Q$*T"DQ4ll ꌋ Occľz7 [t(S54fER_cԓW6߄5feWYkUԶ|4XT0HU/ۉUPv,j0|ipY5âx*FW %{8jLMG+VPO׻Xn#NJ\]Nw YBK@`yC,:iD 5dGJNipq$*J45a:>K"WkG8WSP3)?V%\znu$;6_r%-UMe>t=q)V+m){ڰBh`O(L=ݱ]θzh y"\%ͧݟlk 7.&;cy1=9A|dB6PCGlSGxzR&%/7~0$v) )(7DhT{)> >> endobj 13 0 obj << /Length 39 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [233 123 306 216] /Resources 14 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xu͎$ u6r2̟t[#4` ̞Z 31$A<%ο_#jf,qQjZg8^hubefgX֌5Zgv>>4fڲaX.Z ^u'60hzCkkWyqi]Y;ljN[l%:ظu q QlG{>r2{m]oX3e񸣍Wa/ذ>Fr7$IΩqEJZi773Z|{ BH>d%ު8c**W;^sUm&6>Pe  x[.+uĹi` cƪ”=a"4o"(}A,O4>4nWTgO>H,r"^:9qa;6F\$+)Ȫn4%x '1 n0И00QB)vp-J&V%G >(k Ed5g#)^w4$7OHz5$zaW pnتTʍaJɉrRa8#;^sxeӁvnYj/` $w$&I&HzA/pXJVËR/{s”!.aF-QUQSa] %ձ2&BGI{RZTh3&3LAww} ,͖]͍?8NZýIpZ6Z}cdҥYTjLQ d9YWb!ۇ/ě6E0fV)1nX"P3:d<ĖLyZ °TD @K)'&}"R-eɻIbOiKDc'7˺_eFt$rU)x%QRx[cZ٘ak&y 7MG{_p ?CHCmP)C&H`̆$rORn"-7TW:Q]3ZqߋYZj/E8R"b&0-J1n-au?#Q)kIDjz0,:ec0"ټ#!Aы(L=P|(3U^ NK!WI#٢KhͼWhbGE~V϶rOTWA@C(9M B:M!'ްkyGcx.PNs Xj0}G!Wc0N.}7UhmJjqa ,!#7ӌ13BG.\)taߧ%SeiiKJJ_-5(R,pgbĤjJv R72&n YZ95q8ESP505,/#\_cF1%Ssݐѻ>rlS)+ZHeu36-*$iH ejBL4S_>:&`~'\c_4Q%u)4ks4?w bRt `$v,&)eU%'Fk !7 Lxp/}6Rv3ۑ9j >5/%3 $%HܰOZd ꞘࡲeCɈ:nᗯEVzz2б&]tFFV^ c펩3CjAV eUlsJ"t |yPN4;EG/%԰2*}Rj NAœαǫ=U˨isl)uL܂}m{j\4"`HSd(?Hփu١YqZ{ZSWN!/Xy$,| & 3gM}* 5z7MdԚY]s7tfc鴾0B߄$'ɗ4GtK-e&iʘ&k߈}@8 C, endstream endobj 39 0 obj 2862 endobj 14 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 5 0 R >> >> endobj 17 0 obj << /Length 40 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [187 117 239 247] /Resources 18 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xuͮFz Dӿdڻ ?3Eż~bIAb#MY,?{:,ůw׷y^fy?_{v%zcB7y -e{IGkpwt$=F4l;zmڧL˛<&H9*t1{y,5[t&VU ~k#Ѭq|UϘ9Gr;s`6\P:>^z<ϣdKV8h1yQӚc.lF0j>gG^/5gppfq9X;f}MhKqQIg99^=0c6yuF*bs/ձg&]K7bm޼!M;31~`s8w/G,aT\X3gS%"S`hϬE81 [$ෑlLĂV[ #ygK{IAfZr J ч4.L'c$iX άRQok;$Vp==gp@+;VZJ@Th +IT(U~[rwCvrԷ\C;Wi$%e &8a)j Kk-.xm}oŜgQzO30nw!}6iC/>{:}=ތa+"w~XAnC_oD}~|F_ jTƷ$sȺ8d(X2%uNH:PF56Giָ0 /"bt?T!/Gol8F9;'R k ǝn(_e\Xn |+7rfdiVC%{`7MV? 9YkdHeX1U> kbz{ hr5Y4G xRkwr,UuC̈́u^UG<S Q--r+욍uU9)]0&p,2.)F hSkvmC\~# HU^6V%T=ǭgߗ[]l[6r}eL{ Y(A FؘhXR"k &&(2y$.TYK"'Nf|"V}3{^ gƱe)6v}'$p"O|eԾ#̞A\0 qw,T+,z'wT aMṰR~h6ajfe$h U[7-]'"H!U7Tn1F.'OpɃ$ОFq 1z7 uh!zp e1"D uc\8=Elfl24q'qli6H(8e+$@h4U9}WtH萂ç.Ϋ^}' 8P endstream endobj 40 0 obj 2954 endobj 18 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 5 0 R >> >> endobj 29 0 obj << /Length 41 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [93 211 125 237] /Resources 30 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xuj1 ~ qg]w!0PB%~<%3$\>$Kq~asp??S?}q*QщHaޙ֘,nIǜ L\2hp‰UR.6lZ#8ݚ3ly%7F.nљ-pH,fc łsvQ#5ū'f3 NډHYᛚVt<3kb4\dC$#ռSTX6lg(h #ofq"\{ՙ!2a,g㒺"ڐD4V2kcA,`lFk.nZ;YYu#)ZE;Fl ?4 $R*`80KѿG_?u:/q _Qm'OU7-+tB['8E!ML-Ɍ[1XޅdX:`GÕjsW$V+B nݘcz8;l0xecC([`]!t&p^7d ⳬ Wax9J,%T? Kd2%آ.l芉W>Dbp(]CR3G!2s@<7S ;u Cm1ֺ(NAGn3l,ʹscIѪX~E?6\V3mhmZ9N g D] ua }d;KX<@-)owٿ_orI endstream endobj 41 0 obj 753 endobj 30 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 5 0 R >> >> endobj 9 0 obj << /Length 42 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [309 124 377 226] /Resources 10 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xmͮ\ ):H(k3L~ۭl3jb>%?/-zq8mmzaWe'hkq~֫Dv6eic]޽Z65.8lܗZNuG{̹\/av|&Zlu7̯=|2璽HX=vYon9r5[ q3}.}-9^'f+cro=.9˜ݹy7TXfbZ{bw|}$]DYukS(c9>Z?nl6W&YEw,*g=Z)؀9C+»NV"W/uM6e1 _WkWXZ_aVjb:<i>y9aFuU2s6uk6&DMaf>(k֋ʢ|>TP+zUs_L0. Y$=kBvŬN;%C ƱcNJo(fhe9GԶvZpk3|jYcn{ӈ5ƌ#mf̶Yc\lsPtbC9a͈_x]k]c?1rg$j%hhjYyljP72S6.5i66UM&4u\F2¹edU eDH7 m_˔.Kٿ@ʍlY#gZ+*EkHy D$Ē mYwgb|p.k5a|}5 ^(֮am*e/hؑJ,04"ԕTm''SM9PӧPu⇰C)_JOlg6ҤMk(6 8utO@ϖgzPuLj=᣼("d6nE/\ eY86ivpY- @iu`Mj-֮%i qT+kwpeedpP+/HOvPN4KZb0z|/c7 ҽ0"C6s}ԥޅH3( طIYU eVh Nt=K)}'KDԊ譱g6o?v}g WQ?1TB?Oecw@| N&3G\:W ٠x8^HQ_d8/P">h^-oFdҐ^ɧODڇ@ u*;nR,\>-6C$F  5RFY{ TQT#!.3ø!}f;&2EV͉ 4tQbKW~ *`ki'vOLB+p)RK(x$1'lUؖHkA:$B\flyJ3.:aEpյ鍶D #:&SóAӀ92{Jc$:ЖK!š@%zzlפ*NSҚ.ooK|%d|]>""X-WUey 4v IJ6Ti9."}lr+ mqGNׂ0 hŏM".kA@~-{JkxVx i{w`oTNf1\v;QS O>g|lӴa\:adzpP"4S\=`dMN7Ĺ!'q[SPdD.Bx@%pܣ?uq<5bD01T! lzD ÈH`!cn5H>Co'چ+IYG-mG*ٺagtņlV17א¢Ho6UTPOW%)4]lkO ڽ~~{ɳ6t^f^.n70S<166\;h:5s'Xt'v= l+j[P v~PhK~:csI_['ʋR3+0;xOuǐ8Ne endstream endobj 42 0 obj 2250 endobj 10 0 obj << /ProcSet [ /PDF ] /ColorSpace << /Cs1 5 0 R >> >> endobj 25 0 obj << /Length 43 0 R /Type /XObject /Subtype /Form /FormType 1 /BBox [92 128 125 206] /Resources 26 0 R /Group << /S /Transparency /I true /K false >> /Filter /FlateDecode >> stream xu7 E HJt?a pϡfmH^^{RV?-?kxQ=ڴo.1c!뫯Qdڧ Z7j# s2Fu62=8ua/*үX_u>;kúޭKf!t(Θ:7#*#Q?PikxļZEKk>2[hԳQ;cLkZm>|e6WGaBqZ^`7ܜζYcژǶUvA.!,%D0i[8RC_O/-Bdu\3è= ܒ'뎬Z!ܰCf S1tj8Gɼ$LMnOVԔLj:zXfzgqh]H5kq.:ZOPimG5fZd8vh'[tmg7zGo6GFTyUu7EI9.#мЈ4~sp:a]DfușB 2g9u⺖F E ]Gߊ)+헭|R&J0Z V#214uS%%$hu~s$>Уd\acP\# Yb6orBO(L/ G\fpd7~:d ;zVDuxnf7L!0+=F'# b%X:{oc3LD0MfHHsȓW% -W&h$'a=hW?`Rd94fwQPfcPE#E9yXf3%O9塵> >> endobj 19 0 obj << /Length 20 0 R /Type /XObject /Subtype /Image /Width 54 /Height 138 /ColorSpace 5 0 R /Interpolate true /SMask 44 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream x  Om7WT endstream endobj 20 0 obj 45 endobj 7 0 obj << /Length 8 0 R /Type /XObject /Subtype /Image /Width 70 /Height 104 /ColorSpace 5 0 R /Interpolate true /SMask 46 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream x  Om7> UP endstream endobj 8 0 obj 44 endobj 23 0 obj << /Length 24 0 R /Type /XObject /Subtype /Image /Width 35 /Height 80 /ColorSpace 5 0 R /Interpolate true /SMask 48 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream x1 Om   endstream endobj 24 0 obj 31 endobj 31 0 obj << /Length 32 0 R /Type /XObject /Subtype /Image /Width 80 /Height 141 /ColorSpace 5 0 R /Interpolate true /SMask 50 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream xàS_U0 endstream endobj 32 0 obj 56 endobj 15 0 obj << /Length 16 0 R /Type /XObject /Subtype /Image /Width 54 /Height 132 /ColorSpace 5 0 R /Interpolate true /SMask 52 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream x  OmS endstream endobj 16 0 obj 43 endobj 27 0 obj << /Length 28 0 R /Type /XObject /Subtype /Image /Width 34 /Height 28 /ColorSpace 5 0 R /Interpolate true /SMask 54 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream x1 Oo7 ( endstream endobj 28 0 obj 25 endobj 11 0 obj << /Length 12 0 R /Type /XObject /Subtype /Image /Width 75 /Height 95 /ColorSpace 5 0 R /Interpolate true /SMask 56 0 R /BitsPerComponent 8 /Filter /FlateDecode >> stream x  OmS endstream endobj 12 0 obj 43 endobj 50 0 obj << /Length 51 0 R /Type /XObject /Subtype /Image /Width 80 /Height 141 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Interpolate true /BitsPerComponent 8 /Filter /FlateDecode >> stream xZipJ+>6bR.@@+bh )C1@ i3a`-`Ȕ! &(_:˒ҮjX~Z什z;yp|rQ@$)*LG89ހI#KovGZ>٤Rsbno0[;.u;mWx2FS;# M񢸷z8=h= :=-WX? CoIrX,O}sd B˲s-_(s8k<(63/[8$kN> ]gOOǻY5ZnuL˞^Y5fhY}vp at1$$ρE/; ӻ/<2'd62=x/|up.HV,ǑOsun[7!˼jb!ĽunC 4ن.zs4}H~֑F_(ղ]HtAI_#@4". v"2=EmvuzKqhEA YѢ8Z2qZz-ȆFDFe$kl+(i GX B5Ew!" F%;%i|Ie=Lࢸ}]jůQW< CGF~Ԝ f~,D/7~5MxxXӓ +RoNSJ/`UBo<w>TJV{O;8"9_ST?=Vcto|$4 T6Mls1"Tʡ_6 ǯ }yw(cRtm]z7\oǔ,`ROEc QIeM(eI}7h"x;*E$ ϹJL?k/3$#C y$"WYu?r:lW6s4oA7BX7wiv]{AԜ$oWղM]lBmÏ8 7.oL`^'v>Gд #N? K}4mak;%XB_ ޝKkχ:W(v9/IdrTMiЩd1/Kx/5@?"pJ1XLэhoc>~]1S,"(uP,ʔb}PP}^Oݭ}gc"> óHf.(CFvY!edi/`B |z=80K\SٶKEFzFGLpR&ŰvI i߲t= h ~i]3UbW/sd\IԤF . |H&i~&d4Aj2j#g 5 20GK<`UkZ1R=$.k)hSR\xA3ya[/)VHY0H^\q3CE4Bw6f-xSB^u-396C D# ^o^-&+W ΄4yD2lK͍V>sVɁ]+wDpKKSL]^`,PӮ7iF|pZ>hSuJXAwf U:tc.rb66\<>)o2vU ƚ)5 ڷ"w- w&Ž}խ?kG_}驉n"(wՇѭتZ*to|tS5x}\p'Ok3mk7#ԓڳi̇&tOj&8oeZ~D70v`OǾ#:dS?rrq^xKߤs`lZXysKYw|v0|ܶj@BYұ P r>tЦc !PDT} E:\5t m|nƁFQJh_mt|kb&A|!tƳ;y> stream xڝyle߷uzo`NG&pH f$(x uq( "pL 0v]uG:vi=>%xYJkU2>T9eVo;t؎Q)V˪_v)_0:[֕ɛ^}IGv BsvwIZ1B%agшۣJa-\)AMemӵDTg ,s5|.n6ap%| cGNe-桵1A=V7‚V)&L#sEa00#_^* J R z %?k.| (tCoK]?Fe Ѽ4)c1{ й`a (GeO2)ci?_bRKYR:Vjȉ?~= imL(C}P-_^s (ѓ??zpPNA]u/]GA,+w3c 盨 R+7*|fE0B٘QrߩYvsZӱ,cؐB0Ns?R$Vn싯!1|U̾̽=Huۜ`G2)Tgk)3k.?4_;Yc;](? ךɘơL:B,d!Bixw7o$ T_hDTd{2`A~=jl-J)j K%Oi GlRd)зzSm<vs * l!~{ _PEL:wP|GSQ8Si5[z_7TO)yqڠ)i(Zlخ" N Z?@;WDBl15AG%n@d-sb9wbW* VU*K| E2P*ۍbtcx 9'>%s`KdL n,ña)( "Aq_rvѸj opB)gM13zp$GeѵJlw~*njNp=@2y"Qj)k z2cؤEjViƋ! 3 e,k;nm-'I%y+h6J^v͇3Bv^ 5 L*5AiIYE_HD9_$ P3w|+Kz_ \&xb_)_%Q@R-JvSwA TVQЁzp{QdE3 7JPd G~%>S'J^SeRo>jj{Oew][yLO+ZF>Q5q)7>T)7:0~7gI]F@f> stream xڭylUw>v Xn9B"PK)P@"x QDJ4 Dpb ZBi{n۽w=7$?g^>F$b L33&J,Jɤr6ŰIԠ)qjx͘Է3wv*E1wb`oc rBa`j'x(@(yaz;B)gI: H0g;HbJZJ11<`16>$I/}RfHO E]E1xxIĢR!&p!aVOR$W_Kq)5kQt鱡0ۄQ2L,ܘMn>."{6?){PE~餫 v=PpY@QU2gs](=mԒ v(mE^ĸѮ5xnQZ䏶*:Ɇi.3˹@I)V$e -9Tw\Ei/QdS!3\.Xi!<[b"h=cbIveB@\9gtӟu=LV-aᑝ+ttS9F)7Y\s-6K{tuw9YeϖL5k}Š%*)_ՕjL$p` R,1L"S4ZR"?ǿs6 endstream endobj 47 0 obj 1867 endobj 48 0 obj << /Length 49 0 R /Type /XObject /Subtype /Image /Width 35 /Height 80 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Interpolate true /BitsPerComponent 8 /Filter /FlateDecode >> stream xڕkHSqM(@PVa@*򃄕_J "S'Aї **PD"1H(޻w2x|q=ιJfpd,&RF hYPV]q^\i1*@.hH,\2 CZ$U >0PgY慙#9\IFW" dNBM@PTHF2ڂ !pr8M6$ w 0Do6!Q'rJf8w6'qpo"ap ''p :L)JRc;2,CӗM\DD<ե$v"E~ xT"$8\5)‰^'H>J8S&A"dJOM,Te(IUH4$N?aVD;eOU8jki?̗ϾiTx{Cҙ}uZ&ҽ*լi=kE*WD@s~[]gh I` t|##XA1؞% sb@9GRN6Qiu _{H !x\|m % Зkx>`}" &vG%{ ժcϏ^Y4ȝq)> stream xڭ{lSum>v[6A* ,@DĈ A .(Y^d %D#&x:qm>v[ou6=-Rd L"R[H7iÏןz݌"RI&kq{¡9óBs|lpE3o" +w5"Sx*51B8 ( H>P@(Y$S{`ʻKM0յSE B~0> SɯA7PH J Oϗ)S|y1L%pa06Fx`q^c3Lj*N65(U7\rҟ͵@P:8QsQT֯0e.|,C΀ |; 7`T ^Ōlaa7EVأZ -[ᄩ'/Zgh2ՒPcd2-(KH6)SzQ~Ze'}&;fhyum?>!794_>d5 Vo35iWNh!? ~syPGVg>˽5^^P,;/ڞm-~d=^ߣyNۧr'/ʳ9Z̄cy]̹,vlsh$QXeLb"[=b p0-Cbhr{$hڴr=w\uLvWҗ]3ǀd=znzoxP,߹@Lk 4BE*&#[U2.hts1oz58/wCi/T5Dy>SjG@uĩn C۫mн\"<|5IQE$|!ou*e-jfy Val+PI7_ Hv?C=su$r,A5x4ۦ0yI"Pd[ơ./,2B x.It)I!3PQj<8Ż .g -EA6+L$`P5g^7&WC[ zT@KGmf6]\xXA aq,V^ps^+L< yo=KY_IpʴiV$:,ۓk)CJ.m1*_ s,S ΩʪTdSJEb'ޔdm w8ufᑶ숋Hf$Q8r\w I:Hs#߱}E0A%oؼXl#=`vհyh'q_[׀.BaiMlޤ|`VU^\ÀLӵ-xYOnMsmj{SD.$!js CS5b endstream endobj 53 0 obj 1608 endobj 54 0 obj << /Length 55 0 R /Type /XObject /Subtype /Image /Width 34 /Height 28 /ColorSpace /DeviceGray /Decode [ 0 1 ] /Interpolate true /BitsPerComponent 8 /Filter /FlateDecode >> stream xc`F^AQ!~n6&Q8fM;(cx>`W5IQ!Aa1IqF GC__޿ztf?VrT緯[lw֛8(N eqPN٫q{A .l/qxa V7U> stream xڽY{LSg}ܾ[jK F̘qQ,Lۜ)S1.nLѨa6 Cd1f EaNqCQ -A>--&;ޓ=9;s`Axa qis-_惷gUɄ<%`2M.]kMі"vdAo^/1(a1Un#Ahĭt aVAؓ2q ~ջQIQ)+; J gʜf 'p^h15.8M\#NuHv*IQA؎*Ȏșe"$Fŝ%"˜( ȡQHԺGDx%7KtK-6oPe r r2tC$yAPos`+b͂>n/UCpr7 Y[b0T,@l~:/%Pnb`'8M?rwtڋ"O  (y =%|:NPF 7*}7<oq}d)h]=]Q)0}޲uUuSQ)Wr[MFE`wSy:iG~g2ì;a9 )?aϩ* ༥'S5º]guGlv{qԷwV;)|0, "]Yݲ9^5P)o 8;f.͎MaсZ}Qyw̽܍vT}nu1PXef-˅mrěXP`ZP|+.0/ws&f 8$d'p/h\& F7 X$fJ -F2&R'G4r V{t_DV1YI,2r@VF |L*$M b𨤛$"覙~(zXWى5vKǥ ˤyPfٿ !Vf0,p08`)&5bڳ`bPKowf+,i}_'qPɐVl<.@7jl",n&tQ&%AP`Zg 9 g=!ݍgo@[i|Αf28SI72ކW.`}u=6Ҿ-J%43EN 4mUY_%񮍲 |鴉. 7{FZsXGZ!\JG&74:#<9K]dF"{= iGw<ٚ ~+UaH4"ΪimI : F$1\gs,g-b_piȢ3qɸuu!~.!o lpc#za_npm;G6!׍}mV#pܪҦG255gxyQ"&: endstream endobj 57 0 obj 1739 endobj 36 0 obj << /Type /PropertyList /Style << /Type /Style /Subtype /Shadow /Offset [ 3.5000002 -2.4999998 ] /Radius 1 /ColorSpace 5 0 R /Color [ 0 0 0 1 ] >> >> endobj 58 0 obj << /Length 59 0 R /N 3 /Alternate /DeviceRGB /Filter /FlateDecode >> stream x}OHQǿ%Be&RNW`oʶkξn%B.A1XI:b]"(73ڃ73{@](mzy(;>7PA+Xf$vlqd}䜛] UƬxiO:bM1Wg>q[ 2M'"()Y'ld4䗉2'&Sg^}8&w֚, \V:kݤ;iR;;\u?V\\C9u(JI]BSs_ QP5Fz׋G%t{3qWD0vz \}\$um+٬C;X9:Y^gB,\ACioci]g(L;z9AnI ꭰ4Iݠx#{zwAj}΅Q=8m (o{1cd5Ugҷtlaȱi"\.5汔^8tph0k!~D Thd6챖:>f&mxA4L&%kiĔ?Cqոm&/By#Ց%i'W:XlErr'=_ܗ)i7Ҭ,F|Nٮͯ6rm^ UHW5;?Ͱh endstream endobj 59 0 obj 706 endobj 5 0 obj [ /ICCBased 58 0 R ] endobj 35 0 obj << /Type /Pages /MediaBox [0 0 612 792] /Count 1 /Kids [ 1 0 R ] >> endobj 60 0 obj << /Type /Catalog /Pages 35 0 R /Version /1.4 >> endobj 61 0 obj << /Length 62 0 R /Length1 3704 /Filter /FlateDecode >> stream xW{PT?},‚]VdP@H+f,  TQ1$mfƍut'ձFKviLdjݻ {ι=w߹$\% :脠&ֵƽ#up |mMXU;Z}|㳠D榆m@h;G߀09!Qx* |5xwz>Now I"tiPl ,=" ]d:V?D, K2m莎:€9T t Ѡn\$QXUSKFI_~Og)u*>@8qSiHp ^cG L F~ƲLՔu]phtC}_렲sgheTQ’}dt%Oک pp2P(&Ȱ@bIh.)(T2!dY8z xVWꇽt҅|A[8|$DXc5Wj3P14 \/ϫqEeOEA^B'Ǥ7?4v>Z}ݻB\,&F냸b" aۘ!6("Q<$^FkV`$g x8̌0Z;9+?KWz K<&^JNnYi9r^=:W/,Hf BVZFpѡ 2pV`_)Fta'ՁbRl%Jeqܸ[:Ce- L`#:#I(;̠ҸW4Z?2LΔ FYwEMǻй_~wJ˺{׮ZU|@3ْ{+k^_8vgvbj~fCk^G5l!t 7"Y͍6Bb=g[D)mFё&9&)K6d~ꉪ{3'4O}ںkMO®KV}MWCD.! cO4B DA ^ sDL7777,d#XxBDBm^HϯeBN>_X(_3}@0`Vɂ<PT&R9#=mRе1Dr,'wm@Jo]^a0t9m1AZȺ!G;${/#kꅴ>7<`]3PH>y{'N ?^S{vhi?gu >3=yvN7B}b@@@:d,[iQ?:!խ๱À*JqHx!eٍMNd4F:zoNdoN|*42)7Yge;V9=ŷ9i҃_\N]LjO_jnz%q'ٶ&gyz|Rr%ܜGlio2YL=UU$ITg”f9J8 &.Xjfzv?qff^16&މs_%NoRqlijy7a JM L`S8! Pg+W&9xq-k141萘%S#˶D=bu|`v̇k6Nvtn|Y[yNcsN0`.vFoR4N6iHP+~KboPA٪n(? [X)^⥽EUEvSh⨺x]]ٵaiYӶ6si]QD.ZCkJ*S@T̤ɻYN! O<'R90FrNzB w!-Z.z5iJ$ODBu endstream endobj 62 0 obj 2270 endobj 63 0 obj << /Type /FontDescriptor /Ascent 1039 /CapHeight 761 /Descent -260 /Flags 32 /FontBBox [ -525 -289 1322 1057 ] /FontName /TKINWR+Futura-Medium /ItalicAngle 0 /StemV 0 /Leading 30 /MaxWidth 1359 /XHeight 483 /FontFile2 61 0 R >> endobj 64 0 obj [ 309 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 500 600 500 477 500 553 500 599 548 249 500 500 500 500 548 601 500 500 374 406 273 545 ] endobj 6 0 obj << /Type /Font /Subtype /TrueType /BaseFont /TKINWR+Futura-Medium /FontDescriptor 63 0 R /Widths 64 0 R /FirstChar 32 /LastChar 117 /Encoding /MacRomanEncoding >> endobj 65 0 obj << /CreationDate (D:20070803105859+02'00') /ModDate (D:20070803105859+02'00') /Producer (Mac OS X 10.4.10 Quartz PDFContext) >> endobj xref 0 66 0000000000 00000 n 0000003290 00000 n 0000000022 00000 n 0000003407 00000 n 0000003270 00000 n 0000036818 00000 n 0000039992 00000 n 0000021991 00000 n 0000022232 00000 n 0000017613 00000 n 0000020090 00000 n 0000023275 00000 n 0000023516 00000 n 0000010155 00000 n 0000013245 00000 n 0000022772 00000 n 0000023014 00000 n 0000013314 00000 n 0000016496 00000 n 0000021728 00000 n 0000021972 00000 n 0000003740 00000 n 0000006921 00000 n 0000022250 00000 n 0000022479 00000 n 0000020159 00000 n 0000021659 00000 n 0000023033 00000 n 0000023256 00000 n 0000016565 00000 n 0000017544 00000 n 0000022498 00000 n 0000022753 00000 n 0000006990 00000 n 0000010086 00000 n 0000036854 00000 n 0000035824 00000 n 0000006900 00000 n 0000010065 00000 n 0000013224 00000 n 0000016475 00000 n 0000017524 00000 n 0000020069 00000 n 0000021638 00000 n 0000026450 00000 n 0000028297 00000 n 0000028318 00000 n 0000030392 00000 n 0000030413 00000 n 0000031406 00000 n 0000023535 00000 n 0000026429 00000 n 0000031426 00000 n 0000033241 00000 n 0000033262 00000 n 0000033838 00000 n 0000033858 00000 n 0000035803 00000 n 0000035989 00000 n 0000036798 00000 n 0000036938 00000 n 0000037003 00000 n 0000039363 00000 n 0000039384 00000 n 0000039628 00000 n 0000040170 00000 n trailer << /Size 66 /Root 60 0 R /Info 65 0 R /ID [ <0cec5421eafdb6485b67d2a55cc41f62> <0cec5421eafdb6485b67d2a55cc41f62> ] >> startxref 40314 %%EOF Bitten-0.6/doc/logo.png000644 000765 000120 00000047444 10654567501 015317 0ustar00simonadmin000000 000000 PNG  IHDR*d $sBITOPLTEڹMOOJJJ.//ﹺVXX:::333!!!̱wyysssoqqkkk333ŭfffRRRBBB.//ڄoqq^``VXXRRRӵsss^``EGG:::ބsss^``^``.//EGGJJJӵ^``MOO333쵵믯꭭襥朜䔔⌌቉{{ssnnkk{{{ccwyyZZsssoqqRRkkkfffJJHHBB^``ZZZ::VXX33RRRMOO))JJJ((EGG BBB>@@:::333 .//)))!!!DtRNS""""""""""""""333333333333333DDDDDDDDDDDDDDDDUUUUUUUUUUUUffffffffffffwwwwwwwwwwwwwwww_MD pHYs  ~tEXtSoftwareMacromedia Fireworks 8hx@IDATxy`v&toɴIאMtI+nt4mS1钤bt_f&6Imעk?قۏwb"".J\EQ PEJ."of09D>{E`ߜĉw}OT6BuVCTZѡh&J퇚>G \c9$1mQEm ħbj` 61Gu /$Y[PH:'@IIgs$jՅG&H~l!$DbY`6 lT#BF(?e:[V~n XBRQYPoxk#0Q}H1SеU)29:p`~Q&S]ɸ:еYX敄ːi`,Ռ @& 2kÂ-u5rf"$`043O%P+LF ܚMENĬftgrcR &r@B" Yb)7Ξ\fg囆HԮ,$b60]6D r#[ qo Itͬ-rW%dB$T ˆq}=X؊wSJA=$VFɘFdSmZePkhZNmUQ-+;j#ȡ  M&ue]FA7B5AES33+FnhHH$G ZuDˉ0ȀkM PE1ecljf Z;AZ \7 Ce\x;u*ƍWK`=*08&+%M/+ XkWr&+qUOC6Ss N*Ei 0K'T"1F%Hؠ. g͂=GA#dY8Ĭ`ƞYj"˖4' ?4'@F8I"%L.;nS)F̀<7 U{ĄWK` e=CV(X|h~%mrr̦FC6nw"]S@_(:a9ƻq8أFd6Kt(aARL԰)ـ-b]i0k0V3oͨ+ Zl`oKE-1πErkVcø쌭!onCe,@, AH[ݽ.jp㝠R(wU Pik4d](b_A } [z –>l^>0_rԟwcV5 . 5 )_J!T5?>ؕTOW#҉2w@` ~?*tMal'I6Z c &c`$4:IoLu۰+"S8i>.khlnnjxߚ҅FN+P'baZ Jި$5Aqs#TТÑ?h.P0Be9Lp?N^HQ̔o45ިo軱xsީ odZcCMSaRl-qr7.Mi{ 5ѥc!).>~U;w)l73G _|L&Pc 2GOrn/+c;x lhk_d4pKv4S1Ը[GOrn/I-.+nT뭫̇Q#WkJi0=Fu^΃{%=O'="2a󨨡qW9 a}!2xcFlPwqZy/E0cCzD~woHh2u"ׂGBB!.; rð?[ܝvN֕Ԑohjwז* X=rza &GqԨcƄT_hOorQs_q5x]1IאqG0R#m4ːy{SsoH=E jLBA hFKfi>=a>C;0Qj-UoPkXXe}E6hu]csK NZW9jt̲ Ijw=X2+RKŨs"Qe;l!FCC}{R&h#XBKe^#o?BH#uP9}G;Cq\iܔ s5' vtL}7;?3ϟg#XB?E3[g:P.FEg'z]DuovwmWOYR؀ė:EJ{~O +G&"H-dU5J&8ot4Ѹ}bhOѹˇbVmB]cЋM,މ$5kN*{0@ BjHOq6Lȕ?$KXy}Q+X}c ݟ쐴G\E7BMPv᥸|YƤT'$$ѭ:̼r6ٕ6-!Ph4k: Dz/@ E={ ڤVSp[~%qbzȥb_X] 1٪ƒ>1q &Y6{'JWz2͘[51Xy)zHcѐB~pPp[~Pd./r7{dAMǸmL2\[uPh;9 _Y'e(w]YațR7j6'9]_tɓRffpս:aEa>`;^ⲙ]2}ri<.6~ fȤ-q9ZרRv)wq SzÜp.hsپ'l-Ȁ&4TW#K__/9C_F*rP_v:hJ 8MA_y;7nzj#a!.2aL߃nS5 w+h-G)Q,. /E>)?&dI?eF”)c}>KB'0Il ڻlp0RS :Wm/9[D-L 4 1/%{Y!r3jhϔrõX60y^E96?{0CC55Օ¨KNڔ}  2@QsƠ9e=y=f#pπ7'6v1lMnE5R̫k BԄ48wtЙH'ʑeI n虰h+Si.wMHd]47bZ>?K6Lf!\4;8|lPW7I!}QM I#QKYܺ˽ojxȕz&l/P(y5c-ƟW638B\zZ(_C0/+b0vv#s6nãuN3(Iw?ZI9n0<ApRʦS]S1L뎥5%MZ-Ѫ.]8{da8?{ Ni6yO)$o(gV r]4؋R{O4Ƽ% lϑU r)3]U,7ؓZ"dRiky#Ν;{UV֘~7hFq9}owkuK}6$7Ƅr?woYMۀW-H /W5剋c. I}=d\lvXUpxEE?tFt|5<-C>eEL$Ƅ<]:SUQLulG,$_h sb*\kK0Lj>)/ctAjxP)x#eMb!{O5+{;[ÛԕIءnC껺 *`AWue/w` Ʀt[]n@Z9 4Z. p q_Ca_M Lnk!EFI !'c{?tR3Jhܝvv>ؘ.\xSK;zM`6Ɔ/LZhJ礎om➘ ŨIJR]4U6ܡ-?],SJi9cYp-{-l衫c ar-&}xsuG?AwL%ԆiCM~ #R#E^0RLlŎ( `SSk#C]}(c !::u"" 3)h|APv?SQ*{VNȚrK7y0PUd ށ jxS)HNJZJ+^VPA8ʂs-~<5&aRAD7ɬrlo|pOFp֋J+!ZHzڠ=Ћ'Bl[y iMg>&7a!SQKAN7@^^KX76?ǝ&v8yVrz6@RCfAebqU#-J+SVUU:s OYD}ut7եAڕRbvډ58Lu-S^FXjPG1֠Pq٪,6(~EzD1~BG9m&L畍yrԮ|BfK˴9:)1ئfCRDk2jũᮢW0EKgB c{%vlM#уpT$lCJc K Jn}:mst՟Tsb& "}3ŨQepWѫC5URTt"ܹ!NoNqjTv, mL Z[X]Ɇqe!fg%kg%aޙpX3f6,.z3˸U _J9ǎPv12׃O  LD_᫢Ԑ5P{צJ lM=Waz#M/s&W/RCN 33|H&6!~Fb1nsL |uا( Y,ٌ/%/!yx/Q}w1hXFj&.5H踫Ra?$Fl1qR#_\x<݀9pءstHS( E ܃|lcGEH+.;vxKW)I5(jf`Q'j^tPnx:װa~"@LMŖ׍YQ~LO;ox6]o#3.m>4f^t`;;5IԨ/` gG EG^0)+d)㪙$yVBr˶Q͛n SX1]㴳Qkҡw`x g{W4ܻ3HD9vL7Ԑ.5%)2tث`U=և#O\ht)Y7 c O ‘5(FqrmRV԰vv0ؚr_S0UlY;B&; ?<ޫYњd{R0^J vċkWzzzpvuT- %X5MG^bK`IꏾOySq3߱(l Zs5gc*G±E„@wH]T<]2+ZK|ZKf7 xpڀ84 ;j}/B\0;Z}>{F@7 qp!s}3jt(b,D:;D:w$9:mbӸlu`͜knD54o3'+(]ԩv i,5(RhjP|an94q幝`ķNL]ҺY:S({4!78щVX: ZUGOR2/FDj:7kNv{L >~~ ޓl2qܬ N bcK 83v^"X}T鴌ͪyAfw=KzKfŐT;؍/s| bm6N!^re``WA&RF1T8_@hi79De'L帯2[*MJ( C`<*F`,R}H/ѡa8=F"hg(,pP濨aktթH՛H vpl!_ʪ޶lVEVbdzZR*LƂIDcFW+6qr[5X[6z5<)cMt 5`o5Rۛ4Rw<(qz71ʖ77S"֏ӵ_T|-ĹXl:9Zf)jzkt@9{C̟p:oܙ*ޑ_MZe+P)c8vῢgh%#O`O2A|g^ԫ=ZX#Hifr},PX$} 엵o c>Bk`ϖ@MIz$[]n%% puσUq1+On*K i;Z.33z I!<_*5{%݌2#kDC1B{rfm CP^B1.g-0"{1.bz%YkFe3mӚ}(OoKc>OAܔܡǦI I}ì kDlc7C?yF ؕZ*dVl6Yk+~5-SgL W ~B2hfti?|>q1Fh& psWjdJg1l@u+iB45BаLo²I7~BxmO"Ew辒#e;pg!7T6p~6evk;&AZ4/(C Lh(nٸ}5TGɴJ/j,xV6zU 1?G]|#'bJbnY Ulv+]q+d{Z6Iv+w* ^ 6@HAKcrw~Qx;?.6!`YFg|_$:lċ*>EҢX܄pV'I$Y-ca"Ux{ PզKKm+~>2YDorŦ^Œ#CE[b?"$aؓk'՝"fO2VdaeQ/0X~_BY` e+DyxZ?eK̓>f`Db'<:=F{PY1ʛ{y]/jK˭t RiÃ_~IuS(ž֥&/-йƆ}DST06+\B}^ک %عvʝV%r%&a*@M}߻qo6aR +,hI~_þcDzm"x~P OMc- 5w9G7*+g쾾1؊q_bot' ԂWӬ 8#sjƁ+xϲiUOXdԒ؉Fr5B;sТukN7#sd0Ğzu*S!ٴG ߑԄ;WCۥԆM"a_ @ެ+ސAgGw?M%/;dc%XI7*?l/~\2~]KM9?Et!˂L4)F4Bەՠ%Nb_ !pVUg~ypju ZCπ0-h stVީC-,[gve }d팠ݴ /jQPlxsPkpx.Y芪3ߚ=e&KA/Xi[@ 7m'Sɺ hEmU>Gf\0ٿ0(6^>g4Bč{•75TTVQmj#}&nDžW=z5PB,[y]6\$2 >U"kt j,4TDW'ECC-Gm{E+߀2hht6qQﴵ >z"pOT /w؞e4vzMY~[5P i_ʓPJv:},Iw^$2ג9#kT*vap =d7A;77CI5q U`k`< FS mO.yƞ*U;:w-"޳ОˏLC؃QaaݾaX#ɪ&6P$w> 9)U&$De*#'F5BXngUzKjd7PJCe$}Q< jl9՞ Atgcq:j&쩿tx%k'R{c҆) 5 w0?.,8πQvڅ 9|/9.8<31bƃNm (;:JC )VMӾTl(2)[JwC%r)3&oF[-LOOOi^G.YjD7@oрKd SӤ+֊3/{1})C;$8;/u~Uxf!A*:Mf8e?ʶ2Z|:rgNK*N_hlGlpt $ݟ6,;Smj8M6S0GⰢ@_AkG ep_3$nsh[UaONJ2i~q{1'">ygҸy'ZRh+$2|Gh~7H~eZ¿֢==c7oMvƍKƚ66A{P\jSsFK.^p̩EX?Dli'ۛ>:kڇ=0dM<܎;4ng*MܽSq2*iz[_Pq+=T ?{QHDC G35e-Cd݆ ՒѰ*k/UfyG jp=:K,A1 dFƠOmZ[X?=q5$1.v>#%͙s.^j5۩pGϿZ$Gok(E;zF\򦻐ʞy8+^֏P>.ϙs ɗ-Ѩ/=oЪSi-:w|/_<4{F;@N53ť/L}oFd~WDx'<ǼxK8yD !ނ^l}<羿'IOsHRqh+N=wI;w!sy^UgΝ:uɓN9{3ί﷉YXhf%N^﹂E5N2 R*vpE\d7=QqN.C7{~J-iǹM(\@&Uo\O-y!-[ZJ2ίCHirѥZN,gN9uO:82p&ao=JƐzDCl`Vgӏ?VplhhOg ,Mgܯ${yN`"5U-~<z?-:%(wFHbϾyו#F!3fz'2{/Xo~wg?P gj+T3e-{NjO:v3e%R!]5FEKTOɿH7cE=,`lx/"r9~_9K4&|QN<,L٥a-Q QL H"Fמ!#J2#Ń_%8sϐ+%Y 񢩶VhKoN6yykFCF6YUIKGfa)-, LI'x-6֭.Jf㺪i0֏J2EL" .GjfYϑh>o1kOK>˯LdjSGf%k<) G_Qcv -Her& 4Fb5~l~I{] !>R@KQ(\*$hl4ؤSۡB H &/!ڰ9l&b;Gט/VPmR#Sl:N1I/'^t>/;חJxhҦ'h3i$&^F\q[0K5z84_?=`P=ZӂL 6cB Ij(.[>OY;>6 pS[Vsj"6p0=9w[bͩ_c񋬍ݜx(Sj P#k5EISFR\X'>* cld@.WvMdp W)PC^~(ړlb3$j*3U%hͫރ0Kȫ{/>=!sgIIin]|n@\*ѻ﹉O ?+剁Yvob@ V\[8{xd|w[5FSE2/18>ԥPC|z[ yl2ѿ_5R#=z=$6Bj@.5Ϻ]OY WHg&%ZmRh `3dWThK|)׍I4ś@j<BA5fLI1g3rXMF yPfn߈=%wUjو-ɪfJjwS554{]7#^i Ԙ |RcY~go M ZVUVFyɼQH* .Vjht0Rc!+MIUcT(b)sQ/B''x-gYW!Wv^)+52u]xaa:5o*Wx&yQi"N3f(4 xIDAT ?nƪBZjLɛyH ^[*ڢdFim%ؖqEݼUC)VKF.S(Tw1j F^D.F[|^ :PCOIjN#{QDDdyuH 64{vE&*h wxϖ\ޕɈu#ZѝW~|Sh*.ι%^񙬞`m5+l>9d#![fG3͌ _dd{S\-#@z3H3z H&VYڒ,ݒOecz%a47Ck-5'XfV(3Nf>e+_i H)b=V础)ӳ}¤IJёa95l<ٻE1Su]n%"߈~Fdv9R yQ#?z W^ᬜC<jx q 0AC爢q8yvܙ)#$ɴ;S53eSf Yr"Na"嶶 ^*ɡoqi<ӛL(4I<%l̨߁584qZ1y@ୣKo,Om.JPCS'1L4b5\nd5d0;'CtP`ۣ1hn&Cg%b.8I vAJ!(O8Nx1D C  A%喇Y Q3ƒ6i%;p SAyoc]8 F*e_+Cuu$ MH-alu]\k8[뗶Dw9B7TH;ָfTDmY]a ueW&*lOMୠJˇy#WLV]Ń:!DD"Me)e+hKl@{P?jl0^\αbjҎm4q7߹Kxl EP,DQk5pE4 m$X\u82)H\Cx/!BsU~.'Ig<ȭMcc/qH\'|mZD f6')*ݴȮ&")ΰB&'+]˗HђY q@hp`9k5 nW[[pR 4["ls֜hRzE.04 [tҎ!ґj$վ0#08?+-Qz5$5bD$fX5%%jao>ocGq)MXeyE j$7 E5&@|jK+Y*?TQ +M`}.4. |9f淂ɍ0>LʇzC=,GE|&TPm!a`rfk.VF[X>t|ؠg"n\krk.D#34d=Ow0CG ުd-Q`*Dk-ԅLAn- z !a5D}aVi9p]H4XF !ĆX @A= l*HRFRTGL03Jm6h#k`k j#HϠ'0 r'(hu" IENDB`Bitten-0.6/doc/logo_small.png000644 000765 000120 00000013401 10654654766 016503 0ustar00simonadmin000000 000000 PNG  IHDRU2KsBITOPLTE ZZkkkRRR""߭BBB߄??.//^``kkǽwyyޓ)))..VXXֽ츸RRoqq絵FGGZZZuu彽ޜᵵ:::((II``;;{{؉ݦMOO̿挌 sss{{{))޽MMnnfff歭䥥啕dd䜜݋´JJJ333ss}}!!!RRBB::33{{㎎#)tRNS+~ pHYs  ~tEXtSoftwareMacromedia Fireworks 8hx]IDATx՛{Ⱦ{JCš閬hi՛ޭԪ#кlZ$3Z L34 M 5 7o}d6 i$2oW> 9mGchahҟ3 ,3JrЗzMx`B<{? m\d_kJ>RU5TfŒK ?Ǹ\uW܌C  vj# nEmj^Hz0 L+fxv}>w"7U5"4泤S =grspLG\mӾ߷~ 鐯 B:r]6G9fjxt>?/ I*MH4F.%U)|(Nr7]7uigXRf(84J$ [ޅOS̥Q4}\H`^ gu<ɟư\lHHj4Sn;GNƻ_ej_sФ:%00+6m9~3sr`wMM {9l*%;@- )ߩVOa0ϖnePcfJ\48La\vbsUok:$Q|FI5*.51ɼ5cYiƗ 7+,uNN5#t%:  SL7OD3ow;0ݑ=: Fu><1|7y(G=>{{^D|3`=<7ܶ&xfaVQ(,Ni񃜰Nm̍H9 %翭3UpP3S뼮oS` \mZ/٢WYV 1Vy[9f8V_mn72w0aJ^@k 3n(̓[Qiw"Hk;tD_[M1x hnr>=Z6rc0j?_4h_ ~nOo*?wޥͭp9fB`lBn6]ЍOd{}<m,]\ƢQ9Q3L:Fõ1~!oz-TrSۧ c0C`܎ 52aLa*<ˤ?DyJˇq57hs m Rhd7dOU^qN75; VO28܈`b&tds2A%s`d;s6 OU\h4ohqsUW\6eGƭG(?68[\#Ԧon{5|y?u8#s^=yvr Kgܟ7M|si$ 0u wCQ?.W%`z$e&Ѥ0Xv eNm%}=H Zm5L_jfCۆ%8l!: ttZhՄD+\k^v(5E =7x1l$neɝn氶A5;HT`܁{s/9aw Zfh=]Q &иـfjH BÅU% L6УXqoegbΌs1_4(gyr9'kdwð8QxjNӼ0/):$A&{]~t* ^EFMpwvtI(u9q.&)6솙+K (vbl!f}DלD z@fRmEl<~p*hMR͞uאx:8q3Pdo/a8K:ԶB\zwؓF-<le{:_\01=@ ŪIGv& e*HK6X6ALϹaTAsta]0 씎X#fDiilv`ٶNe.[|}S۽ L :pÎ& #S96s!%[.#f ͔y`Nnpb?0-nKptf!l[2<ԝ.ֶM}& &y`ovwh mhDUN ّre͎V36N ȥ L`fW3sέ|YaTysc6, \|#hUݭm•\ R؛ةvy]4 /~tV"o?ɻpy4X7'Wcw;qhkȢhysƵqFu:6Ј>B+6[6l:BDxYgf 13@},+}[B[ћ9zͬkޡ-8=e3MMG_w`ƶ|=Fr;}ݎenivSкa4qʷ9:UL4's>m11=Ff/v^n9,Gf;BGE y[6>c[UOm!aQDml#ft s:V-Ȓ٧u&HT\++% :JnٯTna85?Ğ2-%c]M]WvW3 nnKZiӡDlNdNDxm/nE,{KIy}Rƶ13iu<}z%S&~ms嘵#gІdqg{9hiH\퍁J_hTҽE7gqpug_E':M=,rQL}%6+:(ˍp):|Im+j7 -MeP~ҟ/L,>\]j>?Ѣ[Z+d;nHM9^ș@6=!i,䫉cX_4]hem]'*<Hc:]7$~h 87~a  䃑#,2;HeÁ)z`ȰcP0؂*6 j qnߡhaG 7&DFg)Uǩ8۹ϝͳd9PТs=#0gfrBg}Kp_v¶)I[vgOgix,0ND;:2u  Jb3deVbfG\\^ڵ vf TU|-f.0?-*)~2j[oݲԎd*=sHhŬ<4;im YlÙȎJgQMITJDJ6 (ء0$W|w[͐]Z]mHY"t>ʷ }f0L :f> "J0o5.vc&`jA)0RgPTHL40|E);O+ggdi6UB&lM2ĝax!=0g)ϮjL g/IT5Kiӿ R?p1jLX78|NfǗ#%϶ WCӯ뮴m`?*4}mIENDB`Bitten-0.6/doc/notify.html000644 000765 000120 00000010564 11536424527 016040 0ustar00simonadmin000000 000000 Bitten: Build Notification

Build Notification

Introduction

Bitten includes a mechanism to send out notification mails whenever a build has failed (or succeeded, if required).

The notification mails contain the revision number of the build, the author name and the build log.

Notification Example:

Failed build of MyProject [32]
---------------------------------------------------------------------

Changeset:             32 - <http://trac.mydomain.com/changeset/32>
Committed by:          author

Build Configuration:   main
Build Slave:           client
Build Number:          30 - <http://trac.mydomain.com/build/main/30>

Failed Steps:
Failure Log:

Configuration

The mechanism employs the trac notification system and uses most of its options of the [notification] section of trac.ini, specifically:

Option Description
smtp_enabled activates mail notifications
smtp_default_domain the domain to be appended to not fully qualified usernames
smtp_server the smtp server to use
smtp_user smtp server username
smtp_password smtp server password

For an indepth description of the trac notification system and its options please refer to the Trac documentation.

To further adjust the notification behaviour you can use the following options:

Option Description
notify_on_failed_build notifies on failed builds (defaults to True)
notify_on_successful_build notifies on successful builds (defaults to False)

Configuration Example:

[notification]
smtp_enabled = true
smtp_default_domain = mydomain.com
smtp_server = smtp.mydomain.com
smtp_user = admin@mydomain.com
smtp_password = 12345
notify_on_failed_build = true
notify_on_successful_build = true
Bitten-0.6/doc/notify.txt000644 000765 000120 00000006436 11250332721 015701 0ustar00simonadmin000000 000000 .. -*- mode: rst; encoding: utf-8 -*- ================== Build Notification ================== Introduction ============ Bitten includes a mechanism to send out notification mails whenever a build has failed (or succeeded, if required). The notification mails contain the revision number of the build, the author name and the build log. Notification Example: .. code-block:: text Failed build of MyProject [32] --------------------------------------------------------------------- Changeset: 32 - Committed by: author Build Configuration: main Build Slave: client Build Number: 30 - Failed Steps: Failure Log: Configuration ============= The mechanism employs the trac notification system and uses most of its options of the ``[notification]`` section of ``trac.ini``, specifically: +-------------------------+--------------------------------------------------+ | Option | Description | +=========================+==================================================+ | ``smtp_enabled`` | activates mail notifications | +-------------------------+--------------------------------------------------+ | ``smtp_default_domain`` | the domain to be appended to not fully qualified | | | usernames | +-------------------------+--------------------------------------------------+ | ``smtp_server`` | the smtp server to use | +-------------------------+--------------------------------------------------+ | ``smtp_user`` | smtp server username | +-------------------------+--------------------------------------------------+ | ``smtp_password`` | smtp server password | +-------------------------+--------------------------------------------------+ For an indepth description of the trac notification system and its options please refer to the `Trac documentation`_. .. _`trac documentation`: http://trac.edgewall.org/wiki/TracNotification To further adjust the notification behaviour you can use the following options: +--------------------------------+-------------------------------------------+ | Option | Description | +================================+===========================================+ | ``notify_on_failed_build`` | notifies on failed builds (defaults to | | | ``True``) | +--------------------------------+-------------------------------------------+ | ``notify_on_successful_build`` | notifies on successful builds (defaults | | | to ``False``) | +--------------------------------+-------------------------------------------+ Configuration Example: .. code-block:: ini [notification] smtp_enabled = true smtp_default_domain = mydomain.com smtp_server = smtp.mydomain.com smtp_user = admin@mydomain.com smtp_password = 12345 notify_on_failed_build = true notify_on_successful_build = true Bitten-0.6/doc/recipes.html000644 000765 000120 00000023750 11536424527 016163 0ustar00simonadmin000000 000000 Bitten: Build Recipes

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.

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.

The <build> element can optionally have an onerror attribute that dictates how a build should proceed after the failure of a step. Allowable values are:

  • fail: failure of a step causes the build to terminate. (default)
  • continue: builds continue after step failures. Failing steps contribute to the overall build status.
  • ignore: builds continue after step failures. Builds are marked as successful even in the presence of failed steps with onerror='ignore'

<step> elements can override the <build> onerror attribute with their own onerror attributes.

Commonly, the first step of any build recipe will perform the checkout from the repository.

<build xmlns:python="http://bitten.edgewall.org/tools/python"
       xmlns:svn="http://bitten.edgewall.org/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.

Recipes may contain variables, for example ${path}, which are expanded before the recipe is executed. A small set of variables is pre-defined but custom variables may be added (see Slave Configuration for further instructions). The pre-defined recipe variables are:

Variable name Expanded value
${path} Repository path from the build configuration
${config} The build configuration name
${build} The index of this build request
${revision} The repository revision being tested
${platform} The name of the target platform being built
${name} The name of the build slave
${basedir} The absolute path of the build location, joining work-dir (absolute) with build-dir (relative)

As the recipe needs to be valid XML, any reserved characters in attributes must be quoted using regular XML entities:

Character Quoted
" &quot;
< &lt;
> &gt;
& &amp;
' &apos;

If needed, most commands use regular shell rules to split parts of the input - typically like args input for sh:exec command. Double-quotes (&quot;) can be used to mark the start and end if any sub-parts contain whitespace, alternatively '\' can be used to escape whitespace or any other character that carries meaning as part of input - including double-quotes and backslash itself:

<sh:exec file="echo" args="o\\ne &quot;4 2&quot; \&quot;hi\ there\&quot;"/>

This will pass 3 arguments: o\ne + 4 2 + "hi there".

Note: On Windows, batch scripts and built-ins will execute through a shell. This may affect quoting of arguments.

Bitten-0.6/doc/recipes.txt000644 000765 000120 00000013530 11365331154 016022 0ustar00simonadmin000000 000000 .. -*- 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. File Format =========== Build recipes are stored internally in an XML-based format. Recipe documents have a single ```` root element with one or more ```` child elements. The steps are executed in the order they appear in the recipe. A ```` element will consist of any number of commands and reports. Most of these elements are declared in XML namespaces, where the namespace URI defines a collection of related commands. The ```` element can optionally have an ``onerror`` attribute that dictates how a build should proceed after the failure of a step. Allowable values are: - ``fail``: failure of a step causes the build to terminate. (default) - ``continue``: builds continue after step failures. Failing steps contribute to the overall build status. - ``ignore``: builds continue after step failures. Builds are marked as successful even in the presence of failed steps with onerror='ignore' ```` elements can override the ```` ``onerror`` attribute with their own ``onerror`` attributes. Commonly, the first step of any build recipe will perform the checkout from the repository. .. code-block:: xml See `Build Recipe Commands`_ for a comprehensive reference of the commands available by default. .. _`build recipe commands`: commands.html Recipes may contain variables, for example ``${path}``, which are expanded before the recipe is executed. A small set of variables is pre-defined but custom variables may be added (see `Slave Configuration`_ for further instructions). The pre-defined recipe variables are: .. _`slave configuration`: configure.html +-----------------+----------------------------------------------------------+ | Variable name | Expanded value | +=================+==========================================================+ | ``${path}`` | Repository path from the build configuration | +-----------------+----------------------------------------------------------+ | ``${config}`` | The build configuration name | +-----------------+----------------------------------------------------------+ | ``${build}`` | The index of this build request | +-----------------+----------------------------------------------------------+ | ``${revision}`` | The repository revision being tested | +-----------------+----------------------------------------------------------+ | ``${platform}`` | The name of the target platform being built | +-----------------+----------------------------------------------------------+ | ``${name}`` | The name of the build slave | +-----------------+----------------------------------------------------------+ | ``${basedir}`` | The absolute path of the build location, joining | | | ``work-dir`` (absolute) with ``build-dir`` (relative) | +-----------------+----------------------------------------------------------+ As the recipe needs to be valid XML, any reserved characters in attributes must be quoted using regular XML entities: +-----------+------------+ | Character | Quoted | +===========+============+ | ``"`` | ``"`` | +-----------+------------+ | ``<`` | ``<`` | +-----------+------------+ | ``>`` | ``>`` | +-----------+------------+ | ``&`` | ``&`` | +-----------+------------+ | ``'`` | ``'`` | +-----------+------------+ If needed, most commands use regular shell rules to split parts of the input - typically like ``args`` input for ``sh:exec`` command. Double-quotes (``"``) can be used to mark the start and end if any sub-parts contain whitespace, alternatively ``'\'`` can be used to escape whitespace or any other character that carries meaning as part of input - including double-quotes and backslash itself: .. code-block:: xml This will pass 3 arguments: ``o\ne`` + ``4 2`` + ``"hi there"``. **Note:** On Windows, batch scripts and built-ins will execute through a shell. This may affect quoting of arguments. Bitten-0.6/doc/reports.html000644 000765 000120 00000020264 11536424527 016224 0ustar00simonadmin000000 000000 Bitten: Report Formats

Report Formats

The base element of the report must be "report" and have an attribute "category" that is one of "test", "coverage" or "lint":

<report category="test|coverage|lint">
</report>

Inside the report there must be elements for each report type. The way the data is captured is pretty flexible because it can either be in attributes or in child elements.

Test Reports

Test reports must have sub-elements of report of type <test />. These elements can have any of these attributes (or subelements with contained cdata):

Attribute Description
duration Duration of test (float)
status "success", "failure", "error", or "ignore" (string)
name Name of the test (string)
fixture Name of the test fixture (string)
file Path to test file relative to the base path for the build configuration (string)
stdout The output from the test (string)
traceback The traceback from any error or failure (string)

Example:

<report category="test">
   <test duration="0.073"
         status="success"
         fixture="bitten.tests.model.BuildConfigTestCase"
         name="test_config_update_name"
         file="bitten/tests/model.py"
         stdout="Renaming build configuration"
         traceback="None">
   </test>
   <test>
     <duration>0.073</duration>
     <status>success</status>
     <fixture>bitten.tests.model.BuildConfigTestCase</fixture>
     <name>test_config_update_name</name>
     <file>bitten/tests/model.py</file>
     <stdout>Renaming build configuration</stdout>
   </test>
</report>

Coverage Reports

Coverage reports must be inside <coverage /> elements. The allowable attributes are:

Attribute Description
name The name of the module being tested for coverage
file The name of the file relative to the base path in the build configuration
percentage The percentage of lines in that file covered
lines The number of lines covered
line_hits Line-by-line coverage of the file, where code lines have 0 or more times covered and non-code lines are marked as '-' (optional)

Example:

<report category="coverage">
   <coverage name="my_module"
             file="my_module.py"
             percentage="75"
             lines="4"
             line_hits="2 0 - 1 1">
    </coverage>
</report>

Lint Reports

Lint issues are placed inside <problem /> elements, with allowed attributes of:

Attribute Description
file The name of the file relative to the base path in the build configuration
tag Class, method or other useful identifiable location inside the file
line Line number
category Category for problem; convention \| warning \| refactor \| error

Example:

<report category="lint">
   <problem category="convention"
            line="17"
            tag="TestResultsChartGenerator"
            file="bitten/report/testing.py">
        Missing docstring
    </problem>
</report>
Bitten-0.6/doc/reports.txt000644 000765 000120 00000013451 11444363330 016067 0ustar00simonadmin000000 000000 .. -*- mode: rst; encoding: utf-8 -*- ============== Report Formats ============== The base element of the report must be "report" and have an attribute "category" that is one of "test", "coverage" or "lint": .. code-block:: xml Inside the report there must be elements for each report type. The way the data is captured is pretty flexible because it can either be in attributes or in child elements. Test Reports ============ Test reports must have sub-elements of report of type ````. These elements can have any of these attributes (or subelements with contained cdata): +-----------------+----------------------------------------------------------+ | Attribute | Description | +=================+==========================================================+ | ``duration`` | Duration of test (float) | +-----------------+----------------------------------------------------------+ | ``status`` | "success", "failure", "error", or "ignore" (string) | +-----------------+----------------------------------------------------------+ | ``name`` | Name of the test (string) | +-----------------+----------------------------------------------------------+ | ``fixture`` | Name of the test fixture (string) | +-----------------+----------------------------------------------------------+ | ``file`` | Path to test file relative to the base path for the | | | build configuration (string) | +-----------------+----------------------------------------------------------+ | ``stdout`` | The output from the test (string) | +-----------------+----------------------------------------------------------+ | ``traceback`` | The traceback from any error or failure (string) | +-----------------+----------------------------------------------------------+ Example: .. code-block:: xml 0.073 success bitten.tests.model.BuildConfigTestCase test_config_update_name bitten/tests/model.py Renaming build configuration Coverage Reports ================ Coverage reports must be inside ```` elements. The allowable attributes are: +-----------------+----------------------------------------------------------+ | Attribute | Description | +=================+==========================================================+ | ``name`` | The name of the module being tested for coverage | +-----------------+----------------------------------------------------------+ | ``file`` | The name of the file relative to the base path in the | | | build configuration | +-----------------+----------------------------------------------------------+ | ``percentage`` | The percentage of lines in that file covered | +-----------------+----------------------------------------------------------+ | ``lines`` | The number of lines covered | +-----------------+----------------------------------------------------------+ | ``line_hits`` | Line-by-line coverage of the file, where code lines have | | | 0 or more times covered and non-code lines are marked | | | as `'-'` (optional) | +-----------------+----------------------------------------------------------+ Example: .. code-block:: xml Lint Reports ============ Lint issues are placed inside ```` elements, with allowed attributes of: +-----------------+----------------------------------------------------------+ | Attribute | Description | +=================+==========================================================+ | ``file`` | The name of the file relative to the base path in the | | | build configuration | +-----------------+----------------------------------------------------------+ | ``tag`` | Class, method or other useful identifiable location | | | inside the file | +-----------------+----------------------------------------------------------+ | ``line`` | Line number | +-----------------+----------------------------------------------------------+ | ``category`` | Category for problem; | | | ``convention \| warning \| refactor \| error`` | +-----------------+----------------------------------------------------------+ Example: .. code-block:: xml Missing docstring Bitten-0.6/doc/upgrade.html000644 000765 000120 00000020110 11536424527 016143 0ustar00simonadmin000000 000000 Bitten: Upgrading

Upgrading

Bitten's database schema is versioned using the bitten_version entry in Trac's system table. If you install a version of Bitten with a more recent schema you will need to run trac-admin update after installing Bitten to update your database. Until this done Trac will not activate your new Bitten installation.

Note

You should back up your Trac database and Trac environment before running trac-admin upgrade.

Note

If you are using a SQLite database file, Trac automatically creates a copy before proceeding with the update. This should not be relied upon but can be useful if you have ignored the advice above.

Bitten's upgrade scripts attempt to commit changes made to the database each time a new schema version is reached. This ensures that if an upgrade fails to complete the Bitten database is left in the most up-to-date state possible.

For example, if you were upgrading from Bitten schema 6 to schema version 12 and duplicate builds were found during the attempt to upgrade to schema 10, your database would be left at schema 9.

1   Removing Duplicate Builds

Bitten schema version 10 adds a unique index on the columns (config, platform, rev) to the bitten_build table. This constraint is relied on by Bitten but was not enforced in earlier versions and duplicate builds could arise. Before the upgrade to schema version 10 can complete, these duplicate builds must be removed. The trac-admin upgrade will produce a list of duplicates but the decision on which to keep remains with the user.

A script for removing builds is provided. This scripts supports schema versions 9 through 12.

2   Reporting Upgrade Issues

Debugging upgrade issues can be difficult since the Bitten development team does not have access to your database. Attaching a description of your database schema before and after running trac-admin upgrade to your request for assistance may help others diagnose the problem more swiftly.

You can create such a schema description using Gerald or a similar tool.

3   Upgrade history

The current version of the Bitten schema is version 12.

3.1   Version 12

  • last activity column added to bitten_build.

3.2   Version 11

  • Badly named log level files that ended with .log.level created by earlier versions of Bitten were renamed to end in .log.levels which is the correct extension.
  • Log level files that had not been correctly deleted by earlier versions of Bitten were cleaned up.

3.3   Version 10

  • PostgreSQL sequences on the id column that had not been correctly updated by previous Bitten upgrade scripts were corrected. This affected four tables: bitten_build, bitten_log, bitten_platform and bitten_report.
  • A unique index on the columns (config, platform, rev) was added to the bitten_build table and an old unique index on (config, rev, slave) was removed.

Note

The lack of an index on (config, platform, rev) in ealier versions of Bitten allowed duplicate builds to be created in the database under some conditions. These duplicate builds need to be manually removed before the upgrade to version 10 can complete.

3.4   Version 9

  • The id column of the bitten_rule table was converted from a text column to an integer one.

3.5   Version 8

  • Log messages that had been stored in the bitten_log_messages table were migrated to files.
  • A filename field was added to the bitten_log table to record the location of the new log files.

3.6   Version 7

  • The bitten_error table as added to record the reasons for step failures.

3.7   Version 6

  • Filenames stored in the value column of bitten_report_item where normalized to use / as the path separator.
  • The generator columns in bitten_log and bitten_report previously stored just the short tag name of the associated generator. These were updated to store the full tag name of the generator (including the XML namespace). For example, pipe was changed to http://bitten.edgewall.org/tools/sh#pipe.

3.8   Version 5

  • An orderno column was added to the bitten_log table to record the order in which sets of log messages were generated.
  • The bitten_report and bitten_report_item tables were added to record reports generated by unit tests and other build tools.
  • Report data was imported from the old Berkeley DB XML store into the new database tables.

3.9   Version 4

  • The build configuration that generated a report was added to the report metadata in the Berkeley DB XML store.

3.10   Version 3

  • The recipe column was added to bitten_config to store the recipe XML.

3.11   Version 2

  • The bitten_log and bitten_log_message tables were created for storing log messages.
  • The log column was removed from bitten_step and the log messages where migrated to the two new logging tables.

3.12   Version 1

  • The first Bitten database schema.
Bitten-0.6/doc/upgrade.txt000644 000765 000120 00000012271 11457032362 016021 0ustar00simonadmin000000 000000 .. -*- mode: rst; encoding: utf-8 -*- ========= Upgrading ========= .. contents:: Contents :depth: 1 .. sectnum:: Bitten's database schema is versioned using the `bitten_version` entry in Trac's system table. If you install a version of Bitten with a more recent schema you will need to run `trac-admin update` after installing Bitten to update your database. Until this done Trac will not activate your new Bitten installation. .. note:: You should back up your Trac database and Trac environment before running `trac-admin upgrade`. .. note:: If you are using a SQLite database file, Trac automatically creates a copy before proceeding with the update. This should not be relied upon but can be useful if you have ignored the advice above. Bitten's upgrade scripts attempt to commit changes made to the database each time a new schema version is reached. This ensures that if an upgrade fails to complete the Bitten database is left in the most up-to-date state possible. For example, if you were upgrading from Bitten schema 6 to schema version 12 and duplicate builds were found during the attempt to upgrade to schema 10, your database would be left at schema 9. Removing Duplicate Builds ========================= Bitten schema version 10 adds a unique index on the columns `(config, platform, rev)` to the `bitten_build` table. This constraint is relied on by Bitten but was not enforced in earlier versions and duplicate builds could arise. Before the upgrade to schema version 10 can complete, these duplicate builds must be removed. The `trac-admin upgrade` will produce a list of duplicates but the decision on which to keep remains with the user. A script_ for removing builds is provided. This scripts supports schema versions 9 through 12. .. _script: http://bitten.edgewall.org/browser/trunk/contrib/deletebuild.py Reporting Upgrade Issues ======================== Debugging upgrade issues can be difficult since the Bitten development team does not have access to your database. Attaching a description of your database schema before and after running `trac-admin upgrade` to your request for assistance may help others diagnose the problem more swiftly. You can create such a schema description using Gerald_ or a similar tool. .. _gerald: http://pypi.python.org/pypi/gerald Upgrade history =============== The current version of the Bitten schema is version 12. Version 12 ---------- * `last activity` column added to `bitten_build`. Version 11 ---------- * Badly named log level files that ended with `.log.level` created by earlier versions of Bitten were renamed to end in `.log.levels` which is the correct extension. * Log level files that had not been correctly deleted by earlier versions of Bitten were cleaned up. Version 10 ---------- * PostgreSQL sequences on the `id` column that had not been correctly updated by previous Bitten upgrade scripts were corrected. This affected four tables: `bitten_build`, `bitten_log`, `bitten_platform` and `bitten_report`. * A unique index on the columns `(config, platform, rev)` was added to the `bitten_build` table and an old unique index on `(config, rev, slave)` was removed. .. note:: The lack of an index on `(config, platform, rev)` in ealier versions of Bitten allowed duplicate builds to be created in the database under some conditions. These duplicate builds need to be manually removed before the upgrade to version 10 can complete. Version 9 --------- * The `id` column of the `bitten_rule` table was converted from a text column to an integer one. Version 8 --------- * Log messages that had been stored in the `bitten_log_messages` table were migrated to files. * A `filename` field was added to the `bitten_log` table to record the location of the new log files. Version 7 --------- * The `bitten_error` table as added to record the reasons for step failures. Version 6 --------- * Filenames stored in the `value` column of `bitten_report_item` where normalized to use `/` as the path separator. * The `generator` columns in `bitten_log` and `bitten_report` previously stored just the short tag name of the associated generator. These were updated to store the full tag name of the generator (including the XML namespace). For example, `pipe` was changed to `http://bitten.edgewall.org/tools/sh#pipe`. Version 5 --------- * An `orderno` column was added to the `bitten_log` table to record the order in which sets of log messages were generated. * The `bitten_report` and `bitten_report_item` tables were added to record reports generated by unit tests and other build tools. * Report data was imported from the old Berkeley DB XML store into the new database tables. Version 4 --------- * The build configuration that generated a report was added to the report metadata in the Berkeley DB XML store. Version 3 --------- * The `recipe` column was added to `bitten_config` to store the recipe XML. Version 2 --------- * The `bitten_log` and `bitten_log_message` tables were created for storing log messages. * The `log` column was removed from `bitten_step` and the log messages where migrated to the two new logging tables. Version 1 --------- * The first Bitten database schema. Bitten-0.6/doc/common/conf/000755 000765 000120 00000000000 11536424600 016041 5ustar00simonadmin000000 000000 Bitten-0.6/doc/common/COPYING000644 000765 000120 00000002604 10656055446 016163 0ustar00simonadmin000000 000000 Copyright (C) 2007 Edgewall Software 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. Bitten-0.6/doc/common/doctools.py000644 000765 000120 00000010670 11473774533 017335 0ustar00simonadmin000000 000000 # -*- 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://babel.edgewall.org/wiki/License. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://babel.edgewall.org/log/. from distutils.cmd import Command import doctest from glob import glob import os import sys TOOLS_DIR = os.path.dirname(__file__) class build_doc(Command): description = 'generate the documentation' user_options = [ ('force', None, "force regeneration even if no reStructuredText files have changed"), ('without-apidocs', None, "whether to skip the generation of API documentaton"), ] boolean_options = ['force', 'without-apidocs'] def initialize_options(self): self.force = False self.without_apidocs = False def finalize_options(self): pass def run(self): from docutils.core import publish_cmdline from docutils.nodes import raw from docutils.parsers import rst from genshi.input import HTMLParser from genshi.template import TemplateLoader docutils_conf = os.path.join(TOOLS_DIR, 'conf', 'docutils.ini') epydoc_conf = os.path.join(TOOLS_DIR, 'conf', 'epydoc.ini') try: from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter def code_block(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): lexer = get_lexer_by_name(arguments[0]) html = highlight('\n'.join(content), lexer, HtmlFormatter()) return [raw('', html, format='html')] code_block.arguments = (1, 0, 0) code_block.options = {'language' : rst.directives.unchanged} code_block.content = 1 rst.directives.register_directive('code-block', code_block) except ImportError: print('Pygments not installed, syntax highlighting disabled') loader = TemplateLoader(['doc', 'doc/common'], variable_lookup='strict') for source in glob('doc/*.txt'): dest = os.path.splitext(source)[0] + '.html' if self.force or not os.path.exists(dest) or \ os.path.getmtime(dest) < os.path.getmtime(source): print('building documentation file %s' % dest) publish_cmdline(writer_name='html', argv=['--config=%s' % docutils_conf, source, dest]) fileobj = open(dest) try: html = HTMLParser(fileobj, encoding='utf-8') template = loader.load('template.html') output = template.generate( html=html, project=self.distribution ).render('html', encoding='utf-8') finally: fileobj.close() fileobj = open(dest, 'w') try: fileobj.write(output) finally: fileobj.close() if not self.without_apidocs: try: from epydoc import cli old_argv = sys.argv[1:] sys.argv[1:] = [ '--config=%s' % epydoc_conf, '--top=%s' % self.distribution.packages[0], '--no-private', # epydoc bug, not read from config '--simple-term', '--verbose' ] + self.distribution.packages cli.cli() sys.argv[1:] = old_argv except ImportError: print('epydoc not installed, skipping API documentation.') class test_doc(Command): description = 'test the code examples in the documentation' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): for filename in glob('doc/*.txt'): print('testing documentation file %s' % filename) doctest.testfile(filename, False, optionflags=doctest.ELLIPSIS) Bitten-0.6/doc/common/README.txt000644 000765 000120 00000000467 10656055446 016633 0ustar00simonadmin000000 000000 Edgewall Documentation Utilities ================================ This repository contains distutils commands for generating offline documentation from reStructuredText files and Python source code. These tools are shared among a couple of different Edgewall projects to ensure common style and functionality. Bitten-0.6/doc/common/style/000755 000765 000120 00000000000 11536424600 016254 5ustar00simonadmin000000 000000 Bitten-0.6/doc/common/template.html000644 000765 000120 00000001221 10661136320 017606 0ustar00simonadmin000000 000000 ${project.get_name()}: ${select('text()')}
${select('*|text()')}
$html Bitten-0.6/doc/common/style/bkgnd_pattern.png000644 000765 000120 00000000160 10656055446 021613 0ustar00simonadmin000000 000000 PNG  IHDR,XPLTEKMMUWWcg%IDATcq`X(BwZGA cҿj<IENDB`Bitten-0.6/doc/common/style/docutils.css000644 000765 000120 00000012536 10656055446 020635 0ustar00simonadmin000000 000000 /* :Author: David Goodger :Contact: goodger@users.sourceforge.net :Date: $Date: 2005-12-18 01:56:14 +0100 (Sun, 18 Dec 2005) $ :Revision: $Revision: 4224 $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to customize this style sheet. */ /* used to remove borders from tables and images */ .borderless, table.borderless td, table.borderless th { border: 0 } table.borderless td, table.borderless th { /* Override padding for "table.docutils td" with "! important". The right padding separates the table cells. */ padding: 0 0.5em 0 0 ! important } .first { /* Override more specific margin styles with "! important". */ margin-top: 0 ! important } .last, .with-subtitle { margin-bottom: 0 ! important } .hidden { display: none } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dl.docutils dd { margin-bottom: 0.5em } dl.docutils dt { font-weight: bold } div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: medium outset ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: red ; font-weight: bold ; font-family: sans-serif } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. div.compound .compound-first, div.compound .compound-middle { margin-bottom: 0.5em } div.compound .compound-last, div.compound .compound-middle { margin-top: 0.5em } */ div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em ; margin-right: 2em } div.footer, div.header { clear: both; font-size: smaller } div.line-block { display: block ; margin-top: 1em ; margin-bottom: 1em } div.line-block div.line-block { margin-top: 0 ; margin-bottom: 0 ; margin-left: 1.5em } div.sidebar { margin-left: 1em ; border: medium outset ; padding: 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { margin-top: 0.4em } h1.title { text-align: center } h2.subtitle { text-align: center } hr.docutils { width: 75% } img.align-left { clear: left } img.align-right { clear: right } ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font-family: serif ; font-size: 100% } pre.literal-block, pre.doctest-block { margin-left: 2em ; margin-right: 2em ; background-color: #eeeeee } span.classifier { font-family: sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: sans-serif ; font-weight: bold } span.interpreted { font-family: sans-serif } span.option { white-space: nowrap } span.pre { white-space: pre } span.problematic { color: red } span.section-subtitle { /* font-size relative to parent (h1..h6 element) */ font-size: 80% } table.citation { border-left: solid 1px gray; margin-left: 1px } table.docinfo { margin: 2em 4em } table.docutils { margin-top: 0.5em ; margin-bottom: 0.5em } table.footnote { border-left: solid 1px black; margin-left: 1px } table.docutils td, table.docutils th, table.docinfo td, table.docinfo th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } table.docutils th.field-name, table.docinfo th.docinfo-name { font-weight: bold ; text-align: left ; white-space: nowrap ; padding-left: 0 } h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { font-size: 100% } tt.docutils { background-color: #eeeeee } ul.auto-toc { list-style-type: none } Bitten-0.6/doc/common/style/edgewall.css000644 000765 000120 00000007775 10660074300 020565 0ustar00simonadmin000000 000000 @import url(docutils.css); @import url(pygments.css); html, body { height: 100%; margin: 0; padding: 0; } body { background: #4b4d4d url(bkgnd_pattern.png); color: #000; } body, th, td { font: normal small Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; } pre, code, tt { font-size: medium; } h1, h2, h3, h4 { border-bottom: 1px solid #ccc; font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; font-weight: bold; letter-spacing: -0.018em; } h1 { font-size: 19px; margin: 2em 0 .5em; } h2 { font-size: 16px; margin: 1.5em 0 .5em; } h3 { color: #333; font-size: 14px; margin: 1.2em 0 .5em; } hr { border: none; border-top: 1px solid #ccb; margin: 2em 0; } p { margin: 0 0 1em; } table { border: 1px solid #999; border-width: 0 1px 0 0; border-collapse: separate; border-spacing: 0; } table thead th { background: #999; border: 1px solid #999;; color: #fff; font-weight: bold; } table td { border: 1px solid #ccc; border-width: 0 0 1px 1px; padding: .3em; } :link, :visited { text-decoration: none; border-bottom: 1px dotted #bbb; color: #b00; } :link:hover, :visited:hover { background-color: #eee; color: #555; } :link img, :visited img { border: none } h1 :link, h1 :visited ,h2 :link, h2 :visited, h3 :link, h3 :visited, h4 :link, h4 :visited, h5 :link, h5 :visited, h6 :link, h6 :visited { color: #000; } #navigation { background: #b00; color: #fff; font-size: 90%; margin: 0 -20px; padding: .2em .5em; text-align: right; } #navigation :link, #navigation :visited { border: none; color: #fff; } #navigation :link:hover, #navigation :visited:hover { background: #fff; color: #b00; } #navigation span.projinfo { float: left; } #footer { border-top: 1px solid #d7d7d7; color: #666; font-size: 90%; margin: 8em -20px 0; padding: .5em 0 2em; text-align: center; } #footer :link, #footer :visited { color: #666; } div.document { background: #fff url(shadow.gif) right top repeat-y; border-left: 1px solid #000; margin: 0 auto 0 40px; min-height: 100%; padding: 0 180px 1px 20px; width: 700px; } div.contents { font-size: 90%; position: absolute; position: fixed; margin-left: 81px; left: 60em; top: 30px; right: 0; bottom: 0; overflow: auto; overflow-x: hidden; } div.contents .topic-title { display: none; } div.contents ul { list-style: none; padding-left: 0; } div.contents .auto-toc :link, div.contents .auto-toc :visited { color: #e9e9e9; border: none; display: block; font-weight: bold; padding: 1px 5px 2px 10px; } div.contents .auto-toc :link:hover, div.contents .auto-toc :visited:hover { background: #000; color: #fff; } div.contents .auto-toc .auto-toc :link, div.contents .auto-toc .auto-toc :visited { color: #c6c6c6; font-weight: normal; } h1.title, div.document#preface h1 { border: none; color: #666; font-size: x-large; margin: 0 -20px 1em; padding: 2em 20px 0; } h1.title { background: url(vertbars.png) repeat-x; } div.document#preface h1.title { text-indent: -4000px; } div.document#preface h1 { text-align: center; } pre.literal-block, div.highlight pre { background: #f4f4f4; border: 1px solid #e6e6e6; color: #000; margin: 1em 1em; padding: .25em; overflow: auto; } table.field-list { border-collapse: collapse; } table.field-list th.field-name { color: #333; font-weight: normal; text-align: right; } table.field-list td { border-width: 1px; } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { border: none; color: #333; font-style: italic; margin: 1em 2em; } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title { color: #b00; } p.admonition-title { margin-bottom: 0; text-transform: uppercase; } tt.docutils { background-color: transparent; } span.pre { white-space: normal; } div.section { margin-left: 1.5em; } div.section h1 { margin-left: -1em; } div.section div.section { margin-left: 0; } div.section div.section { margin-bottom: 4em; } div.section div.section div.section { margin-bottom: 0; } Bitten-0.6/doc/common/style/epydoc.css000644 000765 000120 00000013037 10656055446 020267 0ustar00simonadmin000000 000000 html { background: #4b4d4d url(../style/bkgnd_pattern.png); margin: 0; padding: 1em 1em 3em; } body { background: #fff url(../style/vertbars.png) repeat-x; border: 1px solid #000; color: #000; margin: 1em 0; padding: 0 1em 1em; } body, th, td { font: normal small Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; } h1, h2, h3, h4 { font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; font-weight: bold; letter-spacing: -0.018em; } h1 { font-size: 19px; margin: 2em 0 .5em; } h2 { font-size: 16px; margin: 1.5em 0 .5em; } h3 { font-size: 14px; margin: 1.2em 0 .5em; } hr { border: none; border-top: 1px solid #ccb; margin: 2em 0; } p { margin: 0 0 1em; } :link, :visited { text-decoration: none; border-bottom: 1px dotted #bbb; color: #b00; } :link:hover, :visited:hover { background-color: #eee; color: #555; } table { border: none; border-collapse: collapse; } table.navbar { background: #000; color: #fff; margin: 2em 0 .33em; } table.navbar th { border: 1px solid #000; font-weight: bold; padding: 1px; } table.navbar :link, table.navbar :visited { border: none; color: #fff; } table.navbar :link:hover, table.navbar :visited:hover { background: none; text-decoration: underline overline; } table.navbar th.navbar-select { background: #fff; color: #000; } span.breadcrumbs { color: #666; font-size: 95%; } h1.epydoc { border: none; color: #666; font-size: x-large; margin: 1em 0 0; padding: 0; } pre.base-tree { color: #666; margin: 0; padding: 0; } pre.base-tree :link, pre.base-tree :visited { border: none; } pre.py-doctest, pre.variable, pre.rst-literal-block { background: #eee; border: 1px solid #e6e6e6; color: #000; margin: 1em; padding: .25em; overflow: auto; } pre.variable { margin: 0; } /* Summary tables */ table.summary { margin: .5em 0; } table.summary tr.table-header { background: #f7f7f0; } table.summary td.table-header { color: #666; font-weight: bold; } table.summary th.group-header { background: #f7f7f0; color: #666; font-size: 90%; font-weight: bold; text-align: left; } table.summary th, table.summary td { border: 1px solid #d7d7d7; } table.summary th th, table.summary td td { border: none; } table.summary td.summary table td { color: #666; font-size: 90%; } table.summary td.summary table br { display: none; } table.summary td.summary span.summary-type { font-family: monospace; font-size: 90%; } table.summary td.summary span.summary-type code { font-size: 110%; } p.indent-wrapped-lines { color: #999; font-size: 85%; margin: 0; padding: 0 0 0 7em; text-indent: -7em; } p.indent-wrapped-lines code { color: #999; font-size: 115%; } p.indent-wrapped-lines :link, p.indent-wrapped-lines :visited { border: none; } .summary-sig { display: block; font-family: monospace; font-size: 120%; margin-bottom: .5em; } .summary-sig-name { font-weight: bold; } .summary-sig-arg { color: #333; } .summary-sig :link, .summary-sig :visited { border: none; } .summary-name { font-family: monospace; font-weight: bold; } /* Details tables */ table.details { margin: 2em 0 0; } div table.details { margin-top: 0; } table.details tr.table-header { background: transparent; } table.details td.table-header { border-bottom: 1px solid #ccc; padding: 2em 0 0; } table.details span.table-header { font: bold 140% Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; letter-spacing: -0.018em; } table.details th, table.details td { border: none; } table.details th th, table.details td td { border: none; } table.details td { padding-left: 2em; } table.details td td { padding-left: 0; } table.details h3.epydoc { margin-left: -2em; } table.details h3.epydoc .sig { color: #999; font-family: monospace; } table.details h3.epydoc .sig-name { color: #000; } table.details h3.epydoc .sig-arg { color: #666; } table.details h3.epydoc .sig-default { font-size: 95%; font-weight: normal; } table.details h3.epydoc .sig-default code { font-weight: normal; } table.details h3.epydoc .fname { color: #999; font-size: 90%; font-style: italic; font-weight: normal; line-height: 1.6em; } dl dt { color: #666; margin-top: 1em; } dl dd { margin: 0; padding-left: 2em; } dl.fields { margin: 1em 0; padding: 0; } dl.fields dt { color: #666; margin-top: 1em; } dl.fields dd ul { margin: 0; padding: 0; } div.fields { font-size: 90%; margin: 0 0 2em 2em; } div.fields p { margin-bottom: 0.5em; } table td.footer { color: #999; font-size: 85%; margin-top: 3em; padding: 0 3em 1em; position: absolute; width: 80%; } table td.footer :link, table td.footer :visited { border: none; color: #999; } table td.footer :link:hover, table td.footer :visited:hover { background: transparent; text-decoration: underline; } /* Syntax highlighting */ .py-prompt, .py-more, .variable-ellipsis, .variable-op { color: #999; } .variable-group { color: #666; font-weight: bold; } .py-string, .variable-string, .variable-quote { color: #093; } .py-comment { color: #06f; font-style: italic; } .py-keyword { color: #00f; } .py-output { background: #f6f6f0; color: #666; font-weight: bold; } /* Index */ table.link-index { background: #f6f6f0; border: none; margin-top: 1em; } table.link-index td.link-index { border: none; font-family: monospace; font-weight: bold; padding: .5em 1em; } table.link-index td table, table.link-index td td { border: none; } table.link-index .index-where { color: #999; font-family: Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; font-size: 90%; font-weight: normal; line-height: 1.6em; } table.link-index .index-where :link, table.link-index .index-where :visited { border: none; color: #666; } h2.epydoc { color: #999; font-size: 200%; line-height: 10px; } Bitten-0.6/doc/common/style/pygments.css000644 000765 000120 00000004552 10656055446 020654 0ustar00simonadmin000000 000000 div.highlight { background: #ffffff; } div.highlight .c { color: #999988; font-style: italic } div.highlight .err { color: #a61717; background-color: #e3d2d2 } div.highlight .k { font-weight: bold } div.highlight .o { font-weight: bold } div.highlight .cm { color: #999988; font-style: italic } div.highlight .cp { color: #999999; font-weight: bold } div.highlight .c1 { color: #999988; font-style: italic } div.highlight .cs { color: #999999; font-weight: bold; font-style: italic } div.highlight .gd { color: #000000; background-color: #ffdddd } div.highlight .ge { font-style: italic } div.highlight .gr { color: #aa0000 } div.highlight .gh { color: #999999 } div.highlight .gi { color: #000000; background-color: #ddffdd } div.highlight .go { color: #888888 } div.highlight .gp { color: #555555 } div.highlight .gs { font-weight: bold } div.highlight .gu { color: #aaaaaa } div.highlight .gt { color: #aa0000 } div.highlight .kc { font-weight: bold } div.highlight .kd { font-weight: bold } div.highlight .kp { font-weight: bold } div.highlight .kr { font-weight: bold } div.highlight .kt { color: #445588; font-weight: bold } div.highlight .m { color: #009999 } div.highlight .s { color: #bb8844 } div.highlight .na { color: #008080 } div.highlight .nb { color: #999999 } div.highlight .nc { color: #445588; font-weight: bold } div.highlight .no { color: #008080 } div.highlight .ni { color: #800080 } div.highlight .ne { color: #990000; font-weight: bold } div.highlight .nf { color: #990000; font-weight: bold } div.highlight .nn { color: #555555 } div.highlight .nt { color: #000080 } div.highlight .nv { color: #008080 } div.highlight .ow { font-weight: bold } div.highlight .mf { color: #009999 } div.highlight .mh { color: #009999 } div.highlight .mi { color: #009999 } div.highlight .mo { color: #009999 } div.highlight .sb { color: #bb8844 } div.highlight .sc { color: #bb8844 } div.highlight .sd { color: #bb8844 } div.highlight .s2 { color: #bb8844 } div.highlight .se { color: #bb8844 } div.highlight .sh { color: #bb8844 } div.highlight .si { color: #bb8844 } div.highlight .sx { color: #bb8844 } div.highlight .sr { color: #808000 } div.highlight .s1 { color: #bb8844 } div.highlight .ss { color: #bb8844 } div.highlight .bp { color: #999999 } div.highlight .vc { color: #008080 } div.highlight .vg { color: #008080 } div.highlight .vi { color: #008080 } div.highlight .il { color: #009999 } Bitten-0.6/doc/common/style/shadow.gif000644 000765 000120 00000000515 10660060470 020227 0ustar00simonadmin000000 000000 GIF89aUWWKMMKMJ9;:333!,X4>u$u٢)+/{#Nr$@Q~BT,̚*ݭVcM}VIA|ܞsJ/F';G#r@qD_MimflnO_LSRkp8~ϓHˑ K崣 ‰|KDp?= dXi]|0(VGzNhdK;Bitten-0.6/doc/common/style/vertbars.png000644 000765 000120 00000000416 10656055446 020625 0ustar00simonadmin000000 000000 PNG  IHDR o pHYs  tIME ((IDAT8˕I  cT*U RuT0 T89 im2ag C˲6ZRrlF[gHuNk 9 C{&CpI[gHuh- ~9buNh .n48PIENDB`Bitten-0.6/doc/common/conf/docutils.ini000644 000765 000120 00000000261 10656055446 020401 0ustar00simonadmin000000 000000 [general] input_encoding = utf-8 strip_comments = yes toc_backlinks = none [html4css1 writer] embed_stylesheet = no stylesheet = common/style/edgewall.css xml_declaration = no Bitten-0.6/doc/common/conf/epydoc.ini000644 000765 000120 00000000502 10656055446 020034 0ustar00simonadmin000000 000000 [epydoc] name: Documentation Index url: ../index.html verbosity: 1 # Extraction docformat: restructuredtext parse: yes introspect: yes exclude: .*\.tests.* inheritance: listed private: no imports: no include-log: no # HTML output output: html target: doc/api/ css: doc/common/style/epydoc.css frames: no sourcecode: no Bitten-0.6/doc/api/api-objects.txt000644 000765 000120 00000136641 11536424540 017354 0ustar00simonadmin000000 000000 bitten bitten-module.html bitten.PROTOCOL_VERSION bitten-module.html#PROTOCOL_VERSION bitten.__package__ bitten-module.html#__package__ bitten.admin bitten.admin-module.html bitten.admin.__package__ bitten.admin-module.html#__package__ bitten.api bitten.api-module.html bitten.api.__package__ bitten.api-module.html#__package__ bitten.build bitten.build-module.html bitten.build.__package__ bitten.build-module.html#__package__ bitten.build.api bitten.build.api-module.html bitten.build.api.__package__ bitten.build.api-module.html#__package__ bitten.build.api._encode bitten.build.api-module.html#_encode bitten.build.api._decode bitten.build.api-module.html#_decode bitten.build.api.log bitten.build.api-module.html#log bitten.build.config bitten.build.config-module.html bitten.build.config.log bitten.build.config-module.html#log bitten.build.config.__package__ bitten.build.config-module.html#__package__ bitten.build.ctools bitten.build.ctools-module.html bitten.build.ctools.cunit bitten.build.ctools-module.html#cunit bitten.build.ctools.configure bitten.build.ctools-module.html#configure bitten.build.ctools.autoreconf bitten.build.ctools-module.html#autoreconf bitten.build.ctools.__package__ bitten.build.ctools-module.html#__package__ bitten.build.ctools.gcov bitten.build.ctools-module.html#gcov bitten.build.ctools.make bitten.build.ctools-module.html#make bitten.build.ctools.cppunit bitten.build.ctools-module.html#cppunit bitten.build.ctools.log bitten.build.ctools-module.html#log bitten.build.hgtools bitten.build.hgtools-module.html bitten.build.hgtools.pull bitten.build.hgtools-module.html#pull bitten.build.hgtools.log bitten.build.hgtools-module.html#log bitten.build.hgtools.__package__ bitten.build.hgtools-module.html#__package__ bitten.build.javatools bitten.build.javatools-module.html bitten.build.javatools.log bitten.build.javatools-module.html#log bitten.build.javatools.junit bitten.build.javatools-module.html#junit bitten.build.javatools.__package__ bitten.build.javatools-module.html#__package__ bitten.build.javatools.ant bitten.build.javatools-module.html#ant bitten.build.javatools._fix_traceback bitten.build.javatools-module.html#_fix_traceback bitten.build.javatools.cobertura bitten.build.javatools-module.html#cobertura bitten.build.monotools bitten.build.monotools-module.html bitten.build.monotools.nunit bitten.build.monotools-module.html#nunit bitten.build.monotools.log bitten.build.monotools-module.html#log bitten.build.monotools.__package__ bitten.build.monotools-module.html#__package__ bitten.build.monotools._parse_suite bitten.build.monotools-module.html#_parse_suite bitten.build.monotools._get_cases bitten.build.monotools-module.html#_get_cases bitten.build.phptools bitten.build.phptools-module.html bitten.build.phptools.phing bitten.build.phptools-module.html#phing bitten.build.phptools.__package__ bitten.build.phptools-module.html#__package__ bitten.build.phptools.phpunit bitten.build.phptools-module.html#phpunit bitten.build.phptools.coverage bitten.build.phptools-module.html#coverage bitten.build.phptools.log bitten.build.phptools-module.html#log bitten.build.pythontools bitten.build.pythontools-module.html bitten.build.pythontools.distutils bitten.build.pythontools-module.html#distutils bitten.build.pythontools.unittest bitten.build.pythontools-module.html#unittest bitten.build.pythontools._normalize_filenames bitten.build.pythontools-module.html#_normalize_filenames bitten.build.pythontools.log bitten.build.pythontools-module.html#log bitten.build.pythontools.__package__ bitten.build.pythontools-module.html#__package__ bitten.build.pythontools.figleaf bitten.build.pythontools-module.html#figleaf bitten.build.pythontools.trace bitten.build.pythontools-module.html#trace bitten.build.pythontools.pylint bitten.build.pythontools-module.html#pylint bitten.build.pythontools.exec_ bitten.build.pythontools-module.html#exec_ bitten.build.pythontools.coverage bitten.build.pythontools-module.html#coverage bitten.build.pythontools._python_path bitten.build.pythontools-module.html#_python_path bitten.build.shtools bitten.build.shtools-module.html bitten.build.shtools.execute bitten.build.shtools-module.html#execute bitten.build.shtools.log bitten.build.shtools-module.html#log bitten.build.shtools.__package__ bitten.build.shtools-module.html#__package__ bitten.build.shtools.pipe bitten.build.shtools-module.html#pipe bitten.build.shtools.exec_ bitten.build.shtools-module.html#exec_ bitten.build.svntools bitten.build.svntools-module.html bitten.build.svntools.log bitten.build.svntools-module.html#log bitten.build.svntools.update bitten.build.svntools-module.html#update bitten.build.svntools.__package__ bitten.build.svntools-module.html#__package__ bitten.build.svntools.export bitten.build.svntools-module.html#export bitten.build.svntools.copytree bitten.build.svntools-module.html#copytree bitten.build.svntools.checkout bitten.build.svntools-module.html#checkout bitten.build.xmltools bitten.build.xmltools-module.html bitten.build.xmltools.have_msxml bitten.build.xmltools-module.html#have_msxml bitten.build.xmltools.log bitten.build.xmltools-module.html#log bitten.build.xmltools.transform bitten.build.xmltools-module.html#transform bitten.build.xmltools.__package__ bitten.build.xmltools-module.html#__package__ bitten.build.xmltools.have_libxslt bitten.build.xmltools-module.html#have_libxslt bitten.main bitten.main-module.html bitten.main.__package__ bitten.main-module.html#__package__ bitten.master bitten.master-module.html bitten.master.HTTP_FORBIDDEN bitten.master-module.html#HTTP_FORBIDDEN bitten.master.HTTP_NOT_FOUND bitten.master-module.html#HTTP_NOT_FOUND bitten.master.HTTP_BAD_REQUEST bitten.master-module.html#HTTP_BAD_REQUEST bitten.master.HTTP_CONFLICT bitten.master-module.html#HTTP_CONFLICT bitten.master.HTTP_METHOD_NOT_ALLOWED bitten.master-module.html#HTTP_METHOD_NOT_ALLOWED bitten.master.__package__ bitten.master-module.html#__package__ bitten.model bitten.model-module.html bitten.model.schema bitten.model-module.html#schema bitten.model.schema_version bitten.model-module.html#schema_version bitten.model.__package__ bitten.model-module.html#__package__ bitten.notify bitten.notify-module.html bitten.notify.__package__ bitten.notify-module.html#__package__ bitten.queue bitten.queue-module.html bitten.queue.__package__ bitten.queue-module.html#__package__ bitten.queue.collect_changes bitten.queue-module.html#collect_changes bitten.recipe bitten.recipe-module.html bitten.recipe.__package__ bitten.recipe-module.html#__package__ bitten.recipe.log bitten.recipe-module.html#log bitten.report bitten.report-module.html bitten.report.__package__ bitten.report-module.html#__package__ bitten.report.coverage bitten.report.coverage-module.html bitten.report.coverage.__package__ bitten.report.coverage-module.html#__package__ bitten.report.lint bitten.report.lint-module.html bitten.report.lint.__package__ bitten.report.lint-module.html#__package__ bitten.report.testing bitten.report.testing-module.html bitten.report.testing.__package__ bitten.report.testing-module.html#__package__ bitten.slave bitten.slave-module.html bitten.slave.EX_IOERR bitten.slave-module.html#EX_IOERR bitten.slave.EX_OK bitten.slave-module.html#EX_OK bitten.slave.encode_multipart_formdata bitten.slave-module.html#encode_multipart_formdata bitten.slave.EX_UNAVAILABLE bitten.slave-module.html#EX_UNAVAILABLE bitten.slave.log bitten.slave-module.html#log bitten.slave.__package__ bitten.slave-module.html#__package__ bitten.slave.EX_NOPERM bitten.slave-module.html#EX_NOPERM bitten.slave.FORM_TOKEN_RE bitten.slave-module.html#FORM_TOKEN_RE bitten.slave._rmtree bitten.slave-module.html#_rmtree bitten.slave.main bitten.slave-module.html#main bitten.slave.temp_net_errors bitten.slave-module.html#temp_net_errors bitten.slave.EX_PROTOCOL bitten.slave-module.html#EX_PROTOCOL bitten.upgrades bitten.upgrades-module.html bitten.upgrades.add_config_platform_rev_index_to_build bitten.upgrades-module.html#add_config_platform_rev_index_to_build bitten.upgrades.drop_index bitten.upgrades-module.html#drop_index bitten.upgrades.migrate_logs_to_files bitten.upgrades-module.html#migrate_logs_to_files bitten.upgrades.add_log_table bitten.upgrades-module.html#add_log_table bitten.upgrades.remove_stray_log_levels_files bitten.upgrades-module.html#remove_stray_log_levels_files bitten.upgrades.add_last_activity_to_build bitten.upgrades-module.html#add_last_activity_to_build bitten.upgrades.add_error_table bitten.upgrades-module.html#add_error_table bitten.upgrades.__package__ bitten.upgrades-module.html#__package__ bitten.upgrades.normalize_file_paths bitten.upgrades-module.html#normalize_file_paths bitten.upgrades.fix_log_levels_misnaming bitten.upgrades-module.html#fix_log_levels_misnaming bitten.upgrades.add_filename_to_logs bitten.upgrades-module.html#add_filename_to_logs bitten.upgrades.add_config_to_reports bitten.upgrades-module.html#add_config_to_reports bitten.upgrades.map bitten.upgrades-module.html#map bitten.upgrades.add_recipe_to_config bitten.upgrades-module.html#add_recipe_to_config bitten.upgrades.update_sequence bitten.upgrades-module.html#update_sequence bitten.upgrades.add_report_tables bitten.upgrades-module.html#add_report_tables bitten.upgrades.parse_scheme bitten.upgrades-module.html#parse_scheme bitten.upgrades.add_order_to_log bitten.upgrades-module.html#add_order_to_log bitten.upgrades.fix_sequences bitten.upgrades-module.html#fix_sequences bitten.upgrades.xmldb_to_db bitten.upgrades-module.html#xmldb_to_db bitten.upgrades.recreate_rule_with_int_id bitten.upgrades-module.html#recreate_rule_with_int_id bitten.upgrades.fixup_generators bitten.upgrades-module.html#fixup_generators bitten.util bitten.util-module.html bitten.util.__package__ bitten.util-module.html#__package__ bitten.util.compat bitten.util.compat-module.html bitten.util.compat.__package__ bitten.util.compat-module.html#__package__ bitten.util.json bitten.util.json-module.html bitten.util.json._js_quote bitten.util.json-module.html#_js_quote bitten.util.json._js_quote_re bitten.util.json-module.html#_js_quote_re bitten.util.json.__package__ bitten.util.json-module.html#__package__ bitten.util.json.to_json bitten.util.json-module.html#to_json bitten.util.loc bitten.util.loc-module.html bitten.util.loc.COMMENT bitten.util.loc-module.html#COMMENT bitten.util.loc.count bitten.util.loc-module.html#count bitten.util.loc.CODE bitten.util.loc-module.html#CODE bitten.util.loc._squote3_finder bitten.util.loc-module.html#_squote3_finder bitten.util.loc.DOC bitten.util.loc-module.html#DOC bitten.util.loc._dquote1_finder bitten.util.loc-module.html#_dquote1_finder bitten.util.loc._has_nightmare bitten.util.loc-module.html#_has_nightmare bitten.util.loc.__package__ bitten.util.loc-module.html#__package__ bitten.util.loc._is_comment bitten.util.loc-module.html#_is_comment bitten.util.loc._is_blank bitten.util.loc-module.html#_is_blank bitten.util.loc.BLANK bitten.util.loc-module.html#BLANK bitten.util.loc._squote1_finder bitten.util.loc-module.html#_squote1_finder bitten.util.loc._is_doc_candidate bitten.util.loc-module.html#_is_doc_candidate bitten.util.loc._dquote3_finder bitten.util.loc-module.html#_dquote3_finder bitten.util.testrunner bitten.util.testrunner-module.html bitten.util.testrunner.filter_coverage bitten.util.testrunner-module.html#filter_coverage bitten.util.testrunner.__package__ bitten.util.testrunner-module.html#__package__ bitten.util.testrunner.main bitten.util.testrunner-module.html#main bitten.util.xmlio bitten.util.xmlio-module.html bitten.util.xmlio._escape_text bitten.util.xmlio-module.html#_escape_text bitten.util.xmlio.__trans bitten.util.xmlio-module.html#__trans bitten.util.xmlio._to_utf8 bitten.util.xmlio-module.html#_to_utf8 bitten.util.xmlio.__uni_trans bitten.util.xmlio-module.html#__uni_trans bitten.util.xmlio.c bitten.util.xmlio-module.html#c bitten.util.xmlio._from_utf8 bitten.util.xmlio-module.html#_from_utf8 bitten.util.xmlio.parse bitten.util.xmlio-module.html#parse bitten.util.xmlio._escape_attr bitten.util.xmlio-module.html#_escape_attr bitten.util.xmlio.__todel bitten.util.xmlio-module.html#__todel bitten.util.xmlio.__package__ bitten.util.xmlio-module.html#__package__ bitten.web_ui bitten.web_ui-module.html bitten.web_ui.__package__ bitten.web_ui-module.html#__package__ bitten.web_ui._step_status_label bitten.web_ui-module.html#_step_status_label bitten.web_ui._has_permission bitten.web_ui-module.html#_has_permission bitten.web_ui.collect_changes bitten.queue-module.html#collect_changes bitten.web_ui._status_label bitten.web_ui-module.html#_status_label bitten.web_ui._status_title bitten.web_ui-module.html#_status_title bitten.web_ui._get_build_data bitten.web_ui-module.html#_get_build_data bitten.admin.BuildConfigurationsAdminPageProvider bitten.admin.BuildConfigurationsAdminPageProvider-class.html bitten.admin.BuildConfigurationsAdminPageProvider.render_admin_panel bitten.admin.BuildConfigurationsAdminPageProvider-class.html#render_admin_panel trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.admin.BuildConfigurationsAdminPageProvider._update_platform bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_update_platform bitten.admin.BuildConfigurationsAdminPageProvider._activate_configs bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_activate_configs bitten.admin.BuildConfigurationsAdminPageProvider.get_admin_panels bitten.admin.BuildConfigurationsAdminPageProvider-class.html#get_admin_panels bitten.admin.BuildConfigurationsAdminPageProvider._update_config bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_update_config bitten.admin.BuildConfigurationsAdminPageProvider.__init__ bitten.admin.BuildConfigurationsAdminPageProvider-class.html#__init__ bitten.admin.BuildConfigurationsAdminPageProvider._remove_configs bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_remove_configs bitten.admin.BuildConfigurationsAdminPageProvider._create_config bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_create_config bitten.admin.BuildConfigurationsAdminPageProvider._create_platform bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_create_platform bitten.admin.BuildConfigurationsAdminPageProvider._implements bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_implements bitten.admin.BuildConfigurationsAdminPageProvider._remove_platforms bitten.admin.BuildConfigurationsAdminPageProvider-class.html#_remove_platforms bitten.admin.BuildMasterAdminPageProvider bitten.admin.BuildMasterAdminPageProvider-class.html bitten.admin.BuildMasterAdminPageProvider.render_admin_panel bitten.admin.BuildMasterAdminPageProvider-class.html#render_admin_panel trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.admin.BuildMasterAdminPageProvider._implements bitten.admin.BuildMasterAdminPageProvider-class.html#_implements bitten.admin.BuildMasterAdminPageProvider.get_admin_panels bitten.admin.BuildMasterAdminPageProvider-class.html#get_admin_panels bitten.admin.BuildMasterAdminPageProvider.__init__ bitten.admin.BuildMasterAdminPageProvider-class.html#__init__ bitten.admin.BuildMasterAdminPageProvider._save_config_changes bitten.admin.BuildMasterAdminPageProvider-class.html#_save_config_changes bitten.api.IBuildListener bitten.api.IBuildListener-class.html bitten.api.IBuildListener.build_completed bitten.api.IBuildListener-class.html#build_completed bitten.api.IBuildListener.build_started bitten.api.IBuildListener-class.html#build_started bitten.api.IBuildListener.build_aborted bitten.api.IBuildListener-class.html#build_aborted bitten.api.ILogFormatter bitten.api.ILogFormatter-class.html bitten.api.ILogFormatter.get_formatter bitten.api.ILogFormatter-class.html#get_formatter bitten.api.IReportChartGenerator bitten.api.IReportChartGenerator-class.html bitten.api.IReportChartGenerator.generate_chart_data bitten.api.IReportChartGenerator-class.html#generate_chart_data bitten.api.IReportChartGenerator.get_supported_categories bitten.api.IReportChartGenerator-class.html#get_supported_categories bitten.api.IReportSummarizer bitten.api.IReportSummarizer-class.html bitten.api.IReportSummarizer.render_summary bitten.api.IReportSummarizer-class.html#render_summary bitten.api.IReportSummarizer.get_supported_categories bitten.api.IReportSummarizer-class.html#get_supported_categories bitten.build.api.BuildError bitten.build.api.BuildError-class.html bitten.build.api.CommandLine bitten.build.api.CommandLine-class.html bitten.build.api.CommandLine.execute bitten.build.api.CommandLine-class.html#execute bitten.build.api.CommandLine.__init__ bitten.build.api.CommandLine-class.html#__init__ bitten.build.api.FileSet bitten.build.api.FileSet-class.html bitten.build.api.FileSet.DEFAULT_EXCLUDES bitten.build.api.FileSet-class.html#DEFAULT_EXCLUDES bitten.build.api.FileSet.__contains__ bitten.build.api.FileSet-class.html#__contains__ bitten.build.api.FileSet.__iter__ bitten.build.api.FileSet-class.html#__iter__ bitten.build.api.FileSet.__init__ bitten.build.api.FileSet-class.html#__init__ bitten.build.api.TimeoutError bitten.build.api.TimeoutError-class.html bitten.build.config.ConfigFileNotFound bitten.build.config.ConfigFileNotFound-class.html bitten.build.config.Configuration bitten.build.config.Configuration-class.html bitten.build.config.Configuration.__str__ bitten.build.config.Configuration-class.html#__str__ bitten.build.config.Configuration.get_dirpath bitten.build.config.Configuration-class.html#get_dirpath bitten.build.config.Configuration.__contains__ bitten.build.config.Configuration-class.html#__contains__ bitten.build.config.Configuration._merge_packages bitten.build.config.Configuration-class.html#_merge_packages bitten.build.config.Configuration.__init__ bitten.build.config.Configuration-class.html#__init__ bitten.build.config.Configuration.__getitem__ bitten.build.config.Configuration-class.html#__getitem__ bitten.build.config.Configuration.interpolate bitten.build.config.Configuration-class.html#interpolate bitten.build.config.Configuration._VAR_RE bitten.build.config.Configuration-class.html#_VAR_RE bitten.build.config.Configuration._merge_sysinfo bitten.build.config.Configuration-class.html#_merge_sysinfo bitten.build.config.Configuration.get_filepath bitten.build.config.Configuration-class.html#get_filepath bitten.build.svntools.Error bitten.build.svntools.Error-class.html bitten.main.BuildSystem bitten.main.BuildSystem-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.main.BuildSystem.get_resource_url bitten.main.BuildSystem-class.html#get_resource_url bitten.main.BuildSystem.get_link_resolvers bitten.main.BuildSystem-class.html#get_link_resolvers bitten.main.BuildSystem.__init__ bitten.main.BuildSystem-class.html#__init__ bitten.main.BuildSystem._parse_resource bitten.main.BuildSystem-class.html#_parse_resource bitten.main.BuildSystem.resource_exists bitten.main.BuildSystem-class.html#resource_exists bitten.main.BuildSystem.check_attachment_permission bitten.main.BuildSystem-class.html#check_attachment_permission bitten.main.BuildSystem.get_permission_actions bitten.main.BuildSystem-class.html#get_permission_actions bitten.main.BuildSystem.get_resource_realms bitten.main.BuildSystem-class.html#get_resource_realms bitten.main.BuildSystem.get_wiki_syntax bitten.main.BuildSystem-class.html#get_wiki_syntax bitten.main.BuildSystem.listeners bitten.main.BuildSystem-class.html#listeners bitten.main.BuildSystem.get_resource_description bitten.main.BuildSystem-class.html#get_resource_description bitten.main.BuildSystem._implements bitten.main.BuildSystem-class.html#_implements bitten.master.BuildMaster bitten.master.BuildMaster-class.html bitten.master.BuildMaster.quick_status bitten.master.BuildMaster-class.html#quick_status trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.master.BuildMaster.process_request bitten.master.BuildMaster-class.html#process_request bitten.master.BuildMaster._process_build_creation bitten.master.BuildMaster-class.html#_process_build_creation bitten.master.BuildMaster._start_new_step bitten.master.BuildMaster-class.html#_start_new_step bitten.master.BuildMaster.stabilize_wait bitten.master.BuildMaster-class.html#stabilize_wait bitten.master.BuildMaster.__init__ bitten.master.BuildMaster-class.html#__init__ bitten.master.BuildMaster._send_error bitten.master.BuildMaster-class.html#_send_error bitten.master.BuildMaster.slave_timeout bitten.master.BuildMaster-class.html#slave_timeout bitten.master.BuildMaster._process_attachment bitten.master.BuildMaster-class.html#_process_attachment bitten.master.BuildMaster._process_build_initiation bitten.master.BuildMaster-class.html#_process_build_initiation bitten.master.BuildMaster.logs_dir bitten.master.BuildMaster-class.html#logs_dir bitten.master.BuildMaster._process_build_step bitten.master.BuildMaster-class.html#_process_build_step bitten.master.BuildMaster.match_request bitten.master.BuildMaster-class.html#match_request bitten.master.BuildMaster.adjust_timestamps bitten.master.BuildMaster-class.html#adjust_timestamps bitten.master.BuildMaster._send_response bitten.master.BuildMaster-class.html#_send_response bitten.master.BuildMaster._process_keepalive bitten.master.BuildMaster-class.html#_process_keepalive bitten.master.BuildMaster._process_build_cancellation bitten.master.BuildMaster-class.html#_process_build_cancellation bitten.master.BuildMaster._implements bitten.master.BuildMaster-class.html#_implements bitten.master.BuildMaster.build_all bitten.master.BuildMaster-class.html#build_all bitten.model.Build bitten.model.Build-class.html bitten.model.Build.OS_NAME bitten.model.Build-class.html#OS_NAME bitten.model.Build.exists bitten.model.Build-class.html#exists bitten.model.Build._schema bitten.model.Build-class.html#_schema bitten.model.Build.select bitten.model.Build-class.html#select bitten.model.Build.PROCESSOR bitten.model.Build-class.html#PROCESSOR bitten.model.Build.PENDING bitten.model.Build-class.html#PENDING bitten.model.Build.__init__ bitten.model.Build-class.html#__init__ bitten.model.Build.IP_ADDRESS bitten.model.Build-class.html#IP_ADDRESS bitten.model.Build.MAINTAINER bitten.model.Build-class.html#MAINTAINER bitten.model.Build.SUCCESS bitten.model.Build-class.html#SUCCESS bitten.model.Build.successful bitten.model.Build-class.html#successful bitten.model.Build.completed bitten.model.Build-class.html#completed bitten.model.Build.update bitten.model.Build-class.html#update bitten.model.Build.MACHINE bitten.model.Build-class.html#MACHINE bitten.model.Build.OS_VERSION bitten.model.Build-class.html#OS_VERSION bitten.model.Build.TOKEN bitten.model.Build-class.html#TOKEN bitten.model.Build.OS_FAMILY bitten.model.Build-class.html#OS_FAMILY bitten.model.Build.IN_PROGRESS bitten.model.Build-class.html#IN_PROGRESS bitten.model.Build.insert bitten.model.Build-class.html#insert bitten.model.Build.resource bitten.model.Build-class.html#resource bitten.model.Build.FAILURE bitten.model.Build-class.html#FAILURE bitten.model.Build.__repr__ bitten.model.Build-class.html#__repr__ bitten.model.Build.fetch bitten.model.Build-class.html#fetch bitten.model.Build.delete bitten.model.Build-class.html#delete bitten.model.BuildConfig bitten.model.BuildConfig-class.html bitten.model.BuildConfig.exists bitten.model.BuildConfig-class.html#exists bitten.model.BuildConfig._schema bitten.model.BuildConfig-class.html#_schema bitten.model.BuildConfig.select bitten.model.BuildConfig-class.html#select bitten.model.BuildConfig.min_rev_time bitten.model.BuildConfig-class.html#min_rev_time bitten.model.BuildConfig.max_rev_time bitten.model.BuildConfig-class.html#max_rev_time bitten.model.BuildConfig.__init__ bitten.model.BuildConfig-class.html#__init__ bitten.model.BuildConfig.update bitten.model.BuildConfig-class.html#update bitten.model.BuildConfig.insert bitten.model.BuildConfig-class.html#insert bitten.model.BuildConfig.resource bitten.model.BuildConfig-class.html#resource bitten.model.BuildConfig.__repr__ bitten.model.BuildConfig-class.html#__repr__ bitten.model.BuildConfig.fetch bitten.model.BuildConfig-class.html#fetch bitten.model.BuildConfig.delete bitten.model.BuildConfig-class.html#delete bitten.model.BuildLog bitten.model.BuildLog-class.html bitten.model.BuildLog.exists bitten.model.BuildLog-class.html#exists bitten.model.BuildLog.UNKNOWN bitten.model.BuildLog-class.html#UNKNOWN bitten.model.BuildLog._schema bitten.model.BuildLog-class.html#_schema bitten.model.BuildLog.__init__ bitten.model.BuildLog-class.html#__init__ bitten.model.BuildLog.LEVELS_SUFFIX bitten.model.BuildLog-class.html#LEVELS_SUFFIX bitten.model.BuildLog.WARNING bitten.model.BuildLog-class.html#WARNING bitten.model.BuildLog.select bitten.model.BuildLog-class.html#select bitten.model.BuildLog.ERROR bitten.model.BuildLog-class.html#ERROR bitten.model.BuildLog.DEBUG bitten.model.BuildLog-class.html#DEBUG bitten.model.BuildLog.INFO bitten.model.BuildLog-class.html#INFO bitten.model.BuildLog.insert bitten.model.BuildLog-class.html#insert bitten.model.BuildLog.get_log_file bitten.model.BuildLog-class.html#get_log_file bitten.model.BuildLog.fetch bitten.model.BuildLog-class.html#fetch bitten.model.BuildLog.delete bitten.model.BuildLog-class.html#delete bitten.model.BuildStep bitten.model.BuildStep-class.html bitten.model.BuildStep.exists bitten.model.BuildStep-class.html#exists bitten.model.BuildStep._schema bitten.model.BuildStep-class.html#_schema bitten.model.BuildStep.select bitten.model.BuildStep-class.html#select bitten.model.BuildStep.FAILURE bitten.model.BuildStep-class.html#FAILURE bitten.model.BuildStep.__init__ bitten.model.BuildStep-class.html#__init__ bitten.model.BuildStep.SUCCESS bitten.model.BuildStep-class.html#SUCCESS bitten.model.BuildStep.successful bitten.model.BuildStep-class.html#successful bitten.model.BuildStep.completed bitten.model.BuildStep-class.html#completed bitten.model.BuildStep.IN_PROGRESS bitten.model.BuildStep-class.html#IN_PROGRESS bitten.model.BuildStep.insert bitten.model.BuildStep-class.html#insert bitten.model.BuildStep.fetch bitten.model.BuildStep-class.html#fetch bitten.model.BuildStep.delete bitten.model.BuildStep-class.html#delete bitten.model.Report bitten.model.Report-class.html bitten.model.Report.insert bitten.model.Report-class.html#insert bitten.model.Report.exists bitten.model.Report-class.html#exists bitten.model.Report._schema bitten.model.Report-class.html#_schema bitten.model.Report.select bitten.model.Report-class.html#select bitten.model.Report.fetch bitten.model.Report-class.html#fetch bitten.model.Report.__init__ bitten.model.Report-class.html#__init__ bitten.model.Report.delete bitten.model.Report-class.html#delete bitten.model.TargetPlatform bitten.model.TargetPlatform-class.html bitten.model.TargetPlatform.insert bitten.model.TargetPlatform-class.html#insert bitten.model.TargetPlatform.exists bitten.model.TargetPlatform-class.html#exists bitten.model.TargetPlatform._schema bitten.model.TargetPlatform-class.html#_schema bitten.model.TargetPlatform.update bitten.model.TargetPlatform-class.html#update bitten.model.TargetPlatform.select bitten.model.TargetPlatform-class.html#select bitten.model.TargetPlatform.__repr__ bitten.model.TargetPlatform-class.html#__repr__ bitten.model.TargetPlatform.fetch bitten.model.TargetPlatform-class.html#fetch bitten.model.TargetPlatform.__init__ bitten.model.TargetPlatform-class.html#__init__ bitten.model.TargetPlatform.delete bitten.model.TargetPlatform-class.html#delete bitten.notify.BittenNotify bitten.notify.BittenNotify-class.html bitten.notify.BittenNotify.build_completed bitten.notify.BittenNotify-class.html#build_completed trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.notify.BittenNotify.get_templates_dirs bitten.notify.BittenNotify-class.html#get_templates_dirs bitten.notify.BittenNotify.build_started bitten.notify.BittenNotify-class.html#build_started bitten.notify.BittenNotify.notify_on_failure bitten.notify.BittenNotify-class.html#notify_on_failure bitten.notify.BittenNotify.notify bitten.notify.BittenNotify-class.html#notify bitten.notify.BittenNotify._should_notify bitten.notify.BittenNotify-class.html#_should_notify bitten.notify.BittenNotify.__init__ bitten.notify.BittenNotify-class.html#__init__ bitten.notify.BittenNotify.notify_on_success bitten.notify.BittenNotify-class.html#notify_on_success bitten.notify.BittenNotify._implements bitten.notify.BittenNotify-class.html#_implements bitten.notify.BittenNotify.get_htdocs_dirs bitten.notify.BittenNotify-class.html#get_htdocs_dirs bitten.notify.BittenNotify.build_aborted bitten.notify.BittenNotify-class.html#build_aborted bitten.notify.BuildNotifyEmail bitten.notify.BuildNotifyEmail-class.html bitten.notify.BuildNotifyEmail.readable_states bitten.notify.BuildNotifyEmail-class.html#readable_states bitten.notify.BuildNotifyEmail.template_name bitten.notify.BuildNotifyEmail-class.html#template_name bitten.notify.BuildNotifyEmail.build_link bitten.notify.BuildNotifyEmail-class.html#build_link bitten.notify.BuildNotifyEmail.notify bitten.notify.BuildNotifyEmail-class.html#notify bitten.notify.BuildNotifyEmail.get_changeset bitten.notify.BuildNotifyEmail-class.html#get_changeset bitten.notify.BuildNotifyEmail.get_author bitten.notify.BuildNotifyEmail-class.html#get_author bitten.notify.BuildNotifyEmail.__init__ bitten.notify.BuildNotifyEmail-class.html#__init__ bitten.notify.BuildNotifyEmail.send bitten.notify.BuildNotifyEmail-class.html#send bitten.notify.BuildNotifyEmail.from_email bitten.notify.BuildNotifyEmail-class.html#from_email bitten.notify.BuildNotifyEmail.template_data bitten.notify.BuildNotifyEmail-class.html#template_data bitten.notify.BuildNotifyEmail.get_recipients bitten.notify.BuildNotifyEmail-class.html#get_recipients bitten.notify.BuildNotifyEmail.get_all_log_messages_for_step bitten.notify.BuildNotifyEmail-class.html#get_all_log_messages_for_step bitten.queue.BuildQueue bitten.queue.BuildQueue-class.html bitten.queue.BuildQueue.populate bitten.queue.BuildQueue-class.html#populate bitten.queue.BuildQueue.reset_orphaned_builds bitten.queue.BuildQueue-class.html#reset_orphaned_builds bitten.queue.BuildQueue.match_slave bitten.queue.BuildQueue-class.html#match_slave bitten.queue.BuildQueue.get_build_for_slave bitten.queue.BuildQueue-class.html#get_build_for_slave bitten.queue.BuildQueue.should_delete_build bitten.queue.BuildQueue-class.html#should_delete_build bitten.queue.BuildQueue.__init__ bitten.queue.BuildQueue-class.html#__init__ bitten.recipe.Context bitten.recipe.Context-class.html bitten.recipe.Context.__init__ bitten.recipe.Context-class.html#__init__ bitten.recipe.Context.log bitten.recipe.Context-class.html#log bitten.recipe.Context.generator bitten.recipe.Context-class.html#generator bitten.recipe.Context.attach bitten.recipe.Context-class.html#attach bitten.recipe.Context.run bitten.recipe.Context-class.html#run bitten.recipe.Context.step bitten.recipe.Context-class.html#step bitten.recipe.Context.report bitten.recipe.Context-class.html#report bitten.recipe.Context.report_file bitten.recipe.Context-class.html#report_file bitten.recipe.Context.resolve bitten.recipe.Context-class.html#resolve bitten.recipe.Context.error bitten.recipe.Context-class.html#error bitten.recipe.InvalidRecipeError bitten.recipe.InvalidRecipeError-class.html bitten.recipe.Recipe bitten.recipe.Recipe-class.html bitten.recipe.Recipe.LOG bitten.recipe.Recipe-class.html#LOG bitten.recipe.Recipe.ATTACH bitten.recipe.Recipe-class.html#ATTACH bitten.recipe.Recipe.__iter__ bitten.recipe.Recipe-class.html#__iter__ bitten.recipe.Recipe.ERROR bitten.recipe.Recipe-class.html#ERROR bitten.recipe.Recipe.REPORT bitten.recipe.Recipe-class.html#REPORT bitten.recipe.Recipe.validate bitten.recipe.Recipe-class.html#validate bitten.recipe.Recipe.__init__ bitten.recipe.Recipe-class.html#__init__ bitten.recipe.Step bitten.recipe.Step-class.html bitten.recipe.Step.execute bitten.recipe.Step-class.html#execute bitten.recipe.Step.__repr__ bitten.recipe.Step-class.html#__repr__ bitten.recipe.Step.__init__ bitten.recipe.Step-class.html#__init__ bitten.report.coverage.TestCoverageAnnotator bitten.report.coverage.TestCoverageAnnotator-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.coverage.TestCoverageAnnotator.pre_process_request bitten.report.coverage.TestCoverageAnnotator-class.html#pre_process_request bitten.report.coverage.TestCoverageAnnotator.get_annotation_data bitten.report.coverage.TestCoverageAnnotator-class.html#get_annotation_data bitten.report.coverage.TestCoverageAnnotator.__init__ bitten.report.coverage.TestCoverageAnnotator-class.html#__init__ bitten.report.coverage.TestCoverageAnnotator.get_annotation_type bitten.report.coverage.TestCoverageAnnotator-class.html#get_annotation_type bitten.report.coverage.TestCoverageAnnotator.annotate_row bitten.report.coverage.TestCoverageAnnotator-class.html#annotate_row bitten.report.coverage.TestCoverageAnnotator.post_process_request bitten.report.coverage.TestCoverageAnnotator-class.html#post_process_request bitten.report.coverage.TestCoverageAnnotator._implements bitten.report.coverage.TestCoverageAnnotator-class.html#_implements bitten.report.coverage.TestCoverageChartGenerator bitten.report.coverage.TestCoverageChartGenerator-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.coverage.TestCoverageChartGenerator.generate_chart_data bitten.report.coverage.TestCoverageChartGenerator-class.html#generate_chart_data bitten.report.coverage.TestCoverageChartGenerator._implements bitten.report.coverage.TestCoverageChartGenerator-class.html#_implements bitten.report.coverage.TestCoverageChartGenerator.get_supported_categories bitten.report.coverage.TestCoverageChartGenerator-class.html#get_supported_categories bitten.report.coverage.TestCoverageChartGenerator.__init__ bitten.report.coverage.TestCoverageChartGenerator-class.html#__init__ bitten.report.coverage.TestCoverageSummarizer bitten.report.coverage.TestCoverageSummarizer-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.coverage.TestCoverageSummarizer.render_summary bitten.report.coverage.TestCoverageSummarizer-class.html#render_summary bitten.report.coverage.TestCoverageSummarizer._implements bitten.report.coverage.TestCoverageSummarizer-class.html#_implements bitten.report.coverage.TestCoverageSummarizer.get_supported_categories bitten.report.coverage.TestCoverageSummarizer-class.html#get_supported_categories bitten.report.coverage.TestCoverageSummarizer.__init__ bitten.report.coverage.TestCoverageSummarizer-class.html#__init__ bitten.report.lint.PyLintChartGenerator bitten.report.lint.PyLintChartGenerator-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.lint.PyLintChartGenerator.generate_chart_data bitten.report.lint.PyLintChartGenerator-class.html#generate_chart_data bitten.report.lint.PyLintChartGenerator._implements bitten.report.lint.PyLintChartGenerator-class.html#_implements bitten.report.lint.PyLintChartGenerator.get_supported_categories bitten.report.lint.PyLintChartGenerator-class.html#get_supported_categories bitten.report.lint.PyLintChartGenerator.__init__ bitten.report.lint.PyLintChartGenerator-class.html#__init__ bitten.report.lint.PyLintSummarizer bitten.report.lint.PyLintSummarizer-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.lint.PyLintSummarizer.render_summary bitten.report.lint.PyLintSummarizer-class.html#render_summary bitten.report.lint.PyLintSummarizer._implements bitten.report.lint.PyLintSummarizer-class.html#_implements bitten.report.lint.PyLintSummarizer.get_supported_categories bitten.report.lint.PyLintSummarizer-class.html#get_supported_categories bitten.report.lint.PyLintSummarizer.__init__ bitten.report.lint.PyLintSummarizer-class.html#__init__ bitten.report.testing.TestResultsChartGenerator bitten.report.testing.TestResultsChartGenerator-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.testing.TestResultsChartGenerator.generate_chart_data bitten.report.testing.TestResultsChartGenerator-class.html#generate_chart_data bitten.report.testing.TestResultsChartGenerator._implements bitten.report.testing.TestResultsChartGenerator-class.html#_implements bitten.report.testing.TestResultsChartGenerator.get_supported_categories bitten.report.testing.TestResultsChartGenerator-class.html#get_supported_categories bitten.report.testing.TestResultsChartGenerator.__init__ bitten.report.testing.TestResultsChartGenerator-class.html#__init__ bitten.report.testing.TestResultsSummarizer bitten.report.testing.TestResultsSummarizer-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.report.testing.TestResultsSummarizer.render_summary bitten.report.testing.TestResultsSummarizer-class.html#render_summary bitten.report.testing.TestResultsSummarizer._implements bitten.report.testing.TestResultsSummarizer-class.html#_implements bitten.report.testing.TestResultsSummarizer.get_supported_categories bitten.report.testing.TestResultsSummarizer-class.html#get_supported_categories bitten.report.testing.TestResultsSummarizer.__init__ bitten.report.testing.TestResultsSummarizer-class.html#__init__ bitten.slave.BuildSlave bitten.slave.BuildSlave-class.html bitten.slave.BuildSlave._initiate_build bitten.slave.BuildSlave-class.html#_initiate_build bitten.slave.BuildSlave._get_opener bitten.slave.BuildSlave-class.html#_get_opener bitten.slave.BuildSlave._create_build bitten.slave.BuildSlave-class.html#_create_build bitten.slave.BuildSlave.quit bitten.slave.BuildSlave-class.html#quit bitten.slave.BuildSlave.opener bitten.slave.BuildSlave-class.html#opener bitten.slave.BuildSlave.__init__ bitten.slave.BuildSlave-class.html#__init__ bitten.slave.BuildSlave._execute_build bitten.slave.BuildSlave-class.html#_execute_build bitten.slave.BuildSlave._execute_step bitten.slave.BuildSlave-class.html#_execute_step bitten.slave.BuildSlave.run bitten.slave.BuildSlave-class.html#run bitten.slave.BuildSlave._cancel_build bitten.slave.BuildSlave-class.html#_cancel_build bitten.slave.BuildSlave._attach_file bitten.slave.BuildSlave-class.html#_attach_file bitten.slave.BuildSlave.request bitten.slave.BuildSlave-class.html#request bitten.slave.ExitSlave bitten.slave.ExitSlave-class.html bitten.slave.ExitSlave.__init__ bitten.slave.ExitSlave-class.html#__init__ bitten.util.compat.HTTPBasicAuthHandler bitten.util.compat.HTTPBasicAuthHandler-class.html bitten.util.compat.HTTPBasicAuthHandler.retry_http_basic_auth bitten.util.compat.HTTPBasicAuthHandler-class.html#retry_http_basic_auth bitten.util.testrunner.XMLTestResult bitten.util.testrunner.XMLTestResult-class.html bitten.util.testrunner.XMLTestResult.__init__ bitten.util.testrunner.XMLTestResult-class.html#__init__ bitten.util.testrunner.XMLTestResult.stopTest bitten.util.testrunner.XMLTestResult-class.html#stopTest bitten.util.testrunner.XMLTestResult.startTest bitten.util.testrunner.XMLTestResult-class.html#startTest bitten.util.testrunner.XMLTestRunner bitten.util.testrunner.XMLTestRunner-class.html bitten.util.testrunner.XMLTestRunner._makeResult bitten.util.testrunner.XMLTestRunner-class.html#_makeResult bitten.util.testrunner.XMLTestRunner.run bitten.util.testrunner.XMLTestRunner-class.html#run bitten.util.testrunner.XMLTestRunner.__init__ bitten.util.testrunner.XMLTestRunner-class.html#__init__ bitten.util.testrunner.unittest bitten.util.testrunner.unittest-class.html bitten.util.testrunner.unittest._run_with_coverage bitten.util.testrunner.unittest-class.html#_run_with_coverage bitten.util.testrunner.unittest.run_tests bitten.util.testrunner.unittest-class.html#run_tests bitten.util.testrunner.unittest.user_options bitten.util.testrunner.unittest-class.html#user_options bitten.util.testrunner.unittest.initialize_options bitten.util.testrunner.unittest-class.html#initialize_options bitten.util.testrunner.unittest.description bitten.util.testrunner.unittest-class.html#description bitten.util.testrunner.unittest._run_with_trace bitten.util.testrunner.unittest-class.html#_run_with_trace bitten.util.testrunner.unittest._run_with_figleaf bitten.util.testrunner.unittest-class.html#_run_with_figleaf bitten.util.testrunner.unittest.finalize_options bitten.util.testrunner.unittest-class.html#finalize_options bitten.util.testrunner.unittest._run_tests bitten.util.testrunner.unittest-class.html#_run_tests bitten.util.xmlio.Element bitten.util.xmlio.Element-class.html bitten.util.xmlio.Element.attr bitten.util.xmlio.Element-class.html#attr bitten.util.xmlio.Fragment.__getitem__ bitten.util.xmlio.Fragment-class.html#__getitem__ bitten.util.xmlio.Fragment.__str__ bitten.util.xmlio.Fragment-class.html#__str__ bitten.util.xmlio.Element.write bitten.util.xmlio.Element-class.html#write bitten.util.xmlio.Fragment.append bitten.util.xmlio.Fragment-class.html#append bitten.util.xmlio.Fragment.children bitten.util.xmlio.Fragment-class.html#children bitten.util.xmlio.Element.__init__ bitten.util.xmlio.Element-class.html#__init__ bitten.util.xmlio.Element.name bitten.util.xmlio.Element-class.html#name bitten.util.xmlio.Fragment bitten.util.xmlio.Fragment-class.html bitten.util.xmlio.Fragment.__getitem__ bitten.util.xmlio.Fragment-class.html#__getitem__ bitten.util.xmlio.Fragment.__str__ bitten.util.xmlio.Fragment-class.html#__str__ bitten.util.xmlio.Fragment.write bitten.util.xmlio.Fragment-class.html#write bitten.util.xmlio.Fragment.__init__ bitten.util.xmlio.Fragment-class.html#__init__ bitten.util.xmlio.Fragment.children bitten.util.xmlio.Fragment-class.html#children bitten.util.xmlio.Fragment.append bitten.util.xmlio.Fragment-class.html#append bitten.util.xmlio.ParsedElement bitten.util.xmlio.ParsedElement-class.html bitten.util.xmlio.ParsedElement.__str__ bitten.util.xmlio.ParsedElement-class.html#__str__ bitten.util.xmlio.ParsedElement.children bitten.util.xmlio.ParsedElement-class.html#children bitten.util.xmlio.ParsedElement.__init__ bitten.util.xmlio.ParsedElement-class.html#__init__ bitten.util.xmlio.ParsedElement.namespace bitten.util.xmlio.ParsedElement-class.html#namespace bitten.util.xmlio.ParsedElement.gettext bitten.util.xmlio.ParsedElement-class.html#gettext bitten.util.xmlio.ParsedElement.write bitten.util.xmlio.ParsedElement-class.html#write bitten.util.xmlio.ParsedElement._node bitten.util.xmlio.ParsedElement-class.html#_node bitten.util.xmlio.ParsedElement.__iter__ bitten.util.xmlio.ParsedElement-class.html#__iter__ bitten.util.xmlio.ParsedElement.attr bitten.util.xmlio.ParsedElement-class.html#attr bitten.util.xmlio.ParsedElement.name bitten.util.xmlio.ParsedElement-class.html#name bitten.web_ui.BittenChrome bitten.web_ui.BittenChrome-class.html bitten.web_ui.BittenChrome.get_navigation_items bitten.web_ui.BittenChrome-class.html#get_navigation_items bitten.web_ui.BittenChrome.get_templates_dirs bitten.web_ui.BittenChrome-class.html#get_templates_dirs trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.web_ui.BittenChrome.get_active_navigation_item bitten.web_ui.BittenChrome-class.html#get_active_navigation_item bitten.web_ui.BittenChrome._implements bitten.web_ui.BittenChrome-class.html#_implements bitten.web_ui.BittenChrome.get_htdocs_dirs bitten.web_ui.BittenChrome-class.html#get_htdocs_dirs bitten.web_ui.BittenChrome.__init__ bitten.web_ui.BittenChrome-class.html#__init__ bitten.web_ui.BuildConfigController bitten.web_ui.BuildConfigController-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.web_ui.BuildConfigController.process_request bitten.web_ui.BuildConfigController-class.html#process_request bitten.web_ui.BuildConfigController.pre_process_request bitten.web_ui.BuildConfigController-class.html#pre_process_request bitten.web_ui.BuildConfigController._render_inprogress bitten.web_ui.BuildConfigController-class.html#_render_inprogress bitten.web_ui.BuildConfigController.__init__ bitten.web_ui.BuildConfigController-class.html#__init__ bitten.web_ui.BuildConfigController.post_process_request bitten.web_ui.BuildConfigController-class.html#post_process_request bitten.web_ui.BuildConfigController._render_overview bitten.web_ui.BuildConfigController-class.html#_render_overview bitten.web_ui.BuildConfigController.get_navigation_items bitten.web_ui.BuildConfigController-class.html#get_navigation_items bitten.web_ui.BuildConfigController.match_request bitten.web_ui.BuildConfigController-class.html#match_request bitten.web_ui.BuildConfigController.get_active_navigation_item bitten.web_ui.BuildConfigController-class.html#get_active_navigation_item bitten.web_ui.BuildConfigController._report_categories_for_config bitten.web_ui.BuildConfigController-class.html#_report_categories_for_config bitten.web_ui.BuildConfigController.chart_style bitten.web_ui.BuildConfigController-class.html#chart_style bitten.web_ui.BuildConfigController._implements bitten.web_ui.BuildConfigController-class.html#_implements bitten.web_ui.BuildConfigController._render_config bitten.web_ui.BuildConfigController-class.html#_render_config bitten.web_ui.BuildController bitten.web_ui.BuildController-class.html bitten.web_ui.BuildController._render_reports bitten.web_ui.BuildController-class.html#_render_reports trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.web_ui.BuildController.process_request bitten.web_ui.BuildController-class.html#process_request bitten.web_ui.BuildController._render_log bitten.web_ui.BuildController-class.html#_render_log bitten.web_ui.BuildController.__init__ bitten.web_ui.BuildController-class.html#__init__ bitten.web_ui.BuildController.report_summarizers bitten.web_ui.BuildController-class.html#report_summarizers bitten.web_ui.BuildController.get_navigation_items bitten.web_ui.BuildController-class.html#get_navigation_items bitten.web_ui.BuildController.log_formatters bitten.web_ui.BuildController-class.html#log_formatters bitten.web_ui.BuildController.get_timeline_events bitten.web_ui.BuildController-class.html#get_timeline_events bitten.web_ui.BuildController.match_request bitten.web_ui.BuildController-class.html#match_request bitten.web_ui.BuildController.get_active_navigation_item bitten.web_ui.BuildController-class.html#get_active_navigation_item bitten.web_ui.BuildController.get_timeline_filters bitten.web_ui.BuildController-class.html#get_timeline_filters bitten.web_ui.BuildController._do_invalidate bitten.web_ui.BuildController-class.html#_do_invalidate bitten.web_ui.BuildController._implements bitten.web_ui.BuildController-class.html#_implements bitten.web_ui.BuildController.render_timeline_event bitten.web_ui.BuildController-class.html#render_timeline_event bitten.web_ui.ReportChartController bitten.web_ui.ReportChartController-class.html bitten.web_ui.ReportChartController.match_request bitten.web_ui.ReportChartController-class.html#match_request trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.web_ui.ReportChartController.process_request bitten.web_ui.ReportChartController-class.html#process_request bitten.web_ui.ReportChartController.generators bitten.web_ui.ReportChartController-class.html#generators bitten.web_ui.ReportChartController._implements bitten.web_ui.ReportChartController-class.html#_implements bitten.web_ui.ReportChartController.__init__ bitten.web_ui.ReportChartController-class.html#__init__ bitten.web_ui.SourceFileLinkFormatter bitten.web_ui.SourceFileLinkFormatter-class.html trac.core.Component.__metaclass__ trac.core.ComponentMeta-class.html bitten.web_ui.SourceFileLinkFormatter.get_formatter bitten.web_ui.SourceFileLinkFormatter-class.html#get_formatter bitten.web_ui.SourceFileLinkFormatter._implements bitten.web_ui.SourceFileLinkFormatter-class.html#_implements bitten.web_ui.SourceFileLinkFormatter._fileref_re bitten.web_ui.SourceFileLinkFormatter-class.html#_fileref_re bitten.web_ui.SourceFileLinkFormatter.__init__ bitten.web_ui.SourceFileLinkFormatter-class.html#__init__ trac.core.ComponentMeta trac.core.ComponentMeta-class.html trac.core.ComponentMeta.__new__ trac.core.ComponentMeta-class.html#__new__ trac.core.ComponentMeta._components trac.core.ComponentMeta-class.html#_components trac.core.ComponentMeta._registry trac.core.ComponentMeta-class.html#_registry Bitten-0.6/doc/api/bitten-module.html000644 000765 000120 00000024750 11536424537 020054 0ustar00simonadmin000000 000000 bitten
Package bitten

Package bitten


Version: 0.6

Submodules

Variables
  PROTOCOL_VERSION = 5
  __package__ = None
Bitten-0.6/doc/api/bitten.admin-module.html000644 000765 000120 00000012641 11536424537 021137 0ustar00simonadmin000000 000000 bitten.admin
Package bitten :: Module admin

Module admin

Implementation of the web administration interface.
Classes
  BuildMasterAdminPageProvider
Web administration panel for configuring the build master.
  BuildConfigurationsAdminPageProvider
Web administration panel for configuring the build master.
Variables
  __package__ = 'bitten'
Bitten-0.6/doc/api/bitten.admin.BuildConfigurationsAdminPageProvider-class.html000644 000765 000120 00000024317 11536424537 030214 0ustar00simonadmin000000 000000 bitten.admin.BuildConfigurationsAdminPageProvider
Package bitten :: Module admin :: Class BuildConfigurationsAdminPageProvider

Class BuildConfigurationsAdminPageProvider

         object --+    
                  |    
trac.core.Component --+
                      |
                     BuildConfigurationsAdminPageProvider

Web administration panel for configuring the build master.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_admin_panels(self, req)
 
render_admin_panel(self, req, cat, page, path_info)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.admin.BuildConfigurationsAdminPageProvider'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.admin.BuildConfigurationsAdminPageProvider'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.admin.BuildMasterAdminPageProvider-class.html000644 000765 000120 00000024227 11536424537 026455 0ustar00simonadmin000000 000000 bitten.admin.BuildMasterAdminPageProvider
Package bitten :: Module admin :: Class BuildMasterAdminPageProvider

Class BuildMasterAdminPageProvider

         object --+    
                  |    
trac.core.Component --+
                      |
                     BuildMasterAdminPageProvider

Web administration panel for configuring the build master.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_admin_panels(self, req)
 
render_admin_panel(self, req, cat, page, path_info)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.admin.BuildMasterAdminPageProvider'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.admin.BuildMasterAdminPageProvider'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.api-module.html000644 000765 000120 00000012555 11536424537 020624 0ustar00simonadmin000000 000000 bitten.api
Package bitten :: Module api

Module api

Interfaces of extension points provided by the Bitten Trac plugin.
Classes
  IBuildListener
Extension point interface for components that need to be notified of build events.
  ILogFormatter
Extension point interface for components that format build log messages.
  IReportSummarizer
Extension point interface for components that render a summary of reports of some kind.
  IReportChartGenerator
Extension point interface for components that generate a chart for a set of reports.
Bitten-0.6/doc/api/bitten.api.IBuildListener-class.html000644 000765 000120 00000024170 11536424537 023315 0ustar00simonadmin000000 000000 bitten.api.IBuildListener
Package bitten :: Module api :: Class IBuildListener

Class IBuildListener

         object --+    
                  |    
trac.core.Interface --+
                      |
                     IBuildListener

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.

Instance Methods
 
build_started(build)
Called when a build slave has accepted a build initiation.
 
build_aborted(build)
Called when a build slave cancels a build or disconnects.
 
build_completed(build)
Called when a build slave has completed a build, regardless of the outcome.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __init__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

build_started(build)

 
Called when a build slave has accepted a build initiation.
Parameters:
  • build (Build) - the build that was started

build_aborted(build)

 
Called when a build slave cancels a build or disconnects.
Parameters:
  • build (Build) - the build that was aborted

build_completed(build)

 
Called when a build slave has completed a build, regardless of the outcome.
Parameters:
  • build (Build) - the build that was completed

Bitten-0.6/doc/api/bitten.api.ILogFormatter-class.html000644 000765 000120 00000017563 11536424540 023157 0ustar00simonadmin000000 000000 bitten.api.ILogFormatter
Package bitten :: Module api :: Class ILogFormatter

Class ILogFormatter

         object --+    
                  |    
trac.core.Interface --+
                      |
                     ILogFormatter

Extension point interface for components that format build log messages.
Instance Methods
basestring
get_formatter(req, build)
Return a function that gets called for every log message.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __init__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

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.

Parameters:
  • req - the request object
  • build (Build) - the build to which the logs belong that should be formatted
Returns: basestring
the formatted log message

Bitten-0.6/doc/api/bitten.api.IReportChartGenerator-class.html000644 000765 000120 00000021152 11536424540 024643 0ustar00simonadmin000000 000000 bitten.api.IReportChartGenerator
Package bitten :: Module api :: Class IReportChartGenerator

Class IReportChartGenerator

         object --+    
                  |    
trac.core.Interface --+
                      |
                     IReportChartGenerator

Extension point interface for components that generate a chart for a set of reports.
Instance Methods
 
get_supported_categories()
Return a list of strings identifying the types of reports this component supports.
 
generate_chart_data(req, config, category)
Generate the data for a report chart.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __init__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

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.

Parameters:
  • req - the request object
  • config (BuildConfig) - the build configuration
  • category (basestring) - the category of reports to include in the chart

Bitten-0.6/doc/api/bitten.api.IReportSummarizer-class.html000644 000765 000120 00000022364 11536424540 024077 0ustar00simonadmin000000 000000 bitten.api.IReportSummarizer
Package bitten :: Module api :: Class IReportSummarizer

Class IReportSummarizer

         object --+    
                  |    
trac.core.Interface --+
                      |
                     IReportSummarizer

Extension point interface for components that render a summary of reports of some kind.
Instance Methods
 
get_supported_categories()
Return a list of strings identifying the types of reports this component supports.
 
render_summary(req, config, build, step, category)
Render a summary for the given report.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __init__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

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.

Parameters:
  • req - the request object
  • config (BuildConfig) - the build configuration
  • build (Build) - the build
  • step (BuildStep) - the build step
  • category (basestring) - the category of the report that should be summarized

Bitten-0.6/doc/api/bitten.build-module.html000644 000765 000120 00000015560 11536424537 021151 0ustar00simonadmin000000 000000 bitten.build
Package bitten :: Package build

Package build

Submodules

Variables
  __package__ = 'bitten.build'
Bitten-0.6/doc/api/bitten.build.api-module.html000644 000765 000120 00000014620 11536424537 021715 0ustar00simonadmin000000 000000 bitten.build.api
Package bitten :: Package build :: Module api

Module api

Functions and classes used to simplify the implementation recipe commands.
Classes
  BuildError
Exception raised when a build fails.
  TimeoutError
Exception raised when the execution of a command times out.
  CommandLine
Simple helper for executing subprocesses.
  FileSet
Utility class for collecting a list of files in a directory that match given name/path patterns.
Variables
  log = logging.getLogger('bitten.build.api')
  __package__ = 'bitten.build'
Bitten-0.6/doc/api/bitten.build.api.BuildError-class.html000644 000765 000120 00000013766 11536424540 023611 0ustar00simonadmin000000 000000 bitten.build.api.BuildError
Package bitten :: Package build :: Module api :: Class BuildError

Class BuildError

              object --+        
                       |        
exceptions.BaseException --+    
                           |    
        exceptions.Exception --+
                               |
                              BuildError

Exception raised when a build fails.
Instance Methods

Inherited from exceptions.Exception: __init__, __new__

Inherited from exceptions.BaseException: __delattr__, __getattribute__, __getitem__, __getslice__, __reduce__, __repr__, __setattr__, __setstate__, __str__, __unicode__

Inherited from object: __format__, __hash__, __reduce_ex__, __sizeof__, __subclasshook__

Properties

Inherited from exceptions.BaseException: args, message

Inherited from object: __class__

Bitten-0.6/doc/api/bitten.build.api.CommandLine-class.html000644 000765 000120 00000023700 11536424540 023713 0ustar00simonadmin000000 000000 bitten.build.api.CommandLine
Package bitten :: Package build :: Module api :: Class CommandLine

Class CommandLine

object --+
         |
        CommandLine

Simple helper for executing subprocesses.
Instance Methods
 
__init__(self, executable, args, input=None, cwd=None, shell=False)
Initialize the CommandLine object.
 
execute(self, timeout=None)
Execute the command, and return a generator for iterating over the output written to the standard output and error streams.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

__init__(self, executable, args, input=None, cwd=None, shell=False)
(Constructor)

 
Initialize the CommandLine object.
Parameters:
  • executable - the name of the program to execute
  • args - a list of arguments to pass to the executable
  • input - string or file-like object containing any input data for the program
  • cwd - the working directory to change to before executing the command
Overrides: object.__init__

execute(self, timeout=None)

 
Execute the command, and return a generator for iterating over the output written to the standard output and error streams.
Parameters:
  • timeout - number of seconds before the external process should be aborted (not supported on Windows without subprocess module / Python 2.4+)

Bitten-0.6/doc/api/bitten.build.api.FileSet-class.html000644 000765 000120 00000031725 11536424540 023066 0ustar00simonadmin000000 000000 bitten.build.api.FileSet
Package bitten :: Package build :: Module api :: Class FileSet

Class FileSet

object --+
         |
        FileSet

Utility class for collecting a list of files in a directory that match given name/path patterns.
Instance Methods
 
__init__(self, basedir, include=None, exclude=None)
Create a file set.
 
__iter__(self)
Iterate over the names of all files in the set.
 
__contains__(self, filename)
Return whether the given file name is in the set.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Variables
  DEFAULT_EXCLUDES = ['CVS/*', '*/CVS/*', '.svn/*', '*/.svn/*', ...
Properties

Inherited from object: __class__

Method Details

__init__(self, basedir, include=None, exclude=None)
(Constructor)

 
Create a file set.
Parameters:
  • basedir - the base directory for all files in the set
  • include - a list of patterns that define which files should be included in the set
  • exclude - a list of patterns that define which files should be excluded from the set
Overrides: object.__init__

__contains__(self, filename)
(In operator)

 
Return whether the given file name is in the set.
Parameters:
  • filename - the name of the file to check

Class Variable Details

DEFAULT_EXCLUDES

Value:
['CVS/*', '*/CVS/*', '.svn/*', '*/.svn/*', '.DS_Store', 'Thumbs.db']

Bitten-0.6/doc/api/bitten.build.api.TimeoutError-class.html000644 000765 000120 00000014025 11536424540 024165 0ustar00simonadmin000000 000000 bitten.build.api.TimeoutError
Package bitten :: Package build :: Module api :: Class TimeoutError

Class TimeoutError

              object --+        
                       |        
exceptions.BaseException --+    
                           |    
        exceptions.Exception --+
                               |
                              TimeoutError

Exception raised when the execution of a command times out.
Instance Methods

Inherited from exceptions.Exception: __init__, __new__

Inherited from exceptions.BaseException: __delattr__, __getattribute__, __getitem__, __getslice__, __reduce__, __repr__, __setattr__, __setstate__, __str__, __unicode__

Inherited from object: __format__, __hash__, __reduce_ex__, __sizeof__, __subclasshook__

Properties

Inherited from exceptions.BaseException: args, message

Inherited from object: __class__

Bitten-0.6/doc/api/bitten.build.config-module.html000644 000765 000120 00000013254 11536424537 022413 0ustar00simonadmin000000 000000 bitten.build.config
Package bitten :: Package build :: Module config

Module config

Support for build slave configuration.
Classes
  ConfigFileNotFound
  Configuration
Encapsulates the configuration of a build machine.
Variables
  log = logging.getLogger('bitten.config')
  __package__ = 'bitten.build'
Bitten-0.6/doc/api/bitten.build.config.ConfigFileNotFound-class.html000644 000765 000120 00000013771 11536424540 025712 0ustar00simonadmin000000 000000 bitten.build.config.ConfigFileNotFound
Package bitten :: Package build :: Module config :: Class ConfigFileNotFound

Class ConfigFileNotFound

              object --+        
                       |        
exceptions.BaseException --+    
                           |    
        exceptions.Exception --+
                               |
                              ConfigFileNotFound

Instance Methods

Inherited from exceptions.Exception: __init__, __new__

Inherited from exceptions.BaseException: __delattr__, __getattribute__, __getitem__, __getslice__, __reduce__, __repr__, __setattr__, __setstate__, __str__, __unicode__

Inherited from object: __format__, __hash__, __reduce_ex__, __sizeof__, __subclasshook__

Properties

Inherited from exceptions.BaseException: args, message

Inherited from object: __class__

Bitten-0.6/doc/api/bitten.build.config.Configuration-class.html000644 000765 000120 00000044156 11536424540 025040 0ustar00simonadmin000000 000000 bitten.build.config.Configuration
Package bitten :: Package build :: Module config :: Class Configuration

Class Configuration

object --+
         |
        Configuration

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).

Instance Methods
 
__init__(self, filename=None, properties=None)
Create the configuration object.
 
__contains__(self, key)
Return whether the configuration contains a value for the specified key.
 
__getitem__(self, key)
Return the value for the specified configuration key.
 
__str__(self)
str(x)
 
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.
 
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.
 
interpolate(self, text, **vars)
Interpolate configuration and environment properties into a string.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

__init__(self, filename=None, properties=None)
(Constructor)

 
Create the configuration object.
Parameters:
  • filename - the path to the configuration file, if any
  • properties - a dictionary of the configuration properties provided on the command-line
Overrides: object.__init__

__contains__(self, key)
(In operator)

 
Return whether the configuration contains a value for the specified key.
Parameters:
  • key - name of the configuration option using dotted notation (for example, "python.path")

__getitem__(self, key)
(Indexing operator)

 
Return the value for the specified configuration key.
Parameters:
  • key - name of the configuration option using dotted notation (for example, "python.path")

__str__(self)
(Informal representation operator)

 
str(x)
Overrides: object.__str__
(inherited documentation)

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.

Parameters:
  • key - name of the configuration option using dotted notation (for example, "ant.home")

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.

Parameters:
  • key - name of the configuration option using dotted notation (for example, "python.path")

interpolate(self, text, **vars)

 

Interpolate configuration and environment properties into a string.

Configuration 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.

Environment properties substitute from environment variables, supporting the common notations of $VAR and ${VAR}.

Parameters:
  • text - the string containing variable references
  • vars - extra variables to use for the interpolation

Bitten-0.6/doc/api/bitten.build.ctools-module.html000644 000765 000120 00000056306 11536424537 022456 0ustar00simonadmin000000 000000 bitten.build.ctools
Package bitten :: Package build :: Module ctools

Module ctools

Recipe commands for build tasks commonly used for C/C++ projects.
Functions
 
configure(ctxt, file_='configure', enable=None, disable=None, with_=None, without=None, cflags=None, cxxflags=None, prefix=None, **kw)
Run a configure script.
 
autoreconf(ctxt, file_='configure', force=None, install=None, symlink=None, warnings=None, prepend_include=None, include=None)
Run the autotoll autoreconf.
 
make(ctxt, target=None, file_=None, keep_going=False, directory=None, jobs=None, args=None)
Execute a Makefile target.
 
cppunit(ctxt, file_=None, srcdir=None)
Collect CppUnit XML data.
 
cunit(ctxt, file_=None, srcdir=None)
Collect CUnit XML data.
 
gcov(ctxt, include=None, exclude=None, prefix=None, root='')
Run gcov to extract coverage data where available.
Variables
  log = logging.getLogger('bitten.build.ctools')
  __package__ = 'bitten.build'
Function Details

configure(ctxt, file_='configure', enable=None, disable=None, with_=None, without=None, cflags=None, cxxflags=None, prefix=None, **kw)

 
Run a configure script.
Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the configure script
  • enable - names of the features to enable, seperated by spaces
  • disable - names of the features to disable, separated by spaces
  • with_ - names of external packages to include
  • without - names of external packages to exclude
  • cflags - CFLAGS to pass to the configure script
  • cxxflags - CXXFLAGS to pass to the configure script
  • prefix - install prefix to pass to the configure script, will be postfixed by the machine name from the build

autoreconf(ctxt, file_='configure', force=None, install=None, symlink=None, warnings=None, prepend_include=None, include=None)

 
Run the autotoll autoreconf.
Parameters:
  • ctxt (Context) - the build context
  • force - consider all files obsolete
  • install - copy missing auxiliary files
  • symlink - install symbolic links instead of copies
  • warnings - report the warnings falling in CATEGORY
  • prepend_include - prepend directories to search path
  • include - append directories to search path

make(ctxt, target=None, file_=None, keep_going=False, directory=None, jobs=None, args=None)

 
Execute a Makefile target.
Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the Makefile
  • keep_going - whether make should keep going when errors are encountered
  • directory - directory in which to build; defaults to project source directory
  • jobs - number of concurrent jobs to run
  • args - command-line arguments to pass to the script

cppunit(ctxt, file_=None, srcdir=None)

 
Collect CppUnit XML data.
Parameters:
  • ctxt (Context) - the build context
  • file_ - path of the file containing the CppUnit results; may contain globbing wildcards to match multiple files
  • srcdir - name of the directory containing the source files, used to link the test results to the corresponding files

cunit(ctxt, file_=None, srcdir=None)

 
Collect CUnit XML data.
Parameters:
  • ctxt (Context) - the build context
  • file_ - path of the file containing the CUnit results; may contain globbing wildcards to match multiple files
  • srcdir - name of the directory containing the source files, used to link the test results to the corresponding files

gcov(ctxt, include=None, exclude=None, prefix=None, root='')

 
Run gcov to extract coverage data where available.
Parameters:
  • ctxt (Context) - the build context
  • include - patterns of files and directories to include
  • exclude - patterns of files and directories that should be excluded
  • prefix - optional prefix name that is added to object files by the build system
  • root - optional root path in which the build system puts the object files

Bitten-0.6/doc/api/bitten.build.hgtools-module.html000644 000765 000120 00000017221 11536424537 022623 0ustar00simonadmin000000 000000 bitten.build.hgtools
Package bitten :: Package build :: Module hgtools

Module hgtools

Recipe commands for Mercurial.
Functions
 
pull(ctxt, revision=None, dir_='.')
pull and update the local working copy from the Mercurial repository.
Variables
  log = logging.getLogger('bitten.build.hgtools')
  __package__ = 'bitten.build'
Function Details

pull(ctxt, revision=None, dir_='.')

 
pull and update the local working copy from the Mercurial repository.
Parameters:
  • ctxt (Context) - the build context
  • revision - the revision to check out
  • dir_ - the name of a local subdirectory containing the working copy

Bitten-0.6/doc/api/bitten.build.javatools-module.html000644 000765 000120 00000027232 11536424537 023151 0ustar00simonadmin000000 000000 bitten.build.javatools
Package bitten :: Package build :: Module javatools

Module javatools

Recipe commands for tools commonly used in Java projects.
Functions
 
ant(ctxt, file_=None, target=None, keep_going=False, args=None)
Run an Ant build.
 
junit(ctxt, file_=None, srcdir=None)
Extract test results from a JUnit XML report.
 
cobertura(ctxt, file_=None)
Extract test coverage information from a Cobertura XML report.
Variables
  log = logging.getLogger('bitten.build.javatools')
  __package__ = 'bitten.build'
Function Details

ant(ctxt, file_=None, target=None, keep_going=False, args=None)

 
Run an Ant build.
Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the Ant build file
  • target - name of the target that should be executed (optional)
  • keep_going - whether Ant should keep going when errors are encountered
  • args - additional arguments to pass to Ant

junit(ctxt, file_=None, srcdir=None)

 
Extract test results from a JUnit XML report.
Parameters:
  • ctxt (Context) - the build context
  • file_ - path to the JUnit XML test results; may contain globbing wildcards for matching multiple results files
  • srcdir - name of the directory containing the test sources, used to link test results to the corresponding source files

cobertura(ctxt, file_=None)

 
Extract test coverage information from a Cobertura XML report.
Parameters:
  • ctxt (Context) - the build context
  • file_ - path to the Cobertura XML output

Bitten-0.6/doc/api/bitten.build.monotools-module.html000644 000765 000120 00000016305 11536424537 023177 0ustar00simonadmin000000 000000 bitten.build.monotools
Package bitten :: Package build :: Module monotools

Module monotools

Recipe commands for tools commonly used in Mono projects.
Functions
 
nunit(ctxt, file_=None)
Extract test results from a NUnit XML report.
Variables
  log = logging.getLogger('bitten.build.monotools')
  __package__ = 'bitten.build'
Function Details

nunit(ctxt, file_=None)

 
Extract test results from a NUnit XML report.
Parameters:
  • ctxt (Context) - the build context
  • file_ - path to the NUnit XML test results; may contain globbing wildcards for matching multiple results files

Bitten-0.6/doc/api/bitten.build.phptools-module.html000644 000765 000120 00000016641 11536424537 023021 0ustar00simonadmin000000 000000 bitten.build.phptools
Package bitten :: Package build :: Module phptools

Module phptools

Recipe commands for tools commonly used in PHP projects.
Functions
 
phing(ctxt, file_=None, target=None, executable=None, args=None)
Run a phing build
 
phpunit(ctxt, file_=None)
Extract test results from a PHPUnit XML report.
 
coverage(ctxt, file_=None)
Extract data from Phing or PHPUnit code coverage report.
Variables
  log = logging.getLogger('bitten.build.phptools')
  __package__ = 'bitten.build'
Bitten-0.6/doc/api/bitten.build.pythontools-module.html000644 000765 000120 00000056517 11536424537 023561 0ustar00simonadmin000000 000000 bitten.build.pythontools
Package bitten :: Package build :: Module pythontools

Module pythontools

Recipe commands for tools commonly used by Python projects.
Functions
 
distutils(ctxt, file_='setup.py', command='build', options=None, timeout=None)
Execute a distutils command.
 
exec_(ctxt, file_=None, module=None, function=None, output=None, args=None, timeout=None)
Execute a Python script.
 
pylint(ctxt, file_=None)
Extract data from a pylint run written to a file.
 
coverage(ctxt, summary=None, coverdir=None, include=None, exclude=None)
Extract data from a coverage.py run.
 
trace(ctxt, summary=None, coverdir=None, include=None, exclude=None)
Extract data from a trace.py run.
 
figleaf(ctxt, summary=None, include=None, exclude=None)
Extract data from a Figleaf run.
 
unittest(ctxt, file_=None)
Extract data from a unittest results file in XML format.
Variables
  log = logging.getLogger('bitten.build.pythontools')
  __package__ = 'bitten.build'
Function Details

distutils(ctxt, file_='setup.py', command='build', options=None, timeout=None)

 
Execute a distutils command.
Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the file defining the distutils setup
  • command - the setup command to execute
  • options - additional options to pass to the command
  • timeout - the number of seconds before the external process should be aborted (has same constraints as CommandLine)

exec_(ctxt, file_=None, module=None, function=None, output=None, args=None, timeout=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

Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the script file to execute
  • module - name of the Python module to execute
  • function - name of the Python function to run
  • output - name of the file to which output should be written
  • args - extra arguments to pass to the script
  • timeout - the number of seconds before the external process should be aborted (has same constraints as CommandLine)

pylint(ctxt, file_=None)

 
Extract data from a pylint run written to a file.
Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the file containing the Pylint output

coverage(ctxt, summary=None, coverdir=None, include=None, exclude=None)

 
Extract data from a coverage.py run.
Parameters:
  • ctxt (Context) - the build context
  • summary - path to the file containing the coverage summary
  • coverdir - name of the directory containing the per-module coverage details
  • include - patterns of files or directories to include in the report
  • exclude - patterns of files or directories to exclude from the report

trace(ctxt, summary=None, coverdir=None, include=None, exclude=None)

 
Extract data from a trace.py run.
Parameters:
  • ctxt (Context) - the build context
  • summary - path to the file containing the coverage summary
  • coverdir - name of the directory containing the per-module coverage details
  • include - patterns of files or directories to include in the report
  • exclude - patterns of files or directories to exclude from the report

figleaf(ctxt, summary=None, include=None, exclude=None)

 
Extract data from a Figleaf run.
Parameters:
  • ctxt (Context) - the build context
  • summary - path to the file containing the coverage summary
  • include - patterns of files or directories to include in the report
  • exclude - patterns of files or directories to exclude from the report

unittest(ctxt, file_=None)

 
Extract data from a unittest results file in XML format.
Parameters:
  • ctxt (Context) - the build context
  • file_ - name of the file containing the test results

Bitten-0.6/doc/api/bitten.build.shtools-module.html000644 000765 000120 00000037505 11536424537 022646 0ustar00simonadmin000000 000000 bitten.build.shtools
Package bitten :: Package build :: Module shtools

Module shtools

Generic recipe commands for executing external processes.
Functions
 
exec_(ctxt, executable=None, file_=None, output=None, args=None, dir_=None, timeout=None)
Execute a program or shell script.
 
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.
 
execute(ctxt, executable=None, file_=None, input_=None, output=None, args=None, dir_=None, filter_=None, timeout=None)
Generic external program execution.
Variables
  log = logging.getLogger('bitten.build.shtools')
  __package__ = 'bitten.build'
Function Details

exec_(ctxt, executable=None, file_=None, output=None, args=None, dir_=None, timeout=None)

 
Execute a program or shell script.
Parameters:
  • ctxt (Context) - the build context
  • executable - name of the executable to run
  • file_ - name of the script file, relative to the project directory, that should be run
  • output - name of the file to which the output of the script should be written
  • args - command-line arguments to pass to the script
  • dir_ - directory to change to before executing the command
  • timeout - the number of seconds before the external process should be aborted (has same constraints as CommandLine)

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.
Parameters:
  • ctxt (Context) - the build context
  • executable - name of the executable to run
  • file_ - name of the script file, relative to the project directory, that should be run
  • input_ - name of the file containing the data that should be passed to the shell script on its standard input stream
  • output - name of the file to which the output of the script should be written
  • args - command-line arguments to pass to the script
  • dir_ - directory to change to before executing the command

execute(ctxt, executable=None, file_=None, input_=None, output=None, args=None, dir_=None, filter_=None, timeout=None)

 

Generic external program execution.

This function is not itself bound to a recipe command, but rather used from other commands.

Parameters:
  • ctxt (Context) - the build context
  • executable - name of the executable to run
  • file_ - name of the script file, relative to the project directory, that should be run
  • input_ - name of the file containing the data that should be passed to the shell script on its standard input stream
  • output - name of the file to which the output of the script should be written
  • args - command-line arguments to pass to the script
  • dir_ - directory to change to before executing the command
  • filter_ - function to filter out messages from the executable stdout
  • timeout - the number of seconds before the external process should be aborted (has same constraints as CommandLine)

Bitten-0.6/doc/api/bitten.build.svntools-module.html000644 000765 000120 00000046316 11536424537 023042 0ustar00simonadmin000000 000000 bitten.build.svntools
Package bitten :: Package build :: Module svntools

Module svntools

Recipe commands for Subversion.
Classes
  Error
Functions
 
copytree(src, dst, symlinks=False)
Recursively copy a directory tree using copy2().
 
checkout(ctxt, url, path=None, revision=None, dir_='.', verbose='false', shared_path=None, username=None, password=None, no_auth_cache='false')
Perform a checkout from a Subversion repository.
 
export(ctxt, url, path=None, revision=None, dir_='.', username=None, password=None, no_auth_cache='false')
Perform an export from a Subversion repository.
 
update(ctxt, revision=None, dir_='.', username=None, password=None, no_auth_cache='false')
Update the local working copy from the Subversion repository.
Variables
  log = logging.getLogger('bitten.build.svntools')
  __package__ = 'bitten.build'
Function Details

copytree(src, dst, symlinks=False)

 

Recursively copy a directory tree using copy2().

If exception(s) occur, an Error is raised with a list of reasons.

If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic links are copied.

Adapted from shtuil.copytree

checkout(ctxt, url, path=None, revision=None, dir_='.', verbose='false', shared_path=None, username=None, password=None, no_auth_cache='false')

 
Perform a checkout from a Subversion repository.
Parameters:
  • ctxt (Context) - the build context
  • url - the URL of the repository
  • path - the path inside the repository
  • revision - the revision to check out
  • dir_ - the name of a local subdirectory to check out into
  • verbose - whether to log the list of checked out files
  • shared_path - a shared directory to do the checkout in, before copying to dir_
  • username - a username of the repository
  • password - a password of the repository
  • no_auth_cache - do not cache authentication tokens

export(ctxt, url, path=None, revision=None, dir_='.', username=None, password=None, no_auth_cache='false')

 
Perform an export from a Subversion repository.
Parameters:
  • ctxt (Context) - the build context
  • url - the URL of the repository
  • path - the path inside the repository
  • revision - the revision to check out
  • dir_ - the name of a local subdirectory to export out into
  • username - a username of the repository
  • password - a password of the repository
  • no_auth_cache - do not cache authentication tokens

update(ctxt, revision=None, dir_='.', username=None, password=None, no_auth_cache='false')

 
Update the local working copy from the Subversion repository.
Parameters:
  • ctxt (Context) - the build context
  • revision - the revision to check out
  • dir_ - the name of a local subdirectory containing the working copy
  • username - a username of the repository
  • password - a password of the repository
  • no_auth_cache - do not cache authentication tokens

Bitten-0.6/doc/api/bitten.build.svntools.Error-class.html000644 000765 000120 00000014560 11536424540 023740 0ustar00simonadmin000000 000000 bitten.build.svntools.Error
Package bitten :: Package build :: Module svntools :: Class Error

Class Error

              object --+                
                       |                
exceptions.BaseException --+            
                           |            
        exceptions.Exception --+        
                               |        
        exceptions.StandardError --+    
                                   |    
         exceptions.EnvironmentError --+
                                       |
                                      Error

Instance Methods

Inherited from exceptions.EnvironmentError: __init__, __new__, __reduce__, __str__

Inherited from exceptions.BaseException: __delattr__, __getattribute__, __getitem__, __getslice__, __repr__, __setattr__, __setstate__, __unicode__

Inherited from object: __format__, __hash__, __reduce_ex__, __sizeof__, __subclasshook__

Properties

Inherited from exceptions.EnvironmentError: errno, filename, strerror

Inherited from exceptions.BaseException: args, message

Inherited from object: __class__

Bitten-0.6/doc/api/bitten.build.xmltools-module.html000644 000765 000120 00000020516 11536424537 023026 0ustar00simonadmin000000 000000 bitten.build.xmltools
Package bitten :: Package build :: Module xmltools

Module xmltools

Recipe commands for XML processing.
Functions
 
transform(ctxt, src=None, dest=None, stylesheet=None)
Apply an XSLT stylesheet to a source XML document.
Variables
  have_libxslt = False
  have_msxml = False
  log = logging.getLogger('bitten.build.xmltools')
  __package__ = 'bitten.build'
Function Details

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.

Parameters:
  • ctxt (Context) - the build context
  • src - name of the XML input file
  • dest - name of the XML output file
  • stylesheet - name of the file containing the XSLT stylesheet

Bitten-0.6/doc/api/bitten.main-module.html000644 000765 000120 00000010223 11536424537 020765 0ustar00simonadmin000000 000000 bitten.main
Package bitten :: Module main

Module main

Classes
  BuildSystem
Bitten-0.6/doc/api/bitten.main.BuildSystem-class.html000644 000765 000120 00000036252 11536424540 023054 0ustar00simonadmin000000 000000 bitten.main.BuildSystem
Package bitten :: Module main :: Class BuildSystem

Class BuildSystem

         object --+    
                  |    
trac.core.Component --+
                      |
                     BuildSystem

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_permission_actions(self)
 
get_wiki_syntax(self)
 
get_link_resolvers(self)
 
get_resource_realms(self)
 
get_resource_url(self, resource, href, **kwargs)
 
get_resource_description(self, resource, format=None, context=None, **kwargs)
 
resource_exists(self, resource)
 
check_attachment_permission(self, action, username, resource, perm)
Respond to the various actions into the legacy attachment permissions used by the Attachment module.
 
__init__(self, compmgr, init=None, cls=<class 'bitten.main.BuildSystem'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties
  listeners
List of components that implement IBuildListener

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.main.BuildSystem'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Property Details

listeners

List of components that implement IBuildListener
Get Method:
unreachable.extensions(component) - Return a list of components that declare to implement the extension point interface.

Bitten-0.6/doc/api/bitten.master-module.html000644 000765 000120 00000010377 11536424537 021346 0ustar00simonadmin000000 000000 bitten.master
Package bitten :: Module master

Module master

Build master implementation.
Classes
  BuildMaster
Trac request handler implementation for the build master.
Bitten-0.6/doc/api/bitten.master.BuildMaster-class.html000644 000765 000120 00000031001 11536424540 023355 0ustar00simonadmin000000 000000 bitten.master.BuildMaster
Package bitten :: Module master :: Class BuildMaster

Class BuildMaster

         object --+    
                  |    
trac.core.Component --+
                      |
                     BuildMaster

Trac request handler implementation for the build master.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
__init__(self, compmgr, init=<function __init__ at 0x1022e8d70>, cls=<class 'bitten.master.BuildMaster'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 
match_request(self, req)
 
process_request(self, req)

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Class Variables
  adjust_timestamps = <BoolOption [bitten] "adjust_timestamps">
  build_all = <BoolOption [bitten] "build_all">
  stabilize_wait = <IntOption [bitten] "stabilize_wait">
  slave_timeout = <IntOption [bitten] "slave_timeout">
  logs_dir = <Option [bitten] "logs_dir">
  quick_status = <BoolOption [bitten] "quick_status">
Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=<function __init__ at 0x1022e8d70>, cls=<class 'bitten.master.BuildMaster'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.model-module.html000644 000765 000120 00000020142 11536424537 021142 0ustar00simonadmin000000 000000 bitten.model
Package bitten :: Module model

Module model

Model classes for objects persisted in the database.
Classes
  BuildConfig
Representation of a build configuration.
  TargetPlatform
Target platform for a build configuration.
  Build
Representation of a build.
  BuildStep
Represents an individual step of an executed build.
  BuildLog
Represents a build log.
  Report
Represents a generated report.
Variables
  schema = BuildConfig._schema+ TargetPlatform._schema+ Build._s...
  schema_version = 12
  __package__ = 'bitten'
Variables Details

schema

Value:
BuildConfig._schema+ TargetPlatform._schema+ Build._schema+ BuildStep.\
_schema+ BuildLog._schema+ Report._schema

Bitten-0.6/doc/api/bitten.model.Build-class.html000644 000765 000120 00000055257 11536424540 022031 0ustar00simonadmin000000 000000 bitten.model.Build
Package bitten :: Module model :: Class Build

Class Build

object --+
         |
        Build

Representation of a build.
Instance Methods
 
__init__(self, env, config=None, rev=None, platform=None, slave=None, started=0, stopped=0, last_activity=0, rev_time=0, status='P')
Initialize a new build with the specified attributes.
 
__repr__(self)
repr(x)
 
delete(self, db=None)
Remove the build from the database.
 
insert(self, db=None)
Insert a new build into the database.
 
update(self, db=None)
Save changes to an existing build.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Methods
 
fetch(cls, env, id, db=None)
Retrieve an existing build from the database by ID.
 
select(cls, env, config=None, rev=None, platform=None, slave=None, status=None, db=None, min_rev_time=None, max_rev_time=None)
Retrieve existing builds from the database that match the specified criteria.
Class Variables
  PENDING = 'P'
  IN_PROGRESS = 'I'
  SUCCESS = 'S'
  FAILURE = 'F'
  IP_ADDRESS = 'ipnr'
  MAINTAINER = 'owner'
  OS_NAME = 'os'
  OS_FAMILY = 'family'
  OS_VERSION = 'version'
  MACHINE = 'machine'
  PROCESSOR = 'processor'
  TOKEN = 'token'
Properties
  exists
Whether this build exists in the database
  completed
Whether the build has been completed
  successful
Whether the build was successful
  resource
Build resource identification

Inherited from object: __class__

Method Details

__init__(self, env, config=None, rev=None, platform=None, slave=None, started=0, stopped=0, last_activity=0, rev_time=0, status='P')
(Constructor)

 

Initialize a new build with the specified attributes.

To actually create this build in the database, the insert method needs to be called.

Overrides: object.__init__

__repr__(self)
(Representation operator)

 
repr(x)
Overrides: object.__repr__
(inherited documentation)

Property Details

exists

Whether this build exists in the database
Get Method:
unreachable(self)

completed

Whether the build has been completed
Get Method:
unreachable(self)

successful

Whether the build was successful
Get Method:
unreachable(self)

resource

Build resource identification
Get Method:
unreachable(self) - build

Bitten-0.6/doc/api/bitten.model.BuildConfig-class.html000644 000765 000120 00000044445 11536424540 023154 0ustar00simonadmin000000 000000 bitten.model.BuildConfig
Package bitten :: Module model :: Class BuildConfig

Class BuildConfig

object --+
         |
        BuildConfig

Representation of a build configuration.
Instance Methods
 
__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.
 
__repr__(self)
repr(x)
 
delete(self, db=None)
Remove a build configuration and all dependent objects from the database.
 
insert(self, db=None)
Insert a new configuration into the database.
 
update(self, db=None)
Save changes to an existing build configuration.
 
min_rev_time(self, env)
Returns the time of the minimum revision being built for this configuration.
 
max_rev_time(self, env)
Returns the time of the maximum revision being built for this configuration.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Methods
 
fetch(cls, env, name, db=None)
Retrieve an existing build configuration from the database by name.
 
select(cls, env, include_inactive=False, db=None)
Retrieve existing build configurations from the database that match the specified criteria.
Properties
  exists
Whether this configuration exists in the database
  resource
Build Config resource identification

Inherited from object: __class__

Method Details

__init__(self, env, name=None, path=None, active=False, recipe=None, min_rev=None, max_rev=None, label=None, description=None)
(Constructor)

 

Initialize a new build configuration with the specified attributes.

To actually create this configuration in the database, the insert method needs to be called.

Overrides: object.__init__

__repr__(self)
(Representation operator)

 
repr(x)
Overrides: object.__repr__
(inherited documentation)

min_rev_time(self, env)

 
Returns the time of the minimum revision being built for this configuration. Returns utcmin if not specified.

max_rev_time(self, env)

 
Returns the time of the maximum revision being built for this configuration. Returns utcmax if not specified.

Property Details

exists

Whether this configuration exists in the database
Get Method:
unreachable(self)

resource

Build Config resource identification
Get Method:
unreachable(self) - build

Bitten-0.6/doc/api/bitten.model.BuildLog-class.html000644 000765 000120 00000037412 11536424540 022464 0ustar00simonadmin000000 000000 bitten.model.BuildLog
Package bitten :: Module model :: Class BuildLog

Class BuildLog

object --+
         |
        BuildLog

Represents a build log.
Instance Methods
 
__init__(self, env, build=None, step=None, generator=None, orderno=None, filename=None)
Initialize a new build log with the specified attributes.
 
get_log_file(self, filename)
Returns the full path to the log file
 
delete(self, db=None)
Remove the build log from the database.
 
insert(self, db=None)
Insert a new build log into the database.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Methods
 
fetch(cls, env, id, db=None)
Retrieve an existing build from the database by ID.
 
select(cls, env, build=None, step=None, generator=None, db=None)
Retrieve existing build logs from the database that match the specified criteria.
Class Variables
  DEBUG = 'D'
  INFO = 'I'
  WARNING = 'W'
  ERROR = 'E'
  UNKNOWN = ''
  LEVELS_SUFFIX = '.levels'
Properties
  exists
Whether this build log exists in the database

Inherited from object: __class__

Method Details

__init__(self, env, build=None, step=None, generator=None, orderno=None, filename=None)
(Constructor)

 

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.

Overrides: object.__init__

Property Details

exists

Whether this build log exists in the database
Get Method:
unreachable(self)

Bitten-0.6/doc/api/bitten.model.BuildStep-class.html000644 000765 000120 00000037445 11536424540 022664 0ustar00simonadmin000000 000000 bitten.model.BuildStep
Package bitten :: Module model :: Class BuildStep

Class BuildStep

object --+
         |
        BuildStep

Represents an individual step of an executed build.
Instance Methods
 
__init__(self, env, build=None, name=None, description=None, status=None, started=None, stopped=None)
Initialize a new build step with the specified attributes.
 
delete(self, db=None)
Remove the build step from the database.
 
insert(self, db=None)
Insert a new build step into the database.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Methods
 
fetch(cls, env, build, name, db=None)
Retrieve an existing build from the database by build ID and step name.
 
select(cls, env, build=None, name=None, status=None, db=None)
Retrieve existing build steps from the database that match the specified criteria.
Class Variables
  SUCCESS = 'S'
  IN_PROGRESS = 'I'
  FAILURE = 'F'
Properties
  exists
Whether this build step exists in the database
  successful
Whether the build step was successful
  completed
Whether this build step has completed processing

Inherited from object: __class__

Method Details

__init__(self, env, build=None, name=None, description=None, status=None, started=None, stopped=None)
(Constructor)

 

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.

Overrides: object.__init__

Property Details

exists

Whether this build step exists in the database
Get Method:
unreachable(self)

successful

Whether the build step was successful
Get Method:
unreachable(self)

completed

Whether this build step has completed processing
Get Method:
unreachable(self)

Bitten-0.6/doc/api/bitten.model.Report-class.html000644 000765 000120 00000030703 11536424540 022232 0ustar00simonadmin000000 000000 bitten.model.Report
Package bitten :: Module model :: Class Report

Class Report

object --+
         |
        Report

Represents a generated report.
Instance Methods
 
__init__(self, env, build=None, step=None, category=None, generator=None)
Initialize a new report with the specified attributes.
 
delete(self, db=None)
Remove the report from the database.
 
insert(self, db=None)
Insert a new build log into the database.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Methods
 
fetch(cls, env, id, db=None)
Retrieve an existing build from the database by ID.
 
select(cls, env, config=None, build=None, step=None, category=None, db=None)
Retrieve existing reports from the database that match the specified criteria.
Properties
  exists
Whether this report exists in the database

Inherited from object: __class__

Method Details

__init__(self, env, build=None, step=None, category=None, generator=None)
(Constructor)

 

Initialize a new report with the specified attributes.

To actually create this build log in the database, the insert method needs to be called.

Overrides: object.__init__

Property Details

exists

Whether this report exists in the database
Get Method:
unreachable(self)

Bitten-0.6/doc/api/bitten.model.TargetPlatform-class.html000644 000765 000120 00000033447 11536424540 023722 0ustar00simonadmin000000 000000 bitten.model.TargetPlatform
Package bitten :: Module model :: Class TargetPlatform

Class TargetPlatform

object --+
         |
        TargetPlatform

Target platform for a build configuration.
Instance Methods
 
__init__(self, env, config=None, name=None)
Initialize a new target platform with the specified attributes.
 
__repr__(self)
repr(x)
 
delete(self, db=None)
Remove the target platform from the database.
 
insert(self, db=None)
Insert a new target platform into the database.
 
update(self, db=None)
Save changes to an existing target platform.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Methods
 
fetch(cls, env, id, db=None)
Retrieve an existing target platform from the database by ID.
 
select(cls, env, config=None, db=None)
Retrieve existing target platforms from the database that match the specified criteria.
Properties
  exists
Whether this target platform exists in the database

Inherited from object: __class__

Method Details

__init__(self, env, config=None, name=None)
(Constructor)

 

Initialize a new target platform with the specified attributes.

To actually create this platform in the database, the insert method needs to be called.

Overrides: object.__init__

__repr__(self)
(Representation operator)

 
repr(x)
Overrides: object.__repr__
(inherited documentation)

Property Details

exists

Whether this target platform exists in the database
Get Method:
unreachable(self)

Bitten-0.6/doc/api/bitten.notify-module.html000644 000765 000120 00000012377 11536424537 021365 0ustar00simonadmin000000 000000 bitten.notify
Package bitten :: Module notify

Module notify

Classes
  BittenNotify
Sends notifications on build status by mail.
  BuildNotifyEmail
Notification of failed builds.
Variables
  __package__ = 'bitten'
Bitten-0.6/doc/api/bitten.notify.BittenNotify-class.html000644 000765 000120 00000036066 11536424540 023615 0ustar00simonadmin000000 000000 bitten.notify.BittenNotify
Package bitten :: Module notify :: Class BittenNotify

Class BittenNotify

         object --+    
                  |    
trac.core.Component --+
                      |
                     BittenNotify

Sends notifications on build status by mail.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
__init__(self, compmgr, init=<function __init__ at 0x102846aa0>, cls=<class 'bitten.notify.BittenNotify'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 
notify(self, build=None)
 
build_started(self, build)
build started
 
build_aborted(self, build)
build aborted
 
build_completed(self, build)
build completed
 
get_templates_dirs(self)
Return a list of directories containing the provided template files.
 
get_htdocs_dirs(self)
Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc).

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Class Variables
  notify_on_failure = <BoolOption [notification] "notify_on_fail...
  notify_on_success = <BoolOption [notification] "notify_on_succ...
Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=<function __init__ at 0x102846aa0>, cls=<class 'bitten.notify.BittenNotify'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Class Variable Details

notify_on_failure

Value:
<BoolOption [notification] "notify_on_failed_build">

notify_on_success

Value:
<BoolOption [notification] "notify_on_successful_build">

Bitten-0.6/doc/api/bitten.notify.BuildNotifyEmail-class.html000644 000765 000120 00000041531 11536424540 024370 0ustar00simonadmin000000 000000 bitten.notify.BuildNotifyEmail
Package bitten :: Module notify :: Class BuildNotifyEmail

Class BuildNotifyEmail

               object --+        
                        |        
 trac.notification.Notify --+    
                            |    
trac.notification.NotifyEmail --+
                                |
                               BuildNotifyEmail

Notification of failed builds.
Instance Methods
 
__init__(self, env)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 
notify(self, build)
 
get_recipients(self, resid)
Return a pair of list of subscribers to the resource 'resid'.
 
send(self, torcpts, ccrcpts)
Send message to recipients.
 
build_link(self)
 
template_data(self)
 
get_all_log_messages_for_step(self, step)
 
get_changeset(self)
 
get_author(self)

Inherited from trac.notification.NotifyEmail: add_headers, begin_send, encode_header, finish_send, format_header, get_smtp_address

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Variables
  readable_states = {'F': 'Failed', 'S': 'Successful'}
  template_name = 'bitten_notify_email.txt'
  from_email = 'bitten@localhost'

Inherited from trac.notification.NotifyEmail: addrsep_re, nodomaddr_re, smtp_port, smtp_server, subject

Properties

Inherited from object: __class__

Method Details

__init__(self, env)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

notify(self, build)

 
Overrides: trac.notification.Notify.notify

get_recipients(self, resid)

 

Return a pair of list of subscribers to the resource 'resid'.

First list represents the direct recipients (To:), second list represents the recipients in carbon copy (Cc:).

Overrides: trac.notification.Notify.get_recipients
(inherited documentation)

send(self, torcpts, ccrcpts)

 
Send message to recipients.
Overrides: trac.notification.Notify.send
(inherited documentation)

Bitten-0.6/doc/api/bitten.queue-module.html000644 000765 000120 00000021331 11536424537 021167 0ustar00simonadmin000000 000000 bitten.queue
Package bitten :: Module queue

Module queue

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.

Classes
  BuildQueue
Enapsulates the build queue of an environment.
Functions
 
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.
Variables
  __package__ = 'bitten'
Function Details

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.

Parameters:
  • repos - the version control repository
  • config - the build configuration
  • db - a database connection (optional)

Bitten-0.6/doc/api/bitten.queue.BuildQueue-class.html000644 000765 000120 00000037222 11536424540 023052 0ustar00simonadmin000000 000000 bitten.queue.BuildQueue
Package bitten :: Module queue :: Class BuildQueue

Class BuildQueue

object --+
         |
        BuildQueue

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.

Instance Methods
 
__init__(self, env, build_all=False, stabilize_wait=0, timeout=0)
Create the build queue.
Build
get_build_for_slave(self, name, properties)
Check whether one of the pending builds can be built by the build slave.
 
match_slave(self, name, properties)
Match a build slave against available target platforms.
 
populate(self)
Add a build for the next change on each build configuration to the queue.
 
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.
 
should_delete_build(self, build, repos)

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

__init__(self, env, build_all=False, stabilize_wait=0, timeout=0)
(Constructor)

 
Create the build queue.
Parameters:
  • env - the Trac environment
  • build_all - whether older revisions should be built
  • stabilize_wait - The time in seconds to wait before considering the repository stable to create a build in the queue.
  • timeout - the time in seconds after which an in-progress build should be considered orphaned, and reset to pending state
Overrides: object.__init__

get_build_for_slave(self, name, properties)

 
Check whether one of the pending builds can be built by the build slave.
Parameters:
  • name (basestring) - the name of the slave
  • properties (dict) - the slave configuration
Returns: Build
the allocated build, or None if no build was found

match_slave(self, name, properties)

 
Match a build slave against available target platforms.
Parameters:
  • name (basestring) - the name of the slave
  • properties (dict) - the slave configuration
Returns:
the list of platforms the slave matched

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.

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.


Bitten-0.6/doc/api/bitten.recipe-module.html000644 000765 000120 00000012445 11536424537 021320 0ustar00simonadmin000000 000000 bitten.recipe
Package bitten :: Module recipe

Module recipe

Execution of build recipes.

This module provides various classes that can be used to process build recipes, most importantly the Recipe class.

Classes
  InvalidRecipeError
Exception raised when a recipe is not valid.
  Context
The context in which a build is executed.
  Step
Represents a single step of a build recipe.
  Recipe
A build recipe.
Bitten-0.6/doc/api/bitten.recipe.Context-class.html000644 000765 000120 00000050036 11536424540 022553 0ustar00simonadmin000000 000000 bitten.recipe.Context
Package bitten :: Module recipe :: Class Context

Class Context

object --+
         |
        Context

The context in which a build is executed.
Instance Methods
 
__init__(self, basedir, config=None, vars=None)
Initialize the context.
 
run(self, step, namespace, name, attr)
Run the specified recipe command.
 
error(self, message)
Record an error message.
 
log(self, xml)
Record log output.
 
report(self, category, xml)
Record report data.
 
report_file(self, category=None, file_=None)
Read report data from a file and record it.
 
attach(self, file_=None, description=None, resource=None)
Attach a file to the build or build configuration.
 
resolve(self, *path)
Return the path of a file relative to the base directory.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Variables
  step = None
  generator = None
Properties

Inherited from object: __class__

Method Details

__init__(self, basedir, config=None, vars=None)
(Constructor)

 
Initialize the context.
Parameters:
  • basedir - a string containing the working directory for the build. (may be a pattern for replacement ex: 'build_${build}'
  • config (Configuration) - the build slave configuration
Overrides: object.__init__

run(self, step, namespace, name, attr)

 
Run the specified recipe command.
Parameters:
  • step - the build step that the command belongs to
  • namespace - the namespace URI of the command
  • name - the local tag name of the command
  • attr - a dictionary containing the attributes defined on the command element

error(self, message)

 
Record an error message.
Parameters:
  • message - a string containing the error message.

log(self, xml)

 
Record log output.
Parameters:
  • xml - an XML fragment containing the log messages

report(self, category, xml)

 
Record report data.
Parameters:
  • category - the name of category of the report
  • xml - an XML fragment containing the report data

report_file(self, category=None, file_=None)

 
Read report data from a file and record it.
Parameters:
  • category - the name of the category of the report
  • file_ - the path to the file containing the report data, relative to the base directory

attach(self, file_=None, description=None, resource=None)

 
Attach a file to the build or build configuration.
Parameters:
  • file_ - the path to the file to attach, relative to base directory.
  • description - description saved with attachment
  • resource - which resource to attach the file to, either 'build' (default) or 'config'

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.


Bitten-0.6/doc/api/bitten.recipe.InvalidRecipeError-class.html000644 000765 000120 00000013726 11536424540 024664 0ustar00simonadmin000000 000000 bitten.recipe.InvalidRecipeError
Package bitten :: Module recipe :: Class InvalidRecipeError

Class InvalidRecipeError

              object --+        
                       |        
exceptions.BaseException --+    
                           |    
        exceptions.Exception --+
                               |
                              InvalidRecipeError

Exception raised when a recipe is not valid.
Instance Methods

Inherited from exceptions.Exception: __init__, __new__

Inherited from exceptions.BaseException: __delattr__, __getattribute__, __getitem__, __getslice__, __reduce__, __repr__, __setattr__, __setstate__, __str__, __unicode__

Inherited from object: __format__, __hash__, __reduce_ex__, __sizeof__, __subclasshook__

Properties

Inherited from exceptions.BaseException: args, message

Inherited from object: __class__

Bitten-0.6/doc/api/bitten.recipe.Recipe-class.html000644 000765 000120 00000030667 11536424540 022346 0ustar00simonadmin000000 000000 bitten.recipe.Recipe
Package bitten :: Module recipe :: Class Recipe

Class Recipe

object --+
         |
        Recipe

A build recipe.

Iterate over this object to get the individual build steps in the order they have been defined in the recipe file.

Instance Methods
 
__init__(self, xml, basedir='/Users/simon/dev/projects/bitten/repos/bitten-0.6', config=None)
Create the recipe.
 
__iter__(self)
Iterate over the individual steps of the recipe.
 
validate(self)
Validate the recipe.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Variables
  ERROR = 'error'
  LOG = 'log'
  REPORT = 'report'
  ATTACH = 'attach'
Properties

Inherited from object: __class__

Method Details

__init__(self, xml, basedir='/Users/simon/dev/projects/bitten/repos/bitten-0.6', config=None)
(Constructor)

 
Create the recipe.
Parameters:
  • xml (ParsedElement) - the XML document representing the recipe
  • basedir - the base directory for the build
  • config (Configuration) - the slave configuration (optional)
Overrides: object.__init__

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
Raises:

Bitten-0.6/doc/api/bitten.recipe.Step-class.html000644 000765 000120 00000023661 11536424540 022046 0ustar00simonadmin000000 000000 bitten.recipe.Step
Package bitten :: Module recipe :: Class Step

Class Step

object --+
         |
        Step

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.

Instance Methods
 
__init__(self, elem, onerror_default)
Create the step.
 
__repr__(self)
repr(x)
 
execute(self, ctxt)
Execute this step in the given context.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

__init__(self, elem, onerror_default)
(Constructor)

 
Create the step.
Parameters:
Overrides: object.__init__

__repr__(self)
(Representation operator)

 
repr(x)
Overrides: object.__repr__
(inherited documentation)

execute(self, ctxt)

 
Execute this step in the given context.
Parameters:
  • ctxt (Context) - the build context

Bitten-0.6/doc/api/bitten.report-module.html000644 000765 000120 00000011732 11536424537 021362 0ustar00simonadmin000000 000000 bitten.report
Package bitten :: Package report

Package report

Submodules

Variables
  __package__ = None
Bitten-0.6/doc/api/bitten.report.coverage-module.html000644 000765 000120 00000013422 11536424537 023152 0ustar00simonadmin000000 000000 bitten.report.coverage
Package bitten :: Package report :: Module coverage

Module coverage

Classes
  TestCoverageChartGenerator
  TestCoverageSummarizer
  TestCoverageAnnotator
Version in the branch should not match: >>> context = Context.from_request(req, 'source', '/branches/blah/foo.py', 123) >>> ann.get_annotation_data(context) []
Variables
  __package__ = 'bitten.report'
Bitten-0.6/doc/api/bitten.report.coverage.TestCoverageAnnotator-class.html000644 000765 000120 00000040231 11536424540 027242 0ustar00simonadmin000000 000000 bitten.report.coverage.TestCoverageAnnotator
Package bitten :: Package report :: Module coverage :: Class TestCoverageAnnotator

Class TestCoverageAnnotator

         object --+    
                  |    
trac.core.Component --+
                      |
                     TestCoverageAnnotator

>>> from genshi.builder import tag
>>> from trac.test import Mock, MockPerm
>>> from trac.mimeview import Context
>>> from trac.util.datefmt import to_datetime, utc
>>> 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()
>>> repos = Mock(get_changeset=lambda x: Mock(date=to_datetime(12345, utc)))
>>> env.get_repository = lambda: repos
>>> 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={'warnings': []}, args={})

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>'
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
pre_process_request(self, req, handler)
 
post_process_request(self, req, template, data, content_type)
Adds a 'Coverage' context navigation menu item.
 
get_annotation_type(self)
 
get_annotation_data(self, context)
 
annotate_row(self, context, row, lineno, line, data)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.coverage.TestCoverageAnnotator'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.coverage.TestCoverageAnnotator'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.report.coverage.TestCoverageChartGenerator-class.html000644 000765 000120 00000024170 11536424540 030211 0ustar00simonadmin000000 000000 bitten.report.coverage.TestCoverageChartGenerator
Package bitten :: Package report :: Module coverage :: Class TestCoverageChartGenerator

Class TestCoverageChartGenerator

         object --+    
                  |    
trac.core.Component --+
                      |
                     TestCoverageChartGenerator

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_supported_categories(self)
 
generate_chart_data(self, req, config, category)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.coverage.TestCoverageChartGenerator'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.coverage.TestCoverageChartGenerator'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.report.coverage.TestCoverageSummarizer-class.html000644 000765 000120 00000024271 11536424540 027441 0ustar00simonadmin000000 000000 bitten.report.coverage.TestCoverageSummarizer
Package bitten :: Package report :: Module coverage :: Class TestCoverageSummarizer

Class TestCoverageSummarizer

         object --+    
                  |    
trac.core.Component --+
                      |
                     TestCoverageSummarizer

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_supported_categories(self)
 
render_summary(self, req, config, build, step, category)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.coverage.TestCoverageSummarizer'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.coverage.TestCoverageSummarizer'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.report.lint-module.html000644 000765 000120 00000012413 11536424537 022324 0ustar00simonadmin000000 000000 bitten.report.lint
Package bitten :: Package report :: Module lint

Module lint

Classes
  PyLintChartGenerator
  PyLintSummarizer
Variables
  __package__ = 'bitten.report'
Bitten-0.6/doc/api/bitten.report.lint.PyLintChartGenerator-class.html000644 000765 000120 00000024066 11536424540 026214 0ustar00simonadmin000000 000000 bitten.report.lint.PyLintChartGenerator
Package bitten :: Package report :: Module lint :: Class PyLintChartGenerator

Class PyLintChartGenerator

         object --+    
                  |    
trac.core.Component --+
                      |
                     PyLintChartGenerator

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_supported_categories(self)
 
generate_chart_data(self, req, config, category)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.lint.PyLintChartGenerator'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.lint.PyLintChartGenerator'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.report.lint.PyLintSummarizer-class.html000644 000765 000120 00000024167 11536424540 025444 0ustar00simonadmin000000 000000 bitten.report.lint.PyLintSummarizer
Package bitten :: Package report :: Module lint :: Class PyLintSummarizer

Class PyLintSummarizer

         object --+    
                  |    
trac.core.Component --+
                      |
                     PyLintSummarizer

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_supported_categories(self)
 
render_summary(self, req, config, build, step, category)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.lint.PyLintSummarizer'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.lint.PyLintSummarizer'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.report.testing-module.html000644 000765 000120 00000012456 11536424537 023042 0ustar00simonadmin000000 000000 bitten.report.testing
Package bitten :: Package report :: Module testing

Module testing

Classes
  TestResultsChartGenerator
  TestResultsSummarizer
Variables
  __package__ = 'bitten.report'
Bitten-0.6/doc/api/bitten.report.testing.TestResultsChartGenerator-class.html000644 000765 000120 00000024153 11536424540 030002 0ustar00simonadmin000000 000000 bitten.report.testing.TestResultsChartGenerator
Package bitten :: Package report :: Module testing :: Class TestResultsChartGenerator

Class TestResultsChartGenerator

         object --+    
                  |    
trac.core.Component --+
                      |
                     TestResultsChartGenerator

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_supported_categories(self)
 
generate_chart_data(self, req, config, category)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.testing.TestResultsChartGenerator'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.testing.TestResultsChartGenerator'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.report.testing.TestResultsSummarizer-class.html000644 000765 000120 00000024254 11536424540 027232 0ustar00simonadmin000000 000000 bitten.report.testing.TestResultsSummarizer
Package bitten :: Package report :: Module testing :: Class TestResultsSummarizer

Class TestResultsSummarizer

         object --+    
                  |    
trac.core.Component --+
                      |
                     TestResultsSummarizer

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_supported_categories(self)
 
render_summary(self, req, config, build, step, category)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.report.testing.TestResultsSummarizer'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.report.testing.TestResultsSummarizer'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.slave-module.html000644 000765 000120 00000011127 11536424537 021157 0ustar00simonadmin000000 000000 bitten.slave
Package bitten :: Module slave

Module slave

Implementation of the build slave.
Classes
  BuildSlave
HTTP client implementation for the build slave.
  ExitSlave
Exception used internally by the slave to signal that the slave process should be stopped.
Bitten-0.6/doc/api/bitten.slave.BuildSlave-class.html000644 000765 000120 00000035667 11536424540 023041 0ustar00simonadmin000000 000000 bitten.slave.BuildSlave
Package bitten :: Module slave :: Class BuildSlave

Class BuildSlave

object --+
         |
        BuildSlave

HTTP client implementation for the build slave.
Instance Methods
 
__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, keepalive_interval=60, username=None, password=None, dump_reports=False, no_loop=False, form_auth=False)
Create the build slave instance.
 
request(self, method, url, body=None, headers=None)
 
run(self)
 
quit(self)

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties
  opener

Inherited from object: __class__

Method Details

__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, keepalive_interval=60, username=None, password=None, dump_reports=False, no_loop=False, form_auth=False)
(Constructor)

 
Create the build slave instance.
Parameters:
  • 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
  • name - the name with which this slave should identify itself
  • config - the path to the slave configuration file
  • dry_run - wether the build outcome should not be reported back to the master
  • work_dir - the working directory to use for build execution
  • build_dir - the pattern to use for naming the build subdir
  • keep_files - whether files and directories created for build execution should be kept when done
  • single_build - whether this slave should exit after completing a single build, or continue processing builds forever
  • poll_interval - the time in seconds to wait between requesting builds from the build master (default is five minutes)
  • keepalive_interval - the time in seconds to wait between sending keepalive heartbeats (default is 30 seconds)
  • username - the username to use when authentication against the build master is requested
  • password - the password to use when authentication is needed
  • dump_reports - whether report data should be written to the standard output, in addition to being transmitted to the build master
  • no_loop - for this slave to just perform a single check, regardless of whether a build is done or not
  • form_auth - login using AccountManager HTML form instead of HTTP authentication for all urls
Overrides: object.__init__

Property Details

opener

Get Method:
_get_opener(self)

Bitten-0.6/doc/api/bitten.slave.ExitSlave-class.html000644 000765 000120 00000017374 11536424540 022706 0ustar00simonadmin000000 000000 bitten.slave.ExitSlave
Package bitten :: Module slave :: Class ExitSlave

Class ExitSlave

              object --+        
                       |        
exceptions.BaseException --+    
                           |    
        exceptions.Exception --+
                               |
                              ExitSlave

Exception used internally by the slave to signal that the slave process should be stopped.
Instance Methods
 
__init__(self, exit_code)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from exceptions.Exception: __new__

Inherited from exceptions.BaseException: __delattr__, __getattribute__, __getitem__, __getslice__, __reduce__, __repr__, __setattr__, __setstate__, __str__, __unicode__

Inherited from object: __format__, __hash__, __reduce_ex__, __sizeof__, __subclasshook__

Properties

Inherited from exceptions.BaseException: args, message

Inherited from object: __class__

Method Details

__init__(self, exit_code)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.upgrades-module.html000644 000765 000120 00000056662 11536424537 021674 0ustar00simonadmin000000 000000 bitten.upgrades
Package bitten :: Module upgrades

Module upgrades

Automated upgrades for the Bitten database tables, and other data stored in the Trac environment.

Do not import and call directly!

Functions
 
parse_scheme(env)
Retrieve the environment database scheme.
 
update_sequence(env, db, tbl, col)
Update a sequence associated with an autoincrement column.
 
drop_index(env, db, tbl, idx)
Drop an index associated with a table.
 
add_log_table(env, db)
Add a table for storing the builds logs.
 
add_recipe_to_config(env, db)
Add a column for storing the build recipe to the build configuration table.
 
add_last_activity_to_build(env, db)
Add a column for storing the last activity to the build table.
 
add_config_to_reports(env, db)
Add the name of the build configuration as metadata to report documents stored in the BDB XML database.
 
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.
 
add_report_tables(env, db)
Add database tables for report storage.
 
xmldb_to_db(env, db)
Migrate report data from Berkeley DB XML to SQL database.
 
normalize_file_paths(env, db)
Normalize the file separator in file names in reports.
 
fixup_generators(env, db)
Upgrade the identifiers for the recipe commands that generated log messages and report data.
 
add_error_table(env, db)
Add the bitten_error table for recording step failure reasons.
 
add_filename_to_logs(env, db)
Add filename column to log table to save where log files are stored.
 
migrate_logs_to_files(env, db)
Migrates logs that are stored in the bitten_log_messages table into files.
 
fix_log_levels_misnaming(env, db)
Renames or removes *.log.level files created by older versions of migrate_logs_to_files.
 
remove_stray_log_levels_files(env, db)
Remove *.log.levels files without a matching *.log file (old Bitten versions did not delete .log.levels files when builds were deleted)
 
recreate_rule_with_int_id(env, db)
Recreates the bitten_rule table with an integer id column rather than a text one.
 
add_config_platform_rev_index_to_build(env, db)
Adds a unique index on (config, platform, rev) to the bitten_build table.
 
fix_sequences(env, db)
Fixes any auto increment sequences that might have been left in an inconsistent state.
Variables
  map = {2: [add_log_table], 3: [add_recipe_to_config], 4: [add_...
  __package__ = 'bitten'
Function Details

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.

add_config_platform_rev_index_to_build(env, db)

 
Adds a unique index on (config, platform, rev) to the bitten_build table. Also drops the old index on bitten_build that serves no real purpose anymore.

fix_sequences(env, db)

 

Fixes any auto increment sequences that might have been left in an inconsistent state.

Upgrade scripts for schema versions > 10 should handle sequence updates correctly themselves.


Variables Details

map

Value:
{2: [add_log_table], 3: [add_recipe_to_config], 4: [add_config_to_repo\
rts], 5: [add_order_to_log, add_report_tables, xmldb_to_db], 6: [norma\
lize_file_paths, fixup_generators], 7: [add_error_table], 8: [add_file\
name_to_logs, migrate_logs_to_files], 9: [recreate_rule_with_int_id], \
10: [add_config_platform_rev_index_to_build, fix_sequences], 11: [fix_\
log_levels_misnaming, remove_stray_log_levels_files], 12: [add_last_ac\
tivity_to_build],}

Bitten-0.6/doc/api/bitten.util-module.html000644 000765 000120 00000013253 11536424537 021024 0ustar00simonadmin000000 000000 bitten.util
Package bitten :: Package util

Package util

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.

Submodules

Variables
  __package__ = None
Bitten-0.6/doc/api/bitten.util.compat-module.html000644 000765 000120 00000012162 11536424537 022304 0ustar00simonadmin000000 000000 bitten.util.compat
Package bitten :: Package util :: Module compat

Module compat

Compatibility fixes for external libraries and Python.
Classes
  HTTPBasicAuthHandler
Patched version of Python 2.6's HTTPBasicAuthHandler.
Variables
  __package__ = 'bitten.util'
Bitten-0.6/doc/api/bitten.util.compat.HTTPBasicAuthHandler-class.html000644 000765 000120 00000022556 11536424540 025766 0ustar00simonadmin000000 000000 bitten.util.compat.HTTPBasicAuthHandler
Package bitten :: Package util :: Module compat :: Class HTTPBasicAuthHandler

Class HTTPBasicAuthHandler

urllib2.AbstractBasicAuthHandler --+    
                                   |    
             urllib2.BaseHandler --+    
                                   |    
        urllib2.HTTPBasicAuthHandler --+
                                       |
                                      HTTPBasicAuthHandler

Patched version of Python 2.6's HTTPBasicAuthHandler.

The fix for [1] introduced an infinite recursion bug [2] into Python 2.6.x that is triggered by attempting to connect using Basic authentication with a bad username and/or password. This class fixes the problem using the simple solution outlined in [3].

[1]http://bugs.python.org/issue3819
[2]http://bugs.python.org/issue8797
[3]http://bugs.python.org/issue8797#msg126657
Instance Methods
 
retry_http_basic_auth(self, host, req, realm)

Inherited from urllib2.HTTPBasicAuthHandler: http_error_401

Inherited from urllib2.AbstractBasicAuthHandler: __init__, http_error_auth_reqed

Inherited from urllib2.BaseHandler: __lt__, add_parent, close

Class Variables

Inherited from urllib2.HTTPBasicAuthHandler: auth_header

Inherited from urllib2.AbstractBasicAuthHandler: rx

Inherited from urllib2.BaseHandler: handler_order

Method Details

retry_http_basic_auth(self, host, req, realm)

 
Overrides: urllib2.AbstractBasicAuthHandler.retry_http_basic_auth

Bitten-0.6/doc/api/bitten.util.json-module.html000644 000765 000120 00000012541 11536424537 021773 0ustar00simonadmin000000 000000 bitten.util.json
Package bitten :: Package util :: Module json

Module json

Utility functions for converting to web formats
Functions
 
to_json(value)
Encode value to JSON.
Variables
  __package__ = 'bitten.util'
Bitten-0.6/doc/api/bitten.util.loc-module.html000644 000765 000120 00000017536 11536424537 021610 0ustar00simonadmin000000 000000 bitten.util.loc
Package bitten :: Package util :: Module loc

Module loc

Support for counting the lines of code in Python programs.
Functions
 
count(source)
Parse the given file-like object as Python source code.
Variables
  BLANK = 0
  CODE = 1
  COMMENT = 2
  DOC = 3
Function Details

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.

Parameters:
  • source - a file-like object containing Python code

Bitten-0.6/doc/api/bitten.util.testrunner-module.html000644 000765 000120 00000015661 11536424537 023241 0ustar00simonadmin000000 000000 bitten.util.testrunner
Package bitten :: Package util :: Module testrunner

Module testrunner

Classes
  XMLTestResult
  XMLTestRunner
  unittest
Functions
 
filter_coverage(infile, outfile)
 
main()
Variables
  __package__ = 'bitten.util'
Bitten-0.6/doc/api/bitten.util.testrunner.unittest-class.html000644 000765 000120 00000040705 11536424540 024726 0ustar00simonadmin000000 000000 bitten.util.testrunner.unittest
Package bitten :: Package util :: Module testrunner :: Class unittest

Class unittest

distutils.cmd.Command --+        
                        |        
       setuptools.Command --+    
                            |    
 setuptools.command.test.test --+
                                |
                               unittest

Instance Methods
 
initialize_options(self)
Set default values for all the options that this command supports.
 
finalize_options(self)
Set final values for all the options that this command supports.
 
run_tests(self)

Inherited from setuptools.command.test.test: run, with_project_on_sys_path

Inherited from setuptools.Command: __init__, reinitialize_command

Inherited from distutils.cmd.Command: __getattr__, announce, copy_file, copy_tree, debug_print, dump_options, ensure_dirname, ensure_filename, ensure_finalized, ensure_string, ensure_string_list, execute, get_command_name, get_finalized_command, get_sub_commands, make_archive, make_file, mkpath, move_file, run_command, set_undefined_options, spawn, warn

Class Variables
  description = 'run unit tests after in-place build, and option...
  user_options = [('test-module=', 'm', 'Run \'test_suite\' in s...

Inherited from setuptools.Command: __doc__, command_consumes_arguments

Inherited from distutils.cmd.Command: sub_commands

Method Details

initialize_options(self)

 

Set default values for all the options that this command supports. Note that these defaults may be overridden by other commands, by the setup script, by config files, or by the command-line. Thus, this is not the place to code dependencies between options; generally, 'initialize_options()' implementations are just a bunch of "self.foo = None" assignments.

This method must be implemented by all command classes.

Overrides: distutils.cmd.Command.initialize_options
(inherited documentation)

finalize_options(self)

 

Set final values for all the options that this command supports. This is always called as late as possible, ie. after any option assignments from the command-line or from other commands have been done. Thus, this is the place to code option dependencies: if 'foo' depends on 'bar', then it is safe to set 'foo' from 'bar' as long as 'foo' still has the same value it was assigned in 'initialize_options()'.

This method must be implemented by all command classes.

Overrides: distutils.cmd.Command.finalize_options
(inherited documentation)

run_tests(self)

 
Overrides: setuptools.command.test.test.run_tests

Class Variable Details

description

Value:
'run unit tests after in-place build, and optionally record code cover\
age'

user_options

Value:
[('test-module=', 'm', 'Run \'test_suite\' in specified module'),
 ('test-suite=',
  's',
  'Test suite to run (e.g. \'some_module.test_suite\')'),
 ('xml-output=',
  None,
  'Path to the XML file where test results are written to'),
 ('coverage-dir=', None, 'Directory where coverage files are to be sto\
...

Bitten-0.6/doc/api/bitten.util.testrunner.XMLTestResult-class.html000644 000765 000120 00000026344 11536424540 025551 0ustar00simonadmin000000 000000 bitten.util.testrunner.XMLTestResult
Package bitten :: Package util :: Module testrunner :: Class XMLTestResult

Class XMLTestResult

          object --+        
                   |        
 unittest.TestResult --+    
                       |    
unittest._TextTestResult --+
                           |
                          XMLTestResult

Instance Methods
 
__init__(self, stream, descriptions, verbosity)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 
startTest(self, test)
Called when the given test is about to be run
 
stopTest(self, test)
Called when the given test has been run

Inherited from unittest._TextTestResult: addError, addFailure, addSuccess, getDescription, printErrorList, printErrors

Inherited from unittest.TestResult: __repr__, stop, wasSuccessful

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __setattr__, __sizeof__, __str__, __subclasshook__

Class Variables

Inherited from unittest._TextTestResult: separator1, separator2

Properties

Inherited from object: __class__

Method Details

__init__(self, stream, descriptions, verbosity)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

startTest(self, test)

 
Called when the given test is about to be run
Overrides: unittest.TestResult.startTest
(inherited documentation)

stopTest(self, test)

 
Called when the given test has been run
Overrides: unittest.TestResult.stopTest
(inherited documentation)

Bitten-0.6/doc/api/bitten.util.testrunner.XMLTestRunner-class.html000644 000765 000120 00000021461 11536424540 025537 0ustar00simonadmin000000 000000 bitten.util.testrunner.XMLTestRunner
Package bitten :: Package util :: Module testrunner :: Class XMLTestRunner

Class XMLTestRunner

             object --+    
                      |    
unittest.TextTestRunner --+
                          |
                         XMLTestRunner

Instance Methods
 
__init__(self, stream=sys.stdout, xml_stream=None)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 
run(self, test)
Run the given test case or test suite.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Properties

Inherited from object: __class__

Method Details

__init__(self, stream=sys.stdout, xml_stream=None)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

run(self, test)

 
Run the given test case or test suite.
Overrides: unittest.TextTestRunner.run
(inherited documentation)

Bitten-0.6/doc/api/bitten.util.xmlio-module.html000644 000765 000120 00000016253 11536424537 022156 0ustar00simonadmin000000 000000 bitten.util.xmlio
Package bitten :: Package util :: Module xmlio

Module xmlio

Utility code for easy input and output of XML.

The current implementation uses xml.dom.minidom under the hood for parsing.

Classes
  Fragment
A collection of XML elements.
  Element
Simple XML output generator based on the builder pattern.
  ParsedElement
Representation of an XML element that was parsed from a string or file.
Functions
 
parse(text_or_file)
Parse an XML document provided as string or file-like object.
Function Details

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.


Bitten-0.6/doc/api/bitten.util.xmlio.Element-class.html000644 000765 000120 00000033247 11536424540 023362 0ustar00simonadmin000000 000000 bitten.util.xmlio.Element
Package bitten :: Package util :: Module xmlio :: Class Element

Class Element

object --+    
         |    
  Fragment --+
             |
            Element

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="&quot;baz&quot;"/>

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>

Valid input are utf-8 or unicode strings, or any type easily converted to unicode such as integers. Output is always utf-8.

Instance Methods
 
__init__(self, name_, **attr)
Create an XML element using the specified tag name.
 
write(self, out, newlines=False)
Serializes the element and writes the XML to the given output stream.

Inherited from Fragment: __getitem__, __str__, append

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __subclasshook__

Properties
  attr
  name

Inherited from Fragment: children

Inherited from object: __class__

Method Details

__init__(self, name_, **attr)
(Constructor)

 

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.

Overrides: object.__init__

write(self, out, newlines=False)

 
Serializes the element and writes the XML to the given output stream.
Overrides: Fragment.write

Bitten-0.6/doc/api/bitten.util.xmlio.Fragment-class.html000644 000765 000120 00000024647 11536424540 023540 0ustar00simonadmin000000 000000 bitten.util.xmlio.Fragment
Package bitten :: Package util :: Module xmlio :: Class Fragment

Class Fragment

object --+
         |
        Fragment
Known Subclasses:

A collection of XML elements.
Instance Methods
 
__init__(self)
Create an XML fragment.
 
__getitem__(self, nodes)
Add nodes to the fragment.
 
__str__(self)
Return a string representation of the XML fragment.
 
append(self, node)
Append an element or fragment as child.
 
write(self, out, newlines=False)
Serializes the element and writes the XML to the given output stream.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __subclasshook__

Properties
  children

Inherited from object: __class__

Method Details

__init__(self)
(Constructor)

 
Create an XML fragment.
Overrides: object.__init__

__str__(self)
(Informal representation operator)

 
Return a string representation of the XML fragment.
Overrides: object.__str__

Bitten-0.6/doc/api/bitten.util.xmlio.ParsedElement-class.html000644 000765 000120 00000043561 11536424540 024521 0ustar00simonadmin000000 000000 bitten.util.xmlio.ParsedElement
Package bitten :: Package util :: Module xmlio :: Class ParsedElement

Class ParsedElement

object --+
         |
        ParsedElement

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'

Valid input are utf-8 or unicode strings, or any type easily converted to unicode such as integers. Output is always utf-8.

Instance Methods
 
__init__(self, node)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
 
children(self, name=None)
Iterate over the child elements of this element.
 
__iter__(self)
 
gettext(self)
Return the text content of this element.
 
write(self, out, newlines=False)
Serializes the element and writes the XML to the given output stream.
 
__str__(self)
Return a string representation of the XML element.

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __subclasshook__

Properties
  name
Local name of the element
  namespace
Namespace URI of the element
  attr

Inherited from object: __class__

Method Details

__init__(self, node)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

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.

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.

__str__(self)
(Informal representation operator)

 
Return a string representation of the XML element.
Overrides: object.__str__

Property Details

name

Local name of the element
Get Method:
unreachable(self)

namespace

Namespace URI of the element
Get Method:
unreachable(self)

Bitten-0.6/doc/api/bitten.web_ui-module.html000644 000765 000120 00000014433 11536424537 021322 0ustar00simonadmin000000 000000 bitten.web_ui
Package bitten :: Module web_ui

Module web_ui

Implementation of the Bitten web interface.
Classes
  BittenChrome
Provides the Bitten templates and static resources.
  BuildConfigController
Implements the web interface for build configurations.
  BuildController
Renders the build page.
  ReportChartController
  SourceFileLinkFormatter
Detects references to files in the build log and renders them as links to the repository browser.
Variables
  __package__ = 'bitten'
Bitten-0.6/doc/api/bitten.web_ui.BittenChrome-class.html000644 000765 000120 00000026316 11536424540 023521 0ustar00simonadmin000000 000000 bitten.web_ui.BittenChrome
Package bitten :: Module web_ui :: Class BittenChrome

Class BittenChrome

         object --+    
                  |    
trac.core.Component --+
                      |
                     BittenChrome

Provides the Bitten templates and static resources.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_active_navigation_item(self, req)
 
get_navigation_items(self, req)
Return the navigation item for access the build status overview from the Trac navigation bar.
 
get_htdocs_dirs(self)
Return the directories containing static resources.
 
get_templates_dirs(self)
Return the directories containing templates.
 
__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.BittenChrome'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.BittenChrome'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.web_ui.BuildConfigController-class.html000644 000765 000120 00000032402 11536424540 025360 0ustar00simonadmin000000 000000 bitten.web_ui.BuildConfigController
Package bitten :: Module web_ui :: Class BuildConfigController

Class BuildConfigController

         object --+    
                  |    
trac.core.Component --+
                      |
                     BuildConfigController

Implements the web interface for build configurations.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_active_navigation_item(self, req)
 
get_navigation_items(self, req)
 
match_request(self, req)
 
process_request(self, req)
 
pre_process_request(self, req, handler)
 
post_process_request(self, req, template, data, content_type)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.BuildConfigController'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Class Variables
  chart_style = <Option [bitten] "chart_style">
Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.BuildConfigController'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/bitten.web_ui.BuildController-class.html000644 000765 000120 00000037002 11536424540 024233 0ustar00simonadmin000000 000000 bitten.web_ui.BuildController
Package bitten :: Module web_ui :: Class BuildController

Class BuildController

         object --+    
                  |    
trac.core.Component --+
                      |
                     BuildController

Renders the build page.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_active_navigation_item(self, req)
 
get_navigation_items(self, req)
 
match_request(self, req)
 
process_request(self, req)
 
get_timeline_filters(self, req)
 
get_timeline_events(self, req, start, stop, filters)
 
render_timeline_event(self, context, field, event)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.BuildController'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties
  log_formatters
List of components that implement ILogFormatter
  report_summarizers
List of components that implement IReportSummarizer

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.BuildController'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Property Details

log_formatters

List of components that implement ILogFormatter
Get Method:
unreachable.extensions(component) - Return a list of components that declare to implement the extension point interface.

report_summarizers

List of components that implement IReportSummarizer
Get Method:
unreachable.extensions(component) - Return a list of components that declare to implement the extension point interface.

Bitten-0.6/doc/api/bitten.web_ui.ReportChartController-class.html000644 000765 000120 00000026440 11536424540 025435 0ustar00simonadmin000000 000000 bitten.web_ui.ReportChartController
Package bitten :: Module web_ui :: Class ReportChartController

Class ReportChartController

         object --+    
                  |    
trac.core.Component --+
                      |
                     ReportChartController

Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
match_request(self, req)
 
process_request(self, req)
 
__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.ReportChartController'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties
  generators
List of components that implement IReportChartGenerator

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.ReportChartController'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Property Details

generators

List of components that implement IReportChartGenerator
Get Method:
unreachable.extensions(component) - Return a list of components that declare to implement the extension point interface.

Bitten-0.6/doc/api/bitten.web_ui.SourceFileLinkFormatter-class.html000644 000765 000120 00000023017 11536424540 025673 0ustar00simonadmin000000 000000 bitten.web_ui.SourceFileLinkFormatter
Package bitten :: Module web_ui :: Class SourceFileLinkFormatter

Class SourceFileLinkFormatter

         object --+    
                  |    
trac.core.Component --+
                      |
                     SourceFileLinkFormatter

Detects references to files in the build log and renders them as links to the repository browser.
Nested Classes

Inherited from trac.core.Component: __metaclass__

Instance Methods
 
get_formatter(self, req, build)
Return the log message formatter function.
 
__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.SourceFileLinkFormatter'>)
x.__init__(...) initializes x; see x.__class__.__doc__ for signature

Inherited from object: __delattr__, __format__, __getattribute__, __hash__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__

Static Methods

Inherited from trac.core.Component: __new__, implements

Properties

Inherited from object: __class__

Method Details

__init__(self, compmgr, init=None, cls=<class 'bitten.web_ui.SourceFileLinkFormatter'>)
(Constructor)

 
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
Overrides: object.__init__
(inherited documentation)

Bitten-0.6/doc/api/class-tree.html000644 000765 000120 00000041662 11536424537 017347 0ustar00simonadmin000000 000000 Class Hierarchy
 
[ Module Hierarchy | Class Hierarchy ]

Class Hierarchy

Bitten-0.6/doc/api/crarr.png000644 000765 000120 00000000524 11536424537 016226 0ustar00simonadmin000000 000000 PNG  IHDR eE,tEXtCreation TimeTue 22 Aug 2006 00:43:10 -0500` XtIME)} pHYsnu>gAMA aEPLTEðf4sW ЊrD`@bCܖX{`,lNo@xdE螊dƴ~TwvtRNS@fMIDATxc`@0&+(;; /EXؑ? n  b;'+Y#(r<"IENDB`Bitten-0.6/doc/api/epydoc.css000644 000765 000120 00000013037 11536424537 016407 0ustar00simonadmin000000 000000 html { background: #4b4d4d url(../style/bkgnd_pattern.png); margin: 0; padding: 1em 1em 3em; } body { background: #fff url(../style/vertbars.png) repeat-x; border: 1px solid #000; color: #000; margin: 1em 0; padding: 0 1em 1em; } body, th, td { font: normal small Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; } h1, h2, h3, h4 { font-family: Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; font-weight: bold; letter-spacing: -0.018em; } h1 { font-size: 19px; margin: 2em 0 .5em; } h2 { font-size: 16px; margin: 1.5em 0 .5em; } h3 { font-size: 14px; margin: 1.2em 0 .5em; } hr { border: none; border-top: 1px solid #ccb; margin: 2em 0; } p { margin: 0 0 1em; } :link, :visited { text-decoration: none; border-bottom: 1px dotted #bbb; color: #b00; } :link:hover, :visited:hover { background-color: #eee; color: #555; } table { border: none; border-collapse: collapse; } table.navbar { background: #000; color: #fff; margin: 2em 0 .33em; } table.navbar th { border: 1px solid #000; font-weight: bold; padding: 1px; } table.navbar :link, table.navbar :visited { border: none; color: #fff; } table.navbar :link:hover, table.navbar :visited:hover { background: none; text-decoration: underline overline; } table.navbar th.navbar-select { background: #fff; color: #000; } span.breadcrumbs { color: #666; font-size: 95%; } h1.epydoc { border: none; color: #666; font-size: x-large; margin: 1em 0 0; padding: 0; } pre.base-tree { color: #666; margin: 0; padding: 0; } pre.base-tree :link, pre.base-tree :visited { border: none; } pre.py-doctest, pre.variable, pre.rst-literal-block { background: #eee; border: 1px solid #e6e6e6; color: #000; margin: 1em; padding: .25em; overflow: auto; } pre.variable { margin: 0; } /* Summary tables */ table.summary { margin: .5em 0; } table.summary tr.table-header { background: #f7f7f0; } table.summary td.table-header { color: #666; font-weight: bold; } table.summary th.group-header { background: #f7f7f0; color: #666; font-size: 90%; font-weight: bold; text-align: left; } table.summary th, table.summary td { border: 1px solid #d7d7d7; } table.summary th th, table.summary td td { border: none; } table.summary td.summary table td { color: #666; font-size: 90%; } table.summary td.summary table br { display: none; } table.summary td.summary span.summary-type { font-family: monospace; font-size: 90%; } table.summary td.summary span.summary-type code { font-size: 110%; } p.indent-wrapped-lines { color: #999; font-size: 85%; margin: 0; padding: 0 0 0 7em; text-indent: -7em; } p.indent-wrapped-lines code { color: #999; font-size: 115%; } p.indent-wrapped-lines :link, p.indent-wrapped-lines :visited { border: none; } .summary-sig { display: block; font-family: monospace; font-size: 120%; margin-bottom: .5em; } .summary-sig-name { font-weight: bold; } .summary-sig-arg { color: #333; } .summary-sig :link, .summary-sig :visited { border: none; } .summary-name { font-family: monospace; font-weight: bold; } /* Details tables */ table.details { margin: 2em 0 0; } div table.details { margin-top: 0; } table.details tr.table-header { background: transparent; } table.details td.table-header { border-bottom: 1px solid #ccc; padding: 2em 0 0; } table.details span.table-header { font: bold 140% Arial,Verdana,'Bitstream Vera Sans',Helvetica,sans-serif; letter-spacing: -0.018em; } table.details th, table.details td { border: none; } table.details th th, table.details td td { border: none; } table.details td { padding-left: 2em; } table.details td td { padding-left: 0; } table.details h3.epydoc { margin-left: -2em; } table.details h3.epydoc .sig { color: #999; font-family: monospace; } table.details h3.epydoc .sig-name { color: #000; } table.details h3.epydoc .sig-arg { color: #666; } table.details h3.epydoc .sig-default { font-size: 95%; font-weight: normal; } table.details h3.epydoc .sig-default code { font-weight: normal; } table.details h3.epydoc .fname { color: #999; font-size: 90%; font-style: italic; font-weight: normal; line-height: 1.6em; } dl dt { color: #666; margin-top: 1em; } dl dd { margin: 0; padding-left: 2em; } dl.fields { margin: 1em 0; padding: 0; } dl.fields dt { color: #666; margin-top: 1em; } dl.fields dd ul { margin: 0; padding: 0; } div.fields { font-size: 90%; margin: 0 0 2em 2em; } div.fields p { margin-bottom: 0.5em; } table td.footer { color: #999; font-size: 85%; margin-top: 3em; padding: 0 3em 1em; position: absolute; width: 80%; } table td.footer :link, table td.footer :visited { border: none; color: #999; } table td.footer :link:hover, table td.footer :visited:hover { background: transparent; text-decoration: underline; } /* Syntax highlighting */ .py-prompt, .py-more, .variable-ellipsis, .variable-op { color: #999; } .variable-group { color: #666; font-weight: bold; } .py-string, .variable-string, .variable-quote { color: #093; } .py-comment { color: #06f; font-style: italic; } .py-keyword { color: #00f; } .py-output { background: #f6f6f0; color: #666; font-weight: bold; } /* Index */ table.link-index { background: #f6f6f0; border: none; margin-top: 1em; } table.link-index td.link-index { border: none; font-family: monospace; font-weight: bold; padding: .5em 1em; } table.link-index td table, table.link-index td td { border: none; } table.link-index .index-where { color: #999; font-family: Verdana,Arial,'Bitstream Vera Sans',Helvetica,sans-serif; font-size: 90%; font-weight: normal; line-height: 1.6em; } table.link-index .index-where :link, table.link-index .index-where :visited { border: none; color: #666; } h2.epydoc { color: #999; font-size: 200%; line-height: 10px; } Bitten-0.6/doc/api/epydoc.js000644 000765 000120 00000024525 11536424537 016237 0ustar00simonadmin000000 000000 function toggle_private() { // Search for any private/public links on this page. Store // their old text in "cmd," so we will know what action to // take; and change their text to the opposite action. var cmd = "?"; var elts = document.getElementsByTagName("a"); for(var i=0; i...
"; elt.innerHTML = s; } } function toggle(id) { elt = document.getElementById(id+"-toggle"); if (elt.innerHTML == "-") collapse(id); else expand(id); return false; } function highlight(id) { var elt = document.getElementById(id+"-def"); if (elt) elt.className = "py-highlight-hdr"; var elt = document.getElementById(id+"-expanded"); if (elt) elt.className = "py-highlight"; var elt = document.getElementById(id+"-collapsed"); if (elt) elt.className = "py-highlight"; } function num_lines(s) { var n = 1; var pos = s.indexOf("\n"); while ( pos > 0) { n += 1; pos = s.indexOf("\n", pos+1); } return n; } // Collapse all blocks that mave more than `min_lines` lines. function collapse_all(min_lines) { var elts = document.getElementsByTagName("div"); for (var i=0; i 0) if (elt.id.substring(split, elt.id.length) == "-expanded") if (num_lines(elt.innerHTML) > min_lines) collapse(elt.id.substring(0, split)); } } function expandto(href) { var start = href.indexOf("#")+1; if (start != 0 && start != href.length) { if (href.substring(start, href.length) != "-") { collapse_all(4); pos = href.indexOf(".", start); while (pos != -1) { var id = href.substring(start, pos); expand(id); pos = href.indexOf(".", pos+1); } var id = href.substring(start, href.length); expand(id); highlight(id); } } } function kill_doclink(id) { var parent = document.getElementById(id); parent.removeChild(parent.childNodes.item(0)); } function auto_kill_doclink(ev) { if (!ev) var ev = window.event; if (!this.contains(ev.toElement)) { var parent = document.getElementById(this.parentID); parent.removeChild(parent.childNodes.item(0)); } } function doclink(id, name, targets_id) { var elt = document.getElementById(id); // If we already opened the box, then destroy it. // (This case should never occur, but leave it in just in case.) if (elt.childNodes.length > 1) { elt.removeChild(elt.childNodes.item(0)); } else { // The outer box: relative + inline positioning. var box1 = document.createElement("div"); box1.style.position = "relative"; box1.style.display = "inline"; box1.style.top = 0; box1.style.left = 0; // A shadow for fun var shadow = document.createElement("div"); shadow.style.position = "absolute"; shadow.style.left = "-1.3em"; shadow.style.top = "-1.3em"; shadow.style.background = "#404040"; // The inner box: absolute positioning. var box2 = document.createElement("div"); box2.style.position = "relative"; box2.style.border = "1px solid #a0a0a0"; box2.style.left = "-.2em"; box2.style.top = "-.2em"; box2.style.background = "white"; box2.style.padding = ".3em .4em .3em .4em"; box2.style.fontStyle = "normal"; box2.onmouseout=auto_kill_doclink; box2.parentID = id; // Get the targets var targets_elt = document.getElementById(targets_id); var targets = targets_elt.getAttribute("targets"); var links = ""; target_list = targets.split(","); for (var i=0; i" + target[0] + ""; } // Put it all together. elt.insertBefore(box1, elt.childNodes.item(0)); //box1.appendChild(box2); box1.appendChild(shadow); shadow.appendChild(box2); box2.innerHTML = "Which "+name+" do you want to see documentation for?" + ""; } return false; } function get_anchor() { var href = location.href; var start = href.indexOf("#")+1; if ((start != 0) && (start != href.length)) return href.substring(start, href.length); } function redirect_url(dottedName) { // Scan through each element of the "pages" list, and check // if "name" matches with any of them. for (var i=0; i-m" or "-c"; // extract the portion & compare it to dottedName. var pagename = pages[i].substring(0, pages[i].length-2); if (pagename == dottedName.substring(0,pagename.length)) { // We've found a page that matches `dottedName`; // construct its URL, using leftover `dottedName` // content to form an anchor. var pagetype = pages[i].charAt(pages[i].length-1); var url = pagename + ((pagetype=="m")?"-module.html": "-class.html"); if (dottedName.length > pagename.length) url += "#" + dottedName.substring(pagename.length+1, dottedName.length); return url; } } } Bitten-0.6/doc/api/help.html000644 000765 000120 00000025151 11536424537 016230 0ustar00simonadmin000000 000000 Help
 

API Documentation

This document contains the API (Application Programming Interface) documentation for Documentation Index. Documentation for the Python objects defined by the project is divided into separate pages for each package, module, and class. The API documentation also includes two pages containing information about the project as a whole: a trees page, and an index page.

Object Documentation

Each Package Documentation page contains:

  • A description of the package.
  • A list of the modules and sub-packages contained by the package.
  • A summary of the classes defined by the package.
  • A summary of the functions defined by the package.
  • A summary of the variables defined by the package.
  • A detailed description of each function defined by the package.
  • A detailed description of each variable defined by the package.

Each Module Documentation page contains:

  • A description of the module.
  • A summary of the classes defined by the module.
  • A summary of the functions defined by the module.
  • A summary of the variables defined by the module.
  • A detailed description of each function defined by the module.
  • A detailed description of each variable defined by the module.

Each Class Documentation page contains:

  • A class inheritance diagram.
  • A list of known subclasses.
  • A description of the class.
  • A summary of the methods defined by the class.
  • A summary of the instance variables defined by the class.
  • A summary of the class (static) variables defined by the class.
  • A detailed description of each method defined by the class.
  • A detailed description of each instance variable defined by the class.
  • A detailed description of each class (static) variable defined by the class.

Project Documentation

The Trees page contains the module and class hierarchies:

  • The module hierarchy lists every package and module, with modules grouped into packages. At the top level, and within each package, modules and sub-packages are listed alphabetically.
  • The class hierarchy lists every class, grouped by base class. If a class has more than one base class, then it will be listed under each base class. At the top level, and under each base class, classes are listed alphabetically.

The Index page contains indices of terms and identifiers:

  • The term index lists every term indexed by any object's documentation. For each term, the index provides links to each place where the term is indexed.
  • The identifier index lists the (short) name of every package, module, class, method, function, variable, and parameter. For each identifier, the index provides a short description, and a link to its documentation.

The Table of Contents

The table of contents occupies the two frames on the left side of the window. The upper-left frame displays the project contents, and the lower-left frame displays the module contents:

Project
Contents
...
API
Documentation
Frame


Module
Contents
 
...
 

The project contents frame contains a list of all packages and modules that are defined by the project. Clicking on an entry will display its contents in the module contents frame. Clicking on a special entry, labeled "Everything," will display the contents of the entire project.

The module contents frame contains a list of every submodule, class, type, exception, function, and variable defined by a module or package. Clicking on an entry will display its documentation in the API documentation frame. Clicking on the name of the module, at the top of the frame, will display the documentation for the module itself.

The "frames" and "no frames" buttons below the top navigation bar can be used to control whether the table of contents is displayed or not.

The Navigation Bar

A navigation bar is located at the top and bottom of every page. It indicates what type of page you are currently viewing, and allows you to go to related pages. The following table describes the labels on the navigation bar. Note that not some labels (such as [Parent]) are not displayed on all pages.

Label Highlighted when... Links to...
[Parent] (never highlighted) the parent of the current package
[Package] viewing a package the package containing the current object
[Module] viewing a module the module containing the current object
[Class] viewing a class the class containing the current object
[Trees] viewing the trees page the trees page
[Index] viewing the index page the index page
[Help] viewing the help page the help page

The "show private" and "hide private" buttons below the top navigation bar can be used to control whether documentation for private objects is displayed. Private objects are usually defined as objects whose (short) names begin with a single underscore, but do not end with an underscore. For example, "_x", "__pprint", and "epydoc.epytext._tokenize" are private objects; but "re.sub", "__init__", and "type_" are not. However, if a module defines the "__all__" variable, then its contents are used to decide which objects are private.

A timestamp below the bottom navigation bar indicates when each page was last updated.

Bitten-0.6/doc/api/identifier-index.html000644 000765 000120 00000364316 11536424537 020540 0ustar00simonadmin000000 000000 Identifier Index
 

Identifier Index

[ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ ]

A

B

C

D

E

F

G

H

I

J

L

M

N

O

P

Q

R

S

T

U

V

W

X

_



Bitten-0.6/doc/api/index.html000644 000765 000120 00000024750 11536424540 016405 0ustar00simonadmin000000 000000 bitten
Package bitten

Package bitten


Version: 0.6

Submodules

Variables
  PROTOCOL_VERSION = 5
  __package__ = None
Bitten-0.6/doc/api/module-tree.html000644 000765 000120 00000022263 11536424537 017523 0ustar00simonadmin000000 000000 Module Hierarchy
 
[ Module Hierarchy | Class Hierarchy ]

Module Hierarchy

Bitten-0.6/doc/api/redirect.html000644 000765 000120 00000006740 11536424540 017076 0ustar00simonadmin000000 000000 Epydoc Redirect Page

Epydoc Auto-redirect page

When javascript is enabled, this page will redirect URLs of the form redirect.html#dotted.name to the documentation for the object with the given fully-qualified dotted name.

 

Bitten-0.6/doc/api/trac.core.ComponentMeta-class.html000644 000765 000120 00000020535 11536424540 023026 0ustar00simonadmin000000 000000 trac.core.ComponentMeta
trac :: core :: ComponentMeta :: Class ComponentMeta

Type ComponentMeta

object --+    
         |    
      type --+
             |
            ComponentMeta

Meta class for components.

Takes care of component and extension point registration.

Instance Methods

Inherited from type: __call__, __delattr__, __eq__, __ge__, __getattribute__, __gt__, __hash__, __init__, __le__, __lt__, __ne__, __repr__, __setattr__, __subclasses__, mro

Inherited from object: __format__, __reduce__, __reduce_ex__, __sizeof__, __str__, __subclasshook__

Static Methods
a new object with type S, a subtype of T
__new__(cls, name, bases, d)
Create the component class.
Properties

Inherited from type: __abstractmethods__, __base__, __bases__, __basicsize__, __dictoffset__, __flags__, __instancecheck__, __itemsize__, __mro__, __name__, __subclasscheck__, __weakrefoffset__

Inherited from object: __class__

Method Details

__new__(cls, name, bases, d)
Static Method

 
Create the component class.
Returns: a new object with type S, a subtype of T
Overrides: object.__new__

Bitten-0.6/Bitten.egg-info/dependency_links.txt000644 000765 000120 00000000001 11536424600 022044 0ustar00simonadmin000000 000000 Bitten-0.6/Bitten.egg-info/entry_points.txt000644 000765 000120 00000010604 11536424600 021275 0ustar00simonadmin000000 000000 [bitten.recipe_commands] http://bitten.edgewall.org/tools/c#make = bitten.build.ctools:make http://bitten.cmlenz.net/tools/c#configure = bitten.build.ctools:configure http://bitten.edgewall.org/tools/php#phing = bitten.build.phptools:phing http://bitten.edgewall.org/tools/c#cppunit = bitten.build.ctools:cppunit http://bitten.cmlenz.net/tools/sh#exec = bitten.build.shtools:exec_ http://bitten.edgewall.org/tools/java#ant = bitten.build.javatools:ant http://bitten.cmlenz.net/tools/svn#checkout = bitten.build.svntools:checkout http://bitten.cmlenz.net/tools/python#unittest = bitten.build.pythontools:unittest http://bitten.cmlenz.net/tools/sh#pipe = bitten.build.shtools:pipe http://bitten.cmlenz.net/tools/python#pylint = bitten.build.pythontools:pylint http://bitten.edgewall.org/tools/svn#export = bitten.build.svntools:export http://bitten.edgewall.org/tools/java#cobertura = bitten.build.javatools:cobertura http://bitten.cmlenz.net/tools/c#cppunit = bitten.build.ctools:cppunit http://bitten.edgewall.org/tools/c#cunit = bitten.build.ctools:cunit http://bitten.cmlenz.net/tools/hg#pull = bitten.build.hgtools:pull http://bitten.cmlenz.net/tools/php#phing = bitten.build.phptools:phing http://bitten.cmlenz.net/tools/php#coverage = bitten.build.phptools:coverage http://bitten.cmlenz.net/tools/java#ant = bitten.build.javatools:ant http://bitten.edgewall.org/tools/mono#nunit = bitten.build.monotools:nunit http://bitten.cmlenz.net/tools/python#coverage = bitten.build.pythontools:coverage http://bitten.edgewall.org/tools/python#figleaf = bitten.build.pythontools:figleaf http://bitten.edgewall.org/tools/xml#transform = bitten.build.xmltools:transform http://bitten.edgewall.org/tools/php#phpunit = bitten.build.phptools:phpunit http://bitten.edgewall.org/tools/svn#update = bitten.build.svntools:update http://bitten.cmlenz.net/tools/c#cunit = bitten.build.ctools:cunit http://bitten.cmlenz.net/tools/c#make = bitten.build.ctools:make http://bitten.cmlenz.net/tools/python#figleaf = bitten.build.pythontools:figleaf http://bitten.edgewall.org/tools/python#coverage = bitten.build.pythontools:coverage http://bitten.edgewall.org/tools/svn#checkout = bitten.build.svntools:checkout http://bitten.edgewall.org/tools/c#configure = bitten.build.ctools:configure http://bitten.edgewall.org/tools/php#coverage = bitten.build.phptools:coverage http://bitten.cmlenz.net/tools/c#gcov = bitten.build.ctools:gcov http://bitten.cmlenz.net/tools/c#autoreconf = bitten.build.ctools:autoreconf http://bitten.cmlenz.net/tools/xml#transform = bitten.build.xmltools:transform http://bitten.cmlenz.net/tools/python#trace = bitten.build.pythontools:trace http://bitten.cmlenz.net/tools/java#cobertura = bitten.build.javatools:cobertura http://bitten.edgewall.org/tools/java#junit = bitten.build.javatools:junit http://bitten.cmlenz.net/tools/java#junit = bitten.build.javatools:junit http://bitten.edgewall.org/tools/python#trace = bitten.build.pythontools:trace http://bitten.edgewall.org/tools/python#exec = bitten.build.pythontools:exec_ http://bitten.cmlenz.net/tools/python#exec = bitten.build.pythontools:exec_ http://bitten.edgewall.org/tools/c#autoreconf = bitten.build.ctools:autoreconf http://bitten.edgewall.org/tools/sh#pipe = bitten.build.shtools:pipe http://bitten.cmlenz.net/tools/php#phpunit = bitten.build.phptools:phpunit http://bitten.edgewall.org/tools/sh#exec = bitten.build.shtools:exec_ http://bitten.cmlenz.net/tools/svn#export = bitten.build.svntools:export http://bitten.edgewall.org/tools/hg#pull = bitten.build.hgtools:pull http://bitten.cmlenz.net/tools/python#distutils = bitten.build.pythontools:distutils http://bitten.cmlenz.net/tools/mono#nunit = bitten.build.monotools:nunit http://bitten.edgewall.org/tools/c#gcov = bitten.build.ctools:gcov http://bitten.edgewall.org/tools/python#pylint = bitten.build.pythontools:pylint http://bitten.edgewall.org/tools/python#unittest = bitten.build.pythontools:unittest http://bitten.edgewall.org/tools/python#distutils = bitten.build.pythontools:distutils http://bitten.cmlenz.net/tools/svn#update = bitten.build.svntools:update [trac.plugins] bitten.main = bitten.main bitten.master = bitten.master bitten.coverage = bitten.report.coverage bitten.web_ui = bitten.web_ui bitten.notify = bitten.notify bitten.admin = bitten.admin bitten.testing = bitten.report.testing bitten.lint = bitten.report.lint [console_scripts] bitten-slave = bitten.slave:main [distutils.commands] unittest = bitten.util.testrunner:unittest Bitten-0.6/Bitten.egg-info/not-zip-safe000644 000765 000120 00000000001 11536424455 020234 0ustar00simonadmin000000 000000 Bitten-0.6/Bitten.egg-info/PKG-INFO000644 000765 000120 00000000602 11536424600 017071 0ustar00simonadmin000000 000000 Metadata-Version: 1.0 Name: Bitten Version: 0.6 Summary: Continuous integration for Trac Home-page: http://bitten.edgewall.org/ Author: Edgewall Software Author-email: info@edgewall.org License: BSD Download-URL: http://bitten.edgewall.org/wiki/Download Description: A slave for running builds and submitting them to Bitten, the continuous integration system for Trac Platform: UNKNOWN Bitten-0.6/Bitten.egg-info/SOURCES.txt000644 000765 000120 00000015657 11536424600 017700 0ustar00simonadmin000000 000000 COPYING ChangeLog MANIFEST-SLAVE.in MANIFEST.in README.txt setup.cfg setup.py Bitten.egg-info/PKG-INFO Bitten.egg-info/SOURCES.txt Bitten.egg-info/dependency_links.txt Bitten.egg-info/entry_points.txt Bitten.egg-info/not-zip-safe Bitten.egg-info/top_level.txt bitten/__init__.py bitten/admin.py bitten/api.py bitten/main.py bitten/master.py bitten/model.py bitten/notify.py bitten/queue.py bitten/recipe.py bitten/slave.py bitten/upgrades.py bitten/web_ui.py bitten/build/__init__.py bitten/build/api.py bitten/build/config.py bitten/build/ctools.py bitten/build/hgtools.py bitten/build/javatools.py bitten/build/monotools.py bitten/build/phptools.py bitten/build/pythontools.py bitten/build/shtools.py bitten/build/svntools.py bitten/build/xmltools.py bitten/build/tests/__init__.py bitten/build/tests/api.py bitten/build/tests/config.py bitten/build/tests/ctools.py bitten/build/tests/dummy.py bitten/build/tests/hgtools.py bitten/build/tests/javatools.py bitten/build/tests/monotools.py bitten/build/tests/phptools.py bitten/build/tests/pythontools.py bitten/build/tests/xmltools.py bitten/htdocs/admin.css bitten/htdocs/bitten.css bitten/htdocs/bitten_build.png bitten/htdocs/bitten_buildf.png bitten/htdocs/bitten_coverage.css bitten/htdocs/excanvas.js bitten/htdocs/failure.png bitten/htdocs/jquery.flot.js bitten/htdocs/tabset.js bitten/report/__init__.py bitten/report/coverage.py bitten/report/lint.py bitten/report/testing.py bitten/report/tests/__init__.py bitten/report/tests/coverage.py bitten/report/tests/lint.py bitten/report/tests/testing.py bitten/templates/bitten_admin_configs.html bitten/templates/bitten_admin_master.html bitten/templates/bitten_build.html bitten/templates/bitten_config.html bitten/templates/bitten_notify_email.txt bitten/templates/bitten_summary_coverage.html bitten/templates/bitten_summary_lint.html bitten/templates/bitten_summary_tests.html bitten/templates/json.txt bitten/tests/__init__.py bitten/tests/admin.py bitten/tests/master.py bitten/tests/model.py bitten/tests/notify.py bitten/tests/queue.py bitten/tests/upgrades.py bitten/tests/web_ui.py bitten/tests_slave/__init__.py bitten/tests_slave/recipe.py bitten/tests_slave/slave.py bitten/util/__init__.py bitten/util/compat.py bitten/util/json.py bitten/util/loc.py bitten/util/testrunner.py bitten/util/xmlio.py bitten/util/tests/__init__.py bitten/util/tests/json.py bitten/util/tests/xmlio.py doc/commands.html doc/commands.txt doc/configure.html doc/configure.txt doc/index.html doc/index.txt doc/install.html doc/install.txt doc/links.html doc/links.txt doc/logo.pdf doc/logo.png doc/logo_small.png doc/notify.html doc/notify.txt doc/recipes.html doc/recipes.txt doc/reports.html doc/reports.txt doc/upgrade.html doc/upgrade.txt doc/api/api-objects.txt doc/api/bitten-module.html doc/api/bitten.admin-module.html doc/api/bitten.admin.BuildConfigurationsAdminPageProvider-class.html doc/api/bitten.admin.BuildMasterAdminPageProvider-class.html doc/api/bitten.api-module.html doc/api/bitten.api.IBuildListener-class.html doc/api/bitten.api.ILogFormatter-class.html doc/api/bitten.api.IReportChartGenerator-class.html doc/api/bitten.api.IReportSummarizer-class.html doc/api/bitten.build-module.html doc/api/bitten.build.api-module.html doc/api/bitten.build.api.BuildError-class.html doc/api/bitten.build.api.CommandLine-class.html doc/api/bitten.build.api.FileSet-class.html doc/api/bitten.build.api.TimeoutError-class.html doc/api/bitten.build.config-module.html doc/api/bitten.build.config.ConfigFileNotFound-class.html doc/api/bitten.build.config.Configuration-class.html doc/api/bitten.build.ctools-module.html doc/api/bitten.build.hgtools-module.html doc/api/bitten.build.javatools-module.html doc/api/bitten.build.monotools-module.html doc/api/bitten.build.phptools-module.html doc/api/bitten.build.pythontools-module.html doc/api/bitten.build.shtools-module.html doc/api/bitten.build.svntools-module.html doc/api/bitten.build.svntools.Error-class.html doc/api/bitten.build.xmltools-module.html doc/api/bitten.main-module.html doc/api/bitten.main.BuildSystem-class.html doc/api/bitten.master-module.html doc/api/bitten.master.BuildMaster-class.html doc/api/bitten.model-module.html doc/api/bitten.model.Build-class.html doc/api/bitten.model.BuildConfig-class.html doc/api/bitten.model.BuildLog-class.html doc/api/bitten.model.BuildStep-class.html doc/api/bitten.model.Report-class.html doc/api/bitten.model.TargetPlatform-class.html doc/api/bitten.notify-module.html doc/api/bitten.notify.BittenNotify-class.html doc/api/bitten.notify.BuildNotifyEmail-class.html doc/api/bitten.queue-module.html doc/api/bitten.queue.BuildQueue-class.html doc/api/bitten.recipe-module.html doc/api/bitten.recipe.Context-class.html doc/api/bitten.recipe.InvalidRecipeError-class.html doc/api/bitten.recipe.Recipe-class.html doc/api/bitten.recipe.Step-class.html doc/api/bitten.report-module.html doc/api/bitten.report.coverage-module.html doc/api/bitten.report.coverage.TestCoverageAnnotator-class.html doc/api/bitten.report.coverage.TestCoverageChartGenerator-class.html doc/api/bitten.report.coverage.TestCoverageSummarizer-class.html doc/api/bitten.report.lint-module.html doc/api/bitten.report.lint.PyLintChartGenerator-class.html doc/api/bitten.report.lint.PyLintSummarizer-class.html doc/api/bitten.report.testing-module.html doc/api/bitten.report.testing.TestResultsChartGenerator-class.html doc/api/bitten.report.testing.TestResultsSummarizer-class.html doc/api/bitten.slave-module.html doc/api/bitten.slave.BuildSlave-class.html doc/api/bitten.slave.ExitSlave-class.html doc/api/bitten.upgrades-module.html doc/api/bitten.util-module.html doc/api/bitten.util.compat-module.html doc/api/bitten.util.compat.HTTPBasicAuthHandler-class.html doc/api/bitten.util.json-module.html doc/api/bitten.util.loc-module.html doc/api/bitten.util.testrunner-module.html doc/api/bitten.util.testrunner.XMLTestResult-class.html doc/api/bitten.util.testrunner.XMLTestRunner-class.html doc/api/bitten.util.testrunner.unittest-class.html doc/api/bitten.util.xmlio-module.html doc/api/bitten.util.xmlio.Element-class.html doc/api/bitten.util.xmlio.Fragment-class.html doc/api/bitten.util.xmlio.ParsedElement-class.html doc/api/bitten.web_ui-module.html doc/api/bitten.web_ui.BittenChrome-class.html doc/api/bitten.web_ui.BuildConfigController-class.html doc/api/bitten.web_ui.BuildController-class.html doc/api/bitten.web_ui.ReportChartController-class.html doc/api/bitten.web_ui.SourceFileLinkFormatter-class.html doc/api/class-tree.html doc/api/crarr.png doc/api/epydoc.css doc/api/epydoc.js doc/api/help.html doc/api/identifier-index.html doc/api/index.html doc/api/module-tree.html doc/api/redirect.html doc/api/trac.core.ComponentMeta-class.html doc/common/COPYING doc/common/README.txt doc/common/doctools.py doc/common/template.html doc/common/conf/docutils.ini doc/common/conf/epydoc.ini doc/common/style/bkgnd_pattern.png doc/common/style/docutils.css doc/common/style/edgewall.css doc/common/style/epydoc.css doc/common/style/pygments.css doc/common/style/shadow.gif doc/common/style/vertbars.pngBitten-0.6/Bitten.egg-info/top_level.txt000644 000765 000120 00000000007 11536424600 020525 0ustar00simonadmin000000 000000 bitten Bitten-0.6/bitten/__init__.py000644 000765 000120 00000001340 11454420025 016447 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. __docformat__ = 'restructuredtext en' try: __version__ = __import__('pkg_resources').get_distribution('Bitten').version except: try: __version__ = __import__('pkg_resources').get_distribution( 'BittenSlave').version except: pass # The master-slave protocol/configuration version PROTOCOL_VERSION = 5 Bitten-0.6/bitten/admin.py000644 000765 000120 00000035145 11453043212 016010 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 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, add_script, add_warning, add_notice 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 quick_status = 'quick_status' in req.args if quick_status != master.quick_status: self.config['bitten'].set('quick_status', quick_status and 'yes' or 'no') changed = True logs_dir = req.args.get('logs_dir', None) if logs_dir != master.logs_dir: self.config['bitten'].set('logs_dir', str(logs_dir)) 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 warnings = [] 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 platform = self._create_platform(req, config_name) req.redirect(req.abs_href.admin(cat, page, config_name, platform.id)) # 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 'remove' in req.args: # Remove selected platforms self._remove_platforms(req) add_notice(req, "Target Platform(s) Removed.") req.redirect(req.abs_href.admin(cat, page, config.name)) elif 'save' in req.args: # Save this build config warnings = self._update_config(req, config) if not warnings: add_notice(req, "Configuration Saved.") req.redirect(req.abs_href.admin(cat, page, config.name)) for warning in warnings: add_warning(req, warning) # FIXME: Deprecation notice for old namespace. # Remove notice code when migration to new namespace is complete if 'http://bitten.cmlenz.net/tools/' in config.recipe: add_notice(req, "Recipe uses a deprecated namespace. " "Replace 'http://bitten.cmlenz.net/tools/' with " "'http://bitten.edgewall.org/tools/'.") # Add a notice if configuration is not active if not warnings and not config.active and config.recipe: add_notice(req, "Configuration is not active. Activate " "from main 'Configurations' listing to enable it.") # 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), 'recipe': config.recipe and True or False }) data['configs'] = sorted(configs, key=lambda x:x['label'].lower()) add_stylesheet(req, 'bitten/admin.css') add_script(req, 'common/js/suggest.js') 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) warnings = self._update_config(req, config) if warnings: if len(warnings) == 1: raise TracError(warnings[0], 'Add Configuration') else: raise TracError('Errors: %s' % ' '.join(warnings), 'Add Configuration') 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): warnings = [] req.perm.assert_permission('BUILD_MODIFY') name = req.args.get('name') if not name: warnings.append('Missing required field "name".') if name and not re.match(r'^[\w.-]+$', name): warnings.append('The field "name" may only contain letters, ' 'digits, periods, or dashes.') repos = self.env.get_repository(authname=req.authname) if not repos: warnings.append('No "(default)" Repository: Add a repository or ' 'alias named "(default)" to Trac.') path = req.args.get('path', '') min_rev = req.args.get('min_rev') or None max_rev = req.args.get('max_rev') or None if repos: path = repos.normalize_path(path) try: node = repos.get_node(path, max_rev) assert node.isdir, '%s is not a directory' % node.path except (AssertionError, TracError), e: warnings.append('Invalid Repository Path: "%s" does not exist ' 'within the "(default)" repository.' % path) if min_rev: try: repos.get_node(path, min_rev) except TracError, e: warnings.append('Invalid Oldest Revision: %s.' % unicode(e)) recipe_xml = req.args.get('recipe', '') if recipe_xml: try: Recipe(xmlio.parse(recipe_xml)).validate() except xmlio.ParseError, e: warnings.append('Failure parsing recipe: %s.' % unicode(e)) except InvalidRecipeError, e: warnings.append('Invalid Recipe: %s.' % unicode(e)) config.name = name config.path = path config.recipe = recipe_xml config.min_rev = min_rev config.max_rev = max_rev config.label = req.args.get('label', config.name) config.description = req.args.get('description', '') if warnings: # abort return warnings if config.exists: config.update() else: config.insert() return [] def _create_platform(self, req, config_name): req.perm.assert_permission('BUILD_MODIFY') name = req.args.get('platform_name') if not name: raise TracError('Missing required field "name"', 'Missing field') platform = TargetPlatform(self.env, config=config_name, name=name) platform.insert() 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).strip(), req.args.get('pattern_%d' % pattern).strip()) 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 Bitten-0.6/bitten/api.py000644 000765 000120 00000007633 11453043212 015472 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 completed :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` """ Bitten-0.6/bitten/build/000755 000765 000120 00000000000 11536424600 015443 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/htdocs/000755 000765 000120 00000000000 11536424600 015630 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/main.py000644 000765 000120 00000015507 11453043212 015644 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.attachment import ILegacyAttachmentPolicyDelegate from trac.core import * from trac.db import DatabaseManager from trac.env import IEnvironmentSetupParticipant from trac.perm import IPermissionRequestor from trac.resource import IResourceManager from trac.util.html import escape 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 BuildSetup(Component): implements(IEnvironmentSetupParticipant) # 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,)) 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) cursor.execute("UPDATE system SET value=%s WHERE " "name='bitten_version'", (version,)) db.commit() print "Bitten upgrade to version %d done." % version self.log.info('Upgraded Bitten tables to version %d', version) class BuildSystem(Component): implements(IPermissionRequestor, IWikiSyntaxProvider, IResourceManager, ILegacyAttachmentPolicyDelegate) listeners = ExtensionPoint(IBuildListener) # 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): segments = name.split('#') name = segments[0] step = len(segments) == 2 and segments[1] or '' try: name = int(name) except ValueError: return label build = Build.fetch(self.env, 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) if step: if not step.startswith('step_'): step = 'step_' + step step = '#' + escape(step) return '%s' \ % (formatter.href.build(build.config, build.id) + step, title, label) return label yield 'build', _format_link # IResourceManager methods def get_resource_realms(self): yield 'build' def get_resource_url(self, resource, href, **kwargs): config_name, build_id = self._parse_resource(resource.id) return href.build(config_name, build_id) def get_resource_description(self, resource, format=None, context=None, **kwargs): config_name, build_id = self._parse_resource(resource.id) config = BuildConfig.fetch(self.env, config_name) config_label = config and config.label and config.label or config_name if context: if build_id: return tag.a('Build %d ("%s")' % (build_id, config_label), href=href.build(config_name, build_id)) elif config_name: return tag.a('Build Configuration "%s"' % config_label, href=href.build(config_name, build_id)) else: if build_id: return 'Build %d ("%s")' % (build_id, config_label) elif config_name: return 'Build Configuration "%s"' % config_label self.log.error("Unknown build/config resource.id: %s" % resource.id) return 'Unknown Build or Config' def resource_exists(self, resource): config_name, build_id = self._parse_resource(resource.id) if build_id: build = Build.fetch(self.env, build_id) return build and build.exists or False elif config_name: config = BuildConfig.fetch(self.env, config_name) return config and config.exists or False return False def _parse_resource(self, resource_id): """ Returns a (config_name, build_id) tuple. """ r = resource_id.split('/', 1) if len(r) == 1: return r[0], None elif len(r) == 2: try: return r[0], int(r[1]) except: return r[0], None return None, None # ILegacyAttachmentPolicyDelegate methods def check_attachment_permission(self, action, username, resource, perm): """ Respond to the various actions into the legacy attachment permissions used by the Attachment module. """ if resource.parent.realm == 'build': if action == 'ATTACHMENT_VIEW': return 'BUILD_VIEW' in perm(resource.parent) elif action == 'ATTACHMENT_CREATE': return 'BUILD_MODIFY' in perm(resource.parent) \ or 'BUILD_CREATE' in perm(resource.parent) elif action == 'ATTACHMENT_DELETE': return 'BUILD_DELETE' in perm(resource.parent) Bitten-0.6/bitten/master.py000644 000765 000120 00000044373 11457403025 016224 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. """Build master implementation.""" import calendar import re import time from StringIO import StringIO from trac.attachment import Attachment from trac.config import BoolOption, IntOption, Option from trac.core import * from trac.resource import ResourceNotFound from trac.web import IRequestHandler, RequestDone from bitten import PROTOCOL_VERSION from bitten.model import BuildConfig, Build, BuildStep, BuildLog, Report, \ TargetPlatform 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' HTTP_BAD_REQUEST = 400 HTTP_FORBIDDEN = 403 HTTP_NOT_FOUND = 404 HTTP_METHOD_NOT_ALLOWED = 405 HTTP_CONFLICT = 409 class BuildMaster(Component): """Trac request handler 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.""") logs_dir = Option('bitten', 'logs_dir', "log/bitten", doc= """The directory on the server in which client log files will be stored.""") quick_status = BoolOption('bitten', 'quick_status', False, doc= """Whether to show the current build status within the Trac main navigation bar. '''Note:''' The feature requires expensive database and repository checks for every page request, and should not be enabled if the project has a large repository or uses a non-Subversion repository such as Mercurial or Git.""") def __init__(self): self.env.systeminfo.append(('Bitten', __import__('bitten', ['__version__']).__version__)) # 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 'trac_auth' in req.incookie: slave_token = req.incookie['trac_auth'].value else: slave_token = req.session.sid if 'id' not in req.args: if req.method != 'POST': self._send_response(req, body='Only POST allowed for build creation.') return self._process_build_creation(req, slave_token) build = Build.fetch(self.env, req.args['id']) if not build: self._send_error(req, HTTP_NOT_FOUND, 'No such build (%s)' % req.args['id']) build_token = build.slave_info.get('token', '') if build_token != slave_token: self._send_error(req, HTTP_CONFLICT, 'Token mismatch (wrong slave): slave=%s, build=%s' \ % (slave_token, build_token)) 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': self._send_error(req, HTTP_METHOD_NOT_ALLOWED, 'Method %s not allowed' % req.method) if req.args['collection'] == 'steps': return self._process_build_step(req, config, build) elif req.args['collection'] == 'attach': return self._process_attachment(req, config, build) elif req.args['collection'] == 'keepalive': return self._process_keepalive(req, config, build) else: self._send_error(req, HTTP_NOT_FOUND, "No such collection '%s'" % req.args['collection']) # Internal methods def _send_response(self, req, code=200, body='', headers=None): """ Formats and sends the response, raising ``RequestDone``. """ if isinstance(body, unicode): body = body.encode('utf-8') req.send_response(code) headers = headers or {} headers.setdefault('Content-Length', len(body)) for header in headers: req.send_header(header, headers[header]) req.write(body) raise RequestDone def _send_error(self, req, code=500, message=''): """ Formats and sends the error, raising ``RequestDone``. """ headers = {'Content-Type': 'text/plain', 'Content-Length': str(len(message))} self._send_response(req, code, body=message, headers=headers) def _process_build_creation(self, req, slave_token): queue = BuildQueue(self.env, build_all=self.build_all, stabilize_wait=self.stabilize_wait, timeout=self.slave_timeout) try: queue.populate() except AssertionError, e: self.log.error(e.message, exc_info=True) self._send_error(req, HTTP_BAD_REQUEST, e.message) try: elem = xmlio.parse(req.read()) except xmlio.ParseError, e: self.log.error('Error parsing build initialization request: %s', e, exc_info=True) self._send_error(req, HTTP_BAD_REQUEST, 'XML parser error') slave_version = int(elem.attr.get('version', 1)) # FIXME: Remove version compatibility code. # The initial difference between protocol version 3 and 4 is that # the master allows keepalive requests-- the master must be # at least 4 before slaves supporting version 4 are allowed. When # the first force master/slave upgrade requirement comes in # (or we bump the) version number again, remove this code. if slave_version == 3 and PROTOCOL_VERSION == 4: self.log.info('Allowing slave version %d to process build for ' 'compatibility. Upgrade slave to support build ' 'keepalives.', slave_version) elif slave_version != PROTOCOL_VERSION: self._send_error(req, HTTP_BAD_REQUEST, "Master-Slave version mismatch: master=%d, slave=%d" % \ (PROTOCOL_VERSION, slave_version)) slavename = elem.attr['name'] properties = {'name': slavename, Build.IP_ADDRESS: req.remote_addr, Build.TOKEN: slave_token} self.log.info('Build slave %r connected from %s with token %s', slavename, req.remote_addr, slave_token) 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: self._send_response(req, 204, '', {}) self._send_response(req, 201, 'Build pending', headers={ 'Content-Type': 'text/plain', 'Location': req.abs_href.builds(build.id)}) 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) Attachment.delete_all(self.env, 'build', build.resource.id, db) db.commit() for listener in BuildSystem(self.env).listeners: listener.build_aborted(build) self._send_response(req, 204, '', {}) 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.last_activity = build.started 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) target_platform = TargetPlatform.fetch(self.env, build.platform) xml.attr['platform'] = target_platform.name xml.attr['name'] = build.slave xml.attr['form_token'] = req.form_token # For posting attachments body = str(xml) self.log.info('Build slave %r initiated build %d', build.slave, build.id) # create the first step, mark it as in-progress. recipe = Recipe(xmlio.parse(config.recipe)) stepname = recipe.__iter__().next().id step = self._start_new_step(build, stepname) step.insert() self._send_response(req, 200, body, headers={ 'Content-Type': 'application/x-bitten+xml', 'Content-Length': str(len(body)), 'Content-Disposition': 'attachment; filename=recipe_%s_r%s.xml' % (config.name, build.rev)}) 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) self._send_error(req, HTTP_BAD_REQUEST, 'XML parser error') stepname = elem.attr['step'] # we should have created this step previously; if it hasn't, # the master and slave are processing steps out of order. step = BuildStep.fetch(self.env, build=build.id, name=stepname) if not step: self._send_error(req, HTTP_CONFLICT, 'Build step has not been created.') 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: self._send_error(req, HTTP_FORBIDDEN, 'No such build step' % stepname) 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.stopped = int(time.time()) 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')] # TODO: step.update(db=db) step.delete(db=db) 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 otherwise just update last_activity 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 build.last_activity = build.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 == 'fail' or \ recipe_step.onerror == 'continue': build.status = Build.FAILURE break else: build.status = Build.SUCCESS build.update(db=db) else: build.last_activity = step.stopped build.update(db=db) # start the next step. for num, recipe_step in enumerate(recipe): if num == index + 1: next_step = recipe_step if next_step is None: self._send_error(req, HTTP_FORBIDDEN, 'Unable to find step after ' % stepname) step = self._start_new_step(build, next_step.id) step.insert(db=db) db.commit() if last_step: for listener in BuildSystem(self.env).listeners: listener.build_completed(build) body = 'Build step processed' self._send_response(req, 201, body, { 'Content-Type': 'text/plain', 'Content-Length': str(len(body)), 'Location': req.abs_href.builds( build.id, 'steps', stepname)}) def _process_attachment(self, req, config, build): resource_id = req.args['member'] == 'config' \ and build.config or build.resource.id upload = req.args['file'] if not upload.file: send_error(req, message="Attachment not received.") self.log.debug('Received attachment %s for attaching to build:%s', upload.filename, resource_id) # Determine size of file upload.file.seek(0, 2) # to the end size = upload.file.tell() upload.file.seek(0) # beginning again # Delete attachment if it already exists try: old_attach = Attachment(self.env, 'build', parent_id=resource_id, filename=upload.filename) old_attach.delete() except ResourceNotFound: pass # Save new attachment attachment = Attachment(self.env, 'build', parent_id=resource_id) attachment.description = req.args.get('description', '') attachment.author = req.authname attachment.insert(upload.filename, upload.file, size) self._send_response(req, 201, 'Attachment created', headers={ 'Content-Type': 'text/plain', 'Content-Length': str(len('Attachment created'))}) def _process_keepalive(self, req, config, build): build.last_activity = int(time.time()) build.update() self.log.info('Slave %s build %d keepalive ("%s" as of [%s])', build.slave, build.id, build.config, build.rev) body = 'Keepalive processed' self._send_response(req, 200, body, { 'Content-Type': 'text/plain', 'Content-Length': str(len(body))}) def _start_new_step(self, build, stepname): """Creates the in-memory representation for a newly started step, ready to be persisted to the database. """ step = BuildStep(self.env, build=build.id, name=stepname) step.status = BuildStep.IN_PROGRESS step.started = int(time.time()) step.stopped = 0 return step Bitten-0.6/bitten/model.py000644 000765 000120 00000114171 11453043212 016015 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.attachment import Attachment from trac.db import Table, Column, Index from trac.resource import Resource from trac.util.text import to_unicode from trac.util.datefmt import to_timestamp, utcmin, utcmax from datetime import datetime import codecs import os __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') resource = property(fget=lambda self: Resource('build', '%s' % self.name), doc='Build Config resource identification') 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) # Delete attachments Attachment.delete_all(self.env, 'build', self.resource.id, 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) def min_rev_time(self, env): """Returns the time of the minimum revision being built for this configuration. Returns utcmin if not specified. """ repos = env.get_repository() assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' min_time = utcmin if self.min_rev: min_time = repos.get_changeset(self.min_rev).date if isinstance(min_time, datetime): # Trac>=0.11 min_time = to_timestamp(min_time) return min_time def max_rev_time(self, env): """Returns the time of the maximum revision being built for this configuration. Returns utcmax if not specified. """ repos = env.get_repository() assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' max_time = utcmax if self.max_rev: max_time = repos.get_changeset(self.max_rev).date if isinstance(max_time, datetime): # Trac>=0.11 max_time = to_timestamp(max_time) return max_time 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', type='int'), 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 for build in Build.select(self.env, platform=self.id, status=Build.PENDING, db=db): build.delete() 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), Column('last_activity', type='int'), Index(['config', 'rev', 'platform'], unique=True) ], 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' TOKEN = 'token' def __init__(self, env, config=None, rev=None, platform=None, slave=None, started=0, stopped=0, last_activity=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.last_activity = last_activity 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') resource = property(fget=lambda self: Resource('build', '%s/%s' % (self.config, self.id)), doc='Build resource identification') 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) # Delete attachments Attachment.delete_all(self.env, 'build', self.resource.id, 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,last_activity,status) " "VALUES (%s,%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.last_activity 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,last_activity=%s,status=%s WHERE id=%s", (self.slave or '', self.started or 0, self.stopped or 0, self.last_activity 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,last_activity,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, last_activity=row[7] and int(row[7]) or 0, status=row[8]) 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, min_rev_time=None, max_rev_time=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", str(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 min_rev_time is not None: where_clauses.append(("rev_time>=%s", min_rev_time)) if max_rev_time is not None: where_clauses.append(("rev_time<=%s", max_rev_time)) 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 rev_time DESC,config,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' IN_PROGRESS = 'I' 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') completed = property(fget=lambda self: self.status == BuildStep.SUCCESS or self.status == BuildStep.FAILURE, doc='Whether this build step has completed processing') 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.IN_PROGRESS, 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.IN_PROGRESS, 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 started" % 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'), Column('filename'), Index(['build', 'step']) ], ] # Message levels DEBUG = 'D' INFO = 'I' WARNING = 'W' ERROR = 'E' UNKNOWN = '' LEVELS_SUFFIX = '.levels' def __init__(self, env, build=None, step=None, generator=None, orderno=None, filename=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.filename = filename or None self.messages = [] self.logs_dir = env.config.get('bitten', 'logs_dir', 'log/bitten') if not os.path.isabs(self.logs_dir): self.logs_dir = os.path.join(env.path, self.logs_dir) if not os.path.exists(self.logs_dir): os.makedirs(self.logs_dir) exists = property(fget=lambda self: self.id is not None, doc='Whether this build log exists in the database') def get_log_file(self, filename): """Returns the full path to the log file""" if filename != os.path.basename(filename): raise ValueError("Filename may not contain path: %s" % (filename,)) return os.path.join(self.logs_dir, filename) 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 if self.filename: log_file = self.get_log_file(self.filename) if os.path.exists(log_file): try: self.env.log.debug("Deleting log file: %s" % log_file) os.remove(log_file) except Exception, e: self.env.log.warning("Error removing log file %s: %s" % (log_file, e)) level_file = log_file + self.LEVELS_SUFFIX if os.path.exists(level_file): try: self.env.log.debug("Deleting level file: %s" % level_file) os.remove(level_file) except Exception, e: self.env.log.warning("Error removing level file %s: %s" \ % (level_file, e)) cursor = db.cursor() 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') log_file = "%s.log" % (id,) cursor.execute("UPDATE bitten_log SET filename=%s WHERE id=%s", (log_file, id)) if self.messages: log_file_name = self.get_log_file(log_file) level_file_name = log_file_name + self.LEVELS_SUFFIX codecs.open(log_file_name, "wb", "UTF-8").writelines([to_unicode(msg[1]+"\n") for msg in self.messages]) codecs.open(level_file_name, "wb", "UTF-8").writelines([to_unicode(msg[0]+"\n") for msg in 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,filename 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], row[4]) log.id = id if log.filename: log_filename = log.get_log_file(log.filename) if os.path.exists(log_filename): log_lines = codecs.open(log_filename, "rb", "UTF-8").readlines() else: log_lines = [] level_filename = log.get_log_file(log.filename + cls.LEVELS_SUFFIX) if os.path.exists(level_filename): log_levels = dict(enumerate(codecs.open(level_filename, "rb", "UTF-8").readlines())) else: log_levels = {} log.messages = [(log_levels.get(line_num, BuildLog.UNKNOWN).rstrip("\n"), line.rstrip("\n")) for line_num, line in enumerate(log_lines)] else: log.messages = [] 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 = 12 Bitten-0.6/bitten/notify.py000644 000765 000120 00000013256 11460004634 016232 0ustar00simonadmin000000 000000 #-*- coding: utf-8 -*- # # Copyright (C) 2007 Ole Trenner, # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. from genshi.template.text import NewTextTemplate from trac.core import Component, implements from trac.web.chrome import ITemplateProvider, Chrome from trac.config import BoolOption from trac.notification import NotifyEmail from bitten.api import IBuildListener from bitten.model import Build, BuildStep, BuildLog, TargetPlatform class BittenNotify(Component): """Sends notifications on build status by mail.""" implements(IBuildListener, ITemplateProvider) notify_on_failure = BoolOption('notification', 'notify_on_failed_build', 'true', """Notify if bitten build fails.""") notify_on_success = BoolOption('notification', 'notify_on_successful_build', 'false', """Notify if bitten build succeeds.""") def __init__(self): self.log.debug('Initializing BittenNotify plugin') def notify(self, build=None): self.log.info('BittenNotify invoked for build %r', build) self.log.debug('build status: %s', build.status) if not self._should_notify(build): return self.log.info('Sending notification for build %r', build) try: email = BuildNotifyEmail(self.env) email.notify(build) except Exception, e: self.log.exception("Failure sending notification for build " "%s: %s", build.id, e) def _should_notify(self, build): if build.status == Build.FAILURE: return self.notify_on_failure elif build.status == Build.SUCCESS: return self.notify_on_success else: return False # IBuildListener methods def build_started(self, build): """build started""" self.notify(build) def build_aborted(self, build): """build aborted""" self.notify(build) def build_completed(self, build): """build completed""" self.notify(build) # ITemplateProvider methods def get_templates_dirs(self): """Return a list of directories containing the provided template files.""" from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): """Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc).""" return [] class BuildNotifyEmail(NotifyEmail): """Notification of failed builds.""" readable_states = { Build.SUCCESS: 'Successful', Build.FAILURE: 'Failed', } template_name = 'bitten_notify_email.txt' from_email = 'bitten@localhost' def __init__(self, env): NotifyEmail.__init__(self, env) # Override the template type to always use NewTextTemplate if not isinstance(self.template, NewTextTemplate): self.template = Chrome(env).templates.load( self.template.filepath, cls=NewTextTemplate) def notify(self, build): self.build = build self.data.update(self.template_data()) subject = '[%s Build] %s [%s] %s' % (self.readable_states[build.status], self.env.project_name, self.build.rev, self.build.config) NotifyEmail.notify(self, self.build.id, subject) def get_recipients(self, resid): to = [self.get_author()] cc = [] return (to, cc) def send(self, torcpts, ccrcpts): mime_headers = { 'X-Trac-Build-ID': str(self.build.id), 'X-Trac-Build-URL': self.build_link(), } NotifyEmail.send(self, torcpts, ccrcpts, mime_headers) def build_link(self): return self.env.abs_href.build(self.build.config, self.build.id) def template_data(self): failed_steps = BuildStep.select(self.env, build=self.build.id, status=BuildStep.FAILURE) platform = TargetPlatform.fetch(self.env, id=self.build.platform) change = self.get_changeset() return { 'build': { 'id': self.build.id, 'status': self.readable_states[self.build.status], 'link': self.build_link(), 'config': self.build.config, 'platform': getattr(platform, 'name', 'unknown'), 'slave': self.build.slave, 'failed_steps': [{ 'name': step.name, 'description': step.description, 'errors': step.errors, 'log_messages': self.get_all_log_messages_for_step(step), } for step in failed_steps], }, 'change': { 'rev': change.rev, 'link': self.env.abs_href.changeset(change.rev), 'author': change.author, }, } def get_all_log_messages_for_step(self, step): messages = [] for log in BuildLog.select(self.env, build=self.build.id, step=step.name): messages.extend(log.messages) return messages def get_changeset(self): repos = self.env.get_repository() assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' return repos.get_changeset(self.build.rev) def get_author(self): return self.get_changeset().author Bitten-0.6/bitten/queue.py000644 000765 000120 00000033417 11535320455 016054 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. """Implements the scheduling of builds for a project. This module provides the functionality for scheduling builds for a specific Trac environment. It is used by both the build master and the web interface to get the list of required builds (revisions not built yet). Furthermore, the `BuildQueue` class is used by the build master to determine the next pending build, and to match build slaves against configured target platforms. """ from itertools import ifilter import re import time from trac.util.datefmt import to_timestamp from trac.util import pretty_timedelta, format_datetime from trac.attachment import Attachment from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep __docformat__ = 'restructuredtext en' 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, config.max_rev) except Exception, e: env.log.warn('Error accessing path %r for configuration %r', config.path, 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` """ self.log.debug('Checking for pending builds...') db = self.env.get_db_cnx() repos = self.env.get_repository() assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' 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)] builds_to_delete = [] build_found = False 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: build_found = True break if not build_found: 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() assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' 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 = to_timestamp(repos.get_changeset(rev).date) 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: try: build.insert(db=db) db.commit() except Exception, e: # really only want to catch IntegrityErrors raised when # a second slave attempts to add builds with the same # (config, platform, rev) as an existing build. self.log.info('Failed to insert build of configuration "%s" ' 'at revision [%s] on platform [%s]: %s', build.config, build.rev, build.platform, e) db.rollback() 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.last_activity < self.timeout: # This build has not reached the timeout yet, assume it's still # being executed continue self.log.info('Orphaning build %d. Last activity was %s (%s)' % \ (build.id, format_datetime(build.last_activity), pretty_timedelta(build.last_activity))) build.status = Build.PENDING build.slave = None build.slave_info = {} build.started = 0 build.stopped = 0 build.last_activity = 0 for step in list(BuildStep.select(self.env, build=build.id, db=db)): step.delete(db=db) build.update(db=db) Attachment.delete_all(self.env, 'build', build.resource.id, db) db.commit() def should_delete_build(self, build, repos): config = BuildConfig.fetch(self.env, build.config) config_name = config and config.name \ or 'unknown config "%s"' % build.config platform = TargetPlatform.fetch(self.env, build.platform) # Platform may or may not exist anymore - get safe name for logging platform_name = platform and platform.name \ or 'unknown platform "%s"' % build.platform # Drop build if platform no longer exists if not platform: self.log.info('Dropping build of configuration "%s" at ' 'revision [%s] on %s because the platform no longer ' 'exists', config.name, build.rev, platform_name) return True # Ignore pending builds for deactived build configs if not (config and config.active): self.log.info('Dropping build of configuration "%s" at ' 'revision [%s] on %s because the configuration is ' 'deactivated', config_name, build.rev, 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)): self.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, platform_name) return True # If not 'build_all', drop if a more recent revision is available if not self.build_all and \ len(list(Build.select(self.env, config=build.config, min_rev_time=build.rev_time, platform=build.platform))) > 1: self.log.info('Dropping build of configuration "%s" at revision [%s] ' 'on "%s" because a more recent build exists', config.name, build.rev, platform_name) return True return False Bitten-0.6/bitten/recipe.py000644 000765 000120 00000027361 11454420025 016172 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. """Execution of build recipes. This module provides various classes that can be used to process build recipes, most importantly the `Recipe` class. """ import inspect import keyword import logging import os import time try: set except NameError: from sets import Set as set from pkg_resources import WorkingSet from bitten.build import BuildError, TimeoutError 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)) self.vars['basedir'] = self.basedir.replace('\\', '\\\\') 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 elif name == 'attach': function = Context.attach 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]) function_args, has_kwargs = inspect.getargspec(function)[0:3:2] for arg in args: if not (arg in function_args or has_kwargs): raise InvalidRecipeError( "Unsupported argument '%s' for command %s" % \ (arg, qname)) 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 attach(self, file_=None, description=None, resource=None): """Attach a file to the build or build configuration. :param file\_: the path to the file to attach, relative to base directory. :param description: description saved with attachment :param resource: which resource to attach the file to, either 'build' (default) or 'config' """ # Attachments are not added as inline xml, so only adding # the details for later processing. if not file_: self.error('No attachment file specified.') return xml_elem = xmlio.Element('file', filename=file_, description=description or '', resource=resource or 'build') self.output.append((Recipe.ATTACH, None, None, xml_elem)) 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, onerror_default): """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', onerror_default) assert self.onerror in ('fail', 'ignore', 'continue') 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` """ last_finish = time.time() for child in self._elem: try: ctxt.run(self, child.namespace, child.name, child.attr) except (BuildError, InvalidRecipeError, TimeoutError), e: ctxt.error(e) if time.time() < last_finish + 1: # Add a delay to make sure steps appear in correct order time.sleep(1) 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: for _t, error in errors: log.error(error) if self.onerror != 'ignore': raise BuildError("Build step '%s' failed" % self.id) log.warning("Continuing despite errors in step '%s'", self.id) 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' ATTACH = 'attach' 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 self.onerror_default = vars.get('onerror', 'fail') assert self.onerror_default in ('fail', 'ignore', 'continue') def __iter__(self): """Iterate over the individual steps of the recipe.""" for child in self._root.children('step'): yield Step(child, self.onerror_default) def validate(self): """Validate the recipe. This method checks a number of constraints: - the name of the root element must be "build" - the only permitted child elements or the root element with the name "step" - the recipe must contain at least one step - step elements must have a unique "id" attribute - a step must contain at least one nested command - commands must not have nested content :raise InvalidRecipeError: in case any of the above contraints is violated """ if self._root.name != 'build': raise InvalidRecipeError('Root element must be ') steps = list(self._root.children()) if not steps: raise InvalidRecipeError('Recipe defines no build steps') step_ids = set() for step in steps: if step.name != 'step': raise InvalidRecipeError('Only elements allowed at ' 'top level of recipe') if not step.attr.get('id'): raise InvalidRecipeError('Steps must have an "id" attribute') if step.attr['id'] in step_ids: raise InvalidRecipeError('Duplicate step ID "%s"' % step.attr['id']) step_ids.add(step.attr['id']) cmds = list(step.children()) if not cmds: raise InvalidRecipeError('Step "%s" has no recipe commands' % step.attr['id']) for cmd in cmds: if len(list(cmd.children())): raise InvalidRecipeError('Recipe command <%s> has nested ' 'content' % cmd.name) Bitten-0.6/bitten/report/000755 000765 000120 00000000000 11536424600 015657 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/slave.py000755 000765 000120 00000067120 11535713255 016047 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. """Implementation of the build slave.""" from datetime import datetime import errno import urllib import urllib2 import logging import os import platform import shutil import socket import sys import tempfile import time import re import cookielib import threading import mimetools from ConfigParser import MissingSectionHeaderError from bitten import PROTOCOL_VERSION from bitten.build import BuildError from bitten.build.config import Configuration, ConfigFileNotFound from bitten.recipe import Recipe from bitten.util import xmlio from bitten.util.compat import HTTPBasicAuthHandler EX_OK = getattr(os, "EX_OK", 0) EX_UNAVAILABLE = getattr(os, "EX_UNAVAILABLE", 69) EX_IOERR = getattr(os, "EX_IOERR", 74) EX_PROTOCOL = getattr(os, "EX_PROTOCOL", 76) EX_NOPERM = getattr(os, "EX_NOPERM", 77) FORM_TOKEN_RE = re.compile('__FORM_TOKEN\" value=\"(.+)\"') __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, and only remove if root exists.""" def _handle_error(fn, path, excinfo): os.chmod(path, 0666) fn(path) if os.path.exists(root): return shutil.rmtree(root, onerror=_handle_error) else: return False 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 def encode_multipart_formdata(fields): """ Given a dictionary field parameters, returns the HTTP request body and the content_type (which includes the boundary string), to be used with an httplib-like call. Normal key/value items are treated as regular parameters, but key/tuple items are treated as files, where a value tuple is a (filename, data) tuple. For example:: fields = { 'foo': 'bar', 'foofile': ('foofile.txt', 'contents of foofile'), } body, content_type = encode_multipart_formdata(fields) Note: Adapted from http://code.google.com/p/urllib3/ (MIT license) """ BOUNDARY = mimetools.choose_boundary() ENCODE_TEMPLATE= "--%(boundary)s\r\n" \ "Content-Disposition: form-data; name=\"%(name)s\"\r\n" \ "\r\n%(value)s\r\n" ENCODE_TEMPLATE_FILE = "--%(boundary)s\r\n" \ "Content-Disposition: form-data; name=\"%(name)s\"; " \ "filename=\"%(filename)s\"\r\n" \ "Content-Type: %(contenttype)s\r\n" \ "\r\n%(value)s\r\n" body = "" for key, value in fields.iteritems(): if isinstance(value, tuple): filename, value = value body += ENCODE_TEMPLATE_FILE % { 'boundary': BOUNDARY, 'name': str(key), 'value': str(value), 'filename': str(filename), 'contenttype': 'application/octet-stream' } else: body += ENCODE_TEMPLATE % { 'boundary': BOUNDARY, 'name': str(key), 'value': str(value) } body += '--%s--\r\n' % BOUNDARY content_type = 'multipart/form-data; boundary=%s' % BOUNDARY return body, content_type class KeepAliveThread(threading.Thread): "A thread to periodically send keep-alive messages to the master" def __init__(self, opener, build_url, single_build, keepalive_interval): threading.Thread.__init__(self, None, None, "KeepaliveThread") self.build_url = build_url self.keepalive_interval = keepalive_interval self.single_build = single_build self.last_keepalive = int(time.time()) self.kill = False self.opener = opener def keepalive(self): log.debug('Sending keepalive') method = 'POST' url = self.build_url + '/keepalive/' body = None shutdown = False headers = { 'Content-Type': 'application/x-bitten+xml', 'Content-Length': '0' } 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: # a conflict error lets us know that we've been # invalidated. Ideally, we'd engineer something to stop any # running steps in progress, but killing threads is tricky # stuff. For now, we'll wait for whatever's going # on to stop, and the main thread'll figure out that we've # been invalidated. log.warning('Server returned keepalive error %d: %s', e.code, e.msg) except: log.warning('Server returned unknown keepalive error') def run(self): log.debug('Keepalive thread starting.') while (not self.kill): now = int(time.time()) if (self.last_keepalive + self.keepalive_interval) < now: self.keepalive() self.last_keepalive = now time.sleep(1) log.debug('Keepalive thread exiting.') def stop(self): log.debug('Stopping keepalive thread') self.kill = True self.join(30) log.debug('Keepalive thread stopped') class BuildSlave(object): """HTTP client 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, keepalive_interval = 60, username=None, password=None, dump_reports=False, no_loop=False, form_auth=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 keepalive_interval: the time in seconds to wait between sending keepalive heartbeats (default is 30 seconds) :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 :param no_loop: for this slave to just perform a single check, regardless of whether a build is done or not :param form_auth: login using AccountManager HTML form instead of HTTP authentication for all urls """ 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.no_loop = no_loop self.poll_interval = poll_interval self.keepalive_interval = keepalive_interval self.dump_reports = dump_reports self.cookiejar = cookielib.CookieJar() self.username = username \ or self.config['authentication.username'] or '' if not self.local: self.password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() if self.username: log.debug('Enabling authentication with username %r', self.username) self.form_auth = form_auth password = password \ or self.config['authentication.password'] or '' self.config.packages.pop('authentication', None) urls = [url[:-7] for url in urls] self.password_mgr.add_password( None, urls, self.username, password) self.auth_map = dict(map(lambda x: (x, False), urls)) def _get_opener(self): opener = urllib2.build_opener(urllib2.HTTPErrorProcessor()) opener.add_handler(HTTPBasicAuthHandler(self.password_mgr)) opener.add_handler(urllib2.HTTPDigestAuthHandler(self.password_mgr)) opener.add_handler(urllib2.HTTPCookieProcessor(self.cookiejar)) return opener opener = property(_get_opener) 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: resp = self.opener.open(req) if not hasattr(resp, 'code'): resp.code = 200 return resp except urllib2.HTTPError, e: if e.code >= 300: if hasattr(e, 'headers') and \ e.headers.getheader('Content-Type', '' ).startswith('text/plain'): content = e.read() else: content = 'no message available' log.debug('Server returned error %d: %s (%s)', e.code, e.msg, content) 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: if self.username and not self.auth_map.get(url): login_url = '%s/login?referer=%s' % (url[:-7], urllib.quote_plus(url)) # First request to url, authentication needed if self.form_auth: log.debug('Performing http form authentication') resp = self.request('POST', login_url) match = FORM_TOKEN_RE.search(resp.read()) if not match: log.error("Project %s does not support form " "authentication" % url[:-7]) raise ExitSlave(EX_NOPERM) values = {'user': self.username, 'password': self.password_mgr.find_user_password( None, url)[1], 'referer': '', '__FORM_TOKEN': match.group(1)} self.request('POST', login_url, body=urllib.urlencode(values)) else: log.debug('Performing basic/digest authentication') self.request('HEAD', login_url) self.auth_map[url] = True elif self.username: log.debug('Reusing authentication information.') else: log.debug('Authentication not provided. Attempting to ' 'execute build anonymously.') 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 if self.no_loop: break 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, version=PROTOCOL_VERSION)[ 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': str(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) basedir = '' try: if not self.local: keepalive_thread = KeepAliveThread(self.opener, build_url, self.single_build, self.keepalive_interval) keepalive_thread.start() 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: try: log.info('Executing build step %r, onerror = %s', step.id, step.onerror) if not self._execute_step(build_url, recipe, step): log.warning('Stopping build due to failure') break except Exception, e: log.error('Exception raised processing step %s. Reraising %s', step.id, e) raise else: log.info('Build completed') if self.dry_run: self._cancel_build(build_url) finally: if not self.local: keepalive_thread.stop() if not self.keep_files and os.path.isdir(basedir): 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 = int(time.time()) xml = xmlio.Element('result', step=step.id) 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 if type == Recipe.ATTACH: # Attachments are added out-of-band due to major # performance issues with inlined base64 xml content self._attach_file(build_url, recipe, 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', step.id) failed = True except Exception, e: log.error('Internal error in build step %r', step.id, exc_info=True) failed = True xml.attr['duration'] = (time.time() - started) if failed: xml.attr['status'] = 'failure' 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) def _attach_file(self, build_url, recipe, attachment): form_token = recipe._root.attr.get('form_token', '') if self.local or self.dry_run or not form_token: log.info('Attachment %s not sent due to current slave options', attachment.attr['file']) return resource_type = attachment.attr['resource'] url = str(build_url + '/attach/' + resource_type) path = recipe.ctxt.resolve(attachment.attr['filename']) filename = os.path.basename(path) log.debug('Attaching file %s to %s...', attachment.attr['filename'], resource_type) f = open(path, 'rb') try: data, content_type = encode_multipart_formdata({ 'file': (filename, f.read()), 'description': attachment.attr['description'], '__FORM_TOKEN': form_token}) finally: f.close() resp = self.request('POST', url , data, { 'Content-Type': content_type}) if not resp.code == 201: msg = 'Error attaching %s to %s' log.error(msg, filename, resource_type) raise BuildError(msg, filename, resource_type) 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') def _ask_password(option, opt_str, value, parser): from getpass import getpass parser.values.password = getpass('Passsword: ') parser.add_option('-P', '--ask-password', action='callback', callback=_ask_password, help='Prompt for password') parser.add_option('--form-auth', action='store_true', dest='form_auth', help='login using AccountManager HTML form instead of ' 'HTTP authentication for all urls') 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('', '--no-loop', action='store_true', dest='no_loop', help='exit after completing a single check and running ' 'the required builds') 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.add_option('-b', '--keepalive_interval', dest='keepalive_interval', metavar='SECONDS', type='int', help='time to wait between keepalive heartbeats') 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, no_loop=False, dump_reports=False, interval=300, keepalive_interval=60, form_auth=False) 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) log.info("Slave launched at %s" % \ datetime.now().strftime('%Y-%m-%d %H:%M:%S')) slave = None try: 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, no_loop=options.no_loop, poll_interval=options.interval, keepalive_interval=options.keepalive_interval, username=options.username, password=options.password, dump_reports=options.dump_reports, form_auth=options.form_auth) try: exit_code = slave.run() except KeyboardInterrupt: slave.quit() except ConfigFileNotFound, e: log.error(e) exit_code = EX_IOERR except MissingSectionHeaderError: log.error("Error parsing configuration file %r. Wrong format?" \ % options.config) exit_code = EX_IOERR except ExitSlave, e: exit_code = e.exit_code if slave and not (options.work_dir or options.keep_files): log.debug('Removing working directory %s' % slave.work_dir) _rmtree(slave.work_dir) log.info("Slave exited at %s" % \ datetime.now().strftime('%Y-%m-%d %H:%M:%S')) return exit_code if __name__ == '__main__': sys.exit(main()) Bitten-0.6/bitten/templates/000755 000765 000120 00000000000 11536424600 016342 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/tests/000755 000765 000120 00000000000 11536424600 015506 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/tests_slave/000755 000765 000120 00000000000 11536424600 016700 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/upgrades.py000644 000765 000120 00000063046 11457032700 016537 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. """Automated upgrades for the Bitten database tables, and other data stored in the Trac environment. **Do not import and call directly!**""" import os import sys from trac.core import TracError from trac.db import DatabaseManager, Table, Column, Index from trac.util.text import to_unicode import codecs __docformat__ = 'restructuredtext en' # database abstraction functions def parse_scheme(env): """Retrieve the environment database scheme.""" connection_uri = DatabaseManager(env).connection_uri parts = connection_uri.split(':', 1) scheme = parts[0].lower() return scheme def update_sequence(env, db, tbl, col): """Update a sequence associated with an autoincrement column.""" # Hopefully Trac will eventually implement its own version # of this function. scheme = parse_scheme(env) if scheme == "postgres": seq = '%s_%s_seq' % (tbl, col) cursor = db.cursor() cursor.execute("SELECT setval('%s', (SELECT MAX(%s) FROM %s))" % (seq, col, tbl)) def drop_index(env, db, tbl, idx): """Drop an index associated with a table.""" # Hopefully Trac will eventually implement its own version # of this function. scheme = parse_scheme(env) cursor = db.cursor() if scheme == "mysql": cursor.execute("DROP INDEX %s ON %s" % (idx, tbl)) else: cursor.execute("DROP INDEX %s" % (idx,)) # upgrade scripts def add_log_table(env, db): """Add a table for storing the builds logs.""" INFO_LEVEL = 'I' cursor = db.cursor() build_log_schema_v3 = [ Table('bitten_log', key='id')[ Column('id', auto_increment=True), Column('build', type='int'), Column('step'), Column('type') ], Table('bitten_log_message', key=('log', 'line'))[ Column('log', type='int'), Column('line', type='int'), Column('level', size=1), Column('message') ] ] build_step_schema_v3 = [ 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') ] ] connector, _ = DatabaseManager(env)._get_connector() for table in build_log_schema_v3: for stmt in connector.to_sql(table): cursor.execute(stmt) update_cursor = db.cursor() cursor.execute("SELECT build,name,log FROM bitten_step " "WHERE log IS NOT NULL") for build, step, log in cursor: update_cursor.execute("INSERT INTO bitten_log (build, step) " "VALUES (%s,%s)", (build, step)) log_id = db.get_last_id(update_cursor, 'bitten_log') messages = [(log_id, line, INFO_LEVEL, msg) for line, msg in enumerate(log.splitlines())] update_cursor.executemany("INSERT INTO bitten_log_message (log, line, level, message) " "VALUES (%s, %s, %s, %s)", messages) cursor.execute("CREATE TEMPORARY TABLE old_step AS SELECT * FROM bitten_step") cursor.execute("DROP TABLE bitten_step") for table in build_step_schema_v3: 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.""" cursor = db.cursor() build_config_schema_v3 = 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') ] cursor.execute("CREATE TEMPORARY TABLE old_config_v2 AS " "SELECT * FROM bitten_config") cursor.execute("DROP TABLE bitten_config") connector, _ = DatabaseManager(env)._get_connector() for stmt in connector.to_sql(build_config_schema_v3): 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_v2") def add_last_activity_to_build(env, db): """Add a column for storing the last activity to the build table.""" cursor = db.cursor() build_table_schema_v12 = 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), Column('last_activity', type='int'), Index(['config', 'rev', 'platform'], unique=True) ] cursor.execute("CREATE TEMPORARY TABLE old_build_v11 AS " "SELECT * FROM bitten_build") cursor.execute("DROP TABLE bitten_build") connector, _ = DatabaseManager(env)._get_connector() for stmt in connector.to_sql(build_table_schema_v12): cursor.execute(stmt) # it's safe to make the last activity the stop time of the build cursor.execute("INSERT INTO bitten_build (id,config,rev,rev_time,platform," "slave,started,stopped,last_activity,status) " "SELECT id,config,rev,rev_time,platform," "slave,started,stopped,stopped,status FROM old_build_v11") update_sequence(env, db, 'bitten_build', 'id') 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.""" 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()) cursor = db.cursor() cursor.execute("SELECT config FROM bitten_build WHERE id=%s", (build_id,)) row = cursor.fetchone() if row: doc.setMetaData('', 'config', dbxml.XmlValue(row[0])) 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.""" cursor = db.cursor() log_table_schema_v6 = 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']) ] cursor.execute("CREATE TEMPORARY TABLE old_log_v5 AS " "SELECT * FROM bitten_log") cursor.execute("DROP TABLE bitten_log") connector, _ = DatabaseManager(env)._get_connector() for stmt in connector.to_sql(log_table_schema_v6): cursor.execute(stmt) cursor.execute("INSERT INTO bitten_log (id,build,step,generator,orderno) " "SELECT id,build,step,type,0 FROM old_log_v5") def add_report_tables(env, db): """Add database tables for report storage.""" cursor = db.cursor() report_schema_v6 = Table('bitten_report', key='id')[ Column('id', auto_increment=True), Column('build', type='int'), Column('step'), Column('category'), Column('generator'), Index(['build', 'step', 'category']) ] report_item_schema_v6 = Table('bitten_report_item', key=('report', 'item', 'name'))[ Column('report', type='int'), Column('item', type='int'), Column('name'), Column('value') ] connector, _ = DatabaseManager(env)._get_connector() for table in [report_schema_v6, report_item_schema_v6]: 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.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() items = list(get_items(xml)) cursor = db.cursor() cursor.execute("SELECT bitten_report.id FROM bitten_report " "WHERE build=%s AND step=%s AND category=%s", (build, step, category)) rows = cursor.fetchall() if rows: # Duplicate report, skip continue cursor.execute("INSERT INTO bitten_report " "(build,step,category,generator) VALUES (%s,%s,%s,%s)", (build, step, category, report_type)) id = db.get_last_id(cursor, 'bitten_report') for idx, item in enumerate(items): 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()]) 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.edgewall.org/tools/sh#pipe', 'make': 'http://bitten.edgewall.org/tools/c#make', 'distutils': 'http://bitten.edgewall.org/tools/python#distutils', 'exec_': 'http://bitten.edgewall.org/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.edgewall.org/tools/python#unittest', 'trace': 'http://bitten.edgewall.org/tools/python#trace', 'pylint': 'http://bitten.edgewall.org/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.""" 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) def add_filename_to_logs(env, db): """Add filename column to log table to save where log files are stored.""" cursor = db.cursor() build_log_schema_v9 = Table('bitten_log', key='id')[ Column('id', auto_increment=True), Column('build', type='int'), Column('step'), Column('generator'), Column('orderno', type='int'), Column('filename'), Index(['build', 'step']) ] cursor.execute("CREATE TEMPORARY TABLE old_log_v8 AS " "SELECT * FROM bitten_log") cursor.execute("DROP TABLE bitten_log") connector, _ = DatabaseManager(env)._get_connector() for stmt in connector.to_sql(build_log_schema_v9): cursor.execute(stmt) cursor.execute("INSERT INTO bitten_log (id,build,step,generator,orderno,filename) " "SELECT id,build,step,generator,orderno,'' FROM old_log_v8") def migrate_logs_to_files(env, db): """Migrates logs that are stored in the bitten_log_messages table into files.""" logs_dir = env.config.get("bitten", "logs_dir", "log/bitten") if not os.path.isabs(logs_dir): logs_dir = os.path.join(env.path, logs_dir) if os.path.exists(logs_dir): print "Bitten log folder %r already exists" % (logs_dir,) print "Upgrade cannot be performed until the existing folder is moved." print "The upgrade script will now exit with an error:\n" raise TracError("") os.makedirs(logs_dir) cursor = db.cursor() message_cursor = db.cursor() update_cursor = db.cursor() cursor.execute("SELECT id FROM bitten_log") for log_id, in cursor.fetchall(): filename = "%s.log" % (log_id,) message_cursor.execute("SELECT message, level FROM bitten_log_message WHERE log=%s ORDER BY line", (log_id,)) full_filename = os.path.join(logs_dir, filename) message_file = codecs.open(full_filename, "wb", "UTF-8") # Note: the original version of this code erroneously wrote to filename + ".level" instead of ".levels", producing unused level files level_file = codecs.open(full_filename + '.levels', "wb", "UTF-8") for message, level in message_cursor.fetchall() or []: message_file.write(to_unicode(message) + "\n") level_file.write(to_unicode(level) + "\n") message_file.close() level_file.close() update_cursor.execute("UPDATE bitten_log SET filename=%s WHERE id=%s", (filename, log_id)) env.log.info("Migrated log %s", log_id) env.log.warning("Logs have been migrated from the database to files in %s. " "Ensure permissions are set correctly on this file. " "Since we presume that the migration worked correctly, " "we are now dropping the bitten_log_message table in the database (aren't you glad you backed up)", logs_dir) cursor.close() cursor = db.cursor() cursor.execute("DROP TABLE bitten_log_message") cursor.close() env.log.warning("We have dropped the bitten_log_message table - you may want to vaccuum/compress your database to save space") def fix_log_levels_misnaming(env, db): """Renames or removes \*.log.level files created by older versions of migrate_logs_to_files.""" logs_dir = env.config.get("bitten", "logs_dir", "log/bitten") if not os.path.isabs(logs_dir): logs_dir = os.path.join(env.path, logs_dir) if not os.path.isdir(logs_dir): return rename_count = 0 rename_error_count = 0 delete_count = 0 delete_error_count = 0 for wrong_filename in os.listdir(logs_dir): if not wrong_filename.endswith('.log.level'): continue log_filename = os.path.splitext(wrong_filename)[0] right_filename = log_filename + '.levels' full_log_filename = os.path.join(logs_dir, log_filename) full_wrong_filename = os.path.join(logs_dir, wrong_filename) full_right_filename = os.path.join(logs_dir, right_filename) if not os.path.exists(full_log_filename): try: os.remove(full_wrong_filename) delete_count += 1 env.log.info("Deleted stray log level file %s", wrong_filename) except Exception, e: delete_error_count += 1 env.log.warning("Error removing stray log level file %s: %s", wrong_filename, e) else: if os.path.exists(full_right_filename): env.log.warning("Error renaming %s to %s in fix_log_levels_misnaming: new filename already exists", full_wrong_filename, full_right_filename) rename_error_count += 1 continue try: os.rename(full_wrong_filename, full_right_filename) rename_count += 1 env.log.info("Renamed incorrectly named log level file %s to %s", wrong_filename, right_filename) except Exception, e: env.log.warning("Error renaming %s to %s in fix_log_levels_misnaming: %s", full_wrong_filename, full_right_filename, e) rename_error_count += 1 env.log.info("Renamed %d incorrectly named log level files from previous migrate (%d errors)", rename_count, rename_error_count) env.log.info("Deleted %d stray log level (%d errors)", delete_count, delete_error_count) def remove_stray_log_levels_files(env, db): """Remove \*.log.levels files without a matching \*.log file (old Bitten versions did not delete .log.levels files when builds were deleted)""" logs_dir = env.config.get("bitten", "logs_dir", "log/bitten") if not os.path.isabs(logs_dir): logs_dir = os.path.join(env.path, logs_dir) if not os.path.isdir(logs_dir): return delete_count = 0 delete_error_count = 0 for filename in os.listdir(logs_dir): if not filename.endswith('.log.levels'): continue log_filename = os.path.splitext(filename)[0] full_log_filename = os.path.join(logs_dir, log_filename) full_filename = os.path.join(logs_dir, filename) if not os.path.exists(full_log_filename): try: os.remove(full_filename) delete_count += 1 env.log.info("Deleted stray log levels file %s", filename) except Exception, e: delete_error_count += 1 env.log.warning("Error removing stray log levels file %s: %s", filename, e) env.log.info("Deleted %d stray log levels (%d errors)", delete_count, delete_error_count) def recreate_rule_with_int_id(env, db): """Recreates the bitten_rule table with an integer id column rather than a text one.""" cursor = db.cursor() rule_schema_v9 = Table('bitten_rule', key=('id', 'propname'))[ Column('id', type='int'), Column('propname'), Column('pattern'), Column('orderno', type='int') ] env.log.info("Migrating bitten_rule table to integer ids") connector, _ = DatabaseManager(env)._get_connector() cursor.execute("CREATE TEMPORARY TABLE old_rule_v9 AS SELECT * FROM bitten_rule") cursor.execute("DROP TABLE bitten_rule") for stmt in connector.to_sql(rule_schema_v9): cursor.execute(stmt) cursor.execute("INSERT INTO bitten_rule (id,propname,pattern,orderno) " "SELECT %s,propname,pattern,orderno FROM old_rule_v9" % db.cast('id', 'int')) def add_config_platform_rev_index_to_build(env, db): """Adds a unique index on (config, platform, rev) to the bitten_build table. Also drops the old index on bitten_build that serves no real purpose anymore.""" # check for existing duplicates duplicates_cursor = db.cursor() build_cursor = db.cursor() duplicates_cursor.execute("SELECT config, rev, platform FROM bitten_build GROUP BY config, rev, platform HAVING COUNT(config) > 1") duplicates_exist = False for config, rev, platform in duplicates_cursor.fetchall(): if not duplicates_exist: duplicates_exist = True print "\nConfig Name, Revision, Platform :: []" print "--------------------------------------------------------" build_cursor.execute("SELECT id FROM bitten_build WHERE config='%s' AND rev='%s' AND platform='%s'" % (config, rev, platform)) build_ids = [row[0] for row in build_cursor.fetchall()] print "%s, %s, %s :: %s" % (config, rev, platform, build_ids) if duplicates_exist: print "--------------------------------------------------------\n" print "Duplicate builds found. You can obtain help on removing the" print "builds you don't want by reading the Bitten upgrade" print "documentation at:" print "http://bitten.edgewall.org/wiki/Documentation/upgrade.html" print "Upgrades cannot be performed until conflicts are resolved." print "The upgrade script will now exit with an error:\n" duplicates_cursor.close() build_cursor.close() if not duplicates_exist: cursor = db.cursor() scheme = parse_scheme(env) if scheme == "mysql": # 111 = 333 / len(columns in index) -- this is the Trac default cursor.execute("CREATE UNIQUE INDEX bitten_build_config_rev_platform_idx ON bitten_build (config(111), rev(111), platform)") else: cursor.execute("CREATE UNIQUE INDEX bitten_build_config_rev_platform_idx ON bitten_build (config,rev,platform)") drop_index(env, db, 'bitten_build', 'bitten_build_config_rev_slave_idx') else: raise TracError('') def fix_sequences(env, db): """Fixes any auto increment sequences that might have been left in an inconsistent state. Upgrade scripts for schema versions > 10 should handle sequence updates correctly themselves. """ update_sequence(env, db, 'bitten_build', 'id') update_sequence(env, db, 'bitten_log', 'id') update_sequence(env, db, 'bitten_platform', 'id') update_sequence(env, db, 'bitten_report', 'id') 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], 8: [add_filename_to_logs,migrate_logs_to_files], 9: [recreate_rule_with_int_id], 10: [add_config_platform_rev_index_to_build, fix_sequences], 11: [fix_log_levels_misnaming, remove_stray_log_levels_files], 12: [add_last_activity_to_build], } Bitten-0.6/bitten/util/000755 000765 000120 00000000000 11536424600 015321 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/web_ui.py000644 000765 000120 00000106556 11500370724 016203 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.""" import posixpath import re import time from StringIO import StringIO from datetime import datetime import pkg_resources from genshi.builder import tag from trac.attachment import AttachmentModule, Attachment from trac.core import * from trac.config import Option from trac.mimeview.api import Context from trac.perm import PermissionError from trac.resource import Resource from trac.timeline import ITimelineEventProvider from trac.util import escape, pretty_timedelta, format_datetime, shorten_line, \ Markup, arity from trac.util.datefmt import to_timestamp, to_datetime, utc from trac.util.html import html from trac.web import IRequestHandler, IRequestFilter, HTTPNotFound from trac.web.chrome import INavigationContributor, ITemplateProvider, \ add_link, add_stylesheet, add_ctxtnav, \ prevnext_nav, add_script, add_warning from trac.versioncontrol import NoSuchChangeset, NoSuchNode from trac.wiki import wiki_to_html, wiki_to_oneliner from bitten.api import ILogFormatter, IReportChartGenerator, IReportSummarizer from bitten.master import BuildMaster from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ BuildLog, Report from bitten.queue import collect_changes from bitten.util import json _status_label = {Build.PENDING: 'pending', Build.IN_PROGRESS: 'in progress', Build.SUCCESS: 'completed', Build.FAILURE: 'failed'} _status_title = {Build.PENDING: 'Pending', Build.IN_PROGRESS: 'In Progress', Build.SUCCESS: 'Success', Build.FAILURE: 'Failure'} _step_status_label = {BuildStep.SUCCESS: 'success', BuildStep.FAILURE: 'failed', BuildStep.IN_PROGRESS: 'in progress'} def _get_build_data(env, req, build): platform = TargetPlatform.fetch(env, build.platform) data = {'id': build.id, 'name': build.slave, 'rev': build.rev, 'status': _status_label[build.status], 'platform': getattr(platform, 'name', 'unknown'), '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 def _has_permission(perm, repos, path, rev=None, raise_error=False): if hasattr(repos, 'authz'): if not repos.authz.has_permission(path): if not raise_error: return False repos.authz.assert_permission(path) else: node = repos.get_node(path, rev) if not node.can_view(perm): if not raise_error: return False raise PermissionError('BROWSER_VIEW', node.resource) return True class BittenChrome(Component): """Provides the Bitten templates and static resources.""" implements(INavigationContributor, ITemplateProvider) # INavigationContributor methods def get_active_navigation_item(self, req): pass 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: status = '' if BuildMaster(self.env).quick_status: repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository ' \ 'or alias named "(default)" to Trac.' for config in BuildConfig.select(self.env, include_inactive=False): prev_rev = None for platform, rev, build in collect_changes(repos, config): if rev != prev_rev: if prev_rev is not None: break prev_rev = rev if build: build_data = _get_build_data(self.env, req, build) if build_data['status'] == 'failed': status='bittenfailed' break if build_data['status'] == 'in progress': status='bitteninprogress' elif not status: if (build_data['status'] == 'completed'): status='bittencompleted' if not status: status='bittenpending' yield ('mainnav', 'build', tag.a('Build Status', href=req.href.build(), accesskey=5, class_=status)) # 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, IRequestFilter, INavigationContributor) # Configuration options chart_style = Option('bitten', 'chart_style', 'height: 220px; width: 220px;', doc= """Style attribute for charts. Mostly useful for setting the height and width.""") # 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.-]+))?/?$', 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 # IRequestHandler methods def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, data, content_type): if template: add_stylesheet(req, 'bitten/bitten.css') return template, data, content_type # 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 repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' configs = [] for config in BuildConfig.select(self.env, include_inactive=show_all): rev = config.max_rev or repos.youngest_rev try: if not _has_permission(req.perm, repos, config.path, rev=rev): continue except NoSuchNode: add_warning(req, "Configuration '%s' points to non-existing " "path '/%s' at revision '%s'. Configuration skipped." \ % (config.name, config.path, rev)) continue description = config.description if description: description = wiki_to_html(description, self.env, req) platforms_data = [] for platform in TargetPlatform.select(self.env, config=config.name): pd = { 'name': platform.name, 'id': platform.id, 'builds_pending': len(list(Build.select(self.env, config=config.name, status=Build.PENDING, platform=platform.id))), 'builds_inprogress': len(list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, platform=platform.id))) } platforms_data.append(pd) config_data = { 'name': config.name, 'label': config.label or config.name, 'active': config.active, 'path': config.path, 'description': description, 'builds_pending' : len(list(Build.select(self.env, config=config.name, status=Build.PENDING))), 'builds_inprogress' : len(list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS))), 'href': req.href.build(config.name), 'builds': [], 'platforms': platforms_data } configs.append(config_data) if not config.active: continue 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), 'display_rev': repos.normalize_rev(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'] = sorted(configs, key=lambda x:x['label'].lower()) data['page_mode'] = 'overview' in_progress_builds = Build.select(self.env, status=Build.IN_PROGRESS) pending_builds = Build.select(self.env, status=Build.PENDING) data['builds_pending'] = len(list(pending_builds)) data['builds_inprogress'] = len(list(in_progress_builds)) 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() repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' configs = [] for config in BuildConfig.select(self.env, include_inactive=False): rev = config.max_rev or repos.youngest_rev try: if not _has_permission(req.perm, repos, config.path, rev=rev): continue except NoSuchNode: add_warning(req, "Configuration '%s' points to non-existing " "path '/%s' at revision '%s'. Configuration skipped." \ % (config.name, config.path, rev)) continue 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_time) - int(x.rev_time)): 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': to_datetime(step.stopped or int(time.time()), utc) - \ to_datetime(step.started, utc), 'status': _step_status_label[step.status], 'cls': _step_status_label[step.status].replace(' ', '-'), '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'] = sorted(configs, key=lambda x:x['label'].lower()) return data def _render_config(self, req, config_name): db = self.env.get_db_cnx() config = BuildConfig.fetch(self.env, config_name, db=db) if not config: raise HTTPNotFound("Build configuration '%s' does not exist." \ % config_name) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' rev = config.max_rev or repos.youngest_rev try: _has_permission(req.perm, repos, config.path, rev=rev, raise_error=True) except NoSuchNode: raise TracError("Permission checking against repository path %s " "at revision %s failed." % (config.path, rev)) 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) pending_builds = list(Build.select(self.env, config=config.name, status=Build.PENDING)) inprogress_builds = list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS)) 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), 'builds_pending' : len(pending_builds), 'builds_inprogress' : len(inprogress_builds) } context = Context.from_request(req, config.resource) data['context'] = context data['config']['attachments'] = AttachmentModule(self.env).attachment_data(context) platforms = list(TargetPlatform.select(self.env, config=config_name, db=db)) data['config']['platforms'] = [ { 'name': platform.name, 'id': platform.id, 'builds_pending': len(list(Build.select(self.env, config=config.name, status=Build.PENDING, platform=platform.id))), 'builds_inprogress': len(list(Build.select(self.env, config=config.name, status=Build.IN_PROGRESS, platform=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 = [] report_categories = list(self._report_categories_for_config(config)) for generator in ReportChartController(self.env).generators: for category in generator.get_supported_categories(): if category in report_categories: chart_generators.append({ 'href': req.href.build(config.name, 'chart/' + category), 'category': category, 'style': self.config.get('bitten', 'chart_style'), }) data['config']['charts'] = chart_generators page = max(1, int(req.args.get('page', 1))) more = False data['page_number'] = page repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' builds_per_page = 12 * len(platforms) idx = 0 builds = {} revisions = [] 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: if rev not in builds: revisions.append(rev) builds.setdefault(rev, {}) builds[rev].setdefault('href', req.href.changeset(rev)) builds[rev].setdefault('display_rev', repos.normalize_rev(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': to_datetime(step.stopped or int(time.time()), utc) - \ to_datetime(step.started, utc), 'status': _step_status_label[step.status], 'cls': _step_status_label[step.status].replace(' ', '-'), 'errors': step.errors, 'href': build_data['href'] + '#step_' + step.name }) builds[rev][platform.id] = build_data idx += 1 data['config']['builds'] = builds data['config']['revisions'] = revisions 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') if arity(prevnext_nav) == 4: # Trac 0.12 compat, see #450 prevnext_nav(req, 'Previous Page', 'Next Page') else: prevnext_nav (req, 'Page') return data def _report_categories_for_config(self, config): """Yields the categories of reports that exist for active builds of this configuration. """ db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("""SELECT DISTINCT report.category as category FROM bitten_build AS build JOIN bitten_report AS report ON (report.build=build.id) WHERE build.config=%s AND build.rev_time >= %s AND build.rev_time <= %s""", (config.name, config.min_rev_time(self.env), config.max_rev_time(self.env))) for (category,) in cursor: yield category 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) if not build: raise HTTPNotFound("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') data = {'title': 'Build %s - %s' % (build_id, _status_title[build.status]), 'page_mode': 'view_build', 'build': {}} config = BuildConfig.fetch(self.env, build.config, db=db) data['build']['config'] = { 'name': config.label or config.name, 'href': req.href.build(config.name) } context = Context.from_request(req, build.resource) data['context'] = context data['build']['attachments'] = AttachmentModule(self.env).attachment_data(context) 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 or int(time.time())), 'status': _step_status_label[step.status], 'cls': _step_status_label[step.status].replace(' ', '-'), '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 \ and build.status != build.PENDING) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' _has_permission(req.perm, repos, config.path, rev=build.rev, raise_error=True) chgset = repos.get_changeset(build.rev) data['build']['chgset_author'] = chgset.author data['build']['display_rev'] = repos.normalize_rev(build.rev) add_script(req, 'common/js/folding.js') add_script(req, 'bitten/tabset.js') add_script(req, 'bitten/jquery.flot.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 # Attachments (will be rendered by attachment module) for event in AttachmentModule(self.env).get_timeline_events( req, Resource('build'), start, stop): yield event 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,c.path, 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)) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' event_kinds = {Build.SUCCESS: 'successbuild', Build.FAILURE: 'failedbuild'} for id_, config, label, path, rev, platform, stopped, status in cursor: if not _has_permission(req.perm, repos, path, rev=rev): continue 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] display_rev = repos.normalize_rev(rev) yield (event_kinds[status], to_datetime(stopped, utc), None, (id_, config, label, display_rev, platform, status, errors)) def render_timeline_event(self, context, field, event): id_, config, label, rev, platform, status, errors = event[3] if field == 'url': return context.href.build(config, id_) elif field == 'title': return tag('Build of ', tag.em('%s [%s]' % (label, rev)), ' on %s %s' % (platform, _status_label[status])) elif field == 'description': message = '' if context.req.args.get('format') == 'rss': if errors: buf = StringIO() prev_step = None for step, error in errors: if step != prev_step: if prev_step is not None: buf.write('') buf.write('

Step %s failed:

    ' \ % escape(step)) prev_step = step buf.write('
  • %s
  • ' % escape(error)) buf.write('
') message = Markup(buf.getvalue()) else: if errors: steps = [] for step, error in errors: if step not in steps: steps.append(step) steps = [Markup('%s') % step for step in steps] if len(steps) < 2: message = steps[0] elif len(steps) == 2: message = Markup(' and ').join(steps) elif len(steps) > 2: message = Markup(', ').join(steps[:-1]) + ', and ' + \ steps[-1] message = Markup('Step%s %s failed') % ( len(steps) != 1 and 's' or '', message) return 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 = 0 build.stopped = 0 build.last_activity = 0 build.status = Build.PENDING build.slave_info = {} build.update() Attachment.delete_all(self.env, 'build', build.resource.id, db) 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) reports.append({'category': report.category, 'template': tmpl, 'data': data}) else: tmpl = data = None 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) data['dumps'] = json.to_json return tmpl, data, 'text/plain' 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(r'(?P-[A-Za-z])?(?P[\w.-]+(?:[\\/][\w.-]+)+)(?P:\d+)?') def get_formatter(self, req, build): """Return the log message formatter function.""" config = BuildConfig.fetch(self.env, name=build.config) repos = self.env.get_repository(authname=req.authname) assert repos, 'No "(default)" Repository: Add a repository or alias ' \ 'named "(default)" to Trac.' 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: full_path = posixpath.join(config.path, path) full_path = posixpath.normpath(full_path) if full_path.startswith(config.path + "/") \ or full_path == config.path: repos.get_node(full_path, build.rev) cache[path] = True else: cache[path] = False 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 Bitten-0.6/bitten/util/__init__.py000644 000765 000120 00000001115 11453043212 017422 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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' Bitten-0.6/bitten/util/compat.py000644 000765 000120 00000003244 11535711762 017170 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2011 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. """Compatibility fixes for external libraries and Python.""" import sys # Fix for issue http://bugs.python.org/issue8797 in Python 2.6 if sys.version_info[:2] == (2, 6): import urllib2 import base64 class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler): """Patched version of Python 2.6's HTTPBasicAuthHandler. The fix for [1]_ introduced an infinite recursion bug [2]_ into Python 2.6.x that is triggered by attempting to connect using Basic authentication with a bad username and/or password. This class fixes the problem using the simple solution outlined in [3]_. .. [1] http://bugs.python.org/issue3819 .. [2] http://bugs.python.org/issue8797 .. [3] http://bugs.python.org/issue8797#msg126657 """ def retry_http_basic_auth(self, host, req, realm): user, pw = self.passwd.find_user_password(realm, host) if pw is not None: raw = "%s:%s" % (user, pw) auth = 'Basic %s' % base64.b64encode(raw).strip() if req.get_header(self.auth_header, None) == auth: return None req.add_unredirected_header(self.auth_header, auth) return self.parent.open(req, timeout=req.timeout) else: return None else: from urllib2 import HTTPBasicAuthHandler Bitten-0.6/bitten/util/json.py000644 000765 000120 00000006473 11366024332 016655 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C)2006-2009 Edgewall Software # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. """Utility functions for converting to web formats""" # to_json is really a wrapper for trac.util.presentation.to_json, but we have lots of fallbacks for compatibility # trac.util.presentation.to_json is present from Trac 0.12 # If that's not present, we fall back to the default Python json module, present from Python 2.6 onwards # If that's not present, we have a copy of the to_json method, which we will remove once Trac 0.11 support is removed # And finally, the to_json method requires trac.util.text.javascript_quote, which is only present from Trac 0.11.3, so we have a copy of that too import re try: # to_json is present from Trac 0.12 onwards - should remove once Trac 0.11 support is removed from trac.util.presentation import to_json except ImportError: try: # If we have Python 2.6 onwards, use the json method directly from json import dumps def to_json(value): """Encode `value` to JSON.""" return dumps(value, sort_keys=True, separators=(',', ':')) except ImportError: # javascript_quote is present from Trac 0.11.3 onwards - should remove once Trac 0.11.2 support is removed try: from trac.util.text import javascript_quote except ImportError: _js_quote = {'\\': '\\\\', '"': '\\"', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', "'": "\\'"} for i in range(0x20): _js_quote.setdefault(chr(i), '\\u%04x' % i) _js_quote_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t\']') def javascript_quote(text): """Quote strings for inclusion in javascript""" if not text: return '' def replace(match): return _js_quote[match.group(0)] return _js_quote_re.sub(replace, text) def to_json(value): """Encode `value` to JSON.""" if isinstance(value, basestring): return '"%s"' % javascript_quote(value) elif value is None: return 'null' elif value is False: return 'false' elif value is True: return 'true' elif isinstance(value, (int, long)): return str(value) elif isinstance(value, float): return repr(value) elif isinstance(value, (list, tuple)): return '[%s]' % ','.join(to_json(each) for each in value) elif isinstance(value, dict): return '{%s}' % ','.join('%s:%s' % (to_json(k), to_json(v)) for k, v in sorted(value.iteritems())) else: raise TypeError('Cannot encode type %s' % value.__class__.__name__) Bitten-0.6/bitten/util/loc.py000644 000765 000120 00000012252 11453043212 016444 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 1998 Dinu C. Gherman # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 = '' Bitten-0.6/bitten/util/testrunner.py000644 000765 000120 00000023572 11453043212 020107 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2008 Matt Good # Copyright (C) 2008-2010 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() Bitten-0.6/bitten/util/tests/000755 000765 000120 00000000000 11536424600 016463 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/util/xmlio.py000644 000765 000120 00000026200 11453043212 017015 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 __all__ = ['Fragment', 'Element', 'ParsedElement', 'parse'] __docformat__ = 'restructuredtext en' def _from_utf8(text): """Convert utf-8 string to unicode. All other input returned as-is.""" if isinstance(text, str): return text.decode('utf-8') else: return text def _to_utf8(text): """Convert any input to utf-8 byte string.""" if isinstance(text, str): return text # presumes utf-8 elif not isinstance(text, unicode): text = unicode(text) return text.encode('utf-8') __trans = string.maketrans('', '') # http://www.w3.org/TR/xml11/#charsets (partial) __todel = ('\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f\x10\x12\x13\x14' '\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f\x80\x81\x82\x83' '\x84\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94' '\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f') __uni_trans = dict([(ord(c), None) for c in __todel]) def _escape_text(text): """Escape special characters in the provided text so that it can be safely included in XML text nodes. """ if isinstance(text, str): text = cgi.escape(text.translate(__trans, __todel)) elif isinstance(text, unicode): text = cgi.escape(text.translate(__uni_trans)) return text def _escape_attr(attr): """Escape special characters in the provided text so that it can be safely included in XML attribute values. """ if isinstance(attr, basestring): return _escape_text(attr).replace('"', '"') else: return attr 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 != '': if isinstance(node, basestring): self.children.append(_from_utf8(node)) else: self.children.append(unicode(node)) def write(self, out, newlines=False): """Serializes the element and writes the XML to the given output stream. """ for child in self.children: if isinstance(child, (Element, ParsedElement)): child.write(out, newlines=newlines) else: if child.startswith('<'): out.write('') else: out.write(_to_utf8(_escape_text(child))) class Element(Fragment): """Simple XML output generator based on the builder pattern. Construct XML elements by passing the tag name to the constructor: >>> print Element('foo') Attributes can be specified using keyword arguments. The values of the arguments will be converted to strings and any special XML characters escaped: >>> print Element('foo', bar=42) >>> print Element('foo', bar='1 < 2') >>> print Element('foo', bar='"baz"') The order in which attributes are rendered is undefined. Elements can be using item access notation: >>> print Element('foo')[Element('bar'), Element('baz')] Text nodes can be nested in an element by using strings instead of elements in item access. Any special characters in the strings are escaped automatically: >>> print Element('foo')['Hello world'] Hello world >>> print Element('foo')[42] 42 >>> print Element('foo')['1 < 2'] 1 < 2 This technique also allows mixed content: >>> print Element('foo')['Hello ', Element('b')['world']] Hello world Finally, text starting with an opening angle bracket is treated specially: under the assumption that the text actually contains XML itself, the whole thing is wrapped in a CDATA block instead of escaping all special characters individually: >>> print Element('foo')[''] ]]> Valid input are utf-8 or unicode strings, or any type easily converted to unicode such as integers. Output is always utf-8. """ __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 = _from_utf8(name_) self.attr = dict([(_from_utf8(name), _from_utf8(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(_to_utf8(self.name)) for name, value in self.attr.items(): out.write(_to_utf8(' %s="%s"' % (name, _escape_attr(value)))) if self.children: out.write('>') Fragment.write(self, out, newlines) out.write('') else: out.write('/>') if newlines: out.write(os.linesep) class ParseError(Exception): """Exception thrown when there's an error parsing an XML document.""" def parse(text_or_file): """Parse an XML document provided as string or file-like object. Returns an instance of `ParsedElement` that can be used to traverse the parsed document. """ from xml.dom import minidom from xml.parsers import expat try: if isinstance(text_or_file, basestring): dom = minidom.parseString(_to_utf8(text_or_file)) else: dom = minidom.parse(text_or_file) return ParsedElement(dom.documentElement) except expat.error, e: raise ParseError(e) class ParsedElement(object): """Representation of an XML element that was parsed from a string or file. This class should not be used directly. Rather, XML text parsed using `xmlio.parse()` will return an instance of this class. >>> xml = parse('') >>> print xml.name root Parsed elements can be serialized to a string using the `write()` method: >>> import sys >>> parse('').write(sys.stdout) For convenience, this is also done when coercing the object to a string using the builtin ``str()`` function, which is used when printing an object: >>> print parse('') (Note that serializing the element will produce a normalized representation that may not excatly match the input string.) Attributes are accessed via the `attr` member: >>> print parse('').attr['foo'] bar Attributes can also be updated, added or removed: >>> xml = parse('') >>> xml.attr['foo'] = 'baz' >>> print xml >>> del xml.attr['foo'] >>> print xml >>> xml.attr['foo'] = 'bar' >>> print xml CDATA sections are included in the text content of the element returned by `gettext()`: >>> xml = parse('foo ]]>baz') >>> xml.gettext() 'foo baz' Valid input are utf-8 or unicode strings, or any type easily converted to unicode such as integers. Output is always utf-8. """ __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 _to_utf8(attr.value) def __setitem__(self, name, value): self._node.setAttribute(name, value) def __delitem__(self, name): self._node.removeAttribute(name) def keys(self): return [_to_utf8(key) 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([_to_utf8(c.nodeValue) 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. """ out.write(self._node.toprettyxml(newl=newlines and '\n' or '', indent=newlines and '\t' or '', encoding='utf-8')) 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() Bitten-0.6/bitten/util/tests/__init__.py000644 000765 000120 00000001433 11453043212 020567 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 as xmlio_module from bitten.util.tests import xmlio as xmlio_tests from bitten.util.tests import json as json_tests def suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(xmlio_module)) suite.addTest(json_tests.suite()) suite.addTest(xmlio_tests.suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/util/tests/json.py000644 000765 000120 00000003112 11365777015 020016 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C)2006-2009 Edgewall Software # Copyright (C) 2006 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. import doctest import unittest from bitten.util import json class ToJsonTestCase(unittest.TestCase): def test_simple_types(self): self.assertEqual('42', json.to_json(42)) self.assertEqual('123.456', json.to_json(123.456)) self.assertEqual('true', json.to_json(True)) self.assertEqual('false', json.to_json(False)) self.assertEqual('null', json.to_json(None)) self.assertEqual('"String"', json.to_json('String')) self.assertEqual(r'"a \" quote"', json.to_json('a " quote')) def test_compound_types(self): self.assertEqual('[1,2,[true,false]]', json.to_json([1, 2, [True, False]])) self.assertEqual('{"one":1,"other":[null,0],"two":2}', json.to_json({"one": 1, "two": 2, "other": [None, 0]})) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ToJsonTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/util/tests/xmlio.py000644 000765 000120 00000005005 11453043212 020157 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.util import xmlio class XMLIOTestCase(unittest.TestCase): def test_parse(self): """Tests that simple test data is parsed correctly""" s = """ \ """ x = xmlio.parse(s) assert x.name == "build" def test_Element_encoding(self): self.assertEquals('<\xc3\xb8\xc3\xbc arg="\xc3\xa9\xe2\x82\xac"/>', str(xmlio.Element(u'\xf8\xfc', arg=u'\xe9\u20ac'.encode('utf-8')))) def test_ParsedElement_encoding(self): u = u'' s = '' self.assertEquals(u, s.decode('utf-8')) # unicode input x = xmlio.parse(u) out_u = str(x) self.assertEquals(out_u, s) self.assertEquals(out_u.decode('utf-8'), u) # utf-8 input x = xmlio.parse(s) out_s = str(x) self.assertEquals(out_s, s) self.assertEquals(out_s.decode('utf-8'), u) # identical results self.assertEquals(out_u, out_s) def test_escape_text(self): # unicode self.assertEquals(u"Me & you!", xmlio._escape_text(u"Me\x01 & you\x86!")) # str self.assertEquals("Me & you!", xmlio._escape_text("Me\x01 & you\x86!")) # not basestring self.assertEquals(42, xmlio._escape_text(42)) def test_escape_attr(self): # unicode self.assertEquals(u'"Me & you!"', xmlio._escape_attr(u'"Me\x01 & you\x86!"')) # str self.assertEquals('"Me & you!"', xmlio._escape_attr('"Me\x01 & you\x86!"')) # not basestring self.assertEquals(42, xmlio._escape_text(42)) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(XMLIOTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests_slave/__init__.py000644 000765 000120 00000001365 11453043212 021010 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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_slave import recipe, slave from bitten.build import tests as build from bitten.util import tests as util def suite(): suite = unittest.TestSuite() suite.addTest(recipe.suite()) suite.addTest(slave.suite()) suite.addTest(build.suite()) suite.addTest(util.suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests_slave/recipe.py000644 000765 000120 00000016247 11454420025 020527 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.config import Configuration from bitten.recipe import Context, Recipe, InvalidRecipeError from bitten.util import xmlio class ContextTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) def tearDown(self): shutil.rmtree(self.basedir) def test_vars_basedir(self): config = Configuration(properties={'foo.bar': 'baz'}) ctxt = Context('%s/${path}/${foo.bar}' % os.path.realpath('/foo'), config, {'path': 'bar'}) self.assertEquals(os.path.realpath('/foo/bar/baz'), os.path.realpath(ctxt.vars['basedir'])) if os.name == 'nt': # Make sure paths are double-escaped self.failUnless('\\\\' in ctxt.vars['basedir']) def test_run_wrong_arg(self): ctxt = Context(self.basedir) try: ctxt.run(1, 'http://bitten.edgewall.org/tools/sh', 'exec', {'foo':'bar'}) self.fail("InvalidRecipeError expected") except InvalidRecipeError, e: self.failUnless("Unsupported argument 'foo'" in str(e)) def test_attach_file_config(self): # Verify output from attaching a file to a config ctxt = Context(self.basedir, Configuration()) ctxt.attach(file_='config.txt', description='config config', resource='config') self.assertEquals(1, len(ctxt.output)) self.assertEquals(Recipe.ATTACH, ctxt.output[0][0]) attach_xml = ctxt.output[0][3] self.assertEquals('', str(attach_xml)) def test_attach_file_build(self): # Verify output from attaching a file to a build ctxt = Context(self.basedir, Configuration()) ctxt.attach(file_='build.txt', description='build build') self.assertEquals(1, len(ctxt.output)) self.assertEquals(Recipe.ATTACH, ctxt.output[0][0]) attach_xml = ctxt.output[0][3] self.assertEquals('', str(attach_xml)) class RecipeTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) def tearDown(self): shutil.rmtree(self.basedir) def test_empty_recipe(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertEqual(self.basedir, recipe.ctxt.basedir) steps = list(recipe) self.assertEqual(0, len(steps)) def test_empty_step(self): xml = xmlio.parse('' ' ' '') recipe = Recipe(xml, basedir=self.basedir) steps = list(recipe) self.assertEqual(1, len(steps)) self.assertEqual('foo', steps[0].id) self.assertEqual('Bar', steps[0].description) self.assertEqual('fail', steps[0].onerror) def test_validate_bad_root(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_no_steps(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_child_not_step(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_child_not_step(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_step_without_id(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_step_with_empty_id(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_step_without_commands(self): xml = xmlio.parse('') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_step_with_command_children(self): xml = xmlio.parse('' '' '') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_step_with_duplicate_id(self): xml = xmlio.parse('' '' '' '') recipe = Recipe(xml, basedir=self.basedir) self.assertRaises(InvalidRecipeError, recipe.validate) def test_validate_successful(self): xml = xmlio.parse('' '' '' '') recipe = Recipe(xml, basedir=self.basedir) recipe.validate() def test_onerror_defaults(self): xml = xmlio.parse('' ' ' '') recipe = Recipe(xml, basedir=self.basedir) steps = list(recipe) self.assertEqual(1, len(steps)) self.assertEqual('foo', steps[0].id) self.assertEqual('Bar', steps[0].description) self.assertEqual('continue', steps[0].onerror) def test_onerror_override(self): xml = xmlio.parse('' ' ' '') recipe = Recipe(xml, basedir=self.basedir) steps = list(recipe) self.assertEqual(1, len(steps)) self.assertEqual('foo', steps[0].id) self.assertEqual('Bar', steps[0].description) self.assertEqual('continue', steps[0].onerror) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ContextTestCase, 'test')) suite.addTest(unittest.makeSuite(RecipeTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests_slave/slave.py000644 000765 000120 00000011177 11454431226 020374 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 sys import shutil import tempfile import unittest from bitten.slave import BuildSlave, ExitSlave from bitten.util import xmlio from bitten.slave import encode_multipart_formdata class DummyResponse(object): def __init__(self, code): self.code = code class TestSlave(BuildSlave): def __init__(self, filename, work_dir): BuildSlave.__init__(self, [filename], work_dir=work_dir) self.results = [] def _gather(self, method, url, body=None, headers=None): self.results.append(xmlio.parse(body)) return DummyResponse(201) def _execute_step(self, _build_url, recipe, step): old_local, old_request = self.local, self.request try: self.local, self.request = False, self._gather return BuildSlave._execute_step(self, 'dummy_build', recipe, step) finally: self.local, self.request = old_local, old_request class BuildSlaveTestCase(unittest.TestCase): def setUp(self): self.work_dir = tempfile.mkdtemp(prefix='bitten_test') self.python_path = xmlio._escape_attr(sys.executable) 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 _run_slave(self, recipe): results = [] filename = self._create_file("recipe.xml") recipe_file = file(filename, "wb") recipe_file.write(recipe) recipe_file.close() slave = TestSlave(filename, self.work_dir) slave.run() return slave.results def test_quit_raises(self): self.slave = BuildSlave([], work_dir=self.work_dir) self.assertRaises(ExitSlave, self.slave.quit) def test_simple_recipe(self): results = self._run_slave(""" """ % self.python_path) result = results[0] self.assertEqual(result.attr["step"], "print") self.assertEqual(result.attr["status"], "success") log = list(result)[0] msg = list(log)[0] self.assertEqual(str(msg), 'Hello') def test_non_utf8(self): results = self._run_slave(""" """ % self.python_path) result = results[0] self.assertEqual(result.attr["step"], "print") self.assertEqual(result.attr["status"], "success") log = list(result)[0] msg = list(log)[0] # check replacement character (\uFFFD) was generated correctly self.assertEqual(str(msg).decode("utf-8"), u'\uFFFD') class MultiPartEncodeTestCase(unittest.TestCase): def setUp(self): self.work_dir = tempfile.mkdtemp(prefix='bitten_test') def tearDown(self): shutil.rmtree(self.work_dir) def test_mutlipart_encode_one(self): fields = { 'foo': 'bar', 'foofile': ('test.txt', 'contents of foofile'), } body, content_type = encode_multipart_formdata(fields) boundary = content_type.split(';')[1].strip().split('=')[1] self.assertEquals('multipart/form-data; boundary=%s' % boundary, content_type) self.assertEquals('--%s\r\nContent-Disposition: form-data; ' \ 'name="foo"\r\n\r\nbar\r\n--%s\r\nContent-Disposition: ' \ 'form-data; name="foofile"; filename="test.txt"\r\n' \ 'Content-Type: application/octet-stream\r\n\r\n' \ 'contents of foofile\r\n--%s--\r\n' % ( boundary,boundary,boundary), body) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(BuildSlaveTestCase, 'test')) suite.addTest(unittest.makeSuite(MultiPartEncodeTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests/__init__.py000644 000765 000120 00000002277 11453043212 017621 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 def master_suite(): from bitten.tests import admin, master, model, queue, web_ui, notify, \ upgrades from bitten.report import tests as report suite = unittest.TestSuite() suite.addTest(admin.suite()) suite.addTest(master.suite()) suite.addTest(model.suite()) suite.addTest(queue.suite()) suite.addTest(web_ui.suite()) suite.addTest(report.suite()) suite.addTest(notify.suite()) suite.addTest(upgrades.suite()) return suite def suite(): suite = unittest.TestSuite() try: import trac suite.addTest(master_suite()) except ImportError: print "\nTrac not installed -- Skipping master tests\n" import bitten.tests_slave suite.addTest(bitten.tests_slave.suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests/admin.py000644 000765 000120 00000100570 11453043212 017145 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 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 self.assertEqual('log/bitten', master.logs_dir) 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, 'recipe': 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, 'recipe': False }, 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'), chrome={'warnings': []}, href=Href('/'), authname='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('Add Configuration', e.title) def test_process_add_config_invalid_name(self): req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), chrome={'warnings': []}, href=Href('/'), authname='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('Add Configuration', 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.get_node = get_node provider = BuildConfigurationsAdminPageProvider(self.env) try: provider.render_admin_panel(req, 'bitten', 'configs', '') self.fail('Expected TracError') except TracError, e: self.failUnless('Invalid Repository Path' in e.message) 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', chrome={'warnings': [], 'notices': []}, href=Href('/'), 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(['Configuration Saved.'], req.chrome['notices']) self.assertEqual('http://example.org/admin/bitten/configs/foo', 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': '', 'name': ''}, authname='joe', chrome={'warnings':[]}, href=Href('/')) provider = BuildConfigurationsAdminPageProvider(self.env) provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.failUnless(req.chrome['warnings'], "No warnings?") self.assertEquals(['Missing required field "name".'], req.chrome['warnings']) 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'}, authname='joe', chrome={'warnings': []}, href=Href('/')) provider = BuildConfigurationsAdminPageProvider(self.env) provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.failUnless(req.chrome['warnings'], "No warnings?") self.assertEquals(req.chrome['warnings'], ['The field "name" may ' \ 'only contain letters, digits, periods, or dashes.']) 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', chrome={'warnings': []}, href=Href('/'), args={'save': '', 'name': 'foo', 'path': 'invalid/path'}) def get_node(path, rev=None): raise TracError('No such node') self.repos.get_node = get_node provider = BuildConfigurationsAdminPageProvider(self.env) provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.failUnless(req.chrome['warnings'], "No warnings?") self.assertEquals(req.chrome['warnings'], ['Invalid Repository Path: "invalid/path" does not exist ' 'within the "(default)" repository.']) 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', chrome={'warnings': []}, href=Href('/'), args={'save': '', 'name': 'foo', 'recipe': 'not_xml'}) provider = BuildConfigurationsAdminPageProvider(self.env) provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.failUnless(req.chrome['warnings'], "No warnings?") self.assertEquals(['Failure parsing recipe: syntax error: line 1, ' \ 'column 0.'], req.chrome['warnings']) 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', chrome={'warnings': []}, href=Href('/'), args={'save': '', 'name': 'foo', 'recipe': ''}) provider = BuildConfigurationsAdminPageProvider(self.env) provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.failUnless(req.chrome['warnings'], "No warnings?") self.assertEquals(req.chrome['warnings'], ['Invalid Recipe: Steps must have an "id" attribute.']) def test_process_new_platform_no_name(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': '', 'platform_name': ''}) provider = BuildConfigurationsAdminPageProvider(self.env) try: provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail("No TracError?") except Exception, e: self.assertEquals(e.message, 'Missing required field "name"') self.assertEquals(e.title, 'Missing field') def test_process_new_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': '', 'platform_name': 'Test'}) 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/1', redirected_to[0]) platforms = list(TargetPlatform.select(self.env, config='foo')) self.assertEqual(1, len(platforms)) self.assertEqual('Test', platforms[0].name) self.assertEqual([], platforms[0].rules) 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', chrome={'warnings': [], 'notices': []}, href=Href('/'), 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.assertEquals(['Target Platform(s) Removed.'], req.chrome['notices']) 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') Bitten-0.6/bitten/tests/master.py000644 000765 000120 00000123772 11454420025 017363 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 re import shutil from StringIO import StringIO import tempfile import unittest import cgi from Cookie import SimpleCookie as Cookie from trac.attachment import Attachment from trac.db import DatabaseManager from trac.perm import PermissionCache, PermissionSystem from trac.test import EnvironmentStub, Mock from trac.util.datefmt import to_datetime, utc from trac.web.api import RequestDone from trac.web.href import Href from bitten.master import BuildMaster from bitten.slave import encode_multipart_formdata from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ BuildLog, Report, schema from bitten import PROTOCOL_VERSION class BuildMasterTestCase(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) self.env.path = tempfile.mkdtemp() logs_dir = self.env.config.get("bitten", "logs_dir") if os.path.isabs(logs_dir): raise ValueError("Should not have absolute logs directory for temporary test") logs_dir = os.path.join(self.env.path, logs_dir) os.makedirs(logs_dir) 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(get_changeset=lambda rev: Mock(author = 'author')) 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=to_datetime(42, utc)), normalize_path=lambda path: path, rev_older_than=lambda rev1, rev2: rev1 < rev2 ) inheaders = {'Content-Type': 'application/x-bitten+xml'} inbody = StringIO(""" Power Macintosh Darwin """ % PROTOCOL_VERSION) 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, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) 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('') 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, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEquals(400, outheaders['Status']) self.assertEqual('XML parser error', outbody.getvalue()) def test_create_build_no_post(self): outheaders = {} outbody = StringIO() req = Mock(method='GET', base_path='', path_info='/builds', 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, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEquals(200, outheaders['Status']) self.assertEquals('Only POST allowed for build creation.', outbody.getvalue()) def test_create_build_no_match(self): inheaders = {'Content-Type': 'application/x-bitten+xml'} inbody = StringIO(""" Power Macintosh Darwin """ % PROTOCOL_VERSION) 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, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(204, outheaders['Status']) self.assertEqual('', outbody.getvalue()) def test_create_build_protocol_wrong_version(self): inheaders = {'Content-Type': 'application/x-bitten+xml'} inbody = StringIO(""" Power Macintosh Darwin """ % (PROTOCOL_VERSION-2,)) 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, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(400, outheaders['Status']) self.assertEqual('Master-Slave version mismatch: master=%d, slave=%d' \ % (PROTOCOL_VERSION, PROTOCOL_VERSION-2), outbody.getvalue()) def test_create_build_protocol_no_version(self): inheaders = {'Content-Type': 'application/x-bitten+xml'} inbody = StringIO(""" Power Macintosh Darwin """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds', href=Href('/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), get_header=lambda x: inheaders.get(x), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(400, outheaders['Status']) self.assertEqual('Master-Slave version mismatch: master=%d, slave=1' \ % (PROTOCOL_VERSION,), outbody.getvalue()) def test_cancel_build(self): config = BuildConfig(self.env, 'test', path='somepath', active=True, recipe='') config.insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, status=Build.IN_PROGRESS, started=42) build.insert() outheaders = {} outbody = StringIO() req = Mock(method='DELETE', base_path='', path_info='/builds/%d' % build.id, href=Href('/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(204, outheaders['Status']) self.assertEqual('', outbody.getvalue()) # Make sure the started timestamp has been set build = Build.fetch(self.env, build.id) self.assertEqual(Build.PENDING, build.status) assert not build.started def test_initiate_build(self): config = BuildConfig(self.env, 'test', path='somepath', active=True, recipe='') config.insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() build = Build(self.env, 'test', '123', platform.id, slave='hal', rev_time=42) build.insert() outheaders = {} outbody = StringIO() req = Mock(method='GET', base_path='', path_info='/builds/%d' % build.id, href=Href('/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, form_token="12345", incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(200, outheaders['Status']) self.assertEqual('131', outheaders['Content-Length']) self.assertEqual('application/x-bitten+xml', outheaders['Content-Type']) self.assertEqual('attachment; filename=recipe_test_r123.xml', outheaders['Content-Disposition']) self.assertEqual('', outbody.getvalue()) # Make sure the started timestamp has been set build = Build.fetch(self.env, build.id) assert build.started def test_initiate_build_no_such_build(self): outheaders = {} outbody = StringIO() 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'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEquals(404, outheaders['Status']) self.assertEquals('No such build (123)', outbody.getvalue()) def test_process_unknown_collection(self): BuildConfig(self.env, 'test', path='somepath', active=True, recipe='').insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42) build.insert() outheaders = {} outbody = StringIO() 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'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(404, outheaders['Status']) self.assertEqual("No such collection 'files'", outbody.getvalue()) def test_process_build_step_success(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(201, outheaders['Status']) self.assertEqual('20', outheaders['Content-Length']) self.assertEqual('text/plain', outheaders['Content-Type']) self.assertEqual('Build step processed', outbody.getvalue()) build = Build.fetch(self.env, build.id) self.assertEqual(Build.SUCCESS, build.status) assert build.stopped assert build.stopped > build.started steps = list(BuildStep.select(self.env, build.id)) self.assertEqual(1, len(steps)) self.assertEqual('foo', steps[0].name) self.assertEqual(BuildStep.SUCCESS, steps[0].status) def test_process_build_step_success_with_log(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" Doing stuff Ouch that hurt """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) 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.edgewall.org/tools/python#unittest', logs[0].generator) self.assertEqual(2, len(logs[0].messages)) self.assertEqual((u'info', u'Doing stuff'), logs[0].messages[0]) self.assertEqual((u'error', u'Ouch that hurt'), logs[0].messages[1]) def test_process_build_step_success_with_report(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" Doing my thing """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) 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.edgewall.org/tools/python#unittest', reports[0].generator) self.assertEqual(1, len(reports[0].items)) self.assertEqual({ 'fixture': 'my.Fixture', 'file': 'my/test/file.py', 'stdout': 'Doing my thing', 'type': 'test', }, reports[0].items[0]) def test_process_build_step_wrong_slave(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" Doing stuff Ouch that hurt """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(409, outheaders['Status']) self.assertEqual('Token mismatch (wrong slave): slave=, build=123', outbody.getvalue()) 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)) def test_process_build_step_invalidated_build(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" Doing stuff Ouch that hurt """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) 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(2, len(steps)) # invalidate the build. build = Build.fetch(self.env, build.id) build.slave = None build.status = Build.PENDING build.slave_info = {} for step in list(BuildStep.select(self.env, build=build.id)): step.delete() build.update() # have this slave submit more data. inbody = StringIO(""" This is a step after invalidation """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEquals(409, outheaders['Status']) self.assertEquals('Token mismatch (wrong slave): slave=123, build=', outbody.getvalue()) build = Build.fetch(self.env, build.id) self.assertEqual(Build.PENDING, build.status) def test_process_build_step_failure(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(201, outheaders['Status']) self.assertEqual('20', outheaders['Content-Length']) self.assertEqual('text/plain', outheaders['Content-Type']) self.assertEqual('Build step processed', outbody.getvalue()) build = Build.fetch(self.env, build.id) self.assertEqual(Build.FAILURE, build.status) assert build.stopped assert build.stopped > build.started steps = list(BuildStep.select(self.env, build.id)) self.assertEqual(1, len(steps)) self.assertEqual('foo', steps[0].name) self.assertEqual(BuildStep.FAILURE, steps[0].status) def test_process_build_step_failure_ignored(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) 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_failure_continue(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.slave_info[Build.TOKEN] = '123'; build.insert() inbody = StringIO(""" """) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=123')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) 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_invalid_xml(self): recipe = """ """ BuildConfig(self.env, 'test', path='somepath', active=True, recipe=recipe).insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42) build.insert() inbody = StringIO("""""") outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds/%d/steps/' % build.id, href=Href('/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEquals(400, outheaders['Status']) self.assertEquals('XML parser error', outbody.getvalue()) def test_process_build_step_no_post(self): BuildConfig(self.env, 'test', path='somepath', active=True, recipe='').insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42) build.insert() outheaders = {} outbody = StringIO() 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'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) module._start_new_step(build, 'foo').insert() assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(405, outheaders['Status']) self.assertEqual('Method GET not allowed', outbody.getvalue()) def test_process_attach_collection_default_member(self): req = Mock(args={}, path_info='/builds/12/attach/config') module = BuildMaster(self.env) self.assertEquals(True, module.match_request(req)) self.assertTrue(req.args['collection'], 'attach') self.assertTrue(req.args['member'], '') def test_process_attach_collection_config(self): req = Mock(args={}, path_info='/builds/12/attach/config') module = BuildMaster(self.env) self.assertEquals(True, module.match_request(req)) self.assertTrue(req.args['collection'], 'attach') self.assertTrue(req.args['member'], 'config') def test_process_attach_collection_config(self): req = Mock(args={}, path_info='/builds/12/attach/build') module = BuildMaster(self.env) self.assertEquals(True, module.match_request(req)) self.assertTrue(req.args['collection'], 'attach') self.assertTrue(req.args['member'], 'build') def test_process_attach_config(self): body, content_type = encode_multipart_formdata({ 'description': 'baz baz', 'file': ('baz.txt', 'hello baz'), '__FORM_TOKEN': '123456'}) args = {} for k, v in dict(cgi.FieldStorage(fp=StringIO(body), environ={ 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}) ).items(): if v.filename: args[k] = v else: args[k] = v.value args.update({'collection': 'attach', 'member': 'config'}) self.assertTrue('file' in args) outheaders = {} outbody = StringIO() req = Mock(args=args, form_token='123456', authname='hal', remote_addr='127.0.0.1', send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write) config = BuildConfig(self.env, 'test', path='somepath', active=True, recipe='') config.insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.insert() module = BuildMaster(self.env) self.assertRaises(RequestDone, module._process_attachment, req, config, build) self.assertEqual(201, outheaders['Status']) self.assertEqual('18', outheaders['Content-Length']) self.assertEqual('text/plain', outheaders['Content-Type']) self.assertEqual('Attachment created', outbody.getvalue()) config_atts = list(Attachment.select(self.env, 'build', 'test')) self.assertEquals(1, len(config_atts)) self.assertEquals('hal', config_atts[0].author) self.assertEquals('baz baz', config_atts[0].description) self.assertEquals('baz.txt', config_atts[0].filename) self.assertEquals('hello baz', config_atts[0].open().read()) def test_process_attach_build(self): body, content_type = encode_multipart_formdata({ 'description': 'baz baz', 'file': ('baz.txt', 'hello baz'), '__FORM_TOKEN': '123456'}) args = {} for k, v in dict(cgi.FieldStorage(fp=StringIO(body), environ={ 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': content_type}) ).items(): if v.filename: args[k] = v else: args[k] = v.value args.update({'collection': 'attach', 'member': 'build'}) self.assertTrue('file' in args) outheaders = {} outbody = StringIO() req = Mock(args=args, form_token='123456', authname='hal', remote_addr='127.0.0.1', send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write) config = BuildConfig(self.env, 'test', path='somepath', active=True, recipe='') config.insert() build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42, started=42, status=Build.IN_PROGRESS) build.insert() module = BuildMaster(self.env) self.assertRaises(RequestDone, module._process_attachment, req, config, build) self.assertEqual(201, outheaders['Status']) self.assertEqual('18', outheaders['Content-Length']) self.assertEqual('text/plain', outheaders['Content-Type']) self.assertEqual('Attachment created', outbody.getvalue()) build_atts = list(Attachment.select(self.env, 'build', 'test/1')) self.assertEquals(1, len(build_atts)) self.assertEquals('hal', build_atts[0].author) self.assertEquals('baz baz', build_atts[0].description) self.assertEquals('baz.txt', build_atts[0].filename) self.assertEquals('hello baz', build_atts[0].open().read()) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(BuildMasterTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests/model.py000644 000765 000120 00000077273 11453043212 017172 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 import os import shutil import tempfile class BaseModelTestCase(unittest.TestCase): """ Inheritable base for model test case classes. """ # Override with iterable containing required schemas schemas = None def setUp(self): self.env = EnvironmentStub() self.env.path = tempfile.mkdtemp() logs_dir = self.env.config.get("bitten", "logs_dir") if os.path.isabs(logs_dir): raise ValueError("Should not have absolute logs directory for temporary test") logs_dir = os.path.join(self.env.path, logs_dir) os.makedirs(logs_dir) db = self.env.get_db_cnx() cursor = db.cursor() connector, _ = DatabaseManager(self.env)._get_connector() for schema in self.schemas: for table in schema: for stmt in connector.to_sql(table): cursor.execute(stmt) db.commit() def tearDown(self): shutil.rmtree(self.env.path) class BuildConfigTestCase(BaseModelTestCase): schemas = [schema] 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_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(BaseModelTestCase): schemas = [TargetPlatform._schema] 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(BaseModelTestCase): schemas = [Build._schema] 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(BaseModelTestCase): schemas = [BuildStep._schema] 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(BaseModelTestCase): schemas = [BuildLog._schema] 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', filename='1.log') full_file = log.get_log_file('1.log') if os.path.exists(full_file): os.remove(full_file) 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,filename FROM bitten_log " "WHERE id=%s", (log.id,)) self.assertEqual((1, 'test', 'distutils', '1.log'), cursor.fetchone()) lines = open(full_file, "rb").readlines() self.assertEqual('running tests\n', lines[0]) self.assertEqual('tests failed\n', lines[1]) if os.path.exists(full_file): os.remove(full_file) def test_insert_empty(self): log = BuildLog(self.env, build=1, step='test', generator='distutils', filename="1.log") full_file = log.get_log_file('1.log') if os.path.exists(full_file): os.remove(full_file) log.messages = [] log.insert() self.assertNotEqual(None, log.id) db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT build,step,generator,filename FROM bitten_log " "WHERE id=%s", (log.id,)) self.assertEqual((1, 'test', 'distutils', '1.log'), cursor.fetchone()) file_exists = os.path.exists(full_file) if file_exists: os.remove(full_file) assert not file_exists 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,filename) " "VALUES (%s,%s,%s,%s)", (1, 'test', 'distutils', '1.log')) id = db.get_last_id(cursor, 'bitten_log') logs_dir = self.env.config.get("bitten", "logsdir", "log/bitten") if os.path.isabs(logs_dir): raise ValueError("Should not have absolute logs directory for temporary test") logs_dir = os.path.join(self.env.path, logs_dir) full_file = os.path.join(logs_dir, "1.log") open(full_file, "wb").writelines(["running tests\n", "tests failed\n"]) 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()) file_exists = os.path.exists(full_file) if os.path.exists(full_file): os.remove(full_file) assert not file_exists def test_delete_new(self): log = BuildLog(self.env, build=1, step='test', generator='foo') self.assertRaises(AssertionError, log.delete) def test_insert_and_delete_files(self): # create - files should be created automatically build_log = BuildLog(self.env, build=1, step='test', generator='make') build_log.messages = [(BuildLog.INFO, 'running')] build_log.insert() # fetch it fresh - check object and files build_log = BuildLog.fetch(self.env, id=build_log.id) self.assertEquals(build_log.filename, "%s.log" % build_log.id) log_file = build_log.get_log_file(build_log.filename) levels_file = log_file + BuildLog.LEVELS_SUFFIX self.failUnless(os.path.exists(log_file), 'log_file does not exist') self.failUnless(os.path.exists(levels_file), 'levels_file does not exist') self.assertEquals(build_log.messages, [(BuildLog.INFO, 'running')]) # delete - object and file should be gone build_log.delete() self.assertEquals(None, BuildLog.fetch(self.env, id=build_log.id)) self.failIf(os.path.exists(log_file), 'log_file exists after delete()') self.failIf(os.path.exists(levels_file), 'levels_file exists after delete()') def test_fetch(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO bitten_log (build,step,generator,filename) " "VALUES (%s,%s,%s,%s)", (1, 'test', 'distutils', '1.log')) id = db.get_last_id(cursor, 'bitten_log') logs_dir = self.env.config.get("bitten", "logsdir", "log/bitten") if os.path.isabs(logs_dir): raise ValueError("Should not have absolute logs directory for temporary test") logs_dir = os.path.join(self.env.path, logs_dir) full_file = os.path.join(logs_dir, "1.log") open(full_file, "wb").writelines(["running tests\n", "tests failed\n", u"test unicode\xbb\n".encode("UTF-8")]) 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.UNKNOWN, 'running tests'), log.messages[0]) self.assertEqual((BuildLog.UNKNOWN, 'tests failed'), log.messages[1]) self.assertEqual((BuildLog.UNKNOWN, u'test unicode\xbb'), log.messages[2]) if os.path.exists(full_file): os.remove(full_file) def test_select(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO bitten_log (build,step,generator,filename) " "VALUES (%s,%s,%s,%s)", (1, 'test', 'distutils', '1.log')) id = db.get_last_id(cursor, 'bitten_log') logs_dir = self.env.config.get("bitten", "logsdir", "log/bitten") if os.path.isabs(logs_dir): raise ValueError("Should not have absolute logs directory for temporary test") logs_dir = os.path.join(self.env.path, logs_dir) full_file = os.path.join(logs_dir, "1.log") open(full_file, "wb").writelines(["running tests\n", "tests failed\n", u"test unicode\xbb\n".encode("UTF-8")]) 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.UNKNOWN, 'running tests'), log.messages[0]) self.assertEqual((BuildLog.UNKNOWN, 'tests failed'), log.messages[1]) self.assertEqual((BuildLog.UNKNOWN, u'test unicode\xbb'), log.messages[2]) self.assertRaises(StopIteration, logs.next) if os.path.exists(full_file): os.remove(full_file) class ReportTestCase(BaseModelTestCase): schemas = [Report._schema] 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) class PlatformBuildTestCase(BaseModelTestCase): """Tests that involve Builds, TargetPlatforms and BuildSteps""" schemas = [Build._schema, TargetPlatform._schema, BuildStep._schema] def test_delete_platform_with_pending_builds(self): """Check that deleting a platform with pending builds removes those pending builds""" db = self.env.get_db_cnx() platform = TargetPlatform(self.env, config='test', name='Linux') platform.insert() build = Build(self.env, config='test', platform=platform.id, rev='42', rev_time=12039) build.insert() platform.delete() pending = list(build.select(self.env, config='test', status=Build.PENDING)) self.assertEqual(0, len(pending)) 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')) suite.addTest(unittest.makeSuite(PlatformBuildTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests/notify.py000644 000765 000120 00000007705 11444412540 017377 0ustar00simonadmin000000 000000 #-*- coding: utf-8 -*- # # Copyright (C) 2007 Ole Trenner, # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. import unittest from trac.db import DatabaseManager from trac.test import EnvironmentStub, Mock from trac.web.session import DetachedSession from bitten.model import schema, Build, BuildStep, BuildLog from bitten.notify import BittenNotify, BuildNotifyEmail class BittenNotifyBaseTest(unittest.TestCase): def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'bitten.notify.*']) repos = Mock(get_changeset=lambda rev: Mock(author='author', rev=rev)) self.env.get_repository = lambda authname=None: repos 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() class BittenNotifyTest(BittenNotifyBaseTest): """unit tests for BittenNotify dispatcher class""" def setUp(self): BittenNotifyBaseTest.setUp(self) self.dispatcher = BittenNotify(self.env) self.failed_build = Build(self.env, status=Build.FAILURE) self.successful_build = Build(self.env, status=Build.SUCCESS) def test_do_notify_on_failed_build(self): self.set_option(BittenNotify.notify_on_failure, 'true') self.assertTrue(self.dispatcher._should_notify(self.failed_build), 'notifier should be called for failed builds.') def test_do_not_notify_on_failed_build(self): self.set_option(BittenNotify.notify_on_failure, 'false') self.assertFalse(self.dispatcher._should_notify(self.failed_build), 'notifier should not be called for failed build.') def test_do_notify_on_successful_build(self): self.set_option(BittenNotify.notify_on_success, 'true') self.assertTrue(self.dispatcher._should_notify(self.successful_build), 'notifier should be called for successful builds when configured.') def test_do_not_notify_on_successful_build(self): self.set_option(BittenNotify.notify_on_success, 'false') self.assertFalse(self.dispatcher._should_notify(self.successful_build), 'notifier should not be called for successful build.') def set_option(self, option, value): self.env.config.set(option.section, option.name, value) class BuildNotifyEmailTest(BittenNotifyBaseTest): """unit tests for BittenNotifyEmail class""" def setUp(self): BittenNotifyBaseTest.setUp(self) self.env.config.set('notification','smtp_enabled','true') self.notifications_sent_to = [] def send(to, cc, hdrs={}): self.notifications_sent_to = to def noop(*args, **kw): pass self.email = Mock(BuildNotifyEmail, self.env, begin_send=noop, finish_send=noop, send=send) self.build = Build(self.env, status=Build.SUCCESS, rev=123) def test_notification_is_sent_to_author(self): self.email.notify(self.build) self.assertTrue('author' in self.notifications_sent_to, 'Recipient list should contain the author') def test_notification_body_render(self): self.email.notify(self.build) output = self.email.template.generate(**self.email.data).render('text') self.assertTrue('Successful build of My Project [123]' in output) self.assertTrue('' in output) # TODO functional tests of generated mails def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(BittenNotifyTest, 'test')) suite.addTest(unittest.makeSuite(BuildNotifyEmailTest, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests/queue.py000644 000765 000120 00000050554 11535320455 017217 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 threading import time import unittest from trac.db import DatabaseManager from trac.test import EnvironmentStub, Mock from trac.util.datefmt import to_datetime, utc 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() 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_no_repos(self): """ Cannot work when there are no repositories defined. """ self.env.get_repository = lambda: None queue = BuildQueue(self.env) self.assertRaises(AssertionError, queue.populate) def test_populate_not_build_all(self): self.env.get_repository = lambda authname=None: Mock( get_changeset=lambda rev: Mock(date=to_datetime(rev * 1000, utc)), 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=to_datetime(rev * 1000, utc)), 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_populate_thread_race_condition(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) def get_history(): yield ('somepath', 123, 'edit') yield ('somepath', 121, 'edit') yield ('somepath', 120, 'edit') time.sleep(1) # sleep to make sure both threads collect self.env.get_repository = lambda authname=None: Mock( get_changeset=lambda rev: Mock(date=to_datetime(rev * 1000, utc)), get_node=lambda path, rev=None: Mock( get_entries=lambda: [Mock(), Mock()], get_history=get_history ), 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() def build_populator(): queue = BuildQueue(self.env, build_all=True) queue.populate() thread1 = threading.Thread(target=build_populator) thread2 = threading.Thread(target=build_populator) # tiny sleep is to avoid odd segementation faults # (on Linux) and bus errors (on Mac OS X) thread1.start(); time.sleep(0.01); thread2.start() thread1.join(); thread2.join() # check builds got added 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) # check attempts at duplicate inserts were logged. failure_messages = [x for x in messages if x.startswith('Failed to insert build')] self.assertEqual(6, len(failure_messages)) def test_should_delete_build_platform_dont_exist(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) config = BuildConfig(self.env, 'test', active=True) config.insert() build = Build(self.env, config=config.name, rev=42, platform="no-stuff", rev_time=123456) build.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build, self.repos)) self.assert_("platform no longer exists" in messages[0]) def test_should_delete_build_config_deactivated(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) config = BuildConfig(self.env, 'test', active=False) config.insert() platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build = Build(self.env, config=config.name, rev=42, platform=platform.id, rev_time=123456) build.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build, self.repos)) self.assert_("configuration is deactivated" in messages[0]) def test_should_delete_build_config_none(self): out = [] self.env.log = Mock( info=lambda msg, *args: out.extend([msg] + list(args))) platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build = Build(self.env, config='does_not_exist', rev=42, platform=platform.id, rev_time=123456) build.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build, self.repos)) self.assertTrue("configuration is deactivated" in out[0]) self.assertEquals('unknown config "does_not_exist"', out[1]) def test_should_delete_build_outside_revision_range(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) self.repos.rev_older_than = lambda rev1, rev2: rev1 < rev2 config = BuildConfig(self.env, 'test', active=True, min_rev=120, max_rev=123) config.insert() platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build1 = Build(self.env, config=config.name, rev=42, platform=platform.id, rev_time=123456) build1.insert() build2 = Build(self.env, config=config.name, rev=10042, platform=platform.id, rev_time=123456) build2.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build1, self.repos)) self.assertEqual(True, queue.should_delete_build(build2, self.repos)) self.assert_("outside of the revision range" in messages[0]) self.assert_("outside of the revision range" in messages[1]) def test_should_delete_build_old_with_not_buildall(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) config = BuildConfig(self.env, 'test', active=True) config.insert() platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build1 = Build(self.env, config=config.name, rev=42, platform=platform.id, rev_time=123456) build1.insert() build2 = Build(self.env, config=config.name, rev=43, platform=platform.id, rev_time=123457, slave='slave') build2.insert() queue = BuildQueue(self.env, build_all=False) self.assertEqual(True, queue.should_delete_build(build1, self.repos)) self.assert_("more recent build exists" in messages[0]) 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', last_activity=time.time() - 600) # active 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', last_activity=time.time() - 60) # active 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') Bitten-0.6/bitten/tests/upgrades.py000644 000765 000120 00000033670 11456702627 017714 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2009-2010 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 import logging import warnings warnings.filterwarnings('ignore', '^Unknown table') warnings.filterwarnings('ignore', '^the sets module is deprecated') from trac.core import TracError from trac.test import EnvironmentStub from trac.db import Table, Column, Index, DatabaseManager from bitten.upgrades import update_sequence, drop_index from bitten import upgrades, main, model import os import shutil import tempfile class BaseUpgradeTestCase(unittest.TestCase): schema = None other_tables = [] def setUp(self): self.env = EnvironmentStub() if hasattr(self.env, 'dburi'): # Trac gained support for testing against different databases in 0.11.5 # If this support is available, we copy the test db uri configuration # into the main test config so it can be picked up by # upgrades.parse_scheme() self.env.config.set('trac', 'database', self.env.dburi) self.env.path = tempfile.mkdtemp() logs_dir = self.env.config.get("bitten", "logs_dir", "log/bitten") if os.path.isabs(logs_dir): raise ValueError("Should not have absolute logs directory for temporary test") logs_dir = os.path.join(self.env.path, logs_dir) self.logs_dir = logs_dir db = self.env.get_db_cnx() cursor = db.cursor() for table_name in self.other_tables: cursor.execute("DROP TABLE IF EXISTS %s" % (table_name,)) connector, _ = DatabaseManager(self.env)._get_connector() for table in self.schema: cursor.execute("DROP TABLE IF EXISTS %s" % (table.name,)) for stmt in connector.to_sql(table): cursor.execute(stmt) db.commit() def tearDown(self): shutil.rmtree(self.env.path) del self.logs_dir del self.env class LogWatcher(logging.Handler): def __init__(self, level=0): logging.Handler.__init__(self, level=0) self.records = [] def emit(self, record): self.records.append(record) class UpgradeHelperTestCase(BaseUpgradeTestCase): schema = [ Table('test_update_sequence', key='id')[ Column('id', auto_increment=True), Column('name'), ], Table('test_drop_index', key='id')[ Column('id', type='int'), Column('name', size=20), Index(['name']) ], ] def test_update_sequence(self): db = self.env.get_db_cnx() cursor = db.cursor() for rowid, name in [(1, 'a'), (2, 'b'), (3, 'c')]: cursor.execute("INSERT INTO test_update_sequence (id, name)" " VALUES (%s, %s)", (rowid, name)) update_sequence(self.env, db, 'test_update_sequence', 'id') cursor.execute("INSERT INTO test_update_sequence (name)" " VALUES (%s)", ('d',)) cursor.execute("SELECT id FROM test_update_sequence WHERE name = %s", ('d',)) row = cursor.fetchone() self.assertEqual(row[0], 4) def test_drop_index(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO test_drop_index (id, name)" " VALUES (%s, %s)", (1, 'a')) def do_drop(): drop_index(self.env, db, 'test_drop_index', 'test_drop_index_name_idx') # dropping the index should succeed the first time and fail the next do_drop() self.assertRaises(Exception, do_drop) class UpgradeScriptsTestCase(BaseUpgradeTestCase): schema = [ # Sytem Table('system', key='name')[ Column('name'), Column('value') ], # Config Table('bitten_config', key='name')[ Column('name'), Column('path'), Column('label'), Column('active', type='int'), Column('description') ], # Platform 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') ], # Build 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 Step Table('bitten_step', key=('build', 'name'))[ Column('build', type='int'), Column('name'), Column('description'), Column('status', size=1), Column('log'), Column('started', type='int'), Column('stopped', type='int') ], ] other_tables = [ 'bitten_log', 'bitten_log_message', 'bitten_report', 'bitten_report_item', 'bitten_error', 'old_step', 'old_config_v2', 'old_log_v5', 'old_log_v8', 'old_rule_v9', 'old_build_v11', ] basic_data = [ ['system', ('name', 'value'), [ ('bitten_version', '1'), ] ], ['bitten_config', ('name',), [ ('test_config',), ] ], ['bitten_platform', ('config', 'name'), [ ('test_config', 'test_plat'), ] ], ['bitten_build', ('id', 'config', 'rev', 'platform', 'rev_time'), [ (12, 'test_config', '123', 1, 456), ] ], ['bitten_step', ('build', 'name', 'log'), [ (12, 'step1', None), (12, 'step2', "line1\nline2"), ] ], ] def _do_upgrade(self): """Do an full upgrade.""" import inspect db = self.env.get_db_cnx() versions = sorted(upgrades.map.keys()) for version in versions: for function in upgrades.map.get(version): self.assertTrue(inspect.getdoc(function)) function(self.env, db) db.commit() def _insert_data(self, data): """Insert data for upgrading.""" db = self.env.get_db_cnx() cursor = db.cursor() for table, cols, vals in data: cursor.executemany("INSERT INTO %s (%s) VALUES (%s)" % (table, ','.join(cols), ','.join(['%s' for c in cols])), vals) db.commit() def _check_basic_upgrade(self): """Check the results of an upgrade of basic data.""" configs = list(model.BuildConfig.select(self.env, include_inactive=True)) platforms = list(model.TargetPlatform.select(self.env)) builds = list(model.Build.select(self.env)) steps = list(model.BuildStep.select(self.env)) logs = list(model.BuildLog.select(self.env)) self.assertEqual(len(configs), 1) self.assertEqual(configs[0].name, 'test_config') self.assertEqual(len(platforms), 1) self.assertEqual(platforms[0].config, 'test_config') self.assertEqual(platforms[0].name, 'test_plat') self.assertEqual(len(builds), 1) self.assertEqual(builds[0].id, 12) self.assertEqual(builds[0].config, 'test_config') self.assertEqual(builds[0].rev, '123') self.assertEqual(builds[0].platform, 1) self.assertEqual(builds[0].rev_time, 456) self.assertEqual(len(steps), 2) self.assertEqual(steps[0].build, 12) self.assertEqual(steps[0].name, 'step1') self.assertEqual(steps[1].build, 12) self.assertEqual(steps[1].name, 'step2') self.assertEqual(len(logs), 1) self.assertEqual(logs[0].build, 12) self.assertEqual(logs[0].step, 'step2') log_file = logs[0].get_log_file(logs[0].filename) self.assertEqual(file(log_file, "rU").read(), "line1\nline2\n") # check final sequences for tbl, col in [ ('bitten_build', 'id'), ('bitten_log', 'id'), ('bitten_platform', 'id'), ('bitten_report', 'id'), ]: self._check_sequence(tbl, col) def _check_sequence(self, tbl, col): scheme = upgrades.parse_scheme(self.env) if scheme == "postgres": self._check_postgres_sequence(tbl, col) def _check_postgres_sequence(self, tbl, col): """Check a PostgreSQL sequence for the given table and column.""" seq = '%s_%s_seq' % (tbl, col) cursor = self.env.get_db_cnx().cursor() cursor.execute("SELECT MAX(%s) FROM %s" % (col, tbl)) current_max = cursor.fetchone()[0] or 0 # if currently None cursor.execute("SELECT nextval('%s')" % (seq,)) current_seq = cursor.fetchone()[0] - 1 self.assertEqual(current_max, current_seq, "On %s (col: %s) expected column max (%d) " "and sequence value (%d) to match" % (tbl, col, current_max, current_seq)) def test_null_upgrade(self): self._do_upgrade() def test_basic_upgrade(self): self._insert_data(self.basic_data) self._do_upgrade() self._check_basic_upgrade() def test_upgrade_via_buildsetup(self): self._insert_data(self.basic_data) db = self.env.get_db_cnx() build_setup = main.BuildSetup(self.env) self.assertTrue(build_setup.environment_needs_upgrade(db)) build_setup.upgrade_environment(db) self._check_basic_upgrade() # check bitten table version cursor = db.cursor() cursor.execute("SELECT value FROM system WHERE name='bitten_version'") rows = cursor.fetchall() self.assertEqual(rows, [(str(model.schema_version),)]) def test_fix_log_levels_misnaming(self): logfiles = { "1.log": "", "2.log": "", "3.log": "", "1.log.level": "info\n", "2.log.levels": "info\ninfo\n", "3.log.level": "warn\n", "3.log.levels": "warn\nwarn\n", "4.log.level": "error\n", } expected_deletions = [ "4.log.level", ] os.makedirs(self.logs_dir) for filename, data in logfiles.items(): path = os.path.join(self.logs_dir, filename) logfile = open(path, "w") logfile.write(data) logfile.close() logwatch = LogWatcher(logging.INFO) self.env.log.setLevel(logging.INFO) self.env.log.addHandler(logwatch) upgrades.fix_log_levels_misnaming(self.env, None) filenames = sorted(os.listdir(self.logs_dir)) for filename in filenames: path = os.path.join(self.logs_dir, filename) origfile = filename in logfiles and filename or filename.replace("levels", "level") self.assertEqual(logfiles[origfile], open(path).read()) self.assertTrue(filename not in expected_deletions) self.assertEqual(len(filenames), len(logfiles) - len(expected_deletions)) logs = sorted(logwatch.records, key=lambda rec: rec.getMessage()) self.assertEqual(len(logs), 5) self.assertTrue(logs[0].getMessage().startswith( "Deleted 1 stray log level (0 errors)")) self.assertTrue(logs[1].getMessage().startswith( "Deleted stray log level file 4.log.level")) self.assertTrue(logs[2].getMessage().startswith( "Error renaming")) self.assertTrue(logs[3].getMessage().startswith( "Renamed 1 incorrectly named log level files from previous migrate (1 errors)")) self.assertTrue(logs[4].getMessage().startswith( "Renamed incorrectly named log level file")) def test_remove_stray_log_levels_files(self): logfiles = { "1.log": "", "1.log.levels": "info\n", "2.log.levels": "info\ninfo\n", } expected_deletions = [ "2.log.levels", ] os.makedirs(self.logs_dir) for filename, data in logfiles.items(): path = os.path.join(self.logs_dir, filename) logfile = open(path, "w") logfile.write(data) logfile.close() logwatch = LogWatcher(logging.INFO) self.env.log.setLevel(logging.INFO) self.env.log.addHandler(logwatch) upgrades.remove_stray_log_levels_files(self.env, None) filenames = sorted(os.listdir(self.logs_dir)) for filename in filenames: path = os.path.join(self.logs_dir, filename) self.assertEqual(logfiles[filename], open(path).read()) self.assertTrue(filename not in expected_deletions) self.assertEqual(len(filenames), len(logfiles) - len(expected_deletions)) logs = sorted(logwatch.records, key=lambda rec: rec.getMessage()) self.assertEqual(len(logs), 2) self.assertTrue(logs[0].getMessage().startswith( "Deleted 1 stray log levels (0 errors)")) self.assertTrue(logs[1].getMessage().startswith( "Deleted stray log levels file 2.log.levels")) def test_migrate_logs_to_files_with_logs_dir(self): os.makedirs(self.logs_dir) self.assertRaises(TracError, upgrades.migrate_logs_to_files, self.env, None) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(UpgradeHelperTestCase, 'test')) suite.addTest(unittest.makeSuite(UpgradeScriptsTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/tests/web_ui.py000644 000765 000120 00000037713 11500370724 017343 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.api import HTTPNotFound 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, BuildController, \ SourceFileLinkFormatter class AbstractWebUITestCase(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, normalize_rev=lambda rev: rev, sync=lambda: None, ) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) self.env.get_repository = lambda authname=None: self.repos def tearDown(self): shutil.rmtree(self.env.path) class BuildConfigControllerTestCase(AbstractWebUITestCase): 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'), authname='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, normalize_rev=lambda rev: rev, youngest_rev=123) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) 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'] from trac.resource import Resource self.assertEquals(Resource('build', 'test'), data['context'].resource) self.assertEquals([], data['config']['attachments']['attachments']) self.assertEquals('/trac/attachment/build/test/', data['config']['attachments']['attach_href']) def test_bitten_keeps_order_of_revisions_from_versioncontrol(self): # Trac's API specifies that they are sorted chronological (backwards) # We must not assume that these revision numbers can be sorted later on, # for example the mercurial plugin will return the revisions as strings # (e.g. '880:4c19fa95fb9e') 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/'+config.name, href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) # revisions are intentionally not sorted in any way - bitten should just keep them! revision_ids = [5, 8, 2] revision_list = [('trunk', revision, 'edit') for revision in revision_ids] root = Mock(get_entries=lambda: ['foo'], get_history=lambda: revision_list) self.repos = Mock(get_node=lambda path, rev=None: root, sync=lambda: None, normalize_path=lambda path: path, normalize_rev=lambda rev: rev, youngest_rev=5) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) module = BuildConfigController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) actual_revision_ids = data['config']['revisions'] self.assertEquals(revision_ids, actual_revision_ids) 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, normalize_rev=lambda rev: rev, youngest_rev=123) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) 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']) def test_raise_404(self): PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') module = BuildConfigController(self.env) req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/nonexisting', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) self.failUnless(module.match_request(req)) try: module.process_request(req) except Exception, e: self.failUnless(isinstance(e, HTTPNotFound)) self.assertEquals(str(e), "404 Not Found (Build configuration " "'nonexisting' does not exist.)") return self.fail("This should have raised HTTPNotFound") class BuildControllerTestCase(AbstractWebUITestCase): def test_view_build(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, status=Build.SUCCESS, slave='hal') build.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test/1', 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, normalize_rev=lambda rev: rev, get_changeset=lambda rev: Mock(author='joe')) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) module = BuildController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) self.assertEqual('view_build', data['page_mode']) from trac.resource import Resource self.assertEquals(Resource('build', 'test/1'), data['context'].resource) self.assertEquals([], data['build']['attachments']['attachments']) self.assertEquals('/trac/attachment/build/test/1/', data['build']['attachments']['attach_href']) def test_raise_404(self): PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') module = BuildController(self.env) config = BuildConfig(self.env, name='existing', path='trunk') config.insert() req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/existing/42', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) self.failUnless(module.match_request(req)) try: module.process_request(req) except Exception, e: self.failUnless(isinstance(e, HTTPNotFound)) self.assertEquals(str(e), "404 Not Found (Build '42' does not exist.)") return self.fail("This should have raised HTTPNotFound") class SourceFileLinkFormatterTestCase(AbstractWebUITestCase): 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) # posix 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) # windows output = formatter(step, None, None, u'error in foo\\win.c: bad') self.assertEqual(Markup, type(output)) self.assertEqual(r'error in ' 'foo\win.c: bad', output) def test_format_bad_links(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'Linking -I../.. with ../libtool') self.assertEqual(Markup, type(output)) self.assertEqual('Linking -I../.. with ../libtool', 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) # posix 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) # windows output = formatter(step, None, None, u'error in foo\\win.c:123: bad') self.assertEqual(Markup, type(output)) self.assertEqual(r'error in ' 'foo\win.c:123: bad', output) def test_format_link_not_in_repos_with_line(self): BuildConfig(self.env, name='test', path='trunk').insert() build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, status=Build.SUCCESS, slave='hal') build.insert() step = BuildStep(self.env, build=build.id, name='foo', status=BuildStep.SUCCESS) step.insert() def _raise(): raise TracError('No such node') self.repos.get_node = lambda path, rev: _raise() req = Mock(method='GET', href=Href('/trac'), authname='hal') comp = SourceFileLinkFormatter(self.env) formatter = comp.get_formatter(req, build) output = formatter(step, None, None, u'error in foo/bar.c:123: bad') self.assertEqual(Markup, type(output)) self.assertEqual('error in foo/bar.c:123: bad', output) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(BuildConfigControllerTestCase, 'test')) suite.addTest(unittest.makeSuite(BuildControllerTestCase, 'test')) suite.addTest(unittest.makeSuite(SourceFileLinkFormatterTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/templates/bitten_admin_configs.html000755 000765 000120 00000022162 11234443127 023403 0ustar00simonadmin000000 000000 Manage Build Configurations

Manage Build Configurations

Repository Mapping

Target Platforms

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

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

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

The match pattern is a regular expression.

Add Configuration:
 Name PathActive
(No Build Configurations)
$config.label $config.path ${config.recipe == False and 'No recipe' or ''}
Bitten-0.6/bitten/templates/bitten_admin_master.html000755 000765 000120 00000006462 11201767462 023260 0ustar00simonadmin000000 000000 Manage Build Master

Manage Build Master

Configuration Options

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

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


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


Show the latest build status within the Trac navigation bar.
The Bitten button appearance reflects the build status.


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.

The directory on the server in which client log files will be stored.

Bitten-0.6/bitten/templates/bitten_build.html000755 000765 000120 00000007037 11460004634 021703 0ustar00simonadmin000000 000000 $title

$title

Build steps

  1. $step.name
Configuration:
$build.config.name
Platform:
$build.platform
Triggered by:
Changeset [$build.display_rev] by ${format_author(build.chgset_author)}
Built by:
$slave.name ($slave.ipnr)
Operating system:
$slave.os_name $slave.os_version ($slave.os_family)
Hardware:
$slave.machine ($slave.processor)
${build.stopped and 'Started:' or 'Building since:'}
$build.started ($build.started_delta ago)
Stopped:
$build.stopped ($build.stopped_delta ago)
Duration:
$build.duration
${attach_file_form(build.attachments)}
${list_of_attachments(build.attachments, compact=True)}

$step.name ($step.duration)

Errors

  • $error

$step.description

Log

$item.message
Bitten-0.6/bitten/templates/bitten_config.html000755 000765 000120 00000023273 11471201064 022047 0ustar00simonadmin000000 000000 $title Success Failed In-progress
Duration: ${build.duration}
${build.started} (${build.started_delta} ago)
$slave.name ($slave.ipnr)
$slave.os_name $slave.os_version / ${slave.processor or slave.machine or ''}

$title

$builds_pending pending builds
$builds_inprogress in-progress builds

$config.label

$config.builds_pending pending builds ( $platform.name: $platform.builds_pending )
$config.builds_inprogress in-progress builds ( $platform.name: $platform.builds_inprogress )
$config.description

Latest builds

[$youngest_rev.display_rev] by ${format_author(youngest_rev.author)}

$youngest_rev.date

$youngest_rev.message

$build.platform

$build.stopped

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

No build yet


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

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

$config.description
${attach_file_form(config.attachments)}
${list_of_attachments(config.attachments, compact=True)}
$config.builds_pending pending builds ( $platform.name: $platform.builds_pending )
$config.builds_inprogress in-progress builds ( $platform.name: $platform.builds_inprogress )
Chgset $platform.name
[$rev.display_rev]
$build.id ${build_status(build.status)} ${slave_info(build.slave)} ${build_time(build)}
${build_steps(build.steps)}

$config.label

ChgsetBuild
[$build.rev]
$build.id: $build.platform ${slave_info(build.slave)} ${build_time(build)}
${build_steps(build.steps)}
Bitten-0.6/bitten/templates/bitten_notify_email.txt000644 000765 000120 00000001423 11460004634 023124 0ustar00simonadmin000000 000000 $build.status build of $project.name [$change.rev] --------------------------------------------------------------------- Changeset: $change.rev - <$change.link> Committed by: $change.author Build Configuration: $build.config Build Platform: $build.platform Build Slave: $build.slave Build Number: $build.id - <${build.link}> {% if build.failed_steps %}\ Failures: {% for step in build.failed_steps %}\ Step: $step.name Errors: ${', '.join(step.errors)} Log: {% for lvl, msg in step.log_messages %}\ [${lvl.upper().ljust(8)}] $msg {% end for %}\ {% end for %}\ {% end if %}\ -- Build URL: <$build.link> $project.name <${project.url or abs_href()}> $project.descr Bitten-0.6/bitten/templates/bitten_summary_coverage.html000755 000765 000120 00000001567 11155063326 024162 0ustar00simonadmin000000 000000

Code Coverage

UnitLines of Code Coverage
$item.name $item.name $item.loc ${item.cov}%
Total$totals.loc ${totals.cov}%
Bitten-0.6/bitten/templates/bitten_summary_lint.html000644 000765 000120 00000003024 11267045472 023326 0ustar00simonadmin000000 000000

Code Lint

FileProblem Category TotalsTotal
Convention Refactor Warning Error
$item.file $item.file $item.category.convention $item.category.refactor $item.category.warning $item.category.error $item.lines
Total (in $totals.files files) $totals.category.convention $totals.category.refactor $totals.category.warning $totals.category.error $totals.lines
Bitten-0.6/bitten/templates/bitten_summary_tests.html000755 000765 000120 00000002541 11242336702 023520 0ustar00simonadmin000000 000000

Test Results

Test FixtureTotal FailuresIgnoresErrors
$item.name $item.name

$failure.name ($failure.status):
$failure.traceback

${item.num_success + item.num_failure + item.num_error + item.num_ignore} $item.num_failure $item.num_ignore $item.num_error
Total ${totals.success + totals.failure + totals.ignore + totals.error} $totals.failure $totals.ignore $totals.error
Bitten-0.6/bitten/templates/json.txt000644 000765 000120 00000000017 11364623604 020056 0ustar00simonadmin000000 000000 ${dumps(json)} Bitten-0.6/bitten/report/__init__.py000644 000765 000120 00000000516 11453043212 017764 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007-2010 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' Bitten-0.6/bitten/report/coverage.py000644 000765 000120 00000025412 11456171632 020035 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 genshi.builder import tag from trac.core import * from trac.mimeview.api import IHTMLPreviewAnnotator from trac.resource import Resource from trac.util.datefmt import to_timestamp from trac.web.api import IRequestFilter from trac.web.chrome import add_stylesheet, add_ctxtnav, add_warning 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' AND build.rev_time >= %%s AND build.rev_time <= %%s 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, config.min_rev_time(self.env), config.max_rev_time(self.env))) 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': [ {'label': 'Lines of code', 'data': [[item[0], item[1]] for item in coverage], 'lines': {'fill': True}}, {'label': 'Coverage', 'data': [[item[0], item[2]] for item in coverage]}, ], 'options': { 'legend': {'position': 'sw', 'backgroundOpacity': 0.7}, 'xaxis': {'tickDecimals': 0}, 'yaxis': {'tickDecimals': 0}, }, } return 'json.txt', {"json": 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)} } class TestCoverageAnnotator(Component): """ >>> from genshi.builder import tag >>> from trac.test import Mock, MockPerm >>> from trac.mimeview import Context >>> from trac.util.datefmt import to_datetime, utc >>> 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() >>> repos = Mock(get_changeset=lambda x: Mock(date=to_datetime(12345, utc))) >>> env.get_repository = lambda: repos >>> 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={'warnings': []}, args={}) Version in the branch should not match: >>> context = Context.from_request(req, 'source', '/branches/blah/foo.py', 123) >>> ann.get_annotation_data(context) [] Version in the trunk should match: >>> context = Context.from_request(req, 'source', '/trunk/foo.py', 123) >>> data = ann.get_annotation_data(context) >>> print data [u'5', u'-', u'0'] >>> def annotate_row(lineno, line): ... row = tag.tr() ... ann.annotate_row(context, row, lineno, line, data) ... return row.generate().render('html') >>> annotate_row(1, 'x = 1') '5' >>> annotate_row(2, '') '' >>> annotate_row(3, 'y = x') '0' """ implements(IRequestFilter, IHTMLPreviewAnnotator) # IRequestFilter methods def pre_process_request(self, req, handler): return handler def post_process_request(self, req, template, data, content_type): """ Adds a 'Coverage' context navigation menu item. """ resource = data and data.get('context') \ and data.get('context').resource or None if resource and isinstance(resource, Resource) \ and resource.realm=='source' and data.get('file') \ and not req.args.get('annotate', '') == 'coverage': add_ctxtnav(req, tag.a('Coverage', title='Annotate file with test coverage ' 'data (if available)', href=req.href.browser(resource.id, annotate='coverage', rev=req.args.get('rev'), created=data.get('rev')), rel='nofollow')) return template, data, content_type # 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 # attempt to use the version passed in with the request, # otherwise fall back to the latest version of this file. version = context.req.args.get('rev', resource.version) # get the last change revision for the file so that we can # pick coverage data as latest(version >= file_revision) created = context.req.args.get('created', resource.version) repos = self.env.get_repository() version_time = to_timestamp(repos.get_changeset(version).date) if version != created: created_time = to_timestamp(repos.get_changeset(created).date) else: created_time = version_time self.log.debug("Looking for coverage report for %s@%s [%s:%s]..." % ( resource.id, str(resource.version), created, version)) db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(""" SELECT b.id, b.rev, i2.value FROM bitten_config AS c INNER JOIN bitten_build AS b ON c.name=b.config INNER JOIN bitten_report AS r ON b.id=r.build INNER JOIN bitten_report_item AS i1 ON r.id=i1.report INNER JOIN bitten_report_item AS i2 ON (i1.item=i2.item AND i1.report=i2.report) WHERE i2.name='line_hits' AND b.rev_time>=%s AND b.rev_time<=%s AND i1.name='file' AND """ + db.concat('c.path', "'/'", 'i1.value') + """=%s ORDER BY b.rev_time DESC LIMIT 1""" , (created_time, version_time, resource.id.lstrip('/'))) row = cursor.fetchone() if row: build_id, build_rev, line_hits = row coverage = line_hits.split() self.log.debug("Coverage annotate for %s@%s using build %d: %s", resource.id, build_rev, build_id, coverage) return coverage add_warning(context.req, "No coverage annotation found for " "/%s for revision range [%s:%s]." % ( resource.id.lstrip('/'), version, created)) return [] def annotate_row(self, context, row, lineno, line, 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')) Bitten-0.6/bitten/report/lint.py000644 000765 000120 00000014360 11453043212 017175 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007 Jeffrey Kyllo # # Based on code from the Bitten project: # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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://echospiral.com/trac/eatlint/wiki/License. __docformat__ = 'restructuredtext en' from trac.core import * from bitten.api import IReportChartGenerator, IReportSummarizer class PyLintChartGenerator(Component): implements(IReportChartGenerator) # IReportChartGenerator methods def get_supported_categories(self): return ['lint'] def generate_chart_data(self, req, config, category): assert category == 'lint' db = self.env.get_db_cnx() cursor = db.cursor() #self.log.debug('config.name=\'%s\'' % (config.name,)) query = """ select build.rev, (select count(*) from bitten_report_item as item where item.report = report.id and item.name='category' and item.value='convention'), (select count(*) from bitten_report_item as item where item.report = report.id and item.name='category' and item.value='error'), (select count(*) from bitten_report_item as item where item.report = report.id and item.name='category' and item.value='refactor'), (select count(*) from bitten_report_item as item where item.report = report.id and item.name='category' and item.value='warning') from bitten_report as report left outer join bitten_build as build ON (report.build=build.id) where build.config='%s' and report.category='lint' and build.rev_time >= %s and build.rev_time <= %s group by build.rev_time, build.rev, build.platform, report.id order by build.rev_time;""" % (config.name, config.min_rev_time(self.env), config.max_rev_time(self.env)) #self.log.debug('sql=\'%s\'' % (query,)) cursor.execute(query) lint = [] prev_rev = None prev_counts = None for rev, conv, err, ref, warn in cursor: total = conv + err + ref + warn curr_counts = [rev, total, conv, err, ref, warn] if rev != prev_rev: lint.append(curr_counts) else: # cunningly / dubiously set rev = max(rev, rev) along with the counts lint[-1] = [max(prev, curr) for prev, curr in zip(curr_counts, lint[-1])] # recalculate total lint[-1][1] = sum(lint[-1][2:]) prev_rev = rev data = {'title': 'Lint Problems by Type', 'data': [ {'label': 'Total Problems', 'data': [[item[0], item[1]] for item in lint], 'lines': {'fill': True}}, {'label': 'Convention', 'data': [[item[0], item[2]] for item in lint]}, {'label': 'Error', 'data': [[item[0], item[3]] for item in lint]}, {'label': 'Refactor', 'data': [[item[0], item[4]] for item in lint]}, {'label': 'Warning', 'data': [[item[0], item[5]] for item in lint]}, ], 'options': { 'legend': {'position': 'sw', 'backgroundOpacity': 0.7}, 'xaxis': {'tickDecimals': 0}, 'yaxis': {'tickDecimals': 0}, }, } return 'json.txt', {"json": data} class PyLintSummarizer(Component): implements(IReportSummarizer) # IReportSummarizer methods def get_supported_categories(self): return ['lint'] def render_summary(self, req, config, build, step, category): assert category == 'lint' db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute(""" SELECT item_type.value AS type, item_file.value AS file, item_line.value as line, item_category.value as category, report.category as report_category FROM bitten_report AS report LEFT OUTER JOIN bitten_report_item AS item_type ON (item_type.report=report.id AND item_type.name='type') LEFT OUTER JOIN bitten_report_item AS item_file ON (item_file.report=report.id AND item_file.item=item_type.item AND item_file.name='file') LEFT OUTER JOIN bitten_report_item AS item_line ON (item_line.report=report.id AND item_line.item=item_type.item AND item_line.name='lines') LEFT OUTER JOIN bitten_report_item AS item_category ON (item_category.report=report.id AND item_category.item=item_type.item AND item_category.name='category') WHERE report.category='lint' AND build=%s AND step=%s ORDER BY item_type.value""", (build.id, step.name)) file_data = {} type_total = {} category_total = {} line_total = 0 file_total = 0 seen_files = {} for type, file, line, category, report_category in cursor: if not file_data.has_key(file): file_data[file] = {'file': file, 'type': {}, 'lines': 0, 'category': {}} d = file_data[file] #d = {'type': type, 'line': line, 'category': category} if not d['type'].has_key(type): d['type'][type] = 0 d['type'][type] += 1 d['lines'] += 1 line_total += 1 if not d['category'].has_key(category): d['category'][category] = 0 d['category'][category] += 1 if file: d['href'] = req.href.browser(config.path, file) if not type_total.has_key(type): type_total[type] = 0 type_total[type] += 1 if not category_total.has_key(category): category_total[category] = 0 category_total[category] += 1 if not seen_files.has_key(file): seen_files[file] = 0 file_total += 1 data = [] for d in file_data.values(): d['catnames'] = d['category'].keys() data.append(d) template_data = {} template_data['data'] = data template_data['totals'] = {'type': type_total, 'category': category_total, 'files': file_total, 'lines': line_total} return 'bitten_summary_lint.html', template_data Bitten-0.6/bitten/report/testing.py000644 000765 000120 00000016253 11453043212 017707 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 add_script 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' AND build.rev_time >= %s AND build.rev_time <= %s GROUP BY build.rev_time, build.rev, build.platform, item_status.value ORDER BY build.rev_time, build.platform""", (config.name, config.min_rev_time(self.env), config.max_rev_time(self.env))) 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, 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': pass elif status == 'ignore': tests[-1][3] = max(num, tests[-1][3]) else: tests[-1][2] = max(num, tests[-1][2]) data = {'title': 'Unit Tests', 'data': [ {'label': 'Total', 'data': [[item[0], item[1]] for item in tests], 'lines': {'fill': True}}, {'label': 'Failures', 'data': [[item[0], item[2]] for item in tests]}, {'label': 'Ignored', 'data': [[item[0], item[3]] for item in tests]}, ], 'options': { 'legend': {'position': 'sw', 'backgroundOpacity': 0.7}, 'xaxis': {'tickDecimals': 0}, 'yaxis': {'tickDecimals': 0}, }, } return 'json.txt', {"json": 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_ignore.value) AS num_ignore, 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_ignore ON (item_ignore.report=report.id AND item_ignore.item=item_fixture.item AND item_ignore.name='status' AND item_ignore.value='ignore') 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_ignore, total_failure, total_error = 0, 0, 0, 0 for fixture, file, num_success, num_ignore, num_failure, num_error in cursor: fixtures.append({'name': fixture, 'num_success': num_success, 'num_ignore': num_ignore, 'num_error': num_error, 'num_failure': num_failure}) total_success += num_success total_ignore += num_ignore total_failure += num_failure total_error += num_error if file: fixtures[-1]['href'] = req.href.browser(config.path, file) # For each fixture, get a list of tests that don't succeed for fixture in fixtures: cursor.execute(""" SELECT item_status.value AS status, item_name.value AS name, item_traceback.value AS traceback FROM bitten_report LEFT OUTER JOIN bitten_report_item AS item_fixture ON (item_fixture.report=bitten_report.id AND item_fixture.name='fixture') LEFT OUTER JOIN bitten_report_item AS item_status ON (item_status.report=bitten_report.id AND item_status.item=item_fixture.item AND item_status.name='status') LEFT OUTER JOIN bitten_report_item AS item_name ON (item_name.report=bitten_report.id AND item_name.item=item_fixture.item AND item_name.name='name') LEFT OUTER JOIN bitten_report_item AS item_traceback ON (item_traceback.report=bitten_report.id AND item_traceback.item=item_fixture.item AND item_traceback.name='traceback') WHERE category='test' AND build=%s AND step=%s AND item_status.value<>'success' AND item_fixture.value=%s""", (build.id, step.name, fixture['name'])) failures = [] for status, name, traceback in cursor: # use the fixture name if a name isn't supplied for the # individual test if not name: name = fixture['name'] failures.append({'status': status, 'name': name, 'traceback': traceback}) if failures: fixture['failures'] = failures data = {'fixtures': fixtures, 'totals': {'success': total_success, 'ignore': total_ignore, 'failure': total_failure, 'error': total_error} } return 'bitten_summary_tests.html', data Bitten-0.6/bitten/report/tests/000755 000765 000120 00000000000 11536424600 017021 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/report/tests/__init__.py000644 000765 000120 00000001226 11453043212 021125 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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, lint, testing def suite(): suite = unittest.TestSuite() suite.addTest(coverage.suite()) suite.addTest(lint.suite()) suite.addTest(testing.suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/report/tests/coverage.py000644 000765 000120 00000010770 11453043212 021165 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) generator = TestCoverageChartGenerator(self.env) template, data = generator.generate_chart_data(req, config, 'coverage') self.assertEqual('json.txt', template) data = data['json'] self.assertEqual('Test Coverage', data['title']) actual_data = data['data'] self.assertEqual('Lines of code', actual_data[0]['label']) self.assertEqual('Coverage', actual_data[1]['label']) def test_single_platform(self): config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) 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('json.txt', template) data = data['json'] self.assertEqual('Test Coverage', data['title']) actual_data = data['data'] self.assertEqual('123', actual_data[0]['data'][0][0]) self.assertEqual('Lines of code', actual_data[0]['label']) self.assertEqual(12, actual_data[0]['data'][0][1]) self.assertEqual('Coverage', actual_data[1]['label']) self.assertEqual(3, actual_data[1]['data'][0][1]) def test_multi_platform(self): config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) 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('json.txt', template) data = data['json'] self.assertEqual('Test Coverage', data['title']) actual_data = data['data'] self.assertEqual('123', actual_data[0]['data'][0][0]) self.assertEqual('Lines of code', actual_data[0]['label']) self.assertEqual(12, actual_data[0]['data'][0][1]) self.assertEqual('Coverage', actual_data[1]['label']) self.assertEqual(6, actual_data[1]['data'][0][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') Bitten-0.6/bitten/report/tests/lint.py000644 000765 000120 00000012721 11453043212 020336 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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.lint import PyLintChartGenerator class PyLintChartGeneratorTestCase(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 = PyLintChartGenerator(self.env) self.assertEqual(['lint'], generator.get_supported_categories()) def test_no_reports(self): req = Mock() config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) generator = PyLintChartGenerator(self.env) template, data = generator.generate_chart_data(req, config, 'lint') self.assertEqual('json.txt', template) data = data['json'] self.assertEqual('Lint Problems by Type', data['title']) actual_data = data['data'] self.assertEqual('Total Problems', actual_data[0]['label']) self.assertEqual('Convention', actual_data[1]['label']) self.assertEqual('Error', actual_data[2]['label']) self.assertEqual('Refactor', actual_data[3]['label']) self.assertEqual('Warning', actual_data[4]['label']) def test_single_platform(self): config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) 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='lint') report.items += [{'category': 'convention'}, {'category': 'warning'}, {'category': 'error'}, {'category': 'refactor'}, {'category': 'warning'}, {'category': 'error'}, {'category': 'refactor'}, {'category': 'error'}, {'category': 'refactor'}, {'category': 'refactor'}] report.insert() req = Mock() generator = PyLintChartGenerator(self.env) template, data = generator.generate_chart_data(req, config, 'lint') self.assertEqual('json.txt', template) data = data['json'] self.assertEqual('Lint Problems by Type', data['title']) actual_data = data['data'] self.assertEqual('123', actual_data[0]['data'][0][0]) self.assertEqual('Total Problems', actual_data[0]['label']) self.assertEqual(10, actual_data[0]['data'][0][1]) self.assertEqual('Convention', actual_data[1]['label']) self.assertEqual(1, actual_data[1]['data'][0][1]) self.assertEqual('Error', actual_data[2]['label']) self.assertEqual(3, actual_data[2]['data'][0][1]) self.assertEqual('Refactor', actual_data[3]['label']) self.assertEqual(4, actual_data[3]['data'][0][1]) self.assertEqual('Warning', actual_data[4]['label']) self.assertEqual(2, actual_data[4]['data'][0][1]) def test_multi_platform(self): config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) 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='lint') report.items += [{'category': 'error'}, {'category': 'refactor'}] 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='lint') report.items += [{'category': 'convention'}, {'category': 'warning'}] report.insert() req = Mock() generator = PyLintChartGenerator(self.env) template, data = generator.generate_chart_data(req, config, 'lint') self.assertEqual('json.txt', template) data = data['json'] self.assertEqual('Lint Problems by Type', data['title']) actual_data = data['data'] self.assertEqual('123', actual_data[0]['data'][0][0]) self.assertEqual('Total Problems', actual_data[0]['label']) self.assertEqual(4, actual_data[0]['data'][0][1]) self.assertEqual('Convention', actual_data[1]['label']) self.assertEqual(1, actual_data[1]['data'][0][1]) self.assertEqual('Error', actual_data[2]['label']) self.assertEqual(1, actual_data[2]['data'][0][1]) self.assertEqual('Refactor', actual_data[3]['label']) self.assertEqual(1, actual_data[3]['data'][0][1]) self.assertEqual('Warning', actual_data[4]['label']) self.assertEqual(1, actual_data[4]['data'][0][1]) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(PyLintChartGeneratorTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/report/tests/testing.py000644 000765 000120 00000016444 11453043212 021053 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 trac.web.href import Href from bitten.model import * from bitten.report.testing import TestResultsChartGenerator, \ TestResultsSummarizer 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', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) generator = TestResultsChartGenerator(self.env) template, data = generator.generate_chart_data(req, config, 'test') self.assertEqual('json.txt', template) data = data['json'] self.assertEqual('Unit Tests', data['title']) actual_data = data['data'] self.assertEqual([], actual_data[0]['data']) self.assertEqual('Total', actual_data[0]['label']) self.assertEqual('Failures', actual_data[1]['label']) def test_single_platform(self): config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) 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('json.txt', template) data = data['json'] self.assertEqual('Unit Tests', data['title']) actual_data = data['data'] self.assertEqual('123', actual_data[0]['data'][0][0]) self.assertEqual('Total', actual_data[0]['label']) self.assertEqual(3, actual_data[0]['data'][0][1]) self.assertEqual('Failures', actual_data[1]['label']) self.assertEqual(1, actual_data[1]['data'][0][1]) def test_multi_platform(self): config = Mock(name='trunk', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) 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('json.txt', template) data = data['json'] self.assertEqual('Unit Tests', data['title']) actual_data = data['data'] self.assertEqual('123', actual_data[0]['data'][0][0]) self.assertEqual('Total', actual_data[0]['label']) self.assertEqual(3, actual_data[0]['data'][0][1]) self.assertEqual('123', actual_data[1]['data'][0][0]) self.assertEqual('Failures', actual_data[1]['label']) self.assertEqual(2, actual_data[1]['data'][0][1]) self.assertEqual('123', actual_data[2]['data'][0][0]) self.assertEqual('Ignored', actual_data[2]['label']) self.assertEqual(0, actual_data[2]['data'][0][1]) class TestResultsSummarizerTestCase(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_testcase_errors_and_failures(self): config = Mock(name='trunk', path='/somewhere', min_rev_time=lambda env: 0, max_rev_time=lambda env: 1000) step = Mock(name='foo') build = Build(self.env, config=config.name, platform=1, rev=123, rev_time=42) build.insert() report = Report(self.env, build=build.id, step=step.name, category='test') report.items += [{'fixture': 'test_foo', 'name': 'foo', 'file': 'foo.c', 'type': 'test', 'status': 'success'}, {'fixture': 'test_bar', 'name': 'bar', 'file': 'bar.c', 'type': 'test', 'status': 'error', 'traceback': 'Error traceback'}, {'fixture': 'test_baz', 'name': 'baz', 'file': 'baz.c', 'type': 'test', 'status': 'failure', 'traceback': 'Failure reason'}] report.insert() req = Mock(href=Href('trac')) generator = TestResultsSummarizer(self.env) template, data = generator.render_summary(req, config, build, step, 'test') self.assertEquals('bitten_summary_tests.html', template) self.assertEquals(data['totals'], {'ignore': 0, 'failure': 1, 'success': 1, 'error': 1}) for fixture in data['fixtures']: if fixture.has_key('failures'): if fixture['failures'][0]['status'] == 'error': self.assertEquals('test_bar', fixture['name']) self.assertEquals('Error traceback', fixture['failures'][0]['traceback']) if fixture['failures'][0]['status'] == 'failure': self.assertEquals('test_baz', fixture['name']) self.assertEquals('Failure reason', fixture['failures'][0]['traceback']) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestResultsChartGeneratorTestCase)) suite.addTest(unittest.makeSuite(TestResultsSummarizerTestCase)) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/htdocs/admin.css000644 000765 000120 00000000571 11234443127 017435 0ustar00simonadmin000000 000000 table.form th { text-align: right; } div.platforms h3 { margin-top: 3em; } table#platformlist { width: 67%; } table#platformlist td ul { list-style: none; margin: 0; padding: 0; } fieldset#newplatform { float:right; width: 30%; margin-top: -0.75em; padding-top: 0; } dl.help { color: #666; font-size: 90%; margin: 1em .5em; } dl.help dt { font-weight: bold; } Bitten-0.6/bitten/htdocs/bitten.css000644 000765 000120 00000020640 11364623604 017635 0ustar00simonadmin000000 000000 /* 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; clear: both; } #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; margin-left: 0.5em; } #content.build #builds { clear: none; margin-top: 2em; table-layout: fixed; width: auto; } #content.build #builds tbody th, #content.build #builds tbody td { background: #fff; width: 40em; } #content.build #builds th.chgset { width: 5em; } #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.in-progress, #content.build #builds ul.steps li.failed { border: 1px solid; margin: 1px 0; padding: 0 2px 0 12px; } #content.build #builds ul.steps li.in-progress { background: #dd9; border-color: #966; color: #993; } #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 th, #content.build table.tests tr.failed td { font-weight: bold; } #content.build table.tests tr.failed :link, #content.build table.tests tr.failed :visited { color: #b00 } /* collapsible failure details */ #content.build table.tests tr th p { margin: 0; padding: 0; text-align: left; } #content.build table.tests tr th p.details { margin: 0; padding-left: 4px; padding-top: 5px; text-align: left; font-weight: normal; } #content.build table.tests tr th p.details span { white-space: pre; font-family: monospace; font-weight: normal; font-size: smaller; color: #666; } #content.build table.tests .fixture { display: inline-block; } #content.build table.tests tr.failed th .fixture a { background: url(../common/expanded.png) 50% 50% no-repeat; padding-left: 16px; } #content.build table.tests tr.failed th.collapsed .fixture a { background-image: url(../common/collapsed.png); } #content.build table.tests tr.failed th.collapsed p.details { display: none; } #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; } #mainnav .bitteninprogress, #mainnav .bitteninprogress :hover { border-right: 4px solid #edd400 !important; } #mainnav .bittencompleted, #mainnav .bittencompleted :hover { border-right: 4px solid #0b0 !important; } #mainnav .bittenfailed, #mainnav .bittenfailed :hover { border-right: 4px hidden !important; background-color: #d99; } #mainnav .bittenpending, #mainnav .bittenpending :hover { border-right: 4px hidden !important; } .step-toc { padding: .5em 1em; margin: 1em 3em 2em 1em; float: right; border: 1px outset #ddc; background: #ffd; font-size: 85%; position: relative; } .step-toc h4 { font-size: 12px; margin: 0 } .step-toc ul, .step-toc ol { list-style: none; padding: 0 0 0 1.2em; margin: 0 } .step-toc li { margin: 0; padding: 0 } .step-toc .active { background: #ff9; position: relative; } .step-toc li.failed a { color: #a00; font-weight: bold; } div.tickLabel { font-size: 10px; } td.legendLabel { font-size: 10px; } Bitten-0.6/bitten/htdocs/bitten_build.png000755 000765 000120 00000000454 10656040024 021004 0ustar00simonadmin000000 000000 PNG  IHDR [AsBITO$PLTEϤX|BfffsZ)ZZZ:::9* Z tRNS P& pHYs  ~!tEXtSoftwareMacromedia Fireworks 4.0&'utEXtCreation Time07/01/05J8IDATxc``TRdQPƐ(Eg9HlVTdX 2pع`a |IENDB`Bitten-0.6/bitten/htdocs/bitten_buildf.png000644 000765 000120 00000000441 10656040024 021143 0ustar00simonadmin000000 000000 PNG  IHDR agAMAOX2tEXtSoftwareAdobe ImageReadyqe<3PLTEZZZsZ)9* |BϤX:::iifff_tRNS%bWIDATx4[DQgW1=`"xQ63NĴ ]TG\FnԻk5X4 "x-IENDB`Bitten-0.6/bitten/htdocs/bitten_coverage.css000644 000765 000120 00000000262 11234725105 021501 0ustar00simonadmin000000 000000 /* Code coverage file annotations */ table.code th.coverage { width: 4em; } table.code th.covered { background-color: #6F6; } table.code th.uncovered { background-color: #f66; } Bitten-0.6/bitten/htdocs/excanvas.js000644 000765 000120 00000121470 11364623604 020007 0ustar00simonadmin000000 000000 // Copyright 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Known Issues: // // * Patterns only support repeat. // * Radial gradient are not implemented. The VML version of these look very // different from the canvas one. // * Clipping paths are not implemented. // * Coordsize. The width and height attribute have higher priority than the // width and height style values which isn't correct. // * Painting mode isn't implemented. // * Canvas width/height should is using content-box by default. IE in // Quirks mode will draw the canvas using border-box. Either change your // doctype to HTML5 // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype) // or use Box Sizing Behavior from WebFX // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html) // * Non uniform scaling does not correctly scale strokes. // * Filling very large shapes (above 5000 points) is buggy. // * Optimize. There is always room for speed improvements. // Only add this code if we do not already have a canvas implementation if (!document.createElement('canvas').getContext) { (function() { // alias some functions to make (compiled) code shorter var m = Math; var mr = m.round; var ms = m.sin; var mc = m.cos; var abs = m.abs; var sqrt = m.sqrt; // this is used for sub pixel precision var Z = 10; var Z2 = Z / 2; /** * This funtion is assigned to the elements as element.getContext(). * @this {HTMLElement} * @return {CanvasRenderingContext2D_} */ function getContext() { return this.context_ || (this.context_ = new CanvasRenderingContext2D_(this)); } var slice = Array.prototype.slice; /** * Binds a function to an object. The returned function will always use the * passed in {@code obj} as {@code this}. * * Example: * * g = bind(f, obj, a, b) * g(c, d) // will do f.call(obj, a, b, c, d) * * @param {Function} f The function to bind the object to * @param {Object} obj The object that should act as this when the function * is called * @param {*} var_args Rest arguments that will be used as the initial * arguments when the function is called * @return {Function} A new function that has bound this */ function bind(f, obj, var_args) { var a = slice.call(arguments, 2); return function() { return f.apply(obj, a.concat(slice.call(arguments))); }; } function encodeHtmlAttribute(s) { return String(s).replace(/&/g, '&').replace(/"/g, '"'); } function addNamespacesAndStylesheet(doc) { // create xmlns if (!doc.namespaces['g_vml_']) { doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml', '#default#VML'); } if (!doc.namespaces['g_o_']) { doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office', '#default#VML'); } // Setup default CSS. Only add one style sheet per document if (!doc.styleSheets['ex_canvas_']) { var ss = doc.createStyleSheet(); ss.owningElement.id = 'ex_canvas_'; ss.cssText = 'canvas{display:inline-block;overflow:hidden;' + // default size is 300x150 in Gecko and Opera 'text-align:left;width:300px;height:150px}'; } } // Add namespaces and stylesheet at startup. addNamespacesAndStylesheet(document); var G_vmlCanvasManager_ = { init: function(opt_doc) { if (/MSIE/.test(navigator.userAgent) && !window.opera) { var doc = opt_doc || document; // Create a dummy element so that IE will allow canvas elements to be // recognized. doc.createElement('canvas'); doc.attachEvent('onreadystatechange', bind(this.init_, this, doc)); } }, init_: function(doc) { // find all canvas elements var els = doc.getElementsByTagName('canvas'); for (var i = 0; i < els.length; i++) { this.initElement(els[i]); } }, /** * Public initializes a canvas element so that it can be used as canvas * element from now on. This is called automatically before the page is * loaded but if you are creating elements using createElement you need to * make sure this is called on the element. * @param {HTMLElement} el The canvas element to initialize. * @return {HTMLElement} the element that was created. */ initElement: function(el) { if (!el.getContext) { el.getContext = getContext; // Add namespaces and stylesheet to document of the element. addNamespacesAndStylesheet(el.ownerDocument); // Remove fallback content. There is no way to hide text nodes so we // just remove all childNodes. We could hide all elements and remove // text nodes but who really cares about the fallback content. el.innerHTML = ''; // do not use inline function because that will leak memory el.attachEvent('onpropertychange', onPropertyChange); el.attachEvent('onresize', onResize); var attrs = el.attributes; if (attrs.width && attrs.width.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setWidth_(attrs.width.nodeValue); el.style.width = attrs.width.nodeValue + 'px'; } else { el.width = el.clientWidth; } if (attrs.height && attrs.height.specified) { // TODO: use runtimeStyle and coordsize // el.getContext().setHeight_(attrs.height.nodeValue); el.style.height = attrs.height.nodeValue + 'px'; } else { el.height = el.clientHeight; } //el.getContext().setCoordsize_() } return el; } }; function onPropertyChange(e) { var el = e.srcElement; switch (e.propertyName) { case 'width': el.getContext().clearRect(); el.style.width = el.attributes.width.nodeValue + 'px'; // In IE8 this does not trigger onresize. el.firstChild.style.width = el.clientWidth + 'px'; break; case 'height': el.getContext().clearRect(); el.style.height = el.attributes.height.nodeValue + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; break; } } function onResize(e) { var el = e.srcElement; if (el.firstChild) { el.firstChild.style.width = el.clientWidth + 'px'; el.firstChild.style.height = el.clientHeight + 'px'; } } G_vmlCanvasManager_.init(); // precompute "00" to "FF" var decToHex = []; for (var i = 0; i < 16; i++) { for (var j = 0; j < 16; j++) { decToHex[i * 16 + j] = i.toString(16) + j.toString(16); } } function createMatrixIdentity() { return [ [1, 0, 0], [0, 1, 0], [0, 0, 1] ]; } function matrixMultiply(m1, m2) { var result = createMatrixIdentity(); for (var x = 0; x < 3; x++) { for (var y = 0; y < 3; y++) { var sum = 0; for (var z = 0; z < 3; z++) { sum += m1[x][z] * m2[z][y]; } result[x][y] = sum; } } return result; } function copyState(o1, o2) { o2.fillStyle = o1.fillStyle; o2.lineCap = o1.lineCap; o2.lineJoin = o1.lineJoin; o2.lineWidth = o1.lineWidth; o2.miterLimit = o1.miterLimit; o2.shadowBlur = o1.shadowBlur; o2.shadowColor = o1.shadowColor; o2.shadowOffsetX = o1.shadowOffsetX; o2.shadowOffsetY = o1.shadowOffsetY; o2.strokeStyle = o1.strokeStyle; o2.globalAlpha = o1.globalAlpha; o2.font = o1.font; o2.textAlign = o1.textAlign; o2.textBaseline = o1.textBaseline; o2.arcScaleX_ = o1.arcScaleX_; o2.arcScaleY_ = o1.arcScaleY_; o2.lineScale_ = o1.lineScale_; } var colorData = { aliceblue: '#F0F8FF', antiquewhite: '#FAEBD7', aquamarine: '#7FFFD4', azure: '#F0FFFF', beige: '#F5F5DC', bisque: '#FFE4C4', black: '#000000', blanchedalmond: '#FFEBCD', blueviolet: '#8A2BE2', brown: '#A52A2A', burlywood: '#DEB887', cadetblue: '#5F9EA0', chartreuse: '#7FFF00', chocolate: '#D2691E', coral: '#FF7F50', cornflowerblue: '#6495ED', cornsilk: '#FFF8DC', crimson: '#DC143C', cyan: '#00FFFF', darkblue: '#00008B', darkcyan: '#008B8B', darkgoldenrod: '#B8860B', darkgray: '#A9A9A9', darkgreen: '#006400', darkgrey: '#A9A9A9', darkkhaki: '#BDB76B', darkmagenta: '#8B008B', darkolivegreen: '#556B2F', darkorange: '#FF8C00', darkorchid: '#9932CC', darkred: '#8B0000', darksalmon: '#E9967A', darkseagreen: '#8FBC8F', darkslateblue: '#483D8B', darkslategray: '#2F4F4F', darkslategrey: '#2F4F4F', darkturquoise: '#00CED1', darkviolet: '#9400D3', deeppink: '#FF1493', deepskyblue: '#00BFFF', dimgray: '#696969', dimgrey: '#696969', dodgerblue: '#1E90FF', firebrick: '#B22222', floralwhite: '#FFFAF0', forestgreen: '#228B22', gainsboro: '#DCDCDC', ghostwhite: '#F8F8FF', gold: '#FFD700', goldenrod: '#DAA520', grey: '#808080', greenyellow: '#ADFF2F', honeydew: '#F0FFF0', hotpink: '#FF69B4', indianred: '#CD5C5C', indigo: '#4B0082', ivory: '#FFFFF0', khaki: '#F0E68C', lavender: '#E6E6FA', lavenderblush: '#FFF0F5', lawngreen: '#7CFC00', lemonchiffon: '#FFFACD', lightblue: '#ADD8E6', lightcoral: '#F08080', lightcyan: '#E0FFFF', lightgoldenrodyellow: '#FAFAD2', lightgreen: '#90EE90', lightgrey: '#D3D3D3', lightpink: '#FFB6C1', lightsalmon: '#FFA07A', lightseagreen: '#20B2AA', lightskyblue: '#87CEFA', lightslategray: '#778899', lightslategrey: '#778899', lightsteelblue: '#B0C4DE', lightyellow: '#FFFFE0', limegreen: '#32CD32', linen: '#FAF0E6', magenta: '#FF00FF', mediumaquamarine: '#66CDAA', mediumblue: '#0000CD', mediumorchid: '#BA55D3', mediumpurple: '#9370DB', mediumseagreen: '#3CB371', mediumslateblue: '#7B68EE', mediumspringgreen: '#00FA9A', mediumturquoise: '#48D1CC', mediumvioletred: '#C71585', midnightblue: '#191970', mintcream: '#F5FFFA', mistyrose: '#FFE4E1', moccasin: '#FFE4B5', navajowhite: '#FFDEAD', oldlace: '#FDF5E6', olivedrab: '#6B8E23', orange: '#FFA500', orangered: '#FF4500', orchid: '#DA70D6', palegoldenrod: '#EEE8AA', palegreen: '#98FB98', paleturquoise: '#AFEEEE', palevioletred: '#DB7093', papayawhip: '#FFEFD5', peachpuff: '#FFDAB9', peru: '#CD853F', pink: '#FFC0CB', plum: '#DDA0DD', powderblue: '#B0E0E6', rosybrown: '#BC8F8F', royalblue: '#4169E1', saddlebrown: '#8B4513', salmon: '#FA8072', sandybrown: '#F4A460', seagreen: '#2E8B57', seashell: '#FFF5EE', sienna: '#A0522D', skyblue: '#87CEEB', slateblue: '#6A5ACD', slategray: '#708090', slategrey: '#708090', snow: '#FFFAFA', springgreen: '#00FF7F', steelblue: '#4682B4', tan: '#D2B48C', thistle: '#D8BFD8', tomato: '#FF6347', turquoise: '#40E0D0', violet: '#EE82EE', wheat: '#F5DEB3', whitesmoke: '#F5F5F5', yellowgreen: '#9ACD32' }; function getRgbHslContent(styleString) { var start = styleString.indexOf('(', 3); var end = styleString.indexOf(')', start + 1); var parts = styleString.substring(start + 1, end).split(','); // add alpha if needed if (parts.length == 4 && styleString.substr(3, 1) == 'a') { alpha = Number(parts[3]); } else { parts[3] = 1; } return parts; } function percent(s) { return parseFloat(s) / 100; } function clamp(v, min, max) { return Math.min(max, Math.max(min, v)); } function hslToRgb(parts){ var r, g, b; h = parseFloat(parts[0]) / 360 % 360; if (h < 0) h++; s = clamp(percent(parts[1]), 0, 1); l = clamp(percent(parts[2]), 0, 1); if (s == 0) { r = g = b = l; // achromatic } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hueToRgb(p, q, h + 1 / 3); g = hueToRgb(p, q, h); b = hueToRgb(p, q, h - 1 / 3); } return '#' + decToHex[Math.floor(r * 255)] + decToHex[Math.floor(g * 255)] + decToHex[Math.floor(b * 255)]; } function hueToRgb(m1, m2, h) { if (h < 0) h++; if (h > 1) h--; if (6 * h < 1) return m1 + (m2 - m1) * 6 * h; else if (2 * h < 1) return m2; else if (3 * h < 2) return m1 + (m2 - m1) * (2 / 3 - h) * 6; else return m1; } function processStyle(styleString) { var str, alpha = 1; styleString = String(styleString); if (styleString.charAt(0) == '#') { str = styleString; } else if (/^rgb/.test(styleString)) { var parts = getRgbHslContent(styleString); var str = '#', n; for (var i = 0; i < 3; i++) { if (parts[i].indexOf('%') != -1) { n = Math.floor(percent(parts[i]) * 255); } else { n = Number(parts[i]); } str += decToHex[clamp(n, 0, 255)]; } alpha = parts[3]; } else if (/^hsl/.test(styleString)) { var parts = getRgbHslContent(styleString); str = hslToRgb(parts); alpha = parts[3]; } else { str = colorData[styleString] || styleString; } return {color: str, alpha: alpha}; } var DEFAULT_STYLE = { style: 'normal', variant: 'normal', weight: 'normal', size: 10, family: 'sans-serif' }; // Internal text style cache var fontStyleCache = {}; function processFontStyle(styleString) { if (fontStyleCache[styleString]) { return fontStyleCache[styleString]; } var el = document.createElement('div'); var style = el.style; try { style.font = styleString; } catch (ex) { // Ignore failures to set to invalid font. } return fontStyleCache[styleString] = { style: style.fontStyle || DEFAULT_STYLE.style, variant: style.fontVariant || DEFAULT_STYLE.variant, weight: style.fontWeight || DEFAULT_STYLE.weight, size: style.fontSize || DEFAULT_STYLE.size, family: style.fontFamily || DEFAULT_STYLE.family }; } function getComputedStyle(style, element) { var computedStyle = {}; for (var p in style) { computedStyle[p] = style[p]; } // Compute the size var canvasFontSize = parseFloat(element.currentStyle.fontSize), fontSize = parseFloat(style.size); if (typeof style.size == 'number') { computedStyle.size = style.size; } else if (style.size.indexOf('px') != -1) { computedStyle.size = fontSize; } else if (style.size.indexOf('em') != -1) { computedStyle.size = canvasFontSize * fontSize; } else if(style.size.indexOf('%') != -1) { computedStyle.size = (canvasFontSize / 100) * fontSize; } else if (style.size.indexOf('pt') != -1) { computedStyle.size = fontSize / .75; } else { computedStyle.size = canvasFontSize; } // Different scaling between normal text and VML text. This was found using // trial and error to get the same size as non VML text. computedStyle.size *= 0.981; return computedStyle; } function buildStyle(style) { return style.style + ' ' + style.variant + ' ' + style.weight + ' ' + style.size + 'px ' + style.family; } function processLineCap(lineCap) { switch (lineCap) { case 'butt': return 'flat'; case 'round': return 'round'; case 'square': default: return 'square'; } } /** * This class implements CanvasRenderingContext2D interface as described by * the WHATWG. * @param {HTMLElement} surfaceElement The element that the 2D context should * be associated with */ function CanvasRenderingContext2D_(surfaceElement) { this.m_ = createMatrixIdentity(); this.mStack_ = []; this.aStack_ = []; this.currentPath_ = []; // Canvas context properties this.strokeStyle = '#000'; this.fillStyle = '#000'; this.lineWidth = 1; this.lineJoin = 'miter'; this.lineCap = 'butt'; this.miterLimit = Z * 1; this.globalAlpha = 1; this.font = '10px sans-serif'; this.textAlign = 'left'; this.textBaseline = 'alphabetic'; this.canvas = surfaceElement; var el = surfaceElement.ownerDocument.createElement('div'); el.style.width = surfaceElement.clientWidth + 'px'; el.style.height = surfaceElement.clientHeight + 'px'; el.style.overflow = 'hidden'; el.style.position = 'absolute'; surfaceElement.appendChild(el); this.element_ = el; this.arcScaleX_ = 1; this.arcScaleY_ = 1; this.lineScale_ = 1; } var contextPrototype = CanvasRenderingContext2D_.prototype; contextPrototype.clearRect = function() { if (this.textMeasureEl_) { this.textMeasureEl_.removeNode(true); this.textMeasureEl_ = null; } this.element_.innerHTML = ''; }; contextPrototype.beginPath = function() { // TODO: Branch current matrix so that save/restore has no effect // as per safari docs. this.currentPath_ = []; }; contextPrototype.moveTo = function(aX, aY) { var p = this.getCoords_(aX, aY); this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.lineTo = function(aX, aY) { var p = this.getCoords_(aX, aY); this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y}); this.currentX_ = p.x; this.currentY_ = p.y; }; contextPrototype.bezierCurveTo = function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) { var p = this.getCoords_(aX, aY); var cp1 = this.getCoords_(aCP1x, aCP1y); var cp2 = this.getCoords_(aCP2x, aCP2y); bezierCurveTo(this, cp1, cp2, p); }; // Helper function that takes the already fixed cordinates. function bezierCurveTo(self, cp1, cp2, p) { self.currentPath_.push({ type: 'bezierCurveTo', cp1x: cp1.x, cp1y: cp1.y, cp2x: cp2.x, cp2y: cp2.y, x: p.x, y: p.y }); self.currentX_ = p.x; self.currentY_ = p.y; } contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) { // the following is lifted almost directly from // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes var cp = this.getCoords_(aCPx, aCPy); var p = this.getCoords_(aX, aY); var cp1 = { x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_), y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_) }; var cp2 = { x: cp1.x + (p.x - this.currentX_) / 3.0, y: cp1.y + (p.y - this.currentY_) / 3.0 }; bezierCurveTo(this, cp1, cp2, p); }; contextPrototype.arc = function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) { aRadius *= Z; var arcType = aClockwise ? 'at' : 'wa'; var xStart = aX + mc(aStartAngle) * aRadius - Z2; var yStart = aY + ms(aStartAngle) * aRadius - Z2; var xEnd = aX + mc(aEndAngle) * aRadius - Z2; var yEnd = aY + ms(aEndAngle) * aRadius - Z2; // IE won't render arches drawn counter clockwise if xStart == xEnd. if (xStart == xEnd && !aClockwise) { xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something // that can be represented in binary } var p = this.getCoords_(aX, aY); var pStart = this.getCoords_(xStart, yStart); var pEnd = this.getCoords_(xEnd, yEnd); this.currentPath_.push({type: arcType, x: p.x, y: p.y, radius: aRadius, xStart: pStart.x, yStart: pStart.y, xEnd: pEnd.x, yEnd: pEnd.y}); }; contextPrototype.rect = function(aX, aY, aWidth, aHeight) { this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); }; contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.stroke(); this.currentPath_ = oldPath; }; contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) { var oldPath = this.currentPath_; this.beginPath(); this.moveTo(aX, aY); this.lineTo(aX + aWidth, aY); this.lineTo(aX + aWidth, aY + aHeight); this.lineTo(aX, aY + aHeight); this.closePath(); this.fill(); this.currentPath_ = oldPath; }; contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) { var gradient = new CanvasGradient_('gradient'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.x1_ = aX1; gradient.y1_ = aY1; return gradient; }; contextPrototype.createRadialGradient = function(aX0, aY0, aR0, aX1, aY1, aR1) { var gradient = new CanvasGradient_('gradientradial'); gradient.x0_ = aX0; gradient.y0_ = aY0; gradient.r0_ = aR0; gradient.x1_ = aX1; gradient.y1_ = aY1; gradient.r1_ = aR1; return gradient; }; contextPrototype.drawImage = function(image, var_args) { var dx, dy, dw, dh, sx, sy, sw, sh; // to find the original width we overide the width and height var oldRuntimeWidth = image.runtimeStyle.width; var oldRuntimeHeight = image.runtimeStyle.height; image.runtimeStyle.width = 'auto'; image.runtimeStyle.height = 'auto'; // get the original size var w = image.width; var h = image.height; // and remove overides image.runtimeStyle.width = oldRuntimeWidth; image.runtimeStyle.height = oldRuntimeHeight; if (arguments.length == 3) { dx = arguments[1]; dy = arguments[2]; sx = sy = 0; sw = dw = w; sh = dh = h; } else if (arguments.length == 5) { dx = arguments[1]; dy = arguments[2]; dw = arguments[3]; dh = arguments[4]; sx = sy = 0; sw = w; sh = h; } else if (arguments.length == 9) { sx = arguments[1]; sy = arguments[2]; sw = arguments[3]; sh = arguments[4]; dx = arguments[5]; dy = arguments[6]; dw = arguments[7]; dh = arguments[8]; } else { throw Error('Invalid number of arguments'); } var d = this.getCoords_(dx, dy); var w2 = sw / 2; var h2 = sh / 2; var vmlStr = []; var W = 10; var H = 10; // For some reason that I've now forgotten, using divs didn't work vmlStr.push(' ' , '', ''); this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join('')); }; contextPrototype.stroke = function(aFill) { var W = 10; var H = 10; // Divide the shape into chunks if it's too long because IE has a limit // somewhere for how long a VML shape can be. This simple division does // not work with fills, only strokes, unfortunately. var chunkSize = 5000; var min = {x: null, y: null}; var max = {x: null, y: null}; for (var j = 0; j < this.currentPath_.length; j += chunkSize) { var lineStr = []; var lineOpen = false; lineStr.push(''); if (!aFill) { appendStroke(this, lineStr); } else { appendFill(this, lineStr, min, max); } lineStr.push(''); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); } }; function appendStroke(ctx, lineStr) { var a = processStyle(ctx.strokeStyle); var color = a.color; var opacity = a.alpha * ctx.globalAlpha; var lineWidth = ctx.lineScale_ * ctx.lineWidth; // VML cannot correctly render a line if the width is less than 1px. // In that case, we dilute the color to make the line look thinner. if (lineWidth < 1) { opacity *= lineWidth; } lineStr.push( '' ); } function appendFill(ctx, lineStr, min, max) { var fillStyle = ctx.fillStyle; var arcScaleX = ctx.arcScaleX_; var arcScaleY = ctx.arcScaleY_; var width = max.x - min.x; var height = max.y - min.y; if (fillStyle instanceof CanvasGradient_) { // TODO: Gradients transformed with the transformation matrix. var angle = 0; var focus = {x: 0, y: 0}; // additional offset var shift = 0; // scale factor for offset var expansion = 1; if (fillStyle.type_ == 'gradient') { var x0 = fillStyle.x0_ / arcScaleX; var y0 = fillStyle.y0_ / arcScaleY; var x1 = fillStyle.x1_ / arcScaleX; var y1 = fillStyle.y1_ / arcScaleY; var p0 = ctx.getCoords_(x0, y0); var p1 = ctx.getCoords_(x1, y1); var dx = p1.x - p0.x; var dy = p1.y - p0.y; angle = Math.atan2(dx, dy) * 180 / Math.PI; // The angle should be a non-negative number. if (angle < 0) { angle += 360; } // Very small angles produce an unexpected result because they are // converted to a scientific notation string. if (angle < 1e-6) { angle = 0; } } else { var p0 = ctx.getCoords_(fillStyle.x0_, fillStyle.y0_); focus = { x: (p0.x - min.x) / width, y: (p0.y - min.y) / height }; width /= arcScaleX * Z; height /= arcScaleY * Z; var dimension = m.max(width, height); shift = 2 * fillStyle.r0_ / dimension; expansion = 2 * fillStyle.r1_ / dimension - shift; } // We need to sort the color stops in ascending order by offset, // otherwise IE won't interpret it correctly. var stops = fillStyle.colors_; stops.sort(function(cs1, cs2) { return cs1.offset - cs2.offset; }); var length = stops.length; var color1 = stops[0].color; var color2 = stops[length - 1].color; var opacity1 = stops[0].alpha * ctx.globalAlpha; var opacity2 = stops[length - 1].alpha * ctx.globalAlpha; var colors = []; for (var i = 0; i < length; i++) { var stop = stops[i]; colors.push(stop.offset * expansion + shift + ' ' + stop.color); } // When colors attribute is used, the meanings of opacity and o:opacity2 // are reversed. lineStr.push(''); } else if (fillStyle instanceof CanvasPattern_) { if (width && height) { var deltaLeft = -min.x; var deltaTop = -min.y; lineStr.push(''); } } else { var a = processStyle(ctx.fillStyle); var color = a.color; var opacity = a.alpha * ctx.globalAlpha; lineStr.push(''); } } contextPrototype.fill = function() { this.stroke(true); }; contextPrototype.closePath = function() { this.currentPath_.push({type: 'close'}); }; /** * @private */ contextPrototype.getCoords_ = function(aX, aY) { var m = this.m_; return { x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2, y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2 }; }; contextPrototype.save = function() { var o = {}; copyState(this, o); this.aStack_.push(o); this.mStack_.push(this.m_); this.m_ = matrixMultiply(createMatrixIdentity(), this.m_); }; contextPrototype.restore = function() { if (this.aStack_.length) { copyState(this.aStack_.pop(), this); this.m_ = this.mStack_.pop(); } }; function matrixIsFinite(m) { return isFinite(m[0][0]) && isFinite(m[0][1]) && isFinite(m[1][0]) && isFinite(m[1][1]) && isFinite(m[2][0]) && isFinite(m[2][1]); } function setM(ctx, m, updateLineScale) { if (!matrixIsFinite(m)) { return; } ctx.m_ = m; if (updateLineScale) { // Get the line scale. // Determinant of this.m_ means how much the area is enlarged by the // transformation. So its square root can be used as a scale factor // for width. var det = m[0][0] * m[1][1] - m[0][1] * m[1][0]; ctx.lineScale_ = sqrt(abs(det)); } } contextPrototype.translate = function(aX, aY) { var m1 = [ [1, 0, 0], [0, 1, 0], [aX, aY, 1] ]; setM(this, matrixMultiply(m1, this.m_), false); }; contextPrototype.rotate = function(aRot) { var c = mc(aRot); var s = ms(aRot); var m1 = [ [c, s, 0], [-s, c, 0], [0, 0, 1] ]; setM(this, matrixMultiply(m1, this.m_), false); }; contextPrototype.scale = function(aX, aY) { this.arcScaleX_ *= aX; this.arcScaleY_ *= aY; var m1 = [ [aX, 0, 0], [0, aY, 0], [0, 0, 1] ]; setM(this, matrixMultiply(m1, this.m_), true); }; contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) { var m1 = [ [m11, m12, 0], [m21, m22, 0], [dx, dy, 1] ]; setM(this, matrixMultiply(m1, this.m_), true); }; contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) { var m = [ [m11, m12, 0], [m21, m22, 0], [dx, dy, 1] ]; setM(this, m, true); }; /** * The text drawing function. * The maxWidth argument isn't taken in account, since no browser supports * it yet. */ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) { var m = this.m_, delta = 1000, left = 0, right = delta, offset = {x: 0, y: 0}, lineStr = []; var fontStyle = getComputedStyle(processFontStyle(this.font), this.element_); var fontStyleString = buildStyle(fontStyle); var elementStyle = this.element_.currentStyle; var textAlign = this.textAlign.toLowerCase(); switch (textAlign) { case 'left': case 'center': case 'right': break; case 'end': textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left'; break; case 'start': textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left'; break; default: textAlign = 'left'; } // 1.75 is an arbitrary number, as there is no info about the text baseline switch (this.textBaseline) { case 'hanging': case 'top': offset.y = fontStyle.size / 1.75; break; case 'middle': break; default: case null: case 'alphabetic': case 'ideographic': case 'bottom': offset.y = -fontStyle.size / 2.25; break; } switch(textAlign) { case 'right': left = delta; right = 0.05; break; case 'center': left = right = delta / 2; break; } var d = this.getCoords_(x + offset.x, y + offset.y); lineStr.push(''); if (stroke) { appendStroke(this, lineStr); } else { // TODO: Fix the min and max params. appendFill(this, lineStr, {x: -left, y: 0}, {x: right, y: fontStyle.size}); } var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' + m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0'; var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z); lineStr.push('', '', ''); this.element_.insertAdjacentHTML('beforeEnd', lineStr.join('')); }; contextPrototype.fillText = function(text, x, y, maxWidth) { this.drawText_(text, x, y, maxWidth, false); }; contextPrototype.strokeText = function(text, x, y, maxWidth) { this.drawText_(text, x, y, maxWidth, true); }; contextPrototype.measureText = function(text) { if (!this.textMeasureEl_) { var s = ''; this.element_.insertAdjacentHTML('beforeEnd', s); this.textMeasureEl_ = this.element_.lastChild; } var doc = this.element_.ownerDocument; this.textMeasureEl_.innerHTML = ''; this.textMeasureEl_.style.font = this.font; // Don't use innerHTML or innerText because they allow markup/whitespace. this.textMeasureEl_.appendChild(doc.createTextNode(text)); return {width: this.textMeasureEl_.offsetWidth}; }; /******** STUBS ********/ contextPrototype.clip = function() { // TODO: Implement }; contextPrototype.arcTo = function() { // TODO: Implement }; contextPrototype.createPattern = function(image, repetition) { return new CanvasPattern_(image, repetition); }; // Gradient / Pattern Stubs function CanvasGradient_(aType) { this.type_ = aType; this.x0_ = 0; this.y0_ = 0; this.r0_ = 0; this.x1_ = 0; this.y1_ = 0; this.r1_ = 0; this.colors_ = []; } CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) { aColor = processStyle(aColor); this.colors_.push({offset: aOffset, color: aColor.color, alpha: aColor.alpha}); }; function CanvasPattern_(image, repetition) { assertImageIsValid(image); switch (repetition) { case 'repeat': case null: case '': this.repetition_ = 'repeat'; break case 'repeat-x': case 'repeat-y': case 'no-repeat': this.repetition_ = repetition; break; default: throwException('SYNTAX_ERR'); } this.src_ = image.src; this.width_ = image.width; this.height_ = image.height; } function throwException(s) { throw new DOMException_(s); } function assertImageIsValid(img) { if (!img || img.nodeType != 1 || img.tagName != 'IMG') { throwException('TYPE_MISMATCH_ERR'); } if (img.readyState != 'complete') { throwException('INVALID_STATE_ERR'); } } function DOMException_(s) { this.code = this[s]; this.message = s +': DOM Exception ' + this.code; } var p = DOMException_.prototype = new Error; p.INDEX_SIZE_ERR = 1; p.DOMSTRING_SIZE_ERR = 2; p.HIERARCHY_REQUEST_ERR = 3; p.WRONG_DOCUMENT_ERR = 4; p.INVALID_CHARACTER_ERR = 5; p.NO_DATA_ALLOWED_ERR = 6; p.NO_MODIFICATION_ALLOWED_ERR = 7; p.NOT_FOUND_ERR = 8; p.NOT_SUPPORTED_ERR = 9; p.INUSE_ATTRIBUTE_ERR = 10; p.INVALID_STATE_ERR = 11; p.SYNTAX_ERR = 12; p.INVALID_MODIFICATION_ERR = 13; p.NAMESPACE_ERR = 14; p.INVALID_ACCESS_ERR = 15; p.VALIDATION_ERR = 16; p.TYPE_MISMATCH_ERR = 17; // set up externs G_vmlCanvasManager = G_vmlCanvasManager_; CanvasRenderingContext2D = CanvasRenderingContext2D_; CanvasGradient = CanvasGradient_; CanvasPattern = CanvasPattern_; DOMException = DOMException_; })(); } // if Bitten-0.6/bitten/htdocs/failure.png000644 000765 000120 00000000316 10656040024 017761 0ustar00simonadmin000000 000000 PNG  IHDRNgAMAOX2tEXtSoftwareAdobe ImageReadyqe<PLTEii0tRNS0IDATxD10A )iv ?f0>xA@20!BIIENDB`Bitten-0.6/bitten/htdocs/jquery.flot.js000644 000765 000120 00000257102 11364623604 020463 0ustar00simonadmin000000 000000 /* Javascript plotting library for jQuery, v. 0.6. * * Released under the MIT license by IOLA, December 2007. * */ // first an inline dependency, jquery.colorhelpers.js, we inline it here // for convenience /* Plugin for jQuery for working with colors. * * Version 1.0. * * Inspiration from jQuery color animation plugin by John Resig. * * Released under the MIT license by Ole Laursen, October 2009. * * Examples: * * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() * var c = $.color.extract($("#mydiv"), 'background-color'); * console.log(c.r, c.g, c.b, c.a); * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" * * Note that .scale() and .add() work in-place instead of returning * new objects. */ (function(){jQuery.color={};jQuery.color.make=function(E,D,B,C){var F={};F.r=E||0;F.g=D||0;F.b=B||0;F.a=C!=null?C:1;F.add=function(I,H){for(var G=0;G=1){return"rgb("+[F.r,F.g,F.b].join(",")+")"}else{return"rgba("+[F.r,F.g,F.b,F.a].join(",")+")"}};F.normalize=function(){function G(I,J,H){return JH?H:J)}F.r=G(0,parseInt(F.r),255);F.g=G(0,parseInt(F.g),255);F.b=G(0,parseInt(F.b),255);F.a=G(0,F.a,1);return F};F.clone=function(){return jQuery.color.make(F.r,F.b,F.g,F.a)};return F.normalize()};jQuery.color.extract=function(C,B){var D;do{D=C.css(B).toLowerCase();if(D!=""&&D!="transparent"){break}C=C.parent()}while(!jQuery.nodeName(C.get(0),"body"));if(D=="rgba(0, 0, 0, 0)"){D="transparent"}return jQuery.color.parse(D)};jQuery.color.parse=function(E){var D,B=jQuery.color.make;if(D=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10))}if(D=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10),parseFloat(D[4]))}if(D=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55)}if(D=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55,parseFloat(D[4]))}if(D=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(E)){return B(parseInt(D[1],16),parseInt(D[2],16),parseInt(D[3],16))}if(D=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(E)){return B(parseInt(D[1]+D[1],16),parseInt(D[2]+D[2],16),parseInt(D[3]+D[3],16))}var C=jQuery.trim(E).toLowerCase();if(C=="transparent"){return B(255,255,255,0)}else{D=A[C];return B(D[0],D[1],D[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(); // the actual Flot code (function($) { function Plot(placeholder, data_, options_, plugins) { // data is on the form: // [ series1, series2 ... ] // where series is either just the data as [ [x1, y1], [x2, y2], ... ] // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } var series = [], options = { // the color theme used for graphs colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], legend: { show: true, noColumns: 1, // number of colums in legend table labelFormatter: null, // fn: string -> string labelBoxBorderColor: "#ccc", // border color for the little label boxes container: null, // container (as jQuery object) to put legend in, null means default on top of graph position: "ne", // position of default legend container within plot margin: 5, // distance from grid edge to default legend container within plot backgroundColor: null, // null means auto-detect backgroundOpacity: 0.85 // set to 0 to avoid background }, xaxis: { mode: null, // null or "time" transform: null, // null or f: number -> number to transform axis inverseTransform: null, // if transform is set, this should be the inverse function min: null, // min. value to show, null means set automatically max: null, // max. value to show, null means set automatically autoscaleMargin: null, // margin in % to add if auto-setting min/max ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks tickFormatter: null, // fn: number -> string labelWidth: null, // size of tick labels in pixels labelHeight: null, // mode specific options tickDecimals: null, // no. of decimals, null means auto tickSize: null, // number or [number, "unit"] minTickSize: null, // number or [number, "unit"] monthNames: null, // list of names of months timeformat: null, // format string to use twelveHourClock: false // 12 or 24 time in time mode }, yaxis: { autoscaleMargin: 0.02 }, x2axis: { autoscaleMargin: null }, y2axis: { autoscaleMargin: 0.02 }, series: { points: { show: false, radius: 3, lineWidth: 2, // in pixels fill: true, fillColor: "#ffffff" }, lines: { // we don't put in show: false so we can see // whether lines were actively disabled lineWidth: 2, // in pixels fill: false, fillColor: null, steps: false }, bars: { show: false, lineWidth: 2, // in pixels barWidth: 1, // in units of the x axis fill: true, fillColor: null, align: "left", // or "center" horizontal: false // when horizontal, left is now top }, shadowSize: 3 }, grid: { show: true, aboveData: false, color: "#545454", // primary color used for outline and labels backgroundColor: null, // null for transparent, else color tickColor: "rgba(0,0,0,0.15)", // color used for the ticks labelMargin: 5, // in pixels borderWidth: 2, // in pixels borderColor: null, // set if different from the grid color markings: null, // array of ranges or fn: axes -> array of ranges markingsColor: "#f4f4f4", markingsLineWidth: 2, // interactive stuff clickable: false, hoverable: false, autoHighlight: true, // highlight in case mouse is near mouseActiveRadius: 10 // how far the mouse can be away to activate an item }, hooks: {} }, canvas = null, // the canvas for the plot itself overlay = null, // canvas for interactive stuff on top of plot eventHolder = null, // jQuery object that events should be bound to ctx = null, octx = null, axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} }, plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, canvasWidth = 0, canvasHeight = 0, plotWidth = 0, plotHeight = 0, hooks = { processOptions: [], processRawData: [], processDatapoints: [], draw: [], bindEvents: [], drawOverlay: [] }, plot = this; // public functions plot.setData = setData; plot.setupGrid = setupGrid; plot.draw = draw; plot.getPlaceholder = function() { return placeholder; }; plot.getCanvas = function() { return canvas; }; plot.getPlotOffset = function() { return plotOffset; }; plot.width = function () { return plotWidth; }; plot.height = function () { return plotHeight; }; plot.offset = function () { var o = eventHolder.offset(); o.left += plotOffset.left; o.top += plotOffset.top; return o; }; plot.getData = function() { return series; }; plot.getAxes = function() { return axes; }; plot.getOptions = function() { return options; }; plot.highlight = highlight; plot.unhighlight = unhighlight; plot.triggerRedrawOverlay = triggerRedrawOverlay; plot.pointOffset = function(point) { return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left), top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) }; }; // public attributes plot.hooks = hooks; // initialize initPlugins(plot); parseOptions(options_); constructCanvas(); setData(data_); setupGrid(); draw(); bindEvents(); function executeHooks(hook, args) { args = [plot].concat(args); for (var i = 0; i < hook.length; ++i) hook[i].apply(this, args); } function initPlugins() { for (var i = 0; i < plugins.length; ++i) { var p = plugins[i]; p.init(plot); if (p.options) $.extend(true, options, p.options); } } function parseOptions(opts) { $.extend(true, options, opts); if (options.grid.borderColor == null) options.grid.borderColor = options.grid.color; // backwards compatibility, to be removed in future if (options.xaxis.noTicks && options.xaxis.ticks == null) options.xaxis.ticks = options.xaxis.noTicks; if (options.yaxis.noTicks && options.yaxis.ticks == null) options.yaxis.ticks = options.yaxis.noTicks; if (options.grid.coloredAreas) options.grid.markings = options.grid.coloredAreas; if (options.grid.coloredAreasColor) options.grid.markingsColor = options.grid.coloredAreasColor; if (options.lines) $.extend(true, options.series.lines, options.lines); if (options.points) $.extend(true, options.series.points, options.points); if (options.bars) $.extend(true, options.series.bars, options.bars); if (options.shadowSize) options.series.shadowSize = options.shadowSize; for (var n in hooks) if (options.hooks[n] && options.hooks[n].length) hooks[n] = hooks[n].concat(options.hooks[n]); executeHooks(hooks.processOptions, [options]); } function setData(d) { series = parseData(d); fillInSeriesOptions(); processData(); } function parseData(d) { var res = []; for (var i = 0; i < d.length; ++i) { var s = $.extend(true, {}, options.series); if (d[i].data) { s.data = d[i].data; // move the data instead of deep-copy delete d[i].data; $.extend(true, s, d[i]); d[i].data = s.data; } else s.data = d[i]; res.push(s); } return res; } function axisSpecToRealAxis(obj, attr) { var a = obj[attr]; if (!a || a == 1) return axes[attr]; if (typeof a == "number") return axes[attr.charAt(0) + a + attr.slice(1)]; return a; // assume it's OK } function fillInSeriesOptions() { var i; // collect what we already got of colors var neededColors = series.length, usedColors = [], assignedColors = []; for (i = 0; i < series.length; ++i) { var sc = series[i].color; if (sc != null) { --neededColors; if (typeof sc == "number") assignedColors.push(sc); else usedColors.push($.color.parse(series[i].color)); } } // we might need to generate more colors if higher indices // are assigned for (i = 0; i < assignedColors.length; ++i) { neededColors = Math.max(neededColors, assignedColors[i] + 1); } // produce colors as needed var colors = [], variation = 0; i = 0; while (colors.length < neededColors) { var c; if (options.colors.length == i) // check degenerate case c = $.color.make(100, 100, 100); else c = $.color.parse(options.colors[i]); // vary color if needed var sign = variation % 2 == 1 ? -1 : 1; c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2) // FIXME: if we're getting to close to something else, // we should probably skip this one colors.push(c); ++i; if (i >= options.colors.length) { i = 0; ++variation; } } // fill in the options var colori = 0, s; for (i = 0; i < series.length; ++i) { s = series[i]; // assign colors if (s.color == null) { s.color = colors[colori].toString(); ++colori; } else if (typeof s.color == "number") s.color = colors[s.color].toString(); // turn on lines automatically in case nothing is set if (s.lines.show == null) { var v, show = true; for (v in s) if (s[v].show) { show = false; break; } if (show) s.lines.show = true; } // setup axes s.xaxis = axisSpecToRealAxis(s, "xaxis"); s.yaxis = axisSpecToRealAxis(s, "yaxis"); } } function processData() { var topSentry = Number.POSITIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY, i, j, k, m, length, s, points, ps, x, y, axis, val, f, p; for (axis in axes) { axes[axis].datamin = topSentry; axes[axis].datamax = bottomSentry; axes[axis].used = false; } function updateAxis(axis, min, max) { if (min < axis.datamin) axis.datamin = min; if (max > axis.datamax) axis.datamax = max; } for (i = 0; i < series.length; ++i) { s = series[i]; s.datapoints = { points: [] }; executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); } // first pass: clean and copy data for (i = 0; i < series.length; ++i) { s = series[i]; var data = s.data, format = s.datapoints.format; if (!format) { format = []; // find out how to copy format.push({ x: true, number: true, required: true }); format.push({ y: true, number: true, required: true }); if (s.bars.show) format.push({ y: true, number: true, required: false, defaultValue: 0 }); s.datapoints.format = format; } if (s.datapoints.pointsize != null) continue; // already filled in if (s.datapoints.pointsize == null) s.datapoints.pointsize = format.length; ps = s.datapoints.pointsize; points = s.datapoints.points; insertSteps = s.lines.show && s.lines.steps; s.xaxis.used = s.yaxis.used = true; for (j = k = 0; j < data.length; ++j, k += ps) { p = data[j]; var nullify = p == null; if (!nullify) { for (m = 0; m < ps; ++m) { val = p[m]; f = format[m]; if (f) { if (f.number && val != null) { val = +val; // convert to number if (isNaN(val)) val = null; } if (val == null) { if (f.required) nullify = true; if (f.defaultValue != null) val = f.defaultValue; } } points[k + m] = val; } } if (nullify) { for (m = 0; m < ps; ++m) { val = points[k + m]; if (val != null) { f = format[m]; // extract min/max info if (f.x) updateAxis(s.xaxis, val, val); if (f.y) updateAxis(s.yaxis, val, val); } points[k + m] = null; } } else { // a little bit of line specific stuff that // perhaps shouldn't be here, but lacking // better means... if (insertSteps && k > 0 && points[k - ps] != null && points[k - ps] != points[k] && points[k - ps + 1] != points[k + 1]) { // copy the point to make room for a middle point for (m = 0; m < ps; ++m) points[k + ps + m] = points[k + m]; // middle point has same y points[k + 1] = points[k - ps + 1]; // we've added a point, better reflect that k += ps; } } } } // give the hooks a chance to run for (i = 0; i < series.length; ++i) { s = series[i]; executeHooks(hooks.processDatapoints, [ s, s.datapoints]); } // second pass: find datamax/datamin for auto-scaling for (i = 0; i < series.length; ++i) { s = series[i]; points = s.datapoints.points, ps = s.datapoints.pointsize; var xmin = topSentry, ymin = topSentry, xmax = bottomSentry, ymax = bottomSentry; for (j = 0; j < points.length; j += ps) { if (points[j] == null) continue; for (m = 0; m < ps; ++m) { val = points[j + m]; f = format[m]; if (!f) continue; if (f.x) { if (val < xmin) xmin = val; if (val > xmax) xmax = val; } if (f.y) { if (val < ymin) ymin = val; if (val > ymax) ymax = val; } } } if (s.bars.show) { // make sure we got room for the bar on the dancing floor var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2; if (s.bars.horizontal) { ymin += delta; ymax += delta + s.bars.barWidth; } else { xmin += delta; xmax += delta + s.bars.barWidth; } } updateAxis(s.xaxis, xmin, xmax); updateAxis(s.yaxis, ymin, ymax); } for (axis in axes) { if (axes[axis].datamin == topSentry) axes[axis].datamin = null; if (axes[axis].datamax == bottomSentry) axes[axis].datamax = null; } } function constructCanvas() { function makeCanvas(width, height) { var c = document.createElement('canvas'); c.width = width; c.height = height; if ($.browser.msie) // excanvas hack c = window.G_vmlCanvasManager.initElement(c); return c; } canvasWidth = placeholder.width(); canvasHeight = placeholder.height(); placeholder.html(""); // clear placeholder if (placeholder.css("position") == 'static') placeholder.css("position", "relative"); // for positioning labels and overlay if (canvasWidth <= 0 || canvasHeight <= 0) throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight; if ($.browser.msie) // excanvas hack window.G_vmlCanvasManager.init_(document); // make sure everything is setup // the canvas canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(placeholder).get(0); ctx = canvas.getContext("2d"); // overlay canvas for interactive features overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(placeholder).get(0); octx = overlay.getContext("2d"); octx.stroke(); } function bindEvents() { // we include the canvas in the event holder too, because IE 7 // sometimes has trouble with the stacking order eventHolder = $([overlay, canvas]); // bind events if (options.grid.hoverable) eventHolder.mousemove(onMouseMove); if (options.grid.clickable) eventHolder.click(onClick); executeHooks(hooks.bindEvents, [eventHolder]); } function setupGrid() { function setTransformationHelpers(axis, o) { function identity(x) { return x; } var s, m, t = o.transform || identity, it = o.inverseTransform; // add transformation helpers if (axis == axes.xaxis || axis == axes.x2axis) { // precompute how much the axis is scaling a point // in canvas space s = axis.scale = plotWidth / (t(axis.max) - t(axis.min)); m = t(axis.min); // data point to canvas coordinate if (t == identity) // slight optimization axis.p2c = function (p) { return (p - m) * s; }; else axis.p2c = function (p) { return (t(p) - m) * s; }; // canvas coordinate to data point if (!it) axis.c2p = function (c) { return m + c / s; }; else axis.c2p = function (c) { return it(m + c / s); }; } else { s = axis.scale = plotHeight / (t(axis.max) - t(axis.min)); m = t(axis.max); if (t == identity) axis.p2c = function (p) { return (m - p) * s; }; else axis.p2c = function (p) { return (m - t(p)) * s; }; if (!it) axis.c2p = function (c) { return m - c / s; }; else axis.c2p = function (c) { return it(m - c / s); }; } } function measureLabels(axis, axisOptions) { var i, labels = [], l; axis.labelWidth = axisOptions.labelWidth; axis.labelHeight = axisOptions.labelHeight; if (axis == axes.xaxis || axis == axes.x2axis) { // to avoid measuring the widths of the labels, we // construct fixed-size boxes and put the labels inside // them, we don't need the exact figures and the // fixed-size box content is easy to center if (axis.labelWidth == null) axis.labelWidth = canvasWidth / (axis.ticks.length > 0 ? axis.ticks.length : 1); // measure x label heights if (axis.labelHeight == null) { labels = []; for (i = 0; i < axis.ticks.length; ++i) { l = axis.ticks[i].label; if (l) labels.push('
' + l + '
'); } if (labels.length > 0) { var dummyDiv = $('
' + labels.join("") + '
').appendTo(placeholder); axis.labelHeight = dummyDiv.height(); dummyDiv.remove(); } } } else if (axis.labelWidth == null || axis.labelHeight == null) { // calculate y label dimensions for (i = 0; i < axis.ticks.length; ++i) { l = axis.ticks[i].label; if (l) labels.push('
' + l + '
'); } if (labels.length > 0) { var dummyDiv = $('
' + labels.join("") + '
').appendTo(placeholder); if (axis.labelWidth == null) axis.labelWidth = dummyDiv.width(); if (axis.labelHeight == null) axis.labelHeight = dummyDiv.find("div").height(); dummyDiv.remove(); } } if (axis.labelWidth == null) axis.labelWidth = 0; if (axis.labelHeight == null) axis.labelHeight = 0; } function setGridSpacing() { // get the most space needed around the grid for things // that may stick out var maxOutset = options.grid.borderWidth; for (i = 0; i < series.length; ++i) maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset; var margin = options.grid.labelMargin + options.grid.borderWidth; if (axes.xaxis.labelHeight > 0) plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin); if (axes.yaxis.labelWidth > 0) plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin); if (axes.x2axis.labelHeight > 0) plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin); if (axes.y2axis.labelWidth > 0) plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin); plotWidth = canvasWidth - plotOffset.left - plotOffset.right; plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top; } var axis; for (axis in axes) setRange(axes[axis], options[axis]); if (options.grid.show) { for (axis in axes) { prepareTickGeneration(axes[axis], options[axis]); setTicks(axes[axis], options[axis]); measureLabels(axes[axis], options[axis]); } setGridSpacing(); } else { plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0; plotWidth = canvasWidth; plotHeight = canvasHeight; } for (axis in axes) setTransformationHelpers(axes[axis], options[axis]); if (options.grid.show) insertLabels(); insertLegend(); } function setRange(axis, axisOptions) { var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin), max = +(axisOptions.max != null ? axisOptions.max : axis.datamax), delta = max - min; if (delta == 0.0) { // degenerate case var widen = max == 0 ? 1 : 0.01; if (axisOptions.min == null) min -= widen; // alway widen max if we couldn't widen min to ensure we // don't fall into min == max which doesn't work if (axisOptions.max == null || axisOptions.min != null) max += widen; } else { // consider autoscaling var margin = axisOptions.autoscaleMargin; if (margin != null) { if (axisOptions.min == null) { min -= delta * margin; // make sure we don't go below zero if all values // are positive if (min < 0 && axis.datamin != null && axis.datamin >= 0) min = 0; } if (axisOptions.max == null) { max += delta * margin; if (max > 0 && axis.datamax != null && axis.datamax <= 0) max = 0; } } } axis.min = min; axis.max = max; } function prepareTickGeneration(axis, axisOptions) { // estimate number of ticks var noTicks; if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0) noTicks = axisOptions.ticks; else if (axis == axes.xaxis || axis == axes.x2axis) // heuristic based on the model a*sqrt(x) fitted to // some reasonable data points noTicks = 0.3 * Math.sqrt(canvasWidth); else noTicks = 0.3 * Math.sqrt(canvasHeight); var delta = (axis.max - axis.min) / noTicks, size, generator, unit, formatter, i, magn, norm; if (axisOptions.mode == "time") { // pretty handling of time // map of app. size of time units in milliseconds var timeUnitSize = { "second": 1000, "minute": 60 * 1000, "hour": 60 * 60 * 1000, "day": 24 * 60 * 60 * 1000, "month": 30 * 24 * 60 * 60 * 1000, "year": 365.2425 * 24 * 60 * 60 * 1000 }; // the allowed tick sizes, after 1 year we use // an integer algorithm var spec = [ [1, "second"], [2, "second"], [5, "second"], [10, "second"], [30, "second"], [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], [30, "minute"], [1, "hour"], [2, "hour"], [4, "hour"], [8, "hour"], [12, "hour"], [1, "day"], [2, "day"], [3, "day"], [0.25, "month"], [0.5, "month"], [1, "month"], [2, "month"], [3, "month"], [6, "month"], [1, "year"] ]; var minSize = 0; if (axisOptions.minTickSize != null) { if (typeof axisOptions.tickSize == "number") minSize = axisOptions.tickSize; else minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]]; } for (i = 0; i < spec.length - 1; ++i) if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) break; size = spec[i][0]; unit = spec[i][1]; // special-case the possibility of several years if (unit == "year") { magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10)); norm = (delta / timeUnitSize.year) / magn; if (norm < 1.5) size = 1; else if (norm < 3) size = 2; else if (norm < 7.5) size = 5; else size = 10; size *= magn; } if (axisOptions.tickSize) { size = axisOptions.tickSize[0]; unit = axisOptions.tickSize[1]; } generator = function(axis) { var ticks = [], tickSize = axis.tickSize[0], unit = axis.tickSize[1], d = new Date(axis.min); var step = tickSize * timeUnitSize[unit]; if (unit == "second") d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize)); if (unit == "minute") d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize)); if (unit == "hour") d.setUTCHours(floorInBase(d.getUTCHours(), tickSize)); if (unit == "month") d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize)); if (unit == "year") d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize)); // reset smaller components d.setUTCMilliseconds(0); if (step >= timeUnitSize.minute) d.setUTCSeconds(0); if (step >= timeUnitSize.hour) d.setUTCMinutes(0); if (step >= timeUnitSize.day) d.setUTCHours(0); if (step >= timeUnitSize.day * 4) d.setUTCDate(1); if (step >= timeUnitSize.year) d.setUTCMonth(0); var carry = 0, v = Number.NaN, prev; do { prev = v; v = d.getTime(); ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); if (unit == "month") { if (tickSize < 1) { // a bit complicated - we'll divide the month // up but we need to take care of fractions // so we don't end up in the middle of a day d.setUTCDate(1); var start = d.getTime(); d.setUTCMonth(d.getUTCMonth() + 1); var end = d.getTime(); d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); carry = d.getUTCHours(); d.setUTCHours(0); } else d.setUTCMonth(d.getUTCMonth() + tickSize); } else if (unit == "year") { d.setUTCFullYear(d.getUTCFullYear() + tickSize); } else d.setTime(v + step); } while (v < axis.max && v != prev); return ticks; }; formatter = function (v, axis) { var d = new Date(v); // first check global format if (axisOptions.timeformat != null) return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames); var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; var span = axis.max - axis.min; var suffix = (axisOptions.twelveHourClock) ? " %p" : ""; if (t < timeUnitSize.minute) fmt = "%h:%M:%S" + suffix; else if (t < timeUnitSize.day) { if (span < 2 * timeUnitSize.day) fmt = "%h:%M" + suffix; else fmt = "%b %d %h:%M" + suffix; } else if (t < timeUnitSize.month) fmt = "%b %d"; else if (t < timeUnitSize.year) { if (span < timeUnitSize.year) fmt = "%b"; else fmt = "%b %y"; } else fmt = "%y"; return $.plot.formatDate(d, fmt, axisOptions.monthNames); }; } else { // pretty rounding of base-10 numbers var maxDec = axisOptions.tickDecimals; var dec = -Math.floor(Math.log(delta) / Math.LN10); if (maxDec != null && dec > maxDec) dec = maxDec; magn = Math.pow(10, -dec); norm = delta / magn; // norm is between 1.0 and 10.0 if (norm < 1.5) size = 1; else if (norm < 3) { size = 2; // special case for 2.5, requires an extra decimal if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { size = 2.5; ++dec; } } else if (norm < 7.5) size = 5; else size = 10; size *= magn; if (axisOptions.minTickSize != null && size < axisOptions.minTickSize) size = axisOptions.minTickSize; if (axisOptions.tickSize != null) size = axisOptions.tickSize; axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec); generator = function (axis) { var ticks = []; // spew out all possible ticks var start = floorInBase(axis.min, axis.tickSize), i = 0, v = Number.NaN, prev; do { prev = v; v = start + i * axis.tickSize; ticks.push({ v: v, label: axis.tickFormatter(v, axis) }); ++i; } while (v < axis.max && v != prev); return ticks; }; formatter = function (v, axis) { return v.toFixed(axis.tickDecimals); }; } axis.tickSize = unit ? [size, unit] : size; axis.tickGenerator = generator; if ($.isFunction(axisOptions.tickFormatter)) axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); }; else axis.tickFormatter = formatter; } function setTicks(axis, axisOptions) { axis.ticks = []; if (!axis.used) return; if (axisOptions.ticks == null) axis.ticks = axis.tickGenerator(axis); else if (typeof axisOptions.ticks == "number") { if (axisOptions.ticks > 0) axis.ticks = axis.tickGenerator(axis); } else if (axisOptions.ticks) { var ticks = axisOptions.ticks; if ($.isFunction(ticks)) // generate the ticks ticks = ticks({ min: axis.min, max: axis.max }); // clean up the user-supplied ticks, copy them over var i, v; for (i = 0; i < ticks.length; ++i) { var label = null; var t = ticks[i]; if (typeof t == "object") { v = t[0]; if (t.length > 1) label = t[1]; } else v = t; if (label == null) label = axis.tickFormatter(v, axis); axis.ticks[i] = { v: v, label: label }; } } if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) { // snap to ticks if (axisOptions.min == null) axis.min = Math.min(axis.min, axis.ticks[0].v); if (axisOptions.max == null && axis.ticks.length > 1) axis.max = Math.max(axis.max, axis.ticks[axis.ticks.length - 1].v); } } function draw() { ctx.clearRect(0, 0, canvasWidth, canvasHeight); var grid = options.grid; if (grid.show && !grid.aboveData) drawGrid(); for (var i = 0; i < series.length; ++i) drawSeries(series[i]); executeHooks(hooks.draw, [ctx]); if (grid.show && grid.aboveData) drawGrid(); } function extractRange(ranges, coord) { var firstAxis = coord + "axis", secondaryAxis = coord + "2axis", axis, from, to, reverse; if (ranges[firstAxis]) { axis = axes[firstAxis]; from = ranges[firstAxis].from; to = ranges[firstAxis].to; } else if (ranges[secondaryAxis]) { axis = axes[secondaryAxis]; from = ranges[secondaryAxis].from; to = ranges[secondaryAxis].to; } else { // backwards-compat stuff - to be removed in future axis = axes[firstAxis]; from = ranges[coord + "1"]; to = ranges[coord + "2"]; } // auto-reverse as an added bonus if (from != null && to != null && from > to) return { from: to, to: from, axis: axis }; return { from: from, to: to, axis: axis }; } function drawGrid() { var i; ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); // draw background, if any if (options.grid.backgroundColor) { ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); ctx.fillRect(0, 0, plotWidth, plotHeight); } // draw markings var markings = options.grid.markings; if (markings) { if ($.isFunction(markings)) // xmin etc. are backwards-compatible, to be removed in future markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis }); for (i = 0; i < markings.length; ++i) { var m = markings[i], xrange = extractRange(m, "x"), yrange = extractRange(m, "y"); // fill in missing if (xrange.from == null) xrange.from = xrange.axis.min; if (xrange.to == null) xrange.to = xrange.axis.max; if (yrange.from == null) yrange.from = yrange.axis.min; if (yrange.to == null) yrange.to = yrange.axis.max; // clip if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) continue; xrange.from = Math.max(xrange.from, xrange.axis.min); xrange.to = Math.min(xrange.to, xrange.axis.max); yrange.from = Math.max(yrange.from, yrange.axis.min); yrange.to = Math.min(yrange.to, yrange.axis.max); if (xrange.from == xrange.to && yrange.from == yrange.to) continue; // then draw xrange.from = xrange.axis.p2c(xrange.from); xrange.to = xrange.axis.p2c(xrange.to); yrange.from = yrange.axis.p2c(yrange.from); yrange.to = yrange.axis.p2c(yrange.to); if (xrange.from == xrange.to || yrange.from == yrange.to) { // draw line ctx.beginPath(); ctx.strokeStyle = m.color || options.grid.markingsColor; ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; //ctx.moveTo(Math.floor(xrange.from), yrange.from); //ctx.lineTo(Math.floor(xrange.to), yrange.to); ctx.moveTo(xrange.from, yrange.from); ctx.lineTo(xrange.to, yrange.to); ctx.stroke(); } else { // fill area ctx.fillStyle = m.color || options.grid.markingsColor; ctx.fillRect(xrange.from, yrange.to, xrange.to - xrange.from, yrange.from - yrange.to); } } } // draw the inner grid ctx.lineWidth = 1; ctx.strokeStyle = options.grid.tickColor; ctx.beginPath(); var v, axis = axes.xaxis; for (i = 0; i < axis.ticks.length; ++i) { v = axis.ticks[i].v; if (v <= axis.min || v >= axes.xaxis.max) continue; // skip those lying on the axes ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0); ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight); } axis = axes.yaxis; for (i = 0; i < axis.ticks.length; ++i) { v = axis.ticks[i].v; if (v <= axis.min || v >= axis.max) continue; ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); } axis = axes.x2axis; for (i = 0; i < axis.ticks.length; ++i) { v = axis.ticks[i].v; if (v <= axis.min || v >= axis.max) continue; ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5); ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5); } axis = axes.y2axis; for (i = 0; i < axis.ticks.length; ++i) { v = axis.ticks[i].v; if (v <= axis.min || v >= axis.max) continue; ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2); } ctx.stroke(); if (options.grid.borderWidth) { // draw border var bw = options.grid.borderWidth; ctx.lineWidth = bw; ctx.strokeStyle = options.grid.borderColor; ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); } ctx.restore(); } function insertLabels() { placeholder.find(".tickLabels").remove(); var html = ['
']; function addLabels(axis, labelGenerator) { for (var i = 0; i < axis.ticks.length; ++i) { var tick = axis.ticks[i]; if (!tick.label || tick.v < axis.min || tick.v > axis.max) continue; html.push(labelGenerator(tick, axis)); } } var margin = options.grid.labelMargin + options.grid.borderWidth; addLabels(axes.xaxis, function (tick, axis) { return '
' + tick.label + "
"; }); addLabels(axes.yaxis, function (tick, axis) { return '
' + tick.label + "
"; }); addLabels(axes.x2axis, function (tick, axis) { return '
' + tick.label + "
"; }); addLabels(axes.y2axis, function (tick, axis) { return '
' + tick.label + "
"; }); html.push('
'); placeholder.append(html.join("")); } function drawSeries(series) { if (series.lines.show) drawSeriesLines(series); if (series.bars.show) drawSeriesBars(series); if (series.points.show) drawSeriesPoints(series); } function drawSeriesLines(series) { function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, prevx = null, prevy = null; ctx.beginPath(); for (var i = ps; i < points.length; i += ps) { var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1]; if (x1 == null || x2 == null) continue; // clip with ymin if (y1 <= y2 && y1 < axisy.min) { if (y2 < axisy.min) continue; // line segment is outside // compute new intersection point x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min) { if (y1 < axisy.min) continue; x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; } // clip with ymax if (y1 >= y2 && y1 > axisy.max) { if (y2 > axisy.max) continue; x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max) { if (y1 > axisy.max) continue; x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; } // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; } // clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; } if (x1 != prevx || y1 != prevy) ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); prevx = x2; prevy = y2; ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); } ctx.stroke(); } function plotLineArea(datapoints, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize, bottom = Math.min(Math.max(0, axisy.min), axisy.max), top, lastX = 0, areaOpen = false; for (var i = ps; i < points.length; i += ps) { var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1]; if (areaOpen && x1 != null && x2 == null) { // close area ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); ctx.fill(); areaOpen = false; continue; } if (x1 == null || x2 == null) continue; // clip x values // clip with xmin if (x1 <= x2 && x1 < axisx.min) { if (x2 < axisx.min) continue; y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.min; } else if (x2 <= x1 && x2 < axisx.min) { if (x1 < axisx.min) continue; y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.min; } // clip with xmax if (x1 >= x2 && x1 > axisx.max) { if (x2 > axisx.max) continue; y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x1 = axisx.max; } else if (x2 >= x1 && x2 > axisx.max) { if (x1 > axisx.max) continue; y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; x2 = axisx.max; } if (!areaOpen) { // open area ctx.beginPath(); ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); areaOpen = true; } // now first check the case where both is outside if (y1 >= axisy.max && y2 >= axisy.max) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); lastX = x2; continue; } else if (y1 <= axisy.min && y2 <= axisy.min) { ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); lastX = x2; continue; } // else it's a bit more complicated, there might // be two rectangles and two triangles we need to fill // in; to find these keep track of the current x values var x1old = x1, x2old = x2; // and clip the y values, without shortcutting // clip with ymin if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.min; } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.min; } // clip with ymax if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y1 = axisy.max; } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; y2 = axisy.max; } // if the x value was changed we got a rectangle // to fill if (x1 != x1old) { if (y1 <= axisy.min) top = axisy.min; else top = axisy.max; ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top)); ctx.lineTo(axisx.p2c(x1), axisy.p2c(top)); } // fill the triangles ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); // fill the other rectangle if it's there if (x2 != x2old) { if (y2 <= axisy.min) top = axisy.min; else top = axisy.max; ctx.lineTo(axisx.p2c(x2), axisy.p2c(top)); ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top)); } lastX = Math.max(x2, x2old); } if (areaOpen) { ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom)); ctx.fill(); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); ctx.lineJoin = "round"; var lw = series.lines.lineWidth, sw = series.shadowSize; // FIXME: consider another form of shadow when filling is turned on if (lw > 0 && sw > 0) { // draw shadow as a thick and thin line with transparency ctx.lineWidth = sw; ctx.strokeStyle = "rgba(0,0,0,0.1)"; // position shadow at angle from the mid of line var angle = Math.PI/18; plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); ctx.lineWidth = sw/2; plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); if (fillStyle) { ctx.fillStyle = fillStyle; plotLineArea(series.datapoints, series.xaxis, series.yaxis); } if (lw > 0) plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); ctx.restore(); } function drawSeriesPoints(series) { function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize; for (var i = 0; i < points.length; i += ps) { var x = points[i], y = points[i + 1]; if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) continue; ctx.beginPath(); ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false); if (fillStyle) { ctx.fillStyle = fillStyle; ctx.fill(); } ctx.stroke(); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); var lw = series.lines.lineWidth, sw = series.shadowSize, radius = series.points.radius; if (lw > 0 && sw > 0) { // draw shadow in two steps var w = sw / 2; ctx.lineWidth = w; ctx.strokeStyle = "rgba(0,0,0,0.1)"; plotPoints(series.datapoints, radius, null, w + w/2, Math.PI, series.xaxis, series.yaxis); ctx.strokeStyle = "rgba(0,0,0,0.2)"; plotPoints(series.datapoints, radius, null, w/2, Math.PI, series.xaxis, series.yaxis); } ctx.lineWidth = lw; ctx.strokeStyle = series.color; plotPoints(series.datapoints, radius, getFillStyle(series.points, series.color), 0, 2 * Math.PI, series.xaxis, series.yaxis); ctx.restore(); } function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) { var left, right, bottom, top, drawLeft, drawRight, drawTop, drawBottom, tmp; if (horizontal) { drawBottom = drawRight = drawTop = true; drawLeft = false; left = b; right = x; top = y + barLeft; bottom = y + barRight; // account for negative bars if (right < left) { tmp = right; right = left; left = tmp; drawLeft = true; drawRight = false; } } else { drawLeft = drawRight = drawTop = true; drawBottom = false; left = x + barLeft; right = x + barRight; bottom = b; top = y; // account for negative bars if (top < bottom) { tmp = top; top = bottom; bottom = tmp; drawBottom = true; drawTop = false; } } // clip if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max) return; if (left < axisx.min) { left = axisx.min; drawLeft = false; } if (right > axisx.max) { right = axisx.max; drawRight = false; } if (bottom < axisy.min) { bottom = axisy.min; drawBottom = false; } if (top > axisy.max) { top = axisy.max; drawTop = false; } left = axisx.p2c(left); bottom = axisy.p2c(bottom); right = axisx.p2c(right); top = axisy.p2c(top); // fill the bar if (fillStyleCallback) { c.beginPath(); c.moveTo(left, bottom); c.lineTo(left, top); c.lineTo(right, top); c.lineTo(right, bottom); c.fillStyle = fillStyleCallback(bottom, top); c.fill(); } // draw outline if (drawLeft || drawRight || drawTop || drawBottom) { c.beginPath(); // FIXME: inline moveTo is buggy with excanvas c.moveTo(left, bottom + offset); if (drawLeft) c.lineTo(left, top + offset); else c.moveTo(left, top + offset); if (drawTop) c.lineTo(right, top + offset); else c.moveTo(right, top + offset); if (drawRight) c.lineTo(right, bottom + offset); else c.moveTo(right, bottom + offset); if (drawBottom) c.lineTo(left, bottom + offset); else c.moveTo(left, bottom + offset); c.stroke(); } } function drawSeriesBars(series) { function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { var points = datapoints.points, ps = datapoints.pointsize; for (var i = 0; i < points.length; i += ps) { if (points[i] == null) continue; drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal); } } ctx.save(); ctx.translate(plotOffset.left, plotOffset.top); // FIXME: figure out a way to add shadows (for instance along the right edge) ctx.lineWidth = series.bars.lineWidth; ctx.strokeStyle = series.color; var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); ctx.restore(); } function getFillStyle(filloptions, seriesColor, bottom, top) { var fill = filloptions.fill; if (!fill) return null; if (filloptions.fillColor) return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); var c = $.color.parse(seriesColor); c.a = typeof fill == "number" ? fill : 0.4; c.normalize(); return c.toString(); } function insertLegend() { placeholder.find(".legend").remove(); if (!options.legend.show) return; var fragments = [], rowStarted = false, lf = options.legend.labelFormatter, s, label; for (i = 0; i < series.length; ++i) { s = series[i]; label = s.label; if (!label) continue; if (i % options.legend.noColumns == 0) { if (rowStarted) fragments.push(''); fragments.push(''); rowStarted = true; } if (lf) label = lf(label, s); fragments.push( '
' + '' + label + ''); } if (rowStarted) fragments.push(''); if (fragments.length == 0) return; var table = '' + fragments.join("") + '
'; if (options.legend.container != null) $(options.legend.container).html(table); else { var pos = "", p = options.legend.position, m = options.legend.margin; if (m[0] == null) m = [m, m]; if (p.charAt(0) == "n") pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; else if (p.charAt(0) == "s") pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; if (p.charAt(1) == "e") pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; else if (p.charAt(1) == "w") pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); if (options.legend.backgroundOpacity != 0.0) { // put in the transparent background // separately to avoid blended labels and // label boxes var c = options.legend.backgroundColor; if (c == null) { c = options.grid.backgroundColor; if (c && typeof c == "string") c = $.color.parse(c); else c = $.color.extract(legend, 'background-color'); c.a = 1; c = c.toString(); } var div = legend.children(); $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); } } } // interactive features var highlights = [], redrawTimeout = null; // returns the data item the mouse is over, or null if none is found function findNearbyItem(mouseX, mouseY, seriesFilter) { var maxDistance = options.grid.mouseActiveRadius, smallestDistance = maxDistance * maxDistance + 1, item = null, foundPoint = false, i, j; for (i = 0; i < series.length; ++i) { if (!seriesFilter(series[i])) continue; var s = series[i], axisx = s.xaxis, axisy = s.yaxis, points = s.datapoints.points, ps = s.datapoints.pointsize, mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster my = axisy.c2p(mouseY), maxx = maxDistance / axisx.scale, maxy = maxDistance / axisy.scale; if (s.lines.show || s.points.show) { for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1]; if (x == null) continue; // For points and lines, the cursor must be within a // certain distance to the data point if (x - mx > maxx || x - mx < -maxx || y - my > maxy || y - my < -maxy) continue; // We have to calculate distances in pixels, not in // data units, because the scales of the axes may be different var dx = Math.abs(axisx.p2c(x) - mouseX), dy = Math.abs(axisy.p2c(y) - mouseY), dist = dx * dx + dy * dy; // we save the sqrt // use <= to ensure last point takes precedence // (last generally means on top of) if (dist <= smallestDistance) { smallestDistance = dist; item = [i, j / ps]; } } } if (s.bars.show && !item) { // no other point can be nearby var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, barRight = barLeft + s.bars.barWidth; for (j = 0; j < points.length; j += ps) { var x = points[j], y = points[j + 1], b = points[j + 2]; if (x == null) continue; // for a bar graph, the cursor must be inside the bar if (series[i].bars.horizontal ? (mx <= Math.max(b, x) && mx >= Math.min(b, x) && my >= y + barLeft && my <= y + barRight) : (mx >= x + barLeft && mx <= x + barRight && my >= Math.min(b, y) && my <= Math.max(b, y))) item = [i, j / ps]; } } } if (item) { i = item[0]; j = item[1]; ps = series[i].datapoints.pointsize; return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), dataIndex: j, series: series[i], seriesIndex: i }; } return null; } function onMouseMove(e) { if (options.grid.hoverable) triggerClickHoverEvent("plothover", e, function (s) { return s["hoverable"] != false; }); } function onClick(e) { triggerClickHoverEvent("plotclick", e, function (s) { return s["clickable"] != false; }); } // trigger click or hover event (they send the same parameters // so we share their code) function triggerClickHoverEvent(eventname, event, seriesFilter) { var offset = eventHolder.offset(), pos = { pageX: event.pageX, pageY: event.pageY }, canvasX = event.pageX - offset.left - plotOffset.left, canvasY = event.pageY - offset.top - plotOffset.top; if (axes.xaxis.used) pos.x = axes.xaxis.c2p(canvasX); if (axes.yaxis.used) pos.y = axes.yaxis.c2p(canvasY); if (axes.x2axis.used) pos.x2 = axes.x2axis.c2p(canvasX); if (axes.y2axis.used) pos.y2 = axes.y2axis.c2p(canvasY); var item = findNearbyItem(canvasX, canvasY, seriesFilter); if (item) { // fill in mouse pos for any listeners out there item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left); item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top); } if (options.grid.autoHighlight) { // clear auto-highlights for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.auto == eventname && !(item && h.series == item.series && h.point == item.datapoint)) unhighlight(h.series, h.point); } if (item) highlight(item.series, item.datapoint, eventname); } placeholder.trigger(eventname, [ pos, item ]); } function triggerRedrawOverlay() { if (!redrawTimeout) redrawTimeout = setTimeout(drawOverlay, 30); } function drawOverlay() { redrawTimeout = null; // draw highlights octx.save(); octx.clearRect(0, 0, canvasWidth, canvasHeight); octx.translate(plotOffset.left, plotOffset.top); var i, hi; for (i = 0; i < highlights.length; ++i) { hi = highlights[i]; if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point); else drawPointHighlight(hi.series, hi.point); } octx.restore(); executeHooks(hooks.drawOverlay, [octx]); } function highlight(s, point, auto) { if (typeof s == "number") s = series[s]; if (typeof point == "number") point = s.data[point]; var i = indexOfHighlight(s, point); if (i == -1) { highlights.push({ series: s, point: point, auto: auto }); triggerRedrawOverlay(); } else if (!auto) highlights[i].auto = false; } function unhighlight(s, point) { if (s == null && point == null) { highlights = []; triggerRedrawOverlay(); } if (typeof s == "number") s = series[s]; if (typeof point == "number") point = s.data[point]; var i = indexOfHighlight(s, point); if (i != -1) { highlights.splice(i, 1); triggerRedrawOverlay(); } } function indexOfHighlight(s, p) { for (var i = 0; i < highlights.length; ++i) { var h = highlights[i]; if (h.series == s && h.point[0] == p[0] && h.point[1] == p[1]) return i; } return -1; } function drawPointHighlight(series, point) { var x = point[0], y = point[1], axisx = series.xaxis, axisy = series.yaxis; if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) return; var pointRadius = series.points.radius + series.points.lineWidth / 2; octx.lineWidth = pointRadius; octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); var radius = 1.5 * pointRadius; octx.beginPath(); octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false); octx.stroke(); } function drawBarHighlight(series, point) { octx.lineWidth = series.bars.lineWidth; octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString(); var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString(); var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal); } function getColorOrGradient(spec, bottom, top, defaultColor) { if (typeof spec == "string") return spec; else { // assume this is a gradient spec; IE currently only // supports a simple vertical gradient properly, so that's // what we support too var gradient = ctx.createLinearGradient(0, top, 0, bottom); for (var i = 0, l = spec.colors.length; i < l; ++i) { var c = spec.colors[i]; if (typeof c != "string") { c = $.color.parse(defaultColor).scale('rgb', c.brightness); c.a *= c.opacity; c = c.toString(); } gradient.addColorStop(i / (l - 1), c); } return gradient; } } } $.plot = function(placeholder, data, options) { var plot = new Plot($(placeholder), data, options, $.plot.plugins); /*var t0 = new Date(); var t1 = new Date(); var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime()) if (window.console) console.log(tstr); else alert(tstr);*/ return plot; }; $.plot.plugins = []; // returns a string with the date d formatted according to fmt $.plot.formatDate = function(d, fmt, monthNames) { var leftPad = function(n) { n = "" + n; return n.length == 1 ? "0" + n : n; }; var r = []; var escape = false; var hours = d.getUTCHours(); var isAM = hours < 12; if (monthNames == null) monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; if (fmt.search(/%p|%P/) != -1) { if (hours > 12) { hours = hours - 12; } else if (hours == 0) { hours = 12; } } for (var i = 0; i < fmt.length; ++i) { var c = fmt.charAt(i); if (escape) { switch (c) { case 'h': c = "" + hours; break; case 'H': c = leftPad(hours); break; case 'M': c = leftPad(d.getUTCMinutes()); break; case 'S': c = leftPad(d.getUTCSeconds()); break; case 'd': c = "" + d.getUTCDate(); break; case 'm': c = "" + (d.getUTCMonth() + 1); break; case 'y': c = "" + d.getUTCFullYear(); break; case 'b': c = "" + monthNames[d.getUTCMonth()]; break; case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; } r.push(c); escape = false; } else { if (c == "%") escape = true; else r.push(c); } } return r.join(""); }; // round to nearby lower multiple of base function floorInBase(n, base) { return base * Math.floor(n / base); } })(jQuery); Bitten-0.6/bitten/htdocs/tabset.js000644 000765 000120 00000003174 10656040024 017451 0ustar00simonadmin000000 000000 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); } Bitten-0.6/bitten/build/__init__.py000644 000765 000120 00000000651 11453043212 017550 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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' Bitten-0.6/bitten/build/api.py000644 000765 000120 00000017635 11455352016 016603 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 import subprocess import sys 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 _encode(text): """Encode input for call. Input must be unicode or utf-8 string.""" if not isinstance(text, unicode): text = unicode(text, 'utf-8') # sys.stdin.encoding might be None (if stdin is directed from a file) # sys.stdin.encoding might be missing (if it is a StringIO object) encoding = sys.getfilesystemencoding() or \ getattr(sys.stdin, 'encoding', None) or 'utf-8' return text.encode(encoding, 'replace') def _decode(text): """Decode output from call.""" try: return text.decode('utf-8') except UnicodeDecodeError: # sys.stdout.encoding might be None (if stdout is directed to a file) # sys.stdout.encoding might be missing (if it is a StringIO object) encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8' return text.decode(encoding, 'replace') class CommandLine(object): """Simple helper for executing subprocesses.""" def __init__(self, executable, args, input=None, cwd=None, shell=False): """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 = [_encode(arg) for arg in args] self.input = input self.cwd = cwd if self.cwd: assert os.path.isdir(self.cwd) self.shell = shell self.returncode = None 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 without ``subprocess`` module / Python 2.4+) """ from threading import Thread from Queue import Queue, Empty def reader(pipe, pipe_name, queue): while pipe and not pipe.closed: line = pipe.readline() if line == '': break queue.put((pipe_name, line)) if not pipe.closed: pipe.close() def writer(pipe, data): if data and pipe and not pipe.closed: pipe.write(data) if not pipe.closed: pipe.close() args = [self.executable] + self.arguments try: p = subprocess.Popen(args, bufsize=1, # Line buffered stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=(self.cwd or None), shell=self.shell, universal_newlines=True, env=None) except Exception, e: raise BuildError('Error executing %s: %s %s' % (args, e.__class__.__name__, str(e))) log.debug('Executing %s, (pid = %s, timeout = %s)', args, p.pid, timeout) if self.input: if isinstance(self.input, basestring): in_data = self.input else: in_data = self.input.read() else: in_data = None queue = Queue() limit = timeout and timeout + time.time() or 0 pipe_in = Thread(target=writer, args=(p.stdin, in_data)) pipe_out = Thread(target=reader, args=(p.stdout, 'stdout', queue)) pipe_err = Thread(target=reader, args=(p.stderr, 'stderr', queue)) pipe_err.start(); pipe_out.start(); pipe_in.start() while True: if limit and limit < time.time(): if hasattr(p, 'kill'): # Python 2.6+ log.debug('Killing process.') p.kill() raise TimeoutError('Command %s timed out' % self.executable) if p.poll() != None and self.returncode == None: self.returncode = p.returncode try: name, line = queue.get(block=True, timeout=.01) line = line and _decode(line.rstrip().replace('\x00', '')) if name == 'stderr': yield (None, line) else: yield (line, None) except Empty: if self.returncode != None: break pipe_out.join(); pipe_in.join(); pipe_err.join() log.debug('%s exited with code %s', self.executable, self.returncode) 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 Bitten-0.6/bitten/build/config.py000644 000765 000120 00000017246 11453043212 017266 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 from string import Template log = logging.getLogger('bitten.config') __docformat__ = 'restructuredtext en' class ConfigFileNotFound(Exception): pass 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). """ 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: if not (os.path.isfile(filename) or os.path.islink(filename)): raise ConfigFileNotFound( "Configuration file %r not found." % 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_option(section, option): 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): if option == 'name': log.warning("Reserved configuration option 'name' used " "for package/section '%s'. Skipping." % section) continue 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 propname == 'name': log.warning("Reserved configuration option 'name' " "used for property '%s'. Skipping." % key) continue if package not in self.packages: self.packages[package] = {} self.packages[package][propname] = value def __contains__(self, key): """Return whether the configuration contains a value for the specified key. :param key: name of the configuration option using dotted notation (for example, "python.path") """ if '.' in key: package, propname = key.split('.', 1) return propname in self.packages.get(package, {}) return key in self.properties def __getitem__(self, key): """Return the value for the specified configuration key. :param key: name of the configuration option using dotted notation (for example, "python.path") """ if '.' in key: package, propname = key.split('.', 1) return self.packages.get(package, {}).get(propname) return self.properties.get(key) def __str__(self): return str({'properties': self.properties, 'packages': self.packages}) def get_dirpath(self, key): """Return the value of the specified configuration key, but verify that the value refers to the path of an existing directory. If the value does not exist, or is not a directory path, return `None`. :param key: name of the configuration option using dotted notation (for example, "ant.home") """ dirpath = self[key] if dirpath: if os.path.isdir(dirpath): return dirpath log.warning('Invalid %s: %s is not a directory', key, dirpath) return None def get_filepath(self, key): """Return the value of the specified configuration key, but verify that the value refers to the path of an existing file. If the value does not exist, or is not a file path, return `None`. :param key: name of the configuration option using dotted notation (for example, "python.path") """ filepath = self[key] if filepath: if os.path.isfile(filepath): return filepath log.warning('Invalid %s: %s is not a file', key, filepath) return None _VAR_RE = re.compile(r'\$\{(?P\w[\w.]*?\w)(?:\:(?P.+))?\}') def interpolate(self, text, **vars): """Interpolate configuration and environment properties into a string. Configuration 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. Environment properties substitute from environment variables, supporting the common notations of ``$VAR`` and ``${VAR}``. :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 Template(self._VAR_RE.sub(_replace, text) ).safe_substitute(os.environ) Bitten-0.6/bitten/build/ctools.py000644 000765 000120 00000035545 11453043212 017326 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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, prefix=None, **kw): """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 :param prefix: install prefix to pass to the configure script, will be postfixed by the machine name from the build """ args = [] if enable: args += ['--enable-%s' % feature for feature in enable.split()] if disable: args += ['--disable-%s' % feature for feature in disable.split()] # since 'with' is a reserved word in python, we need to handle the argument carefully with_ = kw.pop('with', with_) for key in kw: raise TypeError("configure() got an unexpected keyword argument '%s'" % key) 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) if prefix: args.append('--prefix=%ss' % prefix) 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 :param prepend_include: prepend directories to search path :param include: append directories to search path """ args = [] if install: args.append('--install') if symlink: args.append('--symlink') if force: args.append('--force') if warnings: args.append('--warnings=%s' % warnings) if include: args += ['--include=%s' % inc for inc in include.split()] if prepend_include: args += ['--prepend-include=%s' % pinc for pinc in prepend_include.split()] from bitten.build import shtools returncode = shtools.execute(ctxt, 'autoreconf', args=args) if returncode != 0: ctxt.error('autoreconf failed (%s)' % returncode) def make(ctxt, target=None, file_=None, keep_going=False, directory=None, jobs=None, args=None): """Execute a Makefile target. :param ctxt: the build context :type ctxt: `Context` :param file\_: name of the Makefile :param keep_going: whether make should keep going when errors are encountered :param directory: directory in which to build; defaults to project source directory :param jobs: number of concurrent jobs to run :param args: command-line arguments to pass to the script """ executable = ctxt.config.get_filepath('make.path') or 'make' if directory is None: directory = ctxt.basedir margs = ['--directory', directory] if file_: margs += ['--file', ctxt.resolve(file_)] if keep_going: margs.append('--keep-going') if target: margs.append(target) if jobs: margs += ['--jobs', jobs] if args: if isinstance(args, basestring): margs += shlex.split(args) from bitten.build import shtools returncode = shtools.execute(ctxt, executable=executable, args=margs) if returncode != 0: ctxt.error('make failed (%s)' % returncode) def cppunit(ctxt, file_=None, srcdir=None): """Collect CppUnit XML data. :param ctxt: the build context :type ctxt: `Context` :param file\_: path of the file containing the CppUnit results; may contain globbing wildcards to match multiple files :param srcdir: name of the directory containing the source files, used to link the test results to the corresponding files """ assert file_, 'Missing required attribute "file"' try: fileobj = file(ctxt.resolve(file_), 'r') try: total, failed = 0, 0 results = xmlio.Fragment() for group in xmlio.parse(fileobj): if group.name not in ('FailedTests', 'SuccessfulTests'): continue for child in group.children(): test = xmlio.Element('test') name = child.children('Name').next().gettext() if '::' in name: parts = name.split('::') test.attr['fixture'] = '::'.join(parts[:-1]) name = parts[-1] test.attr['name'] = name for location in child.children('Location'): for file_elem in location.children('File'): filepath = file_elem.gettext() if srcdir is not None: filepath = posixpath.join(srcdir, filepath) test.attr['file'] = filepath break for line_elem in location.children('Line'): test.attr['line'] = line_elem.gettext() break break if child.name == 'FailedTest': for message in child.children('Message'): test.append(xmlio.Element('traceback')[ message.gettext() ]) test.attr['status'] = 'failure' failed += 1 else: test.attr['status'] = 'success' results.append(test) total += 1 if failed: ctxt.error('%d of %d test%s failed' % (failed, total, total != 1 and 's' or '')) ctxt.report('test', results) finally: fileobj.close() except IOError, e: log.warning('Error opening CppUnit results file (%s)', e) except xmlio.ParseError, e: print e log.warning('Error parsing CppUnit results file (%s)', e) def cunit (ctxt, file_=None, srcdir=None): """Collect CUnit XML data. :param ctxt: the build context :type ctxt: `Context` :param file\_: path of the file containing the CUnit results; may contain globbing wildcards to match multiple files :param srcdir: name of the directory containing the source files, used to link the test results to the corresponding files """ assert file_, 'Missing required attribute "file"' try: fileobj = file(ctxt.resolve(file_), 'r') try: total, failed = 0, 0 results = xmlio.Fragment() log_elem = xmlio.Fragment() def info (msg): log.info (msg) log_elem.append (xmlio.Element ('message', level='info')[msg]) def warning (msg): log.warning (msg) log_elem.append (xmlio.Element ('message', level='warning')[msg]) def error (msg): log.error (msg) log_elem.append (xmlio.Element ('message', level='error')[msg]) for node in xmlio.parse(fileobj): if node.name != 'CUNIT_RESULT_LISTING': continue for suiteRun in node.children ('CUNIT_RUN_SUITE'): for suite in suiteRun.children(): if suite.name not in ('CUNIT_RUN_SUITE_SUCCESS', 'CUNIT_RUN_SUITE_FAILURE'): warning ("Unknown node: %s" % suite.name) continue suiteName = suite.children ('SUITE_NAME').next().gettext() info ("%s [%s]" % ("*" * (57 - len (suiteName)), suiteName)) for record in suite.children ('CUNIT_RUN_TEST_RECORD'): for result in record.children(): if result.name not in ('CUNIT_RUN_TEST_SUCCESS', 'CUNIT_RUN_TEST_FAILURE'): continue testName = result.children ('TEST_NAME').next().gettext() info ("Running %s..." % testName); test = xmlio.Element('test') test.attr['fixture'] = suiteName test.attr['name'] = testName if result.name == 'CUNIT_RUN_TEST_FAILURE': error ("%s(%d): %s" % (result.children ('FILE_NAME').next().gettext(), int (result.children ('LINE_NUMBER').next().gettext()), result.children ('CONDITION').next().gettext())) test.attr['status'] = 'failure' failed += 1 else: test.attr['status'] = 'success' results.append(test) total += 1 if failed: ctxt.error('%d of %d test%s failed' % (failed, total, total != 1 and 's' or '')) ctxt.report('test', results) ctxt.log (log_elem) finally: fileobj.close() except IOError, e: log.warning('Error opening CUnit results file (%s)', e) except xmlio.ParseError, e: print e log.warning('Error parsing CUnit results file (%s)', e) def gcov(ctxt, include=None, exclude=None, prefix=None, root=""): """Run ``gcov`` to extract coverage data where available. :param ctxt: the build context :type ctxt: `Context` :param include: patterns of files and directories to include :param exclude: patterns of files and directories that should be excluded :param prefix: optional prefix name that is added to object files by the build system :param root: optional root path in which the build system puts the object files """ file_re = re.compile(r'^File (?:\'|\`)(?P[^\']+)\'\s*$') lines_re = re.compile(r'^Lines executed:(?P\d+\.\d+)\% of (?P\d+)\s*$') files = [] for filename in FileSet(ctxt.basedir, include, exclude): if os.path.splitext(filename)[1] in ('.c', '.cpp', '.cc', '.cxx'): files.append(filename) coverage = xmlio.Fragment() log_elem = xmlio.Fragment() def info (msg): log.info (msg) log_elem.append (xmlio.Element ('message', level='info')[msg]) def warning (msg): log.warning (msg) log_elem.append (xmlio.Element ('message', level='warning')[msg]) def error (msg): log.error (msg) log_elem.append (xmlio.Element ('message', level='error')[msg]) for srcfile in files: # Determine the coverage for each source file by looking for a .gcno # and .gcda pair info ("Getting coverage info for %s" % srcfile) filepath, filename = os.path.split(srcfile) stem = os.path.splitext(filename)[0] if prefix is not None: stem = prefix + '-' + stem objfile = os.path.join (root, filepath, stem + '.o') if not os.path.isfile(ctxt.resolve(objfile)): warning ('No object file found for %s at %s' % (srcfile, objfile)) continue if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcno'))): warning ('No .gcno file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcno'))) continue if not os.path.isfile (ctxt.resolve (os.path.join (root, filepath, stem + '.gcda'))): warning ('No .gcda file found for %s at %s' % (srcfile, os.path.join (root, filepath, stem + '.gcda'))) continue num_lines, num_covered = 0, 0 skip_block = False cmd = CommandLine('gcov', ['-b', '-n', '-o', objfile, srcfile], cwd=ctxt.basedir) for out, err in cmd.execute(): if out == '': # catch blank lines, reset the block state... skip_block = False elif out and not skip_block: # Check for a file name match = file_re.match(out) if match: if os.path.isabs(match.group('file')): skip_block = True continue else: # check for a "Lines executed" message match = lines_re.match(out) if match: lines = float(match.group('num')) cov = float(match.group('cov')) num_covered += int(lines * cov / 100) num_lines += int(lines) if cmd.returncode != 0: continue module = xmlio.Element('coverage', name=os.path.basename(srcfile), file=srcfile.replace(os.sep, '/'), lines=num_lines, percentage=0) if num_lines: percent = int(round(num_covered * 100 / num_lines)) module.attr['percentage'] = percent coverage.append(module) ctxt.report('coverage', coverage) ctxt.log (log_elem) Bitten-0.6/bitten/build/hgtools.py000644 000765 000120 00000001402 11500372054 017465 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- """Recipe commands for Mercurial.""" import logging log = logging.getLogger('bitten.build.hgtools') __docformat__ = 'restructuredtext en' def pull(ctxt, revision=None, dir_='.'): """pull and update the local working copy from the Mercurial 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 = ['pull', '-u'] if revision: args += ['-r', revision.split(':')[-1]] from bitten.build import shtools returncode = shtools.execute(ctxt, file_='hg', args=args, dir_=dir_) if returncode != 0: ctxt.error('hg pull -u failed (%s)' % returncode) Bitten-0.6/bitten/build/javatools.py000644 000765 000120 00000021553 11453043212 020017 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2006 Matthew Good # Copyright (C) 2007-2010 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) shell = False if os.name == 'nt': # Need to execute ant.bat through a shell on Windows shell = True cmdline = CommandLine(executable, args, cwd=ctxt.basedir, shell=shell) 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 _fix_traceback(result): """ Sometimes the traceback isn't prefixed with the exception type and message, so add it in if needed. """ attr = result[0].attr tracebackprefix = "" # py.test does not include the type tag in some cases so don't do this # fix when it is missing if "type" in attr: tracebackprefix = "%s: %s" % (attr['type'], attr.get('message', '')) if result[0].gettext().startswith(tracebackprefix): return result[0].gettext() else: return "\n".join((tracebackprefix, result[0].gettext())) 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'] test.attr['name'] = testcase.attr['name'] 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: junit_status = result[0].name test.append(xmlio.Element('traceback')[_fix_traceback(result)]) if junit_status == 'skipped': test.attr['status'] = 'ignore' elif junit_status == 'error': test.attr['status'] = 'error' failed += 1 else: test.attr['status'] = 'failure' 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) Bitten-0.6/bitten/build/monotools.py000644 000765 000120 00000006714 11453043212 020050 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2006 Matthew Good # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2009 Grzegorz Sobanski # 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 Mono 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.monotools') __docformat__ = 'restructuredtext en' def _parse_suite(element): for child in element.children('results'): testcases = list(child.children('test-case')) if testcases: yield element, testcases for xmlsuite in child.children('test-suite'): for suite in _parse_suite(xmlsuite): yield suite def _get_cases(fileobj): for testsuite in xmlio.parse(fileobj).children('test-suite'): for suite in _parse_suite(testsuite): yield suite def nunit(ctxt, file_=None): """Extract test results from a NUnit XML report. :param ctxt: the build context :type ctxt: `Context` :param file\_: path to the NUnit XML test results; may contain globbing wildcards for matching multiple results 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 suite, testcases in _get_cases(fileobj): for testcase in testcases: test = xmlio.Element('test') test.attr['fixture'] = suite.attr['name'] if 'time' in testcase.attr: test.attr['duration'] = testcase.attr['time'] if testcase.attr['executed'] == 'True': if testcase.attr['success'] != 'True': test.attr['status'] = 'failure' failure = list(testcase.children('failure')) if failure: stacktraceNode = list(failure[0].children('stack-trace')) if stacktraceNode: test.append(xmlio.Element('traceback')[ stacktraceNode[0].gettext() ]) failed += 1 else: test.attr['status'] = 'success' else: test.attr['status'] = 'ignore' 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 NUnit results file (%s)', e) except xmlio.ParseError, e: log.warning('Error parsing NUnit results file (%s)', e) Bitten-0.6/bitten/build/phptools.py000644 000765 000120 00000014460 11453043212 017664 0ustar00simonadmin000000 000000 # -*- coding: UTF-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2007 Wei Zhuo # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. "Recipe commands for tools commonly used in PHP projects." 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"' def _process_testsuite(testsuite, results, parent_file=''): for testcase in testsuite.children(): if testcase.name == 'testsuite': _process_testsuite(testcase, results, parent_file=testcase.attr.get('file', parent_file)) continue test = xmlio.Element('test') test.attr['fixture'] = testsuite.attr['name'] 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 testsuite.attr or parent_file: testfile = os.path.realpath( testsuite.attr.get('file', parent_file)) if testfile.startswith(ctxt.basedir): testfile = testfile[len(ctxt.basedir) + 1:] testfile = testfile.replace(os.sep, '/') test.attr['file'] = testfile results.append(test) try: total, failed = 0, 0 results = xmlio.Fragment() fileobj = file(ctxt.resolve(file_), 'r') try: for testsuite in xmlio.parse(fileobj).children('testsuite'): total += int(testsuite.attr['tests']) failed += int(testsuite.attr['failures']) + \ int(testsuite.attr['errors']) _process_testsuite(testsuite, results) 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 Phing or PHPUnit code coverage report.""" assert file_, 'Missing required attribute "file"' def _process_phing_coverage(ctxt, element, coverage): for cls in element.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) def _process_phpunit_coverage(ctxt, element, coverage): for cls in element._node.getElementsByTagName('class'): sourcefile = cls.parentNode.getAttribute('name') if not os.path.isabs(sourcefile): sourcefile = os.path.join(ctxt.basedir, sourcefile) if sourcefile.startswith(ctxt.basedir): loc, ncloc = 0, 0.0 for line in cls.parentNode.getElementsByTagName('line'): if str(line.getAttribute('type')) == 'stmt': loc += 1 if int(line.getAttribute('count')) == 0: ncloc += 1 if loc > 0: percentage = 100 - (ncloc / loc * 100) else: percentage = 100 if sourcefile.startswith(ctxt.basedir): sourcefile = sourcefile[len(ctxt.basedir) + 1:] class_coverage = xmlio.Element('coverage', name=cls.getAttribute('name'), lines=int(loc), percentage=int(percentage), file=sourcefile.replace(os.sep, '/')) coverage.append(class_coverage) try: summary_file = file(ctxt.resolve(file_), 'r') summary = xmlio.parse(summary_file) coverage = xmlio.Fragment() try: for element in summary.children(): if element.name == 'package': _process_phing_coverage(ctxt, element, coverage) elif element.name == 'project': _process_phpunit_coverage(ctxt, element, 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) Bitten-0.6/bitten/build/pythontools.py000644 000765 000120 00000046677 11453043212 020435 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2008 Matt Good # Copyright (C) 2008-2010 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, timeout=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 :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ if options: if isinstance(options, basestring): options = shlex.split(options) else: options = [] if timeout: timeout = int(timeout) 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(timeout): 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, timeout=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 :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ 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, timeout=timeout) if returncode != 0: ctxt.error('Executing %s failed (error code %s)' % \ (file_ or function or module, returncode)) def pylint(ctxt, file_=None): """Extract data from a ``pylint`` run written to a file. :param ctxt: the build context :type ctxt: `Context` :param file\_: name of the file containing the Pylint output """ assert file_, 'Missing required attribute "file"' msg_re = re.compile(r'^(?P.+):(?P\d+): ' r'\[(?P[A-Z]\d*)(?:, (?P[\w\.]+))?\] ' r'(?P.*)$') msg_categories = dict(W='warning', E='error', C='convention', R='refactor') problems = xmlio.Fragment() try: fd = open(ctxt.resolve(file_), 'r') try: for line in fd: match = msg_re.search(line) if match: msg_type = match.group('type') category = msg_categories.get(msg_type[0]) if len(msg_type) == 1: msg_type = None filename = match.group('file') if os.path.isabs(filename) \ and filename.startswith(ctxt.basedir): filename = filename[len(ctxt.basedir) + 1:] filename = filename.replace('\\', '/') lineno = int(match.group('line')) tag = match.group('tag') problems.append(xmlio.Element('problem', category=category, type=msg_type, tag=tag, line=lineno, file=filename)[ match.group('msg') or '' ]) ctxt.report('lint', problems) finally: fd.close() except IOError, e: log.warning('Error opening pylint results file (%s)', e) def coverage(ctxt, summary=None, coverdir=None, include=None, exclude=None): """Extract data from a ``coverage.py`` run. :param ctxt: the build context :type ctxt: `Context` :param summary: path to the file containing the coverage summary :param coverdir: name of the directory containing the per-module coverage details :param include: patterns of files or directories to include in the report :param exclude: patterns of files or directories to exclude from the report """ assert summary, 'Missing required attribute "summary"' summary_line_re = re.compile(r'^(?P.*?)\s+(?P\d+)\s+' r'(?P\d+)\s+(?P\d+)%\s+' r'(?:(?P(?:\d+(?:-\d+)?(?:, )?)*)\s+)?' r'(?P.+)$') fileset = FileSet(ctxt.basedir, include, exclude) missing_files = [] for filename in fileset: if os.path.splitext(filename)[1] != '.py': continue missing_files.append(filename) covered_modules = set() try: summary_file = open(ctxt.resolve(summary), 'r') try: coverage = xmlio.Fragment() for summary_line in summary_file: match = summary_line_re.search(summary_line) if match: modname = match.group(1) filename = match.group(6) if not os.path.isabs(filename): filename = os.path.normpath(os.path.join(ctxt.basedir, filename)) else: filename = os.path.realpath(filename) if not filename.startswith(ctxt.basedir): continue filename = filename[len(ctxt.basedir) + 1:] if not filename in fileset: continue percentage = int(match.group(4).rstrip('%')) num_lines = int(match.group(2)) missing_files.remove(filename) covered_modules.add(modname) module = xmlio.Element('coverage', name=modname, file=filename.replace(os.sep, '/'), percentage=percentage, lines=num_lines) coverage.append(module) for filename in missing_files: modname = os.path.splitext(filename.replace(os.sep, '.'))[0] if modname in covered_modules: continue covered_modules.add(modname) module = xmlio.Element('coverage', name=modname, file=filename.replace(os.sep, '/'), percentage=0) coverage.append(module) ctxt.report('coverage', coverage) finally: summary_file.close() except IOError, e: log.warning('Error opening coverage summary file (%s)', e) def trace(ctxt, summary=None, coverdir=None, include=None, exclude=None): """Extract data from a ``trace.py`` run. :param ctxt: the build context :type ctxt: `Context` :param summary: path to the file containing the coverage summary :param coverdir: name of the directory containing the per-module coverage details :param include: patterns of files or directories to include in the report :param exclude: patterns of files or directories to exclude from the report """ assert summary, 'Missing required attribute "summary"' assert coverdir, 'Missing required attribute "coverdir"' summary_line_re = re.compile(r'^\s*(?P\d+)\s+(?P\d+)%\s+' r'(?P.*?)\s+\((?P.*?)\)') coverage_line_re = re.compile(r'\s*(?:(?P\d+): )?(?P.*)') fileset = FileSet(ctxt.basedir, include, exclude) missing_files = [] for filename in fileset: if os.path.splitext(filename)[1] != '.py': continue missing_files.append(filename) covered_modules = set() def handle_file(elem, sourcefile, coverfile=None): code_lines = set() for lineno, linetype, line in loc.count(sourcefile): if linetype == loc.CODE: code_lines.add(lineno) num_covered = 0 lines = [] if coverfile: prev_hits = '0' for idx, coverline in enumerate(coverfile): match = coverage_line_re.search(coverline) if match: hits = match.group(1) if hits: # Line covered if hits != '0': num_covered += 1 lines.append(hits) prev_hits = hits elif coverline.startswith('>'): # Line not covered lines.append('0') prev_hits = '0' elif idx not in code_lines: # Not a code line lines.append('-') prev_hits = '0' else: # A code line not flagged by trace.py if prev_hits != '0': num_covered += 1 lines.append(prev_hits) elem.append(xmlio.Element('line_hits')[' '.join(lines)]) num_lines = not lines and len(code_lines) or \ len([l for l in lines if l != '-']) 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): """Extract data from a ``Figleaf`` run. :param ctxt: the build context :type ctxt: `Context` :param summary: path to the file containing the coverage summary :param include: patterns of files or directories to include in the report :param exclude: patterns of files or directories to exclude from the report """ 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)) if not interesting_lines: continue 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.replace(os.sep, '/'), 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) Bitten-0.6/bitten/build/shtools.py000644 000765 000120 00000015017 11453043212 017506 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 codecs 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, timeout=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 :param dir\_: directory to change to before executing the command :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ assert executable or file_, \ 'Either "executable" or "file" attribute required' returncode = execute(ctxt, executable=executable, file_=file_, output=output, args=args, dir_=dir_, timeout=timeout) 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 :param dir\_: directory to change to before executing the command """ 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, timeout=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 dir\_: directory to change to before executing the command :param filter\_: function to filter out messages from the executable stdout :param timeout: the number of seconds before the external process should be aborted (has same constraints as CommandLine) """ 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_) shell = False if file_ and os.name == 'nt': # Need to execute script files through a shell on Windows shell = True if executable is None: executable = file_ elif file_: args[:0] = [file_] # Support important Windows CMD.EXE built-ins (and it does its own quoting) if os.name == 'nt' and executable.upper() in ['COPY', 'DIR', 'ECHO', 'ERASE', 'DEL', 'MKDIR', 'MD', 'MOVE', 'RMDIR', 'RD', 'TYPE']: shell = True if input_: input_file = codecs.open(resolve(input_), 'r', 'utf-8') else: input_file = None if output: output_file = codecs.open(resolve(output), 'w', 'utf-8') 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 if timeout: timeout = int(timeout) try: cmdline = CommandLine(executable, args, input=input_file, cwd=dir_, shell=shell) log_elem = xmlio.Fragment() for out, err in cmdline.execute(timeout=timeout): 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 Bitten-0.6/bitten/build/svntools.py000644 000765 000120 00000014022 11453043212 017675 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2007 Christopher Lenz # Copyright (C) 2007-2010 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 import shutil import os log = logging.getLogger('bitten.build.svntools') __docformat__ = 'restructuredtext en' class Error(EnvironmentError): pass def copytree(src, dst, symlinks=False): """Recursively copy a directory tree using copy2(). If exception(s) occur, an Error is raised with a list of reasons. If the optional symlinks flag is true, symbolic links in the source tree result in symbolic links in the destination tree; if it is false, the contents of the files pointed to by symbolic links are copied. Adapted from shtuil.copytree """ names = os.listdir(src) if not os.path.isdir(dst): os.makedirs(dst) errors = [] for name in names: srcname = os.path.join(src, name) dstname = os.path.join(dst, name) try: if symlinks and os.path.islink(srcname): linkto = os.readlink(srcname) os.symlink(linkto, dstname) elif os.path.isdir(srcname): copytree(srcname, dstname, symlinks) else: shutil.copy2(srcname, dstname) except (IOError, os.error), why: errors.append((srcname, dstname, str(why))) # catch the Error from the recursive copytree so that we can # continue with other files except Error, err: errors.extend(err.args[0]) try: shutil.copystat(src, dst) except WindowsError: # can't copy file access times on Windows pass except OSError, why: errors.extend((src, dst, str(why))) if errors: raise Error, errors def checkout(ctxt, url, path=None, revision=None, dir_='.', verbose='false', shared_path=None, username=None, password=None, no_auth_cache='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 :param shared_path: a shared directory to do the checkout in, before copying to dir\_ :param username: a username of the repository :param password: a password of the repository :param no\_auth\_cache: do not cache authentication tokens """ args = ['checkout'] if revision: args += ['-r', revision] if path: final_url = posixpath.join(url, path.lstrip('/')) else: final_url = url if username: args += ['--username', username] if password: args += ['--password', password] if no_auth_cache.lower() == 'true': args += ['--no-auth-cache'] args += [final_url, dir_] cofilter = None if verbose.lower() == 'false': cre = re.compile(r'^[AU]\s.*$') cofilter = lambda s: cre.sub('', s) if shared_path is not None: # run checkout on shared_path, then copy shared_path = ctxt.resolve(shared_path) checkout(ctxt, url, path, revision, dir_=shared_path, verbose=verbose) try: copytree(shared_path, ctxt.resolve(dir_)) except Exception, e: ctxt.log('error copying shared tree (%s)' % e) 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_='.', username=None, password=None, no_auth_cache='false'): """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 :param username: a username of the repository :param password: a password of the repository :param no\_auth\_cache: do not cache authentication tokens """ args = ['export', '--force'] if revision: args += ['-r', revision] if path: url = posixpath.join(url, path) if username: args += ['--username', username] if password: args += ['--password', password] if no_auth_cache.lower() == 'true': args += ['--no-auth-cache'] 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_='.', username=None, password=None, no_auth_cache='false'): """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 :param username: a username of the repository :param password: a password of the repository :param no\_auth\_cache: do not cache authentication tokens """ args = ['update'] if revision: args += ['-r', revision] if username: args += ['--username', username] if password: args += ['--password', password] if no_auth_cache.lower() == 'true': args += ['--no-auth-cache'] 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) Bitten-0.6/bitten/build/tests/000755 000765 000120 00000000000 11536424600 016605 5ustar00simonadmin000000 000000 Bitten-0.6/bitten/build/xmltools.py000644 000765 000120 00000006404 11453043212 017674 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 = srcdoc.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? Bitten-0.6/bitten/build/tests/__init__.py000644 000765 000120 00000001753 11453043212 020716 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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, hgtools, \ monotools, phptools, pythontools, \ xmltools, javatools def suite(): suite = unittest.TestSuite() suite.addTest(api.suite()) suite.addTest(config.suite()) suite.addTest(ctools.suite()) suite.addTest(hgtools.suite()) suite.addTest(monotools.suite()) suite.addTest(phptools.suite()) suite.addTest(pythontools.suite()) suite.addTest(javatools.suite()) suite.addTest(xmltools.suite()) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/build/tests/api.py000644 000765 000120 00000016223 11453043212 017726 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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, BuildError 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_escape_and_quote_args(self): # Verify shlex.split() used by many commands to split input -> args import shlex self.assertEquals(['o\ne', '4 2', '"hi there"'], shlex.split('o\\\ne "4 2" \\"hi\\ there\\"')) 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]] ).rstrip('.0') 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) 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]]) 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)) self.assertRaises(TimeoutError, iterable.next) def test_nonexisting_command(self): cmdline = CommandLine('doesnotexist', []) iterable = iter(cmdline.execute()) try: out, err = iterable.next() self.fail("Expected BuildError") except BuildError, e: self.failUnless("Error executing ['doesnotexist']" in str(e)) 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') Bitten-0.6/bitten/build/tests/config.py000644 000765 000120 00000020060 11453043212 020414 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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, ConfigFileNotFound 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_sysinfo_configfile_partial_override(self): inifd, ininame = tempfile.mkstemp(prefix='bitten_test') try: os.write(inifd, """ [machine] name = MACHINE [os] name = OS """) os.close(inifd) config = Configuration(ininame) self.assertEqual('MACHINE', config['machine']) self.assertEqual('OS', config['os']) # Remaining options should be set to default value system, release, version = platform.system_alias(platform.system(), platform.release(), platform.version()) self.assertEqual(platform.processor(), config['processor']) self.assertEqual(os.name, config['family']) self.assertEqual(release, 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', 'python.name': 'invalid option' }) 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']) self.failIf('name' in config.packages['python'], "Invalid option 'name' should not be included...") 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 name = invalid option """) 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']) self.failIf('name' in config.packages['python'], "Invalid option 'name' should not be included...") finally: os.remove(ininame) def test_package_configfile_non_existant(self): try: conf = Configuration(filename='doesnotexist.ini') self.fail('Expected ConfigFileNotFound') except ConfigFileNotFound, e: self.assertEquals(str(e), "Configuration file 'doesnotexist.ini' not found.") 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 test_interpolate_environment(self): config = Configuration() os.environ['BITTEN_TEST'] = 'foo' try: # regular substitutions self.assertEquals(os.environ['BITTEN_TEST'], config.interpolate('$BITTEN_TEST')) self.assertEquals(os.environ['BITTEN_TEST'], config.interpolate('${BITTEN_TEST}')) if os.name == 'posix': # case-sensitive self.assertEquals('${bitten_test}', config.interpolate('${bitten_test}')) self.assertEquals('$bitten_test', config.interpolate('$bitten_test')) elif os.name == 'nt': # case-insensitive self.assertEquals(os.environ['bitten_test'], config.interpolate('$bitten_test')) self.assertEquals(os.environ['bitten_test'], config.interpolate('${bitten_test}')) finally: del os.environ['BITTEN_TEST'] def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(ConfigurationTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/build/tests/ctools.py000644 000765 000120 00000011543 11453043212 020460 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. import os import shutil import tempfile import unittest from bitten.build import ctools from bitten.build.tests import dummy from bitten.recipe import Context, Recipe class CppUnitTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def test_missing_param_file(self): self.assertRaises(AssertionError, ctools.cppunit, self.ctxt) def test_empty_summary(self): cppunit_xml = file(self.ctxt.resolve('cppunit.xml'), 'w') cppunit_xml.write(""" HelloTest::secondTest Assertion HelloTest.cxx 95 assertion failed - Expression: 2 == 3 HelloTest::firstTest HelloTest::thirdTest 3 1 0 1 """) cppunit_xml.close() ctools.cppunit(self.ctxt, file_='cppunit.xml') type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('test', category) tests = list(xml.children) self.assertEqual(3, len(tests)) self.assertEqual('HelloTest', tests[0].attr['fixture']) self.assertEqual('secondTest', tests[0].attr['name']) self.assertEqual('failure', tests[0].attr['status']) self.assertEqual('HelloTest.cxx', tests[0].attr['file']) self.assertEqual('95', tests[0].attr['line']) self.assertEqual('HelloTest', tests[1].attr['fixture']) self.assertEqual('firstTest', tests[1].attr['name']) self.assertEqual('success', tests[1].attr['status']) self.assertEqual('HelloTest', tests[2].attr['fixture']) self.assertEqual('thirdTest', tests[2].attr['name']) self.assertEqual('success', tests[2].attr['status']) class GCovTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def _create_file(self, *path): filename = os.path.join(self.basedir, *path) dirname = os.path.dirname(filename) if not os.path.isdir(dirname): os.makedirs(dirname) fd = file(filename, 'w') fd.close() return filename[len(self.basedir) + 1:] def test_no_file(self): ctools.CommandLine = dummy.CommandLine() ctools.gcov(self.ctxt) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('log', type) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(0, len(xml.children)) def test_single_file(self): self._create_file('foo.c') self._create_file('foo.o') self._create_file('foo.gcno') self._create_file('foo.gcda') ctools.CommandLine = dummy.CommandLine(stdout=""" File `foo.c' Lines executed:45.81% of 884 Branches executed:54.27% of 398 Taken at least once:36.68% of 398 Calls executed:48.19% of 249 File `foo.h' Lines executed:50.00% of 4 No branches Calls executed:100.00% of 1 """) ctools.gcov(self.ctxt) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('log', type) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('coverage', elem.name) self.assertEqual('foo.c', elem.attr['file']) self.assertEqual('foo.c', elem.attr['name']) self.assertEqual(888, elem.attr['lines']) self.assertEqual(45, elem.attr['percentage']) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(CppUnitTestCase, 'test')) suite.addTest(unittest.makeSuite(GCovTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/build/tests/dummy.py000644 000765 000120 00000001542 11453043212 020306 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 map(lambda x,y: (x,y), self.stdout.readlines(), self.stderr.readlines()) Bitten-0.6/bitten/build/tests/hgtools.py000644 000765 000120 00000002073 11453043212 020632 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 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 import inspect from bitten.build import hgtools from bitten.recipe import Context class HgPullTestCase(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_command_signature(self): self.assertEquals(inspect.getargspec(hgtools.pull), (['ctxt', 'revision', 'dir_'], None, None, (None, '.'))) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(HgPullTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/build/tests/javatools.py000644 000765 000120 00000020515 11453043212 021156 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2006 Matthew Good # Copyright (C) 2007-2010 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 os.path import shutil import tempfile import unittest from bitten.build import javatools from bitten.recipe import Context, Recipe class CoberturaTestCase(unittest.TestCase): xml_template=""" src %s """ def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def _create_file(self, *path, **kw): filename = os.path.join(self.basedir, *path) dirname = os.path.dirname(filename) if not os.path.exists(dirname): os.makedirs(dirname) fd = file(filename, 'w') content = kw.get('content') if content is not None: fd.write(content) fd.close() return filename[len(self.basedir) + 1:] def test_basic(self): filename = self._create_file('coverage.xml', content=self.xml_template % """ """) javatools.cobertura(self.ctxt, file_=filename) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('coverage', elem.name) self.assertEqual('src/test/TestClass.java', elem.attr['file']) self.assertEqual('test.TestClass', elem.attr['name']) self.assertEqual(4, elem.attr['lines']) self.assertEqual(50, elem.attr['percentage']) def test_skipped_lines(self): filename = self._create_file('coverage.xml', content=self.xml_template % """ """) javatools.cobertura(self.ctxt, file_=filename) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('coverage', elem.name) self.assertEqual('src/test/TestClass.java', elem.attr['file']) self.assertEqual('test.TestClass', elem.attr['name']) self.assertEqual(2, elem.attr['lines']) self.assertEqual(50, elem.attr['percentage']) line_hits = elem.children[0] self.assertEqual('line_hits', line_hits.name) self.assertEqual('0 - 1', line_hits.children[0]) def test_interface(self): filename = self._create_file('coverage.xml', content=self.xml_template % """ """) javatools.cobertura(self.ctxt, file_=filename) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('coverage', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('coverage', elem.name) self.assertEqual('src/test/TestInterface.java', elem.attr['file']) self.assertEqual('test.TestInterface', elem.attr['name']) self.assertEqual(0, elem.attr['lines']) self.assertEqual(0, elem.attr['percentage']) class PyTestTestCase(unittest.TestCase): xml_template = """ %(body)s """ def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def _xml_file(self, body, name="", errors=0, failures=0, skips=0, tests=0, time=0.01): if tests == 0: tests = errors + failures + skips (fd, path) = tempfile.mkstemp(prefix="junit", suffix=".xml", dir=self.basedir, text=True) stream = os.fdopen(fd, "w") content = self.xml_template % dict(body=body, name=name, errors=errors, failures=failures, skips=skips, tests=tests, time=time) stream.write(content) stream.close() return path def test_simple(self): body = '' filename = self._xml_file(body, tests=1) javatools.junit(self.ctxt, file_=filename) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('test', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('test', elem.name) self.assertEqual('test_simple', elem.attr['name']) self.assertEqual('success', elem.attr['status']) self.assertEqual(0, len(elem.children)) def test_setup_fail(self): """Check that py.test setup failures are handled""" body = '' \ + 'request = <FuncargRequest for <Function...' \ + '' filename = self._xml_file(body, errors=1) javatools.junit(self.ctxt, file_=filename) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('test', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('test', elem.name) self.assertEqual('test_simple', elem.attr['name']) self.assertEqual('error', elem.attr['status']) self.assertEqual(1, len(elem.children)) trace = elem.children[0] self.assertEqual('traceback', trace.name) self.assertEqual(1, len(trace.children)) self.assertEqual('request = ' filename = self._xml_file(body, skips=1) javatools.junit(self.ctxt, file_=filename) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual('report', type) self.assertEqual('test', category) self.assertEqual(1, len(xml.children)) elem = xml.children[0] self.assertEqual('test', elem.name) self.assertEqual('test_simple', elem.attr['name']) self.assertEqual('ignore', elem.attr['status']) self.assertEqual(1, len(elem.children)) trace = elem.children[0] self.assertEqual('traceback', trace.name) self.assertEqual(0, len(trace.children)) self.assertEqual(0, len(self.ctxt.output)) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(CoberturaTestCase, 'test')) suite.addTest(unittest.makeSuite(PyTestTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/build/tests/monotools.py000644 000765 000120 00000014770 11453043212 021213 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2008 Matt Good # Copyright (C) 2008-2010 Edgewall Software # Copyright (C) 2009 Grzegorz Sobanski # 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 monotools from bitten.build import FileSet from bitten.recipe import Context, Recipe class NUnitTestCase(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, monotools.nunit, self.ctxt) def test_empty_results(self): self.results_xml.write('' '' '') self.results_xml.close() monotools.nunit(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_simple(self): self.results_xml.write( '' '' '' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '' ) self.results_xml.close() monotools.nunit(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.001', test_elem.attr['duration']) self.assertEqual('success', test_elem.attr['status']) self.assertEqual('Test.dll', test_elem.attr['fixture']) def test_failure_test_simple(self): self.results_xml.write( '' '' '' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '' ) self.results_xml.close() monotools.nunit(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.001', test_elem.attr['duration']) self.assertEqual('failure', test_elem.attr['status']) self.assertEqual('Test.dll', test_elem.attr['fixture']) self.assertEqual(1, len(test_elem.children)) def test_successful_test_recursive(self): self.results_xml.write( '' '' '' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '' ) self.results_xml.close() monotools.nunit(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.001', test_elem.attr['duration']) self.assertEqual('success', test_elem.attr['status']) self.assertEqual('Test', test_elem.attr['fixture']) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(NUnitTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite') Bitten-0.6/bitten/build/tests/phptools.py000644 000765 000120 00000026345 11453043212 021033 0ustar00simonadmin000000 000000 # -*- coding: UTF-8 -*- # # Copyright (C) 2007-2010 Edgewall Software # Copyright (C) 2007 Wei Zhuo # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. import os import shutil import tempfile import unittest from bitten.build import phptools from bitten.recipe import Context, Recipe class PhpUnitTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def test_missing_param_file(self): self.assertRaises(AssertionError, phptools.phpunit, self.ctxt) def test_sample_unit_test_result(self): phpunit_xml = file(self.ctxt.resolve('phpunit.xml'), 'w') phpunit_xml.write(""" ... """) phpunit_xml.close() phptools.phpunit(self.ctxt, file_='phpunit.xml') type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('test', category) tests = list(xml.children) self.assertEqual(3, len(tests)) self.assertEqual('FooTest', tests[0].attr['fixture']) self.assertEqual('testBar', tests[0].attr['name']) self.assertEqual('failure', tests[0].attr['status']) self.assert_('FooTest.php' in tests[0].attr['file']) self.assertEqual('FooTest', tests[1].attr['fixture']) self.assertEqual('testBar2', tests[1].attr['name']) self.assertEqual('success', tests[1].attr['status']) self.assertEqual('BarTest', tests[2].attr['fixture']) self.assertEqual('testFoo', tests[2].attr['name']) self.assertEqual('success', tests[2].attr['status']) class PhpCodeCoverageTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def test_missing_param_file(self): self.assertRaises(AssertionError, phptools.coverage, self.ctxt) def test_sample_phing_code_coverage(self): coverage_xml = file(self.ctxt.resolve('phpcoverage.xml'), 'w') coverage_xml.write(""" ... ... ... """) coverage_xml.close() phptools.coverage(self.ctxt, file_='phpcoverage.xml') type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('coverage', category) coverage = list(xml.children) self.assertEqual(3, len(coverage)) self.assertEqual(7, coverage[0].attr['lines']) self.assertEqual('Foo', coverage[0].attr['name']) self.assert_('xxxx/Foo.php' in coverage[0].attr['file']) self.assertEqual(4, coverage[1].attr['lines']) self.assertEqual(50.0, coverage[1].attr['percentage']) self.assertEqual('Foo2', coverage[1].attr['name']) self.assert_('xxxx/Foo.php' in coverage[1].attr['file']) self.assertEqual(0, coverage[2].attr['lines']) self.assertEqual(100.0, coverage[2].attr['percentage']) self.assertEqual('Bar', coverage[2].attr['name']) self.assert_('xxxx/Bar.php' in coverage[2].attr['file']) def test_sample_phpunit_code_coverage(self): coverage_xml = file(self.ctxt.resolve('phpcoverage.xml'), 'w') coverage_xml.write(""" """ % ((self.basedir,)*6)) # One relative path, remaining is absolute 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(6, len(coverage)) self.assertEqual(27, sum([int(c.attr['lines']) for c in coverage])) self.assertEqual(['Foo', 'All_Foo_Tests', 'AllTests', 'All_Bar_Tests', 'All_Bar_Nested_Tests', 'Bar'], [c.attr['name'] for c in coverage]) self.assertEqual(['Foo/classes/Foo.php', 'Foo/tests/Foo/AllTests.php', 'Foo/tests/AllTests.php', 'Foo/tests/Bar/AllTests.php', 'Foo/tests/Bar/Nested/AllTests.php', 'Foo/classes/Bar.php'], [c.attr['file'] for c in coverage]) self.assertEqual([100, 0, 0, 0, 0, 100], [c.attr['percentage'] for c in coverage]) 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') Bitten-0.6/bitten/build/tests/pythontools.py000644 000765 000120 00000044673 11453043212 021571 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2008 Matt Good # Copyright (C) 2008-2010 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 PyLintTestCase(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, '.lint'), 'w') def tearDown(self): shutil.rmtree(self.basedir) def test_summary_with_absolute_path(self): # One posix + one windows path to normalize self.summary.write(""" %s/module/file1.py:42: [C] Missing docstring %s\\module\\file2.py:42: [C] Missing docstring """ % (self.ctxt.basedir, self.ctxt.basedir)) self.summary.close() pythontools.pylint(self.ctxt, file_=self.summary.name) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('lint', category) self.assertEqual(2, len(xml.children)) child = xml.children[0] self.assertEqual('problem', child.name) self.assertEqual('module/file1.py', child.attr['file']) child = xml.children[1] self.assertEqual('problem', child.name) self.assertEqual('module/file2.py', child.attr['file']) def test_summary_with_relative_path(self): # One posix + one windows path to normalize self.summary.write(""" module/file1.py:42: [C] Missing docstring module\\file2.py:42: [C] Missing docstring """) self.summary.close() pythontools.pylint(self.ctxt, file_=self.summary.name) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('lint', category) self.assertEqual(2, len(xml.children)) child = xml.children[0] self.assertEqual('problem', child.name) self.assertEqual('module/file1.py', child.attr['file']) child = xml.children[1] self.assertEqual('problem', child.name) self.assertEqual('module/file2.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('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('test/module.py', child.attr['file']) self.assertEqual(0, child.attr['percentage']) self.assertEqual(5, child.attr['lines']) def test_summary_with_non_python_files(self): "Figleaf coverage reports should not include files that do not end in .py" pickle.dump({}, self.summary) self.summary.close() sourcefile = self.ctxt.resolve(self._create_file('test', 'document.txt')) open(sourcefile, 'w').write("\n") pythontools.figleaf(self.ctxt, summary=self.summary.name, include='test/*') type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('coverage', category) self.assertEqual(0, len(xml.children)) class FilenameNormalizationTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def _create_file(self, *path): filename = os.path.join(self.basedir, *path) dirname = os.path.dirname(filename) os.makedirs(dirname) fd = file(filename, 'w') fd.close() return filename[len(self.basedir) + 1:] def test_absolute_path(self): filename = os.sep.join([self.ctxt.basedir, 'test', 'module.py']) self._create_file('test', 'module.py') filenames = pythontools._normalize_filenames( self.ctxt, [filename], FileSet(self.ctxt.basedir, '**/*.py', None)) self.assertEqual(['test/module.py'], list(filenames)) class UnittestTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) self.results_xml = open(os.path.join(self.basedir, 'test-results.xml'), 'w') def tearDown(self): shutil.rmtree(self.basedir) def test_missing_file_param(self): self.results_xml.close() self.assertRaises(AssertionError, pythontools.unittest, self.ctxt) def test_empty_results(self): self.results_xml.write('' '' '') self.results_xml.close() pythontools.unittest(self.ctxt, self.results_xml.name) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(Recipe.REPORT, type) self.assertEqual('test', category) self.assertEqual(0, len(xml.children)) def test_successful_test(self): self.results_xml.write('' '' '' '' % os.path.join(self.ctxt.basedir, 'bar_test.py')) self.results_xml.close() pythontools.unittest(self.ctxt, self.results_xml.name) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(1, len(xml.children)) test_elem = xml.children[0] self.assertEqual('test', test_elem.name) self.assertEqual('0.12', test_elem.attr['duration']) self.assertEqual('success', test_elem.attr['status']) self.assertEqual('bar_test.py', test_elem.attr['file']) self.assertEqual('test_foo (pkg.BarTestCase)', test_elem.attr['name']) def test_file_path_normalization(self): self.results_xml.write('' '' '' '' % os.path.join(self.ctxt.basedir, 'bar_test.py')) self.results_xml.close() pythontools.unittest(self.ctxt, self.results_xml.name) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(1, len(xml.children)) self.assertEqual('bar_test.py', xml.children[0].attr['file']) def test_missing_file_attribute(self): self.results_xml.write('' '' '' '') self.results_xml.close() pythontools.unittest(self.ctxt, self.results_xml.name) type, category, generator, xml = self.ctxt.output.pop() self.assertEqual(1, len(xml.children)) self.assertEqual(None, xml.children[0].attr.get('file')) def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(CoverageTestCase, 'test')) suite.addTest(unittest.makeSuite(TraceTestCase, 'test')) suite.addTest(unittest.makeSuite(PyLintTestCase, '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') Bitten-0.6/bitten/build/tests/xmltools.py000644 000765 000120 00000010011 11453043212 021023 0ustar00simonadmin000000 000000 # -*- coding: utf-8 -*- # # Copyright (C) 2005-2007 Christopher Lenz # Copyright (C) 2007-2010 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. import os import shutil import tempfile import unittest from bitten.build import xmltools from bitten.recipe import Context from bitten.util import xmlio class TransformTestCase(unittest.TestCase): def setUp(self): self.basedir = os.path.realpath(tempfile.mkdtemp()) self.ctxt = Context(self.basedir) def tearDown(self): shutil.rmtree(self.basedir) def test_transform_no_src(self): self.assertRaises(AssertionError, xmltools.transform, self.ctxt) def test_transform_no_dest(self): self.assertRaises(AssertionError, xmltools.transform, self.ctxt, src='src.xml') def test_transform_no_stylesheet(self): self.assertRaises(AssertionError, xmltools.transform, self.ctxt, src='src.xml', dest='dest.xml') def test_transform(self): src_file = file(self.ctxt.resolve('src.xml'), 'w') try: src_file.write(""" Document Title
Section Title This is a test. This is a note.
""") finally: src_file.close() style_file = file(self.ctxt.resolve('style.xsl'), 'w') try: style_file.write(""" <xsl:value-of select="title"/>

NOTE:

""") finally: style_file.close() xmltools.transform(self.ctxt, src='src.xml', dest='dest.xml', stylesheet='style.xsl') dest_file = file(self.ctxt.resolve('dest.xml')) try: dest = xmlio.parse(dest_file) finally: dest_file.close() self.assertEqual('html', dest.name) self.assertEqual('http://www.w3.org/TR/xhtml1/strict', dest.namespace) children = list(dest.children()) self.assertEqual(2, len(children)) self.assertEqual('head', children[0].name) head_children = list(children[0].children()) self.assertEqual(1, len(head_children)) self.assertEqual('title', head_children[0].name) self.assertEqual('Document Title', head_children[0].gettext()) self.assertEqual('body', children[1].name) body_children = list(children[1].children()) self.assertEqual(4, len(body_children)) self.assertEqual('h1', body_children[0].name) self.assertEqual('Document Title', body_children[0].gettext()) self.assertEqual('h2', body_children[1].name) self.assertEqual('Section Title', body_children[1].gettext()) self.assertEqual('p', body_children[2].name) self.assertEqual('This is a test.', body_children[2].gettext()) self.assertEqual('p', body_children[3].name) self.assertEqual('note', body_children[3].attr['class']) self.assertEqual('This is a note.', body_children[3].gettext()) def suite(): suite = unittest.TestSuite() if xmltools.have_libxslt or xmltools.have_msxml: suite.addTest(unittest.makeSuite(TransformTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite')