Index: third_party/buildbot_7_12/buildbot/process/buildstep.py |
diff --git a/third_party/buildbot_7_12/buildbot/process/buildstep.py b/third_party/buildbot_7_12/buildbot/process/buildstep.py |
deleted file mode 100644 |
index 39739a46af98272f2361f280eb2d7e4099f572b2..0000000000000000000000000000000000000000 |
--- a/third_party/buildbot_7_12/buildbot/process/buildstep.py |
+++ /dev/null |
@@ -1,1172 +0,0 @@ |
-# -*- test-case-name: buildbot.test.test_steps -*- |
- |
-from zope.interface import implements |
-from twisted.internet import reactor, defer, error |
-from twisted.protocols import basic |
-from twisted.spread import pb |
-from twisted.python import log |
-from twisted.python.failure import Failure |
-from twisted.web.util import formatFailure |
- |
-from buildbot import interfaces, locks |
-from buildbot.status import progress |
-from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \ |
- EXCEPTION |
- |
-""" |
-BuildStep and RemoteCommand classes for master-side representation of the |
-build process |
-""" |
- |
-class RemoteCommand(pb.Referenceable): |
- """ |
- I represent a single command to be run on the slave. I handle the details |
- of reliably gathering status updates from the slave (acknowledging each), |
- and (eventually, in a future release) recovering from interrupted builds. |
- This is the master-side object that is known to the slave-side |
- L{buildbot.slave.bot.SlaveBuilder}, to which status updates are sent. |
- |
- My command should be started by calling .run(), which returns a |
- Deferred that will fire when the command has finished, or will |
- errback if an exception is raised. |
- |
- Typically __init__ or run() will set up self.remote_command to be a |
- string which corresponds to one of the SlaveCommands registered in |
- the buildslave, and self.args to a dictionary of arguments that will |
- be passed to the SlaveCommand instance. |
- |
- start, remoteUpdate, and remoteComplete are available to be overridden |
- |
- @type commandCounter: list of one int |
- @cvar commandCounter: provides a unique value for each |
- RemoteCommand executed across all slaves |
- @type active: boolean |
- @ivar active: whether the command is currently running |
- """ |
- commandCounter = [0] # we use a list as a poor man's singleton |
- active = False |
- |
- def __init__(self, remote_command, args): |
- """ |
- @type remote_command: string |
- @param remote_command: remote command to start. This will be |
- passed to |
- L{buildbot.slave.bot.SlaveBuilder.remote_startCommand} |
- and needs to have been registered |
- slave-side by |
- L{buildbot.slave.registry.registerSlaveCommand} |
- @type args: dict |
- @param args: arguments to send to the remote command |
- """ |
- |
- self.remote_command = remote_command |
- self.args = args |
- |
- def run(self, step, remote): |
- self.active = True |
- self.step = step |
- self.remote = remote |
- c = self.commandCounter[0] |
- self.commandCounter[0] += 1 |
- #self.commandID = "%d %d" % (c, random.randint(0, 1000000)) |
- self.commandID = "%d" % c |
- log.msg("%s: RemoteCommand.run [%s]" % (self, self.commandID)) |
- self.deferred = defer.Deferred() |
- |
- d = defer.maybeDeferred(self.start) |
- |
- # _finished is called with an error for unknown commands, errors |
- # that occur while the command is starting (including OSErrors in |
- # exec()), StaleBroker (when the connection was lost before we |
- # started), and pb.PBConnectionLost (when the slave isn't responding |
- # over this connection, perhaps it had a power failure, or NAT |
- # weirdness). If this happens, self.deferred is fired right away. |
- d.addErrback(self._finished) |
- |
- # Connections which are lost while the command is running are caught |
- # when our parent Step calls our .lostRemote() method. |
- return self.deferred |
- |
- def start(self): |
- """ |
- Tell the slave to start executing the remote command. |
- |
- @rtype: L{twisted.internet.defer.Deferred} |
- @returns: a deferred that will fire when the remote command is |
- done (with None as the result) |
- """ |
- |
- # Allow use of WithProperties in logfile path names. |
- cmd_args = self.args |
- if cmd_args.has_key("logfiles") and cmd_args["logfiles"]: |
- cmd_args = cmd_args.copy() |
- properties = self.step.build.getProperties() |
- cmd_args["logfiles"] = properties.render(cmd_args["logfiles"]) |
- |
- # This method only initiates the remote command. |
- # We will receive remote_update messages as the command runs. |
- # We will get a single remote_complete when it finishes. |
- # We should fire self.deferred when the command is done. |
- d = self.remote.callRemote("startCommand", self, self.commandID, |
- self.remote_command, cmd_args) |
- return d |
- |
- def interrupt(self, why): |
- # TODO: consider separating this into interrupt() and stop(), where |
- # stop() unconditionally calls _finished, but interrupt() merely |
- # asks politely for the command to stop soon. |
- |
- log.msg("RemoteCommand.interrupt", self, why) |
- if not self.active: |
- log.msg(" but this RemoteCommand is already inactive") |
- return |
- if not self.remote: |
- log.msg(" but our .remote went away") |
- return |
- if isinstance(why, Failure) and why.check(error.ConnectionLost): |
- log.msg("RemoteCommand.disconnect: lost slave") |
- self.remote = None |
- self._finished(why) |
- return |
- |
- # tell the remote command to halt. Returns a Deferred that will fire |
- # when the interrupt command has been delivered. |
- |
- d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand", |
- self.commandID, str(why)) |
- # the slave may not have remote_interruptCommand |
- d.addErrback(self._interruptFailed) |
- return d |
- |
- def _interruptFailed(self, why): |
- log.msg("RemoteCommand._interruptFailed", self) |
- # TODO: forcibly stop the Command now, since we can't stop it |
- # cleanly |
- return None |
- |
- def remote_update(self, updates): |
- """ |
- I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so |
- I can receive updates from the running remote command. |
- |
- @type updates: list of [object, int] |
- @param updates: list of updates from the remote command |
- """ |
- self.buildslave.messageReceivedFromSlave() |
- max_updatenum = 0 |
- for (update, num) in updates: |
- #log.msg("update[%d]:" % num) |
- try: |
- if self.active: # ignore late updates |
- self.remoteUpdate(update) |
- except: |
- # log failure, terminate build, let slave retire the update |
- self._finished(Failure()) |
- # TODO: what if multiple updates arrive? should |
- # skip the rest but ack them all |
- if num > max_updatenum: |
- max_updatenum = num |
- return max_updatenum |
- |
- def remoteUpdate(self, update): |
- raise NotImplementedError("You must implement this in a subclass") |
- |
- def remote_complete(self, failure=None): |
- """ |
- Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to |
- notify me the remote command has finished. |
- |
- @type failure: L{twisted.python.failure.Failure} or None |
- |
- @rtype: None |
- """ |
- self.buildslave.messageReceivedFromSlave() |
- # call the real remoteComplete a moment later, but first return an |
- # acknowledgement so the slave can retire the completion message. |
- if self.active: |
- reactor.callLater(0, self._finished, failure) |
- return None |
- |
- def _finished(self, failure=None): |
- self.active = False |
- # call .remoteComplete. If it raises an exception, or returns the |
- # Failure that we gave it, our self.deferred will be errbacked. If |
- # it does not (either it ate the Failure or there the step finished |
- # normally and it didn't raise a new exception), self.deferred will |
- # be callbacked. |
- d = defer.maybeDeferred(self.remoteComplete, failure) |
- # arrange for the callback to get this RemoteCommand instance |
- # instead of just None |
- d.addCallback(lambda r: self) |
- # this fires the original deferred we returned from .run(), |
- # with self as the result, or a failure |
- d.addBoth(self.deferred.callback) |
- |
- def remoteComplete(self, maybeFailure): |
- """Subclasses can override this. |
- |
- This is called when the RemoteCommand has finished. 'maybeFailure' |
- will be None if the command completed normally, or a Failure |
- instance in one of the following situations: |
- |
- - the slave was lost before the command was started |
- - the slave didn't respond to the startCommand message |
- - the slave raised an exception while starting the command |
- (bad command name, bad args, OSError from missing executable) |
- - the slave raised an exception while finishing the command |
- (they send back a remote_complete message with a Failure payload) |
- |
- and also (for now): |
- - slave disconnected while the command was running |
- |
- This method should do cleanup, like closing log files. It should |
- normally return the 'failure' argument, so that any exceptions will |
- be propagated to the Step. If it wants to consume them, return None |
- instead.""" |
- |
- return maybeFailure |
- |
-class LoggedRemoteCommand(RemoteCommand): |
- """ |
- |
- I am a L{RemoteCommand} which gathers output from the remote command into |
- one or more local log files. My C{self.logs} dictionary contains |
- references to these L{buildbot.status.builder.LogFile} instances. Any |
- stdout/stderr/header updates from the slave will be put into |
- C{self.logs['stdio']}, if it exists. If the remote command uses other log |
- files, they will go into other entries in C{self.logs}. |
- |
- If you want to use stdout or stderr, you should create a LogFile named |
- 'stdio' and pass it to my useLog() message. Otherwise stdout/stderr will |
- be ignored, which is probably not what you want. |
- |
- Unless you tell me otherwise, when my command completes I will close all |
- the LogFiles that I know about. |
- |
- @ivar logs: maps logname to a LogFile instance |
- @ivar _closeWhenFinished: maps logname to a boolean. If true, this |
- LogFile will be closed when the RemoteCommand |
- finishes. LogFiles which are shared between |
- multiple RemoteCommands should use False here. |
- |
- """ |
- |
- rc = None |
- debug = False |
- |
- def __init__(self, *args, **kwargs): |
- self.logs = {} |
- self.delayedLogs = {} |
- self._closeWhenFinished = {} |
- RemoteCommand.__init__(self, *args, **kwargs) |
- |
- def __repr__(self): |
- return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self)) |
- |
- def useLog(self, loog, closeWhenFinished=False, logfileName=None): |
- """Start routing messages from a remote logfile to a local LogFile |
- |
- I take a local ILogFile instance in 'loog', and arrange to route |
- remote log messages for the logfile named 'logfileName' into it. By |
- default this logfileName comes from the ILogFile itself (using the |
- name by which the ILogFile will be displayed), but the 'logfileName' |
- argument can be used to override this. For example, if |
- logfileName='stdio', this logfile will collect text from the stdout |
- and stderr of the command. |
- |
- @param loog: an instance which implements ILogFile |
- @param closeWhenFinished: a boolean, set to False if the logfile |
- will be shared between multiple |
- RemoteCommands. If True, the logfile will |
- be closed when this ShellCommand is done |
- with it. |
- @param logfileName: a string, which indicates which remote log file |
- should be routed into this ILogFile. This should |
- match one of the keys of the logfiles= argument |
- to ShellCommand. |
- |
- """ |
- |
- assert interfaces.ILogFile.providedBy(loog) |
- if not logfileName: |
- logfileName = loog.getName() |
- assert logfileName not in self.logs |
- assert logfileName not in self.delayedLogs |
- self.logs[logfileName] = loog |
- self._closeWhenFinished[logfileName] = closeWhenFinished |
- |
- def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False): |
- assert logfileName not in self.logs |
- assert logfileName not in self.delayedLogs |
- self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished) |
- |
- def start(self): |
- log.msg("LoggedRemoteCommand.start") |
- if 'stdio' not in self.logs: |
- log.msg("LoggedRemoteCommand (%s) is running a command, but " |
- "it isn't being logged to anything. This seems unusual." |
- % self) |
- self.updates = {} |
- return RemoteCommand.start(self) |
- |
- def addStdout(self, data): |
- if 'stdio' in self.logs: |
- self.logs['stdio'].addStdout(data) |
- def addStderr(self, data): |
- if 'stdio' in self.logs: |
- self.logs['stdio'].addStderr(data) |
- def addHeader(self, data): |
- if 'stdio' in self.logs: |
- self.logs['stdio'].addHeader(data) |
- |
- def addToLog(self, logname, data): |
- # Activate delayed logs on first data. |
- if logname in self.delayedLogs: |
- (activateCallBack, closeWhenFinished) = self.delayedLogs[logname] |
- del self.delayedLogs[logname] |
- loog = activateCallBack(self) |
- self.logs[logname] = loog |
- self._closeWhenFinished[logname] = closeWhenFinished |
- |
- if logname in self.logs: |
- self.logs[logname].addStdout(data) |
- else: |
- log.msg("%s.addToLog: no such log %s" % (self, logname)) |
- |
- def remoteUpdate(self, update): |
- if self.debug: |
- for k,v in update.items(): |
- log.msg("Update[%s]: %s" % (k,v)) |
- if update.has_key('stdout'): |
- # 'stdout': data |
- self.addStdout(update['stdout']) |
- if update.has_key('stderr'): |
- # 'stderr': data |
- self.addStderr(update['stderr']) |
- if update.has_key('header'): |
- # 'header': data |
- self.addHeader(update['header']) |
- if update.has_key('log'): |
- # 'log': (logname, data) |
- logname, data = update['log'] |
- self.addToLog(logname, data) |
- if update.has_key('rc'): |
- rc = self.rc = update['rc'] |
- log.msg("%s rc=%s" % (self, rc)) |
- self.addHeader("program finished with exit code %d\n" % rc) |
- |
- for k in update: |
- if k not in ('stdout', 'stderr', 'header', 'rc'): |
- if k not in self.updates: |
- self.updates[k] = [] |
- self.updates[k].append(update[k]) |
- |
- def remoteComplete(self, maybeFailure): |
- for name,loog in self.logs.items(): |
- if self._closeWhenFinished[name]: |
- if maybeFailure: |
- loog.addHeader("\nremoteFailed: %s" % maybeFailure) |
- else: |
- log.msg("closing log %s" % loog) |
- loog.finish() |
- return maybeFailure |
- |
- |
-class LogObserver: |
- implements(interfaces.ILogObserver) |
- |
- def setStep(self, step): |
- self.step = step |
- |
- def setLog(self, loog): |
- assert interfaces.IStatusLog.providedBy(loog) |
- loog.subscribe(self, True) |
- |
- def logChunk(self, build, step, log, channel, text): |
- if channel == interfaces.LOG_CHANNEL_STDOUT: |
- self.outReceived(text) |
- elif channel == interfaces.LOG_CHANNEL_STDERR: |
- self.errReceived(text) |
- |
- # TODO: add a logEnded method? er, stepFinished? |
- |
- def outReceived(self, data): |
- """This will be called with chunks of stdout data. Override this in |
- your observer.""" |
- pass |
- |
- def errReceived(self, data): |
- """This will be called with chunks of stderr data. Override this in |
- your observer.""" |
- pass |
- |
- |
-class LogLineObserver(LogObserver): |
- def __init__(self): |
- self.stdoutParser = basic.LineOnlyReceiver() |
- self.stdoutParser.delimiter = "\n" |
- self.stdoutParser.lineReceived = self.outLineReceived |
- self.stdoutParser.transport = self # for the .disconnecting attribute |
- self.disconnecting = False |
- |
- self.stderrParser = basic.LineOnlyReceiver() |
- self.stderrParser.delimiter = "\n" |
- self.stderrParser.lineReceived = self.errLineReceived |
- self.stderrParser.transport = self |
- |
- def setMaxLineLength(self, max_length): |
- """ |
- Set the maximum line length: lines longer than max_length are |
- dropped. Default is 16384 bytes. Use sys.maxint for effective |
- infinity. |
- """ |
- self.stdoutParser.MAX_LENGTH = max_length |
- self.stderrParser.MAX_LENGTH = max_length |
- |
- def outReceived(self, data): |
- self.stdoutParser.dataReceived(data) |
- |
- def errReceived(self, data): |
- self.stderrParser.dataReceived(data) |
- |
- def outLineReceived(self, line): |
- """This will be called with complete stdout lines (not including the |
- delimiter). Override this in your observer.""" |
- pass |
- |
- def errLineReceived(self, line): |
- """This will be called with complete lines of stderr (not including |
- the delimiter). Override this in your observer.""" |
- pass |
- |
- |
-class RemoteShellCommand(LoggedRemoteCommand): |
- """This class helps you run a shell command on the build slave. It will |
- accumulate all the command's output into a Log named 'stdio'. When the |
- command is finished, it will fire a Deferred. You can then check the |
- results of the command and parse the output however you like.""" |
- |
- def __init__(self, workdir, command, env=None, |
- want_stdout=1, want_stderr=1, |
- timeout=20*60, maxTime=None, logfiles={}, |
- usePTY="slave-config", logEnviron=True, **kwargs): |
- """ |
- @type workdir: string |
- @param workdir: directory where the command ought to run, |
- relative to the Builder's home directory. Defaults to |
- '.': the same as the Builder's homedir. This should |
- probably be '.' for the initial 'cvs checkout' |
- command (which creates a workdir), and the Build-wide |
- workdir for all subsequent commands (including |
- compiles and 'cvs update'). |
- |
- @type command: list of strings (or string) |
- @param command: the shell command to run, like 'make all' or |
- 'cvs update'. This should be a list or tuple |
- which can be used directly as the argv array. |
- For backwards compatibility, if this is a |
- string, the text will be given to '/bin/sh -c |
- %s'. |
- |
- @type env: dict of string->string |
- @param env: environment variables to add or change for the |
- slave. Each command gets a separate |
- environment; all inherit the slave's initial |
- one. TODO: make it possible to delete some or |
- all of the slave's environment. |
- |
- @type want_stdout: bool |
- @param want_stdout: defaults to True. Set to False if stdout should |
- be thrown away. Do this to avoid storing or |
- sending large amounts of useless data. |
- |
- @type want_stderr: bool |
- @param want_stderr: False if stderr should be thrown away |
- |
- @type timeout: int |
- @param timeout: tell the remote that if the command fails to |
- produce any output for this number of seconds, |
- the command is hung and should be killed. Use |
- None to disable the timeout. |
- |
- @param logEnviron: whether to log env vars on the slave side |
- |
- @type maxTime: int |
- @param maxTime: tell the remote that if the command fails to complete |
- in this number of seconds, the command should be |
- killed. Use None to disable maxTime. |
- """ |
- |
- self.command = command # stash .command, set it later |
- if env is not None: |
- # avoid mutating the original master.cfg dictionary. Each |
- # ShellCommand gets its own copy, any start() methods won't be |
- # able to modify the original. |
- env = env.copy() |
- args = {'workdir': workdir, |
- 'env': env, |
- 'want_stdout': want_stdout, |
- 'want_stderr': want_stderr, |
- 'logfiles': logfiles, |
- 'timeout': timeout, |
- 'maxTime': maxTime, |
- 'usePTY': usePTY, |
- 'logEnviron': logEnviron, |
- } |
- LoggedRemoteCommand.__init__(self, "shell", args) |
- |
- def start(self): |
- self.args['command'] = self.command |
- if self.remote_command == "shell": |
- # non-ShellCommand slavecommands are responsible for doing this |
- # fixup themselves |
- if self.step.slaveVersion("shell", "old") == "old": |
- self.args['dir'] = self.args['workdir'] |
- what = "command '%s' in dir '%s'" % (self.args['command'], |
- self.args['workdir']) |
- log.msg(what) |
- return LoggedRemoteCommand.start(self) |
- |
- def __repr__(self): |
- return "<RemoteShellCommand '%s'>" % repr(self.command) |
- |
-class BuildStep: |
- """ |
- I represent a single step of the build process. This step may involve |
- zero or more commands to be run in the build slave, as well as arbitrary |
- processing on the master side. Regardless of how many slave commands are |
- run, the BuildStep will result in a single status value. |
- |
- The step is started by calling startStep(), which returns a Deferred that |
- fires when the step finishes. See C{startStep} for a description of the |
- results provided by that Deferred. |
- |
- __init__ and start are good methods to override. Don't forget to upcall |
- BuildStep.__init__ or bad things will happen. |
- |
- To launch a RemoteCommand, pass it to .runCommand and wait on the |
- Deferred it returns. |
- |
- Each BuildStep generates status as it runs. This status data is fed to |
- the L{buildbot.status.builder.BuildStepStatus} listener that sits in |
- C{self.step_status}. It can also feed progress data (like how much text |
- is output by a shell command) to the |
- L{buildbot.status.progress.StepProgress} object that lives in |
- C{self.progress}, by calling C{self.setProgress(metric, value)} as it |
- runs. |
- |
- @type build: L{buildbot.process.base.Build} |
- @ivar build: the parent Build which is executing this step |
- |
- @type progress: L{buildbot.status.progress.StepProgress} |
- @ivar progress: tracks ETA for the step |
- |
- @type step_status: L{buildbot.status.builder.BuildStepStatus} |
- @ivar step_status: collects output status |
- """ |
- |
- # these parameters are used by the parent Build object to decide how to |
- # interpret our results. haltOnFailure will affect the build process |
- # immediately, the others will be taken into consideration when |
- # determining the overall build status. |
- # |
- # steps that are makred as alwaysRun will be run regardless of the outcome |
- # of previous steps (especially steps with haltOnFailure=True) |
- haltOnFailure = False |
- flunkOnWarnings = False |
- flunkOnFailure = False |
- warnOnWarnings = False |
- warnOnFailure = False |
- alwaysRun = False |
- |
- # 'parms' holds a list of all the parameters we care about, to allow |
- # users to instantiate a subclass of BuildStep with a mixture of |
- # arguments, some of which are for us, some of which are for the subclass |
- # (or a delegate of the subclass, like how ShellCommand delivers many |
- # arguments to the RemoteShellCommand that it creates). Such delegating |
- # subclasses will use this list to figure out which arguments are meant |
- # for us and which should be given to someone else. |
- parms = ['name', 'locks', |
- 'haltOnFailure', |
- 'flunkOnWarnings', |
- 'flunkOnFailure', |
- 'warnOnWarnings', |
- 'warnOnFailure', |
- 'alwaysRun', |
- 'progressMetrics', |
- 'doStepIf', |
- ] |
- |
- name = "generic" |
- locks = [] |
- progressMetrics = () # 'time' is implicit |
- useProgress = True # set to False if step is really unpredictable |
- build = None |
- step_status = None |
- progress = None |
- # doStepIf can be False, True, or a function that returns False or True |
- doStepIf = True |
- |
- def __init__(self, **kwargs): |
- self.factory = (self.__class__, dict(kwargs)) |
- for p in self.__class__.parms: |
- if kwargs.has_key(p): |
- setattr(self, p, kwargs[p]) |
- del kwargs[p] |
- if kwargs: |
- why = "%s.__init__ got unexpected keyword argument(s) %s" \ |
- % (self, kwargs.keys()) |
- raise TypeError(why) |
- self._pendingLogObservers = [] |
- |
- def setBuild(self, build): |
- # subclasses which wish to base their behavior upon qualities of the |
- # Build (e.g. use the list of changed files to run unit tests only on |
- # code which has been modified) should do so here. The Build is not |
- # available during __init__, but setBuild() will be called just |
- # afterwards. |
- self.build = build |
- |
- def setBuildSlave(self, buildslave): |
- self.buildslave = buildslave |
- |
- def setDefaultWorkdir(self, workdir): |
- # The Build calls this just after __init__(). ShellCommand |
- # and variants use a slave-side workdir, but some other steps |
- # do not. Subclasses which use a workdir should use the value |
- # set by this method unless they were constructed with |
- # something more specific. |
- pass |
- |
- def addFactoryArguments(self, **kwargs): |
- self.factory[1].update(kwargs) |
- |
- def getStepFactory(self): |
- return self.factory |
- |
- def setStepStatus(self, step_status): |
- self.step_status = step_status |
- |
- def setupProgress(self): |
- if self.useProgress: |
- sp = progress.StepProgress(self.name, self.progressMetrics) |
- self.progress = sp |
- self.step_status.setProgress(sp) |
- return sp |
- return None |
- |
- def setProgress(self, metric, value): |
- """BuildSteps can call self.setProgress() to announce progress along |
- some metric.""" |
- if self.progress: |
- self.progress.setProgress(metric, value) |
- |
- def getProperty(self, propname): |
- return self.build.getProperty(propname) |
- |
- def setProperty(self, propname, value, source="Step"): |
- self.build.setProperty(propname, value, source) |
- |
- def startStep(self, remote): |
- """Begin the step. This returns a Deferred that will fire when the |
- step finishes. |
- |
- This deferred fires with a tuple of (result, [extra text]), although |
- older steps used to return just the 'result' value, so the receiving |
- L{base.Build} needs to be prepared to handle that too. C{result} is |
- one of the SUCCESS/WARNINGS/FAILURE/SKIPPED constants from |
- L{buildbot.status.builder}, and the extra text is a list of short |
- strings which should be appended to the Build's text results. This |
- text allows a test-case step which fails to append B{17 tests} to the |
- Build's status, in addition to marking the build as failing. |
- |
- The deferred will errback if the step encounters an exception, |
- including an exception on the slave side (or if the slave goes away |
- altogether). Failures in shell commands (rc!=0) will B{not} cause an |
- errback, in general the BuildStep will evaluate the results and |
- decide whether to treat it as a WARNING or FAILURE. |
- |
- @type remote: L{twisted.spread.pb.RemoteReference} |
- @param remote: a reference to the slave's |
- L{buildbot.slave.bot.SlaveBuilder} instance where any |
- RemoteCommands may be run |
- """ |
- |
- self.remote = remote |
- self.deferred = defer.Deferred() |
- # convert all locks into their real form |
- lock_list = [] |
- for access in self.locks: |
- if not isinstance(access, locks.LockAccess): |
- # Buildbot 0.7.7 compability: user did not specify access |
- access = access.defaultAccess() |
- lock = self.build.builder.botmaster.getLockByID(access.lockid) |
- lock_list.append((lock, access)) |
- self.locks = lock_list |
- # then narrow SlaveLocks down to the slave that this build is being |
- # run on |
- self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks] |
- for l, la in self.locks: |
- if l in self.build.locks: |
- log.msg("Hey, lock %s is claimed by both a Step (%s) and the" |
- " parent Build (%s)" % (l, self, self.build)) |
- raise RuntimeError("lock claimed by both Step and Build") |
- d = self.acquireLocks() |
- d.addCallback(self._startStep_2) |
- return self.deferred |
- |
- def acquireLocks(self, res=None): |
- log.msg("acquireLocks(step %s, locks %s)" % (self, self.locks)) |
- if not self.locks: |
- return defer.succeed(None) |
- for lock, access in self.locks: |
- if not lock.isAvailable(access): |
- log.msg("step %s waiting for lock %s" % (self, lock)) |
- d = lock.waitUntilMaybeAvailable(self, access) |
- d.addCallback(self.acquireLocks) |
- return d |
- # all locks are available, claim them all |
- for lock, access in self.locks: |
- lock.claim(self, access) |
- return defer.succeed(None) |
- |
- def _startStep_2(self, res): |
- if self.progress: |
- self.progress.start() |
- self.step_status.stepStarted() |
- try: |
- skip = None |
- if isinstance(self.doStepIf, bool): |
- if not self.doStepIf: |
- skip = SKIPPED |
- elif not self.doStepIf(self): |
- skip = SKIPPED |
- |
- if skip is None: |
- skip = self.start() |
- |
- if skip == SKIPPED: |
- # this return value from self.start is a shortcut |
- # to finishing the step immediately |
- reactor.callLater(0, self.finished, SKIPPED) |
- except: |
- log.msg("BuildStep.startStep exception in .start") |
- self.failed(Failure()) |
- |
- def start(self): |
- """Begin the step. Override this method and add code to do local |
- processing, fire off remote commands, etc. |
- |
- To spawn a command in the buildslave, create a RemoteCommand instance |
- and run it with self.runCommand:: |
- |
- c = RemoteCommandFoo(args) |
- d = self.runCommand(c) |
- d.addCallback(self.fooDone).addErrback(self.failed) |
- |
- As the step runs, it should send status information to the |
- BuildStepStatus:: |
- |
- self.step_status.setText(['compile', 'failed']) |
- self.step_status.setText2(['4', 'warnings']) |
- |
- To have some code parse stdio (or other log stream) in realtime, add |
- a LogObserver subclass. This observer can use self.step.setProgress() |
- to provide better progress notification to the step.:: |
- |
- self.addLogObserver('stdio', MyLogObserver()) |
- |
- To add a LogFile, use self.addLog. Make sure it gets closed when it |
- finishes. When giving a Logfile to a RemoteShellCommand, just ask it |
- to close the log when the command completes:: |
- |
- log = self.addLog('output') |
- cmd = RemoteShellCommand(args) |
- cmd.useLog(log, closeWhenFinished=True) |
- |
- You can also create complete Logfiles with generated text in a single |
- step:: |
- |
- self.addCompleteLog('warnings', text) |
- |
- When the step is done, it should call self.finished(result). 'result' |
- will be provided to the L{buildbot.process.base.Build}, and should be |
- one of the constants defined above: SUCCESS, WARNINGS, FAILURE, or |
- SKIPPED. |
- |
- If the step encounters an exception, it should call self.failed(why). |
- 'why' should be a Failure object. This automatically fails the whole |
- build with an exception. It is a good idea to add self.failed as an |
- errback to any Deferreds you might obtain. |
- |
- If the step decides it does not need to be run, start() can return |
- the constant SKIPPED. This fires the callback immediately: it is not |
- necessary to call .finished yourself. This can also indicate to the |
- status-reporting mechanism that this step should not be displayed. |
- |
- A step can be configured to only run under certain conditions. To |
- do this, set the step's doStepIf to a boolean value, or to a function |
- that returns a boolean value. If the value or function result is |
- False, then the step will return SKIPPED without doing anything, |
- otherwise the step will be executed normally. If you set doStepIf |
- to a function, that function should accept one parameter, which will |
- be the Step object itself.""" |
- |
- raise NotImplementedError("your subclass must implement this method") |
- |
- def interrupt(self, reason): |
- """Halt the command, either because the user has decided to cancel |
- the build ('reason' is a string), or because the slave has |
- disconnected ('reason' is a ConnectionLost Failure). Any further |
- local processing should be skipped, and the Step completed with an |
- error status. The results text should say something useful like |
- ['step', 'interrupted'] or ['remote', 'lost']""" |
- pass |
- |
- def releaseLocks(self): |
- log.msg("releaseLocks(%s): %s" % (self, self.locks)) |
- for lock, access in self.locks: |
- lock.release(self, access) |
- |
- def finished(self, results): |
- if self.progress: |
- self.progress.finish() |
- self.step_status.stepFinished(results) |
- self.releaseLocks() |
- self.deferred.callback(results) |
- |
- def failed(self, why): |
- # if isinstance(why, pb.CopiedFailure): # a remote exception might |
- # only have short traceback, so formatFailure is not as useful as |
- # you'd like (no .frames, so no traceback is displayed) |
- log.msg("BuildStep.failed, traceback follows") |
- log.err(why) |
- try: |
- if self.progress: |
- self.progress.finish() |
- self.addHTMLLog("err.html", formatFailure(why)) |
- self.addCompleteLog("err.text", why.getTraceback()) |
- # could use why.getDetailedTraceback() for more information |
- self.step_status.setText([self.name, "exception"]) |
- self.step_status.setText2([self.name]) |
- self.step_status.stepFinished(EXCEPTION) |
- except: |
- log.msg("exception during failure processing") |
- log.err() |
- # the progress stuff may still be whacked (the StepStatus may |
- # think that it is still running), but the build overall will now |
- # finish |
- try: |
- self.releaseLocks() |
- except: |
- log.msg("exception while releasing locks") |
- log.err() |
- |
- log.msg("BuildStep.failed now firing callback") |
- self.deferred.callback(EXCEPTION) |
- |
- # utility methods that BuildSteps may find useful |
- |
- def slaveVersion(self, command, oldversion=None): |
- """Return the version number of the given slave command. For the |
- commands defined in buildbot.slave.commands, this is the value of |
- 'cvs_ver' at the top of that file. Non-existent commands will return |
- a value of None. Buildslaves running buildbot-0.5.0 or earlier did |
- not respond to the version query: commands on those slaves will |
- return a value of OLDVERSION, so you can distinguish between old |
- buildslaves and missing commands. |
- |
- If you know that <=0.5.0 buildslaves have the command you want (CVS |
- and SVN existed back then, but none of the other VC systems), then it |
- makes sense to call this with oldversion='old'. If the command you |
- want is newer than that, just leave oldversion= unspecified, and the |
- command will return None for a buildslave that does not implement the |
- command. |
- """ |
- return self.build.getSlaveCommandVersion(command, oldversion) |
- |
- def slaveVersionIsOlderThan(self, command, minversion): |
- sv = self.build.getSlaveCommandVersion(command, None) |
- if sv is None: |
- return True |
- # the version we get back is a string form of the CVS version number |
- # of the slave's buildbot/slave/commands.py, something like 1.39 . |
- # This might change in the future (I might move away from CVS), but |
- # if so I'll keep updating that string with suitably-comparable |
- # values. |
- if sv.split(".") < minversion.split("."): |
- return True |
- return False |
- |
- def getSlaveName(self): |
- return self.build.getSlaveName() |
- |
- def addLog(self, name): |
- loog = self.step_status.addLog(name) |
- self._connectPendingLogObservers() |
- return loog |
- |
- def getLog(self, name): |
- for l in self.step_status.getLogs(): |
- if l.getName() == name: |
- return l |
- raise KeyError("no log named '%s'" % (name,)) |
- |
- def addCompleteLog(self, name, text): |
- log.msg("addCompleteLog(%s)" % name) |
- loog = self.step_status.addLog(name) |
- size = loog.chunkSize |
- for start in range(0, len(text), size): |
- loog.addStdout(text[start:start+size]) |
- loog.finish() |
- self._connectPendingLogObservers() |
- |
- def addHTMLLog(self, name, html): |
- log.msg("addHTMLLog(%s)" % name) |
- self.step_status.addHTMLLog(name, html) |
- self._connectPendingLogObservers() |
- |
- def addLogObserver(self, logname, observer): |
- assert interfaces.ILogObserver.providedBy(observer) |
- observer.setStep(self) |
- self._pendingLogObservers.append((logname, observer)) |
- self._connectPendingLogObservers() |
- |
- def _connectPendingLogObservers(self): |
- if not self._pendingLogObservers: |
- return |
- if not self.step_status: |
- return |
- current_logs = {} |
- for loog in self.step_status.getLogs(): |
- current_logs[loog.getName()] = loog |
- for logname, observer in self._pendingLogObservers[:]: |
- if logname in current_logs: |
- observer.setLog(current_logs[logname]) |
- self._pendingLogObservers.remove((logname, observer)) |
- |
- def addURL(self, name, url): |
- """Add a BuildStep URL to this step. |
- |
- An HREF to this URL will be added to any HTML representations of this |
- step. This allows a step to provide links to external web pages, |
- perhaps to provide detailed HTML code coverage results or other forms |
- of build status. |
- """ |
- self.step_status.addURL(name, url) |
- |
- def runCommand(self, c): |
- c.buildslave = self.buildslave |
- d = c.run(self, self.remote) |
- return d |
- |
- |
-class OutputProgressObserver(LogObserver): |
- length = 0 |
- |
- def __init__(self, name): |
- self.name = name |
- |
- def logChunk(self, build, step, log, channel, text): |
- self.length += len(text) |
- self.step.setProgress(self.name, self.length) |
- |
-class LoggingBuildStep(BuildStep): |
- """This is an abstract base class, suitable for inheritance by all |
- BuildSteps that invoke RemoteCommands which emit stdout/stderr messages. |
- """ |
- |
- progressMetrics = ('output',) |
- logfiles = {} |
- |
- parms = BuildStep.parms + ['logfiles', 'lazylogfiles'] |
- |
- def __init__(self, logfiles={}, lazylogfiles=False, *args, **kwargs): |
- BuildStep.__init__(self, *args, **kwargs) |
- self.addFactoryArguments(logfiles=logfiles, |
- lazylogfiles=lazylogfiles) |
- # merge a class-level 'logfiles' attribute with one passed in as an |
- # argument |
- self.logfiles = self.logfiles.copy() |
- self.logfiles.update(logfiles) |
- self.lazylogfiles = lazylogfiles |
- self.addLogObserver('stdio', OutputProgressObserver("output")) |
- |
- def describe(self, done=False): |
- raise NotImplementedError("implement this in a subclass") |
- |
- def addLogFile(self, logname, filename): |
- """ |
- This allows to add logfiles after construction, but before calling |
- startCommand(). |
- """ |
- self.logfiles[logname] = filename |
- |
- def startCommand(self, cmd, errorMessages=[]): |
- """ |
- @param cmd: a suitable RemoteCommand which will be launched, with |
- all output being put into our self.stdio_log LogFile |
- """ |
- log.msg("ShellCommand.startCommand(cmd=%s)" % (cmd,)) |
- args = cmd.args |
- if cmd.args.get("patch", None): |
- # Don't print the patch in the logs, it's often too large and not |
- # useful. |
- args = cmd.args.copy() |
- # This is usually a tuple so convert it to a list to be able to modify |
- # it. |
- patch = list(args['patch'][:]) |
- if patch[1]: |
- patch[1] = "(%s bytes)" % len(patch[1]) |
- args['patch'] = patch |
- log.msg(" cmd.args = %r" % (args)) |
- self.cmd = cmd # so we can interrupt it |
- self.step_status.setText(self.describe(False)) |
- |
- # stdio is the first log |
- self.stdio_log = stdio_log = self.addLog("stdio") |
- cmd.useLog(stdio_log, True) |
- for em in errorMessages: |
- stdio_log.addHeader(em) |
- # TODO: consider setting up self.stdio_log earlier, and have the |
- # code that passes in errorMessages instead call |
- # self.stdio_log.addHeader() directly. |
- |
- # there might be other logs |
- self.setupLogfiles(cmd, self.logfiles) |
- |
- d = self.runCommand(cmd) # might raise ConnectionLost |
- d.addCallback(lambda res: self.commandComplete(cmd)) |
- d.addCallback(lambda res: self.createSummary(cmd.logs['stdio'])) |
- d.addCallback(lambda res: self.evaluateCommand(cmd)) # returns results |
- def _gotResults(results): |
- self.setStatus(cmd, results) |
- return results |
- d.addCallback(_gotResults) # returns results |
- d.addCallbacks(self.finished, self.checkDisconnect) |
- d.addErrback(self.failed) |
- |
- def setupLogfiles(self, cmd, logfiles): |
- """Set up any additional logfiles= logs. |
- """ |
- for logname,remotefilename in logfiles.items(): |
- if self.lazylogfiles: |
- # Ask LoggedRemoteCommand to watch a logfile, but only add |
- # it when/if we see any data. |
- # |
- # The dummy default argument local_logname is a work-around for |
- # Python name binding; default values are bound by value, but |
- # captured variables in the body are bound by name. |
- callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname) |
- cmd.useLogDelayed(logname, callback, True) |
- else: |
- # tell the BuildStepStatus to add a LogFile |
- newlog = self.addLog(logname) |
- # and tell the LoggedRemoteCommand to feed it |
- cmd.useLog(newlog, True) |
- |
- def interrupt(self, reason): |
- # TODO: consider adding an INTERRUPTED or STOPPED status to use |
- # instead of FAILURE, might make the text a bit more clear. |
- # 'reason' can be a Failure, or text |
- self.addCompleteLog('interrupt', str(reason)) |
- d = self.cmd.interrupt(reason) |
- return d |
- |
- def checkDisconnect(self, f): |
- f.trap(error.ConnectionLost) |
- self.step_status.setText(self.describe(True) + |
- ["failed", "slave", "lost"]) |
- self.step_status.setText2(["failed", "slave", "lost"]) |
- return self.finished(EXCEPTION) |
- |
- # to refine the status output, override one or more of the following |
- # methods. Change as little as possible: start with the first ones on |
- # this list and only proceed further if you have to |
- # |
- # createSummary: add additional Logfiles with summarized results |
- # evaluateCommand: decides whether the step was successful or not |
- # |
- # getText: create the final per-step text strings |
- # describeText2: create the strings added to the overall build status |
- # |
- # getText2: only adds describeText2() when the step affects build status |
- # |
- # setStatus: handles all status updating |
- |
- # commandComplete is available for general-purpose post-completion work. |
- # It is a good place to do one-time parsing of logfiles, counting |
- # warnings and errors. It should probably stash such counts in places |
- # like self.warnings so they can be picked up later by your getText |
- # method. |
- |
- # TODO: most of this stuff should really be on BuildStep rather than |
- # ShellCommand. That involves putting the status-setup stuff in |
- # .finished, which would make it hard to turn off. |
- |
- def commandComplete(self, cmd): |
- """This is a general-purpose hook method for subclasses. It will be |
- called after the remote command has finished, but before any of the |
- other hook functions are called.""" |
- pass |
- |
- def createSummary(self, log): |
- """To create summary logs, do something like this: |
- warnings = grep('^Warning:', log.getText()) |
- self.addCompleteLog('warnings', warnings) |
- """ |
- pass |
- |
- def evaluateCommand(self, cmd): |
- """Decide whether the command was SUCCESS, WARNINGS, or FAILURE. |
- Override this to, say, declare WARNINGS if there is any stderr |
- activity, or to say that rc!=0 is not actually an error.""" |
- |
- if cmd.rc != 0: |
- return FAILURE |
- # if cmd.log.getStderr(): return WARNINGS |
- return SUCCESS |
- |
- def getText(self, cmd, results): |
- if results == SUCCESS: |
- return self.describe(True) |
- elif results == WARNINGS: |
- return self.describe(True) + ["warnings"] |
- else: |
- return self.describe(True) + ["failed"] |
- |
- def getText2(self, cmd, results): |
- """We have decided to add a short note about ourselves to the overall |
- build description, probably because something went wrong. Return a |
- short list of short strings. If your subclass counts test failures or |
- warnings of some sort, this is a good place to announce the count.""" |
- # return ["%d warnings" % warningcount] |
- # return ["%d tests" % len(failedTests)] |
- return [self.name] |
- |
- def maybeGetText2(self, cmd, results): |
- if results == SUCCESS: |
- # successful steps do not add anything to the build's text |
- pass |
- elif results == WARNINGS: |
- if (self.flunkOnWarnings or self.warnOnWarnings): |
- # we're affecting the overall build, so tell them why |
- return self.getText2(cmd, results) |
- else: |
- if (self.haltOnFailure or self.flunkOnFailure |
- or self.warnOnFailure): |
- # we're affecting the overall build, so tell them why |
- return self.getText2(cmd, results) |
- return [] |
- |
- def setStatus(self, cmd, results): |
- # this is good enough for most steps, but it can be overridden to |
- # get more control over the displayed text |
- self.step_status.setText(self.getText(cmd, results)) |
- self.step_status.setText2(self.maybeGetText2(cmd, results)) |
- |
-# (WithProperties used to be available in this module) |
-from buildbot.process.properties import WithProperties |
-_hush_pyflakes = [WithProperties] |
-del _hush_pyflakes |
- |