Package buildbot :: Package status :: Module buildstep
[frames] | no frames]

Source Code for Module buildbot.status.buildstep

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  import os 
 17  from zope.interface import implements 
 18  from twisted.persisted import styles 
 19  from twisted.python import log 
 20  from twisted.internet import reactor, defer 
 21  from buildbot import interfaces, util 
 22  from buildbot.status.logfile import LogFile, HTMLLogFile 
 23   
24 -class BuildStepStatus(styles.Versioned):
25 """ 26 I represent a collection of output status for a 27 L{buildbot.process.step.BuildStep}. 28 29 Statistics contain any information gleaned from a step that is 30 not in the form of a logfile. As an example, steps that run 31 tests might gather statistics about the number of passed, failed, 32 or skipped tests. 33 34 @type progress: L{buildbot.status.progress.StepProgress} 35 @cvar progress: tracks ETA for the step 36 @type text: list of strings 37 @cvar text: list of short texts that describe the command and its status 38 @type text2: list of strings 39 @cvar text2: list of short texts added to the overall build description 40 @type logs: dict of string -> L{buildbot.status.logfile.LogFile} 41 @ivar logs: logs of steps 42 @type statistics: dict 43 @ivar statistics: results from running this step 44 """ 45 # note that these are created when the Build is set up, before each 46 # corresponding BuildStep has started. 47 implements(interfaces.IBuildStepStatus, interfaces.IStatusEvent) 48 49 persistenceVersion = 3 50 persistenceForgets = ( 'wasUpgraded', ) 51 52 started = None 53 finished = None 54 progress = None 55 text = [] 56 results = None 57 text2 = [] 58 watchers = [] 59 updates = {} 60 finishedWatchers = [] 61 statistics = {} 62 step_number = None 63
64 - def __init__(self, parent, step_number):
65 assert interfaces.IBuildStatus(parent) 66 self.build = parent 67 self.step_number = step_number 68 self.logs = [] 69 self.urls = {} 70 self.watchers = [] 71 self.updates = {} 72 self.finishedWatchers = [] 73 self.statistics = {} 74 self.skipped = False 75 76 self.waitingForLocks = False
77
78 - def getName(self):
79 """Returns a short string with the name of this step. This string 80 may have spaces in it.""" 81 return self.name
82
83 - def getBuild(self):
84 return self.build
85
86 - def getTimes(self):
87 return (self.started, self.finished)
88
89 - def getExpectations(self):
90 """Returns a list of tuples (name, current, target).""" 91 if not self.progress: 92 return [] 93 ret = [] 94 metrics = self.progress.progress.keys() 95 metrics.sort() 96 for m in metrics: 97 t = (m, self.progress.progress[m], self.progress.expectations[m]) 98 ret.append(t) 99 return ret
100
101 - def getLogs(self):
102 return self.logs
103
104 - def getURLs(self):
105 return self.urls.copy()
106
107 - def isStarted(self):
108 return (self.started is not None)
109
110 - def isSkipped(self):
111 return self.skipped
112
113 - def isFinished(self):
114 return (self.finished is not None)
115
116 - def waitUntilFinished(self):
117 if self.finished: 118 d = defer.succeed(self) 119 else: 120 d = defer.Deferred() 121 self.finishedWatchers.append(d) 122 return d
123 124 # while the step is running, the following methods make sense. 125 # Afterwards they return None 126
127 - def getETA(self):
128 if self.started is None: 129 return None # not started yet 130 if self.finished is not None: 131 return None # already finished 132 if not self.progress: 133 return None # no way to predict 134 return self.progress.remaining()
135 136 # Once you know the step has finished, the following methods are legal. 137 # Before this step has finished, they all return None. 138
139 - def getText(self):
140 """Returns a list of strings which describe the step. These are 141 intended to be displayed in a narrow column. If more space is 142 available, the caller should join them together with spaces before 143 presenting them to the user.""" 144 return self.text
145
146 - def getResults(self):
147 """Return a tuple describing the results of the step. 148 'result' is one of the constants in L{buildbot.status.builder}: 149 SUCCESS, WARNINGS, FAILURE, or SKIPPED. 150 'strings' is an optional list of strings that the step wants to 151 append to the overall build's results. These strings are usually 152 more terse than the ones returned by getText(): in particular, 153 successful Steps do not usually contribute any text to the 154 overall build. 155 156 @rtype: tuple of int, list of strings 157 @returns: (result, strings) 158 """ 159 return (self.results, self.text2)
160
161 - def hasStatistic(self, name):
162 """Return true if this step has a value for the given statistic. 163 """ 164 return self.statistics.has_key(name)
165
166 - def getStatistic(self, name, default=None):
167 """Return the given statistic, if present 168 """ 169 return self.statistics.get(name, default)
170 171 # subscription interface 172
173 - def subscribe(self, receiver, updateInterval=10):
174 # will get logStarted, logFinished, stepETAUpdate 175 assert receiver not in self.watchers 176 self.watchers.append(receiver) 177 self.sendETAUpdate(receiver, updateInterval)
178
179 - def sendETAUpdate(self, receiver, updateInterval):
180 self.updates[receiver] = None 181 # they might unsubscribe during stepETAUpdate 182 receiver.stepETAUpdate(self.build, self, 183 self.getETA(), self.getExpectations()) 184 if receiver in self.watchers: 185 self.updates[receiver] = reactor.callLater(updateInterval, 186 self.sendETAUpdate, 187 receiver, 188 updateInterval)
189
190 - def unsubscribe(self, receiver):
191 if receiver in self.watchers: 192 self.watchers.remove(receiver) 193 if receiver in self.updates: 194 if self.updates[receiver] is not None: 195 self.updates[receiver].cancel() 196 del self.updates[receiver]
197 198 199 # methods to be invoked by the BuildStep 200
201 - def setName(self, stepname):
202 self.name = stepname
203
204 - def setColor(self, color):
205 log.msg("BuildStepStatus.setColor is no longer supported -- ignoring color %s" % (color,))
206
207 - def setProgress(self, stepprogress):
208 self.progress = stepprogress
209
210 - def stepStarted(self):
211 self.started = util.now() 212 if self.build: 213 self.build.stepStarted(self)
214
215 - def addLog(self, name):
216 assert self.started # addLog before stepStarted won't notify watchers 217 logfilename = self.build.generateLogfileName(self.name, name) 218 log = LogFile(self, name, logfilename) 219 log.logMaxSize = self.build.builder.logMaxSize 220 log.logMaxTailSize = self.build.builder.logMaxTailSize 221 log.compressMethod = self.build.builder.logCompressionMethod 222 self.logs.append(log) 223 for w in self.watchers: 224 receiver = w.logStarted(self.build, self, log) 225 if receiver: 226 log.subscribe(receiver, True) 227 d = log.waitUntilFinished() 228 d.addCallback(lambda log: log.unsubscribe(receiver)) 229 d = log.waitUntilFinished() 230 d.addCallback(self.logFinished) 231 return log
232
233 - def addHTMLLog(self, name, html):
234 assert self.started # addLog before stepStarted won't notify watchers 235 logfilename = self.build.generateLogfileName(self.name, name) 236 log = HTMLLogFile(self, name, logfilename, html) 237 self.logs.append(log) 238 for w in self.watchers: 239 w.logStarted(self.build, self, log) 240 w.logFinished(self.build, self, log)
241
242 - def logFinished(self, log):
243 for w in self.watchers: 244 w.logFinished(self.build, self, log)
245
246 - def addURL(self, name, url):
247 self.urls[name] = url
248
249 - def setText(self, text):
250 self.text = text 251 for w in self.watchers: 252 w.stepTextChanged(self.build, self, text)
253 - def setText2(self, text):
254 self.text2 = text 255 for w in self.watchers: 256 w.stepText2Changed(self.build, self, text)
257
258 - def setStatistic(self, name, value):
259 """Set the given statistic. Usually called by subclasses. 260 """ 261 self.statistics[name] = value
262
263 - def setSkipped(self, skipped):
264 self.skipped = skipped
265
266 - def stepFinished(self, results):
267 self.finished = util.now() 268 self.results = results 269 cld = [] # deferreds for log compression 270 logCompressionLimit = self.build.builder.logCompressionLimit 271 for loog in self.logs: 272 if not loog.isFinished(): 273 loog.finish() 274 # if log compression is on, and it's a real LogFile, 275 # HTMLLogFiles aren't files 276 if logCompressionLimit is not False and \ 277 isinstance(loog, LogFile): 278 if os.path.getsize(loog.getFilename()) > logCompressionLimit: 279 loog_deferred = loog.compressLog() 280 if loog_deferred: 281 cld.append(loog_deferred) 282 283 for r in self.updates.keys(): 284 if self.updates[r] is not None: 285 self.updates[r].cancel() 286 del self.updates[r] 287 288 watchers = self.finishedWatchers 289 self.finishedWatchers = [] 290 for w in watchers: 291 w.callback(self) 292 if cld: 293 return defer.DeferredList(cld)
294
295 - def checkLogfiles(self):
296 # filter out logs that have been deleted 297 self.logs = [ l for l in self.logs if l.hasContents() ]
298
299 - def isWaitingForLocks(self):
300 return self.waitingForLocks
301
302 - def setWaitingForLocks(self, waiting):
303 self.waitingForLocks = waiting
304 305 # persistence 306
307 - def __getstate__(self):
308 d = styles.Versioned.__getstate__(self) 309 del d['build'] # filled in when loading 310 if d.has_key('progress'): 311 del d['progress'] 312 del d['watchers'] 313 del d['finishedWatchers'] 314 del d['updates'] 315 return d
316
317 - def __setstate__(self, d):
318 styles.Versioned.__setstate__(self, d) 319 # self.build must be filled in by our parent 320 321 # point the logs to this object 322 for loog in self.logs: 323 loog.step = self 324 self.watchers = [] 325 self.finishedWatchers = [] 326 self.updates = {}
327
328 - def upgradeToVersion1(self):
329 if not hasattr(self, "urls"): 330 self.urls = {} 331 self.wasUpgraded = True
332
333 - def upgradeToVersion2(self):
334 if not hasattr(self, "statistics"): 335 self.statistics = {} 336 self.wasUpgraded = True
337
338 - def upgradeToVersion3(self):
339 if not hasattr(self, "step_number"): 340 self.step_number = 0 341 self.wasUpgraded = True
342
343 - def asDict(self):
344 result = {} 345 # Constant 346 result['name'] = self.getName() 347 348 # Transient 349 result['text'] = self.getText() 350 result['results'] = self.getResults() 351 result['isStarted'] = self.isStarted() 352 result['isFinished'] = self.isFinished() 353 result['statistics'] = self.statistics 354 result['times'] = self.getTimes() 355 result['expectations'] = self.getExpectations() 356 result['eta'] = self.getETA() 357 result['urls'] = self.getURLs() 358 result['step_number'] = self.step_number 359 result['logs'] = [[l.getName(), 360 self.build.builder.status.getURLForThing(l)] 361 for l in self.getLogs()] 362 return result
363