def testSteps(self): m = BuildMaster(".") m.loadConfig(cfg1) b = m.botmaster.builders["builder1"] steps = b.buildFactory.steps self.failUnlessEqual(len(steps), 4) self.failUnlessExpectedShell(steps[0], command="echo yes") self.failUnlessExpectedShell(steps[1], defaults=False, command="old-style") self.failUnlessExpectedDarcs(steps[2], repourl="http://buildbot.net/repos/trunk") self.failUnlessExpectedShell(steps[3], defaults=False, command="echo old-style")
def check_master_cfg(self): from buildbot.master import BuildMaster from twisted.python import log, failure master_cfg = os.path.join(self.basedir, "master.cfg") if not os.path.exists(master_cfg): if not self.quiet: print "No master.cfg found" return 1 # side-effects of loading the config file: # for each Builder defined in c['builders'], if the status directory # didn't already exist, it will be created, and the # $BUILDERNAME/builder pickle might be created (with a single # "builder created" event). # we put basedir in front of sys.path, because that's how the # buildmaster itself will run, and it is quite common to have the # buildmaster import helper classes from other .py files in its # basedir. if sys.path[0] != self.basedir: sys.path.insert(0, self.basedir) m = BuildMaster(self.basedir) # we need to route log.msg to stdout, so any problems can be seen # there. But if everything goes well, I'd rather not clutter stdout # with log messages. So instead we add a logObserver which gathers # messages and only displays them if something goes wrong. messages = [] log.addObserver(messages.append) try: # this will raise an exception if there's something wrong with # the config file. Note that this BuildMaster instance is never # started, so it won't actually do anything with the # configuration. m.loadConfig(open(master_cfg, "r")) except: f = failure.Failure() if not self.quiet: print for m in messages: print "".join(m['message']) print f print print "An error was detected in the master.cfg file." print "Please correct the problem and run 'buildbot upgrade-master' again." print return 1 return 0
def check_master_cfg(self): from buildbot.master import BuildMaster from twisted.python import log, failure master_cfg = os.path.join(self.basedir, "master.cfg") if not os.path.exists(master_cfg): if not self.quiet: print "No master.cfg found" return 1 # side-effects of loading the config file: # for each Builder defined in c['builders'], if the status directory # didn't already exist, it will be created, and the # $BUILDERNAME/builder pickle might be created (with a single # "builder created" event). # we put basedir in front of sys.path, because that's how the # buildmaster itself will run, and it is quite common to have the # buildmaster import helper classes from other .py files in its # basedir. if sys.path[0] != self.basedir: sys.path.insert(0, self.basedir) m = BuildMaster(self.basedir) # we need to route log.msg to stdout, so any problems can be seen # there. But if everything goes well, I'd rather not clutter stdout # with log messages. So instead we add a logObserver which gathers # messages and only displays them if something goes wrong. messages = [] log.addObserver(messages.append) try: # this will raise an exception if there's something wrong with # the config file. Note that this BuildMaster instance is never # started, so it won't actually do anything with the # configuration. m.loadConfig(open(master_cfg, "r"), check_synchronously_only=True) except: f = failure.Failure() if not self.quiet: print for m in messages: print "".join(m['message']) print f print print "An error was detected in the master.cfg file." print "Please correct the problem and run 'buildbot upgrade-master' again." print return 1 return 0
def check_master_cfg(self, expected_db_url=None): """Check the buildmaster configuration, returning a deferred that fires with an approprate exit status (so 0=success).""" from buildbot.master import BuildMaster from twisted.python import log master_cfg = os.path.join(self.basedir, "master.cfg") if not os.path.exists(master_cfg): if not self.quiet: print "No master.cfg found" return defer.succeed(1) # side-effects of loading the config file: # for each Builder defined in c['builders'], if the status directory # didn't already exist, it will be created, and the # $BUILDERNAME/builder pickle might be created (with a single # "builder created" event). # we put basedir in front of sys.path, because that's how the # buildmaster itself will run, and it is quite common to have the # buildmaster import helper classes from other .py files in its # basedir. if sys.path[0] != self.basedir: sys.path.insert(0, self.basedir) m = BuildMaster(self.basedir) # we need to route log.msg to stdout, so any problems can be seen # there. But if everything goes well, I'd rather not clutter stdout # with log messages. So instead we add a logObserver which gathers # messages and only displays them if something goes wrong. messages = [] log.addObserver(messages.append) # this will errback if there's something wrong with the config file. # Note that this BuildMaster instance is never started, so it won't # actually do anything with the configuration. d = defer.maybeDeferred(lambda : m.loadConfig(open(master_cfg, "r"), checkOnly=True)) def check_db_url(config): if (expected_db_url and config.get('db_url', 'sqlite:///state.sqlite') != expected_db_url): raise ValueError("c['db_url'] in the config file ('%s') does" " not match '%s'; please edit the configuration" " file before upgrading." % (config['db_url'], expected_db_url)) d.addCallback(check_db_url) def cb(_): return 0 def eb(f): if not self.quiet: print for m in messages: print "".join(m['message']) f.printTraceback() print print "An error was detected in the master.cfg file." print "Please correct the problem and run 'buildbot upgrade-master' again." print return 1 d.addCallbacks(cb, eb) return d
class ConfigTest(unittest.TestCase): def setUp(self): # this class generates several deprecation warnings, which the user # doesn't need to see. warnings.simplefilter('ignore', exceptions.DeprecationWarning) self.buildmaster = BuildMaster(".") def failUnlessListsEquivalent(self, list1, list2): l1 = list1[:] l1.sort() l2 = list2[:] l2.sort() self.failUnlessEqual(l1, l2) def servers(self, s, types): # perform a recursive search of s.services, looking for instances of # twisted.application.internet.TCPServer, then extract their .args # values to find the TCP ports they want to listen on for child in s: if service.IServiceCollection.providedBy(child): for gc in self.servers(child, types): yield gc if isinstance(child, types): yield child def TCPports(self, s): return list(self.servers(s, internet.TCPServer)) def UNIXports(self, s): return list(self.servers(s, internet.UNIXServer)) def TCPclients(self, s): return list(self.servers(s, internet.TCPClient)) def checkPorts(self, svc, expected): """Verify that the TCPServer and UNIXServer children of the given service have the expected portnum/pathname and factory classes. As a side-effect, return a list of servers in the same order as the 'expected' list. This can be used to verify properties of the factories contained therein.""" expTCP = [e for e in expected if type(e[0]) == int] expUNIX = [e for e in expected if type(e[0]) == str] haveTCP = [(p.args[0], p.args[1].__class__) for p in self.TCPports(svc)] haveUNIX = [(p.args[0], p.args[1].__class__) for p in self.UNIXports(svc)] self.failUnlessListsEquivalent(expTCP, haveTCP) self.failUnlessListsEquivalent(expUNIX, haveUNIX) ret = [] for e in expected: for have in self.TCPports(svc) + self.UNIXports(svc): if have.args[0] == e[0]: ret.append(have) continue assert(len(ret) == len(expected)) return ret def testEmpty(self): self.failUnlessRaises(KeyError, self.buildmaster.loadConfig, "") def testSimple(self): # covers slavePortnum, base checker passwords master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) # note: this doesn't actually start listening, because the app # hasn't been started running self.failUnlessEqual(master.slavePortnum, "tcp:9999") self.checkPorts(master, [(9999, pb.PBServerFactory)]) self.failUnlessEqual(list(master.change_svc), []) self.failUnlessEqual(master.botmaster.builders, {}) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) self.failUnlessEqual(master.projectName, "dummy project") self.failUnlessEqual(master.projectURL, "http://dummy.example.com") self.failUnlessEqual(master.buildbotURL, "http://dummy.example.com/buildbot") def testSlavePortnum(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.slavePortnum, "tcp:9999") ports = self.checkPorts(master, [(9999, pb.PBServerFactory)]) p = ports[0] master.loadConfig(emptyCfg) self.failUnlessEqual(master.slavePortnum, "tcp:9999") ports = self.checkPorts(master, [(9999, pb.PBServerFactory)]) self.failUnlessIdentical(p, ports[0], "the slave port was changed even " + \ "though the configuration was not") master.loadConfig(emptyCfg + "c['slavePortnum'] = 9000\n") self.failUnlessEqual(master.slavePortnum, "tcp:9000") ports = self.checkPorts(master, [(9000, pb.PBServerFactory)]) self.failIf(p is ports[0], "slave port was unchanged but configuration was changed") def testSlaves(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.botmaster.builders, {}) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) # 'botsCfg' is testing backwards compatibility, for 0.7.5 config # files that have not yet been updated to 0.7.6 . This compatibility # (and this test) is scheduled for removal in 0.8.0 . botsCfg = (emptyCfg + "c['bots'] = [('bot1', 'pw1'), ('bot2', 'pw2')]\n") master.loadConfig(botsCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw", "bot1": "pw1", "bot2": "pw2"}) master.loadConfig(botsCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw", "bot1": "pw1", "bot2": "pw2"}) master.loadConfig(emptyCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) slavesCfg = (emptyCfg + "from buildbot.buildslave import BuildSlave\n" "c['slaves'] = [BuildSlave('bot1','pw1'), " "BuildSlave('bot2','pw2')]\n") master.loadConfig(slavesCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw", "bot1": "pw1", "bot2": "pw2"}) def testChangeSource(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(list(master.change_svc), []) sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['change_source'] = PBChangeSource() """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) s1 = list(self.buildmaster.change_svc)[0] self.failUnless(isinstance(s1, PBChangeSource)) self.failUnlessEqual(s1, list(self.buildmaster.change_svc)[0]) self.failUnless(s1.parent) # verify that unchanged sources are not interrupted d1 = self.buildmaster.loadConfig(sourcesCfg) def _check2(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) s2 = list(self.buildmaster.change_svc)[0] self.failUnlessIdentical(s1, s2) self.failUnless(s1.parent) d1.addCallback(_check2) return d1 d.addCallback(_check1) # make sure we can get rid of the sources too d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg)) def _check3(res): self.failUnlessEqual(list(self.buildmaster.change_svc), []) d.addCallback(_check3) return d def testChangeSources(self): # make sure we can accept a list master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(list(master.change_svc), []) sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource from buildbot.changes.mail import SyncmailMaildirSource c['change_source'] = [PBChangeSource(), SyncmailMaildirSource('.'), ] """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 2) s1,s2 = list(self.buildmaster.change_svc) if isinstance(s2, PBChangeSource): s1,s2 = s2,s1 self.failUnless(isinstance(s1, PBChangeSource)) self.failUnless(s1.parent) self.failUnless(isinstance(s2, SyncmailMaildirSource)) self.failUnless(s2.parent) d.addCallback(_check1) return d def testSources(self): # test backwards compatibility. c['sources'] is deprecated. master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(list(master.change_svc), []) sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['sources'] = [PBChangeSource()] """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) s1 = list(self.buildmaster.change_svc)[0] self.failUnless(isinstance(s1, PBChangeSource)) self.failUnless(s1.parent) d.addCallback(_check1) return d def shouldBeFailure(self, res, *expected): self.failUnless(isinstance(res, failure.Failure), "we expected this to fail, not produce %s" % (res,)) res.trap(*expected) return None # all is good def testSchedulerErrors(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.allSchedulers(), []) def _shouldBeFailure(res, hint=None): self.shouldBeFailure(res, AssertionError, ValueError) if hint: self.failUnless(str(res).find(hint) != -1) def _loadConfig(res, newcfg): return self.buildmaster.loadConfig(newcfg) d = defer.succeed(None) # c['schedulers'] must be a list badcfg = schedulersCfg + \ """ c['schedulers'] = Scheduler('full', None, 60, ['builder1']) """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "c['schedulers'] must be a list of Scheduler instances") # c['schedulers'] must be a list of IScheduler objects badcfg = schedulersCfg + \ """ c['schedulers'] = ['oops', 'problem'] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "c['schedulers'] must be a list of Scheduler instances") # c['schedulers'] must point at real builders badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, ['builder-bogus'])] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "uses unknown builder") # builderNames= must be a list badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, 'builder1')] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "must be a list of Builder description names") # builderNames= must be a list of strings, not dicts badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, [b1])] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "must be a list of Builder description names") # builderNames= must be a list of strings, not a dict badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, b1)] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "must be a list of Builder description names") # each Scheduler must have a unique name badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('dup', None, 60, []), Scheduler('dup', None, 60, [])] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "Schedulers must have unique names") return d def testSchedulers(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.allSchedulers(), []) d = self.buildmaster.loadConfig(schedulersCfg) d.addCallback(self._testSchedulers_1) return d def _testSchedulers_1(self, res): sch = self.buildmaster.allSchedulers() self.failUnlessEqual(len(sch), 1) s = sch[0] self.failUnless(isinstance(s, scheduler.Scheduler)) self.failUnlessEqual(s.name, "full") self.failUnlessEqual(s.branch, None) self.failUnlessEqual(s.treeStableTimer, 60) self.failUnlessEqual(s.builderNames, ['builder1']) newcfg = schedulersCfg + \ """ s1 = Scheduler('full', None, 60, ['builder1']) c['schedulers'] = [s1, Dependent('downstream', s1, ['builder1'])] """ d = self.buildmaster.loadConfig(newcfg) d.addCallback(self._testSchedulers_2, newcfg) return d def _testSchedulers_2(self, res, newcfg): sch = self.buildmaster.allSchedulers() self.failUnlessEqual(len(sch), 2) s = sch[0] self.failUnless(isinstance(s, scheduler.Scheduler)) s = sch[1] self.failUnless(isinstance(s, scheduler.Dependent)) self.failUnlessEqual(s.name, "downstream") self.failUnlessEqual(s.builderNames, ['builder1']) # reloading the same config file should leave the schedulers in place d = self.buildmaster.loadConfig(newcfg) d.addCallback(self._testSchedulers_3, sch) return d def _testSchedulers_3(self, res, sch1): sch2 = self.buildmaster.allSchedulers() self.failUnlessEqual(len(sch2), 2) sch1.sort() sch2.sort() self.failUnlessEqual(sch1, sch2) self.failUnlessIdentical(sch1[0], sch2[0]) self.failUnlessIdentical(sch1[1], sch2[1]) self.failUnlessIdentical(sch1[0].parent, self.buildmaster) self.failUnlessIdentical(sch1[1].parent, self.buildmaster) def testBuilders(self): master = self.buildmaster master.loadConfig(emptyCfg) self.failUnlessEqual(master.botmaster.builders, {}) master.loadConfig(buildersCfg) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b = master.botmaster.builders["builder1"] self.failUnless(isinstance(b, Builder)) self.failUnlessEqual(b.name, "builder1") self.failUnlessEqual(b.slavenames, ["bot1"]) self.failUnlessEqual(b.builddir, "workdir") f1 = b.buildFactory self.failUnless(isinstance(f1, BasicBuildFactory)) steps = f1.steps self.failUnlessEqual(len(steps), 3) self.failUnlessEqual(steps[0], (CVS, {'cvsroot': 'cvsroot', 'cvsmodule': 'cvsmodule', 'mode': 'clobber'})) self.failUnlessEqual(steps[1], (Compile, {'command': 'make all'})) self.failUnlessEqual(steps[2], (Test, {'command': 'make check'})) # make sure a reload of the same data doesn't interrupt the Builder master.loadConfig(buildersCfg) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b2 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b, b2) # TODO: test that the BuilderStatus object doesn't change #statusbag2 = master.client_svc.statusbags["builder1"] #self.failUnlessIdentical(statusbag, statusbag2) # but changing something should result in a new Builder master.loadConfig(buildersCfg2) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b3 = master.botmaster.builders["builder1"] self.failIf(b is b3) # the statusbag remains the same TODO #statusbag3 = master.client_svc.statusbags["builder1"] #self.failUnlessIdentical(statusbag, statusbag3) # adding new builder master.loadConfig(buildersCfg3) self.failUnlessEqual(master.botmaster.builderNames, ["builder1", "builder2"]) self.failUnlessListsEquivalent(master.botmaster.builders.keys(), ["builder1", "builder2"]) b4 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b3, b4) # changing first builder should leave it at the same place in the list master.loadConfig(buildersCfg4) self.failUnlessEqual(master.botmaster.builderNames, ["builder1", "builder2"]) self.failUnlessListsEquivalent(master.botmaster.builders.keys(), ["builder1", "builder2"]) b5 = master.botmaster.builders["builder1"] self.failIf(b4 is b5) master.loadConfig(buildersCfg5) self.failUnlessEqual(master.botmaster.builderNames, ["builder1", "builder2"]) self.failUnlessListsEquivalent(master.botmaster.builders.keys(), ["builder1", "builder2"]) b5 = master.botmaster.builders["builder1"] # and removing it should make the Builder go away master.loadConfig(emptyCfg) self.failUnlessEqual(master.botmaster.builderNames, []) self.failUnlessEqual(master.botmaster.builders, {}) #self.failUnlessEqual(master.client_svc.statusbags, {}) # TODO def testWithProperties(self): master = self.buildmaster master.loadConfig(wpCfg1) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b1 = master.botmaster.builders["builder1"] # reloading the same config should leave the builder unchanged master.loadConfig(wpCfg1) b2 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b1, b2) # but changing the parameters of the WithProperties should change it master.loadConfig(wpCfg2) b3 = master.botmaster.builders["builder1"] self.failIf(b1 is b3) # again, reloading same config should leave the builder unchanged master.loadConfig(wpCfg2) b4 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b3, b4) def checkIRC(self, m, expected): ircs = {} for irc in self.servers(m, words.IRC): ircs[irc.host] = (irc.nick, irc.channels) self.failUnlessEqual(ircs, expected) def testIRC(self): if not words: raise unittest.SkipTest("Twisted Words package is not installed") master = self.buildmaster master.loadChanges() d = master.loadConfig(emptyCfg) e1 = {} d.addCallback(lambda res: self.checkIRC(master, e1)) d.addCallback(lambda res: master.loadConfig(ircCfg1)) e2 = {'irc.us.freenode.net': ('buildbot', ['twisted'])} d.addCallback(lambda res: self.checkIRC(master, e2)) d.addCallback(lambda res: master.loadConfig(ircCfg2)) e3 = {'irc.us.freenode.net': ('buildbot', ['twisted']), 'irc.example.com': ('otherbot', ['chan1', 'chan2'])} d.addCallback(lambda res: self.checkIRC(master, e3)) d.addCallback(lambda res: master.loadConfig(ircCfg3)) e4 = {'irc.us.freenode.net': ('buildbot', ['knotted'])} d.addCallback(lambda res: self.checkIRC(master, e4)) d.addCallback(lambda res: master.loadConfig(ircCfg1)) e5 = {'irc.us.freenode.net': ('buildbot', ['twisted'])} d.addCallback(lambda res: self.checkIRC(master, e5)) return d def testWebPortnum(self): master = self.buildmaster master.loadChanges() d = master.loadConfig(webCfg1) def _check1(res): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9980, Site)]) p = ports[1] self.p = p # nothing should be changed d.addCallback(_check1) d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg1)) def _check2(res): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9980, Site)]) self.failUnlessIdentical(self.p, ports[1], "web port was changed even though " "configuration was not") # WebStatus is no longer a ComparableMixin, so it will be # rebuilt on each reconfig #d.addCallback(_check2) d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg2)) # changes port to 9981 def _check3(p): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9981, Site)]) self.failIf(self.p is ports[1], "configuration was changed but web port was unchanged") d.addCallback(_check3) d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg3)) # make 9981 on only localhost def _check4(p): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9981, Site)]) self.failUnlessEqual(ports[1].kwargs['interface'], "127.0.0.1") d.addCallback(_check4) d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg)) d.addCallback(lambda res: self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory)])) return d def testWebPathname(self): master = self.buildmaster master.loadChanges() d = master.loadConfig(webNameCfg1) def _check1(res): self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), ('~/.twistd-web-pb', pb.PBServerFactory)]) unixports = self.UNIXports(self.buildmaster) self.f = f = unixports[0].args[1] self.failUnless(isinstance(f.root, ResourcePublisher)) d.addCallback(_check1) d.addCallback(lambda res: self.buildmaster.loadConfig(webNameCfg1)) # nothing should be changed def _check2(res): self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), ('~/.twistd-web-pb', pb.PBServerFactory)]) newf = self.UNIXports(self.buildmaster)[0].args[1] self.failUnlessIdentical(self.f, newf, "web factory was changed even though " "configuration was not") # WebStatus is no longer a ComparableMixin, so it will be # rebuilt on each reconfig #d.addCallback(_check2) d.addCallback(lambda res: self.buildmaster.loadConfig(webNameCfg2)) def _check3(res): self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), ('./bar.socket', pb.PBServerFactory)]) newf = self.UNIXports(self.buildmaster)[0].args[1], self.failIf(self.f is newf, "web factory was unchanged but " "configuration was changed") d.addCallback(_check3) d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg)) d.addCallback(lambda res: self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory)])) return d def testDebugPassword(self): master = self.buildmaster master.loadConfig(debugPasswordCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw", "debug": "sekrit"}) master.loadConfig(debugPasswordCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw", "debug": "sekrit"}) master.loadConfig(emptyCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) def testLocks(self): master = self.buildmaster botmaster = master.botmaster # make sure that c['interlocks'] is rejected properly self.failUnlessRaises(KeyError, master.loadConfig, interlockCfgBad) # and that duplicate-named Locks are caught self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad1) self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad2) self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad3) # create a Builder that uses Locks master.loadConfig(lockCfg1a) b1 = master.botmaster.builders["builder1"] self.failUnlessEqual(len(b1.locks), 2) # reloading the same config should not change the Builder master.loadConfig(lockCfg1a) self.failUnlessIdentical(b1, master.botmaster.builders["builder1"]) # but changing the set of locks used should change it master.loadConfig(lockCfg1b) self.failIfIdentical(b1, master.botmaster.builders["builder1"]) b1 = master.botmaster.builders["builder1"] self.failUnlessEqual(len(b1.locks), 1) # similar test with step-scoped locks master.loadConfig(lockCfg2a) b1 = master.botmaster.builders["builder1"] # reloading the same config should not change the Builder master.loadConfig(lockCfg2a) self.failUnlessIdentical(b1, master.botmaster.builders["builder1"]) # but changing the set of locks used should change it master.loadConfig(lockCfg2b) self.failIfIdentical(b1, master.botmaster.builders["builder1"]) b1 = master.botmaster.builders["builder1"] # remove the locks entirely master.loadConfig(lockCfg2c) self.failIfIdentical(b1, master.botmaster.builders["builder1"]) def testNoChangeHorizon(self): master = self.buildmaster master.loadChanges() sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['change_source'] = PBChangeSource() """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) self.failUnlessEqual(self.buildmaster.change_svc.changeHorizon, 0) d.addCallback(_check1) return d def testChangeHorizon(self): master = self.buildmaster master.loadChanges() sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['change_source'] = PBChangeSource() c['changeHorizon'] = 5 """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) self.failUnlessEqual(self.buildmaster.change_svc.changeHorizon, 5) d.addCallback(_check1) return d
class ConfigTest(unittest.TestCase): def setUp(self): # this class generates several deprecation warnings, which the user # doesn't need to see. warnings.simplefilter('ignore', exceptions.DeprecationWarning) self.buildmaster = BuildMaster(".") def failUnlessListsEquivalent(self, list1, list2): l1 = list1[:] l1.sort() l2 = list2[:] l2.sort() self.failUnlessEqual(l1, l2) def servers(self, s, types): # perform a recursive search of s.services, looking for instances of # twisted.application.internet.TCPServer, then extract their .args # values to find the TCP ports they want to listen on for child in s: if service.IServiceCollection.providedBy(child): for gc in self.servers(child, types): yield gc if isinstance(child, types): yield child def TCPports(self, s): return list(self.servers(s, internet.TCPServer)) def UNIXports(self, s): return list(self.servers(s, internet.UNIXServer)) def TCPclients(self, s): return list(self.servers(s, internet.TCPClient)) def checkPorts(self, svc, expected): """Verify that the TCPServer and UNIXServer children of the given service have the expected portnum/pathname and factory classes. As a side-effect, return a list of servers in the same order as the 'expected' list. This can be used to verify properties of the factories contained therein.""" expTCP = [e for e in expected if type(e[0]) == int] expUNIX = [e for e in expected if type(e[0]) == str] haveTCP = [(p.args[0], p.args[1].__class__) for p in self.TCPports(svc)] haveUNIX = [(p.args[0], p.args[1].__class__) for p in self.UNIXports(svc)] self.failUnlessListsEquivalent(expTCP, haveTCP) self.failUnlessListsEquivalent(expUNIX, haveUNIX) ret = [] for e in expected: for have in self.TCPports(svc) + self.UNIXports(svc): if have.args[0] == e[0]: ret.append(have) continue assert (len(ret) == len(expected)) return ret def testEmpty(self): self.failUnlessRaises(KeyError, self.buildmaster.loadConfig, "") def testSimple(self): # covers slavePortnum, base checker passwords master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) # note: this doesn't actually start listening, because the app # hasn't been started running self.failUnlessEqual(master.slavePortnum, "tcp:9999") self.checkPorts(master, [(9999, pb.PBServerFactory)]) self.failUnlessEqual(list(master.change_svc), []) self.failUnlessEqual(master.botmaster.builders, {}) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) self.failUnlessEqual(master.projectName, "dummy project") self.failUnlessEqual(master.projectURL, "http://dummy.example.com") self.failUnlessEqual(master.buildbotURL, "http://dummy.example.com/buildbot") def testSlavePortnum(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.slavePortnum, "tcp:9999") ports = self.checkPorts(master, [(9999, pb.PBServerFactory)]) p = ports[0] master.loadConfig(emptyCfg) self.failUnlessEqual(master.slavePortnum, "tcp:9999") ports = self.checkPorts(master, [(9999, pb.PBServerFactory)]) self.failUnlessIdentical(p, ports[0], "the slave port was changed even " + \ "though the configuration was not") master.loadConfig(emptyCfg + "c['slavePortnum'] = 9000\n") self.failUnlessEqual(master.slavePortnum, "tcp:9000") ports = self.checkPorts(master, [(9000, pb.PBServerFactory)]) self.failIf(p is ports[0], "slave port was unchanged but configuration was changed") def testSlaves(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.botmaster.builders, {}) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) # 'botsCfg' is testing backwards compatibility, for 0.7.5 config # files that have not yet been updated to 0.7.6 . This compatibility # (and this test) is scheduled for removal in 0.8.0 . botsCfg = (emptyCfg + "c['bots'] = [('bot1', 'pw1'), ('bot2', 'pw2')]\n") master.loadConfig(botsCfg) self.failUnlessEqual(master.checker.users, { "change": "changepw", "bot1": "pw1", "bot2": "pw2" }) master.loadConfig(botsCfg) self.failUnlessEqual(master.checker.users, { "change": "changepw", "bot1": "pw1", "bot2": "pw2" }) master.loadConfig(emptyCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) slavesCfg = (emptyCfg + "from buildbot.buildslave import BuildSlave\n" "c['slaves'] = [BuildSlave('bot1','pw1'), " "BuildSlave('bot2','pw2')]\n") master.loadConfig(slavesCfg) self.failUnlessEqual(master.checker.users, { "change": "changepw", "bot1": "pw1", "bot2": "pw2" }) def testChangeSource(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(list(master.change_svc), []) sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['change_source'] = PBChangeSource() """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) s1 = list(self.buildmaster.change_svc)[0] self.failUnless(isinstance(s1, PBChangeSource)) self.failUnlessEqual(s1, list(self.buildmaster.change_svc)[0]) self.failUnless(s1.parent) # verify that unchanged sources are not interrupted d1 = self.buildmaster.loadConfig(sourcesCfg) def _check2(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) s2 = list(self.buildmaster.change_svc)[0] self.failUnlessIdentical(s1, s2) self.failUnless(s1.parent) d1.addCallback(_check2) return d1 d.addCallback(_check1) # make sure we can get rid of the sources too d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg)) def _check3(res): self.failUnlessEqual(list(self.buildmaster.change_svc), []) d.addCallback(_check3) return d def testChangeSources(self): # make sure we can accept a list master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(list(master.change_svc), []) sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource from buildbot.changes.mail import SyncmailMaildirSource c['change_source'] = [PBChangeSource(), SyncmailMaildirSource('.'), ] """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 2) s1, s2 = list(self.buildmaster.change_svc) if isinstance(s2, PBChangeSource): s1, s2 = s2, s1 self.failUnless(isinstance(s1, PBChangeSource)) self.failUnless(s1.parent) self.failUnless(isinstance(s2, SyncmailMaildirSource)) self.failUnless(s2.parent) d.addCallback(_check1) return d def testSources(self): # test backwards compatibility. c['sources'] is deprecated. master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(list(master.change_svc), []) sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['sources'] = [PBChangeSource()] """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) s1 = list(self.buildmaster.change_svc)[0] self.failUnless(isinstance(s1, PBChangeSource)) self.failUnless(s1.parent) d.addCallback(_check1) return d def shouldBeFailure(self, res, *expected): self.failUnless(isinstance(res, failure.Failure), "we expected this to fail, not produce %s" % (res, )) res.trap(*expected) return None # all is good def testSchedulerErrors(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.allSchedulers(), []) def _shouldBeFailure(res, hint=None): self.shouldBeFailure(res, AssertionError, ValueError) if hint: self.failUnless(str(res).find(hint) != -1) def _loadConfig(res, newcfg): return self.buildmaster.loadConfig(newcfg) d = defer.succeed(None) # c['schedulers'] must be a list badcfg = schedulersCfg + \ """ c['schedulers'] = Scheduler('full', None, 60, ['builder1']) """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "c['schedulers'] must be a list of Scheduler instances") # c['schedulers'] must be a list of IScheduler objects badcfg = schedulersCfg + \ """ c['schedulers'] = ['oops', 'problem'] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "c['schedulers'] must be a list of Scheduler instances") # c['schedulers'] must point at real builders badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, ['builder-bogus'])] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "uses unknown builder") # builderNames= must be a list badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, 'builder1')] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "must be a list of Builder description names") # builderNames= must be a list of strings, not dicts badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, [b1])] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "must be a list of Builder description names") # builderNames= must be a list of strings, not a dict badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('full', None, 60, b1)] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "must be a list of Builder description names") # each Scheduler must have a unique name badcfg = schedulersCfg + \ """ c['schedulers'] = [Scheduler('dup', None, 60, []), Scheduler('dup', None, 60, [])] """ d.addCallback(_loadConfig, badcfg) d.addBoth(_shouldBeFailure, "Schedulers must have unique names") return d def testSchedulers(self): master = self.buildmaster master.loadChanges() master.loadConfig(emptyCfg) self.failUnlessEqual(master.allSchedulers(), []) d = self.buildmaster.loadConfig(schedulersCfg) d.addCallback(self._testSchedulers_1) return d def _testSchedulers_1(self, res): sch = self.buildmaster.allSchedulers() self.failUnlessEqual(len(sch), 1) s = sch[0] self.failUnless(isinstance(s, scheduler.Scheduler)) self.failUnlessEqual(s.name, "full") self.failUnlessEqual(s.branch, None) self.failUnlessEqual(s.treeStableTimer, 60) self.failUnlessEqual(s.builderNames, ['builder1']) newcfg = schedulersCfg + \ """ s1 = Scheduler('full', None, 60, ['builder1']) c['schedulers'] = [s1, Dependent('downstream', s1, ['builder1'])] """ d = self.buildmaster.loadConfig(newcfg) d.addCallback(self._testSchedulers_2, newcfg) return d def _testSchedulers_2(self, res, newcfg): sch = self.buildmaster.allSchedulers() self.failUnlessEqual(len(sch), 2) s = sch[0] self.failUnless(isinstance(s, scheduler.Scheduler)) s = sch[1] self.failUnless(isinstance(s, scheduler.Dependent)) self.failUnlessEqual(s.name, "downstream") self.failUnlessEqual(s.builderNames, ['builder1']) # reloading the same config file should leave the schedulers in place d = self.buildmaster.loadConfig(newcfg) d.addCallback(self._testSchedulers_3, sch) return d def _testSchedulers_3(self, res, sch1): sch2 = self.buildmaster.allSchedulers() self.failUnlessEqual(len(sch2), 2) sch1.sort() sch2.sort() self.failUnlessEqual(sch1, sch2) self.failUnlessIdentical(sch1[0], sch2[0]) self.failUnlessIdentical(sch1[1], sch2[1]) self.failUnlessIdentical(sch1[0].parent, self.buildmaster) self.failUnlessIdentical(sch1[1].parent, self.buildmaster) def testBuilders(self): master = self.buildmaster master.loadConfig(emptyCfg) self.failUnlessEqual(master.botmaster.builders, {}) master.loadConfig(buildersCfg) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b = master.botmaster.builders["builder1"] self.failUnless(isinstance(b, Builder)) self.failUnlessEqual(b.name, "builder1") self.failUnlessEqual(b.slavenames, ["bot1"]) self.failUnlessEqual(b.builddir, "workdir") f1 = b.buildFactory self.failUnless(isinstance(f1, BasicBuildFactory)) steps = f1.steps self.failUnlessEqual(len(steps), 3) self.failUnlessEqual(steps[0], (CVS, { 'cvsroot': 'cvsroot', 'cvsmodule': 'cvsmodule', 'mode': 'clobber' })) self.failUnlessEqual(steps[1], (Compile, {'command': 'make all'})) self.failUnlessEqual(steps[2], (Test, {'command': 'make check'})) # make sure a reload of the same data doesn't interrupt the Builder master.loadConfig(buildersCfg) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b2 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b, b2) # TODO: test that the BuilderStatus object doesn't change #statusbag2 = master.client_svc.statusbags["builder1"] #self.failUnlessIdentical(statusbag, statusbag2) # but changing something should result in a new Builder master.loadConfig(buildersCfg2) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b3 = master.botmaster.builders["builder1"] self.failIf(b is b3) # the statusbag remains the same TODO #statusbag3 = master.client_svc.statusbags["builder1"] #self.failUnlessIdentical(statusbag, statusbag3) # adding new builder master.loadConfig(buildersCfg3) self.failUnlessEqual(master.botmaster.builderNames, ["builder1", "builder2"]) self.failUnlessListsEquivalent(master.botmaster.builders.keys(), ["builder1", "builder2"]) b4 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b3, b4) # changing first builder should leave it at the same place in the list master.loadConfig(buildersCfg4) self.failUnlessEqual(master.botmaster.builderNames, ["builder1", "builder2"]) self.failUnlessListsEquivalent(master.botmaster.builders.keys(), ["builder1", "builder2"]) b5 = master.botmaster.builders["builder1"] self.failIf(b4 is b5) master.loadConfig(buildersCfg5) self.failUnlessEqual(master.botmaster.builderNames, ["builder1", "builder2"]) self.failUnlessListsEquivalent(master.botmaster.builders.keys(), ["builder1", "builder2"]) b5 = master.botmaster.builders["builder1"] # and removing it should make the Builder go away master.loadConfig(emptyCfg) self.failUnlessEqual(master.botmaster.builderNames, []) self.failUnlessEqual(master.botmaster.builders, {}) #self.failUnlessEqual(master.client_svc.statusbags, {}) # TODO def testWithProperties(self): master = self.buildmaster master.loadConfig(wpCfg1) self.failUnlessEqual(master.botmaster.builderNames, ["builder1"]) self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"]) b1 = master.botmaster.builders["builder1"] # reloading the same config should leave the builder unchanged master.loadConfig(wpCfg1) b2 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b1, b2) # but changing the parameters of the WithProperties should change it master.loadConfig(wpCfg2) b3 = master.botmaster.builders["builder1"] self.failIf(b1 is b3) # again, reloading same config should leave the builder unchanged master.loadConfig(wpCfg2) b4 = master.botmaster.builders["builder1"] self.failUnlessIdentical(b3, b4) def checkIRC(self, m, expected): ircs = {} for irc in self.servers(m, words.IRC): ircs[irc.host] = (irc.nick, irc.channels) self.failUnlessEqual(ircs, expected) def testIRC(self): if not words: raise unittest.SkipTest("Twisted Words package is not installed") master = self.buildmaster master.loadChanges() d = master.loadConfig(emptyCfg) e1 = {} d.addCallback(lambda res: self.checkIRC(master, e1)) d.addCallback(lambda res: master.loadConfig(ircCfg1)) e2 = {'irc.us.freenode.net': ('buildbot', ['twisted'])} d.addCallback(lambda res: self.checkIRC(master, e2)) d.addCallback(lambda res: master.loadConfig(ircCfg2)) e3 = { 'irc.us.freenode.net': ('buildbot', ['twisted']), 'irc.example.com': ('otherbot', ['chan1', 'chan2']) } d.addCallback(lambda res: self.checkIRC(master, e3)) d.addCallback(lambda res: master.loadConfig(ircCfg3)) e4 = {'irc.us.freenode.net': ('buildbot', ['knotted'])} d.addCallback(lambda res: self.checkIRC(master, e4)) d.addCallback(lambda res: master.loadConfig(ircCfg1)) e5 = {'irc.us.freenode.net': ('buildbot', ['twisted'])} d.addCallback(lambda res: self.checkIRC(master, e5)) return d def testWebPortnum(self): master = self.buildmaster master.loadChanges() d = master.loadConfig(webCfg1) def _check1(res): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9980, Site)]) p = ports[1] self.p = p # nothing should be changed d.addCallback(_check1) d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg1)) def _check2(res): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9980, Site)]) self.failUnlessIdentical( self.p, ports[1], "web port was changed even though " "configuration was not") # WebStatus is no longer a ComparableMixin, so it will be # rebuilt on each reconfig #d.addCallback(_check2) d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg2)) # changes port to 9981 def _check3(p): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9981, Site)]) self.failIf( self.p is ports[1], "configuration was changed but web port was unchanged") d.addCallback(_check3) d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg3)) # make 9981 on only localhost def _check4(p): ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), (9981, Site)]) self.failUnlessEqual(ports[1].kwargs['interface'], "127.0.0.1") d.addCallback(_check4) d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg)) d.addCallback(lambda res: self.checkPorts(self.buildmaster, [( 9999, pb.PBServerFactory)])) return d def testWebPathname(self): master = self.buildmaster master.loadChanges() d = master.loadConfig(webNameCfg1) def _check1(res): self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), ('~/.twistd-web-pb', pb.PBServerFactory)]) unixports = self.UNIXports(self.buildmaster) self.f = f = unixports[0].args[1] self.failUnless(isinstance(f.root, ResourcePublisher)) d.addCallback(_check1) d.addCallback(lambda res: self.buildmaster.loadConfig(webNameCfg1)) # nothing should be changed def _check2(res): self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), ('~/.twistd-web-pb', pb.PBServerFactory)]) newf = self.UNIXports(self.buildmaster)[0].args[1] self.failUnlessIdentical( self.f, newf, "web factory was changed even though " "configuration was not") # WebStatus is no longer a ComparableMixin, so it will be # rebuilt on each reconfig #d.addCallback(_check2) d.addCallback(lambda res: self.buildmaster.loadConfig(webNameCfg2)) def _check3(res): self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory), ('./bar.socket', pb.PBServerFactory)]) newf = self.UNIXports(self.buildmaster)[0].args[1], self.failIf( self.f is newf, "web factory was unchanged but " "configuration was changed") d.addCallback(_check3) d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg)) d.addCallback(lambda res: self.checkPorts(self.buildmaster, [( 9999, pb.PBServerFactory)])) return d def testDebugPassword(self): master = self.buildmaster master.loadConfig(debugPasswordCfg) self.failUnlessEqual(master.checker.users, { "change": "changepw", "debug": "sekrit" }) master.loadConfig(debugPasswordCfg) self.failUnlessEqual(master.checker.users, { "change": "changepw", "debug": "sekrit" }) master.loadConfig(emptyCfg) self.failUnlessEqual(master.checker.users, {"change": "changepw"}) def testLocks(self): master = self.buildmaster botmaster = master.botmaster # make sure that c['interlocks'] is rejected properly self.failUnlessRaises(KeyError, master.loadConfig, interlockCfgBad) # and that duplicate-named Locks are caught self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad1) self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad2) self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad3) # create a Builder that uses Locks master.loadConfig(lockCfg1a) b1 = master.botmaster.builders["builder1"] self.failUnlessEqual(len(b1.locks), 2) # reloading the same config should not change the Builder master.loadConfig(lockCfg1a) self.failUnlessIdentical(b1, master.botmaster.builders["builder1"]) # but changing the set of locks used should change it master.loadConfig(lockCfg1b) self.failIfIdentical(b1, master.botmaster.builders["builder1"]) b1 = master.botmaster.builders["builder1"] self.failUnlessEqual(len(b1.locks), 1) # similar test with step-scoped locks master.loadConfig(lockCfg2a) b1 = master.botmaster.builders["builder1"] # reloading the same config should not change the Builder master.loadConfig(lockCfg2a) self.failUnlessIdentical(b1, master.botmaster.builders["builder1"]) # but changing the set of locks used should change it master.loadConfig(lockCfg2b) self.failIfIdentical(b1, master.botmaster.builders["builder1"]) b1 = master.botmaster.builders["builder1"] # remove the locks entirely master.loadConfig(lockCfg2c) self.failIfIdentical(b1, master.botmaster.builders["builder1"]) def testNoChangeHorizon(self): master = self.buildmaster master.loadChanges() sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['change_source'] = PBChangeSource() """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) self.failUnlessEqual(self.buildmaster.change_svc.changeHorizon, 0) d.addCallback(_check1) return d def testChangeHorizon(self): master = self.buildmaster master.loadChanges() sourcesCfg = emptyCfg + \ """ from buildbot.changes.pb import PBChangeSource c['change_source'] = PBChangeSource() c['changeHorizon'] = 5 """ d = master.loadConfig(sourcesCfg) def _check1(res): self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1) self.failUnlessEqual(self.buildmaster.change_svc.changeHorizon, 5) d.addCallback(_check1) return d