From 202a18a380b0271b993d8694022009bb50b9cc30 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Dec 2013 00:30:16 -0500 Subject: [PATCH 1/3] restore 'gerritchangesource: use gerrit's changeid as the branch' (ccf6c117) --- master/buildbot/changes/gerritchangesource.py | 3 ++- master/buildbot/test/unit/test_changes_gerritchangesource.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/master/buildbot/changes/gerritchangesource.py b/master/buildbot/changes/gerritchangesource.py index 826d99a664b..61041a3e20a 100644 --- a/master/buildbot/changes/gerritchangesource.py +++ b/master/buildbot/changes/gerritchangesource.py @@ -155,7 +155,8 @@ def addChangeFromEvent(self, properties, event): 'repository': "ssh://%s@%s:%s/%s" % ( self.username, self.gerritserver, self.gerritport, event_change["project"]), - 'branch': event_change["branch"], + 'branch': "%s/%s" % (event_change["branch"], + event_change['number']), 'revision': event["patchSet"]["revision"], 'revlink': event_change["url"], 'comments': event_change["subject"], diff --git a/master/buildbot/test/unit/test_changes_gerritchangesource.py b/master/buildbot/test/unit/test_changes_gerritchangesource.py index a1a372366ae..89b4e0447ae 100644 --- a/master/buildbot/test/unit/test_changes_gerritchangesource.py +++ b/master/buildbot/test/unit/test_changes_gerritchangesource.py @@ -52,7 +52,7 @@ def test_describe(self): 'author': u'Dustin ', 'comments': u'fix 1234', 'project': u'pr', - 'branch': u'br', + 'branch': u'br/4321', 'revlink': u'http://buildbot.net', 'properties': {u'event.change.owner.email': u'dustin@mozilla.com', u'event.change.subject': u'fix 1234', From 0931c09084ed00ca5949c067c2d18428f44342d0 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Dec 2013 15:18:42 -0500 Subject: [PATCH 2/3] add buildbot.util.asyncSleep --- master/buildbot/test/unit/test_util.py | 15 +++++++++++++++ master/buildbot/util/__init__.py | 8 ++++++++ master/docs/developer/utils.rst | 5 +++++ 3 files changed, 28 insertions(+) diff --git a/master/buildbot/test/unit/test_util.py b/master/buildbot/test/unit/test_util.py index f0b4cbe313e..bde759b2de7 100644 --- a/master/buildbot/test/unit/test_util.py +++ b/master/buildbot/test/unit/test_util.py @@ -16,6 +16,8 @@ import datetime from twisted.trial import unittest +from twisted.internet import reactor +from twisted.internet import task from buildbot import util @@ -199,3 +201,16 @@ def test_deep(self): def test_tuples(self): self.assertEqual(util.flatten([(1, 2), 3]), [(1, 2), 3]) + + +class AsyncSleep(unittest.TestCase): + + def test_sleep(self): + clock = task.Clock() + self.patch(reactor, 'callLater', clock.callLater) + d = util.asyncSleep(2) + self.assertFalse(d.called) + clock.advance(1) + self.assertFalse(d.called) + clock.advance(1) + self.assertTrue(d.called) diff --git a/master/buildbot/util/__init__.py b/master/buildbot/util/__init__.py index 4b154202918..2336de0614d 100644 --- a/master/buildbot/util/__init__.py +++ b/master/buildbot/util/__init__.py @@ -235,6 +235,14 @@ def do_stop(r): wrap._orig = f # for tests return wrap + +def asyncSleep(delay): + from twisted.internet import reactor, defer + d = defer.Deferred() + reactor.callLater(delay, d.callback, None) + return d + + __all__ = [ 'naturalSort', 'now', 'formatInterval', 'ComparableMixin', 'json', 'safeTranslate', 'none_or_str', diff --git a/master/docs/developer/utils.rst b/master/docs/developer/utils.rst index 3e0dc3aabf2..8f0fe3dc821 100644 --- a/master/docs/developer/utils.rst +++ b/master/docs/developer/utils.rst @@ -144,6 +144,11 @@ package. the result of the wrapped function. If the wrapped function fails, its traceback will be printed, the reactor halted, and ``None`` returned. +.. py:function:: asyncSleep(secs) + + Yield a deferred that will fire with no result after ``secs`` seconds. + This is the asynchronous equivalent to ``time.sleep``, and can be useful in tests. + buildbot.util.lru ~~~~~~~~~~~~~~~~~ From f12562ba610f8bb89853ed53e0ae7f0a7868efc1 Mon Sep 17 00:00:00 2001 From: "Dustin J. Mitchell" Date: Tue, 24 Dec 2013 18:19:26 -0500 Subject: [PATCH 3/3] Add an integration test for try scheduler client/server interactions. This runs a full master with real builds. Waiting for SSH builds does not work - the buildmaster returns buildset ID's, while the client expects its external_idstring, so it waits forever. This functionality will be difficult to support as-is in nine, so it's been removed. --- master/buildbot/clients/tryclient.py | 63 ++-- master/buildbot/db/model.py | 6 + master/buildbot/schedulers/trysched.py | 4 +- master/buildbot/status/client.py | 1 + .../test/integration/test_try_client.py | 343 ++++++++++++++++++ 5 files changed, 389 insertions(+), 28 deletions(-) create mode 100644 master/buildbot/test/integration/test_try_client.py diff --git a/master/buildbot/clients/tryclient.py b/master/buildbot/clients/tryclient.py index 9daf674340c..627b2d2a815 100644 --- a/master/buildbot/clients/tryclient.py +++ b/master/buildbot/clients/tryclient.py @@ -39,6 +39,10 @@ from buildbot.util.eventual import fireEventually +def output(*msg): + print ' '.join(map(str, msg)) + + class SourceStampExtractor: def __init__(self, treetop, branch, repository): @@ -47,7 +51,7 @@ def __init__(self, treetop, branch, repository): self.branch = branch exes = which(self.vcexe) if not exes: - print "Could not find executable '%s'." % self.vcexe + output("Could not find executable '%s'." % self.vcexe) sys.exit(1) self.exe = exes[0] @@ -110,7 +114,7 @@ def getPatch(self, res): # branch. A bare 'cvs diff' will tell you about the changes # relative to your checked-out versions, but I know of no way to # find out what those checked-out versions are. - print "Sorry, CVS 'try' builds don't work with branches" + output("Sorry, CVS 'try' builds don't work with branches") sys.exit(1) args = ['-q', 'diff', '-u', '-D', self.baserev] d = self.dovc(args) @@ -150,7 +154,7 @@ def parseStatus(self, res): if m: self.baserev = int(m.group(1)) return - print "Could not find 'Status against revision' in SVN output: %s" % res + output("Could not find 'Status against revision' in SVN output: %s" % res) sys.exit(1) def getPatch(self, res): @@ -238,7 +242,7 @@ def parseStatus(self, res): self.baserev = m.group(1) return - print "Could not find change number in output: %s" % res + output("Could not find change number in output: %s" % res) sys.exit(1) def readPatch(self, res, patchlevel): @@ -246,7 +250,7 @@ def readPatch(self, res, patchlevel): # extract the actual patch from "res" # if not self.branch: - print "you must specify a branch" + output("you must specify a branch") sys.exit(1) mpatch = "" found = False @@ -261,7 +265,7 @@ def readPatch(self, res, patchlevel): mpatch += line mpatch += "\n" if not found: - print "could not parse patch file" + output("could not parse patch file") sys.exit(1) self.patch = (patchlevel, mpatch) @@ -350,7 +354,7 @@ def parseStatus(self, res): d = self.readConfig() d.addCallback(self.parseTrackingBranch) return d - print "Could not find current GIT branch: %s" % res + output("Could not find current GIT branch: %s" % res) sys.exit(1) def getPatch(self, res): @@ -398,7 +402,7 @@ def getSourceStamp(vctype, treetop, branch=None, repository=None): elif vctype == "mtn": cls = MonotoneExtractor else: - print "unknown vctype '%s'" % vctype + output("unknown vctype '%s'" % vctype) sys.exit(1) return cls(treetop, branch, repository).get() @@ -460,7 +464,7 @@ def getTopdir(topfile, start=None): break # we've hit the root here = next toomany -= 1 - print ("Unable to find topfile '%s' anywhere from %s upwards" + output("Unable to find topfile '%s' anywhere from %s upwards" % (topfile, start)) sys.exit(1) @@ -505,18 +509,19 @@ def grab(self): self.d = defer.Deferred() # wait a second before querying to give the master's maildir watcher # a chance to see the job - reactor.callLater(1, self.go) + reactor.callLater(0.01, self.go) return self.d def go(self, dummy=None): if self.retryCount == 0: - print "couldn't find matching buildset" + output("couldn't find matching buildset") sys.exit(1) self.retryCount -= 1 d = self.status.callRemote("getBuildSets") d.addCallback(self._gotSets) def _gotSets(self, buildsets): + print "GOT", buildsets, self.bsid for bs, bsid in buildsets: if bsid == self.bsid: # got it @@ -536,7 +541,7 @@ def __init__(self, config): self.config = config self.connect = self.getopt('connect') if self.connect not in ['ssh', 'pb']: - print "you must specify a connect style: ssh or pb" + output("you must specify a connect style: ssh or pb") sys.exit(1) self.builderNames = self.getopt('builders') self.project = self.getopt('project', '') @@ -587,7 +592,7 @@ def createJob(self): if topfile: treedir = getTopdir(topfile) else: - print "Must specify topdir or topfile." + output("Must specify topdir or topfile.") sys.exit(1) else: treedir = os.getcwd() @@ -610,7 +615,7 @@ def _createJob_1(self, ss): def fakeDeliverJob(self): # Display the job to be delivered, but don't perform delivery. ss = self.sourcestamp - print ("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t" + output("Job:\n\tRepository: %s\n\tProject: %s\n\tBranch: %s\n\t" "Revision: %s\n\tBuilders: %s\n%s" % (ss.repository, self.project, ss.branch, ss.revision, @@ -653,7 +658,7 @@ def deliverJob(self): def _deliverJob_pb(self, remote): ss = self.sourcestamp - print "Delivering job; comment=", self.comment + output("Delivering job; comment=", self.comment) d = remote.callRemote("try", ss.branch, @@ -682,7 +687,10 @@ def getStatus(self): # getURLForThing() on the BuildSetStatus. To get URLs for # individual builds would require we wait for the builds to # start. - print "not waiting for builds to finish" + output("not waiting for builds to finish") + return + if self.connect == "ssh": + output("waiting for builds with ssh is not supported") return d = self.running = defer.Deferred() if self.buildsetStatus: @@ -825,7 +833,7 @@ def printStatus(self): def statusDone(self): if self.printloop: self.printloop.stop() - print "All Builds Complete" + output("All Builds Complete") # TODO: include a URL for all failing builds names = sorted(self.buildRequests.keys()) happy = True @@ -834,7 +842,7 @@ def statusDone(self): t = "%s: %s" % (n, builder.Results[code]) if text: t += " (%s)" % " ".join(text) - print t + output(t) if code != builder.SUCCESS: happy = False @@ -857,32 +865,33 @@ def getAvailableBuilderNames(self): f = pb.PBClientFactory() d = f.login(credentials.UsernamePassword(user, passwd)) reactor.connectTCP(tryhost, tryport, f) - d.addCallback(self._getBuilderNames, self._getBuilderNames2) + d.addCallback(self._getBuilderNames) return d if self.connect == "ssh": - print "Cannot get available builders over ssh." + output("Cannot get available builders over ssh.") sys.exit(1) raise RuntimeError( "unknown connecttype '%s', should be 'pb'" % self.connect) - def _getBuilderNames(self, remote, output): + def _getBuilderNames(self, remote): d = remote.callRemote("getAvailableBuilderNames") d.addCallback(self._getBuilderNames2) + d.addCallback(lambda _: remote.broker.transport.loseConnection()) return d def _getBuilderNames2(self, buildernames): - print "The following builders are available for the try scheduler: " + output("The following builders are available for the try scheduler: ") for buildername in buildernames: - print buildername + output(buildername) def announce(self, message): if not self.quiet: - print message + output(message) - def run(self): + def run(self, _inTests=False): # we can't do spawnProcess until we're inside reactor.run(), so get # funky - print "using '%s' connect method" % self.connect + output("using '%s' connect method" % self.connect) self.exitcode = 0 d = fireEventually(None) if bool(self.config.get("get-builder-names")): @@ -899,6 +908,8 @@ def run(self): d.addErrback(self.trapSystemExit) d.addErrback(log.err) d.addCallback(self.cleanup) + if _inTests: + return d d.addCallback(lambda res: reactor.stop()) reactor.run() diff --git a/master/buildbot/db/model.py b/master/buildbot/db/model.py index c5ae5bd1f5f..3da34804fdf 100644 --- a/master/buildbot/db/model.py +++ b/master/buildbot/db/model.py @@ -442,6 +442,12 @@ def thd(engine): return db_version == repo_version return self.db.pool.do_with_engine(thd) + def create(self): + # this is nice and simple, but used only for tests + def thd(engine): + self.metadata.create_all(bind=engine) + return self.db.pool.do_with_engine(thd) + def upgrade(self): # here, things are a little tricky. If we have a 'version' table, then diff --git a/master/buildbot/schedulers/trysched.py b/master/buildbot/schedulers/trysched.py index ae00f8d0a62..45d6e7f1679 100644 --- a/master/buildbot/schedulers/trysched.py +++ b/master/buildbot/schedulers/trysched.py @@ -248,7 +248,7 @@ def perspective_try(self, branch, revision, patch, repository, project, branch=branch, revision=revision, repository=repository, project=project, patch_level=patch[0], patch_body=patch[1], patch_subdir='', patch_author=who or '', - patch_comment=comment or '', + patch_comment=comment or '', codebase='', sourcestampsetid=sourcestampsetid) # note: no way to specify patch subdir - #1769 @@ -282,6 +282,7 @@ def __init__(self, name, builderNames, port, userpass, properties=properties) self.port = port self.userpass = userpass + self.registrations = [] def startService(self): TryBase.startService(self) @@ -289,7 +290,6 @@ def startService(self): # register each user/passwd with the pbmanager def factory(mind, username): return Try_Userpass_Perspective(self, username) - self.registrations = [] for user, passwd in self.userpass: self.registrations.append( self.master.pbmanager.register( diff --git a/master/buildbot/status/client.py b/master/buildbot/status/client.py index a64653ee572..aa67c4bb2f6 100644 --- a/master/buildbot/status/client.py +++ b/master/buildbot/status/client.py @@ -228,6 +228,7 @@ def remote_subscribe(self, observer, updateInterval=None): self.observers.append(observer) s = BuildSubscriber(observer) self.b.subscribe(s, updateInterval) + observer.notifyOnDisconnect(lambda _: self.b.unsubscribe(s)) def remote_unsubscribe(self, observer): # TODO: is the observer automatically unsubscribed when the build diff --git a/master/buildbot/test/integration/test_try_client.py b/master/buildbot/test/integration/test_try_client.py new file mode 100644 index 00000000000..4c9abd4404a --- /dev/null +++ b/master/buildbot/test/integration/test_try_client.py @@ -0,0 +1,343 @@ +# This file is part of Buildbot. Buildbot is free software: you can +# redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright Buildbot Team Members + +import mock +import os +from buildbot import config +from buildbot import util +from buildbot.schedulers import trysched +from buildbot import master +from buildbot.status import client +from buildbot.clients import tryclient +from buildbot.test.util import dirs +from twisted.internet import defer +from twisted.internet import reactor +from twisted.spread import pb +from twisted.cred import credentials +from twisted.python import log +from twisted.trial import unittest + + +# wait for some asynchronous result +@defer.inlineCallbacks +def waitFor(fn): + while True: + res = yield fn() + if res: + defer.returnValue(res) + yield util.asyncSleep(.01) + + +class FakeRemoteSlave(pb.Referenceable): + # the bare minimum to connect to a master and convince it that the slave is + # ready + + def __init__(self, port): + self.port = port + + @defer.inlineCallbacks + def start(self): + f = pb.PBClientFactory() + d = f.login(credentials.UsernamePassword('local1', 'localpw'), self) + reactor.connectTCP('127.0.0.1', self.port, f) + # we need to hold a reference to this, otherwise the broker will sever + # the connection + self.mind = yield d + + def stop(self): + self.mind.broker.transport.loseConnection() + self.mind = None + + # BuildSlave methods + + def remote_print(self, message): + log.msg("from master: %s" % (message,)) + + def remote_getSlaveInfo(self): + return {} + + def remote_getCommands(self): + return {} + + def remote_getVersion(self): + return '0.0' + + def remote_setMaster(self, master): + pass + + def remote_setBuilderList(self, wanted): + return {'a': self} + + def remote_startBuild(self): + return + +class Schedulers(dirs.DirsMixin, unittest.TestCase): + + def setUp(self): + self.basedir = os.path.abspath('basedir') + self.setUpDirs(self.basedir) + + self.configfile = os.path.join(self.basedir, 'master.cfg') + open(self.configfile, "w").write( + 'from buildbot.test.integration.test_try_client \\\n' + 'import BuildmasterConfig\n') + + self.master = None + self.sch = None + self.slave = None + + def spawnProcess(pp, executable, args, environ): + assert executable == 'ssh' + tmpfile = os.path.join(self.jobdir, 'tmp', 'testy') + newfile = os.path.join(self.jobdir, 'new', 'testy') + open(tmpfile, "w").write(pp.job) + os.rename(tmpfile, newfile) + log.msg("wrote jobfile %s" % newfile) + # get the scheduler to poll this directory now + d = self.sch.watcher.poll() + d.addErrback(log.err, 'while polling') + def finished(_): + st = mock.Mock() + st.value.signal = None + st.value.exitCode = 0 + pp.processEnded(st) + d.addCallback(finished) + + self.patch(reactor, 'spawnProcess', spawnProcess) + + def getSourceStamp(vctype, treetop, branch=None, repository=None): + return defer.succeed( + tryclient.SourceStamp(branch='br', revision='rr', + patch=(0, '++--'))) + self.patch(tryclient, 'getSourceStamp', getSourceStamp) + + self.output = [] + def output(*msg): + msg = ' '.join(map(str, msg)) + log.msg("output: %s" % msg) + self.output.append(msg) + self.patch(tryclient, 'output', output) + + @defer.inlineCallbacks + def tearDown(self): + if self.slave: + log.msg("stopping slave") + yield self.slave.stop() + if self.master: + log.msg("stopping master") + yield self.master.stopService() + if self.master.db.pool: + log.msg("stopping master db pool") + yield self.master.db.pool.shutdown() + log.msg("tearDown complete") + yield self.tearDownDirs() + + def setupJobdir(self): + self.jobdir = os.path.join(self.basedir, 'jobs') + for sub in 'new', 'tmp', 'cur': + p = os.path.join(self.jobdir, sub) + os.makedirs(p) + return self.jobdir + + @defer.inlineCallbacks + def startMaster(self, sch, startSlave=False, wantPBListener=False): + BuildmasterConfig['schedulers'] = [sch] + self.sch = sch + + if wantPBListener: + self.pblistener = client.PBListener(0) + BuildmasterConfig['status'] = [self.pblistener] + else: + BuildmasterConfig['status'] = [] + + # create the master and set its config + m = self.master = master.BuildMaster(self.basedir, self.configfile) + m.config = config.MasterConfig.loadConfig( + self.basedir, self.configfile) + + # set up the db + yield m.db.setup(check_version=False) + yield m.db.model.create() + + # stub out m.db.setup since it was already called above + m.db.setup = lambda: None + + # mock reactor.stop (which trial *really* doesn't + # like test code to call!) + mock_reactor = mock.Mock(spec=reactor) + mock_reactor.callWhenRunning = reactor.callWhenRunning + + # start the service + yield m.startService(_reactor=mock_reactor) + self.failIf(mock_reactor.stop.called, + "startService tried to stop the reactor; check logs") + + # hang out until the scheduler has registered its PB port + if isinstance(self.sch, trysched.Try_Userpass): + def getSchedulerPort(): + if not self.sch.registrations: + return + self.serverPort = self.sch.registrations[0].getPort() + log.msg("Scheduler registered at port %d" % self.serverPort) + return True + yield waitFor(getSchedulerPort) + + # now start the fake slave + if startSlave: + self.slave = FakeRemoteSlave(self.serverPort) + yield self.slave.start() + + def runClient(self, config): + self.clt = tryclient.Try(config) + return self.clt.run(_inTests=True) + + @defer.inlineCallbacks + def test_userpass_no_wait(self): + yield self.startMaster( + trysched.Try_Userpass('try', ['a'], 0, [('u', 'p')]), + startSlave=False) + yield self.runClient({ + 'connect': 'pb', + 'master': '127.0.0.1:%s' % self.serverPort, + 'username': 'u', + 'passwd': 'p', + }) + self.assertEqual(self.output, [ + "using 'pb' connect method", + 'job created', + 'Delivering job; comment= None', + 'job has been delivered', + 'not waiting for builds to finish' + ]) + buildsets = yield self.master.db.buildsets.getBuildsets() + self.assertEqual(len(buildsets), 1) + + @defer.inlineCallbacks + def test_userpass_wait(self): + yield self.startMaster( + trysched.Try_Userpass('try', ['a'], 0, [('u', 'p')]), + startSlave=True) + yield self.runClient({ + 'connect': 'pb', + 'master': '127.0.0.1:%s' % self.serverPort, + 'username': 'u', + 'passwd': 'p', + 'wait': True, + }) + self.assertEqual(self.output, [ + "using 'pb' connect method", + 'job created', + 'Delivering job; comment= None', + 'job has been delivered', + 'All Builds Complete', + 'a: success (build successful)', + ]) + buildsets = yield self.master.db.buildsets.getBuildsets() + self.assertEqual(len(buildsets), 1) + + @defer.inlineCallbacks + def test_userpass_list_builders(self): + yield self.startMaster( + trysched.Try_Userpass('try', ['a'], 0, [('u', 'p')]), + startSlave=False) + yield self.runClient({ + 'connect': 'pb', + 'get-builder-names': True, + 'master': '127.0.0.1:%s' % self.serverPort, + 'username': 'u', + 'passwd': 'p', + }) + self.assertEqual(self.output, [ + "using 'pb' connect method", + 'The following builders are available for the try scheduler: ', + 'a' + ]) + buildsets = yield self.master.db.buildsets.getBuildsets() + self.assertEqual(len(buildsets), 0) + + @defer.inlineCallbacks + def test_jobdir_no_wait(self): + jobdir = self.setupJobdir() + yield self.startMaster( + trysched.Try_Jobdir('try', ['a'], jobdir), + startSlave=False, + wantPBListener=True) + yield self.runClient({ + 'connect': 'ssh', + 'master': '127.0.0.1', + 'username': 'u', + 'passwd': 'p', + 'builders': 'a', # appears to be required for ssh + }) + self.assertEqual(self.output, [ + "using 'ssh' connect method", + 'job created', + 'job has been delivered', + 'not waiting for builds to finish' + ]) + buildsets = yield self.master.db.buildsets.getBuildsets() + self.assertEqual(len(buildsets), 1) + + @defer.inlineCallbacks + def test_jobdir_wait(self): + jobdir = self.setupJobdir() + yield self.startMaster( + trysched.Try_Jobdir('try', ['a'], jobdir), + startSlave=False) + yield self.runClient({ + 'connect': 'ssh', + 'wait': True, + 'host': '127.0.0.1', + 'username': 'u', + 'passwd': 'p', + 'builders': 'a', # appears to be required for ssh + }) + self.assertEqual(self.output, [ + "using 'ssh' connect method", + 'job created', + 'job has been delivered', + 'waiting for builds with ssh is not supported' + ]) + buildsets = yield self.master.db.buildsets.getBuildsets() + self.assertEqual(len(buildsets), 1) + + +c = BuildmasterConfig = {} +from buildbot.buildslave import BuildSlave +from buildbot.config import BuilderConfig +from buildbot.process.factory import BuildFactory +from buildbot.process.buildstep import BuildStep +from buildbot.status import results + +class MyBuildStep(BuildStep): + def start(self): + self.finished(results.SUCCESS) + + +c['slaves'] = [BuildSlave("local1", "localpw")] +c['slavePortnum'] = 0 +c['change_source'] = [] +c['schedulers'] = [] # filled in above +f1 = BuildFactory() +f1.addStep(MyBuildStep(name='one')) +f1.addStep(MyBuildStep(name='two')) +c['builders'] = [ + BuilderConfig(name="a", slavenames=["local1"], factory=f1), +] +c['status'] = [] +c['title'] = "test" +c['titleURL'] = "test" +c['buildbotURL'] = "http://localhost:8010/" +c['db'] = {'db_url': "sqlite:///state.sqlite"}