diff --git a/.gitignore b/.gitignore index e5005cf67..d132a1526 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,16 @@ # Vagrant stuff acceptance_config.yml package.box +test/buildbot/config.cfg +test/buildbot/master/http.log +test/buildbot/master/master.cfg.sample +test/buildbot/master/twistd.log +test/buildbot/master/twistd.pid +test/buildbot/master/state.sqlite +test/buildbot/master/kiip-development/ +test/buildbot/slave/twistd.hostname +test/buildbot/slave/twistd.log +test/buildbot/slave/twistd.pid Vagrantfile .vagrant @@ -19,6 +29,9 @@ _site/* .yardoc/ doc/ +# Python +*.pyc + # Rubinius *.rbc diff --git a/test/buildbot/README.md b/test/buildbot/README.md new file mode 100644 index 000000000..7041b4cda --- /dev/null +++ b/test/buildbot/README.md @@ -0,0 +1,72 @@ +# Vagrant Buildbot System + +This is the code for the Vagrant buildbot system. [Buildbot](http://buildbot.net) +is a continuous integration system that offers an extreme amount +of flexibility and power. + +This directory contains a few subdirectories to setup this CI system: + +* `buildbot_config` - This contains the custom Python code to setup the + various parts of the buildbot configuration. +* `master` - This is mostly auto-generated from Buildbot itself, however + the `master.cfg` file is the configuration used on the buildmaster. +* `slave`- This is mostly auto-generated from Buildbot, but the + `buildbot.tac` contains some custom code in it to connect to the Vagrant + buildmaster. + +**NOTE:** One of the dependencies for the Vagrant CI system is currently +not public, and as such can't be setup by the general public. This will be +fixed in the next couple weeks. + +## Contribute a CI Slave! + +**NOTE:** The slave contribution process is still not completely setup and +will be ironed out very soon after the CI system is up and running. + +Vagrant is an open source profit which doesn't have any income from support, +services, or otherwise. All Vagrant slave machines are donated by the +community. Donating a machine doesn't require anything more than installing +and running the slave software. Vagrant is specifically looking for slave +machines that provide a diverse set of operating systems and cpu architectures +for testing Vagrant. + +## Setting up the Buildmaster + +To set up the buildmaster, clone out this directory somewhere and install +the dependencies: + + pip install -r requirements.txt + +Once the dependencies are installed, create a configuration file with the +settings you want to use somewhere. The settings available for a master are +defined in `buildbot_config/config/master.py`. An example configuration file: + + [master] + slaves=foo:password,bar:anotherpassword + web_port=8000 + +Execute the buildbot using: + + BUILDBOT_CONFIG=/path/to/my/config.cfg buildbot start master/ + +## Setting up a Buildslave + +To set up a slave, clone out this directory and install the dependencies: + + pip install -r requirements.txt + +Then, create a configuration file with the slave settings. The settings +available for a slave are defined in `buildbot_config/config/slave.py`. +An example configuration file: + + [slave] + master_host=ci.vagrantup.com + master_port=9989 + name=the-love-machine + password=foobarbaz + +Note that the password above will be assigned to you as part of donating +any slave machine, since it must be setup on the buildmaster side as well. +Once the configuration is done, run the slave: + + BUILDBOT_CONFIG=/path/to/my/config.cfg buildslave start slave/ diff --git a/test/buildbot/buildbot_config/__init__.py b/test/buildbot/buildbot_config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/buildbot/buildbot_config/config/__init__.py b/test/buildbot/buildbot_config/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/buildbot/buildbot_config/config/loader.py b/test/buildbot/buildbot_config/config/loader.py new file mode 100644 index 000000000..a6e9fb020 --- /dev/null +++ b/test/buildbot/buildbot_config/config/loader.py @@ -0,0 +1,24 @@ +""" +This module contains the configuration loader for a specific +choices instance. This is used internally to load the settings. +""" + +import os + +from choices import ConfigFileLoader + +def load_settings(choices, type): + """ + This will load the proper settings for the given choices + instance. + + :Parameters: + - `choices`: The choices instance to load. + - `type`: The type of configuration, either "master" or + "slave" + """ + if "BUILDBOT_CONFIG" not in os.environ: + raise ValueError, "BUILDBOT_CONFIG must be set to point to where the configuration file is." + + choices.add_loader(ConfigFileLoader(os.environ["BUILDBOT_CONFIG"], type)) + return choices.load() diff --git a/test/buildbot/buildbot_config/config/master.py b/test/buildbot/buildbot_config/config/master.py new file mode 100644 index 000000000..89d1852e8 --- /dev/null +++ b/test/buildbot/buildbot_config/config/master.py @@ -0,0 +1,23 @@ +""" +This module contains the choices definition for settings required +for the build master to run. +""" + +from choices import Choices + +from loader import load_settings + +#---------------------------------------------------------------------- +# Define the Settings +#---------------------------------------------------------------------- +c = Choices() +c.define('title', type=str, help="Buildmaster title") +c.define('title_url', type=str, help="URL for title page") +c.define('buildbot_url', type=str, help="URL to the buildbot master.") +c.define('slaves', type=str, help="A list of the slave machines. The format should be name:password,name:password,...") +c.define('web_port', type=int, help="Port to listen on for web service.") + +#---------------------------------------------------------------------- +# Load the Settings +#---------------------------------------------------------------------- +options = load_settings(c, "master") diff --git a/test/buildbot/buildbot_config/config/slave.py b/test/buildbot/buildbot_config/config/slave.py new file mode 100644 index 000000000..a9fe37e14 --- /dev/null +++ b/test/buildbot/buildbot_config/config/slave.py @@ -0,0 +1,22 @@ +""" +This module contains the choices definition for settings required for the +build slave to run. +""" + +from choices import Choices + +from loader import load_settings + +#---------------------------------------------------------------------- +# Define the Settings +#---------------------------------------------------------------------- +c = Choices() +c.define("master_host", type=str, help="Host of the build master.") +c.define("master_port", type=int, help="Port that is listening or build masters.") +c.define("name", type=str, help="Name of the slave machine.") +c.define("password", type=str, help="Password for the slave machine to communicate with the master.") + +#---------------------------------------------------------------------- +# Load the Settings +#---------------------------------------------------------------------- +options = load_settings(c, "slave") diff --git a/test/buildbot/buildbot_config/master/__init__.py b/test/buildbot/buildbot_config/master/__init__.py new file mode 100644 index 000000000..af4542f8e --- /dev/null +++ b/test/buildbot/buildbot_config/master/__init__.py @@ -0,0 +1,6 @@ +import buildsteps +from builders import get_builders +from change_sources import get_change_sources +from schedulers import get_schedulers +from slaves import get_slaves_from_config +from status import get_status diff --git a/test/buildbot/buildbot_config/master/builders.py b/test/buildbot/buildbot_config/master/builders.py new file mode 100644 index 000000000..1dcb05dde --- /dev/null +++ b/test/buildbot/buildbot_config/master/builders.py @@ -0,0 +1,26 @@ +""" +This module contains the logic to create and return the various builders +that this buildmaster supports. The builders are responsible for taking +a set of changes and giving the steps necessary to build the project. +""" + +from buildbot.config import BuilderConfig +from buildbot.process.factory import BuildFactory +from buildbot.process.properties import WithProperties +from buildbot.steps.source.git import Git + +from buildbot_config.master import buildsteps + +def get_builders(slaves): + """ + This returns a list of builder configurations for the given + slaves. + """ + f = BuildFactory() + # TODO + + return [BuilderConfig( + name="vagrant-master", + slavenames=[s.slavename for s in slaves], + factory=f) + ] diff --git a/test/buildbot/buildbot_config/master/buildsteps.py b/test/buildbot/buildbot_config/master/buildsteps.py new file mode 100644 index 000000000..4431bef32 --- /dev/null +++ b/test/buildbot/buildbot_config/master/buildsteps.py @@ -0,0 +1,7 @@ +""" +Contains various buildsteps that the build master uses. +""" + +import os + +# TODO diff --git a/test/buildbot/buildbot_config/master/change_sources.py b/test/buildbot/buildbot_config/master/change_sources.py new file mode 100644 index 000000000..18a40016c --- /dev/null +++ b/test/buildbot/buildbot_config/master/change_sources.py @@ -0,0 +1,7 @@ +""" +Contains the logic to build and return the change sources for +the build master. +""" + +def get_change_sources(): + return [] diff --git a/test/buildbot/buildbot_config/master/schedulers.py b/test/buildbot/buildbot_config/master/schedulers.py new file mode 100644 index 000000000..d48b04a98 --- /dev/null +++ b/test/buildbot/buildbot_config/master/schedulers.py @@ -0,0 +1,7 @@ +""" +This module contains the logic which returns the set of +schedulers to use for the build master. +""" + +def get_schedulers(): + return [] diff --git a/test/buildbot/buildbot_config/master/slaves.py b/test/buildbot/buildbot_config/master/slaves.py new file mode 100644 index 000000000..f890a2109 --- /dev/null +++ b/test/buildbot/buildbot_config/master/slaves.py @@ -0,0 +1,60 @@ +""" +This module contains the classes and methods which help load the +list of available build slaves based on the configuration. +""" + +from buildbot.buildslave import BuildSlave + +class BuildSlavesFromSlaveConfigs(list): + """ + This object turns the ``SlaveConfig`` objects into actual + ``BuildSlave`` objects. This list can be directly used as the + setting. + """ + + def __init__(self, configs): + for config in configs: + self.append(BuildSlave(config.name, config.password)) + +class SlaveListFromConfig(list): + """ + This object knows how to parse the slave configuration settings + and load them into ``SlaveConfig`` value objects. The results + can be read directly from this list. + """ + + def __init__(self, config): + for config in self._slave_configs(config): + self.append(config) + + def _slave_configs(self, config): + """ + Returns an array of all the slaves that were configured + with the given configuration string. + """ + results = [] + for single in config.split(","): + results.append(SlaveConfig(*single.split(":"))) + + return results + +class SlaveConfig(object): + """ + This is a value class, meant to be immutable, representing + the configuration of a single slave. + """ + + def __init__(self, name, password): + self.name = name + self.password = password + + def __eq__(self, other): + """ + Provides equality tests for slave configurations, specifically + for tests. + """ + return self.__dict__ == other.__dict__ + +# Shortcut methods to make things a bit nicer +def get_slaves_from_config(config): + return BuildSlavesFromSlaveConfigs(SlaveListFromConfig(config)) diff --git a/test/buildbot/buildbot_config/master/status.py b/test/buildbot/buildbot_config/master/status.py new file mode 100644 index 000000000..e47e97a21 --- /dev/null +++ b/test/buildbot/buildbot_config/master/status.py @@ -0,0 +1,31 @@ +""" +This module returns the given status handlers to enable for the +buildbot master. +""" + +from buildbot.status import html +from buildbot.status.web.authz import Authz + +def get_status(options): + """ + Returns a list of status targets for the build master. + """ + authz = Authz( + gracefulShutdown = True, + forceBuild = True, + forceAllBuilds = True, + pingBuilder = True, + stopBuild = True, + stopAllBuilds = True, + cancelPendingBuild = True, + stopChange = True, + cleanShutdown= True + ) + + web_status = html.WebStatus( + http_port = options.web_port, + authz = authz, + order_console_by_time = True + ) + + return [web_status] diff --git a/test/buildbot/master/Makefile.sample b/test/buildbot/master/Makefile.sample new file mode 100644 index 000000000..e840b9148 --- /dev/null +++ b/test/buildbot/master/Makefile.sample @@ -0,0 +1,28 @@ +# -*- makefile -*- + +# This is a simple makefile which lives in a buildmaster +# directory (next to the buildbot.tac file). It allows you to start/stop the +# master by doing 'make start' or 'make stop'. + +# The 'reconfig' target will tell a buildmaster to reload its config file. + +start: + twistd --no_save -y buildbot.tac + +stop: + if [ -e twistd.pid ]; \ + then kill `cat twistd.pid`; \ + else echo "Nothing to stop."; \ + fi + +reconfig: + if [ -e twistd.pid ]; \ + then kill -HUP `cat twistd.pid`; \ + else echo "Nothing to reconfig."; \ + fi + +log: + if [ -e twistd.log ]; \ + then tail -f twistd.log; \ + else echo "Nothing to tail."; \ + fi diff --git a/test/buildbot/master/buildbot.tac b/test/buildbot/master/buildbot.tac new file mode 100644 index 000000000..16f0972c1 --- /dev/null +++ b/test/buildbot/master/buildbot.tac @@ -0,0 +1,36 @@ + +import os + +from twisted.application import service +from buildbot.master import BuildMaster + +basedir = r'.' +rotateLength = 10000000 +maxRotatedFiles = 10 + +# if this is a relocatable tac file, get the directory containing the TAC +if basedir == '.': + import os.path + basedir = os.path.abspath(os.path.dirname(__file__)) + +# note: this line is matched against to check that this is a buildmaster +# directory; do not edit it. +application = service.Application('buildmaster') + +try: + from twisted.python.logfile import LogFile + from twisted.python.log import ILogObserver, FileLogObserver + logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, + maxRotatedFiles=maxRotatedFiles) + application.setComponent(ILogObserver, FileLogObserver(logfile).emit) +except ImportError: + # probably not yet twisted 8.2.0 and beyond, can't set log yet + pass + +configfile = r'master.cfg' + +m = BuildMaster(basedir, configfile) +m.setServiceParent(application) +m.log_rotation.rotateLength = rotateLength +m.log_rotation.maxRotatedFiles = maxRotatedFiles + diff --git a/test/buildbot/master/master.cfg b/test/buildbot/master/master.cfg new file mode 100644 index 000000000..d0db4d7a1 --- /dev/null +++ b/test/buildbot/master/master.cfg @@ -0,0 +1,67 @@ +# -*- python -*- +# ex: set syntax=python: + +from buildbot_config.config.master import options +from buildbot_config.master import ( + get_builders, + get_change_sources, + get_schedulers, + get_slaves_from_config, + get_status) + +# This is the special key that Buildbot looks for. We use +# an alias to ``c`` to save us a lot of typing. +c = BuildmasterConfig = {} + +#---------------------------------------------------------------------- +# Project Identity and Basic Configuration +#---------------------------------------------------------------------- +# Title and URL of project +c['title'] = options.title +c['titleURL'] = options.title_url + +# URL to the actual buildbot installation +c['buildbotURL'] = options.buildbot_url + +# This specifies what database buildbot uses to store change and scheduler +# state. You can leave this at its default for all but the largest +# installations. +c['db_url'] = "sqlite:///state.sqlite" + +#---------------------------------------------------------------------- +# Data Storage and Caching +#---------------------------------------------------------------------- +# Time horizons. These specify the number of items that are kept +# persisted on disk before being pruned away. See the buildbot +# documentation for more information on each horizon setting. +c['changeHorizon'] = 0 +c['buildHorizon'] = 0 +c['eventHorizon'] = 0 +c['logHorizon'] = 0 + +# Cache sizes. These are the number of the respective objects to +# hold in memory while buildbot is running. By default buildbot +# only stores 1, so all the numbers are greatly increased. +c['caches'] = { + 'Changes' : 100, + 'chdicts' : 100, + 'BuildRequests' : 10, + 'SourceStamps' : 20, + 'ssdicts' : 20, + 'objectids' : 10, + 'usdicts' : 100, +} +c['buildCacheSize'] = 500 + +#---------------------------------------------------------------------- +# Build Slaves +#---------------------------------------------------------------------- +# The port that the buildmaster listens for buildslaves on. +c['slavePortnum'] = 9989 + +# The actual list of slave machines we have. +c['slaves'] = get_slaves_from_config(options.slaves) +c['status'] = get_status(options) +c['builders'] = get_builders(c['slaves']) +c['schedulers'] = get_schedulers() +c['change_source'] = get_change_sources() diff --git a/test/buildbot/master/public_html/bg_gradient.jpg b/test/buildbot/master/public_html/bg_gradient.jpg new file mode 100644 index 000000000..6c2e5ddf2 Binary files /dev/null and b/test/buildbot/master/public_html/bg_gradient.jpg differ diff --git a/test/buildbot/master/public_html/default.css b/test/buildbot/master/public_html/default.css new file mode 100644 index 000000000..ede0b57ce --- /dev/null +++ b/test/buildbot/master/public_html/default.css @@ -0,0 +1,545 @@ +body.interface { + margin-left: 30px; + margin-right: 30px; + margin-top: 20px; + margin-bottom: 50px; + padding: 0; + background: url(bg_gradient.jpg) repeat-x; + font-family: Verdana, sans-serif; + font-size: 10px; + background-color: #fff; + color: #333; +} + +a:link,a:visited,a:active { + color: #444; +} + +table { + border-spacing: 1px 1px; +} + +table td { + padding: 3px 4px 3px 4px; + text-align: center; +} + +.Project { + min-width: 6em; +} + +.LastBuild,.Activity { + padding: 0 0 0 4px; +} + +.LastBuild,.Activity,.Builder,.BuildStep { + min-width: 5em; +} + +/* Chromium Specific styles */ +div.BuildResultInfo { + color: #444; +} + +div.Announcement { + margin-bottom: 1em; +} + +div.Announcement>a:hover { + color: black; +} + +div.Announcement>div.Notice { + background-color: #afdaff; + padding: 0.5em; + font-size: 16px; + text-align: center; +} + +div.Announcement>div.Open { + border: 3px solid #8fdf5f; + padding: 0.5em; + font-size: 16px; + text-align: center; +} + +div.Announcement>div.Closed { + border: 5px solid #e98080; + padding: 0.5em; + font-size: 24px; + font-weight: bold; + text-align: center; +} + +td.Time { + color: #000; + border-bottom: 1px solid #aaa; + background-color: #eee; +} + +td.Activity,td.Change,td.Builder { + color: #333333; + background-color: #CCCCCC; +} + +td.Change { + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +td.Event { + color: #777; + background-color: #ddd; + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; +} + +td.Activity { + border-top-left-radius: 10px; + -webkit-border-top-left-radius: 10px; + -moz-border-radius-topleft: 10px; + min-height: 20px; + padding: 2px 0 2px 0; +} + +td.idle,td.waiting,td.offline,td.building { + border-top-left-radius: 0px; + -webkit-border-top-left-radius: 0px; + -moz-border-radius-topleft: 0px; +} + +.LastBuild { + border-top-left-radius: 5px; + -webkit-border-top-left-radius: 5px; + -moz-border-radius-topleft: 5px; + border-top-right-radius: 5px; + -webkit-border-top-right-radius: 5px; + -moz-border-radius-topright: 5px; +} + +/* Console view styles */ +td.DevRev { + padding: 4px 8px 4px 8px; + color: #333333; + border-top-left-radius: 5px; + -webkit-border-top-left-radius: 5px; + -moz-border-radius-topleft: 5px; + background-color: #eee; + width: 1%; +} + +td.DevRevCollapse { + border-bottom-left-radius: 5px; + -webkit-border-bottom-left-radius: 5px; + -moz-border-radius-bottomleft: 5px; +} + +td.DevName { + padding: 4px 8px 4px 8px; + color: #333333; + background-color: #eee; + width: 1%; + text-align: left; +} + +td.DevStatus { + padding: 4px 4px 4px 4px; + color: #333333; + background-color: #eee; +} + +td.DevSlave { + padding: 4px 4px 4px 4px; + color: #333333; + background-color: #eee; +} + +td.first { + border-top-left-radius: 5px; + -webkit-border-top-left-radius: 5px; + -moz-border-radius-topleft: 5px; +} + +td.last { + border-top-right-radius: 5px; + -webkit-border-top-right-radius: 5px; + -moz-border-radius-topright: 5px; +} + +td.DevStatusCategory { + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-width: 1px; + border-style: solid; +} + +td.DevStatusCollapse { + border-bottom-right-radius: 5px; + -webkit-border-bottom-right-radius: 5px; + -moz-border-radius-bottomright: 5px; +} + +td.DevDetails { + font-weight: normal; + padding: 8px 8px 8px 8px; + color: #333333; + background-color: #eee; + text-align: left; +} + +td.DevDetails li a { + padding-right: 5px; +} + +td.DevComment { + font-weight: normal; + padding: 8px 8px 8px 8px; + color: #333333; + border-bottom-right-radius: 5px; + -webkit-border-bottom-right-radius: 5px; + -moz-border-radius-bottomright: 5px; + border-bottom-left-radius: 5px; + -webkit-border-bottom-left-radius: 5px; + -moz-border-radius-bottomleft: 5px; + background-color: #eee; + text-align: left; +} + +td.Alt { + background-color: #ddd; +} + +.legend { + border-radius: 5px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + width: 100px; + max-width: 100px; + text-align: center; + padding: 2px 2px 2px 2px; + height: 14px; + white-space: nowrap; +} + +.DevStatusBox { + text-align: center; + height: 20px; + padding: 0 2px; + line-height: 0; + white-space: nowrap; +} + +.DevStatusBox a { + opacity: 0.85; + border-width: 1px; + border-style: solid; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + display: block; + width: 90%; + height: 20px; + line-height: 20px; + margin-left: auto; + margin-right: auto; +} + +.DevSlaveBox { + text-align: center; + height: 10px; + padding: 0 2px; + line-height: 0; + white-space: nowrap; +} + +.DevSlaveBox a { + opacity: 0.85; + border-width: 1px; + border-style: solid; + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + display: block; + width: 90%; + height: 10px; + line-height: 20px; + margin-left: auto; + margin-right: auto; +} + +a.noround { + border-radius: 0px; + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + position: relative; + margin-top: -8px; + margin-bottom: -8px; + height: 36px; + border-top-width: 0; + border-bottom-width: 0; +} + +a.begin { + border-top-width: 1px; + position: relative; + margin-top: 0px; + margin-bottom: -7px; + height: 27px; + border-top-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-right-radius: 4px; + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; +} + +a.end { + border-bottom-width: 1px; + position: relative; + margin-top: -7px; + margin-bottom: 0px; + height: 27px; + border-bottom-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; +} + +.center_align { + text-align: center; +} + +.right_align { + text-align: right; +} + +.left_align { + text-align: left; +} + +div.BuildWaterfall { + border-radius: 7px; + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + position: absolute; + left: 0px; + top: 0px; + background-color: #FFFFFF; + padding: 4px 4px 4px 4px; + float: left; + display: none; + border-width: 1px; + border-style: solid; +} + +/* LastBuild, BuildStep states */ +.success { + color: #000; + background-color: #8d4; + border-color: #4F8530; +} + +.failure { + color: #000; + background-color: #e88; + border-color: #A77272; +} + +.warnings { + color: #FFFFFF; + background-color: #fa3; + border-color: #C29D46; +} + +.skipped { + color: #000; + background: #AADDEE; + border-color: #AADDEE; +} + +.exception,.retry { + color: #FFFFFF; + background-color: #c6c; + border-color: #ACA0B3; +} + +.start { + color: #000; + background-color: #ccc; + border-color: #ccc; +} + +.running,.waiting,td.building { + color: #000; + background-color: #fd3; + border-color: #C5C56D; +} + +.offline,td.offline { + color: #FFFFFF; + background-color: #777777; + border-color: #dddddd; +} + + +.start { + border-bottom-left-radius: 10px; + -webkit-border-bottom-left-radius: 10px; + -moz-border-radius-bottomleft: 10px; + border-bottom-right-radius: 10px; + -webkit-border-bottom-right-radius: 10px; + -moz-border-radius-bottomright: 10px; +} + +.notstarted { + border-width: 1px; + border-style: solid; + border-color: #aaa; + background-color: #fff; +} + +.closed { + background-color: #ff0000; +} + +.closed .large { + font-size: 1.5em; + font-weight: bolder; +} + +td.Project a:hover,td.start a:hover { + color: #000; +} + +.mini-box { + text-align: center; + height: 20px; + padding: 0 2px; + line-height: 0; + white-space: nowrap; +} + +.mini-box a { + border-radius: 0; + -webkit-border-radius: 0; + -moz-border-radius: 0; + display: block; + width: 100%; + height: 20px; + line-height: 20px; + margin-top: -30px; +} + +.mini-closed { + -box-sizing: border-box; + -webkit-box-sizing: border-box; + border: 4px solid red; +} + +/* grid styles */ +table.Grid { + border-collapse: collapse; +} + +table.Grid tr td { + padding: 0.2em; + margin: 0px; + text-align: center; +} + +table.Grid tr td.title { + font-size: 90%; + border-right: 1px gray solid; + border-bottom: 1px gray solid; +} + +table.Grid tr td.sourcestamp { + font-size: 90%; +} + +table.Grid tr td.builder { + text-align: right; + font-size: 90%; +} + +table.Grid tr td.build { + border: 1px gray solid; +} + +/* column container */ +div.column { + margin: 0 2em 2em 0; + float: left; +} + +/* info tables */ +table.info { + border-spacing: 1px; +} + +table.info td { + padding: 0.1em 1em 0.1em 1em; + text-align: center; +} + +table.info th { + padding: 0.2em 1.5em 0.2em 1.5em; + text-align: center; +} + +table.info td.left { + text-align: left +} + +.alt { + background-color: #f6f6f6; +} + +li { + padding: 0.1em 1em 0.1em 1em; +} + +.result { + padding: 0.3em 1em 0.3em 1em; +} + +/* log view */ +.log * { + vlink: #800080; + font-family: "Courier New", courier, monotype, monospace; +} + +span.stdout { + color: black; +} + +span.stderr { + color: red; +} + +span.header { + color: blue; +} + +/* revision & email */ +.revision .full { + display: none; +} + +.user .email { + display: none; +} + +/* change comments (use regular colors here) */ +pre.comments>a:link,pre.comments>a:visited { + color: blue; +} + +pre.comments>a:active { + color: purple; +} diff --git a/test/buildbot/master/public_html/favicon.ico b/test/buildbot/master/public_html/favicon.ico new file mode 100644 index 000000000..b0b0845dc Binary files /dev/null and b/test/buildbot/master/public_html/favicon.ico differ diff --git a/test/buildbot/master/public_html/robots.txt b/test/buildbot/master/public_html/robots.txt new file mode 100644 index 000000000..b139adea1 --- /dev/null +++ b/test/buildbot/master/public_html/robots.txt @@ -0,0 +1,10 @@ +User-agent: * +Disallow: /waterfall +Disallow: /builders +Disallow: /changes +Disallow: /buildslaves +Disallow: /schedulers +Disallow: /one_line_per_build +Disallow: /builders +Disallow: /grid +Disallow: /tgrid diff --git a/test/buildbot/requirements.txt b/test/buildbot/requirements.txt new file mode 100644 index 000000000..28704572e --- /dev/null +++ b/test/buildbot/requirements.txt @@ -0,0 +1,4 @@ +buildbot==0.8.5 +buildbot-slave==0.8.5 +choices==0.3.4 +pytest==2.1.0 diff --git a/test/buildbot/slave/buildbot.tac b/test/buildbot/slave/buildbot.tac new file mode 100644 index 000000000..9654aa1bf --- /dev/null +++ b/test/buildbot/slave/buildbot.tac @@ -0,0 +1,43 @@ +import os + +from twisted.application import service +from buildslave.bot import BuildSlave + +from buildbot_config.config.slave import options + +basedir = r'.' +rotateLength = 10000000 +maxRotatedFiles = 10 + +# if this is a relocatable tac file, get the directory containing the TAC +if basedir == '.': + import os.path + basedir = os.path.abspath(os.path.dirname(__file__)) + +# note: this line is matched against to check that this is a buildslave +# directory; do not edit it. +application = service.Application('buildslave') + +try: + from twisted.python.logfile import LogFile + from twisted.python.log import ILogObserver, FileLogObserver + logfile = LogFile.fromFullPath(os.path.join(basedir, "twistd.log"), rotateLength=rotateLength, + maxRotatedFiles=maxRotatedFiles) + application.setComponent(ILogObserver, FileLogObserver(logfile).emit) +except ImportError: + # probably not yet twisted 8.2.0 and beyond, can't set log yet + pass + +buildmaster_host = options.master_host +port = options.master_port +slavename = options.name +passwd = options.password +keepalive = 600 +usepty = 0 +umask = None +maxdelay = 300 + +s = BuildSlave(buildmaster_host, port, slavename, passwd, basedir, + keepalive, usepty, umask=umask, maxdelay=maxdelay) +s.setServiceParent(application) + diff --git a/test/buildbot/slave/info/admin b/test/buildbot/slave/info/admin new file mode 100644 index 000000000..8e788878a --- /dev/null +++ b/test/buildbot/slave/info/admin @@ -0,0 +1 @@ +Buildslave Admin diff --git a/test/buildbot/slave/info/host b/test/buildbot/slave/info/host new file mode 100644 index 000000000..7ffd2d9b4 --- /dev/null +++ b/test/buildbot/slave/info/host @@ -0,0 +1 @@ +Please put a description of this build host here diff --git a/test/buildbot/tests/__init__.py b/test/buildbot/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/buildbot/tests/master/__init__.py b/test/buildbot/tests/master/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/buildbot/tests/master/test_slaves.py b/test/buildbot/tests/master/test_slaves.py new file mode 100644 index 000000000..ddf53bcf3 --- /dev/null +++ b/test/buildbot/tests/master/test_slaves.py @@ -0,0 +1,41 @@ +from buildbot.buildslave import BuildSlave + +from buildbot_config.master.slaves import ( + BuildSlavesFromSlaveConfigs, + SlaveConfig, + SlaveListFromConfig) + +class TestSlaveListFromConfig(object): + Klass = SlaveListFromConfig + + def test_parse_single(self): + """ + Tests that the config parser can parse a single + slave. + """ + instance = self.Klass("foo:bar") + assert 1 == len(instance) + assert SlaveConfig("foo", "bar") == instance[0] + + def test_parse_multiple(self): + """ + Tests that the config parser can parse multiple + slaves. + """ + instance = self.Klass("foo:bar,bar:baz") + expected = [SlaveConfig("foo", "bar"), SlaveConfig("bar", "baz")] + + assert 2 == len(instance) + assert expected == instance + +class TestBuildSlavesFromSlaveConfig(object): + Klass = BuildSlavesFromSlaveConfigs + + def test_returns_build_slaves(self): + """ + Tests that build slaves are properly returned for each + slave configuration. + """ + instance = self.Klass([SlaveConfig("foo", "bar")]) + assert 1 == len(instance) + assert isinstance(instance[0], BuildSlave)