1 # -*- test-case-name: buildbot.test.test_run -*-
3 from twisted
.trial
import unittest
4 from twisted
.internet
import reactor
, defer
5 from twisted
.python
import log
6 import sys
, os
, os
.path
, shutil
, time
, errno
7 #log.startLogging(sys.stderr)
9 from buildbot
import master
, interfaces
10 from buildbot
.sourcestamp
import SourceStamp
11 from buildbot
.slave
import bot
12 from buildbot
.changes
import changes
13 from buildbot
.status
import builder
14 from buildbot
.process
.base
import BuildRequest
15 from buildbot
.twcompat
import maybeWait
17 from buildbot
.test
.runutils
import RunMixin
20 from buildbot.process import factory, step
23 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None)
25 f2 = factory.BuildFactory([
26 s(step.Dummy, timeout=1),
27 s(step.RemoteDummy, timeout=2),
30 BuildmasterConfig = c = {}
31 c['bots'] = [['bot1', 'sekrit']]
35 c['builders'].append({'name':'quick', 'slavename':'bot1',
36 'builddir': 'quickdir', 'factory': f1})
40 config_run
= config_base
+ """
41 from buildbot.scheduler import Scheduler
42 c['schedulers'] = [Scheduler('quick', None, 120, ['quick'])]
45 config_2
= config_base
+ """
46 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
47 'builddir': 'dummy1', 'factory': f2},
48 {'name': 'testdummy', 'slavename': 'bot1',
49 'builddir': 'dummy2', 'factory': f2, 'category': 'test'}]
52 config_3
= config_2
+ """
53 c['builders'].append({'name': 'adummy', 'slavename': 'bot1',
54 'builddir': 'adummy3', 'factory': f2})
55 c['builders'].append({'name': 'bdummy', 'slavename': 'bot1',
56 'builddir': 'adummy4', 'factory': f2,
60 config_4
= config_base
+ """
61 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
62 'builddir': 'dummy', 'factory': f2}]
65 config_4_newbasedir
= config_4
+ """
66 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1',
67 'builddir': 'dummy2', 'factory': f2}]
70 config_4_newbuilder
= config_4_newbasedir
+ """
71 c['builders'].append({'name': 'dummy2', 'slavename': 'bot1',
72 'builddir': 'dummy23', 'factory': f2})
75 class Run(unittest
.TestCase
):
78 shutil
.rmtree(d
, ignore_errors
=1)
80 # stupid 2.2 appears to ignore ignore_errors
81 if e
.errno
!= errno
.ENOENT
:
85 self
.rmtree("basedir")
87 m
= master
.BuildMaster("basedir")
88 m
.loadConfig(config_run
)
92 c
= changes
.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff")
94 # verify that the Scheduler is now waiting
95 s
= m
.allSchedulers()[0]
96 self
.failUnless(s
.timer
)
97 # halting the service will also stop the timer
98 d
= defer
.maybeDeferred(m
.stopService
)
101 class Ping(RunMixin
, unittest
.TestCase
):
103 self
.master
.loadConfig(config_2
)
104 self
.master
.readConfig
= True
105 self
.master
.startService()
107 d
= self
.connectSlave()
108 d
.addCallback(self
._testPing
_1)
111 def _testPing_1(self
, res
):
112 d
= interfaces
.IControl(self
.master
).getBuilder("dummy").ping(1)
113 d
.addCallback(self
._testPing
_2)
116 def _testPing_2(self
, res
):
119 class BuilderNames(unittest
.TestCase
):
121 def testGetBuilderNames(self
):
123 m
= master
.BuildMaster("bnames")
126 m
.loadConfig(config_3
)
129 self
.failUnlessEqual(s
.getBuilderNames(),
130 ["dummy", "testdummy", "adummy", "bdummy"])
131 self
.failUnlessEqual(s
.getBuilderNames(categories
=['test']),
132 ["testdummy", "bdummy"])
134 class Disconnect(RunMixin
, unittest
.TestCase
):
139 # verify that disconnecting the slave during a build properly
140 # terminates the build
145 m
.loadConfig(config_2
)
149 self
.failUnlessEqual(s
.getBuilderNames(), ["dummy", "testdummy"])
150 self
.s1
= s1
= s
.getBuilder("dummy")
151 self
.failUnlessEqual(s1
.getName(), "dummy")
152 self
.failUnlessEqual(s1
.getState(), ("offline", []))
153 self
.failUnlessEqual(s1
.getCurrentBuilds(), [])
154 self
.failUnlessEqual(s1
.getLastFinishedBuild(), None)
155 self
.failUnlessEqual(s1
.getBuild(-1), None)
157 d
= self
.connectSlave()
158 d
.addCallback(self
._disconnectSetup
_1)
161 def _disconnectSetup_1(self
, res
):
162 self
.failUnlessEqual(self
.s1
.getState(), ("idle", []))
165 def verifyDisconnect(self
, bs
):
166 self
.failUnless(bs
.isFinished())
168 step1
= bs
.getSteps()[0]
169 self
.failUnlessEqual(step1
.getText(), ["delay", "interrupted"])
170 self
.failUnlessEqual(step1
.getResults()[0], builder
.FAILURE
)
172 self
.failUnlessEqual(bs
.getResults(), builder
.FAILURE
)
174 def verifyDisconnect2(self
, bs
):
175 self
.failUnless(bs
.isFinished())
177 step1
= bs
.getSteps()[1]
178 self
.failUnlessEqual(step1
.getText(), ["remote", "delay", "2 secs",
179 "failed", "slave", "lost"])
180 self
.failUnlessEqual(step1
.getResults()[0], builder
.FAILURE
)
182 self
.failUnlessEqual(bs
.getResults(), builder
.FAILURE
)
186 # disconnect the slave before the build starts
187 d
= self
.shutdownAllSlaves() # dies before it gets started
188 d
.addCallback(self
._testIdle
1_1)
190 def _testIdle1_1(self
, res
):
191 # trying to force a build now will cause an error. Regular builds
192 # just wait for the slave to re-appear, but forced builds that
193 # cannot be run right away trigger NoSlaveErrors
194 fb
= self
.control
.getBuilder("dummy").forceBuild
195 self
.failUnlessRaises(interfaces
.NoSlaveError
,
196 fb
, None, "forced build")
199 # now suppose the slave goes missing
200 self
.slaves
['bot1'].bf
.continueTrying
= 0
201 self
.disappearSlave()
203 # forcing a build will work: the build detect that the slave is no
204 # longer available and will be re-queued. Wait 5 seconds, then check
205 # to make sure the build is still in the 'waiting for a slave' queue.
206 self
.control
.getBuilder("dummy").original
.START_BUILD_TIMEOUT
= 1
207 req
= BuildRequest("forced build", SourceStamp())
208 self
.failUnlessEqual(req
.startCount
, 0)
209 self
.control
.getBuilder("dummy").requestBuild(req
)
210 # this should ping the slave, which doesn't respond, and then give up
211 # after a second. The BuildRequest will be re-queued, and its
212 # .startCount will be incremented.
214 d
.addCallback(self
._testIdle
2_1, req
)
215 reactor
.callLater(3, d
.callback
, None)
216 return maybeWait(d
, 5)
217 testIdle2
.timeout
= 5
219 def _testIdle2_1(self
, res
, req
):
220 self
.failUnlessEqual(req
.startCount
, 1)
221 cancelled
= req
.cancel()
222 self
.failUnless(cancelled
)
225 def testBuild1(self
):
226 # this next sequence is timing-dependent. The dummy build takes at
227 # least 3 seconds to complete, and this batch of commands must
228 # complete within that time.
230 d
= self
.control
.getBuilder("dummy").forceBuild(None, "forced build")
231 d
.addCallback(self
._testBuild
1_1)
234 def _testBuild1_1(self
, bc
):
236 # now kill the slave before it gets to start the first step
237 d
= self
.shutdownAllSlaves() # dies before it gets started
238 d
.addCallback(self
._testBuild
1_2, bs
)
239 return d
# TODO: this used to have a 5-second timeout
241 def _testBuild1_2(self
, res
, bs
):
242 # now examine the just-stopped build and make sure it is really
243 # stopped. This is checking for bugs in which the slave-detach gets
244 # missed or causes an exception which prevents the build from being
245 # marked as "finished due to an error".
246 d
= bs
.waitUntilFinished()
247 d2
= self
.master
.botmaster
.waitUntilBuilderDetached("dummy")
248 dl
= defer
.DeferredList([d
, d2
])
249 dl
.addCallback(self
._testBuild
1_3, bs
)
250 return dl
# TODO: this had a 5-second timeout too
252 def _testBuild1_3(self
, res
, bs
):
253 self
.failUnlessEqual(self
.s1
.getState()[0], "offline")
254 self
.verifyDisconnect(bs
)
257 def testBuild2(self
):
258 # this next sequence is timing-dependent
259 d
= self
.control
.getBuilder("dummy").forceBuild(None, "forced build")
260 d
.addCallback(self
._testBuild
1_1)
261 return maybeWait(d
, 30)
262 testBuild2
.timeout
= 30
264 def _testBuild1_1(self
, bc
):
266 # shutdown the slave while it's running the first step
267 reactor
.callLater(0.5, self
.shutdownAllSlaves
)
269 d
= bs
.waitUntilFinished()
270 d
.addCallback(self
._testBuild
2_2, bs
)
273 def _testBuild2_2(self
, res
, bs
):
274 # we hit here when the build has finished. The builder is still being
275 # torn down, however, so spin for another second to allow the
276 # callLater(0) in Builder.detached to fire.
278 reactor
.callLater(1, d
.callback
, None)
279 d
.addCallback(self
._testBuild
2_3, bs
)
282 def _testBuild2_3(self
, res
, bs
):
283 self
.failUnlessEqual(self
.s1
.getState()[0], "offline")
284 self
.verifyDisconnect(bs
)
287 def testBuild3(self
):
288 # this next sequence is timing-dependent
289 d
= self
.control
.getBuilder("dummy").forceBuild(None, "forced build")
290 d
.addCallback(self
._testBuild
3_1)
291 return maybeWait(d
, 30)
292 testBuild3
.timeout
= 30
294 def _testBuild3_1(self
, bc
):
296 # kill the slave while it's running the first step
297 reactor
.callLater(0.5, self
.killSlave
)
298 d
= bs
.waitUntilFinished()
299 d
.addCallback(self
._testBuild
3_2, bs
)
302 def _testBuild3_2(self
, res
, bs
):
303 # the builder is still being torn down, so give it another second
305 reactor
.callLater(1, d
.callback
, None)
306 d
.addCallback(self
._testBuild
3_3, bs
)
309 def _testBuild3_3(self
, res
, bs
):
310 self
.failUnlessEqual(self
.s1
.getState()[0], "offline")
311 self
.verifyDisconnect(bs
)
314 def testBuild4(self
):
315 # this next sequence is timing-dependent
316 d
= self
.control
.getBuilder("dummy").forceBuild(None, "forced build")
317 d
.addCallback(self
._testBuild
4_1)
318 return maybeWait(d
, 30)
319 testBuild4
.timeout
= 30
321 def _testBuild4_1(self
, bc
):
323 # kill the slave while it's running the second (remote) step
324 reactor
.callLater(1.5, self
.killSlave
)
325 d
= bs
.waitUntilFinished()
326 d
.addCallback(self
._testBuild
4_2, bs
)
329 def _testBuild4_2(self
, res
, bs
):
330 # at this point, the slave is in the process of being removed, so it
331 # could either be 'idle' or 'offline'. I think there is a
332 # reactor.callLater(0) standing between here and the offline state.
333 #reactor.iterate() # TODO: remove the need for this
335 self
.failUnlessEqual(self
.s1
.getState()[0], "offline")
336 self
.verifyDisconnect2(bs
)
339 def testInterrupt(self
):
340 # this next sequence is timing-dependent
341 d
= self
.control
.getBuilder("dummy").forceBuild(None, "forced build")
342 d
.addCallback(self
._testInterrupt
_1)
343 return maybeWait(d
, 30)
344 testInterrupt
.timeout
= 30
346 def _testInterrupt_1(self
, bc
):
348 # halt the build while it's running the first step
349 reactor
.callLater(0.5, bc
.stopBuild
, "bang go splat")
350 d
= bs
.waitUntilFinished()
351 d
.addCallback(self
._testInterrupt
_2, bs
)
354 def _testInterrupt_2(self
, res
, bs
):
355 self
.verifyDisconnect(bs
)
358 def testDisappear(self
):
359 bc
= self
.control
.getBuilder("dummy")
361 # ping should succeed
363 d
.addCallback(self
._testDisappear
_1, bc
)
366 def _testDisappear_1(self
, res
, bc
):
367 self
.failUnlessEqual(res
, True)
369 # now, before any build is run, make the slave disappear
370 self
.slaves
['bot1'].bf
.continueTrying
= 0
371 self
.disappearSlave()
373 # at this point, a ping to the slave should timeout
375 d
.addCallback(self
. _testDisappear_2
)
377 def _testDisappear_2(self
, res
):
378 self
.failUnlessEqual(res
, False)
380 def testDuplicate(self
):
381 bc
= self
.control
.getBuilder("dummy")
382 bs
= self
.status
.getBuilder("dummy")
383 ss
= bs
.getSlaves()[0]
385 self
.failUnless(ss
.isConnected())
386 self
.failUnlessEqual(ss
.getAdmin(), "one")
388 # now, before any build is run, make the first slave disappear
389 self
.slaves
['bot1'].bf
.continueTrying
= 0
390 self
.disappearSlave()
392 d
= self
.master
.botmaster
.waitUntilBuilderDetached("dummy")
393 # now let the new slave take over
395 d
.addCallback(self
._testDuplicate
_1, ss
)
396 return maybeWait(d
, 2)
397 testDuplicate
.timeout
= 5
399 def _testDuplicate_1(self
, res
, ss
):
400 d
= self
.master
.botmaster
.waitUntilBuilderAttached("dummy")
401 d
.addCallback(self
._testDuplicate
_2, ss
)
404 def _testDuplicate_2(self
, res
, ss
):
405 self
.failUnless(ss
.isConnected())
406 self
.failUnlessEqual(ss
.getAdmin(), "two")
409 class Disconnect2(RunMixin
, unittest
.TestCase
):
413 # verify that disconnecting the slave during a build properly
414 # terminates the build
419 m
.loadConfig(config_2
)
423 self
.failUnlessEqual(s
.getBuilderNames(), ["dummy", "testdummy"])
424 self
.s1
= s1
= s
.getBuilder("dummy")
425 self
.failUnlessEqual(s1
.getName(), "dummy")
426 self
.failUnlessEqual(s1
.getState(), ("offline", []))
427 self
.failUnlessEqual(s1
.getCurrentBuilds(), [])
428 self
.failUnlessEqual(s1
.getLastFinishedBuild(), None)
429 self
.failUnlessEqual(s1
.getBuild(-1), None)
431 d
= self
.connectSlaveFastTimeout()
432 d
.addCallback(self
._setup
_disconnect
2_1)
435 def _setup_disconnect2_1(self
, res
):
436 self
.failUnlessEqual(self
.s1
.getState(), ("idle", []))
439 def testSlaveTimeout(self
):
440 # now suppose the slave goes missing. We want to find out when it
441 # creates a new Broker, so we reach inside and mark it with the
442 # well-known sigil of impending messy death.
443 bd
= self
.slaves
['bot1'].getServiceNamed("bot").builders
["dummy"]
444 broker
= bd
.remote
.broker
447 # make sure the keepalives will keep the connection up
449 reactor
.callLater(5, d
.callback
, None)
450 d
.addCallback(self
._testSlaveTimeout
_1)
451 return maybeWait(d
, 20)
452 testSlaveTimeout
.timeout
= 20
454 def _testSlaveTimeout_1(self
, res
):
455 bd
= self
.slaves
['bot1'].getServiceNamed("bot").builders
["dummy"]
456 if not bd
.remote
or not hasattr(bd
.remote
.broker
, "redshirt"):
457 self
.fail("slave disconnected when it shouldn't have")
459 d
= self
.master
.botmaster
.waitUntilBuilderDetached("dummy")
460 # whoops! how careless of me.
461 self
.disappearSlave()
462 # the slave will realize the connection is lost within 2 seconds, and
464 d
.addCallback(self
._testSlaveTimeout
_2)
467 def _testSlaveTimeout_2(self
, res
):
468 # the ReconnectingPBClientFactory will attempt a reconnect in two
470 d
= self
.master
.botmaster
.waitUntilBuilderAttached("dummy")
471 d
.addCallback(self
._testSlaveTimeout
_3)
474 def _testSlaveTimeout_3(self
, res
):
475 # make sure it is a new connection (i.e. a new Broker)
476 bd
= self
.slaves
['bot1'].getServiceNamed("bot").builders
["dummy"]
477 self
.failUnless(bd
.remote
, "hey, slave isn't really connected")
478 self
.failIf(hasattr(bd
.remote
.broker
, "redshirt"),
479 "hey, slave's Broker is still marked for death")
482 class Basedir(RunMixin
, unittest
.TestCase
):
483 def testChangeBuilddir(self
):
485 m
.loadConfig(config_4
)
489 d
= self
.connectSlave()
490 d
.addCallback(self
._testChangeBuilddir
_1)
493 def _testChangeBuilddir_1(self
, res
):
494 self
.bot
= bot
= self
.slaves
['bot1'].bot
495 self
.builder
= builder
= bot
.builders
.get("dummy")
496 self
.failUnless(builder
)
497 self
.failUnlessEqual(builder
.builddir
, "dummy")
498 self
.failUnlessEqual(builder
.basedir
,
499 os
.path
.join("slavebase-bot1", "dummy"))
501 d
= self
.master
.loadConfig(config_4_newbasedir
)
502 d
.addCallback(self
._testChangeBuilddir
_2)
505 def _testChangeBuilddir_2(self
, res
):
507 # this causes the builder to be replaced
508 self
.failIfIdentical(self
.builder
, bot
.builders
.get("dummy"))
509 builder
= bot
.builders
.get("dummy")
510 self
.failUnless(builder
)
511 # the basedir should be updated
512 self
.failUnlessEqual(builder
.builddir
, "dummy2")
513 self
.failUnlessEqual(builder
.basedir
,
514 os
.path
.join("slavebase-bot1", "dummy2"))
516 # add a new builder, which causes the basedir list to be reloaded
517 d
= self
.master
.loadConfig(config_4_newbuilder
)
520 # TODO: test everything, from Change submission to Scheduler to Build to
521 # Status. Use all the status types. Specifically I want to catch recurrences
522 # of the bug where I forgot to make Waterfall inherit from StatusReceiver
523 # such that buildSetSubmitted failed.