Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/buildbot/buildbot/test/runutils.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildbot/buildbot/test/runutils.py')
-rw-r--r--buildbot/buildbot/test/runutils.py516
1 files changed, 516 insertions, 0 deletions
diff --git a/buildbot/buildbot/test/runutils.py b/buildbot/buildbot/test/runutils.py
new file mode 100644
index 0000000..2be85d6
--- /dev/null
+++ b/buildbot/buildbot/test/runutils.py
@@ -0,0 +1,516 @@
+
+import signal
+import shutil, os, errno
+from cStringIO import StringIO
+from twisted.internet import defer, reactor, protocol
+from twisted.python import log, util
+
+from buildbot import master, interfaces
+from buildbot.slave import bot
+from buildbot.buildslave import BuildSlave
+from buildbot.process.builder import Builder
+from buildbot.process.base import BuildRequest, Build
+from buildbot.process.buildstep import BuildStep
+from buildbot.sourcestamp import SourceStamp
+from buildbot.status import builder
+from buildbot.process.properties import Properties
+
+
+
+class _PutEverythingGetter(protocol.ProcessProtocol):
+ def __init__(self, deferred, stdin):
+ self.deferred = deferred
+ self.outBuf = StringIO()
+ self.errBuf = StringIO()
+ self.outReceived = self.outBuf.write
+ self.errReceived = self.errBuf.write
+ self.stdin = stdin
+
+ def connectionMade(self):
+ if self.stdin is not None:
+ self.transport.write(self.stdin)
+ self.transport.closeStdin()
+
+ def processEnded(self, reason):
+ out = self.outBuf.getvalue()
+ err = self.errBuf.getvalue()
+ e = reason.value
+ code = e.exitCode
+ if e.signal:
+ self.deferred.errback((out, err, e.signal))
+ else:
+ self.deferred.callback((out, err, code))
+
+def myGetProcessOutputAndValue(executable, args=(), env={}, path='.',
+ _reactor_ignored=None, stdin=None):
+ """Like twisted.internet.utils.getProcessOutputAndValue but takes
+ stdin, too."""
+ d = defer.Deferred()
+ p = _PutEverythingGetter(d, stdin)
+ reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path)
+ return d
+
+
+class MyBot(bot.Bot):
+ def remote_getSlaveInfo(self):
+ return self.parent.info
+
+class MyBuildSlave(bot.BuildSlave):
+ botClass = MyBot
+
+def rmtree(d):
+ try:
+ shutil.rmtree(d, ignore_errors=1)
+ except OSError, e:
+ # stupid 2.2 appears to ignore ignore_errors
+ if e.errno != errno.ENOENT:
+ raise
+
+class RunMixin:
+ master = None
+
+ def rmtree(self, d):
+ rmtree(d)
+
+ def setUp(self):
+ self.slaves = {}
+ self.rmtree("basedir")
+ os.mkdir("basedir")
+ self.master = master.BuildMaster("basedir")
+ self.status = self.master.getStatus()
+ self.control = interfaces.IControl(self.master)
+
+ def connectOneSlave(self, slavename, opts={}):
+ port = self.master.slavePort._port.getHost().port
+ self.rmtree("slavebase-%s" % slavename)
+ os.mkdir("slavebase-%s" % slavename)
+ slave = MyBuildSlave("localhost", port, slavename, "sekrit",
+ "slavebase-%s" % slavename,
+ keepalive=0, usePTY=False, debugOpts=opts)
+ slave.info = {"admin": "one"}
+ self.slaves[slavename] = slave
+ slave.startService()
+
+ def connectSlave(self, builders=["dummy"], slavename="bot1",
+ opts={}):
+ # connect buildslave 'slavename' and wait for it to connect to all of
+ # the given builders
+ dl = []
+ # initiate call for all of them, before waiting on result,
+ # otherwise we might miss some
+ for b in builders:
+ dl.append(self.master.botmaster.waitUntilBuilderAttached(b))
+ d = defer.DeferredList(dl)
+ self.connectOneSlave(slavename, opts)
+ return d
+
+ def connectSlaves(self, slavenames, builders):
+ dl = []
+ # initiate call for all of them, before waiting on result,
+ # otherwise we might miss some
+ for b in builders:
+ dl.append(self.master.botmaster.waitUntilBuilderAttached(b))
+ d = defer.DeferredList(dl)
+ for name in slavenames:
+ self.connectOneSlave(name)
+ return d
+
+ def connectSlave2(self):
+ # this takes over for bot1, so it has to share the slavename
+ port = self.master.slavePort._port.getHost().port
+ self.rmtree("slavebase-bot2")
+ os.mkdir("slavebase-bot2")
+ # this uses bot1, really
+ slave = MyBuildSlave("localhost", port, "bot1", "sekrit",
+ "slavebase-bot2", keepalive=0, usePTY=False)
+ slave.info = {"admin": "two"}
+ self.slaves['bot2'] = slave
+ slave.startService()
+
+ def connectSlaveFastTimeout(self):
+ # this slave has a very fast keepalive timeout
+ port = self.master.slavePort._port.getHost().port
+ self.rmtree("slavebase-bot1")
+ os.mkdir("slavebase-bot1")
+ slave = MyBuildSlave("localhost", port, "bot1", "sekrit",
+ "slavebase-bot1", keepalive=2, usePTY=False,
+ keepaliveTimeout=1)
+ slave.info = {"admin": "one"}
+ self.slaves['bot1'] = slave
+ slave.startService()
+ d = self.master.botmaster.waitUntilBuilderAttached("dummy")
+ return d
+
+ # things to start builds
+ def requestBuild(self, builder):
+ # returns a Deferred that fires with an IBuildStatus object when the
+ # build is finished
+ req = BuildRequest("forced build", SourceStamp(), 'test_builder')
+ self.control.getBuilder(builder).requestBuild(req)
+ return req.waitUntilFinished()
+
+ def failUnlessBuildSucceeded(self, bs):
+ if bs.getResults() != builder.SUCCESS:
+ log.msg("failUnlessBuildSucceeded noticed that the build failed")
+ self.logBuildResults(bs)
+ self.failUnlessEqual(bs.getResults(), builder.SUCCESS)
+ return bs # useful for chaining
+
+ def logBuildResults(self, bs):
+ # emit the build status and the contents of all logs to test.log
+ log.msg("logBuildResults starting")
+ log.msg(" bs.getResults() == %s" % builder.Results[bs.getResults()])
+ log.msg(" bs.isFinished() == %s" % bs.isFinished())
+ for s in bs.getSteps():
+ for l in s.getLogs():
+ log.msg("--- START step %s / log %s ---" % (s.getName(),
+ l.getName()))
+ if not l.getName().endswith(".html"):
+ log.msg(l.getTextWithHeaders())
+ log.msg("--- STOP ---")
+ log.msg("logBuildResults finished")
+
+ def tearDown(self):
+ log.msg("doing tearDown")
+ d = self.shutdownAllSlaves()
+ d.addCallback(self._tearDown_1)
+ d.addCallback(self._tearDown_2)
+ return d
+ def _tearDown_1(self, res):
+ if self.master:
+ return defer.maybeDeferred(self.master.stopService)
+ def _tearDown_2(self, res):
+ self.master = None
+ log.msg("tearDown done")
+
+
+ # various forms of slave death
+
+ def shutdownAllSlaves(self):
+ # the slave has disconnected normally: they SIGINT'ed it, or it shut
+ # down willingly. This will kill child processes and give them a
+ # chance to finish up. We return a Deferred that will fire when
+ # everything is finished shutting down.
+
+ log.msg("doing shutdownAllSlaves")
+ dl = []
+ for slave in self.slaves.values():
+ dl.append(slave.waitUntilDisconnected())
+ dl.append(defer.maybeDeferred(slave.stopService))
+ d = defer.DeferredList(dl)
+ d.addCallback(self._shutdownAllSlavesDone)
+ return d
+ def _shutdownAllSlavesDone(self, res):
+ for name in self.slaves.keys():
+ del self.slaves[name]
+ return self.master.botmaster.waitUntilBuilderFullyDetached("dummy")
+
+ def shutdownSlave(self, slavename, buildername):
+ # this slave has disconnected normally: they SIGINT'ed it, or it shut
+ # down willingly. This will kill child processes and give them a
+ # chance to finish up. We return a Deferred that will fire when
+ # everything is finished shutting down, and the given Builder knows
+ # that the slave has gone away.
+
+ s = self.slaves[slavename]
+ dl = [self.master.botmaster.waitUntilBuilderDetached(buildername),
+ s.waitUntilDisconnected()]
+ d = defer.DeferredList(dl)
+ d.addCallback(self._shutdownSlave_done, slavename)
+ s.stopService()
+ return d
+ def _shutdownSlave_done(self, res, slavename):
+ del self.slaves[slavename]
+
+ def killSlave(self):
+ # the slave has died, its host sent a FIN. The .notifyOnDisconnect
+ # callbacks will terminate the current step, so the build should be
+ # flunked (no further steps should be started).
+ self.slaves['bot1'].bf.continueTrying = 0
+ bot = self.slaves['bot1'].getServiceNamed("bot")
+ broker = bot.builders["dummy"].remote.broker
+ broker.transport.loseConnection()
+ del self.slaves['bot1']
+
+ def disappearSlave(self, slavename="bot1", buildername="dummy",
+ allowReconnect=False):
+ # the slave's host has vanished off the net, leaving the connection
+ # dangling. This will be detected quickly by app-level keepalives or
+ # a ping, or slowly by TCP timeouts.
+
+ # simulate this by replacing the slave Broker's .dataReceived method
+ # with one that just throws away all data.
+ def discard(data):
+ pass
+ bot = self.slaves[slavename].getServiceNamed("bot")
+ broker = bot.builders[buildername].remote.broker
+ broker.dataReceived = discard # seal its ears
+ broker.transport.write = discard # and take away its voice
+ if not allowReconnect:
+ # also discourage it from reconnecting once the connection goes away
+ assert self.slaves[slavename].bf.continueTrying
+ self.slaves[slavename].bf.continueTrying = False
+
+ def ghostSlave(self):
+ # the slave thinks it has lost the connection, and initiated a
+ # reconnect. The master doesn't yet realize it has lost the previous
+ # connection, and sees two connections at once.
+ raise NotImplementedError
+
+
+def setupBuildStepStatus(basedir):
+ """Return a BuildStep with a suitable BuildStepStatus object, ready to
+ use."""
+ os.mkdir(basedir)
+ botmaster = None
+ s0 = builder.Status(botmaster, basedir)
+ s1 = s0.builderAdded("buildername", "buildername")
+ s2 = builder.BuildStatus(s1, 1)
+ s3 = builder.BuildStepStatus(s2)
+ s3.setName("foostep")
+ s3.started = True
+ s3.stepStarted()
+ return s3
+
+def fake_slaveVersion(command, oldversion=None):
+ from buildbot.slave.registry import commandRegistry
+ return commandRegistry[command]
+
+class FakeBuildMaster:
+ properties = Properties(masterprop="master")
+
+class FakeBotMaster:
+ parent = FakeBuildMaster()
+
+def makeBuildStep(basedir, step_class=BuildStep, **kwargs):
+ bss = setupBuildStepStatus(basedir)
+
+ ss = SourceStamp()
+ setup = {'name': "builder1", "slavename": "bot1",
+ 'builddir': "builddir", 'factory': None}
+ b0 = Builder(setup, bss.getBuild().getBuilder())
+ b0.botmaster = FakeBotMaster()
+ br = BuildRequest("reason", ss, 'test_builder')
+ b = Build([br])
+ b.setBuilder(b0)
+ s = step_class(**kwargs)
+ s.setBuild(b)
+ s.setStepStatus(bss)
+ b.build_status = bss.getBuild()
+ b.setupProperties()
+ s.slaveVersion = fake_slaveVersion
+ return s
+
+
+def findDir():
+ # the same directory that holds this script
+ return util.sibpath(__file__, ".")
+
+class SignalMixin:
+ sigchldHandler = None
+
+ def setUpClass(self):
+ # make sure SIGCHLD handler is installed, as it should be on
+ # reactor.run(). problem is reactor may not have been run when this
+ # test runs.
+ if hasattr(reactor, "_handleSigchld") and hasattr(signal, "SIGCHLD"):
+ self.sigchldHandler = signal.signal(signal.SIGCHLD,
+ reactor._handleSigchld)
+
+ def tearDownClass(self):
+ if self.sigchldHandler:
+ signal.signal(signal.SIGCHLD, self.sigchldHandler)
+
+# these classes are used to test SlaveCommands in isolation
+
+class FakeSlaveBuilder:
+ debug = False
+ def __init__(self, usePTY, basedir):
+ self.updates = []
+ self.basedir = basedir
+ self.usePTY = usePTY
+
+ def sendUpdate(self, data):
+ if self.debug:
+ print "FakeSlaveBuilder.sendUpdate", data
+ self.updates.append(data)
+
+
+class SlaveCommandTestBase(SignalMixin):
+ usePTY = False
+
+ def setUpBuilder(self, basedir):
+ if not os.path.exists(basedir):
+ os.mkdir(basedir)
+ self.builder = FakeSlaveBuilder(self.usePTY, basedir)
+
+ def startCommand(self, cmdclass, args):
+ stepId = 0
+ self.cmd = c = cmdclass(self.builder, stepId, args)
+ c.running = True
+ d = c.doStart()
+ return d
+
+ def collectUpdates(self, res=None):
+ logs = {}
+ for u in self.builder.updates:
+ for k in u.keys():
+ if k == "log":
+ logname,data = u[k]
+ oldlog = logs.get(("log",logname), "")
+ logs[("log",logname)] = oldlog + data
+ elif k == "rc":
+ pass
+ else:
+ logs[k] = logs.get(k, "") + u[k]
+ return logs
+
+ def findRC(self):
+ for u in self.builder.updates:
+ if "rc" in u:
+ return u["rc"]
+ return None
+
+ def printStderr(self):
+ for u in self.builder.updates:
+ if "stderr" in u:
+ print u["stderr"]
+
+# ----------------------------------------
+
+class LocalWrapper:
+ # r = pb.Referenceable()
+ # w = LocalWrapper(r)
+ # now you can do things like w.callRemote()
+ def __init__(self, target):
+ self.target = target
+
+ def callRemote(self, name, *args, **kwargs):
+ # callRemote is not allowed to fire its Deferred in the same turn
+ d = defer.Deferred()
+ d.addCallback(self._callRemote, *args, **kwargs)
+ reactor.callLater(0, d.callback, name)
+ return d
+
+ def _callRemote(self, name, *args, **kwargs):
+ method = getattr(self.target, "remote_"+name)
+ return method(*args, **kwargs)
+
+ def notifyOnDisconnect(self, observer):
+ pass
+ def dontNotifyOnDisconnect(self, observer):
+ pass
+
+
+class LocalSlaveBuilder(bot.SlaveBuilder):
+ """I am object that behaves like a pb.RemoteReference, but in fact I
+ invoke methods locally."""
+ _arg_filter = None
+
+ def setArgFilter(self, filter):
+ self._arg_filter = filter
+
+ def remote_startCommand(self, stepref, stepId, command, args):
+ if self._arg_filter:
+ args = self._arg_filter(args)
+ # stepref should be a RemoteReference to the RemoteCommand
+ return bot.SlaveBuilder.remote_startCommand(self,
+ LocalWrapper(stepref),
+ stepId, command, args)
+
+class StepTester:
+ """Utility class to exercise BuildSteps and RemoteCommands, without
+ really using a Build or a Bot. No networks are used.
+
+ Use this as follows::
+
+ class MyTest(StepTester, unittest.TestCase):
+ def testOne(self):
+ self.slavebase = 'testOne.slave'
+ self.masterbase = 'testOne.master'
+ sb = self.makeSlaveBuilder()
+ step = self.makeStep(stepclass, **kwargs)
+ d = self.runStep(step)
+ d.addCallback(_checkResults)
+ return d
+ """
+
+ #slavebase = "slavebase"
+ slavebuilderbase = "slavebuilderbase"
+ #masterbase = "masterbase"
+
+ def makeSlaveBuilder(self):
+ os.mkdir(self.slavebase)
+ os.mkdir(os.path.join(self.slavebase, self.slavebuilderbase))
+ b = bot.Bot(self.slavebase, False)
+ b.startService()
+ sb = LocalSlaveBuilder("slavebuildername", False)
+ sb.setArgFilter(self.filterArgs)
+ sb.usePTY = False
+ sb.setServiceParent(b)
+ sb.setBuilddir(self.slavebuilderbase)
+ self.remote = LocalWrapper(sb)
+ return sb
+
+ workdir = "build"
+ def makeStep(self, factory, **kwargs):
+ step = makeBuildStep(self.masterbase, factory, **kwargs)
+ step.setBuildSlave(BuildSlave("name", "password"))
+ step.setDefaultWorkdir(self.workdir)
+ return step
+
+ def runStep(self, step):
+ d = defer.maybeDeferred(step.startStep, self.remote)
+ return d
+
+ def wrap(self, target):
+ return LocalWrapper(target)
+
+ def filterArgs(self, args):
+ # this can be overridden
+ return args
+
+# ----------------------------------------
+
+_flags = {}
+
+def setTestFlag(flagname, value):
+ _flags[flagname] = value
+
+class SetTestFlagStep(BuildStep):
+ """
+ A special BuildStep to set a named flag; this can be used with the
+ TestFlagMixin to monitor what has and has not run in a particular
+ configuration.
+ """
+ def __init__(self, flagname='flag', value=1, **kwargs):
+ BuildStep.__init__(self, **kwargs)
+ self.addFactoryArguments(flagname=flagname, value=value)
+
+ self.flagname = flagname
+ self.value = value
+
+ def start(self):
+ properties = self.build.getProperties()
+ _flags[self.flagname] = properties.render(self.value)
+ self.finished(builder.SUCCESS)
+
+class TestFlagMixin:
+ def clearFlags(self):
+ """
+ Set up for a test by clearing all flags; call this from your test
+ function.
+ """
+ _flags.clear()
+
+ def failIfFlagSet(self, flagname, msg=None):
+ if not msg: msg = "flag '%s' is set" % flagname
+ self.failIf(_flags.has_key(flagname), msg=msg)
+
+ def failIfFlagNotSet(self, flagname, msg=None):
+ if not msg: msg = "flag '%s' is not set" % flagname
+ self.failUnless(_flags.has_key(flagname), msg=msg)
+
+ def getFlag(self, flagname):
+ self.failIfFlagNotSet(flagname, "flag '%s' not set" % flagname)
+ return _flags.get(flagname)