Corrected regex patterns which extract unittest details. Added a first tentative email notifier. Added ForceSchedulers, in order to be able to force builds. - Legacy-Id: 9350
414 lines
18 KiB
Python
414 lines
18 KiB
Python
# -*- python -*-
|
|
# ex: set syntax=python:
|
|
import re
|
|
from buildbot_passwords import *
|
|
|
|
|
|
# This is the dictionary that the buildmaster pays attention to. We also use
|
|
# a shorter alias to save typing.
|
|
c = BuildmasterConfig = {}
|
|
|
|
####### BUILDSLAVES
|
|
|
|
# The 'slaves' list defines the set of recognized buildslaves. Each element is
|
|
# a BuildSlave object, specifying a unique slave name and password. The same
|
|
# slave name and password must be configured on the slave.
|
|
from buildbot.buildslave import BuildSlave
|
|
c['slaves'] = [
|
|
BuildSlave("datatracker_lin_py27_1", datatracker_lin_py27_1_pw),
|
|
BuildSlave("datatracker_lin_py27_2", datatracker_lin_py27_2_pw),
|
|
BuildSlave("datatracker_lin_py27_3", datatracker_lin_py27_3_pw),
|
|
BuildSlave("datatracker_osx_py27_4", datatracker_osx_py27_4_pw),
|
|
]
|
|
|
|
# 'protocols' contains information about protocols which master will use for
|
|
# communicating with slaves.
|
|
# You must define at least 'port' option that slaves could connect to your master
|
|
# with this protocol.
|
|
# 'port' must match the value configured into the buildslaves (with their
|
|
# --master option)
|
|
c['protocols'] = {'pb': {'host':'zinfandel.tools.ietf.org', 'port': 9989}}
|
|
|
|
####### CHANGESOURCES
|
|
|
|
# the 'change_source' setting tells the buildmaster how it should find out
|
|
# about source code changes.
|
|
|
|
from buildbot.changes.pb import PBChangeSource
|
|
# c['change_source'] = []
|
|
# with open("users") as file:
|
|
# userinfo = json.read(file)
|
|
# for user in userinfo:
|
|
# prefix = userinfo[user]["prefix"]
|
|
# c.['change_source'].append(PBChangeSource(user=user, passwd="BRiR6XcT7x3$", prefix=prefix))
|
|
c['change_source'] = [
|
|
PBChangeSource(user="ietfdb", passwd=ietfdb_svn_hook_pw),
|
|
]
|
|
|
|
####### SCHEDULERS
|
|
|
|
# Configure the Schedulers, which decide how to react to incoming changes. In this
|
|
# case, just kick off a 'runtests' build
|
|
|
|
from buildbot.schedulers.basic import SingleBranchScheduler, AnyBranchScheduler
|
|
from buildbot.schedulers.forcesched import ForceScheduler
|
|
from buildbot.changes import filter
|
|
c['schedulers'] = [
|
|
# Branch schedulers
|
|
SingleBranchScheduler(name="pyflakes", treeStableTimer=10, builderNames=["Check PyFlakes"],
|
|
change_filter=filter.ChangeFilter(branch='trunk')),
|
|
SingleBranchScheduler(name="lin_test", treeStableTimer=60*5, builderNames=["Test Suite"],
|
|
change_filter=filter.ChangeFilter(branch='trunk')),
|
|
SingleBranchScheduler(name="osx_test", treeStableTimer=60*5, builderNames=["Test Suite (OS X)"],
|
|
change_filter=filter.ChangeFilter(branch='trunk')),
|
|
SingleBranchScheduler(name="crawler", treeStableTimer=60*60*4, builderNames=["Test-Crawler"],
|
|
change_filter=filter.ChangeFilter(branch='trunk')),
|
|
#
|
|
AnyBranchScheduler(name="pyflakes_branch", treeStableTimer=10, builderNames=["[branch] Check PyFlakes"],
|
|
change_filter=filter.ChangeFilter(branch_re='branch/.*')),
|
|
AnyBranchScheduler(name="lin_test_branch", treeStableTimer=60*5, builderNames=["[branch] Test Suite"],
|
|
change_filter=filter.ChangeFilter(branch_re='branch/.*')),
|
|
AnyBranchScheduler(name="osx_test_branch", treeStableTimer=60*5, builderNames=["[branch] Test Suite (OS X)"],
|
|
change_filter=filter.ChangeFilter(branch_re='branch/.*')),
|
|
#
|
|
AnyBranchScheduler(name="pyflakes_personal",treeStableTimer=10, builderNames=["[personal] Check PyFlakes"],
|
|
change_filter=filter.ChangeFilter(branch_re='personal/.*')),
|
|
AnyBranchScheduler(name="lin_test_personal",treeStableTimer=60*5, builderNames=["[personal] Test Suite"],
|
|
change_filter=filter.ChangeFilter(branch_re='personal/.*')),
|
|
AnyBranchScheduler(name="osx_test_personal",treeStableTimer=60*5, builderNames=["[personal] Test Suite (OS X)"],
|
|
change_filter=filter.ChangeFilter(branch_re='personal/.*')),
|
|
# Force schedulers
|
|
ForceScheduler(name="Force PyFlakes", builderNames=["Check PyFlakes"]),
|
|
ForceScheduler(name="Force Test Suite", builderNames=["Test Suite"]),
|
|
ForceScheduler(name="Force Test Suite (OS X)", builderNames=["Test Suite (OS X)"]),
|
|
ForceScheduler(name="Force Test-Crawler", builderNames=["Test-Crawler"]),
|
|
#
|
|
ForceScheduler(name="Force [branch] PyFlakes", builderNames=["[branch] Check PyFlakes"]),
|
|
ForceScheduler(name="Force [branch] Test Suite", builderNames=["[branch] Test Suite"]),
|
|
ForceScheduler(name="Force [branch] Test Suite (OS X)", builderNames=["[branch] Test Suite (OS X)"]),
|
|
#
|
|
ForceScheduler(name="Force [personal] PyFlakes", builderNames=["[personal] Check PyFlakes"]),
|
|
ForceScheduler(name="Force [personal] Test Suite", builderNames=["[personal] Test Suite"]),
|
|
ForceScheduler(name="Force [personal] Test Suite (OS X)", builderNames=["[personal] Test Suite (OS X)"]),
|
|
]
|
|
|
|
####### BUILDERS
|
|
|
|
# The 'builders' list defines the Builders, which tell Buildbot how to perform a build:
|
|
# what steps, and which slaves can execute them. Note that any particular build will
|
|
# only take place on one slave.
|
|
|
|
from buildbot.process.factory import BuildFactory
|
|
from buildbot.steps.source.svn import SVN
|
|
from buildbot.steps.shell import ShellCommand, WarningCountingShellCommand
|
|
from buildbot.steps.python import PyFlakes
|
|
from buildbot.steps.python_twisted import RemovePYCs
|
|
#
|
|
from buildbot.process.properties import Property, Interpolate
|
|
from buildbot.config import BuilderConfig
|
|
|
|
#### Custom subclassed builder
|
|
|
|
class TestCrawlerShellCommand(WarningCountingShellCommand):
|
|
name = "testcrawl"
|
|
haltOnFailure = 1
|
|
flunkOnFailure = 1
|
|
descriptionDone = ["test crawler"]
|
|
command=["bin/test-crawl"]
|
|
warningPattern = '.* FAIL'
|
|
|
|
class UnitTest(WarningCountingShellCommand):
|
|
|
|
name = "test"
|
|
warnOnFailure = 1
|
|
description = ["testing"]
|
|
descriptionDone = ["test"]
|
|
command = ["python", "-m", "unittest", "discover"]
|
|
|
|
regexPatterns = {
|
|
"tests": "Ran (\d+) tests in [0-9.]+s",
|
|
"time": "Ran \d+ tests in ([0-9.]+)s",
|
|
"skipped": "(?:OK|FAILED).*skipped=(\d+)",
|
|
"failed": "FAILED.*failures=(\d+)",
|
|
"errors": "FAILED.*errors=(\d+)",
|
|
"template_coverage":" +Template coverage: +([0-9.]+%)",
|
|
"url_coverage": " +Url coverage: +([0-9.]+%)",
|
|
"code_coverage": " +Code coverage: +([0-9.]+%)",
|
|
}
|
|
|
|
def setTestResults(self, **kwargs):
|
|
"""
|
|
Called by subclasses to set the relevant statistics; this actually
|
|
adds to any statistics already present
|
|
"""
|
|
for kw in kwargs:
|
|
value = kwargs[kw]
|
|
if value.isdigit():
|
|
# Counter
|
|
value = int(value)
|
|
value += self.step_status.getStatistic(kw, 0)
|
|
elif re.match("[0-9]+\.[0-9]+$", value):
|
|
# Runtime
|
|
value = float(value)
|
|
value += self.step_status.getStatistic(kw, 0)
|
|
else:
|
|
# This is a percentage, and we can't add them
|
|
pass
|
|
self.step_status.setStatistic(kw, value)
|
|
|
|
def createSummary(self, log):
|
|
info = {}
|
|
for line in log.getText().split("\n"):
|
|
for key in self.regexPatterns:
|
|
regex = self.regexPatterns[key]
|
|
match = re.match(regex, line)
|
|
if match:
|
|
info[key] = match.group(1)
|
|
self.setTestResults(**info)
|
|
|
|
def describe(self, done=False):
|
|
description = WarningCountingShellCommand.describe(self, done)
|
|
if done:
|
|
description = description[:] # make a private copy
|
|
self.step_status.statistics["passed"] = (
|
|
self.step_status.getStatistic("tests",0) -
|
|
self.step_status.getStatistic("skipped",0) -
|
|
self.step_status.getStatistic("failed",0) -
|
|
self.step_status.getStatistic("errors",0))
|
|
for name in ["time", "tests", "passed", "skipped", "failed", "errors", "template_coverage", "url_coverage", "code_coverage", ]:
|
|
if name in self.step_status.statistics:
|
|
value = self.step_status.getStatistic(name)
|
|
displayName = name.replace('_', ' ')
|
|
# special case. Mph.
|
|
if displayName == 'template coverage':
|
|
displayName = 'templ. coverage'
|
|
if type(value) is float: # this is run-time
|
|
description.append('%s: %.2fs' % (displayName, value))
|
|
elif type(value) is int:
|
|
description.append('%s: %d' % (displayName, value))
|
|
else:
|
|
description.append('%s: %s' % (displayName, value))
|
|
return description
|
|
|
|
|
|
## Set up builders
|
|
|
|
c['builders'] = []
|
|
|
|
# --- Run pyflakes ---
|
|
factory = BuildFactory()
|
|
factory.addStep(SVN(
|
|
username='buildbot@tools.ietf.org',
|
|
descriptionDone="svn update",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
|
|
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
|
|
))
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="install requirements",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["pip", "install", "-r", "requirements.txt"],
|
|
))
|
|
factory.addStep(PyFlakes(
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["ietf/manage.py", "pyflakes", "--verbosity=0"],
|
|
))
|
|
# This should be the last action
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="mark as passed",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
command=["svn", "--username=buildbot@tools.ietf.org", "--non-interactive",
|
|
"propset", "--revprop", "-r", Property('got_revision'), "test:pyflakes", "passed" ],
|
|
))
|
|
|
|
c['builders'].append(BuilderConfig(name="Check PyFlakes", factory=factory, category="1. trunk",
|
|
slavenames=["datatracker_lin_py27_1", "datatracker_lin_py27_2", "datatracker_lin_py27_3"]))
|
|
c['builders'].append(BuilderConfig(name="[branch] Check PyFlakes", factory=factory, category="2. branch",
|
|
slavenames=["datatracker_lin_py27_1", "datatracker_lin_py27_2", "datatracker_lin_py27_3"]))
|
|
c['builders'].append(BuilderConfig(name="[personal] Check PyFlakes", factory=factory, category="3. personal",
|
|
slavenames=["datatracker_lin_py27_1", "datatracker_lin_py27_2", "datatracker_lin_py27_3"]))
|
|
|
|
# --- Run tests ---
|
|
factory = BuildFactory()
|
|
factory.addStep(SVN(
|
|
username='buildbot@tools.ietf.org',
|
|
descriptionDone="svn update",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
|
|
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
|
|
))
|
|
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="install requirements",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["pip", "install", "-r", "requirements.txt"],
|
|
))
|
|
factory.addStep(UnitTest(
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["ietf/manage.py", "test", "--settings=settings_sqlitetest"],
|
|
))
|
|
# This should be the last action
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="mark as passed",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
command=["svn", "--username=buildbot@tools.ietf.org", "--non-interactive",
|
|
"propset", "--revprop", "-r", Property('got_revision'), "test:unittest", "passed" ],
|
|
))
|
|
|
|
c['builders'].append(BuilderConfig(name="Test Suite", factory=factory, category="1. trunk",
|
|
slavenames=["datatracker_lin_py27_1", "datatracker_lin_py27_2", "datatracker_lin_py27_3"]))
|
|
c['builders'].append(BuilderConfig(name="[branch] Test Suite", factory=factory, category="2. branch",
|
|
slavenames=["datatracker_lin_py27_1", "datatracker_lin_py27_2", "datatracker_lin_py27_3"]))
|
|
c['builders'].append(BuilderConfig(name="[personal] Test Suite", factory=factory, category="3. personal",
|
|
slavenames=["datatracker_lin_py27_1", "datatracker_lin_py27_2", "datatracker_lin_py27_3"]))
|
|
|
|
|
|
# --- Run tests on os-x ---
|
|
factory = BuildFactory()
|
|
factory.addStep(SVN(
|
|
username='buildbot@tools.ietf.org',
|
|
descriptionDone="svn update",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
|
|
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
|
|
))
|
|
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="install requirements",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["pip", "install", "-r", "requirements.txt"],
|
|
))
|
|
factory.addStep(UnitTest(
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["ietf/manage.py", "test", "--settings=settings_sqlitetest", "--skip-coverage"],
|
|
))
|
|
|
|
c['builders'].append(BuilderConfig(name="Test Suite (OS X)", factory=factory, category="1. trunk",
|
|
slavenames=["datatracker_osx_py27_4"]))
|
|
c['builders'].append(BuilderConfig(name="[branch] Test Suite (OS X)", factory=factory, category="2. branch",
|
|
slavenames=["datatracker_osx_py27_4"]))
|
|
c['builders'].append(BuilderConfig(name="[personal] Test Suite (OS X)", factory=factory, category="3. personal",
|
|
slavenames=["datatracker_osx_py27_4"]))
|
|
|
|
|
|
# --- Run test-crawler ---
|
|
factory = BuildFactory()
|
|
factory.addStep(SVN(
|
|
username='buildbot@tools.ietf.org',
|
|
descriptionDone="svn update",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
repourl=Interpolate('https://svn.tools.ietf.org/svn/tools/ietfdb/%(src::branch:~trunk)s'),
|
|
descriptionSuffix=[Interpolate('%(src::branch)s %(src::revision)s')],
|
|
))
|
|
factory.addStep(RemovePYCs(workdir=Interpolate('build/%(src::branch)s')))
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="install requirements",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["pip", "install", "-r", "requirements.txt"],
|
|
))
|
|
factory.addStep(TestCrawlerShellCommand(
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
haltOnFailure=True,
|
|
command=["bin/test-crawl", "--settings=ietf.settings_testcrawl"],
|
|
))
|
|
# This should be the last action
|
|
factory.addStep(ShellCommand(
|
|
descriptionDone="mark as passed",
|
|
workdir=Interpolate('build/%(src::branch)s'),
|
|
command=["svn", "--username=buildbot@tools.ietf.org", "--non-interactive",
|
|
"propset", "--revprop", "-r", Property('got_revision'), "test:crawler", "passed" ],
|
|
))
|
|
|
|
c['builders'].append(BuilderConfig(name="Test-Crawler", factory=factory, category="1. trunk",
|
|
slavenames=["datatracker_lin_py27_1", ]))
|
|
|
|
|
|
####### STATUS TARGETS
|
|
|
|
# 'status' is a list of Status Targets. The results of each build will be
|
|
# pushed to these targets. buildbot/status/*.py has a variety to choose from,
|
|
# including web pages, email senders, and IRC bots.
|
|
|
|
c['status'] = []
|
|
|
|
from buildbot.status import html, mail
|
|
from buildbot.status.web import authz, auth
|
|
|
|
authz_cfg=authz.Authz(
|
|
# change any of these to True to enable; see the manual for more
|
|
# options
|
|
auth=auth.BasicAuth([("ietfdb","ietfdb")]),
|
|
gracefulShutdown = False,
|
|
forceBuild = 'auth', # use this to test your slave once it is set up
|
|
forceAllBuilds = False,
|
|
pingBuilder = False,
|
|
stopBuild = 'auth',
|
|
stopAllBuilds = False,
|
|
cancelPendingBuild = 'auth',
|
|
)
|
|
c['status'].append(html.WebStatus(http_port=8010, authz=authz_cfg))
|
|
|
|
# A second web status with slightly different rendering
|
|
from twisted.python import log
|
|
def changelinkfilter(html, project):
|
|
log.msg(" * changelinkfilter(html='%s', project='%s')" % (html, project))
|
|
return html
|
|
|
|
import jinja2, os
|
|
trac_template_loaders = [jinja2.FileSystemLoader(os.path.join(os.getcwd(), 'trac_view'))]
|
|
c['status'].append(html.WebStatus(http_port=8011, jinja_loaders=trac_template_loaders,
|
|
authz=authz_cfg))
|
|
|
|
# Email notifications
|
|
from zope.interface import implements
|
|
from buildbot import interfaces
|
|
|
|
class UsernameIsEmailAddress():
|
|
"This IEmailLookup provider assumes that the svn username is a valid email address."
|
|
implements(interfaces.IEmailLookup)
|
|
def getAddress(self, name):
|
|
return name
|
|
|
|
c['status'].append(mail.MailNotifier(
|
|
builders = ['Check Pyflakes', 'Test Suite', 'Test-Crawler', ],
|
|
fromaddr='buildbot@tools.ietf.org',
|
|
extraRecipients=['henrik@levkowetz.com',],
|
|
lookup=UsernameIsEmailAddress(),
|
|
))
|
|
|
|
####### PROJECT IDENTITY
|
|
|
|
# the 'title' string will appear at the top of this buildbot
|
|
# installation's html.WebStatus home page (linked to the
|
|
# 'titleURL') and is embedded in the title of the waterfall HTML page.
|
|
|
|
c['title'] = "IETF Datatracker"
|
|
c['titleURL'] = "https://datatracker.ietf.org/"
|
|
|
|
# the 'buildbotURL' string should point to the location where the buildbot's
|
|
# internal web server (usually the html.WebStatus page) is visible. This
|
|
# typically uses the port number set in the Waterfall 'status' entry, but
|
|
# with an externally-visible host name which the buildbot cannot figure out
|
|
# without some help.
|
|
|
|
c['buildbotURL'] = "http://zinfandel.tools.ietf.org:8010/"
|
|
|
|
####### DB URL
|
|
|
|
c['db'] = {
|
|
# This specifies what database buildbot uses to store its state. You can leave
|
|
# this at its default for all but the largest installations.
|
|
'db_url' : "sqlite:///state.sqlite",
|
|
}
|