1 # -*- test-case-name: buildbot.test.test_slaves -*-
3 from twisted
.trial
import unittest
4 from twisted
.internet
import defer
, reactor
5 from twisted
.python
import log
, runtime
, failure
7 from buildbot
.buildslave
import AbstractLatentBuildSlave
8 from buildbot
.test
.runutils
import RunMixin
9 from buildbot
.sourcestamp
import SourceStamp
10 from buildbot
.process
.base
import BuildRequest
11 from buildbot
.status
.builder
import SUCCESS
12 from buildbot
.status
import mail
13 from buildbot
.slave
import bot
16 from buildbot.process import factory
17 from buildbot.steps import dummy
18 from buildbot.buildslave import BuildSlave
21 BuildmasterConfig = c = {}
22 c['slaves'] = [BuildSlave('bot1', 'sekrit'), BuildSlave('bot2', 'sekrit'),
23 BuildSlave('bot3', 'sekrit')]
28 f1 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=1)])
29 f2 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=2)])
30 f3 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=3)])
31 f4 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=5)])
34 {'name': 'b1', 'slavenames': ['bot1','bot2','bot3'],
35 'builddir': 'b1', 'factory': f1},
39 config_2
= config_1
+ """
42 {'name': 'b1', 'slavenames': ['bot1','bot2','bot3'],
43 'builddir': 'b1', 'factory': f2},
48 config_busyness
= config_1
+ """
50 {'name': 'b1', 'slavenames': ['bot1'],
51 'builddir': 'b1', 'factory': f3},
52 {'name': 'b2', 'slavenames': ['bot1'],
53 'builddir': 'b2', 'factory': f4},
57 class Slave(RunMixin
, unittest
.TestCase
):
61 self
.master
.loadConfig(config_1
)
62 self
.master
.startService()
63 d
= self
.connectSlave(["b1"])
64 d
.addCallback(lambda res
: self
.connectSlave(["b1"], "bot2"))
67 def doBuild(self
, buildername
):
68 br
= BuildRequest("forced", SourceStamp(), 'test_builder')
69 d
= br
.waitUntilFinished()
70 self
.control
.getBuilder(buildername
).requestBuild(br
)
73 def testSequence(self
):
74 # make sure both slaves appear in the list.
75 attached_slaves
= [c
for c
in self
.master
.botmaster
.slaves
.values()
77 self
.failUnlessEqual(len(attached_slaves
), 2)
78 b
= self
.master
.botmaster
.builders
["b1"]
79 self
.failUnlessEqual(len(b
.slaves
), 2)
81 # since the current scheduling algorithm is simple and does not
82 # rotate or attempt any sort of load-balancing, two builds in
83 # sequence should both use the first slave. This may change later if
84 # we move to a more sophisticated scheme.
85 b
.CHOOSE_SLAVES_RANDOMLY
= False
87 d
= self
.doBuild("b1")
88 d
.addCallback(self
._testSequence
_1)
90 def _testSequence_1(self
, res
):
91 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
92 self
.failUnlessEqual(res
.getSlavename(), "bot1")
94 d
= self
.doBuild("b1")
95 d
.addCallback(self
._testSequence
_2)
97 def _testSequence_2(self
, res
):
98 self
.failUnlessEqual(res
.getSlavename(), "bot1")
101 def testSimultaneous(self
):
102 # make sure we can actually run two builds at the same time
103 d1
= self
.doBuild("b1")
104 d2
= self
.doBuild("b1")
105 d1
.addCallback(self
._testSimultaneous
_1, d2
)
107 def _testSimultaneous_1(self
, res
, d2
):
108 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
109 b1_slavename
= res
.getSlavename()
110 d2
.addCallback(self
._testSimultaneous
_2, b1_slavename
)
112 def _testSimultaneous_2(self
, res
, b1_slavename
):
113 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
114 b2_slavename
= res
.getSlavename()
115 # make sure the two builds were run by different slaves
116 slavenames
= [b1_slavename
, b2_slavename
]
118 self
.failUnlessEqual(slavenames
, ["bot1", "bot2"])
120 def testFallback1(self
):
121 # detach the first slave, verify that a build is run using the second
123 d
= self
.shutdownSlave("bot1", "b1")
124 d
.addCallback(self
._testFallback
1_1)
126 def _testFallback1_1(self
, res
):
127 attached_slaves
= [c
for c
in self
.master
.botmaster
.slaves
.values()
129 self
.failUnlessEqual(len(attached_slaves
), 1)
130 self
.failUnlessEqual(len(self
.master
.botmaster
.builders
["b1"].slaves
),
132 d
= self
.doBuild("b1")
133 d
.addCallback(self
._testFallback
1_2)
135 def _testFallback1_2(self
, res
):
136 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
137 self
.failUnlessEqual(res
.getSlavename(), "bot2")
139 def testFallback2(self
):
140 # Disable the first slave, so that a slaveping will timeout. Then
141 # start a build, and verify that the non-failing (second) one is
142 # claimed for the build, and that the failing one is removed from the
145 b1
= self
.master
.botmaster
.builders
["b1"]
146 # reduce the ping time so we'll failover faster
147 b1
.START_BUILD_TIMEOUT
= 1
148 assert b1
.CHOOSE_SLAVES_RANDOMLY
149 b1
.CHOOSE_SLAVES_RANDOMLY
= False
150 self
.disappearSlave("bot1", "b1", allowReconnect
=False)
151 d
= self
.doBuild("b1")
152 d
.addCallback(self
._testFallback
2_1)
154 def _testFallback2_1(self
, res
):
155 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
156 self
.failUnlessEqual(res
.getSlavename(), "bot2")
157 b1slaves
= self
.master
.botmaster
.builders
["b1"].slaves
158 self
.failUnlessEqual(len(b1slaves
), 1, "whoops: %s" % (b1slaves
,))
159 self
.failUnlessEqual(b1slaves
[0].slave
.slavename
, "bot2")
162 def notFinished(self
, brs
):
164 builds
= brs
.getBuilds()
165 self
.failIf(len(builds
) > 1)
167 self
.failIf(builds
[0].isFinished())
169 def testDontClaimPingingSlave(self
):
170 # have two slaves connect for the same builder. Do something to the
171 # first one so that slavepings are delayed (but do not fail
174 self
.slaves
['bot1'].debugOpts
["stallPings"] = (10, timers
)
175 br
= BuildRequest("forced", SourceStamp(), 'test_builder')
176 d1
= br
.waitUntilFinished()
177 self
.master
.botmaster
.builders
["b1"].CHOOSE_SLAVES_RANDOMLY
= False
178 self
.control
.getBuilder("b1").requestBuild(br
)
179 s1
= br
.status
# this is a BuildRequestStatus
180 # give it a chance to start pinging
181 d2
= defer
.Deferred()
182 d2
.addCallback(self
._testDontClaimPingingSlave
_1, d1
, s1
, timers
)
183 reactor
.callLater(1, d2
.callback
, None)
185 def _testDontClaimPingingSlave_1(self
, res
, d1
, s1
, timers
):
186 # now the first build is running (waiting on the ping), so start the
187 # second build. This should claim the second slave, not the first,
188 # because the first is busy doing the ping.
190 d3
= self
.doBuild("b1")
191 d3
.addCallback(self
._testDontClaimPingingSlave
_2, d1
, s1
, timers
)
193 def _testDontClaimPingingSlave_2(self
, res
, d1
, s1
, timers
):
194 self
.failUnlessEqual(res
.getSlavename(), "bot2")
196 # now let the ping complete
197 self
.failUnlessEqual(len(timers
), 1)
199 d1
.addCallback(self
._testDontClaimPingingSlave
_3)
201 def _testDontClaimPingingSlave_3(self
, res
):
202 self
.failUnlessEqual(res
.getSlavename(), "bot1")
204 class FakeLatentBuildSlave(AbstractLatentBuildSlave
):
209 stopped
= testing_substantiation_timeout
= False
211 def start_instance(self
):
212 # responsible for starting instance that will try to connect with
214 # simulate having to do some work.
216 if not self
.testing_substantiation_timeout
:
217 reactor
.callLater(0, self
._start
_instance
, d
)
220 def _start_instance(self
, d
):
221 self
.testcase
.connectOneSlave(self
.slavename
)
222 d
.callback(self
.start_message
)
224 def stop_instance(self
, fast
=False):
225 # responsible for shutting down instance
226 # we're going to emulate dropping off the net.
228 # simulate this by replacing the slave Broker's .dataReceived method
229 # with one that just throws away all data.
230 self
.fast_stop_request
= fast
231 if self
.slavename
not in self
.testcase
.slaves
:
232 assert self
.testing_substantiation_timeout
234 return defer
.succeed(None)
236 if self
.stop_wait
is None:
237 self
._stop
_instance
(d
)
239 reactor
.callLater(self
.stop_wait
, self
._stop
_instance
, d
)
242 def _stop_instance(self
, d
):
244 s
= self
.testcase
.slaves
.pop(self
.slavename
)
250 bot
= s
.getServiceNamed("bot")
251 for buildername
in self
.slavebuilders
:
252 remote
= bot
.builders
[buildername
].remote
255 broker
= remote
.broker
256 broker
.dataReceived
= discard
# seal its ears
257 broker
.transport
.write
= discard
# and take away its voice
258 # also discourage it from reconnecting once the connection goes away
259 s
.bf
.continueTrying
= False
260 # stop the service for cleanliness
265 from buildbot.process import factory
266 from buildbot.steps import dummy
267 from buildbot.buildslave import BuildSlave
268 from buildbot.test.test_slaves import FakeLatentBuildSlave
271 BuildmasterConfig = c = {}
272 c['slaves'] = [FakeLatentBuildSlave('bot1', 'sekrit',
274 FakeLatentBuildSlave('bot2', 'sekrit',
276 BuildSlave('bot3', 'sekrit')]
278 c['slavePortnum'] = 0
281 f1 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=1)])
282 f2 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=2)])
283 f3 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=3)])
284 f4 = factory.BuildFactory([s(dummy.RemoteDummy, timeout=5)])
287 {'name': 'b1', 'slavenames': ['bot1','bot2','bot3'],
288 'builddir': 'b1', 'factory': f1},
293 class LatentSlave(RunMixin
, unittest
.TestCase
):
297 #import twisted.internet.base
298 #twisted.internet.base.DelayedCall.debug = True
301 self
.master
.loadConfig(latent_config
)
302 self
.master
.startService()
303 self
.bot1
= self
.master
.botmaster
.slaves
['bot1']
304 self
.bot2
= self
.master
.botmaster
.slaves
['bot2']
305 self
.bot3
= self
.master
.botmaster
.slaves
['bot3']
306 self
.bot1
.testcase
= self
307 self
.bot2
.testcase
= self
308 self
.b1
= self
.master
.botmaster
.builders
['b1']
310 def doBuild(self
, buildername
):
311 br
= BuildRequest("forced", SourceStamp())
312 d
= br
.waitUntilFinished()
313 self
.control
.getBuilder(buildername
).requestBuild(br
)
316 def testSequence(self
):
317 # make sure both slaves appear in the builder. This is automatically,
318 # without any attaching.
319 self
.assertEqual(len(self
.b1
.slaves
), 2)
320 self
.assertEqual(sorted(sb
.slave
.slavename
for sb
in self
.b1
.slaves
),
322 # These have not substantiated
323 self
.assertEqual([sb
.slave
.substantiated
for sb
in self
.b1
.slaves
],
325 self
.assertEqual([sb
.slave
.slave
for sb
in self
.b1
.slaves
],
327 # we can mix and match latent slaves and normal slaves. ATM, they
328 # are treated identically in terms of selecting slaves.
329 d
= self
.connectSlave(builders
=['b1'], slavename
='bot3')
330 d
.addCallback(self
._testSequence
_1)
332 def _testSequence_1(self
, res
):
333 # now we have all three slaves. Two are latent slaves, and one is a
335 self
.assertEqual(sorted(sb
.slave
.slavename
for sb
in self
.b1
.slaves
),
336 ['bot1', 'bot2', 'bot3'])
337 # Now it's time to try a build on one of the latent slaves,
339 # since the current scheduling algorithm is simple and does not
340 # rotate or attempt any sort of load-balancing, two builds in
341 # sequence should both use the first slave. This may change later if
342 # we move to a more sophisticated scheme.
343 self
.b1
.CHOOSE_SLAVES_RANDOMLY
= False
345 self
.build_deferred
= self
.doBuild("b1")
346 # now there's an event waiting for the slave to substantiate.
347 e
= self
.b1
.builder_status
.getEvent(-1)
348 self
.assertEqual(e
.text
, ['substantiating'])
349 self
.assertEqual(e
.color
, 'yellow')
350 # the substantiation_deferred is an internal stash of a deferred
351 # that we'll grab so we can find the point at which the slave is
352 # substantiated but the build has not yet started.
353 d
= self
.bot1
.substantiation_deferred
354 self
.assertNotIdentical(d
, None)
355 d
.addCallback(self
._testSequence
_2)
357 def _testSequence_2(self
, res
):
358 # bot 1 is substantiated.
359 self
.assertNotIdentical(self
.bot1
.slave
, None)
360 self
.failUnless(self
.bot1
.substantiated
)
361 # the event has announced it's success
362 e
= self
.b1
.builder_status
.getEvent(-1)
363 self
.assertEqual(e
.text
, ['substantiate', 'success'])
364 self
.assertEqual(e
.color
, 'green')
365 self
.assertNotIdentical(e
.finished
, None)
366 # now we'll wait for the build to complete
367 d
= self
.build_deferred
368 del self
.build_deferred
369 d
.addCallback(self
._testSequence
_3)
371 def _testSequence_3(self
, res
):
372 # build was a success!
373 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
374 self
.failUnlessEqual(res
.getSlavename(), "bot1")
375 # bot1 is substantiated now. bot2 has not.
376 self
.failUnless(self
.bot1
.substantiated
)
377 self
.failIf(self
.bot2
.substantiated
)
378 # bot1 is waiting a bit to see if there will be another build before
379 # it shuts down the instance ("insubstantiates")
380 self
.build_wait_timer
= self
.bot1
.build_wait_timer
381 self
.assertNotIdentical(self
.build_wait_timer
, None)
382 self
.failUnless(self
.build_wait_timer
.active())
383 self
.assertApproximates(
384 self
.bot1
.build_wait_timeout
,
385 self
.build_wait_timer
.time
- runtime
.seconds(),
387 # now we'll do another build
388 d
= self
.doBuild("b1")
389 # the slave is already substantiated, so no event is created
390 e
= self
.b1
.builder_status
.getEvent(-1)
391 self
.assertNotEqual(e
.text
, ['substantiating'])
392 # wait for the next build
393 d
.addCallback(self
._testSequence
_4)
395 def _testSequence_4(self
, res
):
396 # build was a success!
397 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
398 self
.failUnlessEqual(res
.getSlavename(), "bot1")
399 # bot1 is still waiting, but with a new timer
400 self
.assertNotIdentical(self
.bot1
.build_wait_timer
, None)
401 self
.assertNotIdentical(self
.build_wait_timer
,
402 self
.bot1
.build_wait_timer
)
403 self
.assertApproximates(
404 self
.bot1
.build_wait_timeout
,
405 self
.bot1
.build_wait_timer
.time
- runtime
.seconds(),
407 del self
.build_wait_timer
408 # We'll set the timer to fire sooner, and wait for it to fire.
409 self
.bot1
.build_wait_timer
.reset(0)
411 reactor
.callLater(1, d
.callback
, None)
412 d
.addCallback(self
._testSequence
_5)
414 def _testSequence_5(self
, res
):
415 # slave is insubstantiated
416 self
.assertIdentical(self
.bot1
.slave
, None)
417 self
.failIf(self
.bot1
.substantiated
)
418 # Now we'll start up another build, to show that the shutdown left
419 # things in such a state that we can restart.
420 d
= self
.doBuild("b1")
421 # the bot can return an informative message on success that the event
422 # will render. Let's use a mechanism of our test latent bot to
424 self
.bot1
.start_message
= ['[instance id]', '[start-up time]']
425 # here's our event again:
426 self
.e
= self
.b1
.builder_status
.getEvent(-1)
427 self
.assertEqual(self
.e
.text
, ['substantiating'])
428 self
.assertEqual(self
.e
.color
, 'yellow')
429 d
.addCallback(self
._testSequence
_6)
431 def _testSequence_6(self
, res
):
432 # build was a success!
433 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
434 self
.failUnlessEqual(res
.getSlavename(), "bot1")
435 # the event has announced it's success. (Just imagine that
436 # [instance id] and [start-up time] were actually valuable
442 ['substantiate', 'success', '[instance id]', '[start-up time]'])
443 self
.assertEqual(e
.color
, 'green')
444 # Now we need to clean up the timer. We could just cancel it, but
445 # we'll go through the full dance once more time to show we can.
446 # We'll set the timer to fire sooner, and wait for it to fire.
447 # Also, we'll set the build_slave to take a little bit longer to shut
448 # down, to see that it doesn't affect anything.
449 self
.bot1
.stop_wait
= 2
450 self
.bot1
.build_wait_timer
.reset(0)
452 reactor
.callLater(1, d
.callback
, None)
453 d
.addCallback(self
._testSequence
_7)
455 def _testSequence_7(self
, res
):
456 # slave is insubstantiated
457 self
.assertIdentical(self
.bot1
.slave
, None)
458 self
.assertNot(self
.bot1
.substantiated
)
459 # the remote is still not cleaned out. We'll wait for it.
461 reactor
.callLater(1, d
.callback
, None)
464 def testNeverSubstantiated(self
):
465 # When a substantiation is requested, the slave may never appear.
466 # This is a serious problem, and recovering from it is not really
467 # handled well right now (in part because a way to handle it is not
468 # clear). However, at the least, the status event will show a
469 # failure, and the slave will be told to insubstantiate, and to be
470 # removed from the botmaster as anavailable slave.
471 # This tells our test bot to never start, and to not complain about
472 # being told to stop without ever starting
473 self
.bot1
.testing_substantiation_timeout
= True
474 # normally (by default) we have 20 minutes to try and connect to the
476 self
.assertEqual(self
.bot1
.missing_timeout
, 20*60)
477 # for testing purposes, we'll put that down to a tenth of a second!
478 self
.bot1
.missing_timeout
= 0.1
479 # since the current scheduling algorithm is simple and does not
480 # rotate or attempt any sort of load-balancing, two builds in
481 # sequence should both use the first slave. This may change later if
482 # we move to a more sophisticated scheme.
483 self
.b1
.CHOOSE_SLAVES_RANDOMLY
= False
485 self
.build_deferred
= self
.doBuild('b1')
486 # the event tells us we are instantiating, as usual
487 e
= self
.b1
.builder_status
.getEvent(-1)
488 self
.assertEqual(e
.text
, ['substantiating'])
489 self
.assertEqual(e
.color
, 'yellow')
490 # we'll see in a moment that the test flag we have to show that the
491 # bot was told to insubstantiate has been fired. Here, we just verify
492 # that it is ready to be fired.
493 self
.failIf(self
.bot1
.stopped
)
494 # That substantiation is going to fail. Let's wait for it.
495 d
= self
.bot1
.substantiation_deferred
496 self
.assertNotIdentical(d
, None)
497 d
.addCallbacks(self
._testNeverSubstantiated
_BadSuccess
,
498 self
._testNeverSubstantiated
_1)
500 def _testNeverSubstantiated_BadSuccess(self
, res
):
501 self
.fail('we should not have succeeded here.')
502 def _testNeverSubstantiated_1(self
, res
):
504 self
.assertIdentical(self
.bot1
.slave
, None)
505 self
.failIf(self
.bot1
.substantiated
)
506 self
.failUnless(isinstance(res
, failure
.Failure
))
507 self
.assertIdentical(self
.bot1
.substantiation_deferred
, None)
508 # our event informs us of this
509 e1
= self
.b1
.builder_status
.getEvent(-3)
510 self
.assertEqual(e1
.text
, ['substantiate', 'failed'])
511 self
.assertEqual(e1
.color
, 'red')
512 self
.assertNotIdentical(e1
.finished
, None)
513 # the slave is no longer available to build. The events show it...
514 e2
= self
.b1
.builder_status
.getEvent(-2)
515 self
.assertEqual(e2
.text
, ['removing', 'latent', 'bot1'])
516 e3
= self
.b1
.builder_status
.getEvent(-1)
517 self
.assertEqual(e3
.text
, ['disconnect', 'bot1'])
518 # ...and the builder shows it.
519 self
.assertEqual(['bot2'],
520 [sb
.slave
.slavename
for sb
in self
.b1
.slaves
])
521 # ideally, we would retry the build, but that infrastructure (which
522 # would be used for other situations in the builder as well) does not
523 # yet exist. Therefore the build never completes one way or the
524 # other, just as if a normal slave detached.
526 def testServiceStop(self
):
527 # if the slave has an instance when it is stopped, the slave should
528 # be told to shut down.
529 self
.b1
.CHOOSE_SLAVES_RANDOMLY
= False
530 d
= self
.doBuild("b1")
531 d
.addCallback(self
._testServiceStop
_1)
533 def _testServiceStop_1(self
, res
):
534 # build was a success!
535 self
.failUnlessEqual(res
.getResults(), SUCCESS
)
536 self
.failUnlessEqual(res
.getSlavename(), "bot1")
537 # bot 1 is substantiated.
538 self
.assertNotIdentical(self
.bot1
.slave
, None)
539 self
.failUnless(self
.bot1
.substantiated
)
540 # now let's stop the bot.
541 d
= self
.bot1
.stopService()
542 d
.addCallback(self
._testServiceStop
_2)
544 def _testServiceStop_2(self
, res
):
545 # bot 1 is NOT substantiated.
546 self
.assertIdentical(self
.bot1
.slave
, None)
547 self
.failIf(self
.bot1
.substantiated
)
550 # While a latent slave pings normally when it is substantiated, (as
551 # happens behind the scene when a build is request), when
552 # it is insubstantial, the ping is a no-op success.
553 self
.assertIdentical(self
.bot1
.slave
, None)
554 self
.failIf(self
.bot1
.substantiated
)
555 d
= self
.connectSlave(builders
=['b1'], slavename
='bot3')
556 d
.addCallback(self
._testPing
_1)
558 def _testPing_1(self
, res
):
559 self
.assertEqual(sorted(sb
.slave
.slavename
for sb
in self
.b1
.slaves
),
560 ['bot1', 'bot2', 'bot3'])
561 d
= self
.control
.getBuilder('b1').ping()
562 d
.addCallback(self
._testPing
_2)
564 def _testPing_2(self
, res
):
565 # all three pings were successful
567 # but neither bot1 not bot2 substantiated.
568 self
.assertIdentical(self
.bot1
.slave
, None)
569 self
.failIf(self
.bot1
.substantiated
)
570 self
.assertIdentical(self
.bot2
.slave
, None)
571 self
.failIf(self
.bot2
.substantiated
)
574 class SlaveBusyness(RunMixin
, unittest
.TestCase
):
578 self
.master
.loadConfig(config_busyness
)
579 self
.master
.startService()
580 d
= self
.connectSlave(["b1", "b2"])
583 def doBuild(self
, buildername
):
584 br
= BuildRequest("forced", SourceStamp(), 'test_builder')
585 d
= br
.waitUntilFinished()
586 self
.control
.getBuilder(buildername
).requestBuild(br
)
589 def getRunningBuilds(self
):
590 return len(self
.status
.getSlave("bot1").getRunningBuilds())
592 def testSlaveNotBusy(self
):
593 self
.failUnlessEqual(self
.getRunningBuilds(), 0)
594 # now kick a build, wait for it to finish, then check again
595 d
= self
.doBuild("b1")
596 d
.addCallback(self
._testSlaveNotBusy
_1)
599 def _testSlaveNotBusy_1(self
, res
):
600 self
.failUnlessEqual(self
.getRunningBuilds(), 0)
602 def testSlaveBusyOneBuild(self
):
603 d1
= self
.doBuild("b1")
604 d2
= defer
.Deferred()
605 reactor
.callLater(.5, d2
.callback
, None)
606 d2
.addCallback(self
._testSlaveBusyOneBuild
_1)
607 d1
.addCallback(self
._testSlaveBusyOneBuild
_finished
_1)
608 return defer
.DeferredList([d1
,d2
])
610 def _testSlaveBusyOneBuild_1(self
, res
):
611 self
.failUnlessEqual(self
.getRunningBuilds(), 1)
613 def _testSlaveBusyOneBuild_finished_1(self
, res
):
614 self
.failUnlessEqual(self
.getRunningBuilds(), 0)
616 def testSlaveBusyTwoBuilds(self
):
617 d1
= self
.doBuild("b1")
618 d2
= self
.doBuild("b2")
619 d3
= defer
.Deferred()
620 reactor
.callLater(.5, d3
.callback
, None)
621 d3
.addCallback(self
._testSlaveBusyTwoBuilds
_1)
622 d1
.addCallback(self
._testSlaveBusyTwoBuilds
_finished
_1, d2
)
623 return defer
.DeferredList([d1
,d3
])
625 def _testSlaveBusyTwoBuilds_1(self
, res
):
626 self
.failUnlessEqual(self
.getRunningBuilds(), 2)
628 def _testSlaveBusyTwoBuilds_finished_1(self
, res
, d2
):
629 self
.failUnlessEqual(self
.getRunningBuilds(), 1)
630 d2
.addCallback(self
._testSlaveBusyTwoBuilds
_finished
_2)
633 def _testSlaveBusyTwoBuilds_finished_2(self
, res
):
634 self
.failUnlessEqual(self
.getRunningBuilds(), 0)
636 def testSlaveDisconnect(self
):
637 d1
= self
.doBuild("b1")
638 d2
= defer
.Deferred()
639 reactor
.callLater(.5, d2
.callback
, None)
640 d2
.addCallback(self
._testSlaveDisconnect
_1)
641 d1
.addCallback(self
._testSlaveDisconnect
_finished
_1)
642 return defer
.DeferredList([d1
, d2
])
644 def _testSlaveDisconnect_1(self
, res
):
645 self
.failUnlessEqual(self
.getRunningBuilds(), 1)
646 return self
.shutdownAllSlaves()
648 def _testSlaveDisconnect_finished_1(self
, res
):
649 self
.failUnlessEqual(self
.getRunningBuilds(), 0)
652 from buildbot.process import factory
653 from buildbot.steps import dummy
654 from buildbot.buildslave import BuildSlave
657 BuildmasterConfig = c = {}
658 c['slaves'] = [BuildSlave('bot1', 'sekrit')]
660 c['slavePortnum'] = 0
663 f1 = factory.BuildFactory([s(dummy.Wait, handle='one')])
664 f2 = factory.BuildFactory([s(dummy.Wait, handle='two')])
665 f3 = factory.BuildFactory([s(dummy.Wait, handle='three')])
668 {'name': 'b1', 'slavenames': ['bot1'],
669 'builddir': 'b1', 'factory': f1},
673 config_4
= config_3
+ """
675 {'name': 'b1', 'slavenames': ['bot1'],
676 'builddir': 'b1', 'factory': f2},
680 config_5
= config_3
+ """
682 {'name': 'b1', 'slavenames': ['bot1'],
683 'builddir': 'b1', 'factory': f3},
687 from buildbot
.slave
.commands
import waitCommandRegistry
689 class Reconfig(RunMixin
, unittest
.TestCase
):
693 self
.master
.loadConfig(config_3
)
694 self
.master
.startService()
695 d
= self
.connectSlave(["b1"])
698 def _one_started(self
):
699 log
.msg("testReconfig._one_started")
700 self
.build1_started
= True
701 self
.d1
.callback(None)
704 def _two_started(self
):
705 log
.msg("testReconfig._two_started")
706 self
.build2_started
= True
707 self
.d3
.callback(None)
710 def _three_started(self
):
711 log
.msg("testReconfig._three_started")
712 self
.build3_started
= True
713 self
.d5
.callback(None)
716 def testReconfig(self
):
717 # reconfiguring a Builder should not interrupt any running Builds. No
718 # queued BuildRequests should be lost. The next Build started should
719 # use the new process.
720 slave1
= self
.slaves
['bot1']
721 bot1
= slave1
.getServiceNamed('bot')
722 sb1
= bot1
.builders
['b1']
723 self
.failUnless(isinstance(sb1
, bot
.SlaveBuilder
))
724 self
.failUnless(sb1
.running
)
725 b1
= self
.master
.botmaster
.builders
['b1']
728 self
.d1
= d1
= defer
.Deferred()
729 self
.d2
= d2
= defer
.Deferred()
730 self
.d3
, self
.d4
= defer
.Deferred(), defer
.Deferred()
731 self
.d5
, self
.d6
= defer
.Deferred(), defer
.Deferred()
732 self
.build1_started
= False
733 self
.build2_started
= False
734 self
.build3_started
= False
735 waitCommandRegistry
[("one","build1")] = self
._one
_started
736 waitCommandRegistry
[("two","build2")] = self
._two
_started
737 waitCommandRegistry
[("three","build3")] = self
._three
_started
739 # use different branches to make sure these cannot be merged
740 br1
= BuildRequest("build1", SourceStamp(branch
="1"), 'test_builder')
741 b1
.submitBuildRequest(br1
)
742 br2
= BuildRequest("build2", SourceStamp(branch
="2"), 'test_builder')
743 b1
.submitBuildRequest(br2
)
744 br3
= BuildRequest("build3", SourceStamp(branch
="3"), 'test_builder')
745 b1
.submitBuildRequest(br3
)
746 self
.requests
= (br1
, br2
, br3
)
747 # all three are now in the queue
749 # wait until the first one has started
750 d1
.addCallback(self
._testReconfig
_2)
753 def _testReconfig_2(self
, res
):
754 log
.msg("_testReconfig_2")
755 # confirm that it is building
756 brs
= self
.requests
[0].status
.getBuilds()
757 self
.failUnlessEqual(len(brs
), 1)
759 self
.failUnlessEqual(self
.build1
.getCurrentStep().getName(), "wait")
760 # br1 is building, br2 and br3 are in the queue (in that order). Now
761 # we reconfigure the Builder.
762 self
.failUnless(self
.build1_started
)
763 d
= self
.master
.loadConfig(config_4
)
764 d
.addCallback(self
._testReconfig
_3)
767 def _testReconfig_3(self
, res
):
768 log
.msg("_testReconfig_3")
769 # now check to see that br1 is still building, and that br2 and br3
770 # are in the queue of the new builder
771 b1
= self
.master
.botmaster
.builders
['b1']
772 self
.failIfIdentical(b1
, self
.orig_b1
)
773 self
.failIf(self
.build1
.isFinished())
774 self
.failUnlessEqual(self
.build1
.getCurrentStep().getName(), "wait")
775 self
.failUnlessEqual(len(b1
.buildable
), 2)
776 self
.failUnless(self
.requests
[1] in b1
.buildable
)
777 self
.failUnless(self
.requests
[2] in b1
.buildable
)
779 # allow br1 to finish, and make sure its status is delivered normally
780 d
= self
.requests
[0].waitUntilFinished()
781 d
.addCallback(self
._testReconfig
_4)
782 self
.d2
.callback(None)
785 def _testReconfig_4(self
, bs
):
786 log
.msg("_testReconfig_4")
787 self
.failUnlessEqual(bs
.getReason(), "build1")
788 self
.failUnless(bs
.isFinished())
789 self
.failUnlessEqual(bs
.getResults(), SUCCESS
)
791 # at this point, the first build has finished, and there is a pending
792 # call to start the second build. Once that pending call fires, there
793 # is a network roundtrip before the 'wait' RemoteCommand is delivered
794 # to the slave. We need to wait for both events to happen before we
795 # can check to make sure it is using the correct process. Just wait a
798 d
.addCallback(self
._testReconfig
_5)
799 reactor
.callLater(1, d
.callback
, None)
802 def _testReconfig_5(self
, res
):
803 log
.msg("_testReconfig_5")
804 # at this point the next build ought to be running
805 b1
= self
.master
.botmaster
.builders
['b1']
806 self
.failUnlessEqual(len(b1
.buildable
), 1)
807 self
.failUnless(self
.requests
[2] in b1
.buildable
)
808 self
.failUnlessEqual(len(b1
.building
), 1)
809 # and it ought to be using the new process
810 self
.failUnless(self
.build2_started
)
812 # now, while the second build is running, change the config multiple
815 d
= self
.master
.loadConfig(config_3
)
816 d
.addCallback(lambda res
: self
.master
.loadConfig(config_4
))
817 d
.addCallback(lambda res
: self
.master
.loadConfig(config_5
))
819 # then once that's done, allow the second build to finish and
820 # wait for it to complete
821 da
= self
.requests
[1].waitUntilFinished()
822 self
.d4
.callback(None)
826 # and once *that*'s done, wait another second to let the third
828 db
= defer
.Deferred()
829 reactor
.callLater(1, db
.callback
, None)
831 d
.addCallback(_done2
)
832 d
.addCallback(self
._testReconfig
_6)
835 def _testReconfig_6(self
, res
):
836 log
.msg("_testReconfig_6")
837 # now check to see that the third build is running
838 self
.failUnless(self
.build3_started
)
844 class Slave2(RunMixin
, unittest
.TestCase
):
850 self
.master
.loadConfig(config_1
)
851 self
.master
.startService()
853 def doBuild(self
, buildername
, reason
="forced"):
854 # we need to prevent these builds from being merged, so we create
855 # each of them with a different revision specifier. The revision is
856 # ignored because our build process does not have a source checkout
859 br
= BuildRequest(reason
, SourceStamp(revision
=self
.revision
),
861 d
= br
.waitUntilFinished()
862 self
.control
.getBuilder(buildername
).requestBuild(br
)
865 def testFirstComeFirstServed(self
):
866 # submit three builds, then connect a slave which fails the
867 # slaveping. The first build will claim the slave, do the slaveping,
868 # give up, and re-queue the build. Verify that the build gets
869 # re-queued in front of all other builds. This may be tricky, because
870 # the other builds may attempt to claim the just-failed slave.
872 d1
= self
.doBuild("b1", "first")
873 d2
= self
.doBuild("b1", "second")
874 #buildable = self.master.botmaster.builders["b1"].buildable
875 #print [b.reason for b in buildable]
877 # specifically, I want the poor build to get precedence over any
878 # others that were waiting. To test this, we need more builds than
881 # now connect a broken slave. The first build started as soon as it
882 # connects, so by the time we get to our _1 method, the ill-fated
883 # build has already started.
884 d
= self
.connectSlave(["b1"], opts
={"failPingOnce": True})
885 d
.addCallback(self
._testFirstComeFirstServed
_1, d1
, d2
)
887 def _testFirstComeFirstServed_1(self
, res
, d1
, d2
):
888 # the master has send the slaveping. When this is received, it will
889 # fail, causing the master to hang up on the slave. When it
890 # reconnects, it should find the first build at the front of the
891 # queue. If we simply wait for both builds to complete, then look at
892 # the status logs, we should see that the builds ran in the correct
895 d
= defer
.DeferredList([d1
,d2
])
896 d
.addCallback(self
._testFirstComeFirstServed
_2)
898 def _testFirstComeFirstServed_2(self
, res
):
899 b
= self
.status
.getBuilder("b1")
900 builds
= b
.getBuild(0), b
.getBuild(1)
901 reasons
= [build
.getReason() for build
in builds
]
902 self
.failUnlessEqual(reasons
, ["first", "second"])
904 config_multi_builders
= config_1
+ """
906 {'name': 'dummy', 'slavenames': ['bot1','bot2','bot3'],
907 'builddir': 'b1', 'factory': f2},
908 {'name': 'dummy2', 'slavenames': ['bot1','bot2','bot3'],
909 'builddir': 'b2', 'factory': f2},
910 {'name': 'dummy3', 'slavenames': ['bot1','bot2','bot3'],
911 'builddir': 'b3', 'factory': f2},
916 config_mail_missing
= config_1
+ """
917 c['slaves'] = [BuildSlave('bot1', 'sekrit', notify_on_missing='admin',
920 {'name': 'dummy', 'slavenames': ['bot1'],
921 'builddir': 'b1', 'factory': f1},
923 c['projectName'] = 'myproject'
924 c['projectURL'] = 'myURL'
927 class FakeMailer(mail
.MailNotifier
):
928 def sendMessage(self
, m
, recipients
):
929 self
.messages
.append((m
,recipients
))
930 return defer
.succeed(None)
932 class BuildSlave(RunMixin
, unittest
.TestCase
):
933 def test_track_builders(self
):
934 self
.master
.loadConfig(config_multi_builders
)
935 self
.master
.readConfig
= True
936 self
.master
.startService()
937 d
= self
.connectSlave()
940 b
= self
.master
.botmaster
.builders
['dummy']
941 self
.failUnless(len(b
.slaves
) == 1) # just bot1
943 bs
= b
.slaves
[0].slave
944 self
.failUnless(len(bs
.slavebuilders
) == 3)
945 self
.failUnless(b
in [sb
.builder
for sb
in
946 bs
.slavebuilders
.values()])
948 d
.addCallback(_check
)
951 def test_mail_on_missing(self
):
952 self
.master
.loadConfig(config_mail_missing
)
953 self
.master
.readConfig
= True
954 self
.master
.startService()
955 fm
= FakeMailer("buildbot@example.org")
957 fm
.setServiceParent(self
.master
)
958 self
.master
.statusTargets
.append(fm
)
960 d
= self
.connectSlave()
961 d
.addCallback(self
.stall
, 1)
962 d
.addCallback(lambda res
: self
.shutdownSlave("bot1", "dummy"))
964 self
.failIf(fm
.messages
)
965 d
.addCallback(_not_yet
)
966 # we reconnect right away, so the timer shouldn't fire
967 d
.addCallback(lambda res
: self
.connectSlave())
968 d
.addCallback(self
.stall
, 3)
969 d
.addCallback(_not_yet
)
970 d
.addCallback(lambda res
: self
.shutdownSlave("bot1", "dummy"))
971 d
.addCallback(_not_yet
)
972 # now we let it sit disconnected for long enough for the timer to
974 d
.addCallback(self
.stall
, 3)
976 self
.failUnlessEqual(len(fm
.messages
), 1)
977 msg
,recips
= fm
.messages
[0]
978 self
.failUnlessEqual(recips
, ["admin"])
979 body
= msg
.as_string()
980 self
.failUnlessIn("To: admin", body
)
981 self
.failUnlessIn("Subject: Buildbot: buildslave bot1 was lost",
983 self
.failUnlessIn("From: buildbot@example.org", body
)
984 self
.failUnlessIn("working for 'myproject'", body
)
985 self
.failUnlessIn("has noticed that the buildslave named bot1 went away",
987 self
.failUnlessIn("was 'one'", body
)
988 self
.failUnlessIn("myURL", body
)
989 d
.addCallback(_check
)
992 def stall(self
, result
, delay
=1):
994 reactor
.callLater(delay
, d
.callback
, result
)