1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 import re
18
19 from zope.interface import implements
20 from twisted.internet import reactor, defer, error
21 from twisted.protocols import basic
22 from twisted.spread import pb
23 from twisted.python import log
24 from twisted.python.failure import Failure
25 from twisted.web.util import formatFailure
26 from twisted.python.reflect import accumulateClassList
27
28 from buildbot import interfaces, locks, util
29 from buildbot.status import progress
30 from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
31 EXCEPTION, RETRY, worst_status
32 from buildbot.process import metrics
33
34 """
35 BuildStep and RemoteCommand classes for master-side representation of the
36 build process
37 """
40 """
41 I represent a single command to be run on the slave. I handle the details
42 of reliably gathering status updates from the slave (acknowledging each),
43 and (eventually, in a future release) recovering from interrupted builds.
44 This is the master-side object that is known to the slave-side
45 L{buildbot.slave.bot.SlaveBuilder}, to which status updates are sent.
46
47 My command should be started by calling .run(), which returns a
48 Deferred that will fire when the command has finished, or will
49 errback if an exception is raised.
50
51 Typically __init__ or run() will set up self.remote_command to be a
52 string which corresponds to one of the SlaveCommands registered in
53 the buildslave, and self.args to a dictionary of arguments that will
54 be passed to the SlaveCommand instance.
55
56 start, remoteUpdate, and remoteComplete are available to be overridden
57
58 @type commandCounter: list of one int
59 @cvar commandCounter: provides a unique value for each
60 RemoteCommand executed across all slaves
61 @type active: boolean
62 @ivar active: whether the command is currently running
63 """
64
65
66 commandCounter = 0
67
68 active = False
69
70 - def __init__(self, remote_command, args, ignore_updates=False):
71 """
72 @type remote_command: string
73 @param remote_command: remote command to start. This will be
74 passed to
75 L{buildbot.slave.bot.SlaveBuilder.remote_startCommand}
76 and needs to have been registered
77 slave-side in L{buildbot.slave.registry}
78 @type args: dict
79 @param args: arguments to send to the remote command
80
81 @param ignore_updates: if true, ignore any updates from the slave side
82 """
83
84 self.remote_command = remote_command
85 self.args = args
86 self.ignore_updates = ignore_updates
87
88 - def run(self, step, remote):
114
116 """
117 Tell the slave to start executing the remote command.
118
119 @rtype: L{twisted.internet.defer.Deferred}
120 @returns: a deferred that will fire when the remote command is
121 done (with None as the result)
122 """
123
124
125 cmd_args = self.args
126 if cmd_args.has_key("logfiles") and cmd_args["logfiles"]:
127 cmd_args = cmd_args.copy()
128 cmd_args["logfiles"] = self.step.build.render(cmd_args["logfiles"])
129
130
131
132
133
134 d = self.remote.callRemote("startCommand", self, self.commandID,
135 self.remote_command, cmd_args)
136 return d
137
139
140
141
142
143 log.msg("RemoteCommand.interrupt", self, why)
144 if not self.active:
145 log.msg(" but this RemoteCommand is already inactive")
146 return
147 if not self.remote:
148 log.msg(" but our .remote went away")
149 return
150 if isinstance(why, Failure) and why.check(error.ConnectionLost):
151 log.msg("RemoteCommand.disconnect: lost slave")
152 self.remote = None
153 self._finished(why)
154 return
155
156
157
158
159 d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand",
160 self.commandID, str(why))
161
162 d.addErrback(self._interruptFailed)
163 return d
164
166 log.msg("RemoteCommand._interruptFailed", self)
167
168
169 return None
170
172 """
173 I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so
174 I can receive updates from the running remote command.
175
176 @type updates: list of [object, int]
177 @param updates: list of updates from the remote command
178 """
179 self.buildslave.messageReceivedFromSlave()
180 max_updatenum = 0
181 for (update, num) in updates:
182
183 try:
184 if self.active and not self.ignore_updates:
185 self.remoteUpdate(update)
186 except:
187
188 self._finished(Failure())
189
190
191 if num > max_updatenum:
192 max_updatenum = num
193 return max_updatenum
194
196 raise NotImplementedError("You must implement this in a subclass")
197
199 """
200 Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to
201 notify me the remote command has finished.
202
203 @type failure: L{twisted.python.failure.Failure} or None
204
205 @rtype: None
206 """
207 self.buildslave.messageReceivedFromSlave()
208
209
210 if self.active:
211 reactor.callLater(0, self._finished, failure)
212 return None
213
215 self.active = False
216
217
218
219
220
221 d = defer.maybeDeferred(self.remoteComplete, failure)
222
223
224 d.addCallback(lambda r: self)
225
226
227 d.addBoth(self.deferred.callback)
228
230 """Subclasses can override this.
231
232 This is called when the RemoteCommand has finished. 'maybeFailure'
233 will be None if the command completed normally, or a Failure
234 instance in one of the following situations:
235
236 - the slave was lost before the command was started
237 - the slave didn't respond to the startCommand message
238 - the slave raised an exception while starting the command
239 (bad command name, bad args, OSError from missing executable)
240 - the slave raised an exception while finishing the command
241 (they send back a remote_complete message with a Failure payload)
242
243 and also (for now):
244 - slave disconnected while the command was running
245
246 This method should do cleanup, like closing log files. It should
247 normally return the 'failure' argument, so that any exceptions will
248 be propagated to the Step. If it wants to consume them, return None
249 instead."""
250
251 return maybeFailure
252
254 """
255
256 I am a L{RemoteCommand} which gathers output from the remote command into
257 one or more local log files. My C{self.logs} dictionary contains
258 references to these L{buildbot.status.logfile.LogFile} instances. Any
259 stdout/stderr/header updates from the slave will be put into
260 C{self.logs['stdio']}, if it exists. If the remote command uses other log
261 files, they will go into other entries in C{self.logs}.
262
263 If you want to use stdout or stderr, you should create a LogFile named
264 'stdio' and pass it to my useLog() message. Otherwise stdout/stderr will
265 be ignored, which is probably not what you want.
266
267 Unless you tell me otherwise, when my command completes I will close all
268 the LogFiles that I know about.
269
270 @ivar logs: maps logname to a LogFile instance
271 @ivar _closeWhenFinished: maps logname to a boolean. If true, this
272 LogFile will be closed when the RemoteCommand
273 finishes. LogFiles which are shared between
274 multiple RemoteCommands should use False here.
275
276 """
277
278 rc = None
279 debug = False
280
282 self.logs = {}
283 self.delayedLogs = {}
284 self._closeWhenFinished = {}
285 RemoteCommand.__init__(self, *args, **kwargs)
286
288 return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self))
289
290 - def useLog(self, loog, closeWhenFinished=False, logfileName=None):
291 """Start routing messages from a remote logfile to a local LogFile
292
293 I take a local ILogFile instance in 'loog', and arrange to route
294 remote log messages for the logfile named 'logfileName' into it. By
295 default this logfileName comes from the ILogFile itself (using the
296 name by which the ILogFile will be displayed), but the 'logfileName'
297 argument can be used to override this. For example, if
298 logfileName='stdio', this logfile will collect text from the stdout
299 and stderr of the command.
300
301 @param loog: an instance which implements ILogFile
302 @param closeWhenFinished: a boolean, set to False if the logfile
303 will be shared between multiple
304 RemoteCommands. If True, the logfile will
305 be closed when this ShellCommand is done
306 with it.
307 @param logfileName: a string, which indicates which remote log file
308 should be routed into this ILogFile. This should
309 match one of the keys of the logfiles= argument
310 to ShellCommand.
311
312 """
313
314 assert interfaces.ILogFile.providedBy(loog)
315 if not logfileName:
316 logfileName = loog.getName()
317 assert logfileName not in self.logs
318 assert logfileName not in self.delayedLogs
319 self.logs[logfileName] = loog
320 self._closeWhenFinished[logfileName] = closeWhenFinished
321 self._startTime = None
322 self._remoteElapsed = None
323
324 - def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False):
325 assert logfileName not in self.logs
326 assert logfileName not in self.delayedLogs
327 self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished)
328
330 log.msg("LoggedRemoteCommand.start")
331 if 'stdio' not in self.logs:
332 log.msg("LoggedRemoteCommand (%s) is running a command, but "
333 "it isn't being logged to anything. This seems unusual."
334 % self)
335 self.updates = {}
336 self._startTime = util.now()
337 return RemoteCommand.start(self)
338
348
350
351 if logname in self.delayedLogs:
352 (activateCallBack, closeWhenFinished) = self.delayedLogs[logname]
353 del self.delayedLogs[logname]
354 loog = activateCallBack(self)
355 self.logs[logname] = loog
356 self._closeWhenFinished[logname] = closeWhenFinished
357
358 if logname in self.logs:
359 self.logs[logname].addStdout(data)
360 else:
361 log.msg("%s.addToLog: no such log %s" % (self, logname))
362
363 @metrics.countMethod('LoggedRemoteCommand.remoteUpdate()')
393
395 if self._startTime and self._remoteElapsed:
396 delta = (util.now() - self._startTime) - self._remoteElapsed
397 metrics.MetricTimeEvent.log("LoggedRemoteCommand.overhead", delta)
398
399 for name,loog in self.logs.items():
400 if self._closeWhenFinished[name]:
401 if maybeFailure:
402 loog.addHeader("\nremoteFailed: %s" % maybeFailure)
403 else:
404 log.msg("closing log %s" % loog)
405 loog.finish()
406 return maybeFailure
407
410 implements(interfaces.ILogObserver)
411
414
418
419 - def logChunk(self, build, step, log, channel, text):
424
425
426
428 """This will be called with chunks of stdout data. Override this in
429 your observer."""
430 pass
431
433 """This will be called with chunks of stderr data. Override this in
434 your observer."""
435 pass
436
450
452 """
453 Set the maximum line length: lines longer than max_length are
454 dropped. Default is 16384 bytes. Use sys.maxint for effective
455 infinity.
456 """
457 self.stdoutParser.MAX_LENGTH = max_length
458 self.stderrParser.MAX_LENGTH = max_length
459
461 self.stdoutParser.dataReceived(data)
462
464 self.stderrParser.dataReceived(data)
465
467 """This will be called with complete stdout lines (not including the
468 delimiter). Override this in your observer."""
469 pass
470
472 """This will be called with complete lines of stderr (not including
473 the delimiter). Override this in your observer."""
474 pass
475
478 """This class helps you run a shell command on the build slave. It will
479 accumulate all the command's output into a Log named 'stdio'. When the
480 command is finished, it will fire a Deferred. You can then check the
481 results of the command and parse the output however you like."""
482
483 - def __init__(self, workdir, command, env=None,
484 want_stdout=1, want_stderr=1,
485 timeout=20*60, maxTime=None, logfiles={},
486 usePTY="slave-config", logEnviron=True):
487 """
488 @type workdir: string
489 @param workdir: directory where the command ought to run,
490 relative to the Builder's home directory. Defaults to
491 '.': the same as the Builder's homedir. This should
492 probably be '.' for the initial 'cvs checkout'
493 command (which creates a workdir), and the Build-wide
494 workdir for all subsequent commands (including
495 compiles and 'cvs update').
496
497 @type command: list of strings (or string)
498 @param command: the shell command to run, like 'make all' or
499 'cvs update'. This should be a list or tuple
500 which can be used directly as the argv array.
501 For backwards compatibility, if this is a
502 string, the text will be given to '/bin/sh -c
503 %s'.
504
505 @type env: dict of string->string
506 @param env: environment variables to add or change for the
507 slave. Each command gets a separate
508 environment; all inherit the slave's initial
509 one. TODO: make it possible to delete some or
510 all of the slave's environment.
511
512 @type want_stdout: bool
513 @param want_stdout: defaults to True. Set to False if stdout should
514 be thrown away. Do this to avoid storing or
515 sending large amounts of useless data.
516
517 @type want_stderr: bool
518 @param want_stderr: False if stderr should be thrown away
519
520 @type timeout: int
521 @param timeout: tell the remote that if the command fails to
522 produce any output for this number of seconds,
523 the command is hung and should be killed. Use
524 None to disable the timeout.
525
526 @param logEnviron: whether to log env vars on the slave side
527
528 @type maxTime: int
529 @param maxTime: tell the remote that if the command fails to complete
530 in this number of seconds, the command should be
531 killed. Use None to disable maxTime.
532 """
533
534 self.command = command
535 if env is not None:
536
537
538
539 env = env.copy()
540 args = {'workdir': workdir,
541 'env': env,
542 'want_stdout': want_stdout,
543 'want_stderr': want_stderr,
544 'logfiles': logfiles,
545 'timeout': timeout,
546 'maxTime': maxTime,
547 'usePTY': usePTY,
548 'logEnviron': logEnviron,
549 }
550 LoggedRemoteCommand.__init__(self, "shell", args)
551
553 self.args['command'] = self.command
554 if self.remote_command == "shell":
555
556
557 if self.step.slaveVersion("shell", "old") == "old":
558 self.args['dir'] = self.args['workdir']
559 what = "command '%s' in dir '%s'" % (self.args['command'],
560 self.args['workdir'])
561 log.msg(what)
562 return LoggedRemoteCommand.start(self)
563
565 return "<RemoteShellCommand '%s'>" % repr(self.command)
566
568 """
569 I represent a single step of the build process. This step may involve
570 zero or more commands to be run in the build slave, as well as arbitrary
571 processing on the master side. Regardless of how many slave commands are
572 run, the BuildStep will result in a single status value.
573
574 The step is started by calling startStep(), which returns a Deferred that
575 fires when the step finishes. See C{startStep} for a description of the
576 results provided by that Deferred.
577
578 __init__ and start are good methods to override. Don't forget to upcall
579 BuildStep.__init__ or bad things will happen.
580
581 To launch a RemoteCommand, pass it to .runCommand and wait on the
582 Deferred it returns.
583
584 Each BuildStep generates status as it runs. This status data is fed to
585 the L{buildbot.status.buildstep.BuildStepStatus} listener that sits in
586 C{self.step_status}. It can also feed progress data (like how much text
587 is output by a shell command) to the
588 L{buildbot.status.progress.StepProgress} object that lives in
589 C{self.progress}, by calling C{self.setProgress(metric, value)} as it
590 runs.
591
592 @type build: L{buildbot.process.build.Build}
593 @ivar build: the parent Build which is executing this step
594
595 @type progress: L{buildbot.status.progress.StepProgress}
596 @ivar progress: tracks ETA for the step
597
598 @type step_status: L{buildbot.status.buildstep.BuildStepStatus}
599 @ivar step_status: collects output status
600 """
601
602
603
604
605
606
607
608
609 haltOnFailure = False
610 flunkOnWarnings = False
611 flunkOnFailure = False
612 warnOnWarnings = False
613 warnOnFailure = False
614 alwaysRun = False
615
616
617
618
619
620
621
622
623 parms = ['name', 'locks',
624 'haltOnFailure',
625 'flunkOnWarnings',
626 'flunkOnFailure',
627 'warnOnWarnings',
628 'warnOnFailure',
629 'alwaysRun',
630 'progressMetrics',
631 'doStepIf',
632 ]
633
634 name = "generic"
635 locks = []
636 progressMetrics = ()
637 useProgress = True
638 build = None
639 step_status = None
640 progress = None
641
642 doStepIf = True
643
645 self.factory = (self.__class__, dict(kwargs))
646 for p in self.__class__.parms:
647 if kwargs.has_key(p):
648 setattr(self, p, kwargs[p])
649 del kwargs[p]
650 if kwargs:
651 why = "%s.__init__ got unexpected keyword argument(s) %s" \
652 % (self, kwargs.keys())
653 raise TypeError(why)
654 self._pendingLogObservers = []
655
656 self._acquiringLock = None
657 self.stopped = False
658
661
669
672
674
675
676
677
678
679 pass
680
683
686
689
697
699 """BuildSteps can call self.setProgress() to announce progress along
700 some metric."""
701 if self.progress:
702 self.progress.setProgress(metric, value)
703
706
707 - def setProperty(self, propname, value, source="Step", runtime=True):
709
711 """Begin the step. This returns a Deferred that will fire when the
712 step finishes.
713
714 This deferred fires with a tuple of (result, [extra text]), although
715 older steps used to return just the 'result' value, so the receiving
716 L{base.Build} needs to be prepared to handle that too. C{result} is
717 one of the SUCCESS/WARNINGS/FAILURE/SKIPPED constants from
718 L{buildbot.status.builder}, and the extra text is a list of short
719 strings which should be appended to the Build's text results. This
720 text allows a test-case step which fails to append B{17 tests} to the
721 Build's status, in addition to marking the build as failing.
722
723 The deferred will errback if the step encounters an exception,
724 including an exception on the slave side (or if the slave goes away
725 altogether). Failures in shell commands (rc!=0) will B{not} cause an
726 errback, in general the BuildStep will evaluate the results and
727 decide whether to treat it as a WARNING or FAILURE.
728
729 @type remote: L{twisted.spread.pb.RemoteReference}
730 @param remote: a reference to the slave's
731 L{buildbot.slave.bot.SlaveBuilder} instance where any
732 RemoteCommands may be run
733 """
734
735 self.remote = remote
736 self.deferred = defer.Deferred()
737
738 lock_list = []
739 for access in self.locks:
740 if not isinstance(access, locks.LockAccess):
741
742 access = access.defaultAccess()
743 lock = self.build.builder.botmaster.getLockByID(access.lockid)
744 lock_list.append((lock, access))
745 self.locks = lock_list
746
747
748 self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks]
749 for l, la in self.locks:
750 if l in self.build.locks:
751 log.msg("Hey, lock %s is claimed by both a Step (%s) and the"
752 " parent Build (%s)" % (l, self, self.build))
753 raise RuntimeError("lock claimed by both Step and Build")
754
755
756
757 self.step_status.setText(self.describe(False))
758 self.step_status.stepStarted()
759
760 d = self.acquireLocks()
761 d.addCallback(self._startStep_2)
762 d.addErrback(self.failed)
763 return self.deferred
764
785
807
825
827 """Begin the step. Override this method and add code to do local
828 processing, fire off remote commands, etc.
829
830 To spawn a command in the buildslave, create a RemoteCommand instance
831 and run it with self.runCommand::
832
833 c = RemoteCommandFoo(args)
834 d = self.runCommand(c)
835 d.addCallback(self.fooDone).addErrback(self.failed)
836
837 As the step runs, it should send status information to the
838 BuildStepStatus::
839
840 self.step_status.setText(['compile', 'failed'])
841 self.step_status.setText2(['4', 'warnings'])
842
843 To have some code parse stdio (or other log stream) in realtime, add
844 a LogObserver subclass. This observer can use self.step.setProgress()
845 to provide better progress notification to the step.::
846
847 self.addLogObserver('stdio', MyLogObserver())
848
849 To add a LogFile, use self.addLog. Make sure it gets closed when it
850 finishes. When giving a Logfile to a RemoteShellCommand, just ask it
851 to close the log when the command completes::
852
853 log = self.addLog('output')
854 cmd = RemoteShellCommand(args)
855 cmd.useLog(log, closeWhenFinished=True)
856
857 You can also create complete Logfiles with generated text in a single
858 step::
859
860 self.addCompleteLog('warnings', text)
861
862 When the step is done, it should call self.finished(result). 'result'
863 will be provided to the L{buildbot.process.build.Build}, and should be
864 one of the constants defined above: SUCCESS, WARNINGS, FAILURE, or
865 SKIPPED.
866
867 If the step encounters an exception, it should call self.failed(why).
868 'why' should be a Failure object. This automatically fails the whole
869 build with an exception. It is a good idea to add self.failed as an
870 errback to any Deferreds you might obtain.
871
872 If the step decides it does not need to be run, start() can return
873 the constant SKIPPED. This fires the callback immediately: it is not
874 necessary to call .finished yourself. This can also indicate to the
875 status-reporting mechanism that this step should not be displayed.
876
877 A step can be configured to only run under certain conditions. To
878 do this, set the step's doStepIf to a boolean value, or to a function
879 that returns a boolean value. If the value or function result is
880 False, then the step will return SKIPPED without doing anything,
881 otherwise the step will be executed normally. If you set doStepIf
882 to a function, that function should accept one parameter, which will
883 be the Step object itself."""
884
885 raise NotImplementedError("your subclass must implement this method")
886
888 """Halt the command, either because the user has decided to cancel
889 the build ('reason' is a string), or because the slave has
890 disconnected ('reason' is a ConnectionLost Failure). Any further
891 local processing should be skipped, and the Step completed with an
892 error status. The results text should say something useful like
893 ['step', 'interrupted'] or ['remote', 'lost']"""
894 self.stopped = True
895 if self._acquiringLock:
896 lock, access, d = self._acquiringLock
897 lock.stopWaitingUntilAvailable(self, access, d)
898 d.callback(None)
899
908
922
931
961
962
963
965 """Return the version number of the given slave command. For the
966 commands defined in buildbot.slave.commands, this is the value of
967 'cvs_ver' at the top of that file. Non-existent commands will return
968 a value of None. Buildslaves running buildbot-0.5.0 or earlier did
969 not respond to the version query: commands on those slaves will
970 return a value of OLDVERSION, so you can distinguish between old
971 buildslaves and missing commands.
972
973 If you know that <=0.5.0 buildslaves have the command you want (CVS
974 and SVN existed back then, but none of the other VC systems), then it
975 makes sense to call this with oldversion='old'. If the command you
976 want is newer than that, just leave oldversion= unspecified, and the
977 command will return None for a buildslave that does not implement the
978 command.
979 """
980 return self.build.getSlaveCommandVersion(command, oldversion)
981
983 sv = self.build.getSlaveCommandVersion(command, None)
984 if sv is None:
985 return True
986
987
988
989
990
991 if map(int, sv.split(".")) < map(int, minversion.split(".")):
992 return True
993 return False
994
997
1002
1008
1017
1022
1024 assert interfaces.ILogObserver.providedBy(observer)
1025 observer.setStep(self)
1026 self._pendingLogObservers.append((logname, observer))
1027 self._connectPendingLogObservers()
1028
1030 if not self._pendingLogObservers:
1031 return
1032 if not self.step_status:
1033 return
1034 current_logs = {}
1035 for loog in self.step_status.getLogs():
1036 current_logs[loog.getName()] = loog
1037 for logname, observer in self._pendingLogObservers[:]:
1038 if logname in current_logs:
1039 observer.setLog(current_logs[logname])
1040 self._pendingLogObservers.remove((logname, observer))
1041
1042 - def addURL(self, name, url):
1043 """Add a BuildStep URL to this step.
1044
1045 An HREF to this URL will be added to any HTML representations of this
1046 step. This allows a step to provide links to external web pages,
1047 perhaps to provide detailed HTML code coverage results or other forms
1048 of build status.
1049 """
1050 self.step_status.addURL(name, url)
1051
1056
1059 length = 0
1060
1063
1064 - def logChunk(self, build, step, log, channel, text):
1067
1069 """This is an abstract base class, suitable for inheritance by all
1070 BuildSteps that invoke RemoteCommands which emit stdout/stderr messages.
1071 """
1072
1073 progressMetrics = ('output',)
1074 logfiles = {}
1075
1076 parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func']
1077 cmd = None
1078
1079 - def __init__(self, logfiles={}, lazylogfiles=False, log_eval_func=None,
1080 *args, **kwargs):
1098
1100 """
1101 This allows to add logfiles after construction, but before calling
1102 startCommand().
1103 """
1104 self.logfiles[logname] = filename
1105
1107 kwargs = dict()
1108 kwargs['logfiles'] = self.logfiles
1109 return kwargs
1110
1140 d.addCallback(_gotResults)
1141 d.addCallbacks(self.finished, self.checkDisconnect)
1142 d.addErrback(self.failed)
1143
1145 """Set up any additional logfiles= logs.
1146
1147 @param cmd: the LoggedRemoteCommand to add additional logs to.
1148
1149 @param logfiles: a dict of tuples (logname,remotefilename)
1150 specifying additional logs to watch. (note:
1151 the remotefilename component is currently
1152 ignored)
1153 """
1154 for logname,remotefilename in logfiles.items():
1155 if self.lazylogfiles:
1156
1157
1158
1159
1160
1161
1162 callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname)
1163 cmd.useLogDelayed(logname, callback, True)
1164 else:
1165
1166 newlog = self.addLog(logname)
1167
1168 cmd.useLog(newlog, True)
1169
1183
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1216 """This is a general-purpose hook method for subclasses. It will be
1217 called after the remote command has finished, but before any of the
1218 other hook functions are called."""
1219 pass
1220
1222 """To create summary logs, do something like this:
1223 warnings = grep('^Warning:', log.getText())
1224 self.addCompleteLog('warnings', warnings)
1225 """
1226 pass
1227
1229 """Decide whether the command was SUCCESS, WARNINGS, or FAILURE.
1230 Override this to, say, declare WARNINGS if there is any stderr
1231 activity, or to say that rc!=0 is not actually an error."""
1232
1233 if self.log_eval_func:
1234 return self.log_eval_func(cmd, self.step_status)
1235 if cmd.rc != 0:
1236 return FAILURE
1237 return SUCCESS
1238
1239 - def getText(self, cmd, results):
1240 if results == SUCCESS:
1241 return self.describe(True)
1242 elif results == WARNINGS:
1243 return self.describe(True) + ["warnings"]
1244 elif results == EXCEPTION:
1245 return self.describe(True) + ["exception"]
1246 else:
1247 return self.describe(True) + ["failed"]
1248
1249 - def getText2(self, cmd, results):
1250 """We have decided to add a short note about ourselves to the overall
1251 build description, probably because something went wrong. Return a
1252 short list of short strings. If your subclass counts test failures or
1253 warnings of some sort, this is a good place to announce the count."""
1254
1255
1256 return [self.name]
1257
1258 - def maybeGetText2(self, cmd, results):
1259 if results == SUCCESS:
1260
1261 pass
1262 elif results == WARNINGS:
1263 if (self.flunkOnWarnings or self.warnOnWarnings):
1264
1265 return self.getText2(cmd, results)
1266 else:
1267 if (self.haltOnFailure or self.flunkOnFailure
1268 or self.warnOnFailure):
1269
1270 return self.getText2(cmd, results)
1271 return []
1272
1278
1288 worst = SUCCESS
1289 if cmd.rc != 0:
1290 worst = FAILURE
1291 for err, possible_status in regexes:
1292
1293
1294
1295 if worst_status(worst, possible_status) == possible_status:
1296 if isinstance(err, (basestring)):
1297 err = re.compile(".*%s.*" % err, re.DOTALL)
1298 for l in cmd.logs.values():
1299 if err.search(l.getText()):
1300 worst = possible_status
1301 return worst
1302
1303
1304
1305 from buildbot.process.properties import WithProperties
1306 _hush_pyflakes = [WithProperties]
1307 del _hush_pyflakes
1308