1 # -*- test-case-name: buildbot.test.test_config -*-
5 from twisted
.trial
import unittest
6 from twisted
.python
import failure
7 from twisted
.internet
import defer
9 from buildbot
.master
import BuildMaster
10 from buildbot
import scheduler
11 from twisted
.application
import service
, internet
12 from twisted
.spread
import pb
13 from twisted
.web
.server
import Site
14 from twisted
.web
.distrib
import ResourcePublisher
15 from buildbot
.process
.builder
import Builder
16 from buildbot
.process
.factory
import BasicBuildFactory
17 from buildbot
.changes
.pb
import PBChangeSource
18 from buildbot
.changes
.mail
import SyncmailMaildirSource
19 from buildbot
.steps
.source
import CVS
, Darcs
20 from buildbot
.steps
.shell
import Compile
, Test
, ShellCommand
21 from buildbot
.status
import base
22 from buildbot
.steps
import dummy
, maxq
, python
, python_twisted
, shell
, \
26 from buildbot
.status
import words
32 from buildbot.buildslave import BuildSlave
33 BuildmasterConfig = c = {}
37 c['slavePortnum'] = 9999
38 c['projectName'] = 'dummy project'
39 c['projectURL'] = 'http://dummy.example.com'
40 c['buildbotURL'] = 'http://dummy.example.com/buildbot'
45 from buildbot.process.factory import BasicBuildFactory
46 from buildbot.buildslave import BuildSlave
47 BuildmasterConfig = c = {}
48 c['slaves'] = [BuildSlave('bot1', 'pw1')]
50 c['slavePortnum'] = 9999
51 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
52 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
53 'builddir':'workdir', 'factory':f1}]
56 buildersCfg2
= buildersCfg
+ \
58 f1 = BasicBuildFactory('cvsroot', 'cvsmodule2')
59 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
60 'builddir':'workdir', 'factory':f1}]
63 buildersCfg3
= buildersCfg2
+ \
65 c['builders'].append({'name': 'builder2', 'slavename': 'bot1',
66 'builddir': 'workdir2', 'factory': f1 })
69 buildersCfg4
= buildersCfg2
+ \
71 c['builders'] = [{ 'name': 'builder1', 'slavename': 'bot1',
72 'builddir': 'newworkdir', 'factory': f1 },
73 { 'name': 'builder2', 'slavename': 'bot1',
74 'builddir': 'workdir2', 'factory': f1 }]
77 wpCfg1
= buildersCfg
+ \
79 from buildbot.steps import shell
80 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
81 f1.addStep(shell.ShellCommand, command=[shell.WithProperties('echo')])
82 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
83 'builddir':'workdir1', 'factory': f1}]
86 wpCfg2
= buildersCfg
+ \
88 from buildbot.steps import shell
89 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
90 f1.addStep(shell.ShellCommand,
91 command=[shell.WithProperties('echo %s', 'revision')])
92 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
93 'builddir':'workdir1', 'factory': f1}]
98 ircCfg1
= emptyCfg
+ \
100 from buildbot.status import words
101 c['status'] = [words.IRC('irc.us.freenode.net', 'buildbot', ['twisted'])]
104 ircCfg2
= emptyCfg
+ \
106 from buildbot.status import words
107 c['status'] = [words.IRC('irc.us.freenode.net', 'buildbot', ['twisted']),
108 words.IRC('irc.example.com', 'otherbot', ['chan1', 'chan2'])]
111 ircCfg3
= emptyCfg
+ \
113 from buildbot.status import words
114 c['status'] = [words.IRC('irc.us.freenode.net', 'buildbot', ['knotted'])]
117 webCfg1
= emptyCfg
+ \
119 from buildbot.status import html
120 c['status'] = [html.Waterfall(http_port=9980)]
123 webCfg2
= emptyCfg
+ \
125 from buildbot.status import html
126 c['status'] = [html.Waterfall(http_port=9981)]
129 webCfg3
= emptyCfg
+ \
131 from buildbot.status import html
132 c['status'] = [html.Waterfall(http_port='tcp:9981:interface=127.0.0.1')]
135 webNameCfg1
= emptyCfg
+ \
137 from buildbot.status import html
138 c['status'] = [html.Waterfall(distrib_port='~/.twistd-web-pb')]
141 webNameCfg2
= emptyCfg
+ \
143 from buildbot.status import html
144 c['status'] = [html.Waterfall(distrib_port='./bar.socket')]
147 debugPasswordCfg
= emptyCfg
+ \
149 c['debugPassword'] = 'sekrit'
154 from buildbot.process.factory import BasicBuildFactory
155 from buildbot.buildslave import BuildSlave
157 c['slaves'] = [BuildSlave('bot1', 'pw1')]
159 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
161 { 'name': 'builder1', 'slavename': 'bot1',
162 'builddir': 'workdir', 'factory': f1 },
163 { 'name': 'builder2', 'slavename': 'bot1',
164 'builddir': 'workdir2', 'factory': f1 },
166 # interlocks have been removed
167 c['interlocks'] = [('lock1', ['builder1'], ['builder2', 'builder3']),
169 c['slavePortnum'] = 9999
170 BuildmasterConfig = c
175 from buildbot.steps.dummy import Dummy
176 from buildbot.process.factory import BuildFactory, s
177 from buildbot.locks import MasterLock
178 from buildbot.buildslave import BuildSlave
180 c['slaves'] = [BuildSlave('bot1', 'pw1')]
182 l1 = MasterLock('lock1')
183 l2 = MasterLock('lock1') # duplicate lock name
184 f1 = BuildFactory([s(Dummy, locks=[])])
186 { 'name': 'builder1', 'slavename': 'bot1',
187 'builddir': 'workdir', 'factory': f1, 'locks': [l1, l2] },
188 { 'name': 'builder2', 'slavename': 'bot1',
189 'builddir': 'workdir2', 'factory': f1 },
191 c['slavePortnum'] = 9999
192 BuildmasterConfig = c
197 from buildbot.steps.dummy import Dummy
198 from buildbot.process.factory import BuildFactory, s
199 from buildbot.locks import MasterLock, SlaveLock
200 from buildbot.buildslave import BuildSlave
202 c['slaves'] = [BuildSlave('bot1', 'pw1')]
204 l1 = MasterLock('lock1')
205 l2 = SlaveLock('lock1') # duplicate lock name
206 f1 = BuildFactory([s(Dummy, locks=[])])
208 { 'name': 'builder1', 'slavename': 'bot1',
209 'builddir': 'workdir', 'factory': f1, 'locks': [l1, l2] },
210 { 'name': 'builder2', 'slavename': 'bot1',
211 'builddir': 'workdir2', 'factory': f1 },
213 c['slavePortnum'] = 9999
214 BuildmasterConfig = c
219 from buildbot.steps.dummy import Dummy
220 from buildbot.process.factory import BuildFactory, s
221 from buildbot.locks import MasterLock
222 from buildbot.buildslave import BuildSlave
224 c['slaves'] = [BuildSlave('bot1', 'pw1')]
226 l1 = MasterLock('lock1')
227 l2 = MasterLock('lock1') # duplicate lock name
228 f1 = BuildFactory([s(Dummy, locks=[l2])])
229 f2 = BuildFactory([s(Dummy)])
231 { 'name': 'builder1', 'slavename': 'bot1',
232 'builddir': 'workdir', 'factory': f2, 'locks': [l1] },
233 { 'name': 'builder2', 'slavename': 'bot1',
234 'builddir': 'workdir2', 'factory': f1 },
236 c['slavePortnum'] = 9999
237 BuildmasterConfig = c
242 from buildbot.process.factory import BasicBuildFactory
243 from buildbot.locks import MasterLock
244 from buildbot.buildslave import BuildSlave
246 c['slaves'] = [BuildSlave('bot1', 'pw1')]
248 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
249 l1 = MasterLock('lock1')
250 l2 = MasterLock('lock2')
252 { 'name': 'builder1', 'slavename': 'bot1',
253 'builddir': 'workdir', 'factory': f1, 'locks': [l1, l2] },
254 { 'name': 'builder2', 'slavename': 'bot1',
255 'builddir': 'workdir2', 'factory': f1 },
257 c['slavePortnum'] = 9999
258 BuildmasterConfig = c
263 from buildbot.process.factory import BasicBuildFactory
264 from buildbot.locks import MasterLock
265 from buildbot.buildslave import BuildSlave
267 c['slaves'] = [BuildSlave('bot1', 'pw1')]
269 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
270 l1 = MasterLock('lock1')
271 l2 = MasterLock('lock2')
273 { 'name': 'builder1', 'slavename': 'bot1',
274 'builddir': 'workdir', 'factory': f1, 'locks': [l1] },
275 { 'name': 'builder2', 'slavename': 'bot1',
276 'builddir': 'workdir2', 'factory': f1 },
278 c['slavePortnum'] = 9999
279 BuildmasterConfig = c
282 # test out step Locks
285 from buildbot.steps.dummy import Dummy
286 from buildbot.process.factory import BuildFactory, s
287 from buildbot.locks import MasterLock
288 from buildbot.buildslave import BuildSlave
290 c['slaves'] = [BuildSlave('bot1', 'pw1')]
292 l1 = MasterLock('lock1')
293 l2 = MasterLock('lock2')
294 f1 = BuildFactory([s(Dummy, locks=[l1,l2])])
295 f2 = BuildFactory([s(Dummy)])
298 { 'name': 'builder1', 'slavename': 'bot1',
299 'builddir': 'workdir', 'factory': f1 },
300 { 'name': 'builder2', 'slavename': 'bot1',
301 'builddir': 'workdir2', 'factory': f2 },
303 c['slavePortnum'] = 9999
304 BuildmasterConfig = c
309 from buildbot.steps.dummy import Dummy
310 from buildbot.process.factory import BuildFactory, s
311 from buildbot.locks import MasterLock
312 from buildbot.buildslave import BuildSlave
314 c['slaves'] = [BuildSlave('bot1', 'pw1')]
316 l1 = MasterLock('lock1')
317 l2 = MasterLock('lock2')
318 f1 = BuildFactory([s(Dummy, locks=[l1])])
319 f2 = BuildFactory([s(Dummy)])
322 { 'name': 'builder1', 'slavename': 'bot1',
323 'builddir': 'workdir', 'factory': f1 },
324 { 'name': 'builder2', 'slavename': 'bot1',
325 'builddir': 'workdir2', 'factory': f2 },
327 c['slavePortnum'] = 9999
328 BuildmasterConfig = c
333 from buildbot.steps.dummy import Dummy
334 from buildbot.process.factory import BuildFactory, s
335 from buildbot.locks import MasterLock
336 from buildbot.buildslave import BuildSlave
338 c['slaves'] = [BuildSlave('bot1', 'pw1')]
340 l1 = MasterLock('lock1')
341 l2 = MasterLock('lock2')
342 f1 = BuildFactory([s(Dummy)])
343 f2 = BuildFactory([s(Dummy)])
346 { 'name': 'builder1', 'slavename': 'bot1',
347 'builddir': 'workdir', 'factory': f1 },
348 { 'name': 'builder2', 'slavename': 'bot1',
349 'builddir': 'workdir2', 'factory': f2 },
351 c['slavePortnum'] = 9999
352 BuildmasterConfig = c
357 from buildbot.scheduler import Scheduler, Dependent
358 from buildbot.process.factory import BasicBuildFactory
359 from buildbot.buildslave import BuildSlave
361 c['slaves'] = [BuildSlave('bot1', 'pw1')]
362 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
363 b1 = {'name':'builder1', 'slavename':'bot1',
364 'builddir':'workdir', 'factory':f1}
366 c['schedulers'] = [Scheduler('full', None, 60, ['builder1'])]
367 c['slavePortnum'] = 9999
368 c['projectName'] = 'dummy project'
369 c['projectURL'] = 'http://dummy.example.com'
370 c['buildbotURL'] = 'http://dummy.example.com/buildbot'
371 BuildmasterConfig = c
374 class ConfigTest(unittest
.TestCase
):
376 self
.buildmaster
= BuildMaster(".")
378 def failUnlessListsEquivalent(self
, list1
, list2
):
383 self
.failUnlessEqual(l1
, l2
)
385 def servers(self
, s
, types
):
386 # perform a recursive search of s.services, looking for instances of
387 # twisted.application.internet.TCPServer, then extract their .args
388 # values to find the TCP ports they want to listen on
390 if service
.IServiceCollection
.providedBy(child
):
391 for gc
in self
.servers(child
, types
):
393 if isinstance(child
, types
):
396 def TCPports(self
, s
):
397 return list(self
.servers(s
, internet
.TCPServer
))
398 def UNIXports(self
, s
):
399 return list(self
.servers(s
, internet
.UNIXServer
))
400 def TCPclients(self
, s
):
401 return list(self
.servers(s
, internet
.TCPClient
))
403 def checkPorts(self
, svc
, expected
):
404 """Verify that the TCPServer and UNIXServer children of the given
405 service have the expected portnum/pathname and factory classes. As a
406 side-effect, return a list of servers in the same order as the
407 'expected' list. This can be used to verify properties of the
408 factories contained therein."""
410 expTCP
= [e
for e
in expected
if type(e
[0]) == int]
411 expUNIX
= [e
for e
in expected
if type(e
[0]) == str]
412 haveTCP
= [(p
.args
[0], p
.args
[1].__class
__)
413 for p
in self
.TCPports(svc
)]
414 haveUNIX
= [(p
.args
[0], p
.args
[1].__class
__)
415 for p
in self
.UNIXports(svc
)]
416 self
.failUnlessListsEquivalent(expTCP
, haveTCP
)
417 self
.failUnlessListsEquivalent(expUNIX
, haveUNIX
)
420 for have
in self
.TCPports(svc
) + self
.UNIXports(svc
):
421 if have
.args
[0] == e
[0]:
424 assert(len(ret
) == len(expected
))
428 self
.failUnlessRaises(KeyError, self
.buildmaster
.loadConfig
, "")
430 def testSimple(self
):
431 # covers slavePortnum, base checker passwords
432 master
= self
.buildmaster
435 master
.loadConfig(emptyCfg
)
436 # note: this doesn't actually start listening, because the app
437 # hasn't been started running
438 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9999")
439 self
.checkPorts(master
, [(9999, pb
.PBServerFactory
)])
440 self
.failUnlessEqual(list(master
.change_svc
), [])
441 self
.failUnlessEqual(master
.botmaster
.builders
, {})
442 self
.failUnlessEqual(master
.checker
.users
,
443 {"change": "changepw"})
444 self
.failUnlessEqual(master
.projectName
, "dummy project")
445 self
.failUnlessEqual(master
.projectURL
, "http://dummy.example.com")
446 self
.failUnlessEqual(master
.buildbotURL
,
447 "http://dummy.example.com/buildbot")
449 def testSlavePortnum(self
):
450 master
= self
.buildmaster
453 master
.loadConfig(emptyCfg
)
454 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9999")
455 ports
= self
.checkPorts(master
, [(9999, pb
.PBServerFactory
)])
458 master
.loadConfig(emptyCfg
)
459 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9999")
460 ports
= self
.checkPorts(master
, [(9999, pb
.PBServerFactory
)])
461 self
.failUnlessIdentical(p
, ports
[0],
462 "the slave port was changed even " + \
463 "though the configuration was not")
465 master
.loadConfig(emptyCfg
+ "c['slavePortnum'] = 9000\n")
466 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9000")
467 ports
= self
.checkPorts(master
, [(9000, pb
.PBServerFactory
)])
468 self
.failIf(p
is ports
[0],
469 "slave port was unchanged but configuration was changed")
471 def testSlaves(self
):
472 master
= self
.buildmaster
474 master
.loadConfig(emptyCfg
)
475 self
.failUnlessEqual(master
.botmaster
.builders
, {})
476 self
.failUnlessEqual(master
.checker
.users
,
477 {"change": "changepw"})
478 # 'botsCfg' is testing backwards compatibility, for 0.7.5 config
479 # files that have not yet been updated to 0.7.6 . This compatibility
480 # (and this test) is scheduled for removal in 0.8.0 .
481 botsCfg
= (emptyCfg
+
482 "c['bots'] = [('bot1', 'pw1'), ('bot2', 'pw2')]\n")
483 master
.loadConfig(botsCfg
)
484 self
.failUnlessEqual(master
.checker
.users
,
485 {"change": "changepw",
488 master
.loadConfig(botsCfg
)
489 self
.failUnlessEqual(master
.checker
.users
,
490 {"change": "changepw",
493 master
.loadConfig(emptyCfg
)
494 self
.failUnlessEqual(master
.checker
.users
,
495 {"change": "changepw"})
496 slavesCfg
= (emptyCfg
+
497 "from buildbot.buildslave import BuildSlave\n"
498 "c['slaves'] = [BuildSlave('bot1','pw1'), "
499 "BuildSlave('bot2','pw2')]\n")
500 master
.loadConfig(slavesCfg
)
501 self
.failUnlessEqual(master
.checker
.users
,
502 {"change": "changepw",
507 def testChangeSource(self
):
508 master
= self
.buildmaster
510 master
.loadConfig(emptyCfg
)
511 self
.failUnlessEqual(list(master
.change_svc
), [])
513 sourcesCfg
= emptyCfg
+ \
515 from buildbot.changes.pb import PBChangeSource
516 c['change_source'] = PBChangeSource()
519 d
= master
.loadConfig(sourcesCfg
)
521 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 1)
522 s1
= list(self
.buildmaster
.change_svc
)[0]
523 self
.failUnless(isinstance(s1
, PBChangeSource
))
524 self
.failUnlessEqual(s1
, list(self
.buildmaster
.change_svc
)[0])
525 self
.failUnless(s1
.parent
)
527 # verify that unchanged sources are not interrupted
528 d1
= self
.buildmaster
.loadConfig(sourcesCfg
)
531 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 1)
532 s2
= list(self
.buildmaster
.change_svc
)[0]
533 self
.failUnlessIdentical(s1
, s2
)
534 self
.failUnless(s1
.parent
)
535 d1
.addCallback(_check2
)
537 d
.addCallback(_check1
)
539 # make sure we can get rid of the sources too
540 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(emptyCfg
))
543 self
.failUnlessEqual(list(self
.buildmaster
.change_svc
), [])
544 d
.addCallback(_check3
)
548 def testChangeSources(self
):
549 # make sure we can accept a list
550 master
= self
.buildmaster
552 master
.loadConfig(emptyCfg
)
553 self
.failUnlessEqual(list(master
.change_svc
), [])
555 sourcesCfg
= emptyCfg
+ \
557 from buildbot.changes.pb import PBChangeSource
558 from buildbot.changes.mail import SyncmailMaildirSource
559 c['change_source'] = [PBChangeSource(),
560 SyncmailMaildirSource('.'),
564 d
= master
.loadConfig(sourcesCfg
)
566 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 2)
567 s1
,s2
= list(self
.buildmaster
.change_svc
)
568 if isinstance(s2
, PBChangeSource
):
570 self
.failUnless(isinstance(s1
, PBChangeSource
))
571 self
.failUnless(s1
.parent
)
572 self
.failUnless(isinstance(s2
, SyncmailMaildirSource
))
573 self
.failUnless(s2
.parent
)
574 d
.addCallback(_check1
)
577 def testSources(self
):
578 # test backwards compatibility. c['sources'] is deprecated.
579 master
= self
.buildmaster
581 master
.loadConfig(emptyCfg
)
582 self
.failUnlessEqual(list(master
.change_svc
), [])
584 sourcesCfg
= emptyCfg
+ \
586 from buildbot.changes.pb import PBChangeSource
587 c['sources'] = [PBChangeSource()]
590 d
= master
.loadConfig(sourcesCfg
)
592 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 1)
593 s1
= list(self
.buildmaster
.change_svc
)[0]
594 self
.failUnless(isinstance(s1
, PBChangeSource
))
595 self
.failUnless(s1
.parent
)
596 d
.addCallback(_check1
)
599 def shouldBeFailure(self
, res
, *expected
):
600 self
.failUnless(isinstance(res
, failure
.Failure
),
601 "we expected this to fail, not produce %s" % (res
,))
603 return None # all is good
605 def testSchedulerErrors(self
):
606 master
= self
.buildmaster
608 master
.loadConfig(emptyCfg
)
609 self
.failUnlessEqual(master
.allSchedulers(), [])
611 def _shouldBeFailure(res
, hint
=None):
612 self
.shouldBeFailure(res
, AssertionError, ValueError)
614 self
.failUnless(str(res
).find(hint
) != -1)
616 def _loadConfig(res
, newcfg
):
617 return self
.buildmaster
.loadConfig(newcfg
)
618 d
= defer
.succeed(None)
620 # c['schedulers'] must be a list
621 badcfg
= schedulersCfg
+ \
623 c['schedulers'] = Scheduler('full', None, 60, ['builder1'])
625 d
.addCallback(_loadConfig
, badcfg
)
626 d
.addBoth(_shouldBeFailure
,
627 "c['schedulers'] must be a list of Scheduler instances")
629 # c['schedulers'] must be a list of IScheduler objects
630 badcfg
= schedulersCfg
+ \
632 c['schedulers'] = ['oops', 'problem']
634 d
.addCallback(_loadConfig
, badcfg
)
635 d
.addBoth(_shouldBeFailure
,
636 "c['schedulers'] must be a list of Scheduler instances")
638 # c['schedulers'] must point at real builders
639 badcfg
= schedulersCfg
+ \
641 c['schedulers'] = [Scheduler('full', None, 60, ['builder-bogus'])]
643 d
.addCallback(_loadConfig
, badcfg
)
644 d
.addBoth(_shouldBeFailure
, "uses unknown builder")
646 # builderNames= must be a list
647 badcfg
= schedulersCfg
+ \
649 c['schedulers'] = [Scheduler('full', None, 60, 'builder1')]
651 d
.addCallback(_loadConfig
, badcfg
)
652 d
.addBoth(_shouldBeFailure
,
653 "must be a list of Builder description names")
655 # builderNames= must be a list of strings, not dicts
656 badcfg
= schedulersCfg
+ \
658 c['schedulers'] = [Scheduler('full', None, 60, [b1])]
660 d
.addCallback(_loadConfig
, badcfg
)
661 d
.addBoth(_shouldBeFailure
,
662 "must be a list of Builder description names")
664 # builderNames= must be a list of strings, not a dict
665 badcfg
= schedulersCfg
+ \
667 c['schedulers'] = [Scheduler('full', None, 60, b1)]
669 d
.addCallback(_loadConfig
, badcfg
)
670 d
.addBoth(_shouldBeFailure
,
671 "must be a list of Builder description names")
673 # each Scheduler must have a unique name
674 badcfg
= schedulersCfg
+ \
676 c['schedulers'] = [Scheduler('dup', None, 60, []),
677 Scheduler('dup', None, 60, [])]
679 d
.addCallback(_loadConfig
, badcfg
)
680 d
.addBoth(_shouldBeFailure
, "Schedulers must have unique names")
684 def testSchedulers(self
):
685 master
= self
.buildmaster
687 master
.loadConfig(emptyCfg
)
688 self
.failUnlessEqual(master
.allSchedulers(), [])
690 d
= self
.buildmaster
.loadConfig(schedulersCfg
)
691 d
.addCallback(self
._testSchedulers
_1)
694 def _testSchedulers_1(self
, res
):
695 sch
= self
.buildmaster
.allSchedulers()
696 self
.failUnlessEqual(len(sch
), 1)
698 self
.failUnless(isinstance(s
, scheduler
.Scheduler
))
699 self
.failUnlessEqual(s
.name
, "full")
700 self
.failUnlessEqual(s
.branch
, None)
701 self
.failUnlessEqual(s
.treeStableTimer
, 60)
702 self
.failUnlessEqual(s
.builderNames
, ['builder1'])
704 newcfg
= schedulersCfg
+ \
706 s1 = Scheduler('full', None, 60, ['builder1'])
707 c['schedulers'] = [s1, Dependent('downstream', s1, ['builder1'])]
709 d
= self
.buildmaster
.loadConfig(newcfg
)
710 d
.addCallback(self
._testSchedulers
_2, newcfg
)
712 def _testSchedulers_2(self
, res
, newcfg
):
713 sch
= self
.buildmaster
.allSchedulers()
714 self
.failUnlessEqual(len(sch
), 2)
716 self
.failUnless(isinstance(s
, scheduler
.Scheduler
))
718 self
.failUnless(isinstance(s
, scheduler
.Dependent
))
719 self
.failUnlessEqual(s
.name
, "downstream")
720 self
.failUnlessEqual(s
.builderNames
, ['builder1'])
722 # reloading the same config file should leave the schedulers in place
723 d
= self
.buildmaster
.loadConfig(newcfg
)
724 d
.addCallback(self
._testSchedulers
_3, sch
)
726 def _testSchedulers_3(self
, res
, sch1
):
727 sch2
= self
.buildmaster
.allSchedulers()
728 self
.failUnlessEqual(len(sch2
), 2)
731 self
.failUnlessEqual(sch1
, sch2
)
732 self
.failUnlessIdentical(sch1
[0], sch2
[0])
733 self
.failUnlessIdentical(sch1
[1], sch2
[1])
734 self
.failUnlessIdentical(sch1
[0].parent
, self
.buildmaster
)
735 self
.failUnlessIdentical(sch1
[1].parent
, self
.buildmaster
)
739 def testBuilders(self
):
740 master
= self
.buildmaster
741 master
.loadConfig(emptyCfg
)
742 self
.failUnlessEqual(master
.botmaster
.builders
, {})
744 master
.loadConfig(buildersCfg
)
745 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
746 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
747 b
= master
.botmaster
.builders
["builder1"]
748 self
.failUnless(isinstance(b
, Builder
))
749 self
.failUnlessEqual(b
.name
, "builder1")
750 self
.failUnlessEqual(b
.slavenames
, ["bot1"])
751 self
.failUnlessEqual(b
.builddir
, "workdir")
753 self
.failUnless(isinstance(f1
, BasicBuildFactory
))
755 self
.failUnlessEqual(len(steps
), 3)
756 self
.failUnlessEqual(steps
[0], (CVS
,
757 {'cvsroot': 'cvsroot',
758 'cvsmodule': 'cvsmodule',
760 self
.failUnlessEqual(steps
[1], (Compile
,
761 {'command': 'make all'}))
762 self
.failUnlessEqual(steps
[2], (Test
,
763 {'command': 'make check'}))
766 # make sure a reload of the same data doesn't interrupt the Builder
767 master
.loadConfig(buildersCfg
)
768 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
769 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
770 b2
= master
.botmaster
.builders
["builder1"]
771 self
.failUnlessIdentical(b
, b2
)
772 # TODO: test that the BuilderStatus object doesn't change
773 #statusbag2 = master.client_svc.statusbags["builder1"]
774 #self.failUnlessIdentical(statusbag, statusbag2)
776 # but changing something should result in a new Builder
777 master
.loadConfig(buildersCfg2
)
778 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
779 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
780 b3
= master
.botmaster
.builders
["builder1"]
782 # the statusbag remains the same TODO
783 #statusbag3 = master.client_svc.statusbags["builder1"]
784 #self.failUnlessIdentical(statusbag, statusbag3)
787 master
.loadConfig(buildersCfg3
)
788 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1",
790 self
.failUnlessListsEquivalent(master
.botmaster
.builders
.keys(),
791 ["builder1", "builder2"])
792 b4
= master
.botmaster
.builders
["builder1"]
793 self
.failUnlessIdentical(b3
, b4
)
795 # changing first builder should leave it at the same place in the list
796 master
.loadConfig(buildersCfg4
)
797 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1",
799 self
.failUnlessListsEquivalent(master
.botmaster
.builders
.keys(),
800 ["builder1", "builder2"])
801 b5
= master
.botmaster
.builders
["builder1"]
802 self
.failIf(b4
is b5
)
804 # and removing it should make the Builder go away
805 master
.loadConfig(emptyCfg
)
806 self
.failUnlessEqual(master
.botmaster
.builderNames
, [])
807 self
.failUnlessEqual(master
.botmaster
.builders
, {})
808 #self.failUnlessEqual(master.client_svc.statusbags, {}) # TODO
810 def testWithProperties(self
):
811 master
= self
.buildmaster
812 master
.loadConfig(wpCfg1
)
813 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
814 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
815 b1
= master
.botmaster
.builders
["builder1"]
817 # reloading the same config should leave the builder unchanged
818 master
.loadConfig(wpCfg1
)
819 b2
= master
.botmaster
.builders
["builder1"]
820 self
.failUnlessIdentical(b1
, b2
)
822 # but changing the parameters of the WithProperties should change it
823 master
.loadConfig(wpCfg2
)
824 b3
= master
.botmaster
.builders
["builder1"]
825 self
.failIf(b1
is b3
)
827 # again, reloading same config should leave the builder unchanged
828 master
.loadConfig(wpCfg2
)
829 b4
= master
.botmaster
.builders
["builder1"]
830 self
.failUnlessIdentical(b3
, b4
)
832 def checkIRC(self
, m
, expected
):
834 for irc
in self
.servers(m
, words
.IRC
):
835 ircs
[irc
.host
] = (irc
.nick
, irc
.channels
)
836 self
.failUnlessEqual(ircs
, expected
)
840 raise unittest
.SkipTest("Twisted Words package is not installed")
841 master
= self
.buildmaster
843 d
= master
.loadConfig(emptyCfg
)
845 d
.addCallback(lambda res
: self
.checkIRC(master
, e1
))
846 d
.addCallback(lambda res
: master
.loadConfig(ircCfg1
))
847 e2
= {'irc.us.freenode.net': ('buildbot', ['twisted'])}
848 d
.addCallback(lambda res
: self
.checkIRC(master
, e2
))
849 d
.addCallback(lambda res
: master
.loadConfig(ircCfg2
))
850 e3
= {'irc.us.freenode.net': ('buildbot', ['twisted']),
851 'irc.example.com': ('otherbot', ['chan1', 'chan2'])}
852 d
.addCallback(lambda res
: self
.checkIRC(master
, e3
))
853 d
.addCallback(lambda res
: master
.loadConfig(ircCfg3
))
854 e4
= {'irc.us.freenode.net': ('buildbot', ['knotted'])}
855 d
.addCallback(lambda res
: self
.checkIRC(master
, e4
))
856 d
.addCallback(lambda res
: master
.loadConfig(ircCfg1
))
857 e5
= {'irc.us.freenode.net': ('buildbot', ['twisted'])}
858 d
.addCallback(lambda res
: self
.checkIRC(master
, e5
))
861 def testWebPortnum(self
):
862 master
= self
.buildmaster
865 d
= master
.loadConfig(webCfg1
)
867 ports
= self
.checkPorts(self
.buildmaster
,
868 [(9999, pb
.PBServerFactory
), (9980, Site
)])
871 # nothing should be changed
872 d
.addCallback(_check1
)
874 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webCfg1
))
876 ports
= self
.checkPorts(self
.buildmaster
,
877 [(9999, pb
.PBServerFactory
), (9980, Site
)])
878 self
.failUnlessIdentical(self
.p
, ports
[1],
879 "web port was changed even though "
880 "configuration was not")
881 # WebStatus is no longer a ComparableMixin, so it will be
882 # rebuilt on each reconfig
883 #d.addCallback(_check2)
885 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webCfg2
))
886 # changes port to 9981
888 ports
= self
.checkPorts(self
.buildmaster
,
889 [(9999, pb
.PBServerFactory
), (9981, Site
)])
890 self
.failIf(self
.p
is ports
[1],
891 "configuration was changed but web port was unchanged")
892 d
.addCallback(_check3
)
894 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webCfg3
))
895 # make 9981 on only localhost
897 ports
= self
.checkPorts(self
.buildmaster
,
898 [(9999, pb
.PBServerFactory
), (9981, Site
)])
899 self
.failUnlessEqual(ports
[1].kwargs
['interface'], "127.0.0.1")
900 d
.addCallback(_check4
)
902 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(emptyCfg
))
903 d
.addCallback(lambda res
:
904 self
.checkPorts(self
.buildmaster
,
905 [(9999, pb
.PBServerFactory
)]))
908 def testWebPathname(self
):
909 master
= self
.buildmaster
912 d
= master
.loadConfig(webNameCfg1
)
914 self
.checkPorts(self
.buildmaster
,
915 [(9999, pb
.PBServerFactory
),
916 ('~/.twistd-web-pb', pb
.PBServerFactory
)])
917 unixports
= self
.UNIXports(self
.buildmaster
)
918 self
.f
= f
= unixports
[0].args
[1]
919 self
.failUnless(isinstance(f
.root
, ResourcePublisher
))
920 d
.addCallback(_check1
)
922 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webNameCfg1
))
923 # nothing should be changed
925 self
.checkPorts(self
.buildmaster
,
926 [(9999, pb
.PBServerFactory
),
927 ('~/.twistd-web-pb', pb
.PBServerFactory
)])
928 newf
= self
.UNIXports(self
.buildmaster
)[0].args
[1]
929 self
.failUnlessIdentical(self
.f
, newf
,
930 "web factory was changed even though "
931 "configuration was not")
932 # WebStatus is no longer a ComparableMixin, so it will be
933 # rebuilt on each reconfig
934 #d.addCallback(_check2)
936 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webNameCfg2
))
938 self
.checkPorts(self
.buildmaster
,
939 [(9999, pb
.PBServerFactory
),
940 ('./bar.socket', pb
.PBServerFactory
)])
941 newf
= self
.UNIXports(self
.buildmaster
)[0].args
[1],
942 self
.failIf(self
.f
is newf
,
943 "web factory was unchanged but "
944 "configuration was changed")
945 d
.addCallback(_check3
)
947 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(emptyCfg
))
948 d
.addCallback(lambda res
:
949 self
.checkPorts(self
.buildmaster
,
950 [(9999, pb
.PBServerFactory
)]))
953 def testDebugPassword(self
):
954 master
= self
.buildmaster
956 master
.loadConfig(debugPasswordCfg
)
957 self
.failUnlessEqual(master
.checker
.users
,
958 {"change": "changepw",
961 master
.loadConfig(debugPasswordCfg
)
962 self
.failUnlessEqual(master
.checker
.users
,
963 {"change": "changepw",
966 master
.loadConfig(emptyCfg
)
967 self
.failUnlessEqual(master
.checker
.users
,
968 {"change": "changepw"})
971 master
= self
.buildmaster
972 botmaster
= master
.botmaster
974 # make sure that c['interlocks'] is rejected properly
975 self
.failUnlessRaises(KeyError, master
.loadConfig
, interlockCfgBad
)
976 # and that duplicate-named Locks are caught
977 self
.failUnlessRaises(ValueError, master
.loadConfig
, lockCfgBad1
)
978 self
.failUnlessRaises(ValueError, master
.loadConfig
, lockCfgBad2
)
979 self
.failUnlessRaises(ValueError, master
.loadConfig
, lockCfgBad3
)
981 # create a Builder that uses Locks
982 master
.loadConfig(lockCfg1a
)
983 b1
= master
.botmaster
.builders
["builder1"]
984 self
.failUnlessEqual(len(b1
.locks
), 2)
986 # reloading the same config should not change the Builder
987 master
.loadConfig(lockCfg1a
)
988 self
.failUnlessIdentical(b1
, master
.botmaster
.builders
["builder1"])
989 # but changing the set of locks used should change it
990 master
.loadConfig(lockCfg1b
)
991 self
.failIfIdentical(b1
, master
.botmaster
.builders
["builder1"])
992 b1
= master
.botmaster
.builders
["builder1"]
993 self
.failUnlessEqual(len(b1
.locks
), 1)
995 # similar test with step-scoped locks
996 master
.loadConfig(lockCfg2a
)
997 b1
= master
.botmaster
.builders
["builder1"]
998 # reloading the same config should not change the Builder
999 master
.loadConfig(lockCfg2a
)
1000 self
.failUnlessIdentical(b1
, master
.botmaster
.builders
["builder1"])
1001 # but changing the set of locks used should change it
1002 master
.loadConfig(lockCfg2b
)
1003 self
.failIfIdentical(b1
, master
.botmaster
.builders
["builder1"])
1004 b1
= master
.botmaster
.builders
["builder1"]
1005 # remove the locks entirely
1006 master
.loadConfig(lockCfg2c
)
1007 self
.failIfIdentical(b1
, master
.botmaster
.builders
["builder1"])
1009 class ConfigElements(unittest
.TestCase
):
1010 # verify that ComparableMixin is working
1011 def testSchedulers(self
):
1012 s1
= scheduler
.Scheduler(name
='quick', branch
=None,
1014 builderNames
=['quick'])
1015 s2
= scheduler
.Scheduler(name
="all", branch
=None,
1016 treeStableTimer
=5*60,
1017 builderNames
=["a", "b"])
1018 s3
= scheduler
.Try_Userpass("try", ["a","b"], port
=9989,
1019 userpass
=[("foo","bar")])
1020 s1a
= scheduler
.Scheduler(name
='quick', branch
=None,
1022 builderNames
=['quick'])
1023 s2a
= scheduler
.Scheduler(name
="all", branch
=None,
1024 treeStableTimer
=5*60,
1025 builderNames
=["a", "b"])
1026 s3a
= scheduler
.Try_Userpass("try", ["a","b"], port
=9989,
1027 userpass
=[("foo","bar")])
1028 self
.failUnless(s1
== s1
)
1029 self
.failUnless(s1
== s1a
)
1030 self
.failUnless(s1a
in [s1
, s2
, s3
])
1031 self
.failUnless(s2a
in [s1
, s2
, s3
])
1032 self
.failUnless(s3a
in [s1
, s2
, s3
])
1036 class ConfigFileTest(unittest
.TestCase
):
1038 def testFindConfigFile(self
):
1040 open(os
.path
.join("test_cf", "master.cfg"), "w").write(emptyCfg
)
1041 slaveportCfg
= emptyCfg
+ "c['slavePortnum'] = 9000\n"
1042 open(os
.path
.join("test_cf", "alternate.cfg"), "w").write(slaveportCfg
)
1044 m
= BuildMaster("test_cf")
1045 m
.loadTheConfigFile()
1046 self
.failUnlessEqual(m
.slavePortnum
, "tcp:9999")
1048 m
= BuildMaster("test_cf", "alternate.cfg")
1049 m
.loadTheConfigFile()
1050 self
.failUnlessEqual(m
.slavePortnum
, "tcp:9000")
1053 class MyTarget(base
.StatusReceiverMultiService
):
1054 def __init__(self
, name
):
1056 base
.StatusReceiverMultiService
.__init
__(self
)
1057 def startService(self
):
1058 # make a note in a list stashed in the BuildMaster
1059 self
.parent
.targetevents
.append(("start", self
.name
))
1060 return base
.StatusReceiverMultiService
.startService(self
)
1061 def stopService(self
):
1062 self
.parent
.targetevents
.append(("stop", self
.name
))
1063 return base
.StatusReceiverMultiService
.stopService(self
)
1065 class MySlowTarget(MyTarget
):
1066 def stopService(self
):
1067 from twisted
.internet
import reactor
1068 d
= base
.StatusReceiverMultiService
.stopService(self
)
1070 d2
= defer
.Deferred()
1071 reactor
.callLater(0.1, d2
.callback
, res
)
1073 d
.addCallback(stall
)
1075 def finishedStalling(res
):
1076 m
.targetevents
.append(("stop", self
.name
))
1078 d
.addCallback(finishedStalling
)
1081 # we can't actually startService a buildmaster with a config that uses a
1082 # fixed slavePortnum like 9999, so instead this makes it possible to pass '0'
1083 # for the first time, and then substitute back in the allocated port number
1084 # on subsequent passes.
1085 startableEmptyCfg
= emptyCfg
+ \
1087 c['slavePortnum'] = %d
1090 targetCfg1
= startableEmptyCfg
+ \
1092 from buildbot.test.test_config import MyTarget
1093 c['status'] = [MyTarget('a')]
1096 targetCfg2
= startableEmptyCfg
+ \
1098 from buildbot.test.test_config import MySlowTarget
1099 c['status'] = [MySlowTarget('b')]
1102 class StartService(unittest
.TestCase
):
1104 return self
.master
.stopService()
1106 def testStartService(self
):
1108 self
.master
= m
= BuildMaster("test_ss")
1109 # inhibit the usual read-config-on-startup behavior
1112 d
= m
.loadConfig(startableEmptyCfg
% 0)
1113 d
.addCallback(self
._testStartService
_0)
1116 def _testStartService_0(self
, res
):
1119 # figure out what port got allocated
1120 self
.portnum
= m
.slavePort
._port
.getHost().port
1121 d
= m
.loadConfig(targetCfg1
% self
.portnum
)
1122 d
.addCallback(self
._testStartService
_1)
1125 def _testStartService_1(self
, res
):
1126 self
.failUnlessEqual(len(self
.master
.statusTargets
), 1)
1127 self
.failUnless(isinstance(self
.master
.statusTargets
[0], MyTarget
))
1128 self
.failUnlessEqual(self
.master
.targetevents
,
1130 self
.master
.targetevents
= []
1131 # reloading the same config should not start or stop the target
1132 d
= self
.master
.loadConfig(targetCfg1
% self
.portnum
)
1133 d
.addCallback(self
._testStartService
_2)
1136 def _testStartService_2(self
, res
):
1137 self
.failUnlessEqual(self
.master
.targetevents
, [])
1138 # but loading a new config file should stop the old one, then
1140 d
= self
.master
.loadConfig(targetCfg2
% self
.portnum
)
1141 d
.addCallback(self
._testStartService
_3)
1144 def _testStartService_3(self
, res
):
1145 self
.failUnlessEqual(self
.master
.targetevents
,
1146 [('stop', 'a'), ('start', 'b')])
1147 self
.master
.targetevents
= []
1148 # and going back to the old one should do the same, in the same
1149 # order, even though the current MySlowTarget takes a moment to shut
1151 d
= self
.master
.loadConfig(targetCfg1
% self
.portnum
)
1152 d
.addCallback(self
._testStartService
_4)
1155 def _testStartService_4(self
, res
):
1156 self
.failUnlessEqual(self
.master
.targetevents
,
1157 [('stop', 'b'), ('start', 'a')])
1161 from buildbot.process.factory import BuildFactory, s
1162 from buildbot.steps.shell import ShellCommand
1163 from buildbot.steps.source import Darcs
1164 from buildbot.buildslave import BuildSlave
1165 BuildmasterConfig = c = {}
1166 c['slaves'] = [BuildSlave('bot1', 'pw1')]
1167 c['schedulers'] = []
1168 c['slavePortnum'] = 9999
1169 f1 = BuildFactory([ShellCommand(command='echo yes'),
1170 s(ShellCommand, command='old-style'),
1172 f1.addStep(Darcs(repourl='http://buildbot.net/repos/trunk'))
1173 f1.addStep(ShellCommand, command='echo old-style')
1174 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
1175 'builddir':'workdir', 'factory':f1}]
1178 class Factories(unittest
.TestCase
):
1180 def failUnlessExpectedShell(self
, factory
, defaults
=True, **kwargs
):
1183 shell_args
.update({'descriptionDone': None,
1184 'description': None,
1188 shell_args
.update(kwargs
)
1189 self
.failUnlessIdentical(factory
[0], ShellCommand
)
1190 if factory
[1] != shell_args
:
1192 print "factory had:"
1193 for k
in sorted(factory
[1].keys()):
1195 print "but we were expecting:"
1196 for k
in sorted(shell_args
.keys()):
1198 self
.failUnlessEqual(factory
[1], shell_args
)
1200 def failUnlessExpectedDarcs(self
, factory
, **kwargs
):
1201 darcs_args
= {'workdir': None,
1202 'alwaysUseLatest': False,
1207 'defaultBranch': None,
1210 darcs_args
.update(kwargs
)
1211 self
.failUnlessIdentical(factory
[0], Darcs
)
1212 if factory
[1] != darcs_args
:
1214 print "factory had:"
1215 for k
in sorted(factory
[1].keys()):
1217 print "but we were expecting:"
1218 for k
in sorted(darcs_args
.keys()):
1220 self
.failUnlessEqual(factory
[1], darcs_args
)
1222 def testSteps(self
):
1223 m
= BuildMaster(".")
1225 b
= m
.botmaster
.builders
["builder1"]
1226 steps
= b
.buildFactory
.steps
1227 self
.failUnlessEqual(len(steps
), 4)
1229 self
.failUnlessExpectedShell(steps
[0], command
="echo yes")
1230 self
.failUnlessExpectedShell(steps
[1], defaults
=False,
1231 command
="old-style")
1232 self
.failUnlessExpectedDarcs(steps
[2],
1233 repourl
="http://buildbot.net/repos/trunk")
1234 self
.failUnlessExpectedShell(steps
[3], defaults
=False,
1235 command
="echo old-style")
1237 def _loop(self
, orig
):
1238 step_class
, kwargs
= orig
.getStepFactory()
1239 newstep
= step_class(**kwargs
)
1242 def testAllSteps(self
):
1243 # make sure that steps can be created from the factories that they
1245 for s
in ( dummy
.Dummy(), dummy
.FailingDummy(), dummy
.RemoteDummy(),
1246 maxq
.MaxQ("testdir"),
1247 python
.BuildEPYDoc(), python
.PyFlakes(),
1248 python_twisted
.HLint(),
1249 python_twisted
.Trial(testpath
=None, tests
="tests"),
1250 python_twisted
.ProcessDocs(), python_twisted
.BuildDebs(),
1251 python_twisted
.RemovePYCs(),
1252 shell
.ShellCommand(), shell
.TreeSize(),
1253 shell
.Configure(), shell
.Compile(), shell
.Test(),
1254 source
.CVS("cvsroot", "module"),
1255 source
.SVN("svnurl"), source
.Darcs("repourl"),
1256 source
.Git("repourl"),
1257 source
.Arch("url", "version"),
1258 source
.Bazaar("url", "version", "archive"),
1259 source
.Bzr("repourl"),
1260 source
.Mercurial("repourl"),
1261 source
.P4("p4base"),
1262 source
.P4Sync(1234, "p4user", "passwd", "client",
1264 source
.Monotone("server", "branch"),
1265 transfer
.FileUpload("src", "dest"),
1266 transfer
.FileDownload("src", "dest"),
1271 print "error checking %s" % s