1 # -*- test-case-name: buildbot.test.test_config -*-
3 import os
, warnings
, exceptions
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 # this class generates several deprecation warnings, which the user
377 # doesn't need to see.
378 warnings
.simplefilter('ignore', exceptions
.DeprecationWarning)
379 self
.buildmaster
= BuildMaster(".")
381 def failUnlessListsEquivalent(self
, list1
, list2
):
386 self
.failUnlessEqual(l1
, l2
)
388 def servers(self
, s
, types
):
389 # perform a recursive search of s.services, looking for instances of
390 # twisted.application.internet.TCPServer, then extract their .args
391 # values to find the TCP ports they want to listen on
393 if service
.IServiceCollection
.providedBy(child
):
394 for gc
in self
.servers(child
, types
):
396 if isinstance(child
, types
):
399 def TCPports(self
, s
):
400 return list(self
.servers(s
, internet
.TCPServer
))
401 def UNIXports(self
, s
):
402 return list(self
.servers(s
, internet
.UNIXServer
))
403 def TCPclients(self
, s
):
404 return list(self
.servers(s
, internet
.TCPClient
))
406 def checkPorts(self
, svc
, expected
):
407 """Verify that the TCPServer and UNIXServer children of the given
408 service have the expected portnum/pathname and factory classes. As a
409 side-effect, return a list of servers in the same order as the
410 'expected' list. This can be used to verify properties of the
411 factories contained therein."""
413 expTCP
= [e
for e
in expected
if type(e
[0]) == int]
414 expUNIX
= [e
for e
in expected
if type(e
[0]) == str]
415 haveTCP
= [(p
.args
[0], p
.args
[1].__class
__)
416 for p
in self
.TCPports(svc
)]
417 haveUNIX
= [(p
.args
[0], p
.args
[1].__class
__)
418 for p
in self
.UNIXports(svc
)]
419 self
.failUnlessListsEquivalent(expTCP
, haveTCP
)
420 self
.failUnlessListsEquivalent(expUNIX
, haveUNIX
)
423 for have
in self
.TCPports(svc
) + self
.UNIXports(svc
):
424 if have
.args
[0] == e
[0]:
427 assert(len(ret
) == len(expected
))
431 self
.failUnlessRaises(KeyError, self
.buildmaster
.loadConfig
, "")
433 def testSimple(self
):
434 # covers slavePortnum, base checker passwords
435 master
= self
.buildmaster
438 master
.loadConfig(emptyCfg
)
439 # note: this doesn't actually start listening, because the app
440 # hasn't been started running
441 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9999")
442 self
.checkPorts(master
, [(9999, pb
.PBServerFactory
)])
443 self
.failUnlessEqual(list(master
.change_svc
), [])
444 self
.failUnlessEqual(master
.botmaster
.builders
, {})
445 self
.failUnlessEqual(master
.checker
.users
,
446 {"change": "changepw"})
447 self
.failUnlessEqual(master
.projectName
, "dummy project")
448 self
.failUnlessEqual(master
.projectURL
, "http://dummy.example.com")
449 self
.failUnlessEqual(master
.buildbotURL
,
450 "http://dummy.example.com/buildbot")
452 def testSlavePortnum(self
):
453 master
= self
.buildmaster
456 master
.loadConfig(emptyCfg
)
457 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9999")
458 ports
= self
.checkPorts(master
, [(9999, pb
.PBServerFactory
)])
461 master
.loadConfig(emptyCfg
)
462 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9999")
463 ports
= self
.checkPorts(master
, [(9999, pb
.PBServerFactory
)])
464 self
.failUnlessIdentical(p
, ports
[0],
465 "the slave port was changed even " + \
466 "though the configuration was not")
468 master
.loadConfig(emptyCfg
+ "c['slavePortnum'] = 9000\n")
469 self
.failUnlessEqual(master
.slavePortnum
, "tcp:9000")
470 ports
= self
.checkPorts(master
, [(9000, pb
.PBServerFactory
)])
471 self
.failIf(p
is ports
[0],
472 "slave port was unchanged but configuration was changed")
474 def testSlaves(self
):
475 master
= self
.buildmaster
477 master
.loadConfig(emptyCfg
)
478 self
.failUnlessEqual(master
.botmaster
.builders
, {})
479 self
.failUnlessEqual(master
.checker
.users
,
480 {"change": "changepw"})
481 # 'botsCfg' is testing backwards compatibility, for 0.7.5 config
482 # files that have not yet been updated to 0.7.6 . This compatibility
483 # (and this test) is scheduled for removal in 0.8.0 .
484 botsCfg
= (emptyCfg
+
485 "c['bots'] = [('bot1', 'pw1'), ('bot2', 'pw2')]\n")
486 master
.loadConfig(botsCfg
)
487 self
.failUnlessEqual(master
.checker
.users
,
488 {"change": "changepw",
491 master
.loadConfig(botsCfg
)
492 self
.failUnlessEqual(master
.checker
.users
,
493 {"change": "changepw",
496 master
.loadConfig(emptyCfg
)
497 self
.failUnlessEqual(master
.checker
.users
,
498 {"change": "changepw"})
499 slavesCfg
= (emptyCfg
+
500 "from buildbot.buildslave import BuildSlave\n"
501 "c['slaves'] = [BuildSlave('bot1','pw1'), "
502 "BuildSlave('bot2','pw2')]\n")
503 master
.loadConfig(slavesCfg
)
504 self
.failUnlessEqual(master
.checker
.users
,
505 {"change": "changepw",
510 def testChangeSource(self
):
511 master
= self
.buildmaster
513 master
.loadConfig(emptyCfg
)
514 self
.failUnlessEqual(list(master
.change_svc
), [])
516 sourcesCfg
= emptyCfg
+ \
518 from buildbot.changes.pb import PBChangeSource
519 c['change_source'] = PBChangeSource()
522 d
= master
.loadConfig(sourcesCfg
)
524 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 1)
525 s1
= list(self
.buildmaster
.change_svc
)[0]
526 self
.failUnless(isinstance(s1
, PBChangeSource
))
527 self
.failUnlessEqual(s1
, list(self
.buildmaster
.change_svc
)[0])
528 self
.failUnless(s1
.parent
)
530 # verify that unchanged sources are not interrupted
531 d1
= self
.buildmaster
.loadConfig(sourcesCfg
)
534 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 1)
535 s2
= list(self
.buildmaster
.change_svc
)[0]
536 self
.failUnlessIdentical(s1
, s2
)
537 self
.failUnless(s1
.parent
)
538 d1
.addCallback(_check2
)
540 d
.addCallback(_check1
)
542 # make sure we can get rid of the sources too
543 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(emptyCfg
))
546 self
.failUnlessEqual(list(self
.buildmaster
.change_svc
), [])
547 d
.addCallback(_check3
)
551 def testChangeSources(self
):
552 # make sure we can accept a list
553 master
= self
.buildmaster
555 master
.loadConfig(emptyCfg
)
556 self
.failUnlessEqual(list(master
.change_svc
), [])
558 sourcesCfg
= emptyCfg
+ \
560 from buildbot.changes.pb import PBChangeSource
561 from buildbot.changes.mail import SyncmailMaildirSource
562 c['change_source'] = [PBChangeSource(),
563 SyncmailMaildirSource('.'),
567 d
= master
.loadConfig(sourcesCfg
)
569 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 2)
570 s1
,s2
= list(self
.buildmaster
.change_svc
)
571 if isinstance(s2
, PBChangeSource
):
573 self
.failUnless(isinstance(s1
, PBChangeSource
))
574 self
.failUnless(s1
.parent
)
575 self
.failUnless(isinstance(s2
, SyncmailMaildirSource
))
576 self
.failUnless(s2
.parent
)
577 d
.addCallback(_check1
)
580 def testSources(self
):
581 # test backwards compatibility. c['sources'] is deprecated.
582 master
= self
.buildmaster
584 master
.loadConfig(emptyCfg
)
585 self
.failUnlessEqual(list(master
.change_svc
), [])
587 sourcesCfg
= emptyCfg
+ \
589 from buildbot.changes.pb import PBChangeSource
590 c['sources'] = [PBChangeSource()]
593 d
= master
.loadConfig(sourcesCfg
)
595 self
.failUnlessEqual(len(list(self
.buildmaster
.change_svc
)), 1)
596 s1
= list(self
.buildmaster
.change_svc
)[0]
597 self
.failUnless(isinstance(s1
, PBChangeSource
))
598 self
.failUnless(s1
.parent
)
599 d
.addCallback(_check1
)
602 def shouldBeFailure(self
, res
, *expected
):
603 self
.failUnless(isinstance(res
, failure
.Failure
),
604 "we expected this to fail, not produce %s" % (res
,))
606 return None # all is good
608 def testSchedulerErrors(self
):
609 master
= self
.buildmaster
611 master
.loadConfig(emptyCfg
)
612 self
.failUnlessEqual(master
.allSchedulers(), [])
614 def _shouldBeFailure(res
, hint
=None):
615 self
.shouldBeFailure(res
, AssertionError, ValueError)
617 self
.failUnless(str(res
).find(hint
) != -1)
619 def _loadConfig(res
, newcfg
):
620 return self
.buildmaster
.loadConfig(newcfg
)
621 d
= defer
.succeed(None)
623 # c['schedulers'] must be a list
624 badcfg
= schedulersCfg
+ \
626 c['schedulers'] = Scheduler('full', None, 60, ['builder1'])
628 d
.addCallback(_loadConfig
, badcfg
)
629 d
.addBoth(_shouldBeFailure
,
630 "c['schedulers'] must be a list of Scheduler instances")
632 # c['schedulers'] must be a list of IScheduler objects
633 badcfg
= schedulersCfg
+ \
635 c['schedulers'] = ['oops', 'problem']
637 d
.addCallback(_loadConfig
, badcfg
)
638 d
.addBoth(_shouldBeFailure
,
639 "c['schedulers'] must be a list of Scheduler instances")
641 # c['schedulers'] must point at real builders
642 badcfg
= schedulersCfg
+ \
644 c['schedulers'] = [Scheduler('full', None, 60, ['builder-bogus'])]
646 d
.addCallback(_loadConfig
, badcfg
)
647 d
.addBoth(_shouldBeFailure
, "uses unknown builder")
649 # builderNames= must be a list
650 badcfg
= schedulersCfg
+ \
652 c['schedulers'] = [Scheduler('full', None, 60, 'builder1')]
654 d
.addCallback(_loadConfig
, badcfg
)
655 d
.addBoth(_shouldBeFailure
,
656 "must be a list of Builder description names")
658 # builderNames= must be a list of strings, not dicts
659 badcfg
= schedulersCfg
+ \
661 c['schedulers'] = [Scheduler('full', None, 60, [b1])]
663 d
.addCallback(_loadConfig
, badcfg
)
664 d
.addBoth(_shouldBeFailure
,
665 "must be a list of Builder description names")
667 # builderNames= must be a list of strings, not a dict
668 badcfg
= schedulersCfg
+ \
670 c['schedulers'] = [Scheduler('full', None, 60, b1)]
672 d
.addCallback(_loadConfig
, badcfg
)
673 d
.addBoth(_shouldBeFailure
,
674 "must be a list of Builder description names")
676 # each Scheduler must have a unique name
677 badcfg
= schedulersCfg
+ \
679 c['schedulers'] = [Scheduler('dup', None, 60, []),
680 Scheduler('dup', None, 60, [])]
682 d
.addCallback(_loadConfig
, badcfg
)
683 d
.addBoth(_shouldBeFailure
, "Schedulers must have unique names")
687 def testSchedulers(self
):
688 master
= self
.buildmaster
690 master
.loadConfig(emptyCfg
)
691 self
.failUnlessEqual(master
.allSchedulers(), [])
693 d
= self
.buildmaster
.loadConfig(schedulersCfg
)
694 d
.addCallback(self
._testSchedulers
_1)
697 def _testSchedulers_1(self
, res
):
698 sch
= self
.buildmaster
.allSchedulers()
699 self
.failUnlessEqual(len(sch
), 1)
701 self
.failUnless(isinstance(s
, scheduler
.Scheduler
))
702 self
.failUnlessEqual(s
.name
, "full")
703 self
.failUnlessEqual(s
.branch
, None)
704 self
.failUnlessEqual(s
.treeStableTimer
, 60)
705 self
.failUnlessEqual(s
.builderNames
, ['builder1'])
707 newcfg
= schedulersCfg
+ \
709 s1 = Scheduler('full', None, 60, ['builder1'])
710 c['schedulers'] = [s1, Dependent('downstream', s1, ['builder1'])]
712 d
= self
.buildmaster
.loadConfig(newcfg
)
713 d
.addCallback(self
._testSchedulers
_2, newcfg
)
715 def _testSchedulers_2(self
, res
, newcfg
):
716 sch
= self
.buildmaster
.allSchedulers()
717 self
.failUnlessEqual(len(sch
), 2)
719 self
.failUnless(isinstance(s
, scheduler
.Scheduler
))
721 self
.failUnless(isinstance(s
, scheduler
.Dependent
))
722 self
.failUnlessEqual(s
.name
, "downstream")
723 self
.failUnlessEqual(s
.builderNames
, ['builder1'])
725 # reloading the same config file should leave the schedulers in place
726 d
= self
.buildmaster
.loadConfig(newcfg
)
727 d
.addCallback(self
._testSchedulers
_3, sch
)
729 def _testSchedulers_3(self
, res
, sch1
):
730 sch2
= self
.buildmaster
.allSchedulers()
731 self
.failUnlessEqual(len(sch2
), 2)
734 self
.failUnlessEqual(sch1
, sch2
)
735 self
.failUnlessIdentical(sch1
[0], sch2
[0])
736 self
.failUnlessIdentical(sch1
[1], sch2
[1])
737 self
.failUnlessIdentical(sch1
[0].parent
, self
.buildmaster
)
738 self
.failUnlessIdentical(sch1
[1].parent
, self
.buildmaster
)
742 def testBuilders(self
):
743 master
= self
.buildmaster
744 master
.loadConfig(emptyCfg
)
745 self
.failUnlessEqual(master
.botmaster
.builders
, {})
747 master
.loadConfig(buildersCfg
)
748 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
749 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
750 b
= master
.botmaster
.builders
["builder1"]
751 self
.failUnless(isinstance(b
, Builder
))
752 self
.failUnlessEqual(b
.name
, "builder1")
753 self
.failUnlessEqual(b
.slavenames
, ["bot1"])
754 self
.failUnlessEqual(b
.builddir
, "workdir")
756 self
.failUnless(isinstance(f1
, BasicBuildFactory
))
758 self
.failUnlessEqual(len(steps
), 3)
759 self
.failUnlessEqual(steps
[0], (CVS
,
760 {'cvsroot': 'cvsroot',
761 'cvsmodule': 'cvsmodule',
763 self
.failUnlessEqual(steps
[1], (Compile
,
764 {'command': 'make all'}))
765 self
.failUnlessEqual(steps
[2], (Test
,
766 {'command': 'make check'}))
769 # make sure a reload of the same data doesn't interrupt the Builder
770 master
.loadConfig(buildersCfg
)
771 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
772 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
773 b2
= master
.botmaster
.builders
["builder1"]
774 self
.failUnlessIdentical(b
, b2
)
775 # TODO: test that the BuilderStatus object doesn't change
776 #statusbag2 = master.client_svc.statusbags["builder1"]
777 #self.failUnlessIdentical(statusbag, statusbag2)
779 # but changing something should result in a new Builder
780 master
.loadConfig(buildersCfg2
)
781 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
782 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
783 b3
= master
.botmaster
.builders
["builder1"]
785 # the statusbag remains the same TODO
786 #statusbag3 = master.client_svc.statusbags["builder1"]
787 #self.failUnlessIdentical(statusbag, statusbag3)
790 master
.loadConfig(buildersCfg3
)
791 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1",
793 self
.failUnlessListsEquivalent(master
.botmaster
.builders
.keys(),
794 ["builder1", "builder2"])
795 b4
= master
.botmaster
.builders
["builder1"]
796 self
.failUnlessIdentical(b3
, b4
)
798 # changing first builder should leave it at the same place in the list
799 master
.loadConfig(buildersCfg4
)
800 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1",
802 self
.failUnlessListsEquivalent(master
.botmaster
.builders
.keys(),
803 ["builder1", "builder2"])
804 b5
= master
.botmaster
.builders
["builder1"]
805 self
.failIf(b4
is b5
)
807 # and removing it should make the Builder go away
808 master
.loadConfig(emptyCfg
)
809 self
.failUnlessEqual(master
.botmaster
.builderNames
, [])
810 self
.failUnlessEqual(master
.botmaster
.builders
, {})
811 #self.failUnlessEqual(master.client_svc.statusbags, {}) # TODO
813 def testWithProperties(self
):
814 master
= self
.buildmaster
815 master
.loadConfig(wpCfg1
)
816 self
.failUnlessEqual(master
.botmaster
.builderNames
, ["builder1"])
817 self
.failUnlessEqual(master
.botmaster
.builders
.keys(), ["builder1"])
818 b1
= master
.botmaster
.builders
["builder1"]
820 # reloading the same config should leave the builder unchanged
821 master
.loadConfig(wpCfg1
)
822 b2
= master
.botmaster
.builders
["builder1"]
823 self
.failUnlessIdentical(b1
, b2
)
825 # but changing the parameters of the WithProperties should change it
826 master
.loadConfig(wpCfg2
)
827 b3
= master
.botmaster
.builders
["builder1"]
828 self
.failIf(b1
is b3
)
830 # again, reloading same config should leave the builder unchanged
831 master
.loadConfig(wpCfg2
)
832 b4
= master
.botmaster
.builders
["builder1"]
833 self
.failUnlessIdentical(b3
, b4
)
835 def checkIRC(self
, m
, expected
):
837 for irc
in self
.servers(m
, words
.IRC
):
838 ircs
[irc
.host
] = (irc
.nick
, irc
.channels
)
839 self
.failUnlessEqual(ircs
, expected
)
843 raise unittest
.SkipTest("Twisted Words package is not installed")
844 master
= self
.buildmaster
846 d
= master
.loadConfig(emptyCfg
)
848 d
.addCallback(lambda res
: self
.checkIRC(master
, e1
))
849 d
.addCallback(lambda res
: master
.loadConfig(ircCfg1
))
850 e2
= {'irc.us.freenode.net': ('buildbot', ['twisted'])}
851 d
.addCallback(lambda res
: self
.checkIRC(master
, e2
))
852 d
.addCallback(lambda res
: master
.loadConfig(ircCfg2
))
853 e3
= {'irc.us.freenode.net': ('buildbot', ['twisted']),
854 'irc.example.com': ('otherbot', ['chan1', 'chan2'])}
855 d
.addCallback(lambda res
: self
.checkIRC(master
, e3
))
856 d
.addCallback(lambda res
: master
.loadConfig(ircCfg3
))
857 e4
= {'irc.us.freenode.net': ('buildbot', ['knotted'])}
858 d
.addCallback(lambda res
: self
.checkIRC(master
, e4
))
859 d
.addCallback(lambda res
: master
.loadConfig(ircCfg1
))
860 e5
= {'irc.us.freenode.net': ('buildbot', ['twisted'])}
861 d
.addCallback(lambda res
: self
.checkIRC(master
, e5
))
864 def testWebPortnum(self
):
865 master
= self
.buildmaster
868 d
= master
.loadConfig(webCfg1
)
870 ports
= self
.checkPorts(self
.buildmaster
,
871 [(9999, pb
.PBServerFactory
), (9980, Site
)])
874 # nothing should be changed
875 d
.addCallback(_check1
)
877 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webCfg1
))
879 ports
= self
.checkPorts(self
.buildmaster
,
880 [(9999, pb
.PBServerFactory
), (9980, Site
)])
881 self
.failUnlessIdentical(self
.p
, ports
[1],
882 "web port was changed even though "
883 "configuration was not")
884 # WebStatus is no longer a ComparableMixin, so it will be
885 # rebuilt on each reconfig
886 #d.addCallback(_check2)
888 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webCfg2
))
889 # changes port to 9981
891 ports
= self
.checkPorts(self
.buildmaster
,
892 [(9999, pb
.PBServerFactory
), (9981, Site
)])
893 self
.failIf(self
.p
is ports
[1],
894 "configuration was changed but web port was unchanged")
895 d
.addCallback(_check3
)
897 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webCfg3
))
898 # make 9981 on only localhost
900 ports
= self
.checkPorts(self
.buildmaster
,
901 [(9999, pb
.PBServerFactory
), (9981, Site
)])
902 self
.failUnlessEqual(ports
[1].kwargs
['interface'], "127.0.0.1")
903 d
.addCallback(_check4
)
905 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(emptyCfg
))
906 d
.addCallback(lambda res
:
907 self
.checkPorts(self
.buildmaster
,
908 [(9999, pb
.PBServerFactory
)]))
911 def testWebPathname(self
):
912 master
= self
.buildmaster
915 d
= master
.loadConfig(webNameCfg1
)
917 self
.checkPorts(self
.buildmaster
,
918 [(9999, pb
.PBServerFactory
),
919 ('~/.twistd-web-pb', pb
.PBServerFactory
)])
920 unixports
= self
.UNIXports(self
.buildmaster
)
921 self
.f
= f
= unixports
[0].args
[1]
922 self
.failUnless(isinstance(f
.root
, ResourcePublisher
))
923 d
.addCallback(_check1
)
925 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webNameCfg1
))
926 # nothing should be changed
928 self
.checkPorts(self
.buildmaster
,
929 [(9999, pb
.PBServerFactory
),
930 ('~/.twistd-web-pb', pb
.PBServerFactory
)])
931 newf
= self
.UNIXports(self
.buildmaster
)[0].args
[1]
932 self
.failUnlessIdentical(self
.f
, newf
,
933 "web factory was changed even though "
934 "configuration was not")
935 # WebStatus is no longer a ComparableMixin, so it will be
936 # rebuilt on each reconfig
937 #d.addCallback(_check2)
939 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(webNameCfg2
))
941 self
.checkPorts(self
.buildmaster
,
942 [(9999, pb
.PBServerFactory
),
943 ('./bar.socket', pb
.PBServerFactory
)])
944 newf
= self
.UNIXports(self
.buildmaster
)[0].args
[1],
945 self
.failIf(self
.f
is newf
,
946 "web factory was unchanged but "
947 "configuration was changed")
948 d
.addCallback(_check3
)
950 d
.addCallback(lambda res
: self
.buildmaster
.loadConfig(emptyCfg
))
951 d
.addCallback(lambda res
:
952 self
.checkPorts(self
.buildmaster
,
953 [(9999, pb
.PBServerFactory
)]))
956 def testDebugPassword(self
):
957 master
= self
.buildmaster
959 master
.loadConfig(debugPasswordCfg
)
960 self
.failUnlessEqual(master
.checker
.users
,
961 {"change": "changepw",
964 master
.loadConfig(debugPasswordCfg
)
965 self
.failUnlessEqual(master
.checker
.users
,
966 {"change": "changepw",
969 master
.loadConfig(emptyCfg
)
970 self
.failUnlessEqual(master
.checker
.users
,
971 {"change": "changepw"})
974 master
= self
.buildmaster
975 botmaster
= master
.botmaster
977 # make sure that c['interlocks'] is rejected properly
978 self
.failUnlessRaises(KeyError, master
.loadConfig
, interlockCfgBad
)
979 # and that duplicate-named Locks are caught
980 self
.failUnlessRaises(ValueError, master
.loadConfig
, lockCfgBad1
)
981 self
.failUnlessRaises(ValueError, master
.loadConfig
, lockCfgBad2
)
982 self
.failUnlessRaises(ValueError, master
.loadConfig
, lockCfgBad3
)
984 # create a Builder that uses Locks
985 master
.loadConfig(lockCfg1a
)
986 b1
= master
.botmaster
.builders
["builder1"]
987 self
.failUnlessEqual(len(b1
.locks
), 2)
989 # reloading the same config should not change the Builder
990 master
.loadConfig(lockCfg1a
)
991 self
.failUnlessIdentical(b1
, master
.botmaster
.builders
["builder1"])
992 # but changing the set of locks used should change it
993 master
.loadConfig(lockCfg1b
)
994 self
.failIfIdentical(b1
, master
.botmaster
.builders
["builder1"])
995 b1
= master
.botmaster
.builders
["builder1"]
996 self
.failUnlessEqual(len(b1
.locks
), 1)
998 # similar test with step-scoped locks
999 master
.loadConfig(lockCfg2a
)
1000 b1
= master
.botmaster
.builders
["builder1"]
1001 # reloading the same config should not change the Builder
1002 master
.loadConfig(lockCfg2a
)
1003 self
.failUnlessIdentical(b1
, master
.botmaster
.builders
["builder1"])
1004 # but changing the set of locks used should change it
1005 master
.loadConfig(lockCfg2b
)
1006 self
.failIfIdentical(b1
, master
.botmaster
.builders
["builder1"])
1007 b1
= master
.botmaster
.builders
["builder1"]
1008 # remove the locks entirely
1009 master
.loadConfig(lockCfg2c
)
1010 self
.failIfIdentical(b1
, master
.botmaster
.builders
["builder1"])
1012 class ConfigElements(unittest
.TestCase
):
1013 # verify that ComparableMixin is working
1014 def testSchedulers(self
):
1015 s1
= scheduler
.Scheduler(name
='quick', branch
=None,
1017 builderNames
=['quick'])
1018 s2
= scheduler
.Scheduler(name
="all", branch
=None,
1019 treeStableTimer
=5*60,
1020 builderNames
=["a", "b"])
1021 s3
= scheduler
.Try_Userpass("try", ["a","b"], port
=9989,
1022 userpass
=[("foo","bar")])
1023 s1a
= scheduler
.Scheduler(name
='quick', branch
=None,
1025 builderNames
=['quick'])
1026 s2a
= scheduler
.Scheduler(name
="all", branch
=None,
1027 treeStableTimer
=5*60,
1028 builderNames
=["a", "b"])
1029 s3a
= scheduler
.Try_Userpass("try", ["a","b"], port
=9989,
1030 userpass
=[("foo","bar")])
1031 self
.failUnless(s1
== s1
)
1032 self
.failUnless(s1
== s1a
)
1033 self
.failUnless(s1a
in [s1
, s2
, s3
])
1034 self
.failUnless(s2a
in [s1
, s2
, s3
])
1035 self
.failUnless(s3a
in [s1
, s2
, s3
])
1039 class ConfigFileTest(unittest
.TestCase
):
1041 def testFindConfigFile(self
):
1043 open(os
.path
.join("test_cf", "master.cfg"), "w").write(emptyCfg
)
1044 slaveportCfg
= emptyCfg
+ "c['slavePortnum'] = 9000\n"
1045 open(os
.path
.join("test_cf", "alternate.cfg"), "w").write(slaveportCfg
)
1047 m
= BuildMaster("test_cf")
1048 m
.loadTheConfigFile()
1049 self
.failUnlessEqual(m
.slavePortnum
, "tcp:9999")
1051 m
= BuildMaster("test_cf", "alternate.cfg")
1052 m
.loadTheConfigFile()
1053 self
.failUnlessEqual(m
.slavePortnum
, "tcp:9000")
1056 class MyTarget(base
.StatusReceiverMultiService
):
1057 def __init__(self
, name
):
1059 base
.StatusReceiverMultiService
.__init
__(self
)
1060 def startService(self
):
1061 # make a note in a list stashed in the BuildMaster
1062 self
.parent
.targetevents
.append(("start", self
.name
))
1063 return base
.StatusReceiverMultiService
.startService(self
)
1064 def stopService(self
):
1065 self
.parent
.targetevents
.append(("stop", self
.name
))
1066 return base
.StatusReceiverMultiService
.stopService(self
)
1068 class MySlowTarget(MyTarget
):
1069 def stopService(self
):
1070 from twisted
.internet
import reactor
1071 d
= base
.StatusReceiverMultiService
.stopService(self
)
1073 d2
= defer
.Deferred()
1074 reactor
.callLater(0.1, d2
.callback
, res
)
1076 d
.addCallback(stall
)
1078 def finishedStalling(res
):
1079 m
.targetevents
.append(("stop", self
.name
))
1081 d
.addCallback(finishedStalling
)
1084 # we can't actually startService a buildmaster with a config that uses a
1085 # fixed slavePortnum like 9999, so instead this makes it possible to pass '0'
1086 # for the first time, and then substitute back in the allocated port number
1087 # on subsequent passes.
1088 startableEmptyCfg
= emptyCfg
+ \
1090 c['slavePortnum'] = %d
1093 targetCfg1
= startableEmptyCfg
+ \
1095 from buildbot.test.test_config import MyTarget
1096 c['status'] = [MyTarget('a')]
1099 targetCfg2
= startableEmptyCfg
+ \
1101 from buildbot.test.test_config import MySlowTarget
1102 c['status'] = [MySlowTarget('b')]
1105 class StartService(unittest
.TestCase
):
1107 return self
.master
.stopService()
1109 def testStartService(self
):
1111 self
.master
= m
= BuildMaster("test_ss")
1112 # inhibit the usual read-config-on-startup behavior
1115 d
= m
.loadConfig(startableEmptyCfg
% 0)
1116 d
.addCallback(self
._testStartService
_0)
1119 def _testStartService_0(self
, res
):
1122 # figure out what port got allocated
1123 self
.portnum
= m
.slavePort
._port
.getHost().port
1124 d
= m
.loadConfig(targetCfg1
% self
.portnum
)
1125 d
.addCallback(self
._testStartService
_1)
1128 def _testStartService_1(self
, res
):
1129 self
.failUnlessEqual(len(self
.master
.statusTargets
), 1)
1130 self
.failUnless(isinstance(self
.master
.statusTargets
[0], MyTarget
))
1131 self
.failUnlessEqual(self
.master
.targetevents
,
1133 self
.master
.targetevents
= []
1134 # reloading the same config should not start or stop the target
1135 d
= self
.master
.loadConfig(targetCfg1
% self
.portnum
)
1136 d
.addCallback(self
._testStartService
_2)
1139 def _testStartService_2(self
, res
):
1140 self
.failUnlessEqual(self
.master
.targetevents
, [])
1141 # but loading a new config file should stop the old one, then
1143 d
= self
.master
.loadConfig(targetCfg2
% self
.portnum
)
1144 d
.addCallback(self
._testStartService
_3)
1147 def _testStartService_3(self
, res
):
1148 self
.failUnlessEqual(self
.master
.targetevents
,
1149 [('stop', 'a'), ('start', 'b')])
1150 self
.master
.targetevents
= []
1151 # and going back to the old one should do the same, in the same
1152 # order, even though the current MySlowTarget takes a moment to shut
1154 d
= self
.master
.loadConfig(targetCfg1
% self
.portnum
)
1155 d
.addCallback(self
._testStartService
_4)
1158 def _testStartService_4(self
, res
):
1159 self
.failUnlessEqual(self
.master
.targetevents
,
1160 [('stop', 'b'), ('start', 'a')])
1164 from buildbot.process.factory import BuildFactory, s
1165 from buildbot.steps.shell import ShellCommand
1166 from buildbot.steps.source import Darcs
1167 from buildbot.buildslave import BuildSlave
1168 BuildmasterConfig = c = {}
1169 c['slaves'] = [BuildSlave('bot1', 'pw1')]
1170 c['schedulers'] = []
1171 c['slavePortnum'] = 9999
1172 f1 = BuildFactory([ShellCommand(command='echo yes'),
1173 s(ShellCommand, command='old-style'),
1175 f1.addStep(Darcs(repourl='http://buildbot.net/repos/trunk'))
1176 f1.addStep(ShellCommand, command='echo old-style')
1177 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
1178 'builddir':'workdir', 'factory':f1}]
1181 class Factories(unittest
.TestCase
):
1183 def failUnlessExpectedShell(self
, factory
, defaults
=True, **kwargs
):
1186 shell_args
.update({'descriptionDone': None,
1187 'description': None,
1191 shell_args
.update(kwargs
)
1192 self
.failUnlessIdentical(factory
[0], ShellCommand
)
1193 if factory
[1] != shell_args
:
1195 print "factory had:"
1196 for k
in sorted(factory
[1].keys()):
1198 print "but we were expecting:"
1199 for k
in sorted(shell_args
.keys()):
1201 self
.failUnlessEqual(factory
[1], shell_args
)
1203 def failUnlessExpectedDarcs(self
, factory
, **kwargs
):
1204 darcs_args
= {'workdir': None,
1205 'alwaysUseLatest': False,
1210 'defaultBranch': None,
1213 darcs_args
.update(kwargs
)
1214 self
.failUnlessIdentical(factory
[0], Darcs
)
1215 if factory
[1] != darcs_args
:
1217 print "factory had:"
1218 for k
in sorted(factory
[1].keys()):
1220 print "but we were expecting:"
1221 for k
in sorted(darcs_args
.keys()):
1223 self
.failUnlessEqual(factory
[1], darcs_args
)
1225 def testSteps(self
):
1226 m
= BuildMaster(".")
1228 b
= m
.botmaster
.builders
["builder1"]
1229 steps
= b
.buildFactory
.steps
1230 self
.failUnlessEqual(len(steps
), 4)
1232 self
.failUnlessExpectedShell(steps
[0], command
="echo yes")
1233 self
.failUnlessExpectedShell(steps
[1], defaults
=False,
1234 command
="old-style")
1235 self
.failUnlessExpectedDarcs(steps
[2],
1236 repourl
="http://buildbot.net/repos/trunk")
1237 self
.failUnlessExpectedShell(steps
[3], defaults
=False,
1238 command
="echo old-style")
1240 def _loop(self
, orig
):
1241 step_class
, kwargs
= orig
.getStepFactory()
1242 newstep
= step_class(**kwargs
)
1245 def testAllSteps(self
):
1246 # make sure that steps can be created from the factories that they
1248 for s
in ( dummy
.Dummy(), dummy
.FailingDummy(), dummy
.RemoteDummy(),
1249 maxq
.MaxQ("testdir"),
1250 python
.BuildEPYDoc(), python
.PyFlakes(),
1251 python_twisted
.HLint(),
1252 python_twisted
.Trial(testpath
=None, tests
="tests"),
1253 python_twisted
.ProcessDocs(), python_twisted
.BuildDebs(),
1254 python_twisted
.RemovePYCs(),
1255 shell
.ShellCommand(), shell
.TreeSize(),
1256 shell
.Configure(), shell
.Compile(), shell
.Test(),
1257 source
.CVS("cvsroot", "module"),
1258 source
.SVN("svnurl"), source
.Darcs("repourl"),
1259 source
.Git("repourl"),
1260 source
.Arch("url", "version"),
1261 source
.Bazaar("url", "version", "archive"),
1262 source
.Bzr("repourl"),
1263 source
.Mercurial("repourl"),
1264 source
.P4("p4base"),
1265 source
.P4Sync(1234, "p4user", "passwd", "client",
1267 source
.Monotone("server", "branch"),
1268 transfer
.FileUpload("src", "dest"),
1269 transfer
.FileDownload("src", "dest"),
1274 print "error checking %s" % s