1
2
3 import re
4
5 from zope.interface import implements
6 from twisted.internet import reactor, defer, error
7 from twisted.protocols import basic
8 from twisted.spread import pb
9 from twisted.python import log
10 from twisted.python.failure import Failure
11 from twisted.web.util import formatFailure
12
13 from buildbot import interfaces, locks
14 from buildbot.status import progress
15 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
16 EXCEPTION, RETRY, worst_status
17
18 """
19 BuildStep and RemoteCommand classes for master-side representation of the
20 build process
21 """
22
24 """
25 I represent a single command to be run on the slave. I handle the details
26 of reliably gathering status updates from the slave (acknowledging each),
27 and (eventually, in a future release) recovering from interrupted builds.
28 This is the master-side object that is known to the slave-side
29 L{buildbot.slave.bot.SlaveBuilder}, to which status updates are sent.
30
31 My command should be started by calling .run(), which returns a
32 Deferred that will fire when the command has finished, or will
33 errback if an exception is raised.
34
35 Typically __init__ or run() will set up self.remote_command to be a
36 string which corresponds to one of the SlaveCommands registered in
37 the buildslave, and self.args to a dictionary of arguments that will
38 be passed to the SlaveCommand instance.
39
40 start, remoteUpdate, and remoteComplete are available to be overridden
41
42 @type commandCounter: list of one int
43 @cvar commandCounter: provides a unique value for each
44 RemoteCommand executed across all slaves
45 @type active: boolean
46 @ivar active: whether the command is currently running
47 """
48 commandCounter = [0]
49 active = False
50
51 - def __init__(self, remote_command, args):
52 """
53 @type remote_command: string
54 @param remote_command: remote command to start. This will be
55 passed to
56 L{buildbot.slave.bot.SlaveBuilder.remote_startCommand}
57 and needs to have been registered
58 slave-side in L{buildbot.slave.registry}
59 @type args: dict
60 @param args: arguments to send to the remote command
61 """
62
63 self.remote_command = remote_command
64 self.args = args
65
66 - def run(self, step, remote):
67 self.active = True
68 self.step = step
69 self.remote = remote
70 c = self.commandCounter[0]
71 self.commandCounter[0] += 1
72
73 self.commandID = "%d" % c
74 log.msg("%s: RemoteCommand.run [%s]" % (self, self.commandID))
75 self.deferred = defer.Deferred()
76
77 d = defer.maybeDeferred(self.start)
78
79
80
81
82
83
84
85 d.addErrback(self._finished)
86
87
88
89 return self.deferred
90
92 """
93 Tell the slave to start executing the remote command.
94
95 @rtype: L{twisted.internet.defer.Deferred}
96 @returns: a deferred that will fire when the remote command is
97 done (with None as the result)
98 """
99
100
101 cmd_args = self.args
102 if cmd_args.has_key("logfiles") and cmd_args["logfiles"]:
103 cmd_args = cmd_args.copy()
104 properties = self.step.build.getProperties()
105 cmd_args["logfiles"] = properties.render(cmd_args["logfiles"])
106
107
108
109
110
111 d = self.remote.callRemote("startCommand", self, self.commandID,
112 self.remote_command, cmd_args)
113 return d
114
116
117
118
119
120 log.msg("RemoteCommand.interrupt", self, why)
121 if not self.active:
122 log.msg(" but this RemoteCommand is already inactive")
123 return
124 if not self.remote:
125 log.msg(" but our .remote went away")
126 return
127 if isinstance(why, Failure) and why.check(error.ConnectionLost):
128 log.msg("RemoteCommand.disconnect: lost slave")
129 self.remote = None
130 self._finished(why)
131 return
132
133
134
135
136 d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand",
137 self.commandID, str(why))
138
139 d.addErrback(self._interruptFailed)
140 return d
141
143 log.msg("RemoteCommand._interruptFailed", self)
144
145
146 return None
147
149 """
150 I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so
151 I can receive updates from the running remote command.
152
153 @type updates: list of [object, int]
154 @param updates: list of updates from the remote command
155 """
156 self.buildslave.messageReceivedFromSlave()
157 max_updatenum = 0
158 for (update, num) in updates:
159
160 try:
161 if self.active:
162 self.remoteUpdate(update)
163 except:
164
165 self._finished(Failure())
166
167
168 if num > max_updatenum:
169 max_updatenum = num
170 return max_updatenum
171
173 raise NotImplementedError("You must implement this in a subclass")
174
176 """
177 Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to
178 notify me the remote command has finished.
179
180 @type failure: L{twisted.python.failure.Failure} or None
181
182 @rtype: None
183 """
184 self.buildslave.messageReceivedFromSlave()
185
186
187 if self.active:
188 reactor.callLater(0, self._finished, failure)
189 return None
190
192 self.active = False
193
194
195
196
197
198 d = defer.maybeDeferred(self.remoteComplete, failure)
199
200
201 d.addCallback(lambda r: self)
202
203
204 d.addBoth(self.deferred.callback)
205
207 """Subclasses can override this.
208
209 This is called when the RemoteCommand has finished. 'maybeFailure'
210 will be None if the command completed normally, or a Failure
211 instance in one of the following situations:
212
213 - the slave was lost before the command was started
214 - the slave didn't respond to the startCommand message
215 - the slave raised an exception while starting the command
216 (bad command name, bad args, OSError from missing executable)
217 - the slave raised an exception while finishing the command
218 (they send back a remote_complete message with a Failure payload)
219
220 and also (for now):
221 - slave disconnected while the command was running
222
223 This method should do cleanup, like closing log files. It should
224 normally return the 'failure' argument, so that any exceptions will
225 be propagated to the Step. If it wants to consume them, return None
226 instead."""
227
228 return maybeFailure
229
231 """
232
233 I am a L{RemoteCommand} which gathers output from the remote command into
234 one or more local log files. My C{self.logs} dictionary contains
235 references to these L{buildbot.status.builder.LogFile} instances. Any
236 stdout/stderr/header updates from the slave will be put into
237 C{self.logs['stdio']}, if it exists. If the remote command uses other log
238 files, they will go into other entries in C{self.logs}.
239
240 If you want to use stdout or stderr, you should create a LogFile named
241 'stdio' and pass it to my useLog() message. Otherwise stdout/stderr will
242 be ignored, which is probably not what you want.
243
244 Unless you tell me otherwise, when my command completes I will close all
245 the LogFiles that I know about.
246
247 @ivar logs: maps logname to a LogFile instance
248 @ivar _closeWhenFinished: maps logname to a boolean. If true, this
249 LogFile will be closed when the RemoteCommand
250 finishes. LogFiles which are shared between
251 multiple RemoteCommands should use False here.
252
253 """
254
255 rc = None
256 debug = False
257
259 self.logs = {}
260 self.delayedLogs = {}
261 self._closeWhenFinished = {}
262 RemoteCommand.__init__(self, *args, **kwargs)
263
265 return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self))
266
267 - def useLog(self, loog, closeWhenFinished=False, logfileName=None):
268 """Start routing messages from a remote logfile to a local LogFile
269
270 I take a local ILogFile instance in 'loog', and arrange to route
271 remote log messages for the logfile named 'logfileName' into it. By
272 default this logfileName comes from the ILogFile itself (using the
273 name by which the ILogFile will be displayed), but the 'logfileName'
274 argument can be used to override this. For example, if
275 logfileName='stdio', this logfile will collect text from the stdout
276 and stderr of the command.
277
278 @param loog: an instance which implements ILogFile
279 @param closeWhenFinished: a boolean, set to False if the logfile
280 will be shared between multiple
281 RemoteCommands. If True, the logfile will
282 be closed when this ShellCommand is done
283 with it.
284 @param logfileName: a string, which indicates which remote log file
285 should be routed into this ILogFile. This should
286 match one of the keys of the logfiles= argument
287 to ShellCommand.
288
289 """
290
291 assert interfaces.ILogFile.providedBy(loog)
292 if not logfileName:
293 logfileName = loog.getName()
294 assert logfileName not in self.logs
295 assert logfileName not in self.delayedLogs
296 self.logs[logfileName] = loog
297 self._closeWhenFinished[logfileName] = closeWhenFinished
298
299 - def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False):
300 assert logfileName not in self.logs
301 assert logfileName not in self.delayedLogs
302 self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished)
303
305 log.msg("LoggedRemoteCommand.start")
306 if 'stdio' not in self.logs:
307 log.msg("LoggedRemoteCommand (%s) is running a command, but "
308 "it isn't being logged to anything. This seems unusual."
309 % self)
310 self.updates = {}
311 return RemoteCommand.start(self)
312
322
324
325 if logname in self.delayedLogs:
326 (activateCallBack, closeWhenFinished) = self.delayedLogs[logname]
327 del self.delayedLogs[logname]
328 loog = activateCallBack(self)
329 self.logs[logname] = loog
330 self._closeWhenFinished[logname] = closeWhenFinished
331
332 if logname in self.logs:
333 self.logs[logname].addStdout(data)
334 else:
335 log.msg("%s.addToLog: no such log %s" % (self, logname))
336
364
366 for name,loog in self.logs.items():
367 if self._closeWhenFinished[name]:
368 if maybeFailure:
369 loog.addHeader("\nremoteFailed: %s" % maybeFailure)
370 else:
371 log.msg("closing log %s" % loog)
372 loog.finish()
373 return maybeFailure
374
375
377 implements(interfaces.ILogObserver)
378
381
385
386 - def logChunk(self, build, step, log, channel, text):
391
392
393
395 """This will be called with chunks of stdout data. Override this in
396 your observer."""
397 pass
398
400 """This will be called with chunks of stderr data. Override this in
401 your observer."""
402 pass
403
404
417
419 """
420 Set the maximum line length: lines longer than max_length are
421 dropped. Default is 16384 bytes. Use sys.maxint for effective
422 infinity.
423 """
424 self.stdoutParser.MAX_LENGTH = max_length
425 self.stderrParser.MAX_LENGTH = max_length
426
428 self.stdoutParser.dataReceived(data)
429
431 self.stderrParser.dataReceived(data)
432
434 """This will be called with complete stdout lines (not including the
435 delimiter). Override this in your observer."""
436 pass
437
439 """This will be called with complete lines of stderr (not including
440 the delimiter). Override this in your observer."""
441 pass
442
443
445 """This class helps you run a shell command on the build slave. It will
446 accumulate all the command's output into a Log named 'stdio'. When the
447 command is finished, it will fire a Deferred. You can then check the
448 results of the command and parse the output however you like."""
449
450 - def __init__(self, workdir, command, env=None,
451 want_stdout=1, want_stderr=1,
452 timeout=20*60, maxTime=None, logfiles={},
453 usePTY="slave-config", logEnviron=True):
454 """
455 @type workdir: string
456 @param workdir: directory where the command ought to run,
457 relative to the Builder's home directory. Defaults to
458 '.': the same as the Builder's homedir. This should
459 probably be '.' for the initial 'cvs checkout'
460 command (which creates a workdir), and the Build-wide
461 workdir for all subsequent commands (including
462 compiles and 'cvs update').
463
464 @type command: list of strings (or string)
465 @param command: the shell command to run, like 'make all' or
466 'cvs update'. This should be a list or tuple
467 which can be used directly as the argv array.
468 For backwards compatibility, if this is a
469 string, the text will be given to '/bin/sh -c
470 %s'.
471
472 @type env: dict of string->string
473 @param env: environment variables to add or change for the
474 slave. Each command gets a separate
475 environment; all inherit the slave's initial
476 one. TODO: make it possible to delete some or
477 all of the slave's environment.
478
479 @type want_stdout: bool
480 @param want_stdout: defaults to True. Set to False if stdout should
481 be thrown away. Do this to avoid storing or
482 sending large amounts of useless data.
483
484 @type want_stderr: bool
485 @param want_stderr: False if stderr should be thrown away
486
487 @type timeout: int
488 @param timeout: tell the remote that if the command fails to
489 produce any output for this number of seconds,
490 the command is hung and should be killed. Use
491 None to disable the timeout.
492
493 @param logEnviron: whether to log env vars on the slave side
494
495 @type maxTime: int
496 @param maxTime: tell the remote that if the command fails to complete
497 in this number of seconds, the command should be
498 killed. Use None to disable maxTime.
499 """
500
501 self.command = command
502 if env is not None:
503
504
505
506 env = env.copy()
507 args = {'workdir': workdir,
508 'env': env,
509 'want_stdout': want_stdout,
510 'want_stderr': want_stderr,
511 'logfiles': logfiles,
512 'timeout': timeout,
513 'maxTime': maxTime,
514 'usePTY': usePTY,
515 'logEnviron': logEnviron,
516 }
517 LoggedRemoteCommand.__init__(self, "shell", args)
518
520 self.args['command'] = self.command
521 if self.remote_command == "shell":
522
523
524 if self.step.slaveVersion("shell", "old") == "old":
525 self.args['dir'] = self.args['workdir']
526 what = "command '%s' in dir '%s'" % (self.args['command'],
527 self.args['workdir'])
528 log.msg(what)
529 return LoggedRemoteCommand.start(self)
530
532 return "<RemoteShellCommand '%s'>" % repr(self.command)
533
535 """
536 I represent a single step of the build process. This step may involve
537 zero or more commands to be run in the build slave, as well as arbitrary
538 processing on the master side. Regardless of how many slave commands are
539 run, the BuildStep will result in a single status value.
540
541 The step is started by calling startStep(), which returns a Deferred that
542 fires when the step finishes. See C{startStep} for a description of the
543 results provided by that Deferred.
544
545 __init__ and start are good methods to override. Don't forget to upcall
546 BuildStep.__init__ or bad things will happen.
547
548 To launch a RemoteCommand, pass it to .runCommand and wait on the
549 Deferred it returns.
550
551 Each BuildStep generates status as it runs. This status data is fed to
552 the L{buildbot.status.builder.BuildStepStatus} listener that sits in
553 C{self.step_status}. It can also feed progress data (like how much text
554 is output by a shell command) to the
555 L{buildbot.status.progress.StepProgress} object that lives in
556 C{self.progress}, by calling C{self.setProgress(metric, value)} as it
557 runs.
558
559 @type build: L{buildbot.process.base.Build}
560 @ivar build: the parent Build which is executing this step
561
562 @type progress: L{buildbot.status.progress.StepProgress}
563 @ivar progress: tracks ETA for the step
564
565 @type step_status: L{buildbot.status.builder.BuildStepStatus}
566 @ivar step_status: collects output status
567 """
568
569
570
571
572
573
574
575
576 haltOnFailure = False
577 flunkOnWarnings = False
578 flunkOnFailure = False
579 warnOnWarnings = False
580 warnOnFailure = False
581 alwaysRun = False
582
583
584
585
586
587
588
589
590 parms = ['name', 'locks',
591 'haltOnFailure',
592 'flunkOnWarnings',
593 'flunkOnFailure',
594 'warnOnWarnings',
595 'warnOnFailure',
596 'alwaysRun',
597 'progressMetrics',
598 'doStepIf',
599 ]
600
601 name = "generic"
602 locks = []
603 progressMetrics = ()
604 useProgress = True
605 build = None
606 step_status = None
607 progress = None
608
609 doStepIf = True
610
612 self.factory = (self.__class__, dict(kwargs))
613 for p in self.__class__.parms:
614 if kwargs.has_key(p):
615 setattr(self, p, kwargs[p])
616 del kwargs[p]
617 if kwargs:
618 why = "%s.__init__ got unexpected keyword argument(s) %s" \
619 % (self, kwargs.keys())
620 raise TypeError(why)
621 self._pendingLogObservers = []
622
623 self._acquiringLock = None
624 self.stopped = False
625
628
636
639
641
642
643
644
645
646 pass
647
650
653
656
664
666 """BuildSteps can call self.setProgress() to announce progress along
667 some metric."""
668 if self.progress:
669 self.progress.setProgress(metric, value)
670
673
674 - def setProperty(self, propname, value, source="Step", runtime=True):
676
678 """Begin the step. This returns a Deferred that will fire when the
679 step finishes.
680
681 This deferred fires with a tuple of (result, [extra text]), although
682 older steps used to return just the 'result' value, so the receiving
683 L{base.Build} needs to be prepared to handle that too. C{result} is
684 one of the SUCCESS/WARNINGS/FAILURE/SKIPPED constants from
685 L{buildbot.status.builder}, and the extra text is a list of short
686 strings which should be appended to the Build's text results. This
687 text allows a test-case step which fails to append B{17 tests} to the
688 Build's status, in addition to marking the build as failing.
689
690 The deferred will errback if the step encounters an exception,
691 including an exception on the slave side (or if the slave goes away
692 altogether). Failures in shell commands (rc!=0) will B{not} cause an
693 errback, in general the BuildStep will evaluate the results and
694 decide whether to treat it as a WARNING or FAILURE.
695
696 @type remote: L{twisted.spread.pb.RemoteReference}
697 @param remote: a reference to the slave's
698 L{buildbot.slave.bot.SlaveBuilder} instance where any
699 RemoteCommands may be run
700 """
701
702 self.remote = remote
703 self.deferred = defer.Deferred()
704
705 lock_list = []
706 for access in self.locks:
707 if not isinstance(access, locks.LockAccess):
708
709 access = access.defaultAccess()
710 lock = self.build.builder.botmaster.getLockByID(access.lockid)
711 lock_list.append((lock, access))
712 self.locks = lock_list
713
714
715 self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks]
716 for l, la in self.locks:
717 if l in self.build.locks:
718 log.msg("Hey, lock %s is claimed by both a Step (%s) and the"
719 " parent Build (%s)" % (l, self, self.build))
720 raise RuntimeError("lock claimed by both Step and Build")
721
722
723
724 self.step_status.setText(self.describe(False))
725 self.step_status.stepStarted()
726
727 d = self.acquireLocks()
728 d.addCallback(self._startStep_2)
729 return self.deferred
730
751
780
782 """Begin the step. Override this method and add code to do local
783 processing, fire off remote commands, etc.
784
785 To spawn a command in the buildslave, create a RemoteCommand instance
786 and run it with self.runCommand::
787
788 c = RemoteCommandFoo(args)
789 d = self.runCommand(c)
790 d.addCallback(self.fooDone).addErrback(self.failed)
791
792 As the step runs, it should send status information to the
793 BuildStepStatus::
794
795 self.step_status.setText(['compile', 'failed'])
796 self.step_status.setText2(['4', 'warnings'])
797
798 To have some code parse stdio (or other log stream) in realtime, add
799 a LogObserver subclass. This observer can use self.step.setProgress()
800 to provide better progress notification to the step.::
801
802 self.addLogObserver('stdio', MyLogObserver())
803
804 To add a LogFile, use self.addLog. Make sure it gets closed when it
805 finishes. When giving a Logfile to a RemoteShellCommand, just ask it
806 to close the log when the command completes::
807
808 log = self.addLog('output')
809 cmd = RemoteShellCommand(args)
810 cmd.useLog(log, closeWhenFinished=True)
811
812 You can also create complete Logfiles with generated text in a single
813 step::
814
815 self.addCompleteLog('warnings', text)
816
817 When the step is done, it should call self.finished(result). 'result'
818 will be provided to the L{buildbot.process.base.Build}, and should be
819 one of the constants defined above: SUCCESS, WARNINGS, FAILURE, or
820 SKIPPED.
821
822 If the step encounters an exception, it should call self.failed(why).
823 'why' should be a Failure object. This automatically fails the whole
824 build with an exception. It is a good idea to add self.failed as an
825 errback to any Deferreds you might obtain.
826
827 If the step decides it does not need to be run, start() can return
828 the constant SKIPPED. This fires the callback immediately: it is not
829 necessary to call .finished yourself. This can also indicate to the
830 status-reporting mechanism that this step should not be displayed.
831
832 A step can be configured to only run under certain conditions. To
833 do this, set the step's doStepIf to a boolean value, or to a function
834 that returns a boolean value. If the value or function result is
835 False, then the step will return SKIPPED without doing anything,
836 otherwise the step will be executed normally. If you set doStepIf
837 to a function, that function should accept one parameter, which will
838 be the Step object itself."""
839
840 raise NotImplementedError("your subclass must implement this method")
841
843 """Halt the command, either because the user has decided to cancel
844 the build ('reason' is a string), or because the slave has
845 disconnected ('reason' is a ConnectionLost Failure). Any further
846 local processing should be skipped, and the Step completed with an
847 error status. The results text should say something useful like
848 ['step', 'interrupted'] or ['remote', 'lost']"""
849 self.stopped = True
850 if self._acquiringLock:
851 lock, access, d = self._acquiringLock
852 lock.stopWaitingUntilAvailable(self, access, d)
853 d.callback(None)
854
863
870
900
901
902
904 """Return the version number of the given slave command. For the
905 commands defined in buildbot.slave.commands, this is the value of
906 'cvs_ver' at the top of that file. Non-existent commands will return
907 a value of None. Buildslaves running buildbot-0.5.0 or earlier did
908 not respond to the version query: commands on those slaves will
909 return a value of OLDVERSION, so you can distinguish between old
910 buildslaves and missing commands.
911
912 If you know that <=0.5.0 buildslaves have the command you want (CVS
913 and SVN existed back then, but none of the other VC systems), then it
914 makes sense to call this with oldversion='old'. If the command you
915 want is newer than that, just leave oldversion= unspecified, and the
916 command will return None for a buildslave that does not implement the
917 command.
918 """
919 return self.build.getSlaveCommandVersion(command, oldversion)
920
922 sv = self.build.getSlaveCommandVersion(command, None)
923 if sv is None:
924 return True
925
926
927
928
929
930 if map(int, sv.split(".")) < map(int, minversion.split(".")):
931 return True
932 return False
933
936
941
947
956
961
963 assert interfaces.ILogObserver.providedBy(observer)
964 observer.setStep(self)
965 self._pendingLogObservers.append((logname, observer))
966 self._connectPendingLogObservers()
967
969 if not self._pendingLogObservers:
970 return
971 if not self.step_status:
972 return
973 current_logs = {}
974 for loog in self.step_status.getLogs():
975 current_logs[loog.getName()] = loog
976 for logname, observer in self._pendingLogObservers[:]:
977 if logname in current_logs:
978 observer.setLog(current_logs[logname])
979 self._pendingLogObservers.remove((logname, observer))
980
982 """Add a BuildStep URL to this step.
983
984 An HREF to this URL will be added to any HTML representations of this
985 step. This allows a step to provide links to external web pages,
986 perhaps to provide detailed HTML code coverage results or other forms
987 of build status.
988 """
989 self.step_status.addURL(name, url)
990
995
996
998 length = 0
999
1002
1003 - def logChunk(self, build, step, log, channel, text):
1006
1008 """This is an abstract base class, suitable for inheritance by all
1009 BuildSteps that invoke RemoteCommands which emit stdout/stderr messages.
1010 """
1011
1012 progressMetrics = ('output',)
1013 logfiles = {}
1014
1015 parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func']
1016 cmd = None
1017
1018 - def __init__(self, logfiles={}, lazylogfiles=False, log_eval_func=None,
1019 *args, **kwargs):
1032
1034 """
1035 This allows to add logfiles after construction, but before calling
1036 startCommand().
1037 """
1038 self.logfiles[logname] = filename
1039
1069 d.addCallback(_gotResults)
1070 d.addCallbacks(self.finished, self.checkDisconnect)
1071 d.addErrback(self.failed)
1072
1074 """Set up any additional logfiles= logs.
1075
1076 @param cmd: the LoggedRemoteCommand to add additional logs to.
1077
1078 @param logfiles: a dict of tuples (logname,remotefilename)
1079 specifying additional logs to watch. (note:
1080 the remotefilename component is currently
1081 ignored)
1082 """
1083 for logname,remotefilename in logfiles.items():
1084 if self.lazylogfiles:
1085
1086
1087
1088
1089
1090
1091 callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname)
1092 cmd.useLogDelayed(logname, callback, True)
1093 else:
1094
1095 newlog = self.addLog(logname)
1096
1097 cmd.useLog(newlog, True)
1098
1112
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1145 """This is a general-purpose hook method for subclasses. It will be
1146 called after the remote command has finished, but before any of the
1147 other hook functions are called."""
1148 pass
1149
1151 """To create summary logs, do something like this:
1152 warnings = grep('^Warning:', log.getText())
1153 self.addCompleteLog('warnings', warnings)
1154 """
1155 pass
1156
1158 """Decide whether the command was SUCCESS, WARNINGS, or FAILURE.
1159 Override this to, say, declare WARNINGS if there is any stderr
1160 activity, or to say that rc!=0 is not actually an error."""
1161
1162 if self.log_eval_func:
1163 return self.log_eval_func(cmd, self.step_status)
1164 if cmd.rc != 0:
1165 return FAILURE
1166 return SUCCESS
1167
1168 - def getText(self, cmd, results):
1169 if results == SUCCESS:
1170 return self.describe(True)
1171 elif results == WARNINGS:
1172 return self.describe(True) + ["warnings"]
1173 else:
1174 return self.describe(True) + ["failed"]
1175
1176 - def getText2(self, cmd, results):
1177 """We have decided to add a short note about ourselves to the overall
1178 build description, probably because something went wrong. Return a
1179 short list of short strings. If your subclass counts test failures or
1180 warnings of some sort, this is a good place to announce the count."""
1181
1182
1183 return [self.name]
1184
1185 - def maybeGetText2(self, cmd, results):
1186 if results == SUCCESS:
1187
1188 pass
1189 elif results == WARNINGS:
1190 if (self.flunkOnWarnings or self.warnOnWarnings):
1191
1192 return self.getText2(cmd, results)
1193 else:
1194 if (self.haltOnFailure or self.flunkOnFailure
1195 or self.warnOnFailure):
1196
1197 return self.getText2(cmd, results)
1198 return []
1199
1205
1206
1207
1208
1209
1210
1211
1212
1213
1215 worst = SUCCESS
1216 if cmd.rc != 0:
1217 worst = FAILURE
1218 for err, possible_status in regexes:
1219
1220
1221
1222 if worst_status(worst, possible_status) == possible_status:
1223 if isinstance(err, (basestring)):
1224 err = re.compile(".*%s.*" % err, re.DOTALL)
1225 for l in cmd.logs.values():
1226 if err.search(l.getText()):
1227 worst = possible_status
1228 return worst
1229
1230
1231
1232 from buildbot.process.properties import WithProperties
1233 _hush_pyflakes = [WithProperties]
1234 del _hush_pyflakes
1235