OpenChange Project Infrastructure¶
Buildbot¶
Buildbot is a Continuous Integration system, allowing distributed testing across multiple configurations / environments.
About the buildbot¶
You can find the OpenChange buildbot at http://buildbot.openchange.org (or http://buildbot.openchange.org:8010 - there is a redirect)
It has an IRC client (oc_buildbot) that lives on #openchange-commits, on Freenode.
You can find out more about buildbot at http://buildbot.net:80/trac
The short form version is that it is a server (the buildmaster) that waits for changes to the svn repository (delivered as email), parses the email, waits for the repository to stabilise (no changes for a set period), and then kicks off the builder processes.
There are several builders which check different aspects of openchange on different architectures. There is a list of builders at http://buildbot.openchange.org/grid
The builders report status back to the buildmaster, which in turn sends status updates (the web server, the IRC bot, and mail). The IRC bot allows a reasonable amount of control - it lives at #openchange-commits
The builderslaves can run on different machines. We currently have several builders (TODO: keep a list)
A couple of things to note:
1. We can add more buildslaves / builders as required. Let Brad Hards know if you'd like to participate.
2. We can make the mail notifier send notifications about every change, or about changes you were involved in. This is strictly opt-in. Let Brad Hards know what svn account should map to which email address if you'd like to be mailed about how buildbot results (and whether you want everything or just your stuff).
3. The buildbot filters out any warnings from exchange.idl, so we can detect new warnings.
Future plans involve extending tests to some new tests with openchangeclient, openchangepfadmin, exchange2ical, exchange2mbox and so on.
Volunteering a builder¶
You need a machine that can build samba4 and openchange. If it won't build as a normal user, it won't build with buildbot either.
We recommend setting up a separate account to run the buildbot. A virtual machine is often a good idea.
See the buildbot manual for general guidance:
Main steps:
1. Checkout openchange from svn. You need to do this to accept the svn HTTPS certificate.
2. Build samba4 and install it.
3. Make sure openchange will build and install. If you need sudo, make sure it will work without a password.
4. Install buildbot (packages or see http://buildbot.net)
5. Set up the buildslave (buildbot create-slave [options] <basedir> <master> <name> <passwd>). Choose a convenient basedir (e.g. ~/buildbot). The master is buildbot.openchange.org:9989 (you need the port). Choose a name consistent with the list above, but not the same. Choose a long, hard to guess password.
6. Send the name and password to whoever is administering the buildmaster (BradHards)
7. Fill in the info/host and info/admin files
Buildbot setup¶
This builder configuration is typical:
slave3env={'PATH' : '/usr/local/samba/bin:/usr/local/bin:/bin:/usr/bin', 'PKG_CONFIG_PATH' : '/home/buildbot1/openchangeinstall/lib/pkgconfig:/usr/local/samb/lib/pkgconfig/'}
slave3checkout factory.BuildFactory()
slave3checkout.addStep(SVN(mode='clobber', svnurl='https://svn.openchange.org/openchange/trunk'))
slave3checkout.addStep(ShellCommand(command=["./autogen.sh"], env=slave3env))
slave3checkout.addStep(Configure(command=["./configure", "--prefix=/home/buildbot1/openchangeinstall"], env=slave3env))
slave3checkout.addStep(CompileNoExchangeIDLWarnings(env=slave3env))
slave3checkout.addStep(ShellCommand(command=["make", "install"],
description=["Installing"],
descriptionDone=["Install"],
env=slave3env))
slave3checkout.addStep(Compile(env=slave3env,
command=["make", "examples"],
description=["Examples"],
descriptionDone=["Examples"]))
slave3checkout.addStep(MapiTest(env=slave3env, command=["/home/buildbot1/openchangeinstall/bin/mapitest", "--no-server"]))
slave3checkout.addStep(MapiTest(env=slave3env, command=["/home/buildbot1/openchangeinstall/bin/mapitest"]))
b6 = {'name': "testinstall-ubuntu810",
'slavename': "buildslave3",
'builddir': "testinstall-ubuntu810",
'factory': slave3checkout,
}
Custom Code¶
We have a custom mail parser:
# try using mail
import re
from buildbot import util
from email.Iterators import body_line_iterator
from buildbot.changes.mail import MaildirSource
from buildbot.changes import changes
from twisted.python import log
from email.Utils import parseaddr
from time import strptime
class OpenChangeEmailMaildirSource(MaildirSource):
name = "OpenChange SVN Commit email"
def parse(self, m, prefix=None):
"""Parse messages sent by the svn 'commit-email.pl' trigger.
"""
name, addr = parseaddr(m["from"])
if (addr != "svn@lists.openchange.org"):
return
files = []
comments = ""
who = "unknown"
lines = list(body_line_iterator(m))
rev = None
when = util.now()
while lines:
line = lines.pop(0)
# "Revision: 105955"
match = re.search(r"^Revision: (\d+)", line)
if match:
rev = match.group(1)
# "Author: jmason"
match = re.search(r"^Author: (\S+)", line)
if match:
who = match.group(1)
# this stanza ends with the "Log:"
if (line == "Log Message:\n"):
# eat the ------- divider
lines.pop(0)
break
# collect the log message
while lines:
line = lines.pop(0)
if (line == "Modified Paths:\n" or
line == "Added Paths:\n" or
line == "Removed Paths:\n"):
# end of log message
# also eat the ----- divider
lines.pop(0)
break
comments = comments + line
# collect the file list
while lines:
line = lines.pop(0)
if ((len(line) == 0) or line.isspace()):
# empty line
continue
if (line.startswith("Modified:") or
line.startswith("Added:")):
#end of file list - finished
break
if (line == "Added Paths:\n" or
line == "Removed Paths:\n"):
# eat the ------ divider
lines.pop(0)
# get the next file line
line = lines.pop(0)
files.append(line.strip())
return changes.Change(who, files, comments, when=when, revision=rev)
We also have some custom build steps.
Compile, without warnings¶
This is basically a normal Compile step that filters out anything from exchange.idl
from buildbot.process import factory
from buildbot.steps.source import SVN
from buildbot.steps.shell import ShellCommand
from buildbot.steps.shell import Configure
from buildbot.steps.shell import Compile
from buildbot.steps.shell import Test
from buildbot.steps.shell import WarningCountingShellCommand
from buildbot.status.builder import SUCCESS, FAILURE
commonenv={'PERL5LIB' : '/perl5', 'PATH' : '/usr/local/samba/bin:/usr/local/bin:/bin:/usr/bin'}
class CompileNoExchangeIDLWarnings(WarningCountingShellCommand):
name = "compile"
haltOnFailure = 1
description = ["compiling"]
descriptionDone = ["compile"]
command = ["make", "all"]
OFFprogressMetrics = ('output',)
# things to track: number of files compiled, number of directories
# traversed (assuming 'make' is being used)
def createSummary(self, log):
self.warnCount = 0
# Now compile a regular expression from whichever warning pattern we're
# using
if not self.warningPattern:
return
wre = self.warningPattern
if isinstance(wre, str):
wre = re.compile(wre)
# Check if each line in the output from this command matched our
# warnings regular expressions. If did, bump the warnings count and
# add the line to the collection of lines with warnings
warnings = []
# TODO: use log.readlines(), except we need to decide about stdout vs
# stderr
for line in log.getText().split("\n"):
if line.startswith("exchange.idl:"):
continue
if wre.match(line):
warnings.append(line)
self.warnCount += 1
# If there were any warnings, make the log if lines with warnings
# available
if self.warnCount:
self.addCompleteLog("warnings", "\n".join(warnings) + "\n")
warnings_stat = self.step_status.getStatistic('warnings', 0)
self.step_status.setStatistic('warnings', warnings_stat + self.warnCount)
try:
old_count = self.getProperty("warnings-count")
except KeyError:
old_count = 0
self.setProperty("warnings-count", old_count + self.warnCount, "WarningCountingShellCommand")
MapiTest build step¶
from string import atoi
class MapiTest(Test):
command=["/home/buildslave2/openchangeinstall/bin/mapitest", "--no-server"]
def evaluateCommand(self, cmd):
lines = self.getLog('stdio').readlines()
for line in lines:
strippedline = line.lstrip();
if strippedline.startswith("Number of passing tests: "):
text, passing = strippedline.split(": ")
self.setTestResults(total=atoi(passing), passed=atoi(passing))
if strippedline.startswith("Number of failing tests: "):
text, failing = strippedline.split(": ")
self.setTestResults(total=atoi(failing), failed=atoi(failing))
if cmd.rc != 0:
return FAILURE
return SUCCESS
You can override the command in an instance:
slave3checkout.addStep(MapiTest(env=slave3env, command=["/home/buildbot1/openchangeinstall/bin/mapitest"]))