| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: buildbot.test.test_locks -*- | |
| 2 | |
| 3 import random | |
| 4 | |
| 5 from twisted.trial import unittest | |
| 6 from twisted.internet import defer, reactor | |
| 7 | |
| 8 from buildbot import master | |
| 9 from buildbot.steps import dummy | |
| 10 from buildbot.sourcestamp import SourceStamp | |
| 11 from buildbot.process.base import BuildRequest | |
| 12 from buildbot.test.runutils import RunMixin | |
| 13 from buildbot import locks | |
| 14 | |
| 15 def claimHarder(lock, owner, la): | |
| 16 """Return a Deferred that will fire when the lock is claimed. Keep trying | |
| 17 until we succeed.""" | |
| 18 if lock.isAvailable(la): | |
| 19 #print "claimHarder(%s): claiming" % owner | |
| 20 lock.claim(owner, la) | |
| 21 return defer.succeed(lock) | |
| 22 #print "claimHarder(%s): waiting" % owner | |
| 23 d = lock.waitUntilMaybeAvailable(owner, la) | |
| 24 d.addCallback(claimHarder, owner, la) | |
| 25 return d | |
| 26 | |
| 27 def hold(lock, owner, la, mode="now"): | |
| 28 if mode == "now": | |
| 29 lock.release(owner, la) | |
| 30 elif mode == "very soon": | |
| 31 reactor.callLater(0, lock.release, owner, la) | |
| 32 elif mode == "soon": | |
| 33 reactor.callLater(0.1, lock.release, owner, la) | |
| 34 | |
| 35 class Unit(unittest.TestCase): | |
| 36 def testNowCounting(self): | |
| 37 lid = locks.MasterLock('dummy') | |
| 38 la = locks.LockAccess(lid, 'counting') | |
| 39 return self._testNow(la) | |
| 40 | |
| 41 def testNowExclusive(self): | |
| 42 lid = locks.MasterLock('dummy') | |
| 43 la = locks.LockAccess(lid, 'exclusive') | |
| 44 return self._testNow(la) | |
| 45 | |
| 46 def _testNow(self, la): | |
| 47 l = locks.BaseLock("name") | |
| 48 self.failUnless(l.isAvailable(la)) | |
| 49 l.claim("owner1", la) | |
| 50 self.failIf(l.isAvailable(la)) | |
| 51 l.release("owner1", la) | |
| 52 self.failUnless(l.isAvailable(la)) | |
| 53 | |
| 54 def testNowMixed1(self): | |
| 55 """ Test exclusive is not possible when a counting has the lock """ | |
| 56 lid = locks.MasterLock('dummy') | |
| 57 lac = locks.LockAccess(lid, 'counting') | |
| 58 lae = locks.LockAccess(lid, 'exclusive') | |
| 59 l = locks.BaseLock("name", maxCount=2) | |
| 60 self.failUnless(l.isAvailable(lac)) | |
| 61 l.claim("count-owner", lac) | |
| 62 self.failIf(l.isAvailable(lae)) | |
| 63 l.release("count-owner", lac) | |
| 64 self.failUnless(l.isAvailable(lac)) | |
| 65 | |
| 66 def testNowMixed2(self): | |
| 67 """ Test counting is not possible when an exclsuive has the lock """ | |
| 68 lid = locks.MasterLock('dummy') | |
| 69 lac = locks.LockAccess(lid, 'counting') | |
| 70 lae = locks.LockAccess(lid, 'exclusive') | |
| 71 l = locks.BaseLock("name", maxCount=2) | |
| 72 self.failUnless(l.isAvailable(lae)) | |
| 73 l.claim("count-owner", lae) | |
| 74 self.failIf(l.isAvailable(lac)) | |
| 75 l.release("count-owner", lae) | |
| 76 self.failUnless(l.isAvailable(lae)) | |
| 77 | |
| 78 def testLaterCounting(self): | |
| 79 lid = locks.MasterLock('dummy') | |
| 80 la = locks.LockAccess(lid, 'counting') | |
| 81 return self._testLater(la) | |
| 82 | |
| 83 def testLaterExclusive(self): | |
| 84 lid = locks.MasterLock('dummy') | |
| 85 la = locks.LockAccess(lid, 'exclusive') | |
| 86 return self._testLater(la) | |
| 87 | |
| 88 def _testLater(self, la): | |
| 89 lock = locks.BaseLock("name") | |
| 90 d = claimHarder(lock, "owner1", la) | |
| 91 d.addCallback(lambda lock: lock.release("owner1", la)) | |
| 92 return d | |
| 93 | |
| 94 def testCompetitionCounting(self): | |
| 95 lid = locks.MasterLock('dummy') | |
| 96 la = locks.LockAccess(lid, 'counting') | |
| 97 return self._testCompetition(la) | |
| 98 | |
| 99 def testCompetitionExclusive(self): | |
| 100 lid = locks.MasterLock('dummy') | |
| 101 la = locks.LockAccess(lid, 'exclusive') | |
| 102 return self._testCompetition(la) | |
| 103 | |
| 104 def _testCompetition(self, la): | |
| 105 lock = locks.BaseLock("name") | |
| 106 d = claimHarder(lock, "owner1", la) | |
| 107 d.addCallback(self._claim1, la) | |
| 108 return d | |
| 109 def _claim1(self, lock, la): | |
| 110 # we should have claimed it by now | |
| 111 self.failIf(lock.isAvailable(la)) | |
| 112 # now set up two competing owners. We don't know which will get the | |
| 113 # lock first. | |
| 114 d2 = claimHarder(lock, "owner2", la) | |
| 115 d2.addCallback(hold, "owner2", la, "now") | |
| 116 d3 = claimHarder(lock, "owner3", la) | |
| 117 d3.addCallback(hold, "owner3", la, "soon") | |
| 118 dl = defer.DeferredList([d2,d3]) | |
| 119 dl.addCallback(self._cleanup, lock, la) | |
| 120 # and release the lock in a moment | |
| 121 reactor.callLater(0.1, lock.release, "owner1", la) | |
| 122 return dl | |
| 123 | |
| 124 def _cleanup(self, res, lock, la): | |
| 125 d = claimHarder(lock, "cleanup", la) | |
| 126 d.addCallback(lambda lock: lock.release("cleanup", la)) | |
| 127 return d | |
| 128 | |
| 129 def testRandomCounting(self): | |
| 130 lid = locks.MasterLock('dummy') | |
| 131 la = locks.LockAccess(lid, 'counting') | |
| 132 return self._testRandom(la) | |
| 133 | |
| 134 def testRandomExclusive(self): | |
| 135 lid = locks.MasterLock('dummy') | |
| 136 la = locks.LockAccess(lid, 'exclusive') | |
| 137 return self._testRandom(la) | |
| 138 | |
| 139 def _testRandom(self, la): | |
| 140 lock = locks.BaseLock("name") | |
| 141 dl = [] | |
| 142 for i in range(100): | |
| 143 owner = "owner%d" % i | |
| 144 mode = random.choice(["now", "very soon", "soon"]) | |
| 145 d = claimHarder(lock, owner, la) | |
| 146 d.addCallback(hold, owner, la, mode) | |
| 147 dl.append(d) | |
| 148 d = defer.DeferredList(dl) | |
| 149 d.addCallback(self._cleanup, lock, la) | |
| 150 return d | |
| 151 | |
| 152 class Multi(unittest.TestCase): | |
| 153 def testNowCounting(self): | |
| 154 lid = locks.MasterLock('dummy') | |
| 155 la = locks.LockAccess(lid, 'counting') | |
| 156 lock = locks.BaseLock("name", 2) | |
| 157 self.failUnless(lock.isAvailable(la)) | |
| 158 lock.claim("owner1", la) | |
| 159 self.failUnless(lock.isAvailable(la)) | |
| 160 lock.claim("owner2", la) | |
| 161 self.failIf(lock.isAvailable(la)) | |
| 162 lock.release("owner1", la) | |
| 163 self.failUnless(lock.isAvailable(la)) | |
| 164 lock.release("owner2", la) | |
| 165 self.failUnless(lock.isAvailable(la)) | |
| 166 | |
| 167 def testLaterCounting(self): | |
| 168 lid = locks.MasterLock('dummy') | |
| 169 la = locks.LockAccess(lid, 'counting') | |
| 170 lock = locks.BaseLock("name", 2) | |
| 171 lock.claim("owner1", la) | |
| 172 lock.claim("owner2", la) | |
| 173 d = claimHarder(lock, "owner3", la) | |
| 174 d.addCallback(lambda lock: lock.release("owner3", la)) | |
| 175 lock.release("owner2", la) | |
| 176 lock.release("owner1", la) | |
| 177 return d | |
| 178 | |
| 179 def _cleanup(self, res, lock, count, la): | |
| 180 dl = [] | |
| 181 for i in range(count): | |
| 182 d = claimHarder(lock, "cleanup%d" % i, la) | |
| 183 dl.append(d) | |
| 184 d2 = defer.DeferredList(dl) | |
| 185 # once all locks are claimed, we know that any previous owners have | |
| 186 # been flushed out | |
| 187 def _release(res): | |
| 188 for i in range(count): | |
| 189 lock.release("cleanup%d" % i, la) | |
| 190 d2.addCallback(_release) | |
| 191 return d2 | |
| 192 | |
| 193 def testRandomCounting(self): | |
| 194 lid = locks.MasterLock('dummy') | |
| 195 la = locks.LockAccess(lid, 'counting') | |
| 196 COUNT = 5 | |
| 197 lock = locks.BaseLock("name", COUNT) | |
| 198 dl = [] | |
| 199 for i in range(100): | |
| 200 owner = "owner%d" % i | |
| 201 mode = random.choice(["now", "very soon", "soon"]) | |
| 202 d = claimHarder(lock, owner, la) | |
| 203 def _check(lock): | |
| 204 self.failIf(len(lock.owners) > COUNT) | |
| 205 return lock | |
| 206 d.addCallback(_check) | |
| 207 d.addCallback(hold, owner, la, mode) | |
| 208 dl.append(d) | |
| 209 d = defer.DeferredList(dl) | |
| 210 d.addCallback(self._cleanup, lock, COUNT, la) | |
| 211 return d | |
| 212 | |
| 213 class Dummy: | |
| 214 pass | |
| 215 | |
| 216 def slave(slavename): | |
| 217 slavebuilder = Dummy() | |
| 218 slavebuilder.slave = Dummy() | |
| 219 slavebuilder.slave.slavename = slavename | |
| 220 return slavebuilder | |
| 221 | |
| 222 class MakeRealLock(unittest.TestCase): | |
| 223 | |
| 224 def make(self, lockid): | |
| 225 return lockid.lockClass(lockid) | |
| 226 | |
| 227 def testMaster(self): | |
| 228 mid1 = locks.MasterLock("name1") | |
| 229 mid2 = locks.MasterLock("name1") | |
| 230 mid3 = locks.MasterLock("name3") | |
| 231 mid4 = locks.MasterLock("name1", 3) | |
| 232 self.failUnlessEqual(mid1, mid2) | |
| 233 self.failIfEqual(mid1, mid3) | |
| 234 # they should all be hashable | |
| 235 d = {mid1: 1, mid2: 2, mid3: 3, mid4: 4} | |
| 236 | |
| 237 l1 = self.make(mid1) | |
| 238 self.failUnlessEqual(l1.name, "name1") | |
| 239 self.failUnlessEqual(l1.maxCount, 1) | |
| 240 self.failUnlessIdentical(l1.getLock(slave("slave1")), l1) | |
| 241 l4 = self.make(mid4) | |
| 242 self.failUnlessEqual(l4.name, "name1") | |
| 243 self.failUnlessEqual(l4.maxCount, 3) | |
| 244 self.failUnlessIdentical(l4.getLock(slave("slave1")), l4) | |
| 245 | |
| 246 def testSlave(self): | |
| 247 sid1 = locks.SlaveLock("name1") | |
| 248 sid2 = locks.SlaveLock("name1") | |
| 249 sid3 = locks.SlaveLock("name3") | |
| 250 sid4 = locks.SlaveLock("name1", maxCount=3) | |
| 251 mcfs = {"bigslave": 4, "smallslave": 1} | |
| 252 sid5 = locks.SlaveLock("name1", maxCount=3, maxCountForSlave=mcfs) | |
| 253 mcfs2 = {"bigslave": 4, "smallslave": 1} | |
| 254 sid5a = locks.SlaveLock("name1", maxCount=3, maxCountForSlave=mcfs2) | |
| 255 mcfs3 = {"bigslave": 1, "smallslave": 99} | |
| 256 sid5b = locks.SlaveLock("name1", maxCount=3, maxCountForSlave=mcfs3) | |
| 257 self.failUnlessEqual(sid1, sid2) | |
| 258 self.failIfEqual(sid1, sid3) | |
| 259 self.failIfEqual(sid1, sid4) | |
| 260 self.failIfEqual(sid1, sid5) | |
| 261 self.failUnlessEqual(sid5, sid5a) | |
| 262 self.failIfEqual(sid5a, sid5b) | |
| 263 # they should all be hashable | |
| 264 d = {sid1: 1, sid2: 2, sid3: 3, sid4: 4, sid5: 5, sid5a: 6, sid5b: 7} | |
| 265 | |
| 266 l1 = self.make(sid1) | |
| 267 self.failUnlessEqual(l1.name, "name1") | |
| 268 self.failUnlessEqual(l1.maxCount, 1) | |
| 269 l1s1 = l1.getLock(slave("slave1")) | |
| 270 self.failIfIdentical(l1s1, l1) | |
| 271 | |
| 272 l4 = self.make(sid4) | |
| 273 self.failUnlessEqual(l4.maxCount, 3) | |
| 274 l4s1 = l4.getLock(slave("slave1")) | |
| 275 self.failUnlessEqual(l4s1.maxCount, 3) | |
| 276 | |
| 277 l5 = self.make(sid5) | |
| 278 l5s1 = l5.getLock(slave("bigslave")) | |
| 279 l5s2 = l5.getLock(slave("smallslave")) | |
| 280 l5s3 = l5.getLock(slave("unnamedslave")) | |
| 281 self.failUnlessEqual(l5s1.maxCount, 4) | |
| 282 self.failUnlessEqual(l5s2.maxCount, 1) | |
| 283 self.failUnlessEqual(l5s3.maxCount, 3) | |
| 284 | |
| 285 class GetLock(unittest.TestCase): | |
| 286 def testGet(self): | |
| 287 # the master.cfg file contains "lock ids", which are instances of | |
| 288 # MasterLock and SlaveLock but which are not actually Locks per se. | |
| 289 # When the build starts, these markers are turned into RealMasterLock | |
| 290 # and RealSlaveLock instances. This insures that any builds running | |
| 291 # on slaves that were unaffected by the config change are still | |
| 292 # referring to the same Lock instance as new builds by builders that | |
| 293 # *were* affected by the change. There have been bugs in the past in | |
| 294 # which this didn't happen, and the Locks were bypassed because half | |
| 295 # the builders were using one incarnation of the lock while the other | |
| 296 # half were using a separate (but equal) incarnation. | |
| 297 # | |
| 298 # Changing the lock id in any way should cause it to be replaced in | |
| 299 # the BotMaster. This will result in a couple of funky artifacts: | |
| 300 # builds in progress might pay attention to a different lock, so we | |
| 301 # might bypass the locking for the duration of a couple builds. | |
| 302 # There's also the problem of old Locks lingering around in | |
| 303 # BotMaster.locks, but they're small and shouldn't really cause a | |
| 304 # problem. | |
| 305 | |
| 306 b = master.BotMaster() | |
| 307 l1 = locks.MasterLock("one") | |
| 308 l1a = locks.MasterLock("one") | |
| 309 l2 = locks.MasterLock("one", maxCount=4) | |
| 310 | |
| 311 rl1 = b.getLockByID(l1) | |
| 312 rl2 = b.getLockByID(l1a) | |
| 313 self.failUnlessIdentical(rl1, rl2) | |
| 314 rl3 = b.getLockByID(l2) | |
| 315 self.failIfIdentical(rl1, rl3) | |
| 316 | |
| 317 s1 = locks.SlaveLock("one") | |
| 318 s1a = locks.SlaveLock("one") | |
| 319 s2 = locks.SlaveLock("one", maxCount=4) | |
| 320 s3 = locks.SlaveLock("one", maxCount=4, | |
| 321 maxCountForSlave={"a":1, "b":2}) | |
| 322 s3a = locks.SlaveLock("one", maxCount=4, | |
| 323 maxCountForSlave={"a":1, "b":2}) | |
| 324 s4 = locks.SlaveLock("one", maxCount=4, | |
| 325 maxCountForSlave={"a":4, "b":4}) | |
| 326 | |
| 327 rl1 = b.getLockByID(s1) | |
| 328 rl2 = b.getLockByID(s1a) | |
| 329 self.failUnlessIdentical(rl1, rl2) | |
| 330 rl3 = b.getLockByID(s2) | |
| 331 self.failIfIdentical(rl1, rl3) | |
| 332 rl4 = b.getLockByID(s3) | |
| 333 self.failIfIdentical(rl1, rl4) | |
| 334 self.failIfIdentical(rl3, rl4) | |
| 335 rl5 = b.getLockByID(s3a) | |
| 336 self.failUnlessIdentical(rl4, rl5) | |
| 337 rl6 = b.getLockByID(s4) | |
| 338 self.failIfIdentical(rl5, rl6) | |
| 339 | |
| 340 | |
| 341 | |
| 342 class LockStep(dummy.Dummy): | |
| 343 def start(self): | |
| 344 number = self.build.requests[0].number | |
| 345 self.build.requests[0].events.append(("start", number)) | |
| 346 dummy.Dummy.start(self) | |
| 347 def done(self): | |
| 348 number = self.build.requests[0].number | |
| 349 self.build.requests[0].events.append(("done", number)) | |
| 350 dummy.Dummy.done(self) | |
| 351 | |
| 352 config_1 = """ | |
| 353 from buildbot import locks | |
| 354 from buildbot.process import factory | |
| 355 from buildbot.buildslave import BuildSlave | |
| 356 from buildbot.config import BuilderConfig | |
| 357 s = factory.s | |
| 358 from buildbot.test.test_locks import LockStep | |
| 359 | |
| 360 BuildmasterConfig = c = {} | |
| 361 c['slaves'] = [BuildSlave('bot1', 'sekrit'), BuildSlave('bot2', 'sekrit')] | |
| 362 c['schedulers'] = [] | |
| 363 c['slavePortnum'] = 0 | |
| 364 | |
| 365 first_lock = locks.SlaveLock('first') | |
| 366 second_lock = locks.MasterLock('second') | |
| 367 f1 = factory.BuildFactory([s(LockStep, timeout=2, locks=[first_lock])]) | |
| 368 f2 = factory.BuildFactory([s(LockStep, timeout=3, locks=[second_lock])]) | |
| 369 f3 = factory.BuildFactory([s(LockStep, timeout=2, locks=[])]) | |
| 370 | |
| 371 b1a = BuilderConfig(name='full1a', slavename='bot1', factory=f1) | |
| 372 b1b = BuilderConfig(name='full1b', slavename='bot1', factory=f1) | |
| 373 b1c = BuilderConfig(name='full1c', slavename='bot1', factory=f3, | |
| 374 locks=[first_lock, second_lock]) | |
| 375 b1d = BuilderConfig(name='full1d', slavename='bot1', factory=f2) | |
| 376 | |
| 377 b2a = BuilderConfig(name='full2a', slavename='bot2', factory=f1) | |
| 378 b2b = BuilderConfig(name='full2b', slavename='bot2', factory=f3, | |
| 379 locks=[second_lock]) | |
| 380 c['builders'] = [b1a, b1b, b1c, b1d, b2a, b2b] | |
| 381 """ | |
| 382 | |
| 383 config_1a = config_1 + \ | |
| 384 """ | |
| 385 b1b = BuilderConfig(name='full1b', builddir='1B', slavename='bot1', factory=f1) | |
| 386 c['builders'] = [b1a, b1b, b1c, b1d, b2a, b2b] | |
| 387 """ | |
| 388 | |
| 389 | |
| 390 class Locks(RunMixin, unittest.TestCase): | |
| 391 def setUp(self): | |
| 392 N = 'test_builder' | |
| 393 RunMixin.setUp(self) | |
| 394 self.req1 = req1 = BuildRequest("forced build", SourceStamp(), N) | |
| 395 req1.number = 1 | |
| 396 self.req2 = req2 = BuildRequest("forced build", SourceStamp(), N) | |
| 397 req2.number = 2 | |
| 398 self.req3 = req3 = BuildRequest("forced build", SourceStamp(), N) | |
| 399 req3.number = 3 | |
| 400 req1.events = req2.events = req3.events = self.events = [] | |
| 401 d = self.master.loadConfig(config_1) | |
| 402 d.addCallback(lambda res: self.master.startService()) | |
| 403 d.addCallback(lambda res: self.connectSlave( | |
| 404 ["full1a", "full1b", "full1c", "full1d"], | |
| 405 "bot1")) | |
| 406 d.addCallback(lambda res: self.connectSlave(["full2a", "full2b"], "bot2"
)) | |
| 407 return d | |
| 408 | |
| 409 def testLock1(self): | |
| 410 self.control.getBuilder("full1a").requestBuild(self.req1) | |
| 411 self.control.getBuilder("full1b").requestBuild(self.req2) | |
| 412 d = defer.DeferredList([self.req1.waitUntilFinished(), | |
| 413 self.req2.waitUntilFinished()]) | |
| 414 d.addCallback(self._testLock1_1) | |
| 415 return d | |
| 416 | |
| 417 def _testLock1_1(self, res): | |
| 418 # full1a should complete its step before full1b starts it | |
| 419 self.failUnlessEqual(self.events, | |
| 420 [("start", 1), ("done", 1), | |
| 421 ("start", 2), ("done", 2)]) | |
| 422 | |
| 423 def dont_testLock1a(self): ## disabled -- test itself is buggy | |
| 424 # just like testLock1, but we reload the config file first, with a | |
| 425 # change that causes full1b to be changed. This tickles a design bug | |
| 426 # in which full1a and full1b wind up with distinct Lock instances. | |
| 427 d = self.master.loadConfig(config_1a) | |
| 428 d.addCallback(self._testLock1a_1) | |
| 429 return d | |
| 430 def _testLock1a_1(self, res): | |
| 431 self.control.getBuilder("full1a").requestBuild(self.req1) | |
| 432 self.control.getBuilder("full1b").requestBuild(self.req2) | |
| 433 d = defer.DeferredList([self.req1.waitUntilFinished(), | |
| 434 self.req2.waitUntilFinished()]) | |
| 435 d.addCallback(self._testLock1a_2) | |
| 436 return d | |
| 437 | |
| 438 def _testLock1a_2(self, res): | |
| 439 # full1a should complete its step before full1b starts it | |
| 440 self.failUnlessEqual(self.events, | |
| 441 [("start", 1), ("done", 1), | |
| 442 ("start", 2), ("done", 2)]) | |
| 443 | |
| 444 def testLock2(self): | |
| 445 # two builds run on separate slaves with slave-scoped locks should | |
| 446 # not interfere | |
| 447 self.control.getBuilder("full1a").requestBuild(self.req1) | |
| 448 self.control.getBuilder("full2a").requestBuild(self.req2) | |
| 449 d = defer.DeferredList([self.req1.waitUntilFinished(), | |
| 450 self.req2.waitUntilFinished()]) | |
| 451 d.addCallback(self._testLock2_1) | |
| 452 return d | |
| 453 | |
| 454 def _testLock2_1(self, res): | |
| 455 # full2a should start its step before full1a finishes it. They run on | |
| 456 # different slaves, however, so they might start in either order. | |
| 457 self.failUnless(self.events[:2] == [("start", 1), ("start", 2)] or | |
| 458 self.events[:2] == [("start", 2), ("start", 1)]) | |
| 459 | |
| 460 def dont_testLock3(self): ## disabled -- test fails sporadically | |
| 461 # two builds run on separate slaves with master-scoped locks should | |
| 462 # not overlap | |
| 463 self.control.getBuilder("full1c").requestBuild(self.req1) | |
| 464 self.control.getBuilder("full2b").requestBuild(self.req2) | |
| 465 d = defer.DeferredList([self.req1.waitUntilFinished(), | |
| 466 self.req2.waitUntilFinished()]) | |
| 467 d.addCallback(self._testLock3_1) | |
| 468 return d | |
| 469 | |
| 470 def _testLock3_1(self, res): | |
| 471 # full2b should not start until after full1c finishes. The builds run | |
| 472 # on different slaves, so we can't really predict which will start | |
| 473 # first. The important thing is that they don't overlap. | |
| 474 self.failUnless(self.events == [("start", 1), ("done", 1), | |
| 475 ("start", 2), ("done", 2)] | |
| 476 or self.events == [("start", 2), ("done", 2), | |
| 477 ("start", 1), ("done", 1)] | |
| 478 ) | |
| 479 | |
| 480 # This test has been disabled due to flakeyness/intermittentness | |
| 481 # def testLock4(self): | |
| 482 # self.control.getBuilder("full1a").requestBuild(self.req1) | |
| 483 # self.control.getBuilder("full1c").requestBuild(self.req2) | |
| 484 # self.control.getBuilder("full1d").requestBuild(self.req3) | |
| 485 # d = defer.DeferredList([self.req1.waitUntilFinished(), | |
| 486 # self.req2.waitUntilFinished(), | |
| 487 # self.req3.waitUntilFinished()]) | |
| 488 # d.addCallback(self._testLock4_1) | |
| 489 # return d | |
| 490 # | |
| 491 # def _testLock4_1(self, res): | |
| 492 # # full1a starts, then full1d starts (because they do not interfere). | |
| 493 # # Once both are done, full1c can run. | |
| 494 # self.failUnlessEqual(self.events, | |
| 495 # [("start", 1), ("start", 3), | |
| 496 # ("done", 1), ("done", 3), | |
| 497 # ("start", 2), ("done", 2)]) | |
| 498 | |
| 499 class BuilderLocks(RunMixin, unittest.TestCase): | |
| 500 config = """\ | |
| 501 from buildbot import locks | |
| 502 from buildbot.process import factory | |
| 503 from buildbot.buildslave import BuildSlave | |
| 504 from buildbot.config import BuilderConfig | |
| 505 s = factory.s | |
| 506 from buildbot.test.test_locks import LockStep | |
| 507 | |
| 508 BuildmasterConfig = c = {} | |
| 509 c['slaves'] = [BuildSlave('bot1', 'sekrit'), BuildSlave('bot2', 'sekrit')] | |
| 510 c['schedulers'] = [] | |
| 511 c['slavePortnum'] = 0 | |
| 512 | |
| 513 master_lock = locks.MasterLock('master', maxCount=2) | |
| 514 f_excl = factory.BuildFactory([s(LockStep, timeout=0, | |
| 515 locks=[master_lock.access("exclusive")])]) | |
| 516 f_count = factory.BuildFactory([s(LockStep, timeout=0, | |
| 517 locks=[master_lock])]) | |
| 518 | |
| 519 slaves = ['bot1', 'bot2'] | |
| 520 c['builders'] = [ | |
| 521 BuilderConfig(name='excl_A', slavenames=slaves, factory=f_excl), | |
| 522 BuilderConfig(name='excl_B', slavenames=slaves, factory=f_excl), | |
| 523 BuilderConfig(name='count_A', slavenames=slaves, factory=f_count), | |
| 524 BuilderConfig(name='count_B', slavenames=slaves, factory=f_count), | |
| 525 ] | |
| 526 """ | |
| 527 | |
| 528 def setUp(self): | |
| 529 N = 'test_builder' | |
| 530 RunMixin.setUp(self) | |
| 531 self.reqs = [BuildRequest("forced build", SourceStamp(), N) | |
| 532 for i in range(4)] | |
| 533 self.events = [] | |
| 534 for i in range(4): | |
| 535 self.reqs[i].number = i | |
| 536 self.reqs[i].events = self.events | |
| 537 d = self.master.loadConfig(self.config) | |
| 538 d.addCallback(lambda res: self.master.startService()) | |
| 539 d.addCallback(lambda res: self.connectSlave( | |
| 540 ["excl_A", "excl_B", "count_A", "count_B"], "bot1")) | |
| 541 d.addCallback(lambda res: self.connectSlave( | |
| 542 ["excl_A", "excl_B", "count_A", "count_B"], "bot2")) | |
| 543 return d | |
| 544 | |
| 545 def testOrder(self): | |
| 546 self.control.getBuilder("excl_A").requestBuild(self.reqs[0]) | |
| 547 self.control.getBuilder("excl_B").requestBuild(self.reqs[1]) | |
| 548 self.control.getBuilder("count_A").requestBuild(self.reqs[2]) | |
| 549 self.control.getBuilder("count_B").requestBuild(self.reqs[3]) | |
| 550 d = defer.DeferredList([r.waitUntilFinished() | |
| 551 for r in self.reqs]) | |
| 552 d.addCallback(self._testOrder) | |
| 553 return d | |
| 554 | |
| 555 def _testOrder(self, res): | |
| 556 # excl_A and excl_B cannot overlap with any other steps. | |
| 557 self.assert_(("start", 0) in self.events) | |
| 558 self.assert_(("done", 0) in self.events) | |
| 559 self.assert_(self.events.index(("start", 0)) + 1 == | |
| 560 self.events.index(("done", 0))) | |
| 561 | |
| 562 self.assert_(("start", 1) in self.events) | |
| 563 self.assert_(("done", 1) in self.events) | |
| 564 self.assert_(self.events.index(("start", 1)) + 1 == | |
| 565 self.events.index(("done", 1))) | |
| 566 | |
| 567 # FIXME: We really want to test that count_A and count_B were | |
| 568 # overlapped, but don't have a reliable way to do this. | |
| OLD | NEW |