1
2
3 from zope.interface import implements
4 from twisted.internet import reactor, defer, error
5 from twisted.protocols import basic
6 from twisted.spread import pb
7 from twisted.python import log
8 from twisted.python.failure import Failure
9 from twisted.web.util import formatFailure
10
11 from buildbot import interfaces, locks
12 from buildbot.status import progress
13 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
14 EXCEPTION, RETRY
15
16 """
17 BuildStep and RemoteCommand classes for master-side representation of the
18 build process
19 """
20
22 """
23 I represent a single command to be run on the slave. I handle the details
24 of reliably gathering status updates from the slave (acknowledging each),
25 and (eventually, in a future release) recovering from interrupted builds.
26 This is the master-side object that is known to the slave-side
27 L{buildbot.slave.bot.SlaveBuilder}, to which status updates are sent.
28
29 My command should be started by calling .run(), which returns a
30 Deferred that will fire when the command has finished, or will
31 errback if an exception is raised.
32
33 Typically __init__ or run() will set up self.remote_command to be a
34 string which corresponds to one of the SlaveCommands registered in
35 the buildslave, and self.args to a dictionary of arguments that will
36 be passed to the SlaveCommand instance.
37
38 start, remoteUpdate, and remoteComplete are available to be overridden
39
40 @type commandCounter: list of one int
41 @cvar commandCounter: provides a unique value for each
42 RemoteCommand executed across all slaves
43 @type active: boolean
44 @ivar active: whether the command is currently running
45 """
46 commandCounter = [0]
47 active = False
48
49 - def __init__(self, remote_command, args):
50 """
51 @type remote_command: string
52 @param remote_command: remote command to start. This will be
53 passed to
54 L{buildbot.slave.bot.SlaveBuilder.remote_startCommand}
55 and needs to have been registered
56 slave-side by
57 L{buildbot.slave.registry.registerSlaveCommand}
58 @type args: dict
59 @param args: arguments to send to the remote command
60 """
61
62 self.remote_command = remote_command
63 self.args = args
64
65 - def run(self, step, remote):
66 self.active = True
67 self.step = step
68 self.remote = remote
69 c = self.commandCounter[0]
70 self.commandCounter[0] += 1
71
72 self.commandID = "%d" % c
73 log.msg("%s: RemoteCommand.run [%s]" % (self, self.commandID))
74 self.deferred = defer.Deferred()
75
76 d = defer.maybeDeferred(self.start)
77
78
79
80
81
82
83
84 d.addErrback(self._finished)
85
86
87
88 return self.deferred
89
91 """
92 Tell the slave to start executing the remote command.
93
94 @rtype: L{twisted.internet.defer.Deferred}
95 @returns: a deferred that will fire when the remote command is
96 done (with None as the result)
97 """
98
99
100 cmd_args = self.args
101 if cmd_args.has_key("logfiles") and cmd_args["logfiles"]:
102 cmd_args = cmd_args.copy()
103 properties = self.step.build.getProperties()
104 cmd_args["logfiles"] = properties.render(cmd_args["logfiles"])
105
106
107
108
109
110 d = self.remote.callRemote("startCommand", self, self.commandID,
111 self.remote_command, cmd_args)
112 return d
113
115
116
117
118
119 log.msg("RemoteCommand.interrupt", self, why)
120 if not self.active:
121 log.msg(" but this RemoteCommand is already inactive")
122 return
123 if not self.remote:
124 log.msg(" but our .remote went away")
125 return
126 if isinstance(why, Failure) and why.check(error.ConnectionLost):
127 log.msg("RemoteCommand.disconnect: lost slave")
128 self.remote = None
129 self._finished(why)
130 return
131
132
133
134
135 d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand",
136 self.commandID, str(why))
137
138 d.addErrback(self._interruptFailed)
139 return d
140
142 log.msg("RemoteCommand._interruptFailed", self)
143
144
145 return None
146
148 """
149 I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so
150 I can receive updates from the running remote command.
151
152 @type updates: list of [object, int]
153 @param updates: list of updates from the remote command
154 """
155 self.buildslave.messageReceivedFromSlave()
156 max_updatenum = 0
157 for (update, num) in updates:
158
159 try:
160 if self.active:
161 self.remoteUpdate(update)
162 except:
163
164 self._finished(Failure())
165
166
167 if num > max_updatenum:
168 max_updatenum = num
169 return max_updatenum
170
172 raise NotImplementedError("You must implement this in a subclass")
173
175 """
176 Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to
177 notify me the remote command has finished.
178
179 @type failure: L{twisted.python.failure.Failure} or None
180
181 @rtype: None
182 """
183 self.buildslave.messageReceivedFromSlave()
184
185
186 if self.active:
187 reactor.callLater(0, self._finished, failure)
188 return None
189
191 self.active = False
192
193
194
195
196
197 d = defer.maybeDeferred(self.remoteComplete, failure)
198
199
200 d.addCallback(lambda r: self)
201
202
203 d.addBoth(self.deferred.callback)
204
206 """Subclasses can override this.
207
208 This is called when the RemoteCommand has finished. 'maybeFailure'
209 will be None if the command completed normally, or a Failure
210 instance in one of the following situations:
211
212 - the slave was lost before the command was started
213 - the slave didn't respond to the startCommand message
214 - the slave raised an exception while starting the command
215 (bad command name, bad args, OSError from missing executable)
216 - the slave raised an exception while finishing the command
217 (they send back a remote_complete message with a Failure payload)
218
219 and also (for now):
220 - slave disconnected while the command was running
221
222 This method should do cleanup, like closing log files. It should
223 normally return the 'failure' argument, so that any exceptions will
224 be propagated to the Step. If it wants to consume them, return None
225 instead."""
226
227 return maybeFailure
228
230 """
231
232 I am a L{RemoteCommand} which gathers output from the remote command into
233 one or more local log files. My C{self.logs} dictionary contains
234 references to these L{buildbot.status.builder.LogFile} instances. Any
235 stdout/stderr/header updates from the slave will be put into
236 C{self.logs['stdio']}, if it exists. If the remote command uses other log
237 files, they will go into other entries in C{self.logs}.
238
239 If you want to use stdout or stderr, you should create a LogFile named
240 'stdio' and pass it to my useLog() message. Otherwise stdout/stderr will
241 be ignored, which is probably not what you want.
242
243 Unless you tell me otherwise, when my command completes I will close all
244 the LogFiles that I know about.
245
246 @ivar logs: maps logname to a LogFile instance
247 @ivar _closeWhenFinished: maps logname to a boolean. If true, this
248 LogFile will be closed when the RemoteCommand
249 finishes. LogFiles which are shared between
250 multiple RemoteCommands should use False here.
251
252 """
253
254 rc = None
255 debug = False
256
258 self.logs = {}
259 self.delayedLogs = {}
260 self._closeWhenFinished = {}
261 RemoteCommand.__init__(self, *args, **kwargs)
262
264 return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self))
265
266 - def useLog(self, loog, closeWhenFinished=False, logfileName=None):
267 """Start routing messages from a remote logfile to a local LogFile
268
269 I take a local ILogFile instance in 'loog', and arrange to route
270 remote log messages for the logfile named 'logfileName' into it. By
271 default this logfileName comes from the ILogFile itself (using the
272 name by which the ILogFile will be displayed), but the 'logfileName'
273 argument can be used to override this. For example, if
274 logfileName='stdio', this logfile will collect text from the stdout
275 and stderr of the command.
276
277 @param loog: an instance which implements ILogFile
278 @param closeWhenFinished: a boolean, set to False if the logfile
279 will be shared between multiple
280 RemoteCommands. If True, the logfile will
281 be closed when this ShellCommand is done
282 with it.
283 @param logfileName: a string, which indicates which remote log file
284 should be routed into this ILogFile. This should
285 match one of the keys of the logfiles= argument
286 to ShellCommand.
287
288 """
289
290 assert interfaces.ILogFile.providedBy(loog)
291 if not logfileName:
292 logfileName = loog.getName()
293 assert logfileName not in self.logs
294 assert logfileName not in self.delayedLogs
295 self.logs[logfileName] = loog
296 self._closeWhenFinished[logfileName] = closeWhenFinished
297
298 - def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False):
299 assert logfileName not in self.logs
300 assert logfileName not in self.delayedLogs
301 self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished)
302
304 log.msg("LoggedRemoteCommand.start")
305 if 'stdio' not in self.logs:
306 log.msg("LoggedRemoteCommand (%s) is running a command, but "
307 "it isn't being logged to anything. This seems unusual."
308 % self)
309 self.updates = {}
310 return RemoteCommand.start(self)
311
321
323
324 if logname in self.delayedLogs:
325 (activateCallBack, closeWhenFinished) = self.delayedLogs[logname]
326 del self.delayedLogs[logname]
327 loog = activateCallBack(self)
328 self.logs[logname] = loog
329 self._closeWhenFinished[logname] = closeWhenFinished
330
331 if logname in self.logs:
332 self.logs[logname].addStdout(data)
333 else:
334 log.msg("%s.addToLog: no such log %s" % (self, logname))
335
363
365 for name,loog in self.logs.items():
366 if self._closeWhenFinished[name]:
367 if maybeFailure:
368 loog.addHeader("\nremoteFailed: %s" % maybeFailure)
369 else:
370 log.msg("closing log %s" % loog)
371 loog.finish()
372 return maybeFailure
373
374
376 implements(interfaces.ILogObserver)
377
380
384
385 - def logChunk(self, build, step, log, channel, text):
390
391
392
394 """This will be called with chunks of stdout data. Override this in
395 your observer."""
396 pass
397
399 """This will be called with chunks of stderr data. Override this in
400 your observer."""
401 pass
402
403
416
418 """
419 Set the maximum line length: lines longer than max_length are
420 dropped. Default is 16384 bytes. Use sys.maxint for effective
421 infinity.
422 """
423 self.stdoutParser.MAX_LENGTH = max_length
424 self.stderrParser.MAX_LENGTH = max_length
425
427 self.stdoutParser.dataReceived(data)
428
430 self.stderrParser.dataReceived(data)
431
433 """This will be called with complete stdout lines (not including the
434 delimiter). Override this in your observer."""
435 pass
436
438 """This will be called with complete lines of stderr (not including
439 the delimiter). Override this in your observer."""
440 pass
441
442
444 """This class helps you run a shell command on the build slave. It will
445 accumulate all the command's output into a Log named 'stdio'. When the
446 command is finished, it will fire a Deferred. You can then check the
447 results of the command and parse the output however you like."""
448
449 - def __init__(self, workdir, command, env=None,
450 want_stdout=1, want_stderr=1,
451 timeout=20*60, maxTime=None, logfiles={},
452 usePTY="slave-config", logEnviron=True):
453 """
454 @type workdir: string
455 @param workdir: directory where the command ought to run,
456 relative to the Builder's home directory. Defaults to
457 '.': the same as the Builder's homedir. This should
458 probably be '.' for the initial 'cvs checkout'
459 command (which creates a workdir), and the Build-wide
460 workdir for all subsequent commands (including
461 compiles and 'cvs update').
462
463 @type command: list of strings (or string)
464 @param command: the shell command to run, like 'make all' or
465 'cvs update'. This should be a list or tuple
466 which can be used directly as the argv array.
467 For backwards compatibility, if this is a
468 string, the text will be given to '/bin/sh -c
469 %s'.
470
471 @type env: dict of string->string
472 @param env: environment variables to add or change for the
473 slave. Each command gets a separate
474 environment; all inherit the slave's initial
475 one. TODO: make it possible to delete some or
476 all of the slave's environment.
477
478 @type want_stdout: bool
479 @param want_stdout: defaults to True. Set to False if stdout should
480 be thrown away. Do this to avoid storing or
481 sending large amounts of useless data.
482
483 @type want_stderr: bool
484 @param want_stderr: False if stderr should be thrown away
485
486 @type timeout: int
487 @param timeout: tell the remote that if the command fails to
488 produce any output for this number of seconds,
489 the command is hung and should be killed. Use
490 None to disable the timeout.
491
492 @param logEnviron: whether to log env vars on the slave side
493
494 @type maxTime: int
495 @param maxTime: tell the remote that if the command fails to complete
496 in this number of seconds, the command should be
497 killed. Use None to disable maxTime.
498 """
499
500 self.command = command
501 if env is not None:
502
503
504
505 env = env.copy()
506 args = {'workdir': workdir,
507 'env': env,
508 'want_stdout': want_stdout,
509 'want_stderr': want_stderr,
510 'logfiles': logfiles,
511 'timeout': timeout,
512 'maxTime': maxTime,
513 'usePTY': usePTY,
514 'logEnviron': logEnviron,
515 }
516 LoggedRemoteCommand.__init__(self, "shell", args)
517
519 self.args['command'] = self.command
520 if self.remote_command == "shell":
521
522
523 if self.step.slaveVersion("shell", "old") == "old":
524 self.args['dir'] = self.args['workdir']
525 what = "command '%s' in dir '%s'" % (self.args['command'],
526 self.args['workdir'])
527 log.msg(what)
528 return LoggedRemoteCommand.start(self)
529
531 return "<RemoteShellCommand '%s'>" % repr(self.command)
532
534 """
535 I represent a single step of the build process. This step may involve
536 zero or more commands to be run in the build slave, as well as arbitrary
537 processing on the master side. Regardless of how many slave commands are
538 run, the BuildStep will result in a single status value.
539
540 The step is started by calling startStep(), which returns a Deferred that
541 fires when the step finishes. See C{startStep} for a description of the
542 results provided by that Deferred.
543
544 __init__ and start are good methods to override. Don't forget to upcall
545 BuildStep.__init__ or bad things will happen.
546
547 To launch a RemoteCommand, pass it to .runCommand and wait on the
548 Deferred it returns.
549
550 Each BuildStep generates status as it runs. This status data is fed to
551 the L{buildbot.status.builder.BuildStepStatus} listener that sits in
552 C{self.step_status}. It can also feed progress data (like how much text
553 is output by a shell command) to the
554 L{buildbot.status.progress.StepProgress} object that lives in
555 C{self.progress}, by calling C{self.setProgress(metric, value)} as it
556 runs.
557
558 @type build: L{buildbot.process.base.Build}
559 @ivar build: the parent Build which is executing this step
560
561 @type progress: L{buildbot.status.progress.StepProgress}
562 @ivar progress: tracks ETA for the step
563
564 @type step_status: L{buildbot.status.builder.BuildStepStatus}
565 @ivar step_status: collects output status
566 """
567
568
569
570
571
572
573
574
575 haltOnFailure = False
576 flunkOnWarnings = False
577 flunkOnFailure = False
578 warnOnWarnings = False
579 warnOnFailure = False
580 alwaysRun = False
581
582
583
584
585
586
587
588
589 parms = ['name', 'locks',
590 'haltOnFailure',
591 'flunkOnWarnings',
592 'flunkOnFailure',
593 'warnOnWarnings',
594 'warnOnFailure',
595 'alwaysRun',
596 'progressMetrics',
597 'doStepIf',
598 ]
599
600 name = "generic"
601 locks = []
602 progressMetrics = ()
603 useProgress = True
604 build = None
605 step_status = None
606 progress = None
607
608 doStepIf = True
609
611 self.factory = (self.__class__, dict(kwargs))
612 for p in self.__class__.parms:
613 if kwargs.has_key(p):
614 setattr(self, p, kwargs[p])
615 del kwargs[p]
616 if kwargs:
617 why = "%s.__init__ got unexpected keyword argument(s) %s" \
618 % (self, kwargs.keys())
619 raise TypeError(why)
620 self._pendingLogObservers = []
621
624
632
635
637
638
639
640
641
642 pass
643
646
649
652
660
662 """BuildSteps can call self.setProgress() to announce progress along
663 some metric."""
664 if self.progress:
665 self.progress.setProgress(metric, value)
666
669
670 - def setProperty(self, propname, value, source="Step"):
672
674 """Begin the step. This returns a Deferred that will fire when the
675 step finishes.
676
677 This deferred fires with a tuple of (result, [extra text]), although
678 older steps used to return just the 'result' value, so the receiving
679 L{base.Build} needs to be prepared to handle that too. C{result} is
680 one of the SUCCESS/WARNINGS/FAILURE/SKIPPED constants from
681 L{buildbot.status.builder}, and the extra text is a list of short
682 strings which should be appended to the Build's text results. This
683 text allows a test-case step which fails to append B{17 tests} to the
684 Build's status, in addition to marking the build as failing.
685
686 The deferred will errback if the step encounters an exception,
687 including an exception on the slave side (or if the slave goes away
688 altogether). Failures in shell commands (rc!=0) will B{not} cause an
689 errback, in general the BuildStep will evaluate the results and
690 decide whether to treat it as a WARNING or FAILURE.
691
692 @type remote: L{twisted.spread.pb.RemoteReference}
693 @param remote: a reference to the slave's
694 L{buildbot.slave.bot.SlaveBuilder} instance where any
695 RemoteCommands may be run
696 """
697
698 self.remote = remote
699 self.deferred = defer.Deferred()
700
701 lock_list = []
702 for access in self.locks:
703 if not isinstance(access, locks.LockAccess):
704
705 access = access.defaultAccess()
706 lock = self.build.builder.botmaster.getLockByID(access.lockid)
707 lock_list.append((lock, access))
708 self.locks = lock_list
709
710
711 self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks]
712 for l, la in self.locks:
713 if l in self.build.locks:
714 log.msg("Hey, lock %s is claimed by both a Step (%s) and the"
715 " parent Build (%s)" % (l, self, self.build))
716 raise RuntimeError("lock claimed by both Step and Build")
717 d = self.acquireLocks()
718 d.addCallback(self._startStep_2)
719 return self.deferred
720
735
762
764 """Begin the step. Override this method and add code to do local
765 processing, fire off remote commands, etc.
766
767 To spawn a command in the buildslave, create a RemoteCommand instance
768 and run it with self.runCommand::
769
770 c = RemoteCommandFoo(args)
771 d = self.runCommand(c)
772 d.addCallback(self.fooDone).addErrback(self.failed)
773
774 As the step runs, it should send status information to the
775 BuildStepStatus::
776
777 self.step_status.setText(['compile', 'failed'])
778 self.step_status.setText2(['4', 'warnings'])
779
780 To have some code parse stdio (or other log stream) in realtime, add
781 a LogObserver subclass. This observer can use self.step.setProgress()
782 to provide better progress notification to the step.::
783
784 self.addLogObserver('stdio', MyLogObserver())
785
786 To add a LogFile, use self.addLog. Make sure it gets closed when it
787 finishes. When giving a Logfile to a RemoteShellCommand, just ask it
788 to close the log when the command completes::
789
790 log = self.addLog('output')
791 cmd = RemoteShellCommand(args)
792 cmd.useLog(log, closeWhenFinished=True)
793
794 You can also create complete Logfiles with generated text in a single
795 step::
796
797 self.addCompleteLog('warnings', text)
798
799 When the step is done, it should call self.finished(result). 'result'
800 will be provided to the L{buildbot.process.base.Build}, and should be
801 one of the constants defined above: SUCCESS, WARNINGS, FAILURE, or
802 SKIPPED.
803
804 If the step encounters an exception, it should call self.failed(why).
805 'why' should be a Failure object. This automatically fails the whole
806 build with an exception. It is a good idea to add self.failed as an
807 errback to any Deferreds you might obtain.
808
809 If the step decides it does not need to be run, start() can return
810 the constant SKIPPED. This fires the callback immediately: it is not
811 necessary to call .finished yourself. This can also indicate to the
812 status-reporting mechanism that this step should not be displayed.
813
814 A step can be configured to only run under certain conditions. To
815 do this, set the step's doStepIf to a boolean value, or to a function
816 that returns a boolean value. If the value or function result is
817 False, then the step will return SKIPPED without doing anything,
818 otherwise the step will be executed normally. If you set doStepIf
819 to a function, that function should accept one parameter, which will
820 be the Step object itself."""
821
822 raise NotImplementedError("your subclass must implement this method")
823
825 """Halt the command, either because the user has decided to cancel
826 the build ('reason' is a string), or because the slave has
827 disconnected ('reason' is a ConnectionLost Failure). Any further
828 local processing should be skipped, and the Step completed with an
829 error status. The results text should say something useful like
830 ['step', 'interrupted'] or ['remote', 'lost']"""
831 pass
832
837
844
874
875
876
878 """Return the version number of the given slave command. For the
879 commands defined in buildbot.slave.commands, this is the value of
880 'cvs_ver' at the top of that file. Non-existent commands will return
881 a value of None. Buildslaves running buildbot-0.5.0 or earlier did
882 not respond to the version query: commands on those slaves will
883 return a value of OLDVERSION, so you can distinguish between old
884 buildslaves and missing commands.
885
886 If you know that <=0.5.0 buildslaves have the command you want (CVS
887 and SVN existed back then, but none of the other VC systems), then it
888 makes sense to call this with oldversion='old'. If the command you
889 want is newer than that, just leave oldversion= unspecified, and the
890 command will return None for a buildslave that does not implement the
891 command.
892 """
893 return self.build.getSlaveCommandVersion(command, oldversion)
894
896 sv = self.build.getSlaveCommandVersion(command, None)
897 if sv is None:
898 return True
899
900
901
902
903
904 if sv.split(".") < minversion.split("."):
905 return True
906 return False
907
910
915
921
930
935
937 assert interfaces.ILogObserver.providedBy(observer)
938 observer.setStep(self)
939 self._pendingLogObservers.append((logname, observer))
940 self._connectPendingLogObservers()
941
943 if not self._pendingLogObservers:
944 return
945 if not self.step_status:
946 return
947 current_logs = {}
948 for loog in self.step_status.getLogs():
949 current_logs[loog.getName()] = loog
950 for logname, observer in self._pendingLogObservers[:]:
951 if logname in current_logs:
952 observer.setLog(current_logs[logname])
953 self._pendingLogObservers.remove((logname, observer))
954
956 """Add a BuildStep URL to this step.
957
958 An HREF to this URL will be added to any HTML representations of this
959 step. This allows a step to provide links to external web pages,
960 perhaps to provide detailed HTML code coverage results or other forms
961 of build status.
962 """
963 self.step_status.addURL(name, url)
964
969
970
972 length = 0
973
976
977 - def logChunk(self, build, step, log, channel, text):
980
982 """This is an abstract base class, suitable for inheritance by all
983 BuildSteps that invoke RemoteCommands which emit stdout/stderr messages.
984 """
985
986 progressMetrics = ('output',)
987 logfiles = {}
988
989 parms = BuildStep.parms + ['logfiles', 'lazylogfiles']
990
991 - def __init__(self, logfiles={}, lazylogfiles=False, *args, **kwargs):
1001
1003 """
1004 This allows to add logfiles after construction, but before calling
1005 startCommand().
1006 """
1007 self.logfiles[logname] = filename
1008
1010 """
1011 @param cmd: a suitable RemoteCommand which will be launched, with
1012 all output being put into our self.stdio_log LogFile
1013 """
1014 log.msg("ShellCommand.startCommand(cmd=%s)" % (cmd,))
1015 log.msg(" cmd.args = %r" % (cmd.args))
1016 self.cmd = cmd
1017 self.step_status.setText(self.describe(False))
1018
1019
1020 self.stdio_log = stdio_log = self.addLog("stdio")
1021 cmd.useLog(stdio_log, True)
1022 for em in errorMessages:
1023 stdio_log.addHeader(em)
1024
1025
1026
1027
1028
1029 self.setupLogfiles(cmd, self.logfiles)
1030
1031 d = self.runCommand(cmd)
1032 d.addCallback(lambda res: self.commandComplete(cmd))
1033 d.addCallback(lambda res: self.createSummary(cmd.logs['stdio']))
1034 d.addCallback(lambda res: self.evaluateCommand(cmd))
1035 def _gotResults(results):
1036 self.setStatus(cmd, results)
1037 return results
1038 d.addCallback(_gotResults)
1039 d.addCallbacks(self.finished, self.checkDisconnect)
1040 d.addErrback(self.failed)
1041
1043 """Set up any additional logfiles= logs.
1044 """
1045 for logname,remotefilename in logfiles.items():
1046 if self.lazylogfiles:
1047
1048
1049
1050
1051
1052
1053 callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname)
1054 cmd.useLogDelayed(logname, callback, True)
1055 else:
1056
1057 newlog = self.addLog(logname)
1058
1059 cmd.useLog(newlog, True)
1060
1068
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1101 """This is a general-purpose hook method for subclasses. It will be
1102 called after the remote command has finished, but before any of the
1103 other hook functions are called."""
1104 pass
1105
1107 """To create summary logs, do something like this:
1108 warnings = grep('^Warning:', log.getText())
1109 self.addCompleteLog('warnings', warnings)
1110 """
1111 pass
1112
1114 """Decide whether the command was SUCCESS, WARNINGS, or FAILURE.
1115 Override this to, say, declare WARNINGS if there is any stderr
1116 activity, or to say that rc!=0 is not actually an error."""
1117
1118 if cmd.rc != 0:
1119 return FAILURE
1120
1121 return SUCCESS
1122
1123 - def getText(self, cmd, results):
1124 if results == SUCCESS:
1125 return self.describe(True)
1126 elif results == WARNINGS:
1127 return self.describe(True) + ["warnings"]
1128 else:
1129 return self.describe(True) + ["failed"]
1130
1131 - def getText2(self, cmd, results):
1132 """We have decided to add a short note about ourselves to the overall
1133 build description, probably because something went wrong. Return a
1134 short list of short strings. If your subclass counts test failures or
1135 warnings of some sort, this is a good place to announce the count."""
1136
1137
1138 return [self.name]
1139
1140 - def maybeGetText2(self, cmd, results):
1141 if results == SUCCESS:
1142
1143 pass
1144 elif results == WARNINGS:
1145 if (self.flunkOnWarnings or self.warnOnWarnings):
1146
1147 return self.getText2(cmd, results)
1148 else:
1149 if (self.haltOnFailure or self.flunkOnFailure
1150 or self.warnOnFailure):
1151
1152 return self.getText2(cmd, results)
1153 return []
1154
1160
1161
1162 from buildbot.process.properties import WithProperties
1163 _hush_pyflakes = [WithProperties]
1164 del _hush_pyflakes
1165