move BuildSlave to new class, merge with BotPerspective, make it long-lived
[buildbot.git] / buildbot / test / test_config.py
blobe14d21c47b0b8a77481cd953cae3b767004bb99f
1 # -*- test-case-name: buildbot.test.test_config -*-
3 import os
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, \
23 source, transfer
24 words = None
25 try:
26 from buildbot.status import words
27 except ImportError:
28 pass
30 emptyCfg = \
31 """
32 from buildbot.buildslave import BuildSlave
33 BuildmasterConfig = c = {}
34 c['slaves'] = []
35 c['schedulers'] = []
36 c['builders'] = []
37 c['slavePortnum'] = 9999
38 c['projectName'] = 'dummy project'
39 c['projectURL'] = 'http://dummy.example.com'
40 c['buildbotURL'] = 'http://dummy.example.com/buildbot'
41 """
43 buildersCfg = \
44 """
45 from buildbot.process.factory import BasicBuildFactory
46 from buildbot.buildslave import BuildSlave
47 BuildmasterConfig = c = {}
48 c['slaves'] = [BuildSlave('bot1', 'pw1')]
49 c['schedulers'] = []
50 c['slavePortnum'] = 9999
51 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
52 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
53 'builddir':'workdir', 'factory':f1}]
54 """
56 buildersCfg2 = buildersCfg + \
57 """
58 f1 = BasicBuildFactory('cvsroot', 'cvsmodule2')
59 c['builders'] = [{'name':'builder1', 'slavename':'bot1',
60 'builddir':'workdir', 'factory':f1}]
61 """
63 buildersCfg3 = buildersCfg2 + \
64 """
65 c['builders'].append({'name': 'builder2', 'slavename': 'bot1',
66 'builddir': 'workdir2', 'factory': f1 })
67 """
69 buildersCfg4 = buildersCfg2 + \
70 """
71 c['builders'] = [{ 'name': 'builder1', 'slavename': 'bot1',
72 'builddir': 'newworkdir', 'factory': f1 },
73 { 'name': 'builder2', 'slavename': 'bot1',
74 'builddir': 'workdir2', 'factory': f1 }]
75 """
77 wpCfg1 = buildersCfg + \
78 """
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}]
84 """
86 wpCfg2 = buildersCfg + \
87 """
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}]
94 """
98 ircCfg1 = emptyCfg + \
99 """
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'
152 interlockCfgBad = \
154 from buildbot.process.factory import BasicBuildFactory
155 from buildbot.buildslave import BuildSlave
156 c = {}
157 c['slaves'] = [BuildSlave('bot1', 'pw1')]
158 c['schedulers'] = []
159 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
160 c['builders'] = [
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
173 lockCfgBad1 = \
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
179 c = {}
180 c['slaves'] = [BuildSlave('bot1', 'pw1')]
181 c['schedulers'] = []
182 l1 = MasterLock('lock1')
183 l2 = MasterLock('lock1') # duplicate lock name
184 f1 = BuildFactory([s(Dummy, locks=[])])
185 c['builders'] = [
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
195 lockCfgBad2 = \
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
201 c = {}
202 c['slaves'] = [BuildSlave('bot1', 'pw1')]
203 c['schedulers'] = []
204 l1 = MasterLock('lock1')
205 l2 = SlaveLock('lock1') # duplicate lock name
206 f1 = BuildFactory([s(Dummy, locks=[])])
207 c['builders'] = [
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
217 lockCfgBad3 = \
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
223 c = {}
224 c['slaves'] = [BuildSlave('bot1', 'pw1')]
225 c['schedulers'] = []
226 l1 = MasterLock('lock1')
227 l2 = MasterLock('lock1') # duplicate lock name
228 f1 = BuildFactory([s(Dummy, locks=[l2])])
229 f2 = BuildFactory([s(Dummy)])
230 c['builders'] = [
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
240 lockCfg1a = \
242 from buildbot.process.factory import BasicBuildFactory
243 from buildbot.locks import MasterLock
244 from buildbot.buildslave import BuildSlave
245 c = {}
246 c['slaves'] = [BuildSlave('bot1', 'pw1')]
247 c['schedulers'] = []
248 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
249 l1 = MasterLock('lock1')
250 l2 = MasterLock('lock2')
251 c['builders'] = [
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
261 lockCfg1b = \
263 from buildbot.process.factory import BasicBuildFactory
264 from buildbot.locks import MasterLock
265 from buildbot.buildslave import BuildSlave
266 c = {}
267 c['slaves'] = [BuildSlave('bot1', 'pw1')]
268 c['schedulers'] = []
269 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
270 l1 = MasterLock('lock1')
271 l2 = MasterLock('lock2')
272 c['builders'] = [
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
283 lockCfg2a = \
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
289 c = {}
290 c['slaves'] = [BuildSlave('bot1', 'pw1')]
291 c['schedulers'] = []
292 l1 = MasterLock('lock1')
293 l2 = MasterLock('lock2')
294 f1 = BuildFactory([s(Dummy, locks=[l1,l2])])
295 f2 = BuildFactory([s(Dummy)])
297 c['builders'] = [
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
307 lockCfg2b = \
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
313 c = {}
314 c['slaves'] = [BuildSlave('bot1', 'pw1')]
315 c['schedulers'] = []
316 l1 = MasterLock('lock1')
317 l2 = MasterLock('lock2')
318 f1 = BuildFactory([s(Dummy, locks=[l1])])
319 f2 = BuildFactory([s(Dummy)])
321 c['builders'] = [
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
331 lockCfg2c = \
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
337 c = {}
338 c['slaves'] = [BuildSlave('bot1', 'pw1')]
339 c['schedulers'] = []
340 l1 = MasterLock('lock1')
341 l2 = MasterLock('lock2')
342 f1 = BuildFactory([s(Dummy)])
343 f2 = BuildFactory([s(Dummy)])
345 c['builders'] = [
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
355 schedulersCfg = \
357 from buildbot.scheduler import Scheduler, Dependent
358 from buildbot.process.factory import BasicBuildFactory
359 from buildbot.buildslave import BuildSlave
360 c = {}
361 c['slaves'] = [BuildSlave('bot1', 'pw1')]
362 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
363 b1 = {'name':'builder1', 'slavename':'bot1',
364 'builddir':'workdir', 'factory':f1}
365 c['builders'] = [b1]
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):
375 def setUp(self):
376 self.buildmaster = BuildMaster(".")
378 def failUnlessListsEquivalent(self, list1, list2):
379 l1 = list1[:]
380 l1.sort()
381 l2 = list2[:]
382 l2.sort()
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
389 for child in s:
390 if service.IServiceCollection.providedBy(child):
391 for gc in self.servers(child, types):
392 yield gc
393 if isinstance(child, types):
394 yield child
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)
418 ret = []
419 for e in expected:
420 for have in self.TCPports(svc) + self.UNIXports(svc):
421 if have.args[0] == e[0]:
422 ret.append(have)
423 continue
424 assert(len(ret) == len(expected))
425 return ret
427 def testEmpty(self):
428 self.failUnlessRaises(KeyError, self.buildmaster.loadConfig, "")
430 def testSimple(self):
431 # covers slavePortnum, base checker passwords
432 master = self.buildmaster
433 master.loadChanges()
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
451 master.loadChanges()
453 master.loadConfig(emptyCfg)
454 self.failUnlessEqual(master.slavePortnum, "tcp:9999")
455 ports = self.checkPorts(master, [(9999, pb.PBServerFactory)])
456 p = ports[0]
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
473 master.loadChanges()
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",
486 "bot1": "pw1",
487 "bot2": "pw2"})
488 master.loadConfig(botsCfg)
489 self.failUnlessEqual(master.checker.users,
490 {"change": "changepw",
491 "bot1": "pw1",
492 "bot2": "pw2"})
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",
503 "bot1": "pw1",
504 "bot2": "pw2"})
507 def testChangeSource(self):
508 master = self.buildmaster
509 master.loadChanges()
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)
520 def _check1(res):
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)
530 def _check2(res):
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)
536 return d1
537 d.addCallback(_check1)
539 # make sure we can get rid of the sources too
540 d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg))
542 def _check3(res):
543 self.failUnlessEqual(list(self.buildmaster.change_svc), [])
544 d.addCallback(_check3)
546 return d
548 def testChangeSources(self):
549 # make sure we can accept a list
550 master = self.buildmaster
551 master.loadChanges()
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)
565 def _check1(res):
566 self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 2)
567 s1,s2 = list(self.buildmaster.change_svc)
568 if isinstance(s2, PBChangeSource):
569 s1,s2 = s2,s1
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)
575 return d
577 def testSources(self):
578 # test backwards compatibility. c['sources'] is deprecated.
579 master = self.buildmaster
580 master.loadChanges()
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)
591 def _check1(res):
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)
597 return d
599 def shouldBeFailure(self, res, *expected):
600 self.failUnless(isinstance(res, failure.Failure),
601 "we expected this to fail, not produce %s" % (res,))
602 res.trap(*expected)
603 return None # all is good
605 def testSchedulerErrors(self):
606 master = self.buildmaster
607 master.loadChanges()
608 master.loadConfig(emptyCfg)
609 self.failUnlessEqual(master.allSchedulers(), [])
611 def _shouldBeFailure(res, hint=None):
612 self.shouldBeFailure(res, AssertionError, ValueError)
613 if hint:
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")
682 return d
684 def testSchedulers(self):
685 master = self.buildmaster
686 master.loadChanges()
687 master.loadConfig(emptyCfg)
688 self.failUnlessEqual(master.allSchedulers(), [])
690 d = self.buildmaster.loadConfig(schedulersCfg)
691 d.addCallback(self._testSchedulers_1)
692 return d
694 def _testSchedulers_1(self, res):
695 sch = self.buildmaster.allSchedulers()
696 self.failUnlessEqual(len(sch), 1)
697 s = sch[0]
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)
711 return d
712 def _testSchedulers_2(self, res, newcfg):
713 sch = self.buildmaster.allSchedulers()
714 self.failUnlessEqual(len(sch), 2)
715 s = sch[0]
716 self.failUnless(isinstance(s, scheduler.Scheduler))
717 s = sch[1]
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)
725 return d
726 def _testSchedulers_3(self, res, sch1):
727 sch2 = self.buildmaster.allSchedulers()
728 self.failUnlessEqual(len(sch2), 2)
729 sch1.sort()
730 sch2.sort()
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")
752 f1 = b.buildFactory
753 self.failUnless(isinstance(f1, BasicBuildFactory))
754 steps = f1.steps
755 self.failUnlessEqual(len(steps), 3)
756 self.failUnlessEqual(steps[0], (CVS,
757 {'cvsroot': 'cvsroot',
758 'cvsmodule': 'cvsmodule',
759 'mode': 'clobber'}))
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"]
781 self.failIf(b is b3)
782 # the statusbag remains the same TODO
783 #statusbag3 = master.client_svc.statusbags["builder1"]
784 #self.failUnlessIdentical(statusbag, statusbag3)
786 # adding new builder
787 master.loadConfig(buildersCfg3)
788 self.failUnlessEqual(master.botmaster.builderNames, ["builder1",
789 "builder2"])
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",
798 "builder2"])
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):
833 ircs = {}
834 for irc in self.servers(m, words.IRC):
835 ircs[irc.host] = (irc.nick, irc.channels)
836 self.failUnlessEqual(ircs, expected)
838 def testIRC(self):
839 if not words:
840 raise unittest.SkipTest("Twisted Words package is not installed")
841 master = self.buildmaster
842 master.loadChanges()
843 d = master.loadConfig(emptyCfg)
844 e1 = {}
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))
859 return d
861 def testWebPortnum(self):
862 master = self.buildmaster
863 master.loadChanges()
865 d = master.loadConfig(webCfg1)
866 def _check1(res):
867 ports = self.checkPorts(self.buildmaster,
868 [(9999, pb.PBServerFactory), (9980, Site)])
869 p = ports[1]
870 self.p = p
871 # nothing should be changed
872 d.addCallback(_check1)
874 d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg1))
875 def _check2(res):
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
887 def _check3(p):
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
896 def _check4(p):
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)]))
906 return d
908 def testWebPathname(self):
909 master = self.buildmaster
910 master.loadChanges()
912 d = master.loadConfig(webNameCfg1)
913 def _check1(res):
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
924 def _check2(res):
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))
937 def _check3(res):
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)]))
951 return d
953 def testDebugPassword(self):
954 master = self.buildmaster
956 master.loadConfig(debugPasswordCfg)
957 self.failUnlessEqual(master.checker.users,
958 {"change": "changepw",
959 "debug": "sekrit"})
961 master.loadConfig(debugPasswordCfg)
962 self.failUnlessEqual(master.checker.users,
963 {"change": "changepw",
964 "debug": "sekrit"})
966 master.loadConfig(emptyCfg)
967 self.failUnlessEqual(master.checker.users,
968 {"change": "changepw"})
970 def testLocks(self):
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,
1013 treeStableTimer=30,
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,
1021 treeStableTimer=30,
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):
1039 os.mkdir("test_cf")
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):
1055 self.name = 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)
1069 def stall(res):
1070 d2 = defer.Deferred()
1071 reactor.callLater(0.1, d2.callback, res)
1072 return d2
1073 d.addCallback(stall)
1074 m = self.parent
1075 def finishedStalling(res):
1076 m.targetevents.append(("stop", self.name))
1077 return res
1078 d.addCallback(finishedStalling)
1079 return d
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):
1103 def tearDown(self):
1104 return self.master.stopService()
1106 def testStartService(self):
1107 os.mkdir("test_ss")
1108 self.master = m = BuildMaster("test_ss")
1109 # inhibit the usual read-config-on-startup behavior
1110 m.readConfig = True
1111 m.startService()
1112 d = m.loadConfig(startableEmptyCfg % 0)
1113 d.addCallback(self._testStartService_0)
1114 return d
1116 def _testStartService_0(self, res):
1117 m = self.master
1118 m.targetevents = []
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)
1123 return d
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,
1129 [('start', 'a')])
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)
1134 return d
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
1139 # start the new one
1140 d = self.master.loadConfig(targetCfg2 % self.portnum)
1141 d.addCallback(self._testStartService_3)
1142 return d
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
1150 # down
1151 d = self.master.loadConfig(targetCfg1 % self.portnum)
1152 d.addCallback(self._testStartService_4)
1153 return d
1155 def _testStartService_4(self, res):
1156 self.failUnlessEqual(self.master.targetevents,
1157 [('stop', 'b'), ('start', 'a')])
1159 cfg1 = \
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):
1181 shell_args = {}
1182 if defaults:
1183 shell_args.update({'descriptionDone': None,
1184 'description': None,
1185 'workdir': None,
1186 'logfiles': {},
1188 shell_args.update(kwargs)
1189 self.failUnlessIdentical(factory[0], ShellCommand)
1190 if factory[1] != shell_args:
1191 print
1192 print "factory had:"
1193 for k in sorted(factory[1].keys()):
1194 print k
1195 print "but we were expecting:"
1196 for k in sorted(shell_args.keys()):
1197 print k
1198 self.failUnlessEqual(factory[1], shell_args)
1200 def failUnlessExpectedDarcs(self, factory, **kwargs):
1201 darcs_args = {'workdir': None,
1202 'alwaysUseLatest': False,
1203 'mode': 'update',
1204 'timeout': 1200,
1205 'retry': None,
1206 'baseURL': None,
1207 'defaultBranch': None,
1208 'logfiles': {},
1210 darcs_args.update(kwargs)
1211 self.failUnlessIdentical(factory[0], Darcs)
1212 if factory[1] != darcs_args:
1213 print
1214 print "factory had:"
1215 for k in sorted(factory[1].keys()):
1216 print k
1217 print "but we were expecting:"
1218 for k in sorted(darcs_args.keys()):
1219 print k
1220 self.failUnlessEqual(factory[1], darcs_args)
1222 def testSteps(self):
1223 m = BuildMaster(".")
1224 m.loadConfig(cfg1)
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)
1240 return newstep
1242 def testAllSteps(self):
1243 # make sure that steps can be created from the factories that they
1244 # return
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",
1263 mode="copy"),
1264 source.Monotone("server", "branch"),
1265 transfer.FileUpload("src", "dest"),
1266 transfer.FileDownload("src", "dest"),
1268 try:
1269 self._loop(s)
1270 except:
1271 print "error checking %s" % s
1272 raise