OLD | NEW |
| (Empty) |
1 # -*- test-case-name: buildbot.test.test_run -*- | |
2 | |
3 from twisted.trial import unittest | |
4 from twisted.internet import reactor, defer | |
5 import os | |
6 | |
7 from buildbot import master, interfaces | |
8 from buildbot.sourcestamp import SourceStamp | |
9 from buildbot.changes import changes | |
10 from buildbot.status import builder | |
11 from buildbot.process.base import BuildRequest | |
12 | |
13 from buildbot.test.runutils import RunMixin, TestFlagMixin, rmtree | |
14 | |
15 config_base = """ | |
16 from buildbot.process import factory | |
17 from buildbot.steps import dummy | |
18 from buildbot.buildslave import BuildSlave | |
19 from buildbot.config import BuilderConfig | |
20 s = factory.s | |
21 | |
22 f1 = factory.QuickBuildFactory('fakerep', 'cvsmodule', configure=None) | |
23 | |
24 f2 = factory.BuildFactory([ | |
25 dummy.Dummy(timeout=1), | |
26 dummy.RemoteDummy(timeout=2), | |
27 ]) | |
28 | |
29 BuildmasterConfig = c = {} | |
30 c['slaves'] = [BuildSlave('bot1', 'sekrit')] | |
31 c['schedulers'] = [] | |
32 c['builders'] = [ | |
33 BuilderConfig(name='quick', slavename='bot1', factory=f1, | |
34 builddir='quickdir', slavebuilddir='slavequickdir'), | |
35 ] | |
36 c['slavePortnum'] = 0 | |
37 """ | |
38 | |
39 config_run = config_base + """ | |
40 from buildbot.scheduler import Scheduler | |
41 c['schedulers'] = [Scheduler('quick', None, 120, ['quick'])] | |
42 """ | |
43 | |
44 config_can_build = config_base + """ | |
45 from buildbot.buildslave import BuildSlave | |
46 c['slaves'] = [ BuildSlave('bot1', 'sekrit') ] | |
47 | |
48 from buildbot.scheduler import Scheduler | |
49 c['schedulers'] = [Scheduler('dummy', None, 0.1, ['dummy'])] | |
50 | |
51 c['builders'] = [ | |
52 BuilderConfig(name='dummy', slavename='bot1', | |
53 factory=f2, builddir='dummy1'), | |
54 ] | |
55 """ | |
56 | |
57 config_cant_build = config_can_build + """ | |
58 class MyBuildSlave(BuildSlave): | |
59 def canStartBuild(self): return False | |
60 c['slaves'] = [ MyBuildSlave('bot1', 'sekrit') ] | |
61 """ | |
62 | |
63 config_concurrency = config_base + """ | |
64 from buildbot.buildslave import BuildSlave | |
65 c['slaves'] = [ BuildSlave('bot1', 'sekrit', max_builds=1) ] | |
66 | |
67 from buildbot.scheduler import Scheduler | |
68 c['schedulers'] = [Scheduler('dummy', None, 0.1, ['dummy', 'dummy2'])] | |
69 | |
70 c['builders'] = c['builders'] + [ | |
71 BuilderConfig(name='dummy', slavename='bot1', factory=f2), | |
72 BuilderConfig(name='dummy2', slavename='bot1', factory=f2), | |
73 ] | |
74 """ | |
75 | |
76 config_2 = config_base + """ | |
77 c['builders'] = [ | |
78 BuilderConfig(name='dummy', slavename='bot1', | |
79 builddir='dummy1', factory=f2), | |
80 BuilderConfig(name='test dummy', slavename='bot1', | |
81 factory=f2, category='test'), | |
82 ] | |
83 """ | |
84 | |
85 config_3 = config_2 + """ | |
86 c['builders'] = c['builders'] + [ | |
87 BuilderConfig(name='adummy', slavename='bot1', | |
88 builddir='adummy3', factory=f2), | |
89 BuilderConfig(name='bdummy', slavename='bot1', | |
90 builddir='adummy4', factory=f2, | |
91 category='test'), | |
92 ] | |
93 """ | |
94 | |
95 config_4 = config_base + """ | |
96 c['builders'] = [ | |
97 BuilderConfig(name='dummy', slavename='bot1', | |
98 slavebuilddir='sdummy', factory=f2), | |
99 ] | |
100 """ | |
101 | |
102 config_4_newbasedir = config_4 + """ | |
103 c['builders'] = [ | |
104 BuilderConfig(name='dummy', slavename='bot1', | |
105 builddir='dummy2', factory=f2), | |
106 ] | |
107 """ | |
108 | |
109 config_4_newbuilder = config_4_newbasedir + """ | |
110 c['builders'] = c['builders'] + [ | |
111 BuilderConfig(name='dummy2', slavename='bot1', | |
112 builddir='dummy23', factory=f2), | |
113 ] | |
114 """ | |
115 | |
116 class Run(unittest.TestCase): | |
117 def rmtree(self, d): | |
118 rmtree(d) | |
119 | |
120 def testMaster(self): | |
121 self.rmtree("basedir") | |
122 os.mkdir("basedir") | |
123 m = master.BuildMaster("basedir") | |
124 m.loadConfig(config_run) | |
125 m.readConfig = True | |
126 m.startService() | |
127 cm = m.change_svc | |
128 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") | |
129 cm.addChange(c) | |
130 # verify that the Scheduler is now waiting | |
131 s = m.allSchedulers()[0] | |
132 self.failUnless(s.timer) | |
133 # halting the service will also stop the timer | |
134 d = defer.maybeDeferred(m.stopService) | |
135 return d | |
136 | |
137 class CanStartBuild(RunMixin, unittest.TestCase): | |
138 def rmtree(self, d): | |
139 rmtree(d) | |
140 | |
141 def testCanStartBuild(self): | |
142 return self.do_test(config_can_build, True) | |
143 | |
144 def testCantStartBuild(self): | |
145 return self.do_test(config_cant_build, False) | |
146 | |
147 def do_test(self, config, builder_should_run): | |
148 self.master.loadConfig(config) | |
149 self.master.readConfig = True | |
150 self.master.startService() | |
151 d = self.connectSlave() | |
152 | |
153 # send a change | |
154 cm = self.master.change_svc | |
155 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") | |
156 cm.addChange(c) | |
157 | |
158 d.addCallback(self._do_test1, builder_should_run) | |
159 | |
160 return d | |
161 | |
162 def _do_test1(self, res, builder_should_run): | |
163 # delay a little bit. Note that relying upon timers is a bit fragile, | |
164 # in this case we're hoping that our 0.5 second timer will land us | |
165 # somewhere in the middle of the [0.1s, 3.1s] window (after the 0.1 | |
166 # second Scheduler fires, then during the 3-second build), so that | |
167 # when we sample BuildSlave.state, we'll see BUILDING (or IDLE if the | |
168 # slave was told to be unavailable). On a heavily loaded system, our | |
169 # 0.5 second timer might not actually fire until after the build has | |
170 # completed. In the long run, it would be good to change this test to | |
171 # pass under those circumstances too. | |
172 d = defer.Deferred() | |
173 reactor.callLater(.5, d.callback, builder_should_run) | |
174 d.addCallback(self._do_test2) | |
175 return d | |
176 | |
177 def _do_test2(self, builder_should_run): | |
178 b = self.master.botmaster.builders['dummy'] | |
179 self.failUnless(len(b.slaves) == 1) | |
180 | |
181 bs = b.slaves[0] | |
182 from buildbot.process.builder import IDLE, BUILDING | |
183 if builder_should_run: | |
184 self.failUnlessEqual(bs.state, BUILDING) | |
185 else: | |
186 self.failUnlessEqual(bs.state, IDLE) | |
187 | |
188 | |
189 class ConcurrencyLimit(RunMixin, unittest.TestCase): | |
190 | |
191 def testConcurrencyLimit(self): | |
192 d = self.master.loadConfig(config_concurrency) | |
193 d.addCallback(lambda res: self.master.startService()) | |
194 d.addCallback(lambda res: self.connectSlave()) | |
195 | |
196 def _send(res): | |
197 # send a change. This will trigger both builders at the same | |
198 # time, but since they share a slave, the max_builds=1 setting | |
199 # will insure that only one of the two builds gets to run. | |
200 cm = self.master.change_svc | |
201 c = changes.Change("bob", ["Makefile", "foo/bar.c"], | |
202 "changed stuff") | |
203 cm.addChange(c) | |
204 d.addCallback(_send) | |
205 | |
206 def _delay(res): | |
207 d1 = defer.Deferred() | |
208 reactor.callLater(1, d1.callback, None) | |
209 # this test depends upon this 1s delay landing us in the middle | |
210 # of one of the builds. | |
211 return d1 | |
212 d.addCallback(_delay) | |
213 | |
214 def _check(res): | |
215 builders = [ self.master.botmaster.builders[bn] | |
216 for bn in ('dummy', 'dummy2') ] | |
217 for builder in builders: | |
218 self.failUnless(len(builder.slaves) == 1) | |
219 | |
220 from buildbot.process.builder import BUILDING | |
221 building_bs = [ builder | |
222 for builder in builders | |
223 if builder.slaves[0].state == BUILDING ] | |
224 # assert that only one build is running right now. If the | |
225 # max_builds= weren't in effect, this would be 2. | |
226 self.failUnlessEqual(len(building_bs), 1) | |
227 d.addCallback(_check) | |
228 | |
229 return d | |
230 | |
231 | |
232 class Ping(RunMixin, unittest.TestCase): | |
233 def testPing(self): | |
234 self.master.loadConfig(config_2) | |
235 self.master.readConfig = True | |
236 self.master.startService() | |
237 | |
238 d = self.connectSlave() | |
239 d.addCallback(self._testPing_1) | |
240 return d | |
241 | |
242 def _testPing_1(self, res): | |
243 d = interfaces.IControl(self.master).getBuilder("dummy").ping() | |
244 d.addCallback(self._testPing_2) | |
245 return d | |
246 | |
247 def _testPing_2(self, res): | |
248 pass | |
249 | |
250 class BuilderNames(unittest.TestCase): | |
251 | |
252 def testGetBuilderNames(self): | |
253 os.mkdir("bnames") | |
254 m = master.BuildMaster("bnames") | |
255 s = m.getStatus() | |
256 | |
257 m.loadConfig(config_3) | |
258 m.readConfig = True | |
259 | |
260 self.failUnlessEqual(s.getBuilderNames(), | |
261 ["dummy", "test dummy", "adummy", "bdummy"]) | |
262 self.failUnlessEqual(s.getBuilderNames(categories=['test']), | |
263 ["test dummy", "bdummy"]) | |
264 | |
265 class Disconnect(RunMixin, unittest.TestCase): | |
266 | |
267 def setUp(self): | |
268 RunMixin.setUp(self) | |
269 | |
270 # verify that disconnecting the slave during a build properly | |
271 # terminates the build | |
272 m = self.master | |
273 s = self.status | |
274 c = self.control | |
275 | |
276 m.loadConfig(config_2) | |
277 m.readConfig = True | |
278 m.startService() | |
279 | |
280 self.failUnlessEqual(s.getBuilderNames(), ["dummy", "test dummy"]) | |
281 self.s1 = s1 = s.getBuilder("dummy") | |
282 self.failUnlessEqual(s1.getName(), "dummy") | |
283 self.failUnlessEqual(s1.getState(), ("offline", [])) | |
284 self.failUnlessEqual(s1.getCurrentBuilds(), []) | |
285 self.failUnlessEqual(s1.getLastFinishedBuild(), None) | |
286 self.failUnlessEqual(s1.getBuild(-1), None) | |
287 | |
288 d = self.connectSlave() | |
289 d.addCallback(self._disconnectSetup_1) | |
290 return d | |
291 | |
292 def _disconnectSetup_1(self, res): | |
293 self.failUnlessEqual(self.s1.getState(), ("idle", [])) | |
294 | |
295 | |
296 def verifyDisconnect(self, bs): | |
297 self.failUnless(bs.isFinished()) | |
298 | |
299 step1 = bs.getSteps()[0] | |
300 self.failUnlessEqual(step1.getText(), ["delay", "interrupted"]) | |
301 self.failUnlessEqual(step1.getResults()[0], builder.FAILURE) | |
302 | |
303 self.failUnlessEqual(bs.getResults(), builder.FAILURE) | |
304 | |
305 def verifyDisconnect2(self, bs): | |
306 self.failUnless(bs.isFinished()) | |
307 | |
308 step1 = bs.getSteps()[1] | |
309 self.failUnlessEqual(step1.getText(), ["remote", "delay", "2 secs", | |
310 "failed", "slave", "lost"]) | |
311 self.failUnlessEqual(step1.getResults()[0], builder.EXCEPTION) | |
312 | |
313 self.failUnlessEqual(bs.getResults(), builder.EXCEPTION) | |
314 | |
315 def submitBuild(self): | |
316 ss = SourceStamp() | |
317 br = BuildRequest("forced build", ss, "dummy") | |
318 self.control.getBuilder("dummy").requestBuild(br) | |
319 d = defer.Deferred() | |
320 def _started(bc): | |
321 br.unsubscribe(_started) | |
322 d.callback(bc) | |
323 br.subscribe(_started) | |
324 return d | |
325 | |
326 def testIdle2(self): | |
327 # now suppose the slave goes missing | |
328 self.disappearSlave(allowReconnect=False) | |
329 | |
330 # forcing a build will work: the build detect that the slave is no | |
331 # longer available and will be re-queued. Wait 5 seconds, then check | |
332 # to make sure the build is still in the 'waiting for a slave' queue. | |
333 req = BuildRequest("forced build", SourceStamp(), "test_builder") | |
334 self.failUnlessEqual(req.startCount, 0) | |
335 self.control.getBuilder("dummy").requestBuild(req) | |
336 # this should ping the slave, which doesn't respond (and eventually | |
337 # times out). The BuildRequest will be re-queued, and its .startCount | |
338 # will be incremented. | |
339 self.killSlave() | |
340 d = defer.Deferred() | |
341 d.addCallback(self._testIdle2_1, req) | |
342 reactor.callLater(3, d.callback, None) | |
343 return d | |
344 testIdle2.timeout = 5 | |
345 | |
346 def _testIdle2_1(self, res, req): | |
347 self.failUnlessEqual(req.startCount, 1) | |
348 cancelled = req.cancel() | |
349 self.failUnless(cancelled) | |
350 | |
351 | |
352 def testBuild1(self): | |
353 # this next sequence is timing-dependent. The dummy build takes at | |
354 # least 3 seconds to complete, and this batch of commands must | |
355 # complete within that time. | |
356 # | |
357 d = self.submitBuild() | |
358 d.addCallback(self._testBuild1_1) | |
359 return d | |
360 | |
361 def _testBuild1_1(self, bc): | |
362 bs = bc.getStatus() | |
363 # now kill the slave before it gets to start the first step | |
364 d = self.shutdownAllSlaves() # dies before it gets started | |
365 d.addCallback(self._testBuild1_2, bs) | |
366 return d # TODO: this used to have a 5-second timeout | |
367 | |
368 def _testBuild1_2(self, res, bs): | |
369 # now examine the just-stopped build and make sure it is really | |
370 # stopped. This is checking for bugs in which the slave-detach gets | |
371 # missed or causes an exception which prevents the build from being | |
372 # marked as "finished due to an error". | |
373 d = bs.waitUntilFinished() | |
374 d2 = self.master.botmaster.waitUntilBuilderDetached("dummy") | |
375 dl = defer.DeferredList([d, d2]) | |
376 dl.addCallback(self._testBuild1_3, bs) | |
377 return dl # TODO: this had a 5-second timeout too | |
378 | |
379 def _testBuild1_3(self, res, bs): | |
380 self.failUnlessEqual(self.s1.getState()[0], "offline") | |
381 self.verifyDisconnect(bs) | |
382 | |
383 | |
384 def testBuild2(self): | |
385 # this next sequence is timing-dependent | |
386 d = self.submitBuild() | |
387 d.addCallback(self._testBuild2_1) | |
388 return d | |
389 testBuild2.timeout = 30 | |
390 | |
391 def _testBuild2_1(self, bc): | |
392 bs = bc.getStatus() | |
393 # shutdown the slave while it's running the first step | |
394 reactor.callLater(0.5, self.shutdownAllSlaves) | |
395 | |
396 d = bs.waitUntilFinished() | |
397 d.addCallback(self._testBuild2_2, bs) | |
398 return d | |
399 | |
400 def _testBuild2_2(self, res, bs): | |
401 # we hit here when the build has finished. The builder is still being | |
402 # torn down, however, so spin for another second to allow the | |
403 # callLater(0) in Builder.detached to fire. | |
404 d = defer.Deferred() | |
405 reactor.callLater(1, d.callback, None) | |
406 d.addCallback(self._testBuild2_3, bs) | |
407 return d | |
408 | |
409 def _testBuild2_3(self, res, bs): | |
410 self.failUnlessEqual(self.s1.getState()[0], "offline") | |
411 self.verifyDisconnect(bs) | |
412 | |
413 | |
414 def testBuild3(self): | |
415 # this next sequence is timing-dependent | |
416 d = self.submitBuild() | |
417 d.addCallback(self._testBuild3_1) | |
418 return d | |
419 testBuild3.timeout = 30 | |
420 | |
421 def _testBuild3_1(self, bc): | |
422 bs = bc.getStatus() | |
423 # kill the slave while it's running the first step | |
424 reactor.callLater(0.5, self.killSlave) | |
425 d = bs.waitUntilFinished() | |
426 d.addCallback(self._testBuild3_2, bs) | |
427 return d | |
428 | |
429 def _testBuild3_2(self, res, bs): | |
430 # the builder is still being torn down, so give it another second | |
431 d = defer.Deferred() | |
432 reactor.callLater(1, d.callback, None) | |
433 d.addCallback(self._testBuild3_3, bs) | |
434 return d | |
435 | |
436 def _testBuild3_3(self, res, bs): | |
437 self.failUnlessEqual(self.s1.getState()[0], "offline") | |
438 self.verifyDisconnect(bs) | |
439 | |
440 | |
441 def testBuild4(self): | |
442 # this next sequence is timing-dependent | |
443 d = self.submitBuild() | |
444 d.addCallback(self._testBuild4_1) | |
445 return d | |
446 testBuild4.timeout = 30 | |
447 | |
448 def _testBuild4_1(self, bc): | |
449 bs = bc.getStatus() | |
450 # kill the slave while it's running the second (remote) step | |
451 reactor.callLater(1.5, self.killSlave) | |
452 d = bs.waitUntilFinished() | |
453 d.addCallback(self._testBuild4_2, bs) | |
454 return d | |
455 | |
456 def _testBuild4_2(self, res, bs): | |
457 # at this point, the slave is in the process of being removed, so it | |
458 # could either be 'idle' or 'offline'. I think there is a | |
459 # reactor.callLater(0) standing between here and the offline state. | |
460 #reactor.iterate() # TODO: remove the need for this | |
461 | |
462 self.failUnlessEqual(self.s1.getState()[0], "offline") | |
463 self.verifyDisconnect2(bs) | |
464 | |
465 | |
466 def testInterrupt(self): | |
467 # this next sequence is timing-dependent | |
468 d = self.submitBuild() | |
469 d.addCallback(self._testInterrupt_1) | |
470 return d | |
471 testInterrupt.timeout = 30 | |
472 | |
473 def _testInterrupt_1(self, bc): | |
474 bs = bc.getStatus() | |
475 # halt the build while it's running the first step | |
476 reactor.callLater(0.5, bc.stopBuild, "bang go splat") | |
477 d = bs.waitUntilFinished() | |
478 d.addCallback(self._testInterrupt_2, bs) | |
479 return d | |
480 | |
481 def _testInterrupt_2(self, res, bs): | |
482 self.verifyDisconnect(bs) | |
483 | |
484 | |
485 def testDisappear(self): | |
486 bc = self.control.getBuilder("dummy") | |
487 | |
488 # ping should succeed | |
489 d = bc.ping() | |
490 d.addCallback(self._testDisappear_1, bc) | |
491 return d | |
492 | |
493 def _testDisappear_1(self, res, bc): | |
494 self.failUnlessEqual(res, True) | |
495 | |
496 # now, before any build is run, make the slave disappear | |
497 self.disappearSlave(allowReconnect=False) | |
498 | |
499 # initiate the ping and then kill the slave, to simulate a disconnect. | |
500 d = bc.ping() | |
501 self.killSlave() | |
502 d.addCallback(self. _testDisappear_2) | |
503 return d | |
504 def _testDisappear_2(self, res): | |
505 self.failUnlessEqual(res, False) | |
506 | |
507 def testDuplicate(self): | |
508 bc = self.control.getBuilder("dummy") | |
509 bs = self.status.getBuilder("dummy") | |
510 ss = bs.getSlaves()[0] | |
511 | |
512 self.failUnless(ss.isConnected()) | |
513 self.failUnlessEqual(ss.getAdmin(), "one") | |
514 | |
515 # now, before any build is run, make the first slave disappear | |
516 self.disappearSlave(allowReconnect=False) | |
517 | |
518 d = self.master.botmaster.waitUntilBuilderDetached("dummy") | |
519 # now let the new slave take over | |
520 self.connectSlave2() | |
521 d.addCallback(self._testDuplicate_1, ss) | |
522 return d | |
523 testDuplicate.timeout = 5 | |
524 | |
525 def _testDuplicate_1(self, res, ss): | |
526 d = self.master.botmaster.waitUntilBuilderAttached("dummy") | |
527 d.addCallback(self._testDuplicate_2, ss) | |
528 return d | |
529 | |
530 def _testDuplicate_2(self, res, ss): | |
531 self.failUnless(ss.isConnected()) | |
532 self.failUnlessEqual(ss.getAdmin(), "two") | |
533 | |
534 | |
535 class Disconnect2(RunMixin, unittest.TestCase): | |
536 | |
537 def setUp(self): | |
538 RunMixin.setUp(self) | |
539 # verify that disconnecting the slave during a build properly | |
540 # terminates the build | |
541 m = self.master | |
542 s = self.status | |
543 c = self.control | |
544 | |
545 m.loadConfig(config_2) | |
546 m.readConfig = True | |
547 m.startService() | |
548 | |
549 self.failUnlessEqual(s.getBuilderNames(), ["dummy", "test dummy"]) | |
550 self.s1 = s1 = s.getBuilder("dummy") | |
551 self.failUnlessEqual(s1.getName(), "dummy") | |
552 self.failUnlessEqual(s1.getState(), ("offline", [])) | |
553 self.failUnlessEqual(s1.getCurrentBuilds(), []) | |
554 self.failUnlessEqual(s1.getLastFinishedBuild(), None) | |
555 self.failUnlessEqual(s1.getBuild(-1), None) | |
556 | |
557 d = self.connectSlaveFastTimeout() | |
558 d.addCallback(self._setup_disconnect2_1) | |
559 return d | |
560 | |
561 def _setup_disconnect2_1(self, res): | |
562 self.failUnlessEqual(self.s1.getState(), ("idle", [])) | |
563 | |
564 | |
565 def testSlaveTimeout(self): | |
566 # now suppose the slave goes missing. We want to find out when it | |
567 # creates a new Broker, so we reach inside and mark it with the | |
568 # well-known sigil of impending messy death. | |
569 bd = self.slaves['bot1'].getServiceNamed("bot").builders["dummy"] | |
570 broker = bd.remote.broker | |
571 broker.redshirt = 1 | |
572 | |
573 # make sure the keepalives will keep the connection up | |
574 d = defer.Deferred() | |
575 reactor.callLater(5, d.callback, None) | |
576 d.addCallback(self._testSlaveTimeout_1) | |
577 return d | |
578 testSlaveTimeout.timeout = 20 | |
579 | |
580 def _testSlaveTimeout_1(self, res): | |
581 bd = self.slaves['bot1'].getServiceNamed("bot").builders["dummy"] | |
582 if not bd.remote or not hasattr(bd.remote.broker, "redshirt"): | |
583 self.fail("slave disconnected when it shouldn't have") | |
584 | |
585 d = self.master.botmaster.waitUntilBuilderDetached("dummy") | |
586 # whoops! how careless of me. | |
587 self.disappearSlave(allowReconnect=True) | |
588 # the slave will realize the connection is lost within 2 seconds, and | |
589 # reconnect. | |
590 d.addCallback(self._testSlaveTimeout_2) | |
591 return d | |
592 | |
593 def _testSlaveTimeout_2(self, res): | |
594 # the ReconnectingPBClientFactory will attempt a reconnect in two | |
595 # seconds. | |
596 d = self.master.botmaster.waitUntilBuilderAttached("dummy") | |
597 d.addCallback(self._testSlaveTimeout_3) | |
598 return d | |
599 | |
600 def _testSlaveTimeout_3(self, res): | |
601 # make sure it is a new connection (i.e. a new Broker) | |
602 bd = self.slaves['bot1'].getServiceNamed("bot").builders["dummy"] | |
603 self.failUnless(bd.remote, "hey, slave isn't really connected") | |
604 self.failIf(hasattr(bd.remote.broker, "redshirt"), | |
605 "hey, slave's Broker is still marked for death") | |
606 | |
607 | |
608 class Basedir(RunMixin, unittest.TestCase): | |
609 def testChangeBuilddir(self): | |
610 m = self.master | |
611 m.loadConfig(config_4) | |
612 m.readConfig = True | |
613 m.startService() | |
614 | |
615 d = self.connectSlave() | |
616 d.addCallback(self._testChangeBuilddir_1) | |
617 return d | |
618 | |
619 def _testChangeBuilddir_1(self, res): | |
620 self.bot = bot = self.slaves['bot1'].bot | |
621 self.builder = builder = bot.builders.get("dummy") | |
622 self.failUnless(builder) | |
623 # slavebuilddir value. | |
624 self.failUnlessEqual(builder.builddir, "sdummy") | |
625 self.failUnlessEqual(builder.basedir, | |
626 os.path.join("slavebase-bot1", "sdummy")) | |
627 | |
628 d = self.master.loadConfig(config_4_newbasedir) | |
629 d.addCallback(self._testChangeBuilddir_2) | |
630 return d | |
631 | |
632 def _testChangeBuilddir_2(self, res): | |
633 bot = self.bot | |
634 # this does NOT cause the builder to be replaced | |
635 builder = bot.builders.get("dummy") | |
636 self.failUnless(builder) | |
637 self.failUnlessIdentical(self.builder, builder) | |
638 # the basedir should be updated | |
639 self.failUnlessEqual(builder.builddir, "dummy2") | |
640 self.failUnlessEqual(builder.basedir, | |
641 os.path.join("slavebase-bot1", "dummy2")) | |
642 | |
643 # add a new builder, which causes the basedir list to be reloaded | |
644 d = self.master.loadConfig(config_4_newbuilder) | |
645 return d | |
646 | |
647 class Triggers(RunMixin, TestFlagMixin, unittest.TestCase): | |
648 config_trigger = config_base + """ | |
649 from buildbot.scheduler import Triggerable, Scheduler | |
650 from buildbot.steps.trigger import Trigger | |
651 from buildbot.steps.dummy import Dummy | |
652 from buildbot.test.runutils import SetTestFlagStep | |
653 from buildbot.process.properties import WithProperties | |
654 c['schedulers'] = [ | |
655 Scheduler('triggerer', None, 0.1, ['triggerer'], properties={'dyn':'dyn'}), | |
656 Triggerable('triggeree', ['triggeree']) | |
657 ] | |
658 triggerer = factory.BuildFactory() | |
659 triggerer.addSteps([ | |
660 SetTestFlagStep(flagname='triggerer_started'), | |
661 Trigger(flunkOnFailure=True, @ARGS@), | |
662 SetTestFlagStep(flagname='triggerer_finished'), | |
663 ]) | |
664 triggeree = factory.BuildFactory([ | |
665 s(SetTestFlagStep, flagname='triggeree_started'), | |
666 s(@DUMMYCLASS@), | |
667 s(SetTestFlagStep, flagname='triggeree_finished'), | |
668 ]) | |
669 c['builders'] = [{'name': 'triggerer', 'slavename': 'bot1', | |
670 'builddir': 'triggerer', 'factory': triggerer}, | |
671 {'name': 'triggeree', 'slavename': 'bot1', | |
672 'builddir': 'triggeree', 'factory': triggeree}] | |
673 """ | |
674 | |
675 def mkConfig(self, args, dummyclass="Dummy"): | |
676 return self.config_trigger.replace("@ARGS@", args).replace("@DUMMYCLASS@
", dummyclass) | |
677 | |
678 def setupTest(self, args, dummyclass, checkFn): | |
679 self.clearFlags() | |
680 m = self.master | |
681 m.loadConfig(self.mkConfig(args, dummyclass)) | |
682 m.readConfig = True | |
683 m.startService() | |
684 | |
685 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") | |
686 m.change_svc.addChange(c) | |
687 | |
688 d = self.connectSlave(builders=['triggerer', 'triggeree']) | |
689 d.addCallback(self.startTimer, 0.5, checkFn) | |
690 return d | |
691 | |
692 def startTimer(self, res, time, next_fn): | |
693 d = defer.Deferred() | |
694 reactor.callLater(time, d.callback, None) | |
695 d.addCallback(next_fn) | |
696 return d | |
697 | |
698 def testTriggerBuild(self): | |
699 return self.setupTest("schedulerNames=['triggeree']", | |
700 "Dummy", | |
701 self._checkTriggerBuild) | |
702 | |
703 def _checkTriggerBuild(self, res): | |
704 self.failIfFlagNotSet('triggerer_started') | |
705 self.failIfFlagNotSet('triggeree_started') | |
706 self.failIfFlagSet('triggeree_finished') | |
707 self.failIfFlagNotSet('triggerer_finished') | |
708 | |
709 def testTriggerBuildWait(self): | |
710 return self.setupTest("schedulerNames=['triggeree'], waitForFinish=1", | |
711 "Dummy", | |
712 self._checkTriggerBuildWait) | |
713 | |
714 def _checkTriggerBuildWait(self, res): | |
715 self.failIfFlagNotSet('triggerer_started') | |
716 self.failIfFlagNotSet('triggeree_started') | |
717 self.failIfFlagSet('triggeree_finished') | |
718 self.failIfFlagSet('triggerer_finished') | |
719 | |
720 def testProperties(self): | |
721 return self.setupTest(""" | |
722 schedulerNames=['triggeree'], | |
723 set_properties={'lit' : 'lit'}, | |
724 copy_properties=['dyn'] | |
725 """, """ | |
726 SetTestFlagStep, flagname='props', | |
727 value=WithProperties('%(lit:-MISSING)s:%(dyn:-MISSING)s') | |
728 """, | |
729 self._checkProperties) | |
730 | |
731 def _checkProperties(self, res): | |
732 self.assertEqual(self.getFlag("props"), "lit:dyn") | |
733 | |
734 class PropertyPropagation(RunMixin, TestFlagMixin, unittest.TestCase): | |
735 def setupTest(self, config, builders, checkFn, changeProps={}): | |
736 self.clearFlags() | |
737 m = self.master | |
738 m.loadConfig(config) | |
739 m.readConfig = True | |
740 m.startService() | |
741 | |
742 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff", | |
743 properties=changeProps) | |
744 m.change_svc.addChange(c) | |
745 | |
746 d = self.connectSlave(builders=builders) | |
747 d.addCallback(self.startTimer, 0.5, checkFn) | |
748 return d | |
749 | |
750 def startTimer(self, res, time, next_fn): | |
751 d = defer.Deferred() | |
752 reactor.callLater(time, d.callback, None) | |
753 d.addCallback(next_fn) | |
754 return d | |
755 | |
756 config_schprop = config_base + """ | |
757 from buildbot.scheduler import Scheduler | |
758 from buildbot.steps.dummy import Dummy | |
759 from buildbot.test.runutils import SetTestFlagStep | |
760 from buildbot.process.properties import WithProperties | |
761 c['schedulers'] = [ | |
762 Scheduler('mysched', None, 0.1, ['flagcolor'], properties={'color':'red'}), | |
763 ] | |
764 factory = factory.BuildFactory([ | |
765 s(SetTestFlagStep, flagname='testresult', | |
766 value=WithProperties('color=%(color)s sched=%(scheduler)s')), | |
767 ]) | |
768 c['builders'] = [{'name': 'flagcolor', 'slavename': 'bot1', | |
769 'builddir': 'test', 'factory': factory}, | |
770 ] | |
771 """ | |
772 | |
773 def testScheduler(self): | |
774 def _check(res): | |
775 self.failUnlessEqual(self.getFlag('testresult'), | |
776 'color=red sched=mysched') | |
777 return self.setupTest(self.config_schprop, ['flagcolor'], _check) | |
778 | |
779 config_changeprop = config_base + """ | |
780 from buildbot.scheduler import Scheduler | |
781 from buildbot.steps.dummy import Dummy | |
782 from buildbot.test.runutils import SetTestFlagStep | |
783 from buildbot.process.properties import WithProperties | |
784 c['schedulers'] = [ | |
785 Scheduler('mysched', None, 0.1, ['flagcolor'], properties={'color':'red'}), | |
786 ] | |
787 factory = factory.BuildFactory([ | |
788 s(SetTestFlagStep, flagname='testresult', | |
789 value=WithProperties('color=%(color)s sched=%(scheduler)s prop1=%(prop1)s'
)), | |
790 ]) | |
791 c['builders'] = [{'name': 'flagcolor', 'slavename': 'bot1', | |
792 'builddir': 'test', 'factory': factory}, | |
793 ] | |
794 """ | |
795 | |
796 def testChangeProp(self): | |
797 def _check(res): | |
798 self.failUnlessEqual(self.getFlag('testresult'), | |
799 'color=blue sched=mysched prop1=prop1') | |
800 return self.setupTest(self.config_changeprop, ['flagcolor'], _check, | |
801 changeProps={'color': 'blue', 'prop1': 'prop1'}) | |
802 | |
803 config_slaveprop = config_base + """ | |
804 from buildbot.scheduler import Scheduler | |
805 from buildbot.steps.dummy import Dummy | |
806 from buildbot.test.runutils import SetTestFlagStep | |
807 from buildbot.process.properties import WithProperties | |
808 c['schedulers'] = [ | |
809 Scheduler('mysched', None, 0.1, ['flagcolor']) | |
810 ] | |
811 c['slaves'] = [BuildSlave('bot1', 'sekrit', properties={'color':'orange'})] | |
812 factory = factory.BuildFactory([ | |
813 s(SetTestFlagStep, flagname='testresult', | |
814 value=WithProperties('color=%(color)s slavename=%(slavename)s')), | |
815 ]) | |
816 c['builders'] = [{'name': 'flagcolor', 'slavename': 'bot1', | |
817 'builddir': 'test', 'factory': factory}, | |
818 ] | |
819 """ | |
820 def testSlave(self): | |
821 def _check(res): | |
822 self.failUnlessEqual(self.getFlag('testresult'), | |
823 'color=orange slavename=bot1') | |
824 return self.setupTest(self.config_slaveprop, ['flagcolor'], _check) | |
825 | |
826 config_trigger = config_base + """ | |
827 from buildbot.scheduler import Triggerable, Scheduler | |
828 from buildbot.steps.trigger import Trigger | |
829 from buildbot.steps.dummy import Dummy | |
830 from buildbot.test.runutils import SetTestFlagStep | |
831 from buildbot.process.properties import WithProperties | |
832 c['schedulers'] = [ | |
833 Scheduler('triggerer', None, 0.1, ['triggerer'], | |
834 properties={'color':'mauve', 'pls_trigger':'triggeree'}), | |
835 Triggerable('triggeree', ['triggeree'], properties={'color':'invisible'}) | |
836 ] | |
837 triggerer = factory.BuildFactory([ | |
838 s(SetTestFlagStep, flagname='testresult', value='wrongone'), | |
839 s(Trigger, flunkOnFailure=True, | |
840 schedulerNames=[WithProperties('%(pls_trigger)s')], | |
841 set_properties={'color' : WithProperties('%(color)s')}), | |
842 s(SetTestFlagStep, flagname='testresult', value='triggered'), | |
843 ]) | |
844 triggeree = factory.BuildFactory([ | |
845 s(SetTestFlagStep, flagname='testresult', | |
846 value=WithProperties('sched=%(scheduler)s color=%(color)s')), | |
847 ]) | |
848 c['builders'] = [{'name': 'triggerer', 'slavename': 'bot1', | |
849 'builddir': 'triggerer', 'factory': triggerer}, | |
850 {'name': 'triggeree', 'slavename': 'bot1', | |
851 'builddir': 'triggeree', 'factory': triggeree}] | |
852 """ | |
853 def testTrigger(self): | |
854 def _check(res): | |
855 self.failUnlessEqual(self.getFlag('testresult'), | |
856 'sched=triggeree color=mauve') | |
857 return self.setupTest(self.config_trigger, | |
858 ['triggerer', 'triggeree'], _check) | |
859 | |
860 | |
861 config_test_flag = config_base + """ | |
862 from buildbot.scheduler import Scheduler | |
863 c['schedulers'] = [Scheduler('quick', None, 0.1, ['dummy'])] | |
864 | |
865 from buildbot.test.runutils import SetTestFlagStep | |
866 f3 = factory.BuildFactory([ | |
867 s(SetTestFlagStep, flagname='foo', value='bar'), | |
868 ]) | |
869 | |
870 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1', | |
871 'builddir': 'dummy', 'factory': f3}] | |
872 """ | |
873 | |
874 class TestFlag(RunMixin, TestFlagMixin, unittest.TestCase): | |
875 """Test for the TestFlag functionality in runutils""" | |
876 def testTestFlag(self): | |
877 m = self.master | |
878 m.loadConfig(config_test_flag) | |
879 m.readConfig = True | |
880 m.startService() | |
881 | |
882 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") | |
883 m.change_svc.addChange(c) | |
884 | |
885 d = self.connectSlave() | |
886 d.addCallback(self._testTestFlag_1) | |
887 return d | |
888 | |
889 def _testTestFlag_1(self, res): | |
890 d = defer.Deferred() | |
891 reactor.callLater(0.5, d.callback, None) | |
892 d.addCallback(self._testTestFlag_2) | |
893 return d | |
894 | |
895 def _testTestFlag_2(self, res): | |
896 self.failUnlessEqual(self.getFlag('foo'), 'bar') | |
897 | |
898 # TODO: test everything, from Change submission to Scheduler to Build to | |
899 # Status. Use all the status types. Specifically I want to catch recurrences | |
900 # of the bug where I forgot to make Waterfall inherit from StatusReceiver | |
901 # such that buildSetSubmitted failed. | |
902 | |
903 config_test_builder = config_base + """ | |
904 from buildbot.scheduler import Scheduler | |
905 c['schedulers'] = [Scheduler('quick', 'dummy', 0.1, ['dummy']), | |
906 Scheduler('quick2', 'dummy2', 0.1, ['dummy2']), | |
907 Scheduler('quick3', 'dummy3', 0.1, ['dummy3'])] | |
908 | |
909 from buildbot.steps.shell import ShellCommand | |
910 f3 = factory.BuildFactory([ | |
911 s(ShellCommand, command="sleep 3", env={'blah':'blah'}) | |
912 ]) | |
913 | |
914 c['builders'] = [{'name': 'dummy', 'slavename': 'bot1', 'env': {'foo':'bar'}, | |
915 'builddir': 'dummy', 'factory': f3}] | |
916 | |
917 c['builders'].append({'name': 'dummy2', 'slavename': 'bot1', | |
918 'env': {'blah':'bar'}, 'builddir': 'dummy2', | |
919 'factory': f3}) | |
920 | |
921 f4 = factory.BuildFactory([ | |
922 s(ShellCommand, command="sleep 3") | |
923 ]) | |
924 | |
925 c['builders'].append({'name': 'dummy3', 'slavename': 'bot1', | |
926 'env': {'blah':'bar'}, 'builddir': 'dummy3', | |
927 'factory': f4}) | |
928 """ | |
929 | |
930 class TestBuilder(RunMixin, unittest.TestCase): | |
931 def setUp(self): | |
932 RunMixin.setUp(self) | |
933 self.master.loadConfig(config_test_builder) | |
934 self.master.readConfig = True | |
935 self.master.startService() | |
936 self.connectSlave(builders=["dummy", "dummy2", "dummy3"]) | |
937 | |
938 def doBuilderEnvTest(self, branch, cb): | |
939 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed", | |
940 branch=branch) | |
941 self.master.change_svc.addChange(c) | |
942 | |
943 d = defer.Deferred() | |
944 reactor.callLater(0.5, d.callback, None) | |
945 d.addCallback(cb) | |
946 | |
947 return d | |
948 | |
949 def testBuilderEnv(self): | |
950 return self.doBuilderEnvTest("dummy", self._testBuilderEnv1) | |
951 | |
952 def _testBuilderEnv1(self, res): | |
953 b = self.master.botmaster.builders['dummy'] | |
954 build = b.building[0] | |
955 s = build.currentStep | |
956 self.failUnless('foo' in s.cmd.args['env']) | |
957 self.failUnlessEqual('bar', s.cmd.args['env']['foo']) | |
958 self.failUnless('blah' in s.cmd.args['env']) | |
959 self.failUnlessEqual('blah', s.cmd.args['env']['blah']) | |
960 | |
961 def testBuilderEnvOverride(self): | |
962 return self.doBuilderEnvTest("dummy2", self._testBuilderEnvOverride1) | |
963 | |
964 def _testBuilderEnvOverride1(self, res): | |
965 b = self.master.botmaster.builders['dummy2'] | |
966 build = b.building[0] | |
967 s = build.currentStep | |
968 self.failUnless('blah' in s.cmd.args['env']) | |
969 self.failUnlessEqual('blah', s.cmd.args['env']['blah']) | |
970 | |
971 def testBuilderNoStepEnv(self): | |
972 return self.doBuilderEnvTest("dummy3", self._testBuilderNoStepEnv1) | |
973 | |
974 def _testBuilderNoStepEnv1(self, res): | |
975 b = self.master.botmaster.builders['dummy3'] | |
976 build = b.building[0] | |
977 s = build.currentStep | |
978 self.failUnless('blah' in s.cmd.args['env']) | |
979 self.failUnlessEqual('bar', s.cmd.args['env']['blah']) | |
980 | |
981 class SchedulerWatchers(RunMixin, TestFlagMixin, unittest.TestCase): | |
982 config_watchable = config_base + """ | |
983 from buildbot.scheduler import AnyBranchScheduler | |
984 from buildbot.steps.dummy import Dummy | |
985 from buildbot.test.runutils import setTestFlag, SetTestFlagStep | |
986 s = AnyBranchScheduler( | |
987 name='abs', | |
988 branches=None, | |
989 treeStableTimer=0, | |
990 builderNames=['a', 'b']) | |
991 c['schedulers'] = [ s ] | |
992 | |
993 # count the number of times a success watcher is called | |
994 numCalls = [ 0 ] | |
995 def watcher(ss): | |
996 numCalls[0] += 1 | |
997 setTestFlag("numCalls", numCalls[0]) | |
998 s.subscribeToSuccessfulBuilds(watcher) | |
999 | |
1000 f = factory.BuildFactory() | |
1001 f.addStep(Dummy(timeout=0)) | |
1002 c['builders'] = [{'name': 'a', 'slavename': 'bot1', | |
1003 'builddir': 'a', 'factory': f}, | |
1004 {'name': 'b', 'slavename': 'bot1', | |
1005 'builddir': 'b', 'factory': f}] | |
1006 """ | |
1007 | |
1008 def testWatchers(self): | |
1009 self.clearFlags() | |
1010 m = self.master | |
1011 m.loadConfig(self.config_watchable) | |
1012 m.readConfig = True | |
1013 m.startService() | |
1014 | |
1015 c = changes.Change("bob", ["Makefile", "foo/bar.c"], "changed stuff") | |
1016 m.change_svc.addChange(c) | |
1017 | |
1018 d = self.connectSlave(builders=['a', 'b']) | |
1019 | |
1020 def pause(res): | |
1021 d = defer.Deferred() | |
1022 reactor.callLater(1, d.callback, res) | |
1023 return d | |
1024 d.addCallback(pause) | |
1025 | |
1026 def checkFn(res): | |
1027 self.failUnlessEqual(self.getFlag('numCalls'), 1) | |
1028 d.addCallback(checkFn) | |
1029 return d | |
1030 | |
1031 config_priority = """ | |
1032 from buildbot.process import factory | |
1033 from buildbot.steps import dummy | |
1034 from buildbot.buildslave import BuildSlave | |
1035 s = factory.s | |
1036 | |
1037 from buildbot.steps.shell import ShellCommand | |
1038 f1 = factory.BuildFactory([ | |
1039 s(ShellCommand, command="sleep 1", env={'blah':'blah'}) | |
1040 ]) | |
1041 | |
1042 BuildmasterConfig = c = {} | |
1043 slavenames = ['bot%i' % i for i in range(5)] | |
1044 c['slaves'] = [BuildSlave(name, 'sekrit', max_builds=1) for name in slavenames] | |
1045 c['schedulers'] = [] | |
1046 c['builders'] = [] | |
1047 c['builders'].append({'name':'quick1', 'slavenames':slavenames, 'builddir': 'qui
ckdir1', 'factory': f1}) | |
1048 c['builders'].append({'name':'quick2', 'slavenames':slavenames, 'builddir': 'qui
ckdir2', 'factory': f1}) | |
1049 c['slavePortnum'] = 0 | |
1050 """ | |
1051 | |
1052 class BuildPrioritization(RunMixin, unittest.TestCase): | |
1053 def rmtree(self, d): | |
1054 rmtree(d) | |
1055 | |
1056 def testPriority(self): | |
1057 self.rmtree("basedir") | |
1058 os.mkdir("basedir") | |
1059 self.master.loadConfig(config_priority) | |
1060 self.master.readConfig = True | |
1061 self.master.startService() | |
1062 | |
1063 # Our fake source stamp | |
1064 # we override canBeMergedWith so that our requests don't get merged toge
ther | |
1065 ss = SourceStamp() | |
1066 ss.canBeMergedWith = lambda x: False | |
1067 | |
1068 # Send 10 requests to alternating builders | |
1069 # We fudge the submittedAt field after submitting since they're all | |
1070 # getting submitted so close together according to time.time() | |
1071 # and all we care about is what order they're run in. | |
1072 reqs = [] | |
1073 self.start_order = [] | |
1074 for i in range(10): | |
1075 req = BuildRequest(str(i), ss, "test_builder") | |
1076 j = i % 2 + 1 | |
1077 self.master.botmaster.builders['quick%i' % j].submitBuildRequest(req
) | |
1078 req.submittedAt = i | |
1079 # Keep track of what order the builds start in | |
1080 def append(build): | |
1081 self.start_order.append(int(build.reason)) | |
1082 req.subscribe(append) | |
1083 reqs.append(req.waitUntilFinished()) | |
1084 | |
1085 dl = defer.DeferredList(reqs) | |
1086 dl.addCallback(self._all_finished) | |
1087 | |
1088 def _delay(res): | |
1089 d1 = defer.Deferred() | |
1090 reactor.callLater(0.5, d1.callback, None) | |
1091 # this test depends upon this 0.5s delay landing us in the middle | |
1092 # of one of the builds. | |
1093 return d1 | |
1094 | |
1095 def _connect(res, i): | |
1096 return self.connectSlave(slavename="bot%i" % i, builders=["quick1",
"quick2"]) | |
1097 | |
1098 # Now add the slaves | |
1099 d = self.connectSlave(slavename="bot0", builders=["quick1", "quick2"]) | |
1100 for i in range(1,5): | |
1101 d.addCallback(_delay) | |
1102 d.addCallback(_connect, i) | |
1103 | |
1104 d.addCallback(lambda x: dl) | |
1105 | |
1106 return d | |
1107 | |
1108 def _all_finished(self, *args): | |
1109 # The builds should have finished in proper order | |
1110 self.failUnlessEqual(self.start_order, range(10)) | |
1111 | |
1112 # Test graceful shutdown when no builds are active, as well as | |
1113 # canStartBuild after graceful shutdown is initiated | |
1114 config_graceful_shutdown_idle = config_base | |
1115 class GracefulShutdownIdle(RunMixin, unittest.TestCase): | |
1116 def testShutdown(self): | |
1117 self.rmtree("basedir") | |
1118 os.mkdir("basedir") | |
1119 self.master.loadConfig(config_graceful_shutdown_idle) | |
1120 self.master.readConfig = True | |
1121 self.master.startService() | |
1122 d = self.connectSlave(builders=['quick']) | |
1123 d.addCallback(self._do_shutdown) | |
1124 return d | |
1125 | |
1126 def _do_shutdown(self, res): | |
1127 bs = self.master.botmaster.builders['quick'].slaves[0] | |
1128 # Check that the slave is accepting builds once it's connected | |
1129 self.assertEquals(bs.slave.canStartBuild(), True) | |
1130 | |
1131 # Monkeypatch the slave's shutdown routine since the real shutdown | |
1132 # interrupts the test harness | |
1133 self.did_shutdown = False | |
1134 def _shutdown(): | |
1135 self.did_shutdown = True | |
1136 bs.slave.shutdown = _shutdown | |
1137 | |
1138 # Start a graceful shutdown | |
1139 bs.slave.slave_status.setGraceful(True) | |
1140 # Check that the slave isn't accepting builds any more | |
1141 self.assertEquals(bs.slave.canStartBuild(), False) | |
1142 | |
1143 # Wait a little bit and then check that we (pretended to) shut down | |
1144 d = defer.Deferred() | |
1145 d.addCallback(self._check_shutdown) | |
1146 reactor.callLater(0.5, d.callback, None) | |
1147 return d | |
1148 | |
1149 def _check_shutdown(self, res): | |
1150 self.assertEquals(self.did_shutdown, True) | |
1151 | |
1152 # Test graceful shutdown when two builds are active | |
1153 config_graceful_shutdown_busy = config_base + """ | |
1154 from buildbot.buildslave import BuildSlave | |
1155 c['slaves'] = [ BuildSlave('bot1', 'sekrit', max_builds=2) ] | |
1156 | |
1157 from buildbot.scheduler import Scheduler | |
1158 c['schedulers'] = [Scheduler('dummy', None, 0.1, ['dummy', 'dummy2'])] | |
1159 | |
1160 c['builders'].append({'name': 'dummy', 'slavename': 'bot1', | |
1161 'builddir': 'dummy', 'factory': f2}) | |
1162 c['builders'].append({'name': 'dummy2', 'slavename': 'bot1', | |
1163 'builddir': 'dummy2', 'factory': f2}) | |
1164 """ | |
1165 class GracefulShutdownBusy(RunMixin, unittest.TestCase): | |
1166 def testShutdown(self): | |
1167 self.rmtree("basedir") | |
1168 os.mkdir("basedir") | |
1169 d = self.master.loadConfig(config_graceful_shutdown_busy) | |
1170 d.addCallback(lambda res: self.master.startService()) | |
1171 d.addCallback(lambda res: self.connectSlave()) | |
1172 | |
1173 def _send(res): | |
1174 # send a change. This will trigger both builders at the same | |
1175 # time, but since they share a slave, the max_builds=1 setting | |
1176 # will insure that only one of the two builds gets to run. | |
1177 cm = self.master.change_svc | |
1178 c = changes.Change("bob", ["Makefile", "foo/bar.c"], | |
1179 "changed stuff") | |
1180 cm.addChange(c) | |
1181 d.addCallback(_send) | |
1182 | |
1183 def _delay(res): | |
1184 d1 = defer.Deferred() | |
1185 reactor.callLater(0.5, d1.callback, None) | |
1186 # this test depends upon this 0.5s delay landing us in the middle | |
1187 # of one of the builds. | |
1188 return d1 | |
1189 d.addCallback(_delay) | |
1190 | |
1191 # Start a graceful shutdown. We should be in the middle of two builds | |
1192 def _shutdown(res): | |
1193 bs = self.master.botmaster.builders['dummy'].slaves[0] | |
1194 # Monkeypatch the slave's shutdown routine since the real shutdown | |
1195 # interrupts the test harness | |
1196 self.did_shutdown = False | |
1197 def _shutdown(): | |
1198 self.did_shutdown = True | |
1199 return defer.succeed(None) | |
1200 bs.slave.shutdown = _shutdown | |
1201 # Start a graceful shutdown | |
1202 bs.slave.slave_status.setGraceful(True) | |
1203 | |
1204 builders = [ self.master.botmaster.builders[bn] | |
1205 for bn in ('dummy', 'dummy2') ] | |
1206 for builder in builders: | |
1207 self.failUnless(len(builder.slaves) == 1) | |
1208 from buildbot.process.builder import BUILDING | |
1209 building_bs = [ builder | |
1210 for builder in builders | |
1211 if builder.slaves[0].state == BUILDING ] | |
1212 # assert that both builds are running right now. | |
1213 self.failUnlessEqual(len(building_bs), 2) | |
1214 | |
1215 d.addCallback(_shutdown) | |
1216 | |
1217 # Wait a little bit again, and then make sure that we are still running | |
1218 # the two builds, and haven't shutdown yet | |
1219 d.addCallback(_delay) | |
1220 def _check(res): | |
1221 self.assertEquals(self.did_shutdown, False) | |
1222 builders = [ self.master.botmaster.builders[bn] | |
1223 for bn in ('dummy', 'dummy2') ] | |
1224 for builder in builders: | |
1225 self.failUnless(len(builder.slaves) == 1) | |
1226 from buildbot.process.builder import BUILDING | |
1227 building_bs = [ builder | |
1228 for builder in builders | |
1229 if builder.slaves[0].state == BUILDING ] | |
1230 # assert that both builds are running right now. | |
1231 self.failUnlessEqual(len(building_bs), 2) | |
1232 d.addCallback(_check) | |
1233 | |
1234 # Wait for all the builds to finish | |
1235 def _wait_finish(res): | |
1236 builders = [ self.master.botmaster.builders[bn] | |
1237 for bn in ('dummy', 'dummy2') ] | |
1238 builds = [] | |
1239 for builder in builders: | |
1240 builds.append(builder.builder_status.currentBuilds[0].waitUntilF
inished()) | |
1241 dl = defer.DeferredList(builds) | |
1242 return dl | |
1243 d.addCallback(_wait_finish) | |
1244 | |
1245 # Wait a little bit after the builds finish, and then | |
1246 # check that the slave has shutdown | |
1247 d.addCallback(_delay) | |
1248 def _check_shutdown(res): | |
1249 # assert that we shutdown the slave | |
1250 self.assertEquals(self.did_shutdown, True) | |
1251 builders = [ self.master.botmaster.builders[bn] | |
1252 for bn in ('dummy', 'dummy2') ] | |
1253 from buildbot.process.builder import BUILDING | |
1254 building_bs = [ builder | |
1255 for builder in builders | |
1256 if builder.slaves[0].state == BUILDING ] | |
1257 # assert that no builds are running right now. | |
1258 self.failUnlessEqual(len(building_bs), 0) | |
1259 d.addCallback(_check_shutdown) | |
1260 | |
1261 return d | |
OLD | NEW |