Add buildbot code for what will become the Vagrant CI

This commit is contained in:
Mitchell Hashimoto 2011-11-12 10:20:07 -08:00
parent 60a350f9c7
commit 44540f369d
28 changed files with 1074 additions and 0 deletions

13
.gitignore vendored
View File

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

72
test/buildbot/README.md Normal file
View File

@ -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/

View File

@ -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()

View File

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

View File

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

View File

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

View File

@ -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)
]

View File

@ -0,0 +1,7 @@
"""
Contains various buildsteps that the build master uses.
"""
import os
# TODO

View File

@ -0,0 +1,7 @@
"""
Contains the logic to build and return the change sources for
the build master.
"""
def get_change_sources():
return []

View File

@ -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 []

View File

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

View File

@ -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]

View File

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

View File

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

View File

@ -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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

@ -0,0 +1,4 @@
buildbot==0.8.5
buildbot-slave==0.8.5
choices==0.3.4
pytest==2.1.0

View File

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

View File

@ -0,0 +1 @@
Buildslave Admin <foo@bar.com>

View File

@ -0,0 +1 @@
Please put a description of this build host here

View File

View File

View File

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