document onlyIfChanged
[buildbot.git] / buildbot / test / test_config.py
blob3160ee369c704dbda49f09c6c618415fd56ca26b
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, \
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 # 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):
382 l1 = list1[:]
383 l1.sort()
384 l2 = list2[:]
385 l2.sort()
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
392 for child in s:
393 if service.IServiceCollection.providedBy(child):
394 for gc in self.servers(child, types):
395 yield gc
396 if isinstance(child, types):
397 yield child
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)
421 ret = []
422 for e in expected:
423 for have in self.TCPports(svc) + self.UNIXports(svc):
424 if have.args[0] == e[0]:
425 ret.append(have)
426 continue
427 assert(len(ret) == len(expected))
428 return ret
430 def testEmpty(self):
431 self.failUnlessRaises(KeyError, self.buildmaster.loadConfig, "")
433 def testSimple(self):
434 # covers slavePortnum, base checker passwords
435 master = self.buildmaster
436 master.loadChanges()
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
454 master.loadChanges()
456 master.loadConfig(emptyCfg)
457 self.failUnlessEqual(master.slavePortnum, "tcp:9999")
458 ports = self.checkPorts(master, [(9999, pb.PBServerFactory)])
459 p = ports[0]
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
476 master.loadChanges()
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",
489 "bot1": "pw1",
490 "bot2": "pw2"})
491 master.loadConfig(botsCfg)
492 self.failUnlessEqual(master.checker.users,
493 {"change": "changepw",
494 "bot1": "pw1",
495 "bot2": "pw2"})
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",
506 "bot1": "pw1",
507 "bot2": "pw2"})
510 def testChangeSource(self):
511 master = self.buildmaster
512 master.loadChanges()
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)
523 def _check1(res):
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)
533 def _check2(res):
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)
539 return d1
540 d.addCallback(_check1)
542 # make sure we can get rid of the sources too
543 d.addCallback(lambda res: self.buildmaster.loadConfig(emptyCfg))
545 def _check3(res):
546 self.failUnlessEqual(list(self.buildmaster.change_svc), [])
547 d.addCallback(_check3)
549 return d
551 def testChangeSources(self):
552 # make sure we can accept a list
553 master = self.buildmaster
554 master.loadChanges()
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)
568 def _check1(res):
569 self.failUnlessEqual(len(list(self.buildmaster.change_svc)), 2)
570 s1,s2 = list(self.buildmaster.change_svc)
571 if isinstance(s2, PBChangeSource):
572 s1,s2 = s2,s1
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)
578 return d
580 def testSources(self):
581 # test backwards compatibility. c['sources'] is deprecated.
582 master = self.buildmaster
583 master.loadChanges()
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)
594 def _check1(res):
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)
600 return d
602 def shouldBeFailure(self, res, *expected):
603 self.failUnless(isinstance(res, failure.Failure),
604 "we expected this to fail, not produce %s" % (res,))
605 res.trap(*expected)
606 return None # all is good
608 def testSchedulerErrors(self):
609 master = self.buildmaster
610 master.loadChanges()
611 master.loadConfig(emptyCfg)
612 self.failUnlessEqual(master.allSchedulers(), [])
614 def _shouldBeFailure(res, hint=None):
615 self.shouldBeFailure(res, AssertionError, ValueError)
616 if hint:
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")
685 return d
687 def testSchedulers(self):
688 master = self.buildmaster
689 master.loadChanges()
690 master.loadConfig(emptyCfg)
691 self.failUnlessEqual(master.allSchedulers(), [])
693 d = self.buildmaster.loadConfig(schedulersCfg)
694 d.addCallback(self._testSchedulers_1)
695 return d
697 def _testSchedulers_1(self, res):
698 sch = self.buildmaster.allSchedulers()
699 self.failUnlessEqual(len(sch), 1)
700 s = sch[0]
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)
714 return d
715 def _testSchedulers_2(self, res, newcfg):
716 sch = self.buildmaster.allSchedulers()
717 self.failUnlessEqual(len(sch), 2)
718 s = sch[0]
719 self.failUnless(isinstance(s, scheduler.Scheduler))
720 s = sch[1]
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)
728 return d
729 def _testSchedulers_3(self, res, sch1):
730 sch2 = self.buildmaster.allSchedulers()
731 self.failUnlessEqual(len(sch2), 2)
732 sch1.sort()
733 sch2.sort()
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")
755 f1 = b.buildFactory
756 self.failUnless(isinstance(f1, BasicBuildFactory))
757 steps = f1.steps
758 self.failUnlessEqual(len(steps), 3)
759 self.failUnlessEqual(steps[0], (CVS,
760 {'cvsroot': 'cvsroot',
761 'cvsmodule': 'cvsmodule',
762 'mode': 'clobber'}))
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"]
784 self.failIf(b is b3)
785 # the statusbag remains the same TODO
786 #statusbag3 = master.client_svc.statusbags["builder1"]
787 #self.failUnlessIdentical(statusbag, statusbag3)
789 # adding new builder
790 master.loadConfig(buildersCfg3)
791 self.failUnlessEqual(master.botmaster.builderNames, ["builder1",
792 "builder2"])
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",
801 "builder2"])
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):
836 ircs = {}
837 for irc in self.servers(m, words.IRC):
838 ircs[irc.host] = (irc.nick, irc.channels)
839 self.failUnlessEqual(ircs, expected)
841 def testIRC(self):
842 if not words:
843 raise unittest.SkipTest("Twisted Words package is not installed")
844 master = self.buildmaster
845 master.loadChanges()
846 d = master.loadConfig(emptyCfg)
847 e1 = {}
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))
862 return d
864 def testWebPortnum(self):
865 master = self.buildmaster
866 master.loadChanges()
868 d = master.loadConfig(webCfg1)
869 def _check1(res):
870 ports = self.checkPorts(self.buildmaster,
871 [(9999, pb.PBServerFactory), (9980, Site)])
872 p = ports[1]
873 self.p = p
874 # nothing should be changed
875 d.addCallback(_check1)
877 d.addCallback(lambda res: self.buildmaster.loadConfig(webCfg1))
878 def _check2(res):
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
890 def _check3(p):
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
899 def _check4(p):
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)]))
909 return d
911 def testWebPathname(self):
912 master = self.buildmaster
913 master.loadChanges()
915 d = master.loadConfig(webNameCfg1)
916 def _check1(res):
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
927 def _check2(res):
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))
940 def _check3(res):
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)]))
954 return d
956 def testDebugPassword(self):
957 master = self.buildmaster
959 master.loadConfig(debugPasswordCfg)
960 self.failUnlessEqual(master.checker.users,
961 {"change": "changepw",
962 "debug": "sekrit"})
964 master.loadConfig(debugPasswordCfg)
965 self.failUnlessEqual(master.checker.users,
966 {"change": "changepw",
967 "debug": "sekrit"})
969 master.loadConfig(emptyCfg)
970 self.failUnlessEqual(master.checker.users,
971 {"change": "changepw"})
973 def testLocks(self):
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,
1016 treeStableTimer=30,
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,
1024 treeStableTimer=30,
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):
1042 os.mkdir("test_cf")
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):
1058 self.name = 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)
1072 def stall(res):
1073 d2 = defer.Deferred()
1074 reactor.callLater(0.1, d2.callback, res)
1075 return d2
1076 d.addCallback(stall)
1077 m = self.parent
1078 def finishedStalling(res):
1079 m.targetevents.append(("stop", self.name))
1080 return res
1081 d.addCallback(finishedStalling)
1082 return d
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):
1106 def tearDown(self):
1107 return self.master.stopService()
1109 def testStartService(self):
1110 os.mkdir("test_ss")
1111 self.master = m = BuildMaster("test_ss")
1112 # inhibit the usual read-config-on-startup behavior
1113 m.readConfig = True
1114 m.startService()
1115 d = m.loadConfig(startableEmptyCfg % 0)
1116 d.addCallback(self._testStartService_0)
1117 return d
1119 def _testStartService_0(self, res):
1120 m = self.master
1121 m.targetevents = []
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)
1126 return d
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,
1132 [('start', 'a')])
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)
1137 return d
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
1142 # start the new one
1143 d = self.master.loadConfig(targetCfg2 % self.portnum)
1144 d.addCallback(self._testStartService_3)
1145 return d
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
1153 # down
1154 d = self.master.loadConfig(targetCfg1 % self.portnum)
1155 d.addCallback(self._testStartService_4)
1156 return d
1158 def _testStartService_4(self, res):
1159 self.failUnlessEqual(self.master.targetevents,
1160 [('stop', 'b'), ('start', 'a')])
1162 cfg1 = \
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):
1184 shell_args = {}
1185 if defaults:
1186 shell_args.update({'descriptionDone': None,
1187 'description': None,
1188 'workdir': None,
1189 'logfiles': {},
1191 shell_args.update(kwargs)
1192 self.failUnlessIdentical(factory[0], ShellCommand)
1193 if factory[1] != shell_args:
1194 print
1195 print "factory had:"
1196 for k in sorted(factory[1].keys()):
1197 print k
1198 print "but we were expecting:"
1199 for k in sorted(shell_args.keys()):
1200 print k
1201 self.failUnlessEqual(factory[1], shell_args)
1203 def failUnlessExpectedDarcs(self, factory, **kwargs):
1204 darcs_args = {'workdir': None,
1205 'alwaysUseLatest': False,
1206 'mode': 'update',
1207 'timeout': 1200,
1208 'retry': None,
1209 'baseURL': None,
1210 'defaultBranch': None,
1211 'logfiles': {},
1213 darcs_args.update(kwargs)
1214 self.failUnlessIdentical(factory[0], Darcs)
1215 if factory[1] != darcs_args:
1216 print
1217 print "factory had:"
1218 for k in sorted(factory[1].keys()):
1219 print k
1220 print "but we were expecting:"
1221 for k in sorted(darcs_args.keys()):
1222 print k
1223 self.failUnlessEqual(factory[1], darcs_args)
1225 def testSteps(self):
1226 m = BuildMaster(".")
1227 m.loadConfig(cfg1)
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)
1243 return newstep
1245 def testAllSteps(self):
1246 # make sure that steps can be created from the factories that they
1247 # return
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",
1266 mode="copy"),
1267 source.Monotone("server", "branch"),
1268 transfer.FileUpload("src", "dest"),
1269 transfer.FileDownload("src", "dest"),
1271 try:
1272 self._loop(s)
1273 except:
1274 print "error checking %s" % s
1275 raise