3 import shutil
, os
, errno
4 from cStringIO
import StringIO
5 from twisted
.internet
import defer
, reactor
, protocol
6 from twisted
.python
import log
, util
8 from buildbot
import master
, interfaces
9 from buildbot
.slave
import bot
10 from buildbot
.buildslave
import BuildSlave
11 from buildbot
.process
.builder
import Builder
12 from buildbot
.process
.base
import BuildRequest
, Build
13 from buildbot
.process
.buildstep
import BuildStep
14 from buildbot
.sourcestamp
import SourceStamp
15 from buildbot
.status
import builder
16 from buildbot
.process
.properties
import Properties
20 class _PutEverythingGetter(protocol
.ProcessProtocol
):
21 def __init__(self
, deferred
, stdin
):
22 self
.deferred
= deferred
23 self
.outBuf
= StringIO()
24 self
.errBuf
= StringIO()
25 self
.outReceived
= self
.outBuf
.write
26 self
.errReceived
= self
.errBuf
.write
29 def connectionMade(self
):
30 if self
.stdin
is not None:
31 self
.transport
.write(self
.stdin
)
32 self
.transport
.closeStdin()
34 def processEnded(self
, reason
):
35 out
= self
.outBuf
.getvalue()
36 err
= self
.errBuf
.getvalue()
40 self
.deferred
.errback((out
, err
, e
.signal
))
42 self
.deferred
.callback((out
, err
, code
))
44 def myGetProcessOutputAndValue(executable
, args
=(), env
={}, path
='.',
45 _reactor_ignored
=None, stdin
=None):
46 """Like twisted.internet.utils.getProcessOutputAndValue but takes
49 p
= _PutEverythingGetter(d
, stdin
)
50 reactor
.spawnProcess(p
, executable
, (executable
,)+tuple(args
), env
, path
)
55 def remote_getSlaveInfo(self
):
56 return self
.parent
.info
58 class MyBuildSlave(bot
.BuildSlave
):
63 shutil
.rmtree(d
, ignore_errors
=1)
65 # stupid 2.2 appears to ignore ignore_errors
66 if e
.errno
!= errno
.ENOENT
:
77 self
.rmtree("basedir")
79 self
.master
= master
.BuildMaster("basedir")
80 self
.status
= self
.master
.getStatus()
81 self
.control
= interfaces
.IControl(self
.master
)
83 def connectOneSlave(self
, slavename
, opts
={}):
84 port
= self
.master
.slavePort
._port
.getHost().port
85 self
.rmtree("slavebase-%s" % slavename
)
86 os
.mkdir("slavebase-%s" % slavename
)
87 slave
= MyBuildSlave("localhost", port
, slavename
, "sekrit",
88 "slavebase-%s" % slavename
,
89 keepalive
=0, usePTY
=False, debugOpts
=opts
)
90 slave
.info
= {"admin": "one"}
91 self
.slaves
[slavename
] = slave
94 def connectSlave(self
, builders
=["dummy"], slavename
="bot1",
96 # connect buildslave 'slavename' and wait for it to connect to all of
99 # initiate call for all of them, before waiting on result,
100 # otherwise we might miss some
102 dl
.append(self
.master
.botmaster
.waitUntilBuilderAttached(b
))
103 d
= defer
.DeferredList(dl
)
104 self
.connectOneSlave(slavename
, opts
)
107 def connectSlaves(self
, slavenames
, builders
):
109 # initiate call for all of them, before waiting on result,
110 # otherwise we might miss some
112 dl
.append(self
.master
.botmaster
.waitUntilBuilderAttached(b
))
113 d
= defer
.DeferredList(dl
)
114 for name
in slavenames
:
115 self
.connectOneSlave(name
)
118 def connectSlave2(self
):
119 # this takes over for bot1, so it has to share the slavename
120 port
= self
.master
.slavePort
._port
.getHost().port
121 self
.rmtree("slavebase-bot2")
122 os
.mkdir("slavebase-bot2")
123 # this uses bot1, really
124 slave
= MyBuildSlave("localhost", port
, "bot1", "sekrit",
125 "slavebase-bot2", keepalive
=0, usePTY
=False)
126 slave
.info
= {"admin": "two"}
127 self
.slaves
['bot2'] = slave
130 def connectSlaveFastTimeout(self
):
131 # this slave has a very fast keepalive timeout
132 port
= self
.master
.slavePort
._port
.getHost().port
133 self
.rmtree("slavebase-bot1")
134 os
.mkdir("slavebase-bot1")
135 slave
= MyBuildSlave("localhost", port
, "bot1", "sekrit",
136 "slavebase-bot1", keepalive
=2, usePTY
=False,
138 slave
.info
= {"admin": "one"}
139 self
.slaves
['bot1'] = slave
141 d
= self
.master
.botmaster
.waitUntilBuilderAttached("dummy")
144 # things to start builds
145 def requestBuild(self
, builder
):
146 # returns a Deferred that fires with an IBuildStatus object when the
148 req
= BuildRequest("forced build", SourceStamp(), 'test_builder')
149 self
.control
.getBuilder(builder
).requestBuild(req
)
150 return req
.waitUntilFinished()
152 def failUnlessBuildSucceeded(self
, bs
):
153 if bs
.getResults() != builder
.SUCCESS
:
154 log
.msg("failUnlessBuildSucceeded noticed that the build failed")
155 self
.logBuildResults(bs
)
156 self
.failUnlessEqual(bs
.getResults(), builder
.SUCCESS
)
157 return bs
# useful for chaining
159 def logBuildResults(self
, bs
):
160 # emit the build status and the contents of all logs to test.log
161 log
.msg("logBuildResults starting")
162 log
.msg(" bs.getResults() == %s" % builder
.Results
[bs
.getResults()])
163 log
.msg(" bs.isFinished() == %s" % bs
.isFinished())
164 for s
in bs
.getSteps():
165 for l
in s
.getLogs():
166 log
.msg("--- START step %s / log %s ---" % (s
.getName(),
168 if not l
.getName().endswith(".html"):
169 log
.msg(l
.getTextWithHeaders())
170 log
.msg("--- STOP ---")
171 log
.msg("logBuildResults finished")
174 log
.msg("doing tearDown")
175 d
= self
.shutdownAllSlaves()
176 d
.addCallback(self
._tearDown
_1)
177 d
.addCallback(self
._tearDown
_2)
179 def _tearDown_1(self
, res
):
181 return defer
.maybeDeferred(self
.master
.stopService
)
182 def _tearDown_2(self
, res
):
184 log
.msg("tearDown done")
187 # various forms of slave death
189 def shutdownAllSlaves(self
):
190 # the slave has disconnected normally: they SIGINT'ed it, or it shut
191 # down willingly. This will kill child processes and give them a
192 # chance to finish up. We return a Deferred that will fire when
193 # everything is finished shutting down.
195 log
.msg("doing shutdownAllSlaves")
197 for slave
in self
.slaves
.values():
198 dl
.append(slave
.waitUntilDisconnected())
199 dl
.append(defer
.maybeDeferred(slave
.stopService
))
200 d
= defer
.DeferredList(dl
)
201 d
.addCallback(self
._shutdownAllSlavesDone
)
203 def _shutdownAllSlavesDone(self
, res
):
204 for name
in self
.slaves
.keys():
205 del self
.slaves
[name
]
206 return self
.master
.botmaster
.waitUntilBuilderFullyDetached("dummy")
208 def shutdownSlave(self
, slavename
, buildername
):
209 # this slave has disconnected normally: they SIGINT'ed it, or it shut
210 # down willingly. This will kill child processes and give them a
211 # chance to finish up. We return a Deferred that will fire when
212 # everything is finished shutting down, and the given Builder knows
213 # that the slave has gone away.
215 s
= self
.slaves
[slavename
]
216 dl
= [self
.master
.botmaster
.waitUntilBuilderDetached(buildername
),
217 s
.waitUntilDisconnected()]
218 d
= defer
.DeferredList(dl
)
219 d
.addCallback(self
._shutdownSlave
_done
, slavename
)
222 def _shutdownSlave_done(self
, res
, slavename
):
223 del self
.slaves
[slavename
]
226 # the slave has died, its host sent a FIN. The .notifyOnDisconnect
227 # callbacks will terminate the current step, so the build should be
228 # flunked (no further steps should be started).
229 self
.slaves
['bot1'].bf
.continueTrying
= 0
230 bot
= self
.slaves
['bot1'].getServiceNamed("bot")
231 broker
= bot
.builders
["dummy"].remote
.broker
232 broker
.transport
.loseConnection()
233 del self
.slaves
['bot1']
235 def disappearSlave(self
, slavename
="bot1", buildername
="dummy",
236 allowReconnect
=False):
237 # the slave's host has vanished off the net, leaving the connection
238 # dangling. This will be detected quickly by app-level keepalives or
239 # a ping, or slowly by TCP timeouts.
241 # simulate this by replacing the slave Broker's .dataReceived method
242 # with one that just throws away all data.
245 bot
= self
.slaves
[slavename
].getServiceNamed("bot")
246 broker
= bot
.builders
[buildername
].remote
.broker
247 broker
.dataReceived
= discard
# seal its ears
248 broker
.transport
.write
= discard
# and take away its voice
249 if not allowReconnect
:
250 # also discourage it from reconnecting once the connection goes away
251 assert self
.slaves
[slavename
].bf
.continueTrying
252 self
.slaves
[slavename
].bf
.continueTrying
= False
254 def ghostSlave(self
):
255 # the slave thinks it has lost the connection, and initiated a
256 # reconnect. The master doesn't yet realize it has lost the previous
257 # connection, and sees two connections at once.
258 raise NotImplementedError
261 def setupBuildStepStatus(basedir
):
262 """Return a BuildStep with a suitable BuildStepStatus object, ready to
266 s0
= builder
.Status(botmaster
, basedir
)
267 s1
= s0
.builderAdded("buildername", "buildername")
268 s2
= builder
.BuildStatus(s1
, 1)
269 s3
= builder
.BuildStepStatus(s2
)
270 s3
.setName("foostep")
275 def fake_slaveVersion(command
, oldversion
=None):
276 from buildbot
.slave
.registry
import commandRegistry
277 return commandRegistry
[command
]
279 class FakeBuildMaster
:
280 properties
= Properties(masterprop
="master")
283 parent
= FakeBuildMaster()
285 def makeBuildStep(basedir
, step_class
=BuildStep
, **kwargs
):
286 bss
= setupBuildStepStatus(basedir
)
289 setup
= {'name': "builder1", "slavename": "bot1",
290 'builddir': "builddir", 'factory': None}
291 b0
= Builder(setup
, bss
.getBuild().getBuilder())
292 b0
.botmaster
= FakeBotMaster()
293 br
= BuildRequest("reason", ss
, 'test_builder')
296 s
= step_class(**kwargs
)
299 b
.build_status
= bss
.getBuild()
301 s
.slaveVersion
= fake_slaveVersion
306 # the same directory that holds this script
307 return util
.sibpath(__file__
, ".")
310 sigchldHandler
= None
312 def setUpSignalHandler(self
):
313 # make sure SIGCHLD handler is installed, as it should be on
314 # reactor.run(). problem is reactor may not have been run when this
316 if hasattr(reactor
, "_handleSigchld") and hasattr(signal
, "SIGCHLD"):
317 self
.sigchldHandler
= signal
.signal(signal
.SIGCHLD
,
318 reactor
._handleSigchld
)
320 def tearDownSignalHandler(self
):
321 if self
.sigchldHandler
:
322 signal
.signal(signal
.SIGCHLD
, self
.sigchldHandler
)
324 # these classes are used to test SlaveCommands in isolation
326 class FakeSlaveBuilder
:
328 def __init__(self
, usePTY
, basedir
):
330 self
.basedir
= basedir
333 def sendUpdate(self
, data
):
335 print "FakeSlaveBuilder.sendUpdate", data
336 self
.updates
.append(data
)
339 class SlaveCommandTestBase(SignalMixin
):
343 self
.setUpSignalHandler()
346 self
.tearDownSignalHandler()
348 def setUpBuilder(self
, basedir
):
349 if not os
.path
.exists(basedir
):
351 self
.builder
= FakeSlaveBuilder(self
.usePTY
, basedir
)
353 def startCommand(self
, cmdclass
, args
):
355 self
.cmd
= c
= cmdclass(self
.builder
, stepId
, args
)
360 def collectUpdates(self
, res
=None):
362 for u
in self
.builder
.updates
:
366 oldlog
= logs
.get(("log",logname
), "")
367 logs
[("log",logname
)] = oldlog
+ data
371 logs
[k
] = logs
.get(k
, "") + u
[k
]
375 for u
in self
.builder
.updates
:
380 def printStderr(self
):
381 for u
in self
.builder
.updates
:
385 # ----------------------------------------
388 # r = pb.Referenceable()
389 # w = LocalWrapper(r)
390 # now you can do things like w.callRemote()
391 def __init__(self
, target
):
394 def callRemote(self
, name
, *args
, **kwargs
):
395 # callRemote is not allowed to fire its Deferred in the same turn
397 d
.addCallback(self
._callRemote
, *args
, **kwargs
)
398 reactor
.callLater(0, d
.callback
, name
)
401 def _callRemote(self
, name
, *args
, **kwargs
):
402 method
= getattr(self
.target
, "remote_"+name
)
403 return method(*args
, **kwargs
)
405 def notifyOnDisconnect(self
, observer
):
407 def dontNotifyOnDisconnect(self
, observer
):
411 class LocalSlaveBuilder(bot
.SlaveBuilder
):
412 """I am object that behaves like a pb.RemoteReference, but in fact I
413 invoke methods locally."""
416 def setArgFilter(self
, filter):
417 self
._arg
_filter
= filter
419 def remote_startCommand(self
, stepref
, stepId
, command
, args
):
421 args
= self
._arg
_filter
(args
)
422 # stepref should be a RemoteReference to the RemoteCommand
423 return bot
.SlaveBuilder
.remote_startCommand(self
,
424 LocalWrapper(stepref
),
425 stepId
, command
, args
)
428 """Utility class to exercise BuildSteps and RemoteCommands, without
429 really using a Build or a Bot. No networks are used.
431 Use this as follows::
433 class MyTest(StepTester, unittest.TestCase):
435 self.slavebase = 'testOne.slave'
436 self.masterbase = 'testOne.master'
437 sb = self.makeSlaveBuilder()
438 step = self.makeStep(stepclass, **kwargs)
439 d = self.runStep(step)
440 d.addCallback(_checkResults)
444 #slavebase = "slavebase"
445 slavebuilderbase
= "slavebuilderbase"
446 #masterbase = "masterbase"
448 def makeSlaveBuilder(self
):
449 os
.mkdir(self
.slavebase
)
450 os
.mkdir(os
.path
.join(self
.slavebase
, self
.slavebuilderbase
))
451 b
= bot
.Bot(self
.slavebase
, False)
453 sb
= LocalSlaveBuilder("slavebuildername", False)
454 sb
.setArgFilter(self
.filterArgs
)
456 sb
.setServiceParent(b
)
457 sb
.setBuilddir(self
.slavebuilderbase
)
458 self
.remote
= LocalWrapper(sb
)
462 def makeStep(self
, factory
, **kwargs
):
463 step
= makeBuildStep(self
.masterbase
, factory
, **kwargs
)
464 step
.setBuildSlave(BuildSlave("name", "password"))
465 step
.setDefaultWorkdir(self
.workdir
)
468 def runStep(self
, step
):
469 d
= defer
.maybeDeferred(step
.startStep
, self
.remote
)
472 def wrap(self
, target
):
473 return LocalWrapper(target
)
475 def filterArgs(self
, args
):
476 # this can be overridden
479 # ----------------------------------------
483 def setTestFlag(flagname
, value
):
484 _flags
[flagname
] = value
486 class SetTestFlagStep(BuildStep
):
488 A special BuildStep to set a named flag; this can be used with the
489 TestFlagMixin to monitor what has and has not run in a particular
492 def __init__(self
, flagname
='flag', value
=1, **kwargs
):
493 BuildStep
.__init
__(self
, **kwargs
)
494 self
.addFactoryArguments(flagname
=flagname
, value
=value
)
496 self
.flagname
= flagname
500 properties
= self
.build
.getProperties()
501 _flags
[self
.flagname
] = properties
.render(self
.value
)
502 self
.finished(builder
.SUCCESS
)
505 def clearFlags(self
):
507 Set up for a test by clearing all flags; call this from your test
512 def failIfFlagSet(self
, flagname
, msg
=None):
513 if not msg
: msg
= "flag '%s' is set" % flagname
514 self
.failIf(_flags
.has_key(flagname
), msg
=msg
)
516 def failIfFlagNotSet(self
, flagname
, msg
=None):
517 if not msg
: msg
= "flag '%s' is not set" % flagname
518 self
.failUnless(_flags
.has_key(flagname
), msg
=msg
)
520 def getFlag(self
, flagname
):
521 self
.failIfFlagNotSet(flagname
, "flag '%s' not set" % flagname
)
522 return _flags
.get(flagname
)