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
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
629
632
634
635
636
637
638
639 pass
640
643
646
649
657
659 """BuildSteps can call self.setProgress() to announce progress along
660 some metric."""
661 if self.progress:
662 self.progress.setProgress(metric, value)
663
666
667 - def setProperty(self, propname, value, source="Step"):
669
671 """Begin the step. This returns a Deferred that will fire when the
672 step finishes.
673
674 This deferred fires with a tuple of (result, [extra text]), although
675 older steps used to return just the 'result' value, so the receiving
676 L{base.Build} needs to be prepared to handle that too. C{result} is
677 one of the SUCCESS/WARNINGS/FAILURE/SKIPPED constants from
678 L{buildbot.status.builder}, and the extra text is a list of short
679 strings which should be appended to the Build's text results. This
680 text allows a test-case step which fails to append B{17 tests} to the
681 Build's status, in addition to marking the build as failing.
682
683 The deferred will errback if the step encounters an exception,
684 including an exception on the slave side (or if the slave goes away
685 altogether). Failures in shell commands (rc!=0) will B{not} cause an
686 errback, in general the BuildStep will evaluate the results and
687 decide whether to treat it as a WARNING or FAILURE.
688
689 @type remote: L{twisted.spread.pb.RemoteReference}
690 @param remote: a reference to the slave's
691 L{buildbot.slave.bot.SlaveBuilder} instance where any
692 RemoteCommands may be run
693 """
694
695 self.remote = remote
696 self.deferred = defer.Deferred()
697
698 lock_list = []
699 for access in self.locks:
700 if not isinstance(access, locks.LockAccess):
701
702 access = access.defaultAccess()
703 lock = self.build.builder.botmaster.getLockByID(access.lockid)
704 lock_list.append((lock, access))
705 self.locks = lock_list
706
707
708 self.locks = [(l.getLock(self.build.slavebuilder), la) for l, la in self.locks]
709 for l, la in self.locks:
710 if l in self.build.locks:
711 log.msg("Hey, lock %s is claimed by both a Step (%s) and the"
712 " parent Build (%s)" % (l, self, self.build))
713 raise RuntimeError("lock claimed by both Step and Build")
714 d = self.acquireLocks()
715 d.addCallback(self._startStep_2)
716 return self.deferred
717
732
755
757 """Begin the step. Override this method and add code to do local
758 processing, fire off remote commands, etc.
759
760 To spawn a command in the buildslave, create a RemoteCommand instance
761 and run it with self.runCommand::
762
763 c = RemoteCommandFoo(args)
764 d = self.runCommand(c)
765 d.addCallback(self.fooDone).addErrback(self.failed)
766
767 As the step runs, it should send status information to the
768 BuildStepStatus::
769
770 self.step_status.setText(['compile', 'failed'])
771 self.step_status.setText2(['4', 'warnings'])
772
773 To have some code parse stdio (or other log stream) in realtime, add
774 a LogObserver subclass. This observer can use self.step.setProgress()
775 to provide better progress notification to the step.::
776
777 self.addLogObserver('stdio', MyLogObserver())
778
779 To add a LogFile, use self.addLog. Make sure it gets closed when it
780 finishes. When giving a Logfile to a RemoteShellCommand, just ask it
781 to close the log when the command completes::
782
783 log = self.addLog('output')
784 cmd = RemoteShellCommand(args)
785 cmd.useLog(log, closeWhenFinished=True)
786
787 You can also create complete Logfiles with generated text in a single
788 step::
789
790 self.addCompleteLog('warnings', text)
791
792 When the step is done, it should call self.finished(result). 'result'
793 will be provided to the L{buildbot.process.base.Build}, and should be
794 one of the constants defined above: SUCCESS, WARNINGS, FAILURE, or
795 SKIPPED.
796
797 If the step encounters an exception, it should call self.failed(why).
798 'why' should be a Failure object. This automatically fails the whole
799 build with an exception. It is a good idea to add self.failed as an
800 errback to any Deferreds you might obtain.
801
802 If the step decides it does not need to be run, start() can return
803 the constant SKIPPED. This fires the callback immediately: it is not
804 necessary to call .finished yourself. This can also indicate to the
805 status-reporting mechanism that this step should not be displayed.
806
807 A step can be configured to only run under certain conditions. To
808 do this, set the step's doStepIf to a boolean value, or to a function
809 that returns a boolean value. If the value or function result is
810 False, then the step will return SKIPPED without doing anything,
811 otherwise the step will be executed normally. If you set doStepIf
812 to a function, that function should accept one parameter, which will
813 be the Step object itself."""
814
815 raise NotImplementedError("your subclass must implement this method")
816
818 """Halt the command, either because the user has decided to cancel
819 the build ('reason' is a string), or because the slave has
820 disconnected ('reason' is a ConnectionLost Failure). Any further
821 local processing should be skipped, and the Step completed with an
822 error status. The results text should say something useful like
823 ['step', 'interrupted'] or ['remote', 'lost']"""
824 pass
825
830
837
867
868
869
871 """Return the version number of the given slave command. For the
872 commands defined in buildbot.slave.commands, this is the value of
873 'cvs_ver' at the top of that file. Non-existent commands will return
874 a value of None. Buildslaves running buildbot-0.5.0 or earlier did
875 not respond to the version query: commands on those slaves will
876 return a value of OLDVERSION, so you can distinguish between old
877 buildslaves and missing commands.
878
879 If you know that <=0.5.0 buildslaves have the command you want (CVS
880 and SVN existed back then, but none of the other VC systems), then it
881 makes sense to call this with oldversion='old'. If the command you
882 want is newer than that, just leave oldversion= unspecified, and the
883 command will return None for a buildslave that does not implement the
884 command.
885 """
886 return self.build.getSlaveCommandVersion(command, oldversion)
887
889 sv = self.build.getSlaveCommandVersion(command, None)
890 if sv is None:
891 return True
892
893
894
895
896
897 if sv.split(".") < minversion.split("."):
898 return True
899 return False
900
903
908
914
923
928
930 assert interfaces.ILogObserver.providedBy(observer)
931 observer.setStep(self)
932 self._pendingLogObservers.append((logname, observer))
933 self._connectPendingLogObservers()
934
936 if not self._pendingLogObservers:
937 return
938 if not self.step_status:
939 return
940 current_logs = {}
941 for loog in self.step_status.getLogs():
942 current_logs[loog.getName()] = loog
943 for logname, observer in self._pendingLogObservers[:]:
944 if logname in current_logs:
945 observer.setLog(current_logs[logname])
946 self._pendingLogObservers.remove((logname, observer))
947
949 """Add a BuildStep URL to this step.
950
951 An HREF to this URL will be added to any HTML representations of this
952 step. This allows a step to provide links to external web pages,
953 perhaps to provide detailed HTML code coverage results or other forms
954 of build status.
955 """
956 self.step_status.addURL(name, url)
957
962
963
965 length = 0
966
969
970 - def logChunk(self, build, step, log, channel, text):
973
975 """This is an abstract base class, suitable for inheritance by all
976 BuildSteps that invoke RemoteCommands which emit stdout/stderr messages.
977 """
978
979 progressMetrics = ('output',)
980 logfiles = {}
981
982 parms = BuildStep.parms + ['logfiles', 'lazylogfiles']
983
984 - def __init__(self, logfiles={}, lazylogfiles=False, *args, **kwargs):
994
996 raise NotImplementedError("implement this in a subclass")
997
999 """
1000 This allows to add logfiles after construction, but before calling
1001 startCommand().
1002 """
1003 self.logfiles[logname] = filename
1004
1006 """
1007 @param cmd: a suitable RemoteCommand which will be launched, with
1008 all output being put into our self.stdio_log LogFile
1009 """
1010 log.msg("ShellCommand.startCommand(cmd=%s)" % (cmd,))
1011 log.msg(" cmd.args = %r" % (cmd.args))
1012 self.cmd = cmd
1013 self.step_status.setText(self.describe(False))
1014
1015
1016 self.stdio_log = stdio_log = self.addLog("stdio")
1017 cmd.useLog(stdio_log, True)
1018 for em in errorMessages:
1019 stdio_log.addHeader(em)
1020
1021
1022
1023
1024
1025 self.setupLogfiles(cmd, self.logfiles)
1026
1027 d = self.runCommand(cmd)
1028 d.addCallback(lambda res: self.commandComplete(cmd))
1029 d.addCallback(lambda res: self.createSummary(cmd.logs['stdio']))
1030 d.addCallback(lambda res: self.evaluateCommand(cmd))
1031 def _gotResults(results):
1032 self.setStatus(cmd, results)
1033 return results
1034 d.addCallback(_gotResults)
1035 d.addCallbacks(self.finished, self.checkDisconnect)
1036 d.addErrback(self.failed)
1037
1039 """Set up any additional logfiles= logs.
1040 """
1041 for logname,remotefilename in logfiles.items():
1042 if self.lazylogfiles:
1043
1044
1045
1046
1047
1048
1049 callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname)
1050 cmd.useLogDelayed(logname, callback, True)
1051 else:
1052
1053 newlog = self.addLog(logname)
1054
1055 cmd.useLog(newlog, True)
1056
1064
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1097 """This is a general-purpose hook method for subclasses. It will be
1098 called after the remote command has finished, but before any of the
1099 other hook functions are called."""
1100 pass
1101
1103 """To create summary logs, do something like this:
1104 warnings = grep('^Warning:', log.getText())
1105 self.addCompleteLog('warnings', warnings)
1106 """
1107 pass
1108
1110 """Decide whether the command was SUCCESS, WARNINGS, or FAILURE.
1111 Override this to, say, declare WARNINGS if there is any stderr
1112 activity, or to say that rc!=0 is not actually an error."""
1113
1114 if cmd.rc != 0:
1115 return FAILURE
1116
1117 return SUCCESS
1118
1119 - def getText(self, cmd, results):
1120 if results == SUCCESS:
1121 return self.describe(True)
1122 elif results == WARNINGS:
1123 return self.describe(True) + ["warnings"]
1124 else:
1125 return self.describe(True) + ["failed"]
1126
1127 - def getText2(self, cmd, results):
1128 """We have decided to add a short note about ourselves to the overall
1129 build description, probably because something went wrong. Return a
1130 short list of short strings. If your subclass counts test failures or
1131 warnings of some sort, this is a good place to announce the count."""
1132
1133
1134 return [self.name]
1135
1136 - def maybeGetText2(self, cmd, results):
1137 if results == SUCCESS:
1138
1139 pass
1140 elif results == WARNINGS:
1141 if (self.flunkOnWarnings or self.warnOnWarnings):
1142
1143 return self.getText2(cmd, results)
1144 else:
1145 if (self.haltOnFailure or self.flunkOnFailure
1146 or self.warnOnFailure):
1147
1148 return self.getText2(cmd, results)
1149 return []
1150
1156
1157
1158 from buildbot.process.properties import WithProperties
1159 _hush_pyflakes = [WithProperties]
1160 del _hush_pyflakes
1161