remove all uses of buildbot.twcompat.maybeWait, now that we don't need to maintain...
[buildbot.git] / buildbot / test / test_config.py
blob074188520bfc9ca87afdafe9b261d226fd60371b
1 # -*- test-case-name: buildbot.test.test_config -*-
3 from __future__ import generators
4 import os
6 from twisted.trial import unittest
7 from twisted.python import failure
8 from twisted.internet import defer
10 cvstoys = None
11 try:
12 import cvstoys
13 from buildbot.changes.freshcvs import FreshCVSSource
14 except ImportError:
15 pass
17 from buildbot.twcompat import providedBy
18 from buildbot.master import BuildMaster
19 from buildbot import scheduler
20 from twisted.application import service, internet
21 from twisted.spread import pb
22 from twisted.web.server import Site
23 from twisted.web.distrib import ResourcePublisher
24 from buildbot.process.builder import Builder
25 from buildbot.process.factory import BasicBuildFactory
26 from buildbot.steps.source import CVS
27 from buildbot.steps.shell import Compile, Test
28 from buildbot.status import base
29 words = None
30 try:
31 from buildbot.status import words
32 except ImportError:
33 pass
35 emptyCfg = \
36 """
37 BuildmasterConfig = c = {}
38 c['bots'] = []
39 c['sources'] = []
40 c['schedulers'] = []
41 c['builders'] = []
42 c['slavePortnum'] = 9999
43 c['projectName'] = 'dummy project'
44 c['projectURL'] = 'http://dummy.example.com'
45 c['buildbotURL'] = 'http://dummy.example.com/buildbot'
46 """
48 buildersCfg = \
49 """
50 from buildbot.process.factory import BasicBuildFactory
51 BuildmasterConfig = c = {}
52 c['bots'] = [('bot1', 'pw1')]
53 c['sources'] = []
54 c['schedulers'] = []
55 c['slavePortnum'] = 9999
56 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
57 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
58 'builddir':'workdir', 'factory':f1}]
59 """
61 buildersCfg2 = buildersCfg + \
62 """
63 f1 = BasicBuildFactory('cvsroot', 'cvsmodule2')
64 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
65 'builddir':'workdir', 'factory':f1}]
66 """
68 buildersCfg3 = buildersCfg2 + \
69 """
70 c['builders'].append({'name': 'builder2', 'slavename': 'bot1',
71 'builddir': 'workdir2', 'factory': f1 })
72 """
74 buildersCfg4 = buildersCfg2 + \
75 """
76 c['builders'] = [{ 'name': 'builder1', 'slavename': 'bot1',
77 'builddir': 'newworkdir', 'factory': f1 },
78 { 'name': 'builder2', 'slavename': 'bot1',
79 'builddir': 'workdir2', 'factory': f1 }]
80 """
82 wpCfg1 = buildersCfg + \
83 """
84 from buildbot.steps import shell
85 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
86 f1.addStep(shell.ShellCommand, command=[shell.WithProperties('echo')])
87 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
88 'builddir':'workdir1', 'factory': f1}]
89 """
91 wpCfg2 = buildersCfg + \
92 """
93 from buildbot.steps import shell
94 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
95 f1.addStep(shell.ShellCommand,
96 command=[shell.WithProperties('echo %s', 'revision')])
97 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
98 'builddir':'workdir1', 'factory': f1}]
99 """
103 ircCfg1 = emptyCfg + \
105 from buildbot.status import words
106 c['status'] = [words.IRC('irc.us.freenode.net', 'buildbot', ['twisted'])]
109 ircCfg2 = emptyCfg + \
111 from buildbot.status import words
112 c['status'] = [words.IRC('irc.us.freenode.net', 'buildbot', ['twisted']),
113 words.IRC('irc.example.com', 'otherbot', ['chan1', 'chan2'])]
116 ircCfg3 = emptyCfg + \
118 from buildbot.status import words
119 c['status'] = [words.IRC('irc.us.freenode.net', 'buildbot', ['knotted'])]
122 webCfg1 = emptyCfg + \
124 from buildbot.status import html
125 c['status'] = [html.Waterfall(http_port=9980)]
128 webCfg2 = emptyCfg + \
130 from buildbot.status import html
131 c['status'] = [html.Waterfall(http_port=9981)]
134 webCfg3 = emptyCfg + \
136 from buildbot.status import html
137 c['status'] = [html.Waterfall(http_port='tcp:9981:interface=127.0.0.1')]
140 webNameCfg1 = emptyCfg + \
142 from buildbot.status import html
143 c['status'] = [html.Waterfall(distrib_port='~/.twistd-web-pb')]
146 webNameCfg2 = emptyCfg + \
148 from buildbot.status import html
149 c['status'] = [html.Waterfall(distrib_port='./bar.socket')]
152 debugPasswordCfg = emptyCfg + \
154 c['debugPassword'] = 'sekrit'
157 interlockCfgBad = \
159 from buildbot.process.factory import BasicBuildFactory
160 c = {}
161 c['bots'] = [('bot1', 'pw1')]
162 c['sources'] = []
163 c['schedulers'] = []
164 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
165 c['builders'] = [
166 { 'name': 'builder1', 'slavename': 'bot1',
167 'builddir': 'workdir', 'factory': f1 },
168 { 'name': 'builder2', 'slavename': 'bot1',
169 'builddir': 'workdir2', 'factory': f1 },
171 # interlocks have been removed
172 c['interlocks'] = [('lock1', ['builder1'], ['builder2', 'builder3']),
174 c['slavePortnum'] = 9999
175 BuildmasterConfig = c
178 lockCfgBad1 = \
180 from buildbot.steps.dummy import Dummy
181 from buildbot.process.factory import BuildFactory, s
182 from buildbot.locks import MasterLock
183 c = {}
184 c['bots'] = [('bot1', 'pw1')]
185 c['sources'] = []
186 c['schedulers'] = []
187 l1 = MasterLock('lock1')
188 l2 = MasterLock('lock1') # duplicate lock name
189 f1 = BuildFactory([s(Dummy, locks=[])])
190 c['builders'] = [
191 { 'name': 'builder1', 'slavename': 'bot1',
192 'builddir': 'workdir', 'factory': f1, 'locks': [l1, l2] },
193 { 'name': 'builder2', 'slavename': 'bot1',
194 'builddir': 'workdir2', 'factory': f1 },
196 c['slavePortnum'] = 9999
197 BuildmasterConfig = c
200 lockCfgBad2 = \
202 from buildbot.steps.dummy import Dummy
203 from buildbot.process.factory import BuildFactory, s
204 from buildbot.locks import MasterLock, SlaveLock
205 c = {}
206 c['bots'] = [('bot1', 'pw1')]
207 c['sources'] = []
208 c['schedulers'] = []
209 l1 = MasterLock('lock1')
210 l2 = SlaveLock('lock1') # duplicate lock name
211 f1 = BuildFactory([s(Dummy, locks=[])])
212 c['builders'] = [
213 { 'name': 'builder1', 'slavename': 'bot1',
214 'builddir': 'workdir', 'factory': f1, 'locks': [l1, l2] },
215 { 'name': 'builder2', 'slavename': 'bot1',
216 'builddir': 'workdir2', 'factory': f1 },
218 c['slavePortnum'] = 9999
219 BuildmasterConfig = c
222 lockCfgBad3 = \
224 from buildbot.steps.dummy import Dummy
225 from buildbot.process.factory import BuildFactory, s
226 from buildbot.locks import MasterLock
227 c = {}
228 c['bots'] = [('bot1', 'pw1')]
229 c['sources'] = []
230 c['schedulers'] = []
231 l1 = MasterLock('lock1')
232 l2 = MasterLock('lock1') # duplicate lock name
233 f1 = BuildFactory([s(Dummy, locks=[l2])])
234 f2 = BuildFactory([s(Dummy)])
235 c['builders'] = [
236 { 'name': 'builder1', 'slavename': 'bot1',
237 'builddir': 'workdir', 'factory': f2, 'locks': [l1] },
238 { 'name': 'builder2', 'slavename': 'bot1',
239 'builddir': 'workdir2', 'factory': f1 },
241 c['slavePortnum'] = 9999
242 BuildmasterConfig = c
245 lockCfg1a = \
247 from buildbot.process.factory import BasicBuildFactory
248 from buildbot.locks import MasterLock
249 c = {}
250 c['bots'] = [('bot1', 'pw1')]
251 c['sources'] = []
252 c['schedulers'] = []
253 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
254 l1 = MasterLock('lock1')
255 l2 = MasterLock('lock2')
256 c['builders'] = [
257 { 'name': 'builder1', 'slavename': 'bot1',
258 'builddir': 'workdir', 'factory': f1, 'locks': [l1, l2] },
259 { 'name': 'builder2', 'slavename': 'bot1',
260 'builddir': 'workdir2', 'factory': f1 },
262 c['slavePortnum'] = 9999
263 BuildmasterConfig = c
266 lockCfg1b = \
268 from buildbot.process.factory import BasicBuildFactory
269 from buildbot.locks import MasterLock
270 c = {}
271 c['bots'] = [('bot1', 'pw1')]
272 c['sources'] = []
273 c['schedulers'] = []
274 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
275 l1 = MasterLock('lock1')
276 l2 = MasterLock('lock2')
277 c['builders'] = [
278 { 'name': 'builder1', 'slavename': 'bot1',
279 'builddir': 'workdir', 'factory': f1, 'locks': [l1] },
280 { 'name': 'builder2', 'slavename': 'bot1',
281 'builddir': 'workdir2', 'factory': f1 },
283 c['slavePortnum'] = 9999
284 BuildmasterConfig = c
287 # test out step Locks
288 lockCfg2a = \
290 from buildbot.steps.dummy import Dummy
291 from buildbot.process.factory import BuildFactory, s
292 from buildbot.locks import MasterLock
293 c = {}
294 c['bots'] = [('bot1', 'pw1')]
295 c['sources'] = []
296 c['schedulers'] = []
297 l1 = MasterLock('lock1')
298 l2 = MasterLock('lock2')
299 f1 = BuildFactory([s(Dummy, locks=[l1,l2])])
300 f2 = BuildFactory([s(Dummy)])
302 c['builders'] = [
303 { 'name': 'builder1', 'slavename': 'bot1',
304 'builddir': 'workdir', 'factory': f1 },
305 { 'name': 'builder2', 'slavename': 'bot1',
306 'builddir': 'workdir2', 'factory': f2 },
308 c['slavePortnum'] = 9999
309 BuildmasterConfig = c
312 lockCfg2b = \
314 from buildbot.steps.dummy import Dummy
315 from buildbot.process.factory import BuildFactory, s
316 from buildbot.locks import MasterLock
317 c = {}
318 c['bots'] = [('bot1', 'pw1')]
319 c['sources'] = []
320 c['schedulers'] = []
321 l1 = MasterLock('lock1')
322 l2 = MasterLock('lock2')
323 f1 = BuildFactory([s(Dummy, locks=[l1])])
324 f2 = BuildFactory([s(Dummy)])
326 c['builders'] = [
327 { 'name': 'builder1', 'slavename': 'bot1',
328 'builddir': 'workdir', 'factory': f1 },
329 { 'name': 'builder2', 'slavename': 'bot1',
330 'builddir': 'workdir2', 'factory': f2 },
332 c['slavePortnum'] = 9999
333 BuildmasterConfig = c
336 lockCfg2c = \
338 from buildbot.steps.dummy import Dummy
339 from buildbot.process.factory import BuildFactory, s
340 from buildbot.locks import MasterLock
341 c = {}
342 c['bots'] = [('bot1', 'pw1')]
343 c['sources'] = []
344 c['schedulers'] = []
345 l1 = MasterLock('lock1')
346 l2 = MasterLock('lock2')
347 f1 = BuildFactory([s(Dummy)])
348 f2 = BuildFactory([s(Dummy)])
350 c['builders'] = [
351 { 'name': 'builder1', 'slavename': 'bot1',
352 'builddir': 'workdir', 'factory': f1 },
353 { 'name': 'builder2', 'slavename': 'bot1',
354 'builddir': 'workdir2', 'factory': f2 },
356 c['slavePortnum'] = 9999
357 BuildmasterConfig = c
360 schedulersCfg = \
362 from buildbot.scheduler import Scheduler, Dependent
363 from buildbot.process.factory import BasicBuildFactory
364 c = {}
365 c['bots'] = [('bot1', 'pw1')]
366 c['sources'] = []
367 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
368 b1 = {'name':'builder1', 'slavename':'bot1',
369 'builddir':'workdir', 'factory':f1}
370 c['builders'] = [b1]
371 c['schedulers'] = [Scheduler('full', None, 60, ['builder1'])]
372 c['slavePortnum'] = 9999
373 c['projectName'] = 'dummy project'
374 c['projectURL'] = 'http://dummy.example.com'
375 c['buildbotURL'] = 'http://dummy.example.com/buildbot'
376 BuildmasterConfig = c
379 class ConfigTest(unittest.TestCase):
380 def setUp(self):
381 self.buildmaster = BuildMaster(".")
383 def failUnlessListsEquivalent(self, list1, list2):
384 l1 = list1[:]
385 l1.sort()
386 l2 = list2[:]
387 l2.sort()
388 self.failUnlessEqual(l1, l2)
390 def servers(self, s, types):
391 # perform a recursive search of s.services, looking for instances of
392 # twisted.application.internet.TCPServer, then extract their .args
393 # values to find the TCP ports they want to listen on
394 for child in s:
395 if providedBy(child, service.IServiceCollection):
396 for gc in self.servers(child, types):
397 yield gc
398 if isinstance(child, types):
399 yield child
401 def TCPports(self, s):
402 return list(self.servers(s, internet.TCPServer))
403 def UNIXports(self, s):
404 return list(self.servers(s, internet.UNIXServer))
405 def TCPclients(self, s):
406 return list(self.servers(s, internet.TCPClient))
408 def checkPorts(self, svc, expected):
409 """Verify that the TCPServer and UNIXServer children of the given
410 service have the expected portnum/pathname and factory classes. As a
411 side-effect, return a list of servers in the same order as the
412 'expected' list. This can be used to verify properties of the
413 factories contained therein."""
415 expTCP = [e for e in expected if type(e[0]) == int]
416 expUNIX = [e for e in expected if type(e[0]) == str]
417 haveTCP = [(p.args[0], p.args[1].__class__)
418 for p in self.TCPports(svc)]
419 haveUNIX = [(p.args[0], p.args[1].__class__)
420 for p in self.UNIXports(svc)]
421 self.failUnlessListsEquivalent(expTCP, haveTCP)
422 self.failUnlessListsEquivalent(expUNIX, haveUNIX)
423 ret = []
424 for e in expected:
425 for have in self.TCPports(svc) + self.UNIXports(svc):
426 if have.args[0] == e[0]:
427 ret.append(have)
428 continue
429 assert(len(ret) == len(expected))
430 return ret
432 def testEmpty(self):
433 self.failUnlessRaises(KeyError, self.buildmaster.loadConfig, "")
435 def testSimple(self):
436 # covers slavePortnum, base checker passwords
437 master = self.buildmaster
438 master.loadChanges()
440 master.loadConfig(emptyCfg)
441 # note: this doesn't actually start listening, because the app
442 # hasn't been started running
443 self.failUnlessEqual(master.slavePortnum, "tcp:9999")
444 self.checkPorts(master, [(9999, pb.PBServerFactory)])
445 self.failUnlessEqual(list(master.change_svc), [])
446 self.failUnlessEqual(master.botmaster.builders, {})
447 self.failUnlessEqual(master.checker.users,
448 {"change": "changepw"})
449 self.failUnlessEqual(master.projectName, "dummy project")
450 self.failUnlessEqual(master.projectURL, "http://dummy.example.com")
451 self.failUnlessEqual(master.buildbotURL,
452 "http://dummy.example.com/buildbot")
454 def testSlavePortnum(self):
455 master = self.buildmaster
456 master.loadChanges()
458 master.loadConfig(emptyCfg)
459 self.failUnlessEqual(master.slavePortnum, "tcp:9999")
460 ports = self.checkPorts(master, [(9999, pb.PBServerFactory)])
461 p = ports[0]
463 master.loadConfig(emptyCfg)
464 self.failUnlessEqual(master.slavePortnum, "tcp:9999")
465 ports = self.checkPorts(master, [(9999, pb.PBServerFactory)])
466 self.failUnlessIdentical(p, ports[0],
467 "the slave port was changed even " + \
468 "though the configuration was not")
470 master.loadConfig(emptyCfg + "c['slavePortnum'] = 9000\n")
471 self.failUnlessEqual(master.slavePortnum, "tcp:9000")
472 ports = self.checkPorts(master, [(9000, pb.PBServerFactory)])
473 self.failIf(p is ports[0],
474 "slave port was unchanged but configuration was changed")
476 def testBots(self):
477 master = self.buildmaster
478 master.loadChanges()
479 master.loadConfig(emptyCfg)
480 self.failUnlessEqual(master.botmaster.builders, {})
481 self.failUnlessEqual(master.checker.users,
482 {"change": "changepw"})
483 botsCfg = (emptyCfg +
484 "c['bots'] = [('bot1', 'pw1'), ('bot2', 'pw2')]\n")
485 master.loadConfig(botsCfg)
486 self.failUnlessEqual(master.checker.users,
487 {"change": "changepw",
488 "bot1": "pw1",
489 "bot2": "pw2"})
490 master.loadConfig(botsCfg)
491 self.failUnlessEqual(master.checker.users,
492 {"change": "changepw",
493 "bot1": "pw1",
494 "bot2": "pw2"})
495 master.loadConfig(emptyCfg)
496 self.failUnlessEqual(master.checker.users,
497 {"change": "changepw"})
500 def testSources(self):
501 if not cvstoys:
502 raise unittest.SkipTest("this test needs CVSToys installed")
503 master = self.buildmaster
504 master.loadChanges()
505 master.loadConfig(emptyCfg)
506 self.failUnlessEqual(list(master.change_svc), [])
508 self.sourcesCfg = emptyCfg + \
510 from buildbot.changes.freshcvs import FreshCVSSource
511 s1 = FreshCVSSource('cvs.example.com', 1000, 'pname', 'spass',
512 prefix='Prefix/')
513 c['sources'] = [s1]
516 d = master.loadConfig(self.sourcesCfg)
517 d.addCallback(self._testSources_1)
518 return d
520 def _testSources_1(self, res):
521 self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1)
522 s1 = list(self.buildmaster.change_svc)[0]
523 self.failUnless(isinstance(s1, FreshCVSSource))
524 self.failUnlessEqual(s1.host, "cvs.example.com")
525 self.failUnlessEqual(s1.port, 1000)
526 self.failUnlessEqual(s1.prefix, "Prefix/")
527 self.failUnlessEqual(s1, list(self.buildmaster.change_svc)[0])
528 self.failUnless(s1.parent)
530 # verify that unchanged sources are not interrupted
531 d = self.buildmaster.loadConfig(self.sourcesCfg)
532 d.addCallback(self._testSources_2, s1)
533 return d
535 def _testSources_2(self, res, s1):
536 self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 1)
537 s2 = list(self.buildmaster.change_svc)[0]
538 self.failUnlessIdentical(s1, s2)
539 self.failUnless(s1.parent)
541 # make sure we can get rid of the sources too
542 d = self.buildmaster.loadConfig(emptyCfg)
543 d.addCallback(self._testSources_3)
544 return d
546 def _testSources_3(self, res):
547 self.failUnlessEqual(list(self.buildmaster.change_svc), [])
549 def shouldBeFailure(self, res, *expected):
550 self.failUnless(isinstance(res, failure.Failure),
551 "we expected this to fail, not produce %s" % (res,))
552 res.trap(*expected)
553 return None # all is good
555 def testSchedulerErrors(self):
556 master = self.buildmaster
557 master.loadChanges()
558 master.loadConfig(emptyCfg)
559 self.failUnlessEqual(master.allSchedulers(), [])
561 def _shouldBeFailure(res, hint=None):
562 self.shouldBeFailure(res, AssertionError, ValueError)
563 if hint:
564 self.failUnless(str(res).find(hint) != -1)
566 def _loadConfig(res, newcfg):
567 return self.buildmaster.loadConfig(newcfg)
568 d = defer.succeed(None)
570 # c['schedulers'] must be a list
571 badcfg = schedulersCfg + \
573 c['schedulers'] = Scheduler('full', None, 60, ['builder1'])
575 d.addCallback(_loadConfig, badcfg)
576 d.addBoth(_shouldBeFailure,
577 "c['schedulers'] must be a list of Scheduler instances")
579 # c['schedulers'] must be a list of IScheduler objects
580 badcfg = schedulersCfg + \
582 c['schedulers'] = ['oops', 'problem']
584 d.addCallback(_loadConfig, badcfg)
585 d.addBoth(_shouldBeFailure,
586 "c['schedulers'] must be a list of Scheduler instances")
588 # c['schedulers'] must point at real builders
589 badcfg = schedulersCfg + \
591 c['schedulers'] = [Scheduler('full', None, 60, ['builder-bogus'])]
593 d.addCallback(_loadConfig, badcfg)
594 d.addBoth(_shouldBeFailure, "uses unknown builder")
596 # builderNames= must be a list
597 badcfg = schedulersCfg + \
599 c['schedulers'] = [Scheduler('full', None, 60, 'builder1')]
601 d.addCallback(_loadConfig, badcfg)
602 d.addBoth(_shouldBeFailure,
603 "must be a list of Builder description names")
605 # builderNames= must be a list of strings, not dicts
606 badcfg = schedulersCfg + \
608 c['schedulers'] = [Scheduler('full', None, 60, [b1])]
610 d.addCallback(_loadConfig, badcfg)
611 d.addBoth(_shouldBeFailure,
612 "must be a list of Builder description names")
614 # builderNames= must be a list of strings, not a dict
615 badcfg = schedulersCfg + \
617 c['schedulers'] = [Scheduler('full', None, 60, b1)]
619 d.addCallback(_loadConfig, badcfg)
620 d.addBoth(_shouldBeFailure,
621 "must be a list of Builder description names")
623 # each Scheduler must have a unique name
624 badcfg = schedulersCfg + \
626 c['schedulers'] = [Scheduler('dup', None, 60, []),
627 Scheduler('dup', None, 60, [])]
629 d.addCallback(_loadConfig, badcfg)
630 d.addBoth(_shouldBeFailure, "Schedulers must have unique names")
632 return d
634 def testSchedulers(self):
635 master = self.buildmaster
636 master.loadChanges()
637 master.loadConfig(emptyCfg)
638 self.failUnlessEqual(master.allSchedulers(), [])
640 d = self.buildmaster.loadConfig(schedulersCfg)
641 d.addCallback(self._testSchedulers_1)
642 return d
644 def _testSchedulers_1(self, res):
645 sch = self.buildmaster.allSchedulers()
646 self.failUnlessEqual(len(sch), 1)
647 s = sch[0]
648 self.failUnless(isinstance(s, scheduler.Scheduler))
649 self.failUnlessEqual(s.name, "full")
650 self.failUnlessEqual(s.branch, None)
651 self.failUnlessEqual(s.treeStableTimer, 60)
652 self.failUnlessEqual(s.builderNames, ['builder1'])
654 newcfg = schedulersCfg + \
656 s1 = Scheduler('full', None, 60, ['builder1'])
657 c['schedulers'] = [s1, Dependent('downstream', s1, ['builder1'])]
659 d = self.buildmaster.loadConfig(newcfg)
660 d.addCallback(self._testSchedulers_2, newcfg)
661 return d
662 def _testSchedulers_2(self, res, newcfg):
663 sch = self.buildmaster.allSchedulers()
664 self.failUnlessEqual(len(sch), 2)
665 s = sch[0]
666 self.failUnless(isinstance(s, scheduler.Scheduler))
667 s = sch[1]
668 self.failUnless(isinstance(s, scheduler.Dependent))
669 self.failUnlessEqual(s.name, "downstream")
670 self.failUnlessEqual(s.builderNames, ['builder1'])
672 # reloading the same config file should leave the schedulers in place
673 d = self.buildmaster.loadConfig(newcfg)
674 d.addCallback(self._testSchedulers_3, sch)
675 return d
676 def _testSchedulers_3(self, res, sch1):
677 sch2 = self.buildmaster.allSchedulers()
678 self.failUnlessEqual(len(sch2), 2)
679 sch1.sort()
680 sch2.sort()
681 self.failUnlessEqual(sch1, sch2)
682 self.failUnlessIdentical(sch1[0], sch2[0])
683 self.failUnlessIdentical(sch1[1], sch2[1])
684 self.failUnlessIdentical(sch1[0].parent, self.buildmaster)
685 self.failUnlessIdentical(sch1[1].parent, self.buildmaster)
689 def testBuilders(self):
690 master = self.buildmaster
691 master.loadConfig(emptyCfg)
692 self.failUnlessEqual(master.botmaster.builders, {})
694 master.loadConfig(buildersCfg)
695 self.failUnlessEqual(master.botmaster.builderNames, ["builder1"])
696 self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"])
697 b = master.botmaster.builders["builder1"]
698 self.failUnless(isinstance(b, Builder))
699 self.failUnlessEqual(b.name, "builder1")
700 self.failUnlessEqual(b.slavenames, ["bot1"])
701 self.failUnlessEqual(b.builddir, "workdir")
702 f1 = b.buildFactory
703 self.failUnless(isinstance(f1, BasicBuildFactory))
704 steps = f1.steps
705 self.failUnlessEqual(len(steps), 3)
706 self.failUnlessEqual(steps[0], (CVS,
707 {'cvsroot': 'cvsroot',
708 'cvsmodule': 'cvsmodule',
709 'mode': 'clobber'}))
710 self.failUnlessEqual(steps[1], (Compile,
711 {'command': 'make all'}))
712 self.failUnlessEqual(steps[2], (Test,
713 {'command': 'make check'}))
716 # make sure a reload of the same data doesn't interrupt the Builder
717 master.loadConfig(buildersCfg)
718 self.failUnlessEqual(master.botmaster.builderNames, ["builder1"])
719 self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"])
720 b2 = master.botmaster.builders["builder1"]
721 self.failUnlessIdentical(b, b2)
722 # TODO: test that the BuilderStatus object doesn't change
723 #statusbag2 = master.client_svc.statusbags["builder1"]
724 #self.failUnlessIdentical(statusbag, statusbag2)
726 # but changing something should result in a new Builder
727 master.loadConfig(buildersCfg2)
728 self.failUnlessEqual(master.botmaster.builderNames, ["builder1"])
729 self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"])
730 b3 = master.botmaster.builders["builder1"]
731 self.failIf(b is b3)
732 # the statusbag remains the same TODO
733 #statusbag3 = master.client_svc.statusbags["builder1"]
734 #self.failUnlessIdentical(statusbag, statusbag3)
736 # adding new builder
737 master.loadConfig(buildersCfg3)
738 self.failUnlessEqual(master.botmaster.builderNames, ["builder1",
739 "builder2"])
740 self.failUnlessListsEquivalent(master.botmaster.builders.keys(),
741 ["builder1", "builder2"])
742 b4 = master.botmaster.builders["builder1"]
743 self.failUnlessIdentical(b3, b4)
745 # changing first builder should leave it at the same place in the list
746 master.loadConfig(buildersCfg4)
747 self.failUnlessEqual(master.botmaster.builderNames, ["builder1",
748 "builder2"])
749 self.failUnlessListsEquivalent(master.botmaster.builders.keys(),
750 ["builder1", "builder2"])
751 b5 = master.botmaster.builders["builder1"]
752 self.failIf(b4 is b5)
754 # and removing it should make the Builder go away
755 master.loadConfig(emptyCfg)
756 self.failUnlessEqual(master.botmaster.builderNames, [])
757 self.failUnlessEqual(master.botmaster.builders, {})
758 #self.failUnlessEqual(master.client_svc.statusbags, {}) # TODO
760 def testWithProperties(self):
761 master = self.buildmaster
762 master.loadConfig(wpCfg1)
763 self.failUnlessEqual(master.botmaster.builderNames, ["builder1"])
764 self.failUnlessEqual(master.botmaster.builders.keys(), ["builder1"])
765 b1 = master.botmaster.builders["builder1"]
767 # reloading the same config should leave the builder unchanged
768 master.loadConfig(wpCfg1)
769 b2 = master.botmaster.builders["builder1"]
770 self.failUnlessIdentical(b1, b2)
772 # but changing the parameters of the WithProperties should change it
773 master.loadConfig(wpCfg2)
774 b3 = master.botmaster.builders["builder1"]
775 self.failIf(b1 is b3)
777 # again, reloading same config should leave the builder unchanged
778 master.loadConfig(wpCfg2)
779 b4 = master.botmaster.builders["builder1"]
780 self.failUnlessIdentical(b3, b4)
782 def checkIRC(self, m, expected):
783 ircs = {}
784 for irc in self.servers(m, words.IRC):
785 ircs[irc.host] = (irc.nick, irc.channels)
786 self.failUnlessEqual(ircs, expected)
788 def testIRC(self):
789 if not words:
790 raise unittest.SkipTest("Twisted Words package is not installed")
791 master = self.buildmaster
792 master.loadChanges()
793 d = master.loadConfig(emptyCfg)
794 e1 = {}
795 d.addCallback(lambda res: self.checkIRC(master, e1))
796 d.addCallback(lambda res: master.loadConfig(ircCfg1))
797 e2 = {'irc.us.freenode.net': ('buildbot', ['twisted'])}
798 d.addCallback(lambda res: self.checkIRC(master, e2))
799 d.addCallback(lambda res: master.loadConfig(ircCfg2))
800 e3 = {'irc.us.freenode.net': ('buildbot', ['twisted']),
801 'irc.example.com': ('otherbot', ['chan1', 'chan2'])}
802 d.addCallback(lambda res: self.checkIRC(master, e3))
803 d.addCallback(lambda res: master.loadConfig(ircCfg3))
804 e4 = {'irc.us.freenode.net': ('buildbot', ['knotted'])}
805 d.addCallback(lambda res: self.checkIRC(master, e4))
806 d.addCallback(lambda res: master.loadConfig(ircCfg1))
807 e5 = {'irc.us.freenode.net': ('buildbot', ['twisted'])}
808 d.addCallback(lambda res: self.checkIRC(master, e5))
809 return d
811 def testWebPortnum(self):
812 master = self.buildmaster
813 master.loadChanges()
815 d = master.loadConfig(webCfg1)
816 d.addCallback(self._testWebPortnum_1)
817 return d
818 def _testWebPortnum_1(self, res):
819 ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory),
820 (9980, Site)])
821 p = ports[1]
823 d = self.buildmaster.loadConfig(webCfg1) # nothing should be changed
824 d.addCallback(self._testWebPortnum_2, p)
825 return d
826 def _testWebPortnum_2(self, res, p):
827 ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory),
828 (9980, Site)])
829 self.failUnlessIdentical(p, ports[1],
830 "web port was changed even though " + \
831 "configuration was not")
833 d = self.buildmaster.loadConfig(webCfg2) # changes to 9981
834 d.addCallback(self._testWebPortnum_3, p)
835 return d
836 def _testWebPortnum_3(self, res, p):
837 ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory),
838 (9981, Site)])
839 self.failIf(p is ports[1],
840 "configuration was changed but web port was unchanged")
841 d = self.buildmaster.loadConfig(webCfg3) # 9981 on only localhost
842 d.addCallback(self._testWebPortnum_4, ports[1])
843 return d
844 def _testWebPortnum_4(self, res, p):
845 ports = self.checkPorts(self.buildmaster, [(9999, pb.PBServerFactory),
846 (9981, Site)])
847 self.failUnlessEqual(ports[1].kwargs['interface'], "127.0.0.1")
848 d = self.buildmaster.loadConfig(emptyCfg)
849 d.addCallback(lambda res:
850 self.checkPorts(self.buildmaster,
851 [(9999, pb.PBServerFactory)]))
852 return d
854 def testWebPathname(self):
855 master = self.buildmaster
856 master.loadChanges()
858 d = master.loadConfig(webNameCfg1)
859 d.addCallback(self._testWebPathname_1)
860 return d
861 def _testWebPathname_1(self, res):
862 self.checkPorts(self.buildmaster,
863 [(9999, pb.PBServerFactory),
864 ('~/.twistd-web-pb', pb.PBServerFactory)])
865 unixports = self.UNIXports(self.buildmaster)
866 f = unixports[0].args[1]
867 self.failUnless(isinstance(f.root, ResourcePublisher))
869 d = self.buildmaster.loadConfig(webNameCfg1)
870 # nothing should be changed
871 d.addCallback(self._testWebPathname_2, f)
872 return d
873 def _testWebPathname_2(self, res, f):
874 self.checkPorts(self.buildmaster,
875 [(9999, pb.PBServerFactory),
876 ('~/.twistd-web-pb', pb.PBServerFactory)])
877 self.failUnlessIdentical(f,
878 self.UNIXports(self.buildmaster)[0].args[1],
879 "web factory was changed even though " + \
880 "configuration was not")
882 d = self.buildmaster.loadConfig(webNameCfg2)
883 d.addCallback(self._testWebPathname_3, f)
884 return d
885 def _testWebPathname_3(self, res, f):
886 self.checkPorts(self.buildmaster,
887 [(9999, pb.PBServerFactory),
888 ('./bar.socket', pb.PBServerFactory)])
889 self.failIf(f is self.UNIXports(self.buildmaster)[0].args[1],
890 "web factory was unchanged but configuration was changed")
892 d = self.buildmaster.loadConfig(emptyCfg)
893 d.addCallback(lambda res:
894 self.checkPorts(self.buildmaster,
895 [(9999, pb.PBServerFactory)]))
896 return d
898 def testDebugPassword(self):
899 master = self.buildmaster
901 master.loadConfig(debugPasswordCfg)
902 self.failUnlessEqual(master.checker.users,
903 {"change": "changepw",
904 "debug": "sekrit"})
906 master.loadConfig(debugPasswordCfg)
907 self.failUnlessEqual(master.checker.users,
908 {"change": "changepw",
909 "debug": "sekrit"})
911 master.loadConfig(emptyCfg)
912 self.failUnlessEqual(master.checker.users,
913 {"change": "changepw"})
915 def testLocks(self):
916 master = self.buildmaster
917 botmaster = master.botmaster
919 # make sure that c['interlocks'] is rejected properly
920 self.failUnlessRaises(KeyError, master.loadConfig, interlockCfgBad)
921 # and that duplicate-named Locks are caught
922 self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad1)
923 self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad2)
924 self.failUnlessRaises(ValueError, master.loadConfig, lockCfgBad3)
926 # create a Builder that uses Locks
927 master.loadConfig(lockCfg1a)
928 b1 = master.botmaster.builders["builder1"]
929 self.failUnlessEqual(len(b1.locks), 2)
931 # reloading the same config should not change the Builder
932 master.loadConfig(lockCfg1a)
933 self.failUnlessIdentical(b1, master.botmaster.builders["builder1"])
934 # but changing the set of locks used should change it
935 master.loadConfig(lockCfg1b)
936 self.failIfIdentical(b1, master.botmaster.builders["builder1"])
937 b1 = master.botmaster.builders["builder1"]
938 self.failUnlessEqual(len(b1.locks), 1)
940 # similar test with step-scoped locks
941 master.loadConfig(lockCfg2a)
942 b1 = master.botmaster.builders["builder1"]
943 # reloading the same config should not change the Builder
944 master.loadConfig(lockCfg2a)
945 self.failUnlessIdentical(b1, master.botmaster.builders["builder1"])
946 # but changing the set of locks used should change it
947 master.loadConfig(lockCfg2b)
948 self.failIfIdentical(b1, master.botmaster.builders["builder1"])
949 b1 = master.botmaster.builders["builder1"]
950 # remove the locks entirely
951 master.loadConfig(lockCfg2c)
952 self.failIfIdentical(b1, master.botmaster.builders["builder1"])
954 class ConfigElements(unittest.TestCase):
955 # verify that ComparableMixin is working
956 def testSchedulers(self):
957 s1 = scheduler.Scheduler(name='quick', branch=None,
958 treeStableTimer=30,
959 builderNames=['quick'])
960 s2 = scheduler.Scheduler(name="all", branch=None,
961 treeStableTimer=5*60,
962 builderNames=["a", "b"])
963 s3 = scheduler.Try_Userpass("try", ["a","b"], port=9989,
964 userpass=[("foo","bar")])
965 s1a = scheduler.Scheduler(name='quick', branch=None,
966 treeStableTimer=30,
967 builderNames=['quick'])
968 s2a = scheduler.Scheduler(name="all", branch=None,
969 treeStableTimer=5*60,
970 builderNames=["a", "b"])
971 s3a = scheduler.Try_Userpass("try", ["a","b"], port=9989,
972 userpass=[("foo","bar")])
973 self.failUnless(s1 == s1)
974 self.failUnless(s1 == s1a)
975 self.failUnless(s1a in [s1, s2, s3])
976 self.failUnless(s2a in [s1, s2, s3])
977 self.failUnless(s3a in [s1, s2, s3])
981 class ConfigFileTest(unittest.TestCase):
983 def testFindConfigFile(self):
984 os.mkdir("test_cf")
985 open(os.path.join("test_cf", "master.cfg"), "w").write(emptyCfg)
986 slaveportCfg = emptyCfg + "c['slavePortnum'] = 9000\n"
987 open(os.path.join("test_cf", "alternate.cfg"), "w").write(slaveportCfg)
989 m = BuildMaster("test_cf")
990 m.loadTheConfigFile()
991 self.failUnlessEqual(m.slavePortnum, "tcp:9999")
993 m = BuildMaster("test_cf", "alternate.cfg")
994 m.loadTheConfigFile()
995 self.failUnlessEqual(m.slavePortnum, "tcp:9000")
998 class MyTarget(base.StatusReceiverMultiService):
999 def __init__(self, name):
1000 self.name = name
1001 base.StatusReceiverMultiService.__init__(self)
1002 def startService(self):
1003 # make a note in a list stashed in the BuildMaster
1004 self.parent.targetevents.append(("start", self.name))
1005 return base.StatusReceiverMultiService.startService(self)
1006 def stopService(self):
1007 self.parent.targetevents.append(("stop", self.name))
1008 return base.StatusReceiverMultiService.stopService(self)
1010 class MySlowTarget(MyTarget):
1011 def stopService(self):
1012 from twisted.internet import reactor
1013 d = base.StatusReceiverMultiService.stopService(self)
1014 def stall(res):
1015 d2 = defer.Deferred()
1016 reactor.callLater(0.1, d2.callback, res)
1017 return d2
1018 d.addCallback(stall)
1019 m = self.parent
1020 def finishedStalling(res):
1021 m.targetevents.append(("stop", self.name))
1022 return res
1023 d.addCallback(finishedStalling)
1024 return d
1026 # we can't actually startService a buildmaster with a config that uses a
1027 # fixed slavePortnum like 9999, so instead this makes it possible to pass '0'
1028 # for the first time, and then substitute back in the allocated port number
1029 # on subsequent passes.
1030 startableEmptyCfg = emptyCfg + \
1032 c['slavePortnum'] = %d
1035 targetCfg1 = startableEmptyCfg + \
1037 from buildbot.test.test_config import MyTarget
1038 c['status'] = [MyTarget('a')]
1041 targetCfg2 = startableEmptyCfg + \
1043 from buildbot.test.test_config import MySlowTarget
1044 c['status'] = [MySlowTarget('b')]
1047 class StartService(unittest.TestCase):
1048 def tearDown(self):
1049 return self.master.stopService()
1051 def testStartService(self):
1052 os.mkdir("test_ss")
1053 self.master = m = BuildMaster("test_ss")
1054 # inhibit the usual read-config-on-startup behavior
1055 m.readConfig = True
1056 m.startService()
1057 d = m.loadConfig(startableEmptyCfg % 0)
1058 d.addCallback(self._testStartService_0)
1059 return d
1061 def _testStartService_0(self, res):
1062 m = self.master
1063 m.targetevents = []
1064 # figure out what port got allocated
1065 self.portnum = m.slavePort._port.getHost().port
1066 d = m.loadConfig(targetCfg1 % self.portnum)
1067 d.addCallback(self._testStartService_1)
1068 return d
1070 def _testStartService_1(self, res):
1071 self.failUnlessEqual(len(self.master.statusTargets), 1)
1072 self.failUnless(isinstance(self.master.statusTargets[0], MyTarget))
1073 self.failUnlessEqual(self.master.targetevents,
1074 [('start', 'a')])
1075 self.master.targetevents = []
1076 # reloading the same config should not start or stop the target
1077 d = self.master.loadConfig(targetCfg1 % self.portnum)
1078 d.addCallback(self._testStartService_2)
1079 return d
1081 def _testStartService_2(self, res):
1082 self.failUnlessEqual(self.master.targetevents, [])
1083 # but loading a new config file should stop the old one, then
1084 # start the new one
1085 d = self.master.loadConfig(targetCfg2 % self.portnum)
1086 d.addCallback(self._testStartService_3)
1087 return d
1089 def _testStartService_3(self, res):
1090 self.failUnlessEqual(self.master.targetevents,
1091 [('stop', 'a'), ('start', 'b')])
1092 self.master.targetevents = []
1093 # and going back to the old one should do the same, in the same
1094 # order, even though the current MySlowTarget takes a moment to shut
1095 # down
1096 d = self.master.loadConfig(targetCfg1 % self.portnum)
1097 d.addCallback(self._testStartService_4)
1098 return d
1100 def _testStartService_4(self, res):
1101 self.failUnlessEqual(self.master.targetevents,
1102 [('stop', 'b'), ('start', 'a')])