Trees | Indices | Help |
|
---|
|
1 # This file is part of Buildbot. Buildbot is free software: you can 2 # redistribute it and/or modify it under the terms of the GNU General Public 3 # License as published by the Free Software Foundation, version 2. 4 # 5 # This program is distributed in the hope that it will be useful, but WITHOUT 6 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 7 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 8 # details. 9 # 10 # You should have received a copy of the GNU General Public License along with 11 # this program; if not, write to the Free Software Foundation, Inc., 51 12 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 13 # 14 # Copyright Buildbot Team Members 15 16 import re 17 18 from zope.interface import implements 19 from twisted.internet import reactor, defer, error 20 from twisted.protocols import basic 21 from twisted.spread import pb 22 from twisted.python import log, components 23 from twisted.python.failure import Failure 24 from twisted.web.util import formatFailure 25 from twisted.python.reflect import accumulateClassList 26 27 from buildbot import interfaces, locks, util, config 28 from buildbot.status import progress 29 from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED, \ 30 EXCEPTION, RETRY, worst_status 31 from buildbot.process import metrics, properties 3537 38 # class-level unique identifier generator for command ids 39 _commandCounter = 0 40 41 active = False 42 rc = None 43 debug = False 44271 LoggedRemoteCommand = RemoteCommand46 self.logs = {} 47 self.delayedLogs = {} 48 self._closeWhenFinished = {} 49 self.collectStdout = collectStdout 50 self.stdout = '' 51 52 self._startTime = None 53 self._remoteElapsed = None 54 self.remote_command = remote_command 55 self.args = args 56 self.ignore_updates = ignore_updates57 6062 self.active = True 63 self.step = step 64 self.remote = remote 65 66 # generate a new command id 67 cmd_id = RemoteCommand._commandCounter 68 RemoteCommand._commandCounter += 1 69 self.commandID = "%d" % cmd_id 70 71 log.msg("%s: RemoteCommand.run [%s]" % (self, self.commandID)) 72 self.deferred = defer.Deferred() 73 74 d = defer.maybeDeferred(self._start) 75 76 # _finished is called with an error for unknown commands, errors 77 # that occur while the command is starting (including OSErrors in 78 # exec()), StaleBroker (when the connection was lost before we 79 # started), and pb.PBConnectionLost (when the slave isn't responding 80 # over this connection, perhaps it had a power failure, or NAT 81 # weirdness). If this happens, self.deferred is fired right away. 82 d.addErrback(self._finished) 83 84 # Connections which are lost while the command is running are caught 85 # when our parent Step calls our .lostRemote() method. 86 return self.deferred8789 assert interfaces.ILogFile.providedBy(loog) 90 if not logfileName: 91 logfileName = loog.getName() 92 assert logfileName not in self.logs 93 assert logfileName not in self.delayedLogs 94 self.logs[logfileName] = loog 95 self._closeWhenFinished[logfileName] = closeWhenFinished9698 assert logfileName not in self.logs 99 assert logfileName not in self.delayedLogs 100 self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished)101103 self.updates = {} 104 self._startTime = util.now() 105 106 # This method only initiates the remote command. 107 # We will receive remote_update messages as the command runs. 108 # We will get a single remote_complete when it finishes. 109 # We should fire self.deferred when the command is done. 110 d = self.remote.callRemote("startCommand", self, self.commandID, 111 self.remote_command, self.args) 112 return d113115 self.active = False 116 # call .remoteComplete. If it raises an exception, or returns the 117 # Failure that we gave it, our self.deferred will be errbacked. If 118 # it does not (either it ate the Failure or there the step finished 119 # normally and it didn't raise a new exception), self.deferred will 120 # be callbacked. 121 d = defer.maybeDeferred(self.remoteComplete, failure) 122 # arrange for the callback to get this RemoteCommand instance 123 # instead of just None 124 d.addCallback(lambda r: self) 125 # this fires the original deferred we returned from .run(), 126 # with self as the result, or a failure 127 d.addBoth(self.deferred.callback)128130 log.msg("RemoteCommand.interrupt", self, why) 131 if not self.active: 132 log.msg(" but this RemoteCommand is already inactive") 133 return defer.succeed(None) 134 if not self.remote: 135 log.msg(" but our .remote went away") 136 return defer.succeed(None) 137 if isinstance(why, Failure) and why.check(error.ConnectionLost): 138 log.msg("RemoteCommand.disconnect: lost slave") 139 self.remote = None 140 self._finished(why) 141 return defer.succeed(None) 142 143 # tell the remote command to halt. Returns a Deferred that will fire 144 # when the interrupt command has been delivered. 145 146 d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand", 147 self.commandID, str(why)) 148 # the slave may not have remote_interruptCommand 149 d.addErrback(self._interruptFailed) 150 return d151153 log.msg("RemoteCommand._interruptFailed", self) 154 # TODO: forcibly stop the Command now, since we can't stop it 155 # cleanly 156 return None157159 """ 160 I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so 161 I can receive updates from the running remote command. 162 163 @type updates: list of [object, int] 164 @param updates: list of updates from the remote command 165 """ 166 self.buildslave.messageReceivedFromSlave() 167 max_updatenum = 0 168 for (update, num) in updates: 169 #log.msg("update[%d]:" % num) 170 try: 171 if self.active and not self.ignore_updates: 172 self.remoteUpdate(update) 173 except: 174 # log failure, terminate build, let slave retire the update 175 self._finished(Failure()) 176 # TODO: what if multiple updates arrive? should 177 # skip the rest but ack them all 178 if num > max_updatenum: 179 max_updatenum = num 180 return max_updatenum181183 """ 184 Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to 185 notify me the remote command has finished. 186 187 @type failure: L{twisted.python.failure.Failure} or None 188 189 @rtype: None 190 """ 191 self.buildslave.messageReceivedFromSlave() 192 # call the real remoteComplete a moment later, but first return an 193 # acknowledgement so the slave can retire the completion message. 194 if self.active: 195 reactor.callLater(0, self._finished, failure) 196 return None197199 if 'stdio' in self.logs: 200 self.logs['stdio'].addStdout(data) 201 if self.collectStdout: 202 self.stdout += data203 207 211213 # Activate delayed logs on first data. 214 if logname in self.delayedLogs: 215 (activateCallBack, closeWhenFinished) = self.delayedLogs[logname] 216 del self.delayedLogs[logname] 217 loog = activateCallBack(self) 218 self.logs[logname] = loog 219 self._closeWhenFinished[logname] = closeWhenFinished 220 221 if logname in self.logs: 222 self.logs[logname].addStdout(data) 223 else: 224 log.msg("%s.addToLog: no such log %s" % (self, logname))225 226 @metrics.countMethod('RemoteCommand.remoteUpdate()')228 if self.debug: 229 for k,v in update.items(): 230 log.msg("Update[%s]: %s" % (k,v)) 231 if update.has_key('stdout'): 232 # 'stdout': data 233 self.addStdout(update['stdout']) 234 if update.has_key('stderr'): 235 # 'stderr': data 236 self.addStderr(update['stderr']) 237 if update.has_key('header'): 238 # 'header': data 239 self.addHeader(update['header']) 240 if update.has_key('log'): 241 # 'log': (logname, data) 242 logname, data = update['log'] 243 self.addToLog(logname, data) 244 if update.has_key('rc'): 245 rc = self.rc = update['rc'] 246 log.msg("%s rc=%s" % (self, rc)) 247 self.addHeader("program finished with exit code %d\n" % rc) 248 if update.has_key('elapsed'): 249 self._remoteElapsed = update['elapsed'] 250 251 # TODO: these should be handled at the RemoteCommand level 252 for k in update: 253 if k not in ('stdout', 'stderr', 'header', 'rc'): 254 if k not in self.updates: 255 self.updates[k] = [] 256 self.updates[k].append(update[k])257259 if self._startTime and self._remoteElapsed: 260 delta = (util.now() - self._startTime) - self._remoteElapsed 261 metrics.MetricTimeEvent.log("RemoteCommand.overhead", delta) 262 263 for name,loog in self.logs.items(): 264 if self._closeWhenFinished[name]: 265 if maybeFailure: 266 loog.addHeader("\nremoteFailed: %s" % maybeFailure) 267 else: 268 log.msg("closing log %s" % loog) 269 loog.finish() 270 return maybeFailure275 implements(interfaces.ILogObserver) 276 279 283301285 if channel == interfaces.LOG_CHANNEL_STDOUT: 286 self.outReceived(text) 287 elif channel == interfaces.LOG_CHANNEL_STDERR: 288 self.errReceived(text)289 290 # TODO: add a logEnded method? er, stepFinished? 291293 """This will be called with chunks of stdout data. Override this in 294 your observer.""" 295 pass296340305 self.stdoutParser = basic.LineOnlyReceiver() 306 self.stdoutParser.delimiter = "\n" 307 self.stdoutParser.lineReceived = self.outLineReceived 308 self.stdoutParser.transport = self # for the .disconnecting attribute 309 self.disconnecting = False 310 311 self.stderrParser = basic.LineOnlyReceiver() 312 self.stderrParser.delimiter = "\n" 313 self.stderrParser.lineReceived = self.errLineReceived 314 self.stderrParser.transport = self315317 """ 318 Set the maximum line length: lines longer than max_length are 319 dropped. Default is 16384 bytes. Use sys.maxint for effective 320 infinity. 321 """ 322 self.stdoutParser.MAX_LENGTH = max_length 323 self.stderrParser.MAX_LENGTH = max_length324 327 330332 """This will be called with complete stdout lines (not including the 333 delimiter). Override this in your observer.""" 334 pass335383343 - def __init__(self, workdir, command, env=None, 344 want_stdout=1, want_stderr=1, 345 timeout=20*60, maxTime=None, logfiles={}, 346 usePTY="slave-config", logEnviron=True, 347 collectStdout=False, interruptSignal=None):348 349 self.command = command # stash .command, set it later 350 if env is not None: 351 # avoid mutating the original master.cfg dictionary. Each 352 # ShellCommand gets its own copy, any start() methods won't be 353 # able to modify the original. 354 env = env.copy() 355 args = {'workdir': workdir, 356 'env': env, 357 'want_stdout': want_stdout, 358 'want_stderr': want_stderr, 359 'logfiles': logfiles, 360 'timeout': timeout, 361 'maxTime': maxTime, 362 'usePTY': usePTY, 363 'logEnviron': logEnviron, 364 } 365 if interruptSignal is not None: 366 args['interruptSignal'] = interruptSignal 367 RemoteCommand.__init__(self, "shell", args, collectStdout=collectStdout)368370 self.args['command'] = self.command 371 if self.remote_command == "shell": 372 # non-ShellCommand slavecommands are responsible for doing this 373 # fixup themselves 374 if self.step.slaveVersion("shell", "old") == "old": 375 self.args['dir'] = self.args['workdir'] 376 what = "command '%s' in dir '%s'" % (self.args['command'], 377 self.args['workdir']) 378 log.msg(what) 379 return RemoteCommand._start(self)380382 return "<RemoteShellCommand '%s'>" % repr(self.command)385 386 haltOnFailure = False 387 flunkOnWarnings = False 388 flunkOnFailure = False 389 warnOnWarnings = False 390 warnOnFailure = False 391 alwaysRun = False 392 doStepIf = True 393 hideStepIf = False 394 395 # properties set on a build step are, by nature, always runtime properties 396 set_runtime_properties = True 397 398 # 'parms' holds a list of all the parameters we care about, to allow 399 # users to instantiate a subclass of BuildStep with a mixture of 400 # arguments, some of which are for us, some of which are for the subclass 401 # (or a delegate of the subclass, like how ShellCommand delivers many 402 # arguments to the RemoteShellCommand that it creates). Such delegating 403 # subclasses will use this list to figure out which arguments are meant 404 # for us and which should be given to someone else. 405 parms = ['name', 'locks', 406 'haltOnFailure', 407 'flunkOnWarnings', 408 'flunkOnFailure', 409 'warnOnWarnings', 410 'warnOnFailure', 411 'alwaysRun', 412 'progressMetrics', 413 'useProgress', 414 'doStepIf', 415 'hideStepIf', 416 ] 417 418 name = "generic" 419 locks = [] 420 progressMetrics = () # 'time' is implicit 421 useProgress = True # set to False if step is really unpredictable 422 build = None 423 step_status = None 424 progress = None 425721 722 components.registerAdapter( 723 lambda step : interfaces.IProperties(step.build), 724 BuildStep, interfaces.IProperties) 736427 self.factory = (self.__class__, dict(kwargs)) 428 for p in self.__class__.parms: 429 if kwargs.has_key(p): 430 setattr(self, p, kwargs[p]) 431 del kwargs[p] 432 if kwargs: 433 why = "%s.__init__ got unexpected keyword argument(s) %s" \ 434 % (self, kwargs.keys()) 435 raise TypeError(why) 436 self._pendingLogObservers = [] 437 438 self._acquiringLock = None 439 self.stopped = False440442 return [self.name]443 446 449 452 455457 return self.factory458 461463 if self.useProgress: 464 sp = progress.StepProgress(self.name, self.progressMetrics) 465 self.progress = sp 466 self.step_status.setProgress(sp) 467 return sp 468 return None469 473475 self.remote = remote 476 self.deferred = defer.Deferred() 477 # convert all locks into their real form 478 lock_list = [] 479 for access in self.locks: 480 if not isinstance(access, locks.LockAccess): 481 # Buildbot 0.7.7 compability: user did not specify access 482 access = access.defaultAccess() 483 lock = self.build.builder.botmaster.getLockByID(access.lockid) 484 lock_list.append((lock, access)) 485 self.locks = lock_list 486 # then narrow SlaveLocks down to the slave that this build is being 487 # run on 488 self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks] 489 for l, la in self.locks: 490 if l in self.build.locks: 491 log.msg("Hey, lock %s is claimed by both a Step (%s) and the" 492 " parent Build (%s)" % (l, self, self.build)) 493 raise RuntimeError("lock claimed by both Step and Build") 494 495 # Set the step's text here so that the stepStarted notification sees 496 # the correct description 497 self.step_status.setText(self.describe(False)) 498 self.step_status.stepStarted() 499 500 d = self.acquireLocks() 501 d.addCallback(self._startStep_2) 502 d.addErrback(self.failed) 503 return self.deferred504506 self._acquiringLock = None 507 if not self.locks: 508 return defer.succeed(None) 509 if self.stopped: 510 return defer.succeed(None) 511 log.msg("acquireLocks(step %s, locks %s)" % (self, self.locks)) 512 for lock, access in self.locks: 513 if not lock.isAvailable(access): 514 self.step_status.setWaitingForLocks(True) 515 log.msg("step %s waiting for lock %s" % (self, lock)) 516 d = lock.waitUntilMaybeAvailable(self, access) 517 d.addCallback(self.acquireLocks) 518 self._acquiringLock = (lock, access, d) 519 return d 520 # all locks are available, claim them all 521 for lock, access in self.locks: 522 lock.claim(self, access) 523 self.step_status.setWaitingForLocks(False) 524 return defer.succeed(None)525527 if self.stopped: 528 self.finished(EXCEPTION) 529 return 530 531 if self.progress: 532 self.progress.start() 533 534 if isinstance(self.doStepIf, bool): 535 doStep = defer.succeed(self.doStepIf) 536 else: 537 doStep = defer.maybeDeferred(self.doStepIf, self) 538 539 renderables = [] 540 accumulateClassList(self.__class__, 'renderables', renderables) 541 542 for renderable in renderables: 543 setattr(self, renderable, self.build.render(getattr(self, renderable))) 544 545 doStep.addCallback(self._startStep_3) 546 return doStep547549 try: 550 if doStep: 551 if self.start() == SKIPPED: 552 doStep = False 553 except: 554 log.msg("BuildStep.startStep exception in .start") 555 self.failed(Failure()) 556 557 if not doStep: 558 self.step_status.setText(self.describe(True) + ['skipped']) 559 self.step_status.setSkipped(True) 560 # this return value from self.start is a shortcut to finishing 561 # the step immediately; we skip calling finished() as 562 # subclasses may have overridden that an expect it to be called 563 # after start() (bug #837) 564 reactor.callLater(0, self._finishFinished, SKIPPED)565 568570 self.stopped = True 571 if self._acquiringLock: 572 lock, access, d = self._acquiringLock 573 lock.stopWaitingUntilAvailable(self, access, d) 574 d.callback(None)575577 log.msg("releaseLocks(%s): %s" % (self, self.locks)) 578 for lock, access in self.locks: 579 if lock.isOwner(self, access): 580 lock.release(self, access) 581 else: 582 # This should only happen if we've been interrupted 583 assert self.stopped584586 if self.stopped and results != RETRY: 587 # We handle this specially because we don't care about 588 # the return code of an interrupted command; we know 589 # that this should just be exception due to interrupt 590 # At the same time we must respect RETRY status because it's used 591 # to retry interrupted build due to some other issues for example 592 # due to slave lost 593 results = EXCEPTION 594 self.step_status.setText(self.describe(True) + 595 ["interrupted"]) 596 self.step_status.setText2(["interrupted"]) 597 self._finishFinished(results)598600 # internal function to indicate that this step is done; this is separated 601 # from finished() so that subclasses can override finished() 602 if self.progress: 603 self.progress.finish() 604 self.step_status.stepFinished(results) 605 606 hidden = self._maybeEvaluate(self.hideStepIf, results, self) 607 self.step_status.setHidden(hidden) 608 609 self.releaseLocks() 610 self.deferred.callback(results)611613 # This can either be a BuildStepFailed exception/failure, meaning we 614 # should call self.finished, or it can be a real exception, which should 615 # be recorded as such. 616 if why.check(BuildStepFailed): 617 self.finished(FAILURE) 618 return 619 620 log.err(why, "BuildStep.failed; traceback follows") 621 try: 622 if self.progress: 623 self.progress.finish() 624 self.addHTMLLog("err.html", formatFailure(why)) 625 self.addCompleteLog("err.text", why.getTraceback()) 626 # could use why.getDetailedTraceback() for more information 627 self.step_status.setText([self.name, "exception"]) 628 self.step_status.setText2([self.name]) 629 self.step_status.stepFinished(EXCEPTION) 630 631 hidden = self._maybeEvaluate(self.hideStepIf, EXCEPTION, self) 632 self.step_status.setHidden(hidden) 633 except: 634 log.msg("exception during failure processing") 635 log.err() 636 # the progress stuff may still be whacked (the StepStatus may 637 # think that it is still running), but the build overall will now 638 # finish 639 try: 640 self.releaseLocks() 641 except: 642 log.msg("exception while releasing locks") 643 log.err() 644 645 log.msg("BuildStep.failed now firing callback") 646 self.deferred.callback(EXCEPTION)647 648 # utility methods that BuildSteps may find useful 649 652654 sv = self.build.getSlaveCommandVersion(command, None) 655 if sv is None: 656 return True 657 if map(int, sv.split(".")) < map(int, minversion.split(".")): 658 return True 659 return False660 663 668670 for l in self.step_status.getLogs(): 671 if l.getName() == name: 672 return l 673 raise KeyError("no log named '%s'" % (name,))674676 log.msg("addCompleteLog(%s)" % name) 677 loog = self.step_status.addLog(name) 678 size = loog.chunkSize 679 for start in range(0, len(text), size): 680 loog.addStdout(text[start:start+size]) 681 loog.finish() 682 self._connectPendingLogObservers()683685 log.msg("addHTMLLog(%s)" % name) 686 self.step_status.addHTMLLog(name, html) 687 self._connectPendingLogObservers()688690 assert interfaces.ILogObserver.providedBy(observer) 691 observer.setStep(self) 692 self._pendingLogObservers.append((logname, observer)) 693 self._connectPendingLogObservers()694696 if not self._pendingLogObservers: 697 return 698 if not self.step_status: 699 return 700 current_logs = {} 701 for loog in self.step_status.getLogs(): 702 current_logs[loog.getName()] = loog 703 for logname, observer in self._pendingLogObservers[:]: 704 if logname in current_logs: 705 observer.setLog(current_logs[logname]) 706 self._pendingLogObservers.remove((logname, observer))707 710 715 716 @staticmethod738 739 progressMetrics = ('output',) 740 logfiles = {} 741 742 parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func'] 743 cmd = None 744 745 renderables = [ 'logfiles', 'lazylogfiles' ] 746809749 BuildStep.__init__(self, *args, **kwargs) 750 self.addFactoryArguments(logfiles=logfiles, 751 lazylogfiles=lazylogfiles, 752 log_eval_func=log_eval_func) 753 754 if logfiles and not isinstance(logfiles, dict): 755 config.error( 756 "the ShellCommand 'logfiles' parameter must be a dictionary") 757 758 # merge a class-level 'logfiles' attribute with one passed in as an 759 # argument 760 self.logfiles = self.logfiles.copy() 761 self.logfiles.update(logfiles) 762 self.lazylogfiles = lazylogfiles 763 if log_eval_func and not callable(log_eval_func): 764 config.error( 765 "the 'log_eval_func' paramater must be a callable") 766 self.log_eval_func = log_eval_func 767 self.addLogObserver('stdio', OutputProgressObserver("output"))768 771 776778 """ 779 @param cmd: a suitable RemoteCommand which will be launched, with 780 all output being put into our self.stdio_log LogFile 781 """ 782 log.msg("ShellCommand.startCommand(cmd=%s)" % (cmd,)) 783 log.msg(" cmd.args = %r" % (cmd.args)) 784 self.cmd = cmd # so we can interrupt it 785 self.step_status.setText(self.describe(False)) 786 787 # stdio is the first log 788 self.stdio_log = stdio_log = self.addLog("stdio") 789 cmd.useLog(stdio_log, True) 790 for em in errorMessages: 791 stdio_log.addHeader(em) 792 # TODO: consider setting up self.stdio_log earlier, and have the 793 # code that passes in errorMessages instead call 794 # self.stdio_log.addHeader() directly. 795 796 # there might be other logs 797 self.setupLogfiles(cmd, self.logfiles) 798 799 d = self.runCommand(cmd) # might raise ConnectionLost 800 d.addCallback(lambda res: self.commandComplete(cmd)) 801 d.addCallback(lambda res: self.createSummary(cmd.logs['stdio'])) 802 d.addCallback(lambda res: self.evaluateCommand(cmd)) # returns results 803 def _gotResults(results): 804 self.setStatus(cmd, results) 805 return results806 d.addCallback(_gotResults) # returns results 807 d.addCallbacks(self.finished, self.checkDisconnect) 808 d.addErrback(self.failed)811 for logname,remotefilename in logfiles.items(): 812 if self.lazylogfiles: 813 # Ask RemoteCommand to watch a logfile, but only add 814 # it when/if we see any data. 815 # 816 # The dummy default argument local_logname is a work-around for 817 # Python name binding; default values are bound by value, but 818 # captured variables in the body are bound by name. 819 callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname) 820 cmd.useLogDelayed(logname, callback, True) 821 else: 822 # tell the BuildStepStatus to add a LogFile 823 newlog = self.addLog(logname) 824 # and tell the RemoteCommand to feed it 825 cmd.useLog(newlog, True)826828 # TODO: consider adding an INTERRUPTED or STOPPED status to use 829 # instead of FAILURE, might make the text a bit more clear. 830 # 'reason' can be a Failure, or text 831 BuildStep.interrupt(self, reason) 832 if self.step_status.isWaitingForLocks(): 833 self.addCompleteLog('interrupt while waiting for locks', str(reason)) 834 else: 835 self.addCompleteLog('interrupt', str(reason)) 836 837 if self.cmd: 838 d = self.cmd.interrupt(reason) 839 d.addErrback(log.err, 'while interrupting command')840842 f.trap(error.ConnectionLost) 843 self.step_status.setText(self.describe(True) + 844 ["exception", "slave", "lost"]) 845 self.step_status.setText2(["exception", "slave", "lost"]) 846 return self.finished(RETRY)847 850 853855 if self.log_eval_func: 856 return self.log_eval_func(cmd, self.step_status) 857 if cmd.rc != 0: 858 return FAILURE 859 return SUCCESS860862 if results == SUCCESS: 863 return self.describe(True) 864 elif results == WARNINGS: 865 return self.describe(True) + ["warnings"] 866 elif results == EXCEPTION: 867 return self.describe(True) + ["exception"] 868 else: 869 return self.describe(True) + ["failed"]870872 return [self.name]873875 if results == SUCCESS: 876 # successful steps do not add anything to the build's text 877 pass 878 elif results == WARNINGS: 879 if (self.flunkOnWarnings or self.warnOnWarnings): 880 # we're affecting the overall build, so tell them why 881 return self.getText2(cmd, results) 882 else: 883 if (self.haltOnFailure or self.flunkOnFailure 884 or self.warnOnFailure): 885 # we're affecting the overall build, so tell them why 886 return self.getText2(cmd, results) 887 return []888890 # this is good enough for most steps, but it can be overridden to 891 # get more control over the displayed text 892 self.step_status.setText(self.getText(cmd, results)) 893 self.step_status.setText2(self.maybeGetText2(cmd, results))894895 896 # Parses the logs for a list of regexs. Meant to be invoked like: 897 # regexes = ((re.compile(...), FAILURE), (re.compile(...), WARNINGS)) 898 # self.addStep(ShellCommand, 899 # command=..., 900 # ..., 901 # log_eval_func=lambda c,s: regex_log_evaluator(c, s, regexs) 902 # ) 903 -def regex_log_evaluator(cmd, step_status, regexes):904 worst = SUCCESS 905 if cmd.rc != 0: 906 worst = FAILURE 907 for err, possible_status in regexes: 908 # worst_status returns the worse of the two status' passed to it. 909 # we won't be changing "worst" unless possible_status is worse than it, 910 # so we don't even need to check the log if that's the case 911 if worst_status(worst, possible_status) == possible_status: 912 if isinstance(err, (basestring)): 913 err = re.compile(".*%s.*" % err, re.DOTALL) 914 for l in cmd.logs.values(): 915 if err.search(l.getText()): 916 worst = possible_status 917 return worst918 919 # (WithProperties used to be available in this module) 920 from buildbot.process.properties import WithProperties 921 _hush_pyflakes = [WithProperties] 922 del _hush_pyflakes 923
Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0.1 on Sun Mar 25 19:40:46 2012 | http://epydoc.sourceforge.net |