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 = 4 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 hidden = False 64
65 - def __init__(self, parent, master, step_number):
66 assert interfaces.IBuildStatus(parent) 67 self.build = parent 68 self.step_number = step_number 69 self.hidden = False 70 self.logs = [] 71 self.urls = {} 72 self.watchers = [] 73 self.updates = {} 74 self.finishedWatchers = [] 75 self.statistics = {} 76 self.skipped = False 77 78 self.master = master 79 80 self.waitingForLocks = False
81
82 - def getName(self):
83 """Returns a short string with the name of this step. This string 84 may have spaces in it.""" 85 return self.name
86
87 - def getBuild(self):
88 return self.build
89
90 - def getTimes(self):
91 return (self.started, self.finished)
92
93 - def getExpectations(self):
94 """Returns a list of tuples (name, current, target).""" 95 if not self.progress: 96 return [] 97 ret = [] 98 metrics = self.progress.progress.keys() 99 metrics.sort() 100 for m in metrics: 101 t = (m, self.progress.progress[m], self.progress.expectations[m]) 102 ret.append(t) 103 return ret
104
105 - def getLogs(self):
106 return self.logs
107
108 - def getURLs(self):
109 return self.urls.copy()
110
111 - def isStarted(self):
112 return (self.started is not None)
113
114 - def isSkipped(self):
115 return self.skipped
116
117 - def isFinished(self):
118 return (self.finished is not None)
119
120 - def isHidden(self):
121 return self.hidden
122
123 - def waitUntilFinished(self):
124 if self.finished: 125 d = defer.succeed(self) 126 else: 127 d = defer.Deferred() 128 self.finishedWatchers.append(d) 129 return d
130 131 # while the step is running, the following methods make sense. 132 # Afterwards they return None 133
134 - def getETA(self):
135 if self.started is None: 136 return None # not started yet 137 if self.finished is not None: 138 return None # already finished 139 if not self.progress: 140 return None # no way to predict 141 return self.progress.remaining()
142 143 # Once you know the step has finished, the following methods are legal. 144 # Before this step has finished, they all return None. 145
146 - def getText(self):
147 """Returns a list of strings which describe the step. These are 148 intended to be displayed in a narrow column. If more space is 149 available, the caller should join them together with spaces before 150 presenting them to the user.""" 151 return self.text
152
153 - def getResults(self):
154 """Return a tuple describing the results of the step. 155 'result' is one of the constants in L{buildbot.status.builder}: 156 SUCCESS, WARNINGS, FAILURE, or SKIPPED. 157 'strings' is an optional list of strings that the step wants to 158 append to the overall build's results. These strings are usually 159 more terse than the ones returned by getText(): in particular, 160 successful Steps do not usually contribute any text to the 161 overall build. 162 163 @rtype: tuple of int, list of strings 164 @returns: (result, strings) 165 """ 166 return (self.results, self.text2)
167
168 - def hasStatistic(self, name):
169 """Return true if this step has a value for the given statistic. 170 """ 171 return self.statistics.has_key(name)
172
173 - def getStatistic(self, name, default=None):
174 """Return the given statistic, if present 175 """ 176 return self.statistics.get(name, default)
177
178 - def getStatistics(self):
179 return self.statistics.copy()
180 181 # subscription interface 182
183 - def subscribe(self, receiver, updateInterval=10):
184 # will get logStarted, logFinished, stepETAUpdate 185 assert receiver not in self.watchers 186 self.watchers.append(receiver) 187 self.sendETAUpdate(receiver, updateInterval)
188
189 - def sendETAUpdate(self, receiver, updateInterval):
190 self.updates[receiver] = None 191 # they might unsubscribe during stepETAUpdate 192 receiver.stepETAUpdate(self.build, self, 193 self.getETA(), self.getExpectations()) 194 if receiver in self.watchers: 195 self.updates[receiver] = reactor.callLater(updateInterval, 196 self.sendETAUpdate, 197 receiver, 198 updateInterval)
199
200 - def unsubscribe(self, receiver):
201 if receiver in self.watchers: 202 self.watchers.remove(receiver) 203 if receiver in self.updates: 204 if self.updates[receiver] is not None: 205 self.updates[receiver].cancel() 206 del self.updates[receiver]
207 208 209 # methods to be invoked by the BuildStep 210
211 - def setName(self, stepname):
212 self.name = stepname
213
214 - def setColor(self, color):
215 log.msg("BuildStepStatus.setColor is no longer supported -- ignoring color %s" % (color,))
216
217 - def setProgress(self, stepprogress):
218 self.progress = stepprogress
219
220 - def setHidden(self, hidden):
221 self.hidden = hidden
222
223 - def stepStarted(self):
224 self.started = util.now() 225 if self.build: 226 self.build.stepStarted(self)
227
228 - def addLog(self, name):
229 assert self.started # addLog before stepStarted won't notify watchers 230 logfilename = self.build.generateLogfileName(self.name, name) 231 log = LogFile(self, name, logfilename) 232 self.logs.append(log) 233 for w in self.watchers: 234 receiver = w.logStarted(self.build, self, log) 235 if receiver: 236 log.subscribe(receiver, True) 237 d = log.waitUntilFinished() 238 d.addCallback(lambda log: log.unsubscribe(receiver)) 239 d = log.waitUntilFinished() 240 d.addCallback(self.logFinished) 241 return log
242
243 - def addHTMLLog(self, name, html):
244 assert self.started # addLog before stepStarted won't notify watchers 245 logfilename = self.build.generateLogfileName(self.name, name) 246 log = HTMLLogFile(self, name, logfilename, html) 247 self.logs.append(log) 248 for w in self.watchers: 249 w.logStarted(self.build, self, log) 250 w.logFinished(self.build, self, log)
251
252 - def logFinished(self, log):
253 for w in self.watchers: 254 w.logFinished(self.build, self, log)
255
256 - def addURL(self, name, url):
257 self.urls[name] = url
258
259 - def setText(self, text):
260 self.text = text 261 for w in self.watchers: 262 w.stepTextChanged(self.build, self, text)
263 - def setText2(self, text):
264 self.text2 = text 265 for w in self.watchers: 266 w.stepText2Changed(self.build, self, text)
267
268 - def setStatistic(self, name, value):
269 """Set the given statistic. Usually called by subclasses. 270 """ 271 self.statistics[name] = value
272
273 - def setSkipped(self, skipped):
274 self.skipped = skipped
275
276 - def stepFinished(self, results):
277 self.finished = util.now() 278 self.results = results 279 cld = [] # deferreds for log compression 280 logCompressionLimit = self.master.config.logCompressionLimit 281 for loog in self.logs: 282 if not loog.isFinished(): 283 loog.finish() 284 # if log compression is on, and it's a real LogFile, 285 # HTMLLogFiles aren't files 286 if logCompressionLimit is not False and \ 287 isinstance(loog, LogFile): 288 if os.path.getsize(loog.getFilename()) > logCompressionLimit: 289 loog_deferred = loog.compressLog() 290 if loog_deferred: 291 cld.append(loog_deferred) 292 293 for r in self.updates.keys(): 294 if self.updates[r] is not None: 295 self.updates[r].cancel() 296 del self.updates[r] 297 298 watchers = self.finishedWatchers 299 self.finishedWatchers = [] 300 for w in watchers: 301 w.callback(self) 302 if cld: 303 return defer.DeferredList(cld)
304
305 - def checkLogfiles(self):
306 # filter out logs that have been deleted 307 self.logs = [ l for l in self.logs if l.hasContents() ]
308
309 - def isWaitingForLocks(self):
310 return self.waitingForLocks
311
312 - def setWaitingForLocks(self, waiting):
313 self.waitingForLocks = waiting
314 315 # persistence 316
317 - def __getstate__(self):
318 d = styles.Versioned.__getstate__(self) 319 del d['build'] # filled in when loading 320 if d.has_key('progress'): 321 del d['progress'] 322 del d['watchers'] 323 del d['finishedWatchers'] 324 del d['updates'] 325 del d['master'] 326 return d
327
328 - def __setstate__(self, d):
329 styles.Versioned.__setstate__(self, d) 330 # self.build must be filled in by our parent 331 332 # point the logs to this object 333 self.watchers = [] 334 self.finishedWatchers = [] 335 self.updates = {}
336
337 - def setProcessObjects(self, build, master):
338 self.build = build 339 self.master = master 340 for loog in self.logs: 341 loog.step = self 342 loog.master = master
343
344 - def upgradeToVersion1(self):
345 if not hasattr(self, "urls"): 346 self.urls = {} 347 self.wasUpgraded = True
348
349 - def upgradeToVersion2(self):
350 if not hasattr(self, "statistics"): 351 self.statistics = {} 352 self.wasUpgraded = True
353
354 - def upgradeToVersion3(self):
355 if not hasattr(self, "step_number"): 356 self.step_number = 0 357 self.wasUpgraded = True
358
359 - def upgradeToVersion4(self):
360 if not hasattr(self, "hidden"): 361 self.hidden = False 362 self.wasUpgraded = True
363
364 - def asDict(self):
365 result = {} 366 # Constant 367 result['name'] = self.getName() 368 369 # Transient 370 result['text'] = self.getText() 371 result['results'] = self.getResults() 372 result['isStarted'] = self.isStarted() 373 result['isFinished'] = self.isFinished() 374 result['statistics'] = self.statistics 375 result['times'] = self.getTimes() 376 result['expectations'] = self.getExpectations() 377 result['eta'] = self.getETA() 378 result['urls'] = self.getURLs() 379 result['step_number'] = self.step_number 380 result['hidden'] = self.hidden 381 result['logs'] = [[l.getName(), 382 self.build.builder.status.getURLForThing(l)] 383 for l in self.getLogs()] 384 return result
385