| OLD | NEW |
| 1 URL: http://buildbot.net/trac | 1 URL: http://buildbot.net/trac |
| 2 Version: 0.8.4p1 | 2 Version: 0.8.4p1 |
| 3 License: GNU General Public License (GPL) Version 2 | 3 License: GNU General Public License (GPL) Version 2 |
| 4 | 4 |
| 5 This is a forked copy of buildbot v0.8.4p1. | 5 This is a forked copy of buildbot v0.8.4p1. |
| 6 | 6 |
| 7 Make hidden steps stay hidden even if not finished, add brDoStepIf | 7 The fork starts at ece89304e7e6e7f06016de19cdb623e3c7137d2e. |
| 8 to support buildrunner. | |
| 9 | 8 |
| 9 To produce the current diff, run: |
| 10 | 10 |
| 11 diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/buildstep.py | 11 $ git diff ece89304e7e6e7f06016de19cdb623e3c7137d2e -- buildbot |
| 12 index 4aa307d..21044ea 100644 | |
| 13 --- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 14 +++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 15 @@ -621,6 +621,7 @@ class BuildStep: | |
| 16 'progressMetrics', | |
| 17 'doStepIf', | |
| 18 'hideStepIf', | |
| 19 + 'brDoStepIf', | |
| 20 ] | |
| 21 | |
| 22 name = "generic" | |
| 23 @@ -633,6 +634,9 @@ class BuildStep: | |
| 24 # doStepIf can be False, True, or a function that returns False or True | |
| 25 doStepIf = True | |
| 26 hideStepIf = False | |
| 27 + # like doStepIf, but evaluated at runtime if executing under runbuild.py | |
| 28 + # we also overload 'False' to signify this isn't a buildrunner step | |
| 29 + brDoStepIf = False | |
| 30 | |
| 31 def __init__(self, **kwargs): | |
| 32 self.factory = (self.__class__, dict(kwargs)) | |
| 33 @@ -679,6 +683,8 @@ class BuildStep: | |
| 34 | |
| 35 def setStepStatus(self, step_status): | |
| 36 self.step_status = step_status | |
| 37 + hidden = self._maybeEvaluate(self.hideStepIf, self.step_status) | |
| 38 + self.step_status.setHidden(hidden) | |
| 39 | |
| 40 def setupProgress(self): | |
| 41 if self.useProgress: | |
| 42 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_par
ty/buildbot_8_4p1/buildbot/status/web/build.py | |
| 43 index 02e4b4d..c634060 100644 | |
| 44 --- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py | |
| 45 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py | |
| 46 @@ -95,10 +95,10 @@ class StatusResourceBuild(HtmlResource): | |
| 47 for s in b.getSteps(): | |
| 48 step = {'name': s.getName() } | |
| 49 | |
| 50 - if s.isFinished(): | |
| 51 - if s.isHidden(): | |
| 52 - continue | |
| 53 + if s.isHidden(): | |
| 54 + continue | |
| 55 | |
| 56 + if s.isFinished(): | |
| 57 step['css_class'] = css_classes[s.getResults()[0]] | |
| 58 (start, end) = s.getTimes() | |
| 59 step['time_to_run'] = util.formatInterval(end - start) | |
| 60 | |
| 61 | |
| 62 Apply 2577dac35b39c4ee9c419681021d7971e8f1e5d2 and | |
| 63 3db80d46d3c35d71591f033e5c3d0a2fa0001c59 from buildbot upstream. | |
| 64 | |
| 65 | |
| 66 diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 67 index ea42207..4aa307d 100644 | |
| 68 --- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 69 +++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 70 @@ -620,6 +620,7 @@ class BuildStep: | |
| 71 'alwaysRun', | |
| 72 'progressMetrics', | |
| 73 'doStepIf', | |
| 74 + 'hideStepIf', | |
| 75 ] | |
| 76 | |
| 77 name = "generic" | |
| 78 @@ -631,6 +632,7 @@ class BuildStep: | |
| 79 progress = None | |
| 80 # doStepIf can be False, True, or a function that returns False or True | |
| 81 doStepIf = True | |
| 82 + hideStepIf = False | |
| 83 | |
| 84 def __init__(self, **kwargs): | |
| 85 self.factory = (self.__class__, dict(kwargs)) | |
| 86 @@ -920,6 +922,10 @@ class BuildStep: | |
| 87 if self.progress: | |
| 88 self.progress.finish() | |
| 89 self.step_status.stepFinished(results) | |
| 90 + | |
| 91 + hidden = self._maybeEvaluate(self.hideStepIf, self.step_status) | |
| 92 + self.step_status.setHidden(hidden) | |
| 93 + | |
| 94 self.releaseLocks() | |
| 95 self.deferred.callback(results) | |
| 96 | |
| 97 @@ -938,6 +944,9 @@ class BuildStep: | |
| 98 self.step_status.setText([self.name, "exception"]) | |
| 99 self.step_status.setText2([self.name]) | |
| 100 self.step_status.stepFinished(EXCEPTION) | |
| 101 + | |
| 102 + hidden = self._maybeEvaluate(self.hideStepIf, EXCEPTION, self) | |
| 103 + self.step_status.setHidden(hidden) | |
| 104 except: | |
| 105 log.msg("exception during failure processing") | |
| 106 log.err() | |
| 107 @@ -1048,6 +1057,11 @@ class BuildStep: | |
| 108 d = c.run(self, self.remote) | |
| 109 return d | |
| 110 | |
| 111 + @staticmethod | |
| 112 + def _maybeEvaluate(value, *args, **kwargs): | |
| 113 + if callable(value): | |
| 114 + value = value(*args, **kwargs) | |
| 115 + return value | |
| 116 | |
| 117 class OutputProgressObserver(LogObserver): | |
| 118 length = 0 | |
| 119 @@ -1294,4 +1308,3 @@ def regex_log_evaluator(cmd, step_status, regexes): | |
| 120 from buildbot.process.properties import WithProperties | |
| 121 _hush_pyflakes = [WithProperties] | |
| 122 del _hush_pyflakes | |
| 123 - | |
| 124 diff --git a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py b/third_par
ty/buildbot_8_4p1/buildbot/status/buildstep.py | |
| 125 index 781f7e8..264b599 100644 | |
| 126 --- a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py | |
| 127 +++ b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py | |
| 128 @@ -46,7 +46,7 @@ class BuildStepStatus(styles.Versioned): | |
| 129 # corresponding BuildStep has started. | |
| 130 implements(interfaces.IBuildStepStatus, interfaces.IStatusEvent) | |
| 131 | |
| 132 - persistenceVersion = 3 | |
| 133 + persistenceVersion = 4 | |
| 134 persistenceForgets = ( 'wasUpgraded', ) | |
| 135 | |
| 136 started = None | |
| 137 @@ -60,6 +60,7 @@ class BuildStepStatus(styles.Versioned): | |
| 138 finishedWatchers = [] | |
| 139 statistics = {} | |
| 140 step_number = None | |
| 141 + hidden = False | |
| 142 | |
| 143 def __init__(self, parent, step_number): | |
| 144 assert interfaces.IBuildStatus(parent) | |
| 145 @@ -67,6 +68,7 @@ class BuildStepStatus(styles.Versioned): | |
| 146 self.build_number = parent.getNumber() | |
| 147 self.builder = parent.getBuilder() | |
| 148 self.step_number = step_number | |
| 149 + self.hidden = False | |
| 150 self.logs = [] | |
| 151 self.urls = {} | |
| 152 self.watchers = [] | |
| 153 @@ -120,6 +122,9 @@ class BuildStepStatus(styles.Versioned): | |
| 154 def isFinished(self): | |
| 155 return (self.finished is not None) | |
| 156 | |
| 157 + def isHidden(self): | |
| 158 + return self.hidden | |
| 159 + | |
| 160 def waitUntilFinished(self): | |
| 161 if self.finished: | |
| 162 d = defer.succeed(self) | |
| 163 @@ -214,6 +219,9 @@ class BuildStepStatus(styles.Versioned): | |
| 164 def setProgress(self, stepprogress): | |
| 165 self.progress = stepprogress | |
| 166 | |
| 167 + def setHidden(self, hidden): | |
| 168 + self.hidden = hidden | |
| 169 + | |
| 170 def stepStarted(self): | |
| 171 self.started = util.now() | |
| 172 build = self.getBuild() | |
| 173 @@ -354,6 +362,11 @@ class BuildStepStatus(styles.Versioned): | |
| 174 self.step_number = 0 | |
| 175 self.wasUpgraded = True | |
| 176 | |
| 177 + def upgradeToVersion4(self): | |
| 178 + if not hasattr(self, "hidden"): | |
| 179 + self.hidden = False | |
| 180 + self.wasUpgraded = True | |
| 181 + | |
| 182 def asDict(self): | |
| 183 result = {} | |
| 184 # Constant | |
| 185 @@ -370,6 +383,7 @@ class BuildStepStatus(styles.Versioned): | |
| 186 result['eta'] = self.getETA() | |
| 187 result['urls'] = self.getURLs() | |
| 188 result['step_number'] = self.step_number | |
| 189 + result['hidden'] = self.hidden | |
| 190 result['logs'] = [[l.getName(), | |
| 191 self.builder.status.getURLForThing(l)] | |
| 192 for l in self.getLogs()] | |
| 193 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_par
ty/buildbot_8_4p1/buildbot/status/web/build.py | |
| 194 index 907adc8..02e4b4d 100644 | |
| 195 --- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py | |
| 196 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py | |
| 197 @@ -94,9 +94,11 @@ class StatusResourceBuild(HtmlResource): | |
| 198 | |
| 199 for s in b.getSteps(): | |
| 200 step = {'name': s.getName() } | |
| 201 - cxt['steps'].append(step) | |
| 202 | |
| 203 if s.isFinished(): | |
| 204 + if s.isHidden(): | |
| 205 + continue | |
| 206 + | |
| 207 step['css_class'] = css_classes[s.getResults()[0]] | |
| 208 (start, end) = s.getTimes() | |
| 209 step['time_to_run'] = util.formatInterval(end - start) | |
| 210 @@ -111,6 +113,8 @@ class StatusResourceBuild(HtmlResource): | |
| 211 step['css_class'] = "not_started" | |
| 212 step['time_to_run'] = "" | |
| 213 | |
| 214 + cxt['steps'].append(step) | |
| 215 + | |
| 216 step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName())
) | |
| 217 step['text'] = " ".join(s.getText()) | |
| 218 step['urls'] = map(lambda x:dict(url=x[1],logname=x[0]), s.getURLs(
).items()) | |
| 219 @@ -255,4 +259,3 @@ class BuildsResource(HtmlResource): | |
| 220 return StatusResourceBuild(build_status) | |
| 221 | |
| 222 return HtmlResource.getChild(self, path, req) | |
| 223 - | |
| 224 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py b/third
_party/buildbot_8_4p1/buildbot/status/web/waterfall.py | |
| 225 index 2c04824..923fe0d 100644 | |
| 226 --- a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py | |
| 227 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py | |
| 228 @@ -576,13 +576,19 @@ class WaterfallStatusResource(HtmlResource): | |
| 229 try: | |
| 230 while True: | |
| 231 e = g.next() | |
| 232 - # e might be builder.BuildStepStatus, | |
| 233 + # e might be buildstep.BuildStepStatus, | |
| 234 # builder.BuildStatus, builder.Event, | |
| 235 # waterfall.Spacer(builder.Event), or changes.Change . | |
| 236 # The showEvents=False flag means we should hide | |
| 237 # builder.Event . | |
| 238 if not showEvents and isinstance(e, builder.Event): | |
| 239 continue | |
| 240 + | |
| 241 + if isinstance(e, buildstep.BuildStepStatus): | |
| 242 + # unfinished steps are always shown | |
| 243 + if e.isFinished() and e.isHidden(): | |
| 244 + continue | |
| 245 + | |
| 246 break | |
| 247 event = interfaces.IStatusEvent(e) | |
| 248 if debug: | |
| 249 -- | |
| 250 | |
| 251 | |
| 252 | |
| 253 | |
| 254 Applied f591d3369270b7897989c242fa7b827ca58b07b7 from buildbot upstream. | |
| 255 | |
| 256 | |
| 257 Add extra parameters to HttpStatusPush as a very basic authentication mechanism. | |
| 258 | |
| 259 diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_p
arty/buildbot_8_4p1/buildbot/status/status_push.py | |
| 260 index b7b3b0a..ca83fdb 100644 | |
| 261 --- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py | |
| 262 +++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py | |
| 263 @@ -328,7 +328,7 @@ class HttpStatusPush(StatusPush): | |
| 264 | |
| 265 def __init__(self, serverUrl, debug=None, maxMemoryItems=None, | |
| 266 maxDiskItems=None, chunkSize=200, maxHttpRequestSize=2**20, | |
| 267 - **kwargs): | |
| 268 + extra_post_params=None, **kwargs): | |
| 269 """ | |
| 270 @serverUrl: Base URL to be used to push events notifications. | |
| 271 @maxMemoryItems: Maximum number of items to keep queued in memory. | |
| 272 @@ -341,6 +341,7 @@ class HttpStatusPush(StatusPush): | |
| 273 """ | |
| 274 # Parameters. | |
| 275 self.serverUrl = serverUrl | |
| 276 + self.extra_post_params = extra_post_params or {} | |
| 277 self.debug = debug | |
| 278 self.chunkSize = chunkSize | |
| 279 self.lastPushWasSuccessful = True | |
| 280 @@ -378,7 +379,9 @@ class HttpStatusPush(StatusPush): | |
| 281 packets = json.dumps(items, indent=2, sort_keys=True) | |
| 282 else: | |
| 283 packets = json.dumps(items, separators=(',',':')) | |
| 284 - data = urllib.urlencode({'packets': packets}) | |
| 285 + params = {'packets': packets} | |
| 286 + params.update(self.extra_post_params) | |
| 287 + data = urllib.urlencode(params) | |
| 288 if (not self.maxHttpRequestSize or | |
| 289 len(data) < self.maxHttpRequestSize): | |
| 290 return (data, items) | |
| 291 @@ -395,6 +398,8 @@ class HttpStatusPush(StatusPush): | |
| 292 | |
| 293 def pushHttp(self): | |
| 294 """Do the HTTP POST to the server.""" | |
| 295 + if not self.serverUrl: | |
| 296 + return | |
| 297 (encoded_packets, items) = self.popChunk() | |
| 298 | |
| 299 def Success(result): | |
| 300 | |
| 301 | |
| 302 | |
| 303 | |
| 304 Increase console customization build range. | |
| 305 | |
| 306 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/console.py | |
| 307 index b00b871..e513ec0 100644 | |
| 308 --- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 309 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 310 @@ -727,10 +727,10 @@ class ConsoleStatusResource(HtmlResource): | |
| 311 # Keep only the revisions we care about. | |
| 312 # By default we process the last 40 revisions. | |
| 313 # If a dev name is passed, we look for the changes by this person in th
e | |
| 314 - # last 80 revisions. | |
| 315 + # last 160 revisions. | |
| 316 numRevs = int(request.args.get("revs", [40])[0]) | |
| 317 if devName: | |
| 318 - numRevs *= 2 | |
| 319 + numRevs *= 4 | |
| 320 numBuilds = numRevs | |
| 321 | |
| 322 # Get all changes we can find. This is a DB operation, so it must use | |
| 323 | |
| 324 | |
| 325 | |
| 326 Port console caching from 0.8.3p1 to 0.8.4p1. | |
| 327 | |
| 328 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/console.py | |
| 329 index 59cbc0e..c95ac7f 100644 | |
| 330 --- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 331 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 332 @@ -53,12 +53,74 @@ def getResultsClass(results, prevResults, inProgress): | |
| 333 else: | |
| 334 # The previous build also failed. | |
| 335 return "warnings" | |
| 336 - | |
| 337 + | |
| 338 # Any other results? Like EXCEPTION? | |
| 339 return "exception" | |
| 340 | |
| 341 class ANYBRANCH: pass # a flag value, used below | |
| 342 | |
| 343 +class CachedStatusBox(object): | |
| 344 + """Basic data class to remember the information for a box on the console.""
" | |
| 345 + def __init__(self, color, pageTitle, details, url, tag): | |
| 346 + self.color = color | |
| 347 + self.pageTitle = pageTitle | |
| 348 + self.details = details | |
| 349 + self.url = url | |
| 350 + self.tag = tag | |
| 351 + | |
| 352 + | |
| 353 +class CacheStatus(object): | |
| 354 + """Basic cache of CachedStatusBox based on builder names and revisions. | |
| 355 + | |
| 356 + Current limitation: If the revisions are not numerically increasing, the | |
| 357 + "trim" feature will not work and the cache will grow | |
| 358 + indefinitely. | |
| 359 + """ | |
| 360 + def __init__(self): | |
| 361 + self.allBoxes = dict() | |
| 362 + | |
| 363 + def display(self): | |
| 364 + """Display the available data in the cache. Used for debugging only.""" | |
| 365 + data = "" | |
| 366 + for builder in self.allBoxes: | |
| 367 + for revision in self.allBoxes[builder]: | |
| 368 + data += "%s %s %s\n" % (builder, str(revision), | |
| 369 + self.allBoxes[builder][revision].color) | |
| 370 + return data | |
| 371 + | |
| 372 + def insert(self, builderName, revision, color, pageTitle, details, url, tag
): | |
| 373 + """Insert a new build into the cache.""" | |
| 374 + box = CachedStatusBox(color, pageTitle, details, url, tag) | |
| 375 + if not self.allBoxes.get(builderName): | |
| 376 + self.allBoxes[builderName] = {} | |
| 377 + | |
| 378 + self.allBoxes[builderName][revision] = box | |
| 379 + | |
| 380 + def get(self, builderName, revision): | |
| 381 + """Retrieve a build from the cache.""" | |
| 382 + if not self.allBoxes.get(builderName): | |
| 383 + return None | |
| 384 + if not self.allBoxes[builderName].get(revision): | |
| 385 + return None | |
| 386 + return self.allBoxes[builderName][revision] | |
| 387 + | |
| 388 + def trim(self): | |
| 389 + """Remove old revisions from the cache. (For integer revisions only)""" | |
| 390 + try: | |
| 391 + for builder in self.allBoxes: | |
| 392 + allRevs = [] | |
| 393 + for revision in self.allBoxes[builder]: | |
| 394 + allRevs.append(revision) | |
| 395 + | |
| 396 + if len(allRevs) > 250: | |
| 397 + allRevs.sort(cmp=lambda x,y: cmp(int(x), int(y))) | |
| 398 + deleteCount = len(allRevs) - 250 | |
| 399 + for i in range(0, deleteCount): | |
| 400 + del self.allBoxes[builder][allRevs[i]] | |
| 401 + except: | |
| 402 + pass | |
| 403 + | |
| 404 + | |
| 405 class DevRevision: | |
| 406 """Helper class that contains all the information we need for a revision.""
" | |
| 407 | |
| 408 @@ -97,6 +159,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 409 HtmlResource.__init__(self) | |
| 410 | |
| 411 self.status = None | |
| 412 + self.cache = CacheStatus() | |
| 413 | |
| 414 if orderByTime: | |
| 415 self.comparator = TimeRevisionComparator() | |
| 416 @@ -133,22 +196,22 @@ class ConsoleStatusResource(HtmlResource): | |
| 417 def fetchChangesFromHistory(self, status, max_depth, max_builds, debugInfo)
: | |
| 418 """Look at the history of the builders and try to fetch as many changes | |
| 419 as possible. We need this when the main source does not contain enough | |
| 420 - sourcestamps. | |
| 421 + sourcestamps. | |
| 422 | |
| 423 max_depth defines how many builds we will parse for a given builder. | |
| 424 max_builds defines how many builds total we want to parse. This is to | |
| 425 limit the amount of time we spend in this function. | |
| 426 - | |
| 427 + | |
| 428 This function is sub-optimal, but the information returned by this | |
| 429 function is cached, so this function won't be called more than once. | |
| 430 """ | |
| 431 - | |
| 432 + | |
| 433 allChanges = list() | |
| 434 build_count = 0 | |
| 435 for builderName in status.getBuilderNames()[:]: | |
| 436 if build_count > max_builds: | |
| 437 break | |
| 438 - | |
| 439 + | |
| 440 builder = status.getBuilder(builderName) | |
| 441 build = self.getHeadBuild(builder) | |
| 442 depth = 0 | |
| 443 @@ -160,7 +223,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 444 build = build.getPreviousBuild() | |
| 445 | |
| 446 debugInfo["source_fetch_len"] = len(allChanges) | |
| 447 - return allChanges | |
| 448 + return allChanges | |
| 449 | |
| 450 @defer.deferredGenerator | |
| 451 def getAllChanges(self, request, status, debugInfo): | |
| 452 @@ -191,6 +254,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 453 prevChange = change | |
| 454 allChanges = newChanges | |
| 455 | |
| 456 + debugInfo["source_len"] = len(allChanges) | |
| 457 yield allChanges | |
| 458 | |
| 459 def getBuildDetails(self, request, builderName, build): | |
| 460 @@ -232,7 +296,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 461 build, and we go down until we find a build that was built prior to the | |
| 462 last change we are interested in.""" | |
| 463 | |
| 464 - revision = lastRevision | |
| 465 + revision = lastRevision | |
| 466 | |
| 467 builds = [] | |
| 468 build = self.getHeadBuild(builder) | |
| 469 @@ -299,7 +363,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 470 display the console page. The key is the builder name, and the value is | |
| 471 an array of build we care about. We also returns a dictionary of | |
| 472 builders we care about. The key is it's category. | |
| 473 - | |
| 474 + | |
| 475 lastRevision is the last revision we want to display in the page. | |
| 476 categories is a list of categories to display. It is coming from the | |
| 477 HTTP GET parameters. | |
| 478 @@ -364,7 +428,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 479 | |
| 480 cs = [] | |
| 481 | |
| 482 - for category in categories: | |
| 483 + for category in categories: | |
| 484 c = {} | |
| 485 | |
| 486 c["name"] = category | |
| 487 @@ -372,9 +436,9 @@ class ConsoleStatusResource(HtmlResource): | |
| 488 # To be able to align the table correctly, we need to know | |
| 489 # what percentage of space this category will be taking. This is | |
| 490 # (#Builders in Category) / (#Builders Total) * 100. | |
| 491 - c["size"] = (len(builderList[category]) * 100) / count | |
| 492 + c["size"] = (len(builderList[category]) * 100) / count | |
| 493 cs.append(c) | |
| 494 - | |
| 495 + | |
| 496 return cs | |
| 497 | |
| 498 def displaySlaveLine(self, status, builderList, debugInfo): | |
| 499 @@ -448,6 +512,23 @@ class ConsoleStatusResource(HtmlResource): | |
| 500 introducedIn = None | |
| 501 firstNotIn = None | |
| 502 | |
| 503 + cached_value = self.cache.get(builder, revision.revision) | |
| 504 + if cached_value: | |
| 505 + debugInfo["from_cache"] += 1 | |
| 506 + | |
| 507 + b = {} | |
| 508 + b["url"] = cached_value.url | |
| 509 + b["pageTitle"] = cached_value.pageTitle | |
| 510 + b["color"] = cached_value.color | |
| 511 + b["tag"] = cached_value.tag | |
| 512 + | |
| 513 + builds[category].append(b) | |
| 514 + | |
| 515 + if cached_value.details and cached_value.color == "failure"
: | |
| 516 + details.append(cached_value.details) | |
| 517 + | |
| 518 + continue | |
| 519 + | |
| 520 # Find the first build that does not include the revision. | |
| 521 for build in allBuilds[builder]: | |
| 522 if self.comparator.isRevisionEarlier(build, revision): | |
| 523 @@ -504,6 +585,13 @@ class ConsoleStatusResource(HtmlResource): | |
| 524 if current_details and resultsClass == "failure": | |
| 525 details.append(current_details) | |
| 526 | |
| 527 + # Add this box to the cache if it's completed so we don't have | |
| 528 + # to compute it again. | |
| 529 + if resultsClass not in ("running", "notstarted"): | |
| 530 + debugInfo["added_blocks"] += 1 | |
| 531 + self.cache.insert(builder, revision.revision, resultsClass, | |
| 532 + pageTitle, current_details, url, tag) | |
| 533 + | |
| 534 return (builds, details) | |
| 535 | |
| 536 def filterRevisions(self, revisions, filter=None, max_revs=None): | |
| 537 @@ -553,7 +641,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 538 | |
| 539 if builderList: | |
| 540 subs["categories"] = self.displayCategories(builderList, debugInfo) | |
| 541 - subs['slaves'] = self.displaySlaveLine(status, builderList, debugIn
fo) | |
| 542 + subs['slaves'] = self.displaySlaveLine(status, builderList, | |
| 543 + debugInfo) | |
| 544 else: | |
| 545 subs["categories"] = [] | |
| 546 | |
| 547 @@ -574,14 +663,14 @@ class ConsoleStatusResource(HtmlResource): | |
| 548 | |
| 549 # Display the status for all builders. | |
| 550 (builds, details) = self.displayStatusLine(builderList, | |
| 551 - allBuilds, | |
| 552 - revision, | |
| 553 - debugInfo) | |
| 554 + allBuilds, | |
| 555 + revision, | |
| 556 + debugInfo) | |
| 557 r['builds'] = builds | |
| 558 r['details'] = details | |
| 559 | |
| 560 # Calculate the td span for the comment and the details. | |
| 561 - r["span"] = len(builderList) + 2 | |
| 562 + r["span"] = len(builderList) + 2 | |
| 563 | |
| 564 subs['revisions'].append(r) | |
| 565 | |
| 566 @@ -678,6 +767,13 @@ class ConsoleStatusResource(HtmlResource): | |
| 567 debugInfo) | |
| 568 | |
| 569 debugInfo["added_blocks"] = 0 | |
| 570 + debugInfo["from_cache"] = 0 | |
| 571 + | |
| 572 + if request.args.get("display_cache", None): | |
| 573 + data = "" | |
| 574 + data += "\nGlobal Cache\n" | |
| 575 + data += self.cache.display() | |
| 576 + return data | |
| 577 | |
| 578 cxt.update(self.displayPage(request, status, builderList, | |
| 579 allBuilds, revisions, categories, | |
| 580 @@ -686,6 +782,11 @@ class ConsoleStatusResource(HtmlResource): | |
| 581 templates = request.site.buildbot_service.templates | |
| 582 template = templates.get_template("console.html") | |
| 583 data = template.render(cxt) | |
| 584 + | |
| 585 + # Clean up the cache. | |
| 586 + if debugInfo["added_blocks"]: | |
| 587 + self.cache.trim() | |
| 588 + | |
| 589 return data | |
| 590 d.addCallback(got_changes) | |
| 591 return d | |
| 592 | |
| 593 | |
| 594 commit ca9f4ed52b4febcebd30ad77a8e40737f3a5ad1f | |
| 595 Author: Nicolas Sylvain <nsylvain@chromium.org> | |
| 596 Date: Fri Nov 23 12:24:04 2012 -0500 | |
| 597 | |
| 598 Optionally add revision to the waterfall display of changes. | |
| 599 | |
| 600 Used in chromium's waterfalls extensively. | |
| 601 publicly reviewed here: https://codereview.chromium.org/7276032 | |
| 602 | |
| 603 Modified to not change the look by default. | |
| 604 | |
| 605 diff --git a/master/buildbot/status/web/changes.py b/master/buildbot/status/web/
changes.py | |
| 606 index 6be37b5..0971bea 100644 | |
| 607 --- a/master/buildbot/status/web/changes.py | |
| 608 +++ b/master/buildbot/status/web/changes.py | |
| 609 @@ -63,7 +63,8 @@ class ChangeBox(components.Adapter): | |
| 610 template = req.site.buildbot_service.templates.get_template("change_mac
ros.html") | |
| 611 text = template.module.box_contents(url=url, | |
| 612 who=self.original.getShortAuthor(), | |
| 613 - pageTitle=self.original.comments) | |
| 614 + pageTitle=self.original.comments, | |
| 615 + revision=self.original.revision) | |
| 616 return Box([text], class_="Change") | |
| 617 components.registerAdapter(ChangeBox, Change, IBox) | |
| 618 | |
| 619 diff --git a/master/buildbot/status/web/templates/change_macros.html b/master/bu
ildbot/status/web/templates/change_macros.html | |
| 620 index 9b46191..dc6a9b2 100644 | |
| 621 --- a/master/buildbot/status/web/templates/change_macros.html | |
| 622 +++ b/master/buildbot/status/web/templates/change_macros.html | |
| 623 @@ -71,6 +71,6 @@ | |
| 624 {% endif %} | |
| 625 {%- endmacro %} | |
| 626 | |
| 627 -{% macro box_contents(who, url, pageTitle) -%} | |
| 628 +{% macro box_contents(who, url, pageTitle, revision) -%} | |
| 629 <a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a> | |
| 630 {%- endmacro %} | |
| 631 | |
| 632 Add revision to the chromium waterfalls. | |
| 633 | |
| 634 Index: buildbot/status/web/templates/change_macros.html | |
| 635 =================================================================== | |
| 636 --- buildbot/status/web/templates/change_macros.html (revision 167249) | |
| 637 +++ buildbot/status/web/templates/change_macros.html (working copy) | |
| 638 @@ -67,6 +67,6 @@ | |
| 639 {% endif %} | |
| 640 {%- endmacro %} | |
| 641 | |
| 642 {% macro box_contents(who, url, pageTitle, revision) -%} | |
| 643 -<a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a> | |
| 644 +<a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a><br>r{{ revisio
n }} | |
| 645 {%- endmacro %} | |
| 646 | |
| 647 | |
| 648 | |
| 649 | |
| 650 commit e6b9fad4373d6e55f7957ee8312d58cf0461d98c | |
| 651 Author: Chase Phillips <cmp@google.com> | |
| 652 Date: Mon Jul 25 10:46:54 2011 -0700 | |
| 653 | |
| 654 Import upstream fix: set journal mode. | |
| 655 | |
| 656 SQLite database fix from buildbot.net to increase | |
| 657 concurrency. This is 4050c5e7a: | |
| 658 https://github.com/buildbot/buildbot/commit/4050c5e7a2641df56f792b06fc1aea6c
16221e8f#diff-0 | |
| 659 | |
| 660 diff --git a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py b/third_pa
rty/buildbot_8_4p1/buildbot/db/enginestrategy.py | |
| 661 index 7be9196..3d05cb6 100644 | |
| 662 --- a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py | |
| 663 +++ b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py | |
| 664 @@ -25,6 +25,7 @@ special cases that Buildbot needs. Those include: | |
| 665 | |
| 666 import os | |
| 667 import sqlalchemy | |
| 668 +from twisted.python import log | |
| 669 from sqlalchemy.engine import strategies, url | |
| 670 from sqlalchemy.pool import NullPool | |
| 671 | |
| 672 @@ -83,6 +84,16 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStra
tegy): | |
| 673 | |
| 674 return u, kwargs, max_conns | |
| 675 | |
| 676 + def set_up_sqlite_engine(self, u, engine): | |
| 677 + """Special setup for sqlite engines""" | |
| 678 + # try to enable WAL logging | |
| 679 + if u.database: | |
| 680 + log.msg("setting database journal mode to 'wal'") | |
| 681 + try: | |
| 682 + engine.execute("pragma journal_mode = wal") | |
| 683 + except: | |
| 684 + log.msg("failed to set journal mode - database may fail") | |
| 685 + | |
| 686 def special_case_mysql(self, u, kwargs): | |
| 687 """For mysql, take max_idle out of the query arguments, and | |
| 688 use its value for pool_recycle. Also, force use_unicode and | |
| 689 @@ -148,9 +159,12 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineSt
rategy): | |
| 690 # by DBConnector to configure the surrounding thread pool | |
| 691 engine.optimal_thread_pool_size = max_conns | |
| 692 | |
| 693 - # and keep the basedir | |
| 694 + # keep the basedir | |
| 695 engine.buildbot_basedir = basedir | |
| 696 | |
| 697 + if u.drivername.startswith('sqlite'): | |
| 698 + self.set_up_sqlite_engine(u, engine) | |
| 699 + | |
| 700 return engine | |
| 701 | |
| 702 BuildbotEngineStrategy() | |
| 703 | |
| 704 | |
| 705 | |
| 706 commit a2a0d76cbd2b016b628decf36ef8e298a9b1e4e8 | |
| 707 Author: Chase Phillips <cmp@google.com> | |
| 708 Date: Thu Jul 28 16:24:09 2011 -0700 | |
| 709 | |
| 710 Backport postgres fix from Buildbot trunk. | |
| 711 | |
| 712 This fixes http://trac.buildbot.net/ticket/2010. | |
| 713 | |
| 714 diff --git a/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py b/third_par
ty/buildbot_8_4p1/buildbot/db/buildrequests.py | |
| 715 index 650ac5d..b7d2b08 100644 | |
| 716 --- a/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py | |
| 717 +++ b/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py | |
| 718 @@ -206,8 +206,6 @@ class BuildRequestsConnectorComponent(base.DBConnectorCompon
ent): | |
| 719 master_incarnation = self.db.master.master_incarnation | |
| 720 tbl = self.db.model.buildrequests | |
| 721 | |
| 722 - transaction = conn.begin() | |
| 723 - | |
| 724 # first, create a temporary table containing all of the ID's | |
| 725 # we want to claim | |
| 726 tmp_meta = sa.MetaData(bind=conn) | |
| 727 @@ -216,6 +214,8 @@ class BuildRequestsConnectorComponent(base.DBConnectorCompon
ent): | |
| 728 prefixes=['TEMPORARY']) | |
| 729 tmp.create() | |
| 730 | |
| 731 + transaction = conn.begin() | |
| 732 + | |
| 733 try: | |
| 734 q = tmp.insert() | |
| 735 conn.execute(q, [ dict(brid=id) for id in brids ]) | |
| 736 @@ -268,8 +268,10 @@ class BuildRequestsConnectorComponent(base.DBConnectorCompo
nent): | |
| 737 raise AlreadyClaimedError | |
| 738 res.close() | |
| 739 finally: | |
| 740 - # clean up after ourselves, even though it's a temporary table | |
| 741 - tmp.drop(checkfirst=True) | |
| 742 + # clean up after ourselves, even though it's a temporary table; | |
| 743 + # note that checkfirst=True does not work here for Postgres | |
| 744 + # (#2010). | |
| 745 + tmp.drop() | |
| 746 | |
| 747 return self.db.pool.do(thd) | |
| 748 | |
| 749 | |
| 750 Truncate commit comments to 1024 characters, which is the maximum size in the | |
| 751 db schema (see buildbot/db/model.py:181). | |
| 752 | |
| 753 --- buildbot/db/changes.py (revision 103214) | |
| 754 +++ buildbot/db/changes.py (working copy) | |
| 755 @@ -53,7 +53,7 @@ | |
| 756 string) | |
| 757 """ | |
| 758 | |
| 759 - def addChange(self, author=None, files=None, comments=None, is_dir=0, | |
| 760 + def addChange(self, author=None, files=None, comments='', is_dir=0, | |
| 761 links=None, revision=None, when_timestamp=None, branch=None, | |
| 762 category=None, revlink='', properties={}, repository='', | |
| 763 project='', _reactor=reactor): | |
| 764 @@ -130,7 +130,7 @@ | |
| 765 ins = self.db.model.changes.insert() | |
| 766 r = conn.execute(ins, dict( | |
| 767 author=author, | |
| 768 - comments=comments, | |
| 769 + comments=comments[:1024], | |
| 770 is_dir=is_dir, | |
| 771 branch=branch, | |
| 772 revision=revision, | |
| 773 | |
| 774 | |
| 775 Fix inheritance bug. | |
| 776 | |
| 777 http://trac.buildbot.net/ticket/2120 | |
| 778 | |
| 779 Index: buildbot/status/web/logs.py | |
| 780 =================================================================== | |
| 781 --- buildbot/status/web/logs.py (revision 103214) | |
| 782 +++ buildbot/status/web/logs.py (working copy) | |
| 783 @@ -65,7 +65,7 @@ | |
| 784 if path == "text": | |
| 785 self.asText = True | |
| 786 return self | |
| 787 - return HtmlResource.getChild(self, path, req) | |
| 788 + return Resource.getChild(self, path, req) | |
| 789 | |
| 790 def content(self, entries): | |
| 791 html_entries = [] | |
| 792 | |
| 793 | |
| 794 | |
| 795 **** Added by cmp on 10/4/2011 | |
| 796 Fix updateSourceStamp=False to work as expected. | |
| 797 | |
| 798 Index: buildbot/steps/trigger.py | |
| 799 =================================================================== | |
| 800 --- buildbot/steps/trigger.py | |
| 801 +++ buildbot/steps/trigger.py | |
| 802 @@ -77,15 +77,18 @@ class Trigger(LoggingBuildStep): | |
| 803 | |
| 804 """ | |
| 805 assert schedulerNames, "You must specify a scheduler to trigger" | |
| 806 - if sourceStamp and updateSourceStamp: | |
| 807 + if sourceStamp and (updateSourceStamp is not None): | |
| 808 raise ValueError("You can't specify both sourceStamp and updateSour
ceStamp") | |
| 809 if sourceStamp and alwaysUseLatest: | |
| 810 raise ValueError("You can't specify both sourceStamp and alwaysUseL
atest") | |
| 811 - if alwaysUseLatest and updateSourceStamp: | |
| 812 + if alwaysUseLatest and (updateSourceStamp is not None): | |
| 813 raise ValueError("You can't specify both alwaysUseLatest and update
SourceStamp") | |
| 814 self.schedulerNames = schedulerNames | |
| 815 self.sourceStamp = sourceStamp | |
| 816 - self.updateSourceStamp = updateSourceStamp or not (alwaysUseLatest or s
ourceStamp) | |
| 817 + if updateSourceStamp is not None: | |
| 818 + self.updateSourceStamp = updateSourceStamp | |
| 819 + else: | |
| 820 + self.updateSourceStamp = not (alwaysUseLatest or sourceStamp) | |
| 821 self.alwaysUseLatest = alwaysUseLatest | |
| 822 self.waitForFinish = waitForFinish | |
| 823 self.set_properties = set_properties | |
| 824 | |
| 825 | |
| 826 Merge from HEAD to pick up a bug fix. | |
| 827 | |
| 828 index 1c7b4ab..7474c0d 100644 | |
| 829 --- a/master/buildbot/util/lru.py | |
| 830 +++ b/master/buildbot/util/lru.py | |
| 831 @@ -47,6 +47,7 @@ class AsyncLRUCache(object): | |
| 832 @ivar hits: cache hits so far | |
| 833 @ivar refhits: cache misses found in the weak ref dictionary, so far | |
| 834 @ivar misses: cache misses leading to re-fetches, so far | |
| 835 + @ivar max_size: maximum allowed size of the cache | |
| 836 """ | |
| 837 | |
| 838 __slots__ = ('max_size max_queue miss_fn ' | |
| 839 @@ -72,7 +73,7 @@ class AsyncLRUCache(object): | |
| 840 self.weakrefs = WeakValueDictionary() | |
| 841 self.concurrent = {} | |
| 842 self.hits = self.misses = self.refhits = 0 | |
| 843 - self.refcount = defaultdict(default_factory = lambda : 0) | |
| 844 + self.refcount = defaultdict(lambda : 0) | |
| 845 | |
| 846 def get(self, key, **miss_fn_kwargs): | |
| 847 """ | |
| 848 @@ -99,7 +100,7 @@ class AsyncLRUCache(object): | |
| 849 # utility function to record recent use of this key | |
| 850 def ref_key(): | |
| 851 queue.append(key) | |
| 852 - refcount[key] = refcount.get(key, 0) + 1 | |
| 853 + refcount[key] = refcount[key] + 1 | |
| 854 | |
| 855 # periodically compact the queue by eliminating duplicate keys | |
| 856 # while preserving order of most recent access. Note that this | |
| 857 @@ -151,11 +152,12 @@ class AsyncLRUCache(object): | |
| 858 cache[key] = result | |
| 859 weakrefs[key] = result | |
| 860 | |
| 861 - self._purge() | |
| 862 + # reference the key once, possibly standing in for multiple | |
| 863 + # concurrent accesses | |
| 864 + ref_key() | |
| 865 | |
| 866 - # reference the key once, possibly standing in for multiple | |
| 867 - # concurrent accesses | |
| 868 - ref_key() | |
| 869 + self.inv() | |
| 870 + self._purge() | |
| 871 | |
| 872 # and fire all of the waiting Deferreds | |
| 873 dlist = concurrent.pop(key) | |
| 874 @@ -182,8 +184,8 @@ class AsyncLRUCache(object): | |
| 875 queue = self.queue | |
| 876 max_size = self.max_size | |
| 877 | |
| 878 - # purge least recently used entries, using refcount | |
| 879 - # to count repeatedly-used entries | |
| 880 + # purge least recently used entries, using refcount to count entries | |
| 881 + # that appear multiple times in the queue | |
| 882 while len(cache) > max_size: | |
| 883 refc = 1 | |
| 884 while refc: | |
| 885 @@ -216,3 +218,31 @@ class AsyncLRUCache(object): | |
| 886 self.max_size = max_size | |
| 887 self.max_queue = max_size * self.QUEUE_SIZE_FACTOR | |
| 888 self._purge() | |
| 889 + | |
| 890 + def inv(self): | |
| 891 + """Check invariants and log if they are not met; used for debugging""" | |
| 892 + global inv_failed | |
| 893 + | |
| 894 + # the keys of the queue and cache should be identical | |
| 895 + cache_keys = set(self.cache.keys()) | |
| 896 + queue_keys = set(self.queue) | |
| 897 + if queue_keys - cache_keys: | |
| 898 + log.msg("INV: uncached keys in queue:", queue_keys - cache_keys) | |
| 899 + inv_failed = True | |
| 900 + if cache_keys - queue_keys: | |
| 901 + log.msg("INV: unqueued keys in cache:", cache_keys - queue_keys) | |
| 902 + inv_failed = True | |
| 903 + | |
| 904 + # refcount should always represent the number of times each key appears | |
| 905 + # in the queue | |
| 906 + exp_refcount = dict() | |
| 907 + for k in self.queue: | |
| 908 + exp_refcount[k] = exp_refcount.get(k, 0) + 1 | |
| 909 + if exp_refcount != self.refcount: | |
| 910 + log.msg("INV: refcounts differ:") | |
| 911 + log.msg(" expected:", sorted(exp_refcount.items())) | |
| 912 + log.msg(" got:", sorted(self.refcount.items())) | |
| 913 + inv_failed = True | |
| 914 + | |
| 915 +# for tests | |
| 916 +inv_failed = False | |
| 917 | |
| 918 | |
| 919 | |
| 920 Truncate comments to maximum length during migration. | |
| 921 | |
| 922 Index: db/migrate/versions/001_initial.py | |
| 923 =================================================================== | |
| 924 --- db/migrate/versions/001_initial.py (revision 104114) | |
| 925 +++ db/migrate/versions/001_initial.py (working copy) | |
| 926 @@ -216,7 +216,7 @@ | |
| 927 values = dict( | |
| 928 changeid=c.number, | |
| 929 author=c.who, | |
| 930 - comments=c.comments, | |
| 931 + comments=c.comments[:1024], | |
| 932 is_dir=c.isdir, | |
| 933 branch=c.branch, | |
| 934 revision=c.revision, | |
| 935 | |
| 936 | |
| 937 Add the 'revlinktmpl' field to GitPoller, used the same way as in | |
| 938 SVNPoller. | |
| 939 | |
| 940 Index: buildbot/changes/gitpoller.py | |
| 941 =================================================================== | |
| 942 --- buildbot/changes/gitpoller.py (revision 104853) | |
| 943 +++ buildbot/changes/gitpoller.py (working copy) | |
| 944 @@ -16,6 +16,7 @@ | |
| 945 import time | |
| 946 import tempfile | |
| 947 import os | |
| 948 +import urllib | |
| 949 from twisted.python import log | |
| 950 from twisted.internet import defer, utils | |
| 951 | |
| 952 @@ -36,7 +37,7 @@ | |
| 953 gitbin='git', usetimestamps=True, | |
| 954 category=None, project=None, | |
| 955 pollinterval=-2, fetch_refspec=None, | |
| 956 - encoding='utf-8'): | |
| 957 + encoding='utf-8', revlinktmpl=''): | |
| 958 # for backward compatibility; the parameter used to be spelled with 'i' | |
| 959 if pollinterval != -2: | |
| 960 pollInterval = pollinterval | |
| 961 @@ -57,6 +58,7 @@ | |
| 962 self.changeCount = 0 | |
| 963 self.commitInfo = {} | |
| 964 self.initLock = defer.DeferredLock() | |
| 965 + self.revlinktmpl = revlinktmpl | |
| 966 | |
| 967 if self.workdir == None: | |
| 968 self.workdir = tempfile.gettempdir() + '/gitpoller_work' | |
| 969 @@ -273,6 +275,10 @@ | |
| 970 # just fail on the first error; they're probably all related! | |
| 971 raise failures[0] | |
| 972 | |
| 973 + revlink = '' | |
| 974 + if self.revlinktmpl and rev: | |
| 975 + revlink = self.revlinktmpl % urllib.quote_plus(rev) | |
| 976 + | |
| 977 timestamp, name, files, comments = [ r[1] for r in results ] | |
| 978 d = self.master.addChange( | |
| 979 author=name, | |
| 980 @@ -283,7 +289,8 @@ | |
| 981 branch=self.branch, | |
| 982 category=self.category, | |
| 983 project=self.project, | |
| 984 - repository=self.repourl) | |
| 985 + repository=self.repourl, | |
| 986 + revlink=revlink) | |
| 987 wfd = defer.waitForDeferred(d) | |
| 988 yield wfd | |
| 989 results = wfd.getResult() | |
| 990 | |
| 991 Add limit query argument to /console | |
| 992 | |
| 993 | |
| 994 --- buildbot/status/web/console.py | |
| 995 +++ buildbot/status/web/console.py | |
| 996 @@ -228,9 +228,9 @@ class ConsoleStatusResource(HtmlResource): | |
| 997 @defer.deferredGenerator | |
| 998 def getAllChanges(self, request, status, debugInfo): | |
| 999 master = request.site.buildbot_service.master | |
| 1000 - | |
| 1001 + limit = min(100, max(1, int(request.args.get('limit', [25])[0]))) | |
| 1002 wfd = defer.waitForDeferred( | |
| 1003 - master.db.changes.getRecentChanges(25)) | |
| 1004 + master.db.changes.getRecentChanges(limit)) | |
| 1005 yield wfd | |
| 1006 chdicts = wfd.getResult() | |
| 1007 | |
| 1008 | |
| 1009 | |
| 1010 Also cache builderName used in console json-like data. | |
| 1011 | |
| 1012 Index: buildbot/status/web/console.py | |
| 1013 =================================================================== | |
| 1014 --- buildbot/status/web/console.py | |
| 1015 +++ buildbot/status/web/console.py | |
| 1016 @@ -61,12 +61,13 @@ class ANYBRANCH: pass # a flag value, used below | |
| 1017 | |
| 1018 class CachedStatusBox(object): | |
| 1019 """Basic data class to remember the information for a box on the console.""
" | |
| 1020 - def __init__(self, color, pageTitle, details, url, tag): | |
| 1021 + def __init__(self, color, pageTitle, details, url, tag, builderName): | |
| 1022 self.color = color | |
| 1023 self.pageTitle = pageTitle | |
| 1024 self.details = details | |
| 1025 self.url = url | |
| 1026 self.tag = tag | |
| 1027 + self.builderName = builderName | |
| 1028 | |
| 1029 | |
| 1030 class CacheStatus(object): | |
| 1031 @@ -90,7 +91,7 @@ class CacheStatus(object): | |
| 1032 | |
| 1033 def insert(self, builderName, revision, color, pageTitle, details, url, tag
): | |
| 1034 """Insert a new build into the cache.""" | |
| 1035 - box = CachedStatusBox(color, pageTitle, details, url, tag) | |
| 1036 + box = CachedStatusBox(color, pageTitle, details, url, tag, builderName) | |
| 1037 if not self.allBoxes.get(builderName): | |
| 1038 self.allBoxes[builderName] = {} | |
| 1039 | |
| 1040 @@ -467,6 +468,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 1041 s["color"] = "notstarted" | |
| 1042 s["pageTitle"] = builder | |
| 1043 s["url"] = "./builders/%s" % urllib.quote(builder) | |
| 1044 + s["builderName"] = builder | |
| 1045 state, builds = status.getBuilder(builder).getState() | |
| 1046 # Check if it's offline, if so, the box is purple. | |
| 1047 if state == "offline": | |
| 1048 @@ -521,6 +523,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 1049 b["pageTitle"] = cached_value.pageTitle | |
| 1050 b["color"] = cached_value.color | |
| 1051 b["tag"] = cached_value.tag | |
| 1052 + b["builderName"] = cached_value.builderName | |
| 1053 | |
| 1054 builds[category].append(b) | |
| 1055 | |
| 1056 @@ -577,6 +580,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 1057 b["pageTitle"] = pageTitle | |
| 1058 b["color"] = resultsClass | |
| 1059 b["tag"] = tag | |
| 1060 + b["builderName"] = builder | |
| 1061 | |
| 1062 builds[category].append(b) | |
| 1063 | |
| 1064 | |
| 1065 | |
| 1066 | |
| 1067 Re-add support for asHTML to Buildbot (used by ChromiumNotifier). | |
| 1068 | |
| 1069 Index: buildbot/changes/changes.py | |
| 1070 =================================================================== | |
| 1071 --- buildbot/changes/changes.py | |
| 1072 +++ buildbot/changes/changes.py | |
| 1073 @@ -25,6 +25,25 @@ from buildbot.util import datetime2epoch | |
| 1074 from buildbot import interfaces, util | |
| 1075 from buildbot.process.properties import Properties | |
| 1076 | |
| 1077 +html_tmpl = """ | |
| 1078 +<p>Changed by: <b>%(who)s</b><br /> | |
| 1079 +Changed at: <b>%(at)s</b><br /> | |
| 1080 +%(repository)s | |
| 1081 +%(branch)s | |
| 1082 +%(revision)s | |
| 1083 +<br /> | |
| 1084 + | |
| 1085 +Changed files: | |
| 1086 +%(files)s | |
| 1087 + | |
| 1088 +Comments: | |
| 1089 +%(comments)s | |
| 1090 + | |
| 1091 +Properties: | |
| 1092 +%(properties)s | |
| 1093 +</p> | |
| 1094 +""" | |
| 1095 + | |
| 1096 class Change: | |
| 1097 """I represent a single change to the source tree. This may involve several | |
| 1098 files, but they are all changed by the same person, and there is a change | |
| 1099 @@ -181,6 +200,47 @@ class Change: | |
| 1100 result['project'] = getattr(self, 'project', None) | |
| 1101 return result | |
| 1102 | |
| 1103 + def asHTML(self): | |
| 1104 + info = self.asDict() | |
| 1105 + links = [] | |
| 1106 + for file in info['files']: | |
| 1107 + if file['url'] is not None: | |
| 1108 + # could get confused | |
| 1109 + links.append('<a href="%s"><b>%s</b></a>' % (file['url'], file[
'name'])) | |
| 1110 + else: | |
| 1111 + links.append('<b>%s</b>' % file['name']) | |
| 1112 + if info['revision']: | |
| 1113 + if getattr(self, 'revlink', ""): | |
| 1114 + revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % ( | |
| 1115 + info['revlink'], info['revision']) | |
| 1116 + else: | |
| 1117 + revision = "Revision: <b>%s</b><br />\n" % info['revision'] | |
| 1118 + else: | |
| 1119 + revision = '' | |
| 1120 + | |
| 1121 + if self.repository: | |
| 1122 + repository = "Repository: <b>%s</b><br />\n" % info['repository'] | |
| 1123 + else: | |
| 1124 + repository = '' | |
| 1125 + | |
| 1126 + branch = "" | |
| 1127 + if info['branch']: | |
| 1128 + branch = "Branch: <b>%s</b><br />\n" % info['branch'] | |
| 1129 + | |
| 1130 + properties = [] | |
| 1131 + for prop in info['properties']: | |
| 1132 + properties.append("%s: %s<br />" % (prop[0], prop[1])) | |
| 1133 + | |
| 1134 + kwargs = { 'who' : html.escape(info['who']), | |
| 1135 + 'at' : info['at'], | |
| 1136 + 'files' : html.UL(links) + '\n', | |
| 1137 + 'repository': repository, | |
| 1138 + 'revision' : revision, | |
| 1139 + 'branch' : branch, | |
| 1140 + 'comments' : html.PRE(info['comments']), | |
| 1141 + 'properties': html.UL(properties) + '\n' } | |
| 1142 + return html_tmpl % kwargs | |
| 1143 + | |
| 1144 def getShortAuthor(self): | |
| 1145 return self.who | |
| 1146 | |
| 1147 | |
| 1148 | |
| 1149 | |
| 1150 Add running_failure build status. | |
| 1151 | |
| 1152 Index: third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 1153 =================================================================== | |
| 1154 --- third_party/buildbot_8_4p1/buildbot/status/web/console.py (revision 105249
) | |
| 1155 +++ third_party/buildbot_8_4p1/buildbot/status/web/console.py (working copy) | |
| 1156 @@ -25,11 +25,27 @@ | |
| 1157 | |
| 1158 class DoesNotPassFilter(Exception): pass # Used for filtering revs | |
| 1159 | |
| 1160 -def getResultsClass(results, prevResults, inProgress): | |
| 1161 +def isBuildGoingToFail(build): | |
| 1162 + """Returns True if one of the step in the running build has failed.""" | |
| 1163 + for step in build.getSteps(): | |
| 1164 + if step.getResults()[0] == builder.FAILURE: | |
| 1165 + return True | |
| 1166 + return False | |
| 1167 + | |
| 1168 +def getInProgressResults(build): | |
| 1169 + """Returns build status expectation for an incomplete build.""" | |
| 1170 + if not build.isFinished() and isBuildGoingToFail(build): | |
| 1171 + return builder.FAILURE | |
| 1172 + | |
| 1173 + return build.getResults() | |
| 1174 + | |
| 1175 +def getResultsClass(results, prevResults, inProgress, inProgressResults=None): | |
| 1176 """Given the current and past results, return the class that will be used | |
| 1177 by the css to display the right color for a box.""" | |
| 1178 | |
| 1179 if inProgress: | |
| 1180 + if inProgressResults == builder.FAILURE: | |
| 1181 + return "running_failure" | |
| 1182 return "running" | |
| 1183 | |
| 1184 if results is None: | |
| 1185 @@ -139,7 +155,7 @@ | |
| 1186 class DevBuild: | |
| 1187 """Helper class that contains all the information we need for a build.""" | |
| 1188 | |
| 1189 - def __init__(self, revision, build, details): | |
| 1190 + def __init__(self, revision, build, details, inProgressResults=None): | |
| 1191 self.revision = revision | |
| 1192 self.results = build.getResults() | |
| 1193 self.number = build.getNumber() | |
| 1194 @@ -149,6 +165,7 @@ | |
| 1195 self.details = details | |
| 1196 self.when = build.getTimes()[0] | |
| 1197 self.source = build.getSourceStamp() | |
| 1198 + self.inProgressResults = inProgressResults | |
| 1199 | |
| 1200 | |
| 1201 class ConsoleStatusResource(HtmlResource): | |
| 1202 @@ -331,7 +348,8 @@ | |
| 1203 # user that his change might have broken the source update. | |
| 1204 if got_rev and got_rev != -1: | |
| 1205 details = self.getBuildDetails(request, builderName, build) | |
| 1206 - devBuild = DevBuild(got_rev, build, details) | |
| 1207 + devBuild = DevBuild(got_rev, build, details, | |
| 1208 + getInProgressResults(build)) | |
| 1209 builds.append(devBuild) | |
| 1210 | |
| 1211 # Now break if we have enough builds. | |
| 1212 @@ -543,9 +561,11 @@ | |
| 1213 # Get the results of the first build with the revision, and the | |
| 1214 # first build that does not include the revision. | |
| 1215 results = None | |
| 1216 + inProgressResults = None | |
| 1217 previousResults = None | |
| 1218 if introducedIn: | |
| 1219 results = introducedIn.results | |
| 1220 + inProgressResults = introducedIn.inProgressResults | |
| 1221 if firstNotIn: | |
| 1222 previousResults = firstNotIn.results | |
| 1223 | |
| 1224 @@ -573,7 +593,8 @@ | |
| 1225 if isRunning: | |
| 1226 pageTitle += ' ETA: %ds' % (introducedIn.eta or 0) | |
| 1227 | |
| 1228 - resultsClass = getResultsClass(results, previousResults, isRunn
ing) | |
| 1229 + resultsClass = getResultsClass(results, previousResults, isRunn
ing, | |
| 1230 + inProgressResults) | |
| 1231 | |
| 1232 b = {} | |
| 1233 b["url"] = url | |
| 1234 @@ -591,7 +612,8 @@ | |
| 1235 | |
| 1236 # Add this box to the cache if it's completed so we don't have | |
| 1237 # to compute it again. | |
| 1238 - if resultsClass not in ("running", "notstarted"): | |
| 1239 + if resultsClass not in ("running", "running_failure", | |
| 1240 + "notstarted"): | |
| 1241 debugInfo["added_blocks"] += 1 | |
| 1242 self.cache.insert(builder, revision.revision, resultsClass, | |
| 1243 pageTitle, current_details, url, tag) | |
| 1244 @@ -840,4 +862,3 @@ | |
| 1245 | |
| 1246 def getSortingKey(self): | |
| 1247 return operator.attrgetter('revision') | |
| 1248 - | |
| 1249 | |
| 1250 | |
| 1251 Add diagnostic info for an error seen frequently in the wild. | |
| 1252 | |
| 1253 Index: buildbot/db/buildsets.py | |
| 1254 =================================================================== | |
| 1255 --- buildbot/db/buildsets.py (revision 106949) | |
| 1256 +++ buildbot/db/buildsets.py (working copy) | |
| 1257 @@ -131,7 +131,9 @@ | |
| 1258 complete_at=_reactor.seconds()) | |
| 1259 | |
| 1260 if res.rowcount != 1: | |
| 1261 - raise KeyError | |
| 1262 + raise KeyError(('"SELECT * FROM buildsets WHERE id=%d AND ' | |
| 1263 + 'complete != 1;" returned %d rows') % ( | |
| 1264 + bsid, res.rowcount)) | |
| 1265 return self.db.pool.do(thd) | |
| 1266 | |
| 1267 def getBuildset(self, bsid): | |
| 1268 @@ -291,4 +293,3 @@ | |
| 1269 complete=bool(row.complete), | |
| 1270 complete_at=mkdt(row.complete_at), results=row.results, | |
| 1271 bsid=row.id) | |
| 1272 - | |
| 1273 | |
| 1274 | |
| 1275 Workaround incomplete Change object. | |
| 1276 | |
| 1277 --- buildbot/status/web/waterfall.py | |
| 1278 +++ buildbot/status/web/waterfall.py | |
| 1279 @@ -320,7 +320,7 @@ class ChangeEventSource(object): | |
| 1280 continue | |
| 1281 if categories and change.category not in categories: | |
| 1282 continue | |
| 1283 - if committers and change.author not in committers: | |
| 1284 + if committers and change.who not in committers: | |
| 1285 continue | |
| 1286 if minTime and change.when < minTime: | |
| 1287 continue | |
| 1288 | |
| 1289 | |
| 1290 Add back support for patch_subdir. | |
| 1291 | |
| 1292 --- buildbot/sourcestamp.py | |
| 1293 +++ buildbot/sourcestamp.py | |
| 1294 @@ -100,8 +100,8 @@ class SourceStamp(util.ComparableMixin, styles.Versioned): | |
| 1295 | |
| 1296 sourcestamp.patch = None | |
| 1297 if ssdict['patch_body']: | |
| 1298 - # note that this class does not store the patch_subdir | |
| 1299 - sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body']) | |
| 1300 + sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body'], | |
| 1301 + ssdict.get('patch_subdir')) | |
| 1302 | |
| 1303 if ssdict['changeids']: | |
| 1304 # sort the changeids in order, oldest to newest | |
| 1305 @@ -129,7 +129,7 @@ class SourceStamp(util.ComparableMixin, styles.Versioned): | |
| 1306 return | |
| 1307 | |
| 1308 if patch is not None: | |
| 1309 - assert len(patch) == 2 | |
| 1310 + assert 2 <= len(patch) <= 3 | |
| 1311 assert int(patch[0]) != -1 | |
| 1312 self.branch = branch | |
| 1313 self.patch = patch | |
| 1314 @@ -257,13 +257,17 @@ class SourceStamp(util.ComparableMixin, styles.Versioned): | |
| 1315 # add it to the DB | |
| 1316 patch_body = None | |
| 1317 patch_level = None | |
| 1318 + patch_subdir = None | |
| 1319 if self.patch: | |
| 1320 - patch_level, patch_body = self.patch | |
| 1321 + patch_level = self.patch[0] | |
| 1322 + patch_body = self.patch[1] | |
| 1323 + if len(self.patch) > 2: | |
| 1324 + patch_subdir = self.patch[2] | |
| 1325 d = master.db.sourcestamps.addSourceStamp( | |
| 1326 branch=self.branch, revision=self.revision, | |
| 1327 repository=self.repository, project=self.project, | |
| 1328 patch_body=patch_body, patch_level=patch_level, | |
| 1329 - patch_subdir=None, changeids=[c.number for c in self.changes]) | |
| 1330 + patch_subdir=patch_subdir, changeids=[c.number for c in self.ch
anges]) | |
| 1331 def set_ssid(ssid): | |
| 1332 self.ssid = ssid | |
| 1333 return ssid | |
| 1334 | |
| 1335 | |
| 1336 Changes with a patch must never be merged. | |
| 1337 | |
| 1338 --- buildbot/sourcestamp.py | |
| 1339 +++ buildbot/sourcestamp.py | |
| 1340 @@ -162,6 +162,8 @@ class SourceStamp(util.ComparableMixin, styles.Versioned): | |
| 1341 return False # the builds are completely unrelated | |
| 1342 if other.project != self.project: | |
| 1343 return False | |
| 1344 + if self.patch or other.patch: | |
| 1345 + return False # you can't merge patched builds with anything | |
| 1346 | |
| 1347 if self.changes and other.changes: | |
| 1348 return True | |
| 1349 @@ -170,8 +172,6 @@ class SourceStamp(util.ComparableMixin, styles.Versioned): | |
| 1350 elif not self.changes and other.changes: | |
| 1351 return False # they're using changes, we aren't | |
| 1352 | |
| 1353 - if self.patch or other.patch: | |
| 1354 - return False # you can't merge patched builds with anything | |
| 1355 if self.revision == other.revision: | |
| 1356 # both builds are using the same specific revision, so they can | |
| 1357 # be merged. It might be the case that revision==None, so they're | |
| 1358 | |
| 1359 | |
| 1360 Add back 'reason' to json/builders/<builder>/pendingBuilds. It is necessary for | |
| 1361 the commit queue. | |
| 1362 | |
| 1363 --- buildbot/status/buildrequest.py | |
| 1364 +++ buildbot/status/buildrequest.py | |
| 1365 @@ -143,10 +143,12 @@ class BuildRequestStatus: | |
| 1366 result = {} | |
| 1367 | |
| 1368 wfd = defer.waitForDeferred( | |
| 1369 - self.getSourceStamp()) | |
| 1370 + self._getBuildRequest()) | |
| 1371 yield wfd | |
| 1372 - ss = wfd.getResult() | |
| 1373 + br = wfd.getResult() | |
| 1374 + ss = br.source | |
| 1375 result['source'] = ss.asDict() | |
| 1376 + result['reason'] = br.reason | |
| 1377 | |
| 1378 result['builderName'] = self.getBuilderName() | |
| 1379 | |
| 1380 | |
| 1381 Fix exception on a race condition with slave disconnecting during a build trigge
r. | |
| 1382 | |
| 1383 --- third_party/buildbot_8_4p1/buildbot/process/slavebuilder.py | |
| 1384 +++ third_party/buildbot_8_4p1/buildbot/process/slavebuilder.py | |
| 1385 @@ -117,7 +117,7 @@ class AbstractSlaveBuilder(pb.Referenceable): | |
| 1386 return d | |
| 1387 | |
| 1388 def prepare(self, builder_status, build): | |
| 1389 - if not self.slave.acquireLocks(): | |
| 1390 + if not self.slave or not self.slave.acquireLocks(): | |
| 1391 return defer.succeed(False) | |
| 1392 return defer.succeed(True) | |
| 1393 | |
| 1394 | |
| 1395 Fix exception on a race condition with slave disconnecting during a build trigge
r. | |
| 1396 | |
| 1397 --- third_party/buildbot_8_4p1/buildbot/process/builder.py | |
| 1398 +++ third_party/buildbot_8_4p1/buildbot/process/builder.py | |
| 1399 @@ -439,7 +439,8 @@ class Builder(pb.Referenceable, service.MultiService): | |
| 1400 "request" % (build, slavebuilder)) | |
| 1401 | |
| 1402 self.building.remove(build) | |
| 1403 - slavebuilder.slave.releaseLocks() | |
| 1404 + if slavebuilder.slave: | |
| 1405 + slavebuilder.slave.releaseLocks() | |
| 1406 | |
| 1407 # release the buildrequest claims | |
| 1408 wfd = defer.waitForDeferred( | |
| 1409 | |
| 1410 | |
| 1411 Cherry-pick command list flattening functionality from upstream buildbot - | |
| 1412 726e9f81c103939d22bd18d53ca65c66cfb8aed7. | |
| 1413 | |
| 1414 --- buildbot/steps/shell.py | |
| 1415 +++ buildbot/steps/shell.py | |
| 1416 @@ -222,6 +222,13 @@ | |
| 1417 # now prevent setupLogfiles() from adding them | |
| 1418 self.logfiles = {} | |
| 1419 | |
| 1420 + def _flattenList(self, mainlist, commands): | |
| 1421 + for x in commands: | |
| 1422 + if isinstance(x, (str, unicode)): | |
| 1423 + mainlist.append(x) | |
| 1424 + elif x != []: | |
| 1425 + self._flattenList(mainlist, x) | |
| 1426 + | |
| 1427 def start(self): | |
| 1428 # this block is specific to ShellCommands. subclasses that don't need | |
| 1429 # to set up an argv array, an environment, or extra logfiles= (like | |
| 1430 @@ -231,8 +238,13 @@ | |
| 1431 | |
| 1432 # create the actual RemoteShellCommand instance now | |
| 1433 kwargs = self.remote_kwargs | |
| 1434 - command = self.command | |
| 1435 - kwargs['command'] = command | |
| 1436 + tmp = [] | |
| 1437 + if isinstance(self.command, list): | |
| 1438 + self._flattenList(tmp, self.command) | |
| 1439 + else: | |
| 1440 + tmp = self.command | |
| 1441 + | |
| 1442 + kwargs['command'] = tmp | |
| 1443 kwargs['logfiles'] = self.logfiles | |
| 1444 | |
| 1445 # check for the usePTY flag | |
| 1446 | |
| 1447 | |
| 1448 Replace the simple (and very slow) LRU cache implementation in BuilderStatus | |
| 1449 with a better one. Added SyncLRUCache to avoid the expensive concurrency | |
| 1450 protection in AsyncLRUCache. | |
| 1451 | |
| 1452 Index: buildbot/status/builder.py | |
| 1453 =================================================================== | |
| 1454 --- buildbot/status/builder.py (revision 127129) | |
| 1455 +++ buildbot/status/builder.py (working copy) | |
| 1456 @@ -86,8 +86,8 @@ | |
| 1457 self.currentBuilds = [] | |
| 1458 self.nextBuild = None | |
| 1459 self.watchers = [] | |
| 1460 - self.buildCache = weakref.WeakValueDictionary() | |
| 1461 - self.buildCache_LRU = [] | |
| 1462 + self.buildCache = util.lru.SyncLRUCache(self.cacheMiss, | |
| 1463 + self.buildCacheSize) | |
| 1464 self.logCompressionLimit = False # default to no compression for tests | |
| 1465 self.logCompressionMethod = "bz2" | |
| 1466 self.logMaxSize = None # No default limit | |
| 1467 @@ -103,7 +103,6 @@ | |
| 1468 d = styles.Versioned.__getstate__(self) | |
| 1469 d['watchers'] = [] | |
| 1470 del d['buildCache'] | |
| 1471 - del d['buildCache_LRU'] | |
| 1472 for b in self.currentBuilds: | |
| 1473 b.saveYourself() | |
| 1474 # TODO: push a 'hey, build was interrupted' event | |
| 1475 @@ -119,8 +118,8 @@ | |
| 1476 # when loading, re-initialize the transient stuff. Remember that | |
| 1477 # upgradeToVersion1 and such will be called after this finishes. | |
| 1478 styles.Versioned.__setstate__(self, d) | |
| 1479 - self.buildCache = weakref.WeakValueDictionary() | |
| 1480 - self.buildCache_LRU = [] | |
| 1481 + self.buildCache = util.lru.SyncLRUCache(self.cacheMiss, | |
| 1482 + self.buildCacheSize) | |
| 1483 self.currentBuilds = [] | |
| 1484 self.watchers = [] | |
| 1485 self.slavenames = [] | |
| 1486 @@ -132,6 +131,7 @@ | |
| 1487 # gets pickled and unpickled. | |
| 1488 if buildmaster.buildCacheSize is not None: | |
| 1489 self.buildCacheSize = buildmaster.buildCacheSize | |
| 1490 + self.buildCache.set_max_size(buildmaster.buildCacheSize) | |
| 1491 | |
| 1492 def upgradeToVersion1(self): | |
| 1493 if hasattr(self, 'slavename'): | |
| 1494 @@ -186,33 +186,17 @@ | |
| 1495 except: | |
| 1496 log.msg("unable to save builder %s" % self.name) | |
| 1497 log.err() | |
| 1498 - | |
| 1499 | |
| 1500 + | |
| 1501 # build cache management | |
| 1502 | |
| 1503 def makeBuildFilename(self, number): | |
| 1504 return os.path.join(self.basedir, "%d" % number) | |
| 1505 | |
| 1506 - def touchBuildCache(self, build): | |
| 1507 - self.buildCache[build.number] = build | |
| 1508 - if build in self.buildCache_LRU: | |
| 1509 - self.buildCache_LRU.remove(build) | |
| 1510 - self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] +
[ build ] | |
| 1511 - return build | |
| 1512 - | |
| 1513 def getBuildByNumber(self, number): | |
| 1514 - # first look in currentBuilds | |
| 1515 - for b in self.currentBuilds: | |
| 1516 - if b.number == number: | |
| 1517 - return self.touchBuildCache(b) | |
| 1518 + return self.buildCache.get(number) | |
| 1519 | |
| 1520 - # then in the buildCache | |
| 1521 - if number in self.buildCache: | |
| 1522 - metrics.MetricCountEvent.log("buildCache.hits", 1) | |
| 1523 - return self.touchBuildCache(self.buildCache[number]) | |
| 1524 - metrics.MetricCountEvent.log("buildCache.misses", 1) | |
| 1525 - | |
| 1526 - # then fall back to loading it from disk | |
| 1527 + def loadBuildFromFile(self, number): | |
| 1528 filename = self.makeBuildFilename(number) | |
| 1529 try: | |
| 1530 log.msg("Loading builder %s's build %d from on-disk pickle" | |
| 1531 @@ -235,12 +219,20 @@ | |
| 1532 build.upgradeLogfiles() | |
| 1533 # check that logfiles exist | |
| 1534 build.checkLogfiles() | |
| 1535 - return self.touchBuildCache(build) | |
| 1536 + return build | |
| 1537 except IOError: | |
| 1538 raise IndexError("no such build %d" % number) | |
| 1539 except EOFError: | |
| 1540 raise IndexError("corrupted build pickle %d" % number) | |
| 1541 | |
| 1542 + def cacheMiss(self, number): | |
| 1543 + # first look in currentBuilds | |
| 1544 + for b in self.currentBuilds: | |
| 1545 + if b.number == number: | |
| 1546 + return b | |
| 1547 + # then fall back to loading it from disk | |
| 1548 + return self.loadBuildFromFile(number) | |
| 1549 + | |
| 1550 def prune(self, events_only=False): | |
| 1551 # begin by pruning our own events | |
| 1552 self.events = self.events[-self.eventHorizon:] | |
| 1553 @@ -287,7 +279,7 @@ | |
| 1554 is_logfile = True | |
| 1555 | |
| 1556 if num is None: continue | |
| 1557 - if num in self.buildCache: continue | |
| 1558 + if num in self.buildCache.cache: continue | |
| 1559 | |
| 1560 if (is_logfile and num < earliest_log) or num < earliest_build: | |
| 1561 pathname = os.path.join(self.basedir, filename) | |
| 1562 @@ -510,7 +502,7 @@ | |
| 1563 assert s.number == self.nextBuildNumber - 1 | |
| 1564 assert s not in self.currentBuilds | |
| 1565 self.currentBuilds.append(s) | |
| 1566 - self.touchBuildCache(s) | |
| 1567 + self.buildCache.put(s.number, s) | |
| 1568 | |
| 1569 # now that the BuildStatus is prepared to answer queries, we can | |
| 1570 # announce the new build to all our watchers | |
| 1571 @@ -620,7 +612,7 @@ | |
| 1572 # Collect build numbers. | |
| 1573 # Important: Only grab the *cached* builds numbers to reduce I/O. | |
| 1574 current_builds = [b.getNumber() for b in self.currentBuilds] | |
| 1575 - cached_builds = list(set(self.buildCache.keys() + current_builds)) | |
| 1576 + cached_builds = list(set(self.buildCache.cache.keys() + current_builds)
) | |
| 1577 cached_builds.sort() | |
| 1578 result['cachedBuilds'] = cached_builds | |
| 1579 result['currentBuilds'] = current_builds | |
| 1580 Index: buildbot/util/lru.py | |
| 1581 =================================================================== | |
| 1582 --- buildbot/util/lru.py (revision 127129) | |
| 1583 +++ buildbot/util/lru.py (working copy) | |
| 1584 @@ -244,5 +244,82 @@ | |
| 1585 log.msg(" got:", sorted(self.refcount.items())) | |
| 1586 inv_failed = True | |
| 1587 | |
| 1588 + | |
| 1589 +class SyncLRUCache(AsyncLRUCache): | |
| 1590 + """ | |
| 1591 + | |
| 1592 + A least-recently-used cache using the same strategy as AsyncLRUCache, | |
| 1593 + minus the protections for concurrent access. The motivation for this | |
| 1594 + class is to provide a speedier implementation for heavily-used caches | |
| 1595 + that don't need the concurrency protections. | |
| 1596 + | |
| 1597 + The constructor takes the same arguments as the AsyncLRUCache | |
| 1598 + constructor, except C{miss_fn} must return the missing value, I{not} a | |
| 1599 + deferred. | |
| 1600 + """ | |
| 1601 + | |
| 1602 + # utility function to record recent use of this key | |
| 1603 + def _ref_key(key): | |
| 1604 + refcount = self.refcount | |
| 1605 + queue = self.queue | |
| 1606 + | |
| 1607 + queue.append(key) | |
| 1608 + refcount[key] = refcount[key] + 1 | |
| 1609 + | |
| 1610 + # periodically compact the queue by eliminating duplicate keys | |
| 1611 + # while preserving order of most recent access. Note that this | |
| 1612 + # is only required when the cache does not exceed its maximum | |
| 1613 + # size | |
| 1614 + if len(queue) > self.max_queue: | |
| 1615 + refcount.clear() | |
| 1616 + queue_appendleft = queue.appendleft | |
| 1617 + queue_appendleft(self.sentinel) | |
| 1618 + for k in ifilterfalse(refcount.__contains__, | |
| 1619 + iter(queue.pop, self.sentinel)): | |
| 1620 + queue_appendleft(k) | |
| 1621 + refcount[k] = 1 | |
| 1622 + | |
| 1623 + def get(self, key, **miss_fn_kwargs): | |
| 1624 + """ | |
| 1625 + Fetch a value from the cache by key, invoking C{self.miss_fn(key)} if | |
| 1626 + the key is not in the cache. | |
| 1627 + | |
| 1628 + No protection is provided against concurrent access. | |
| 1629 + | |
| 1630 + @param key: cache key | |
| 1631 + @param **miss_fn_kwargs: keyword arguments to the miss_fn | |
| 1632 + @returns: cache value | |
| 1633 + """ | |
| 1634 + cache = self.cache | |
| 1635 + weakrefs = self.weakrefs | |
| 1636 + | |
| 1637 + try: | |
| 1638 + result = cache[key] | |
| 1639 + self.hits += 1 | |
| 1640 + self._ref_key(key) | |
| 1641 + return result | |
| 1642 + except KeyError: | |
| 1643 + try: | |
| 1644 + result = weakrefs[key] | |
| 1645 + self.refhits += 1 | |
| 1646 + cache[key] = result | |
| 1647 + self._ref_key(key) | |
| 1648 + return result | |
| 1649 + except KeyError: | |
| 1650 + pass | |
| 1651 + | |
| 1652 + # if we're here, we've missed and need to fetch | |
| 1653 + self.misses += 1 | |
| 1654 + | |
| 1655 + result = self.miss_fn(key, **miss_fn_kwargs) | |
| 1656 + if result is not None: | |
| 1657 + cache[key] = result | |
| 1658 + weakrefs[key] = result | |
| 1659 + self._ref_key(key) | |
| 1660 + self._purge() | |
| 1661 + | |
| 1662 + return result | |
| 1663 + | |
| 1664 + | |
| 1665 # for tests | |
| 1666 inv_failed = False | |
| 1667 Index: buildbot/test/unit/test_status_builder_cache.py | |
| 1668 =================================================================== | |
| 1669 --- buildbot/test/unit/test_status_builder_cache.py (revision 0) | |
| 1670 +++ buildbot/test/unit/test_status_builder_cache.py (revision 0) | |
| 1671 @@ -0,0 +1,60 @@ | |
| 1672 +# This file is part of Buildbot. Buildbot is free software: you can | |
| 1673 +# redistribute it and/or modify it under the terms of the GNU General Public | |
| 1674 +# License as published by the Free Software Foundation, version 2. | |
| 1675 +# | |
| 1676 +# This program is distributed in the hope that it will be useful, but WITHOUT | |
| 1677 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 1678 +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
| 1679 +# details. | |
| 1680 +# | |
| 1681 +# You should have received a copy of the GNU General Public License along with | |
| 1682 +# this program; if not, write to the Free Software Foundation, Inc., 51 | |
| 1683 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 1684 +# | |
| 1685 +# Copyright Buildbot Team Members | |
| 1686 + | |
| 1687 +import os | |
| 1688 +from mock import Mock | |
| 1689 +from twisted.trial import unittest | |
| 1690 +from buildbot.status import builder, master | |
| 1691 + | |
| 1692 +class TestBuildStatus(unittest.TestCase): | |
| 1693 + | |
| 1694 + # that buildstep.BuildStepStatus is never instantiated here should tell you | |
| 1695 + # that these classes are not well isolated! | |
| 1696 + | |
| 1697 + def setupBuilder(self, buildername, category=None): | |
| 1698 + b = builder.BuilderStatus(buildername=buildername, category=category) | |
| 1699 + # Ackwardly, Status sets this member variable. | |
| 1700 + b.basedir = os.path.abspath(self.mktemp()) | |
| 1701 + os.mkdir(b.basedir) | |
| 1702 + # Otherwise, builder.nextBuildNumber is not defined. | |
| 1703 + b.determineNextBuildNumber() | |
| 1704 + # Must initialize these fields before pickling. | |
| 1705 + b.currentBigState = 'idle' | |
| 1706 + b.status = 'idle' | |
| 1707 + return b | |
| 1708 + | |
| 1709 + def setupStatus(self, b): | |
| 1710 + m = Mock() | |
| 1711 + m.buildbotURL = 'http://buildbot:8010/' | |
| 1712 + m.basedir = '/basedir' | |
| 1713 + s = master.Status(m) | |
| 1714 + b.status = s | |
| 1715 + return s | |
| 1716 + | |
| 1717 + def testBuildCache(self): | |
| 1718 + b = self.setupBuilder('builder_1') | |
| 1719 + builds = [] | |
| 1720 + for i in xrange(5): | |
| 1721 + build = b.newBuild() | |
| 1722 + build.setProperty('propkey', 'propval%d' % i, 'test') | |
| 1723 + builds.append(build) | |
| 1724 + build.buildStarted(build) | |
| 1725 + build.buildFinished() | |
| 1726 + for build in builds: | |
| 1727 + build2 = b.getBuild(build.number) | |
| 1728 + self.assertTrue(build2) | |
| 1729 + self.assertEqual(build2.number, build.number) | |
| 1730 + self.assertEqual(build2.getProperty('propkey'), | |
| 1731 + 'propval%d' % build.number) | |
| 1732 + # Do another round, to make sure we're hitting the cache | |
| 1733 + hits = b.buildCache.hits | |
| 1734 + for build in builds: | |
| 1735 + build2 = b.getBuild(build.number) | |
| 1736 + self.assertTrue(build2) | |
| 1737 + self.assertEqual(build2.number, build.number) | |
| 1738 + self.assertEqual(build2.getProperty('propkey'), | |
| 1739 + 'propval%d' % build.number) | |
| 1740 + self.assertEqual(b.buildCache.hits, hits+1) | |
| 1741 + hits = hits + 1 | |
| 1742 | |
| 1743 Import patch from buildbot trunk to remove unnecessary calls to | |
| 1744 gc.collect, which cause synchronous hangs in the master. | |
| 1745 | |
| 1746 Index: buildbot/status/builder.py | |
| 1747 =================================================================== | |
| 1748 --- buildbot/status/builder.py (revision 128258) | |
| 1749 +++ buildbot/status/builder.py (working copy) | |
| 1750 @@ -239,8 +239,6 @@ | |
| 1751 if events_only: | |
| 1752 return | |
| 1753 | |
| 1754 - gc.collect() | |
| 1755 - | |
| 1756 # get the horizons straight | |
| 1757 if self.buildHorizon is not None: | |
| 1758 earliest_build = self.nextBuildNumber - self.buildHorizon | |
| 1759 | |
| 1760 | |
| 1761 | |
| 1762 Add a handler for the signal USR1 that cancels all new builds. | |
| 1763 | |
| 1764 Index: buildbot/master.py | |
| 1765 --- buildbot/master.py | |
| 1766 +++ buildbot/master.py | |
| 1767 @@ -161,6 +161,8 @@ class BuildMaster(service.MultiService): | |
| 1768 self.loadTheConfigFile() | |
| 1769 if hasattr(signal, "SIGHUP"): | |
| 1770 signal.signal(signal.SIGHUP, self._handleSIGHUP) | |
| 1771 + if hasattr(signal, "SIGUSR1"): | |
| 1772 + signal.signal(signal.SIGUSR1, self._handleSIGUSR1) | |
| 1773 for b in self.botmaster.builders.values(): | |
| 1774 b.builder_status.addPointEvent(["master", "started"]) | |
| 1775 b.builder_status.saveYourself() | |
| 1776 @@ -168,12 +170,51 @@ class BuildMaster(service.MultiService): | |
| 1777 def _handleSIGHUP(self, *args): | |
| 1778 reactor.callLater(0, self.loadTheConfigFile) | |
| 1779 | |
| 1780 + def _handleSIGUSR1(self, *args): | |
| 1781 + reactor.callLater(0, self.noNewBuilds) | |
| 1782 + | |
| 1783 def getStatus(self): | |
| 1784 """ | |
| 1785 @rtype: L{buildbot.status.builder.Status} | |
| 1786 """ | |
| 1787 return self.status | |
| 1788 | |
| 1789 + @defer.deferredGenerator | |
| 1790 + def cancelAllPendingBuilds(self): | |
| 1791 + log.msg("canceling pending builds") | |
| 1792 + c = interfaces.IControl(self) | |
| 1793 + for bname in self.botmaster.builders: | |
| 1794 + builder_control = c.getBuilder(bname) | |
| 1795 + wfd = defer.waitForDeferred( | |
| 1796 + builder_control.getPendingBuildRequestControls()) | |
| 1797 + yield wfd | |
| 1798 + brcontrols = wfd.getResult() | |
| 1799 + build_controls = dict((x.brid, x) for x in brcontrols) | |
| 1800 + builder_status = self.status.getBuilder(bname) | |
| 1801 + wfd = defer.waitForDeferred( | |
| 1802 + builder_status.getPendingBuildRequestStatuses()) | |
| 1803 + yield wfd | |
| 1804 + build_req_statuses = wfd.getResult() | |
| 1805 + number_cancelled_builds = 0 | |
| 1806 + for build_req in build_req_statuses: | |
| 1807 + control = build_controls[build_req.brid] | |
| 1808 + control.cancel() | |
| 1809 + number_cancelled_builds += 1 | |
| 1810 + log.msg("builder '%s' cancelled %d pending builds" % ( | |
| 1811 + bname, number_cancelled_builds)) | |
| 1812 + | |
| 1813 + def noNewBuilds(self): | |
| 1814 + log.msg("stopping schedulers") | |
| 1815 + self.loadConfig_Schedulers([]) | |
| 1816 + log.msg("stopping sources") | |
| 1817 + self.loadConfig_Sources([]) | |
| 1818 + d = self.cancelAllPendingBuilds() | |
| 1819 + def doneStopping(res): | |
| 1820 + log.msg("new builds stopped") | |
| 1821 + return res | |
| 1822 + d.addCallback(doneStopping) | |
| 1823 + return d | |
| 1824 + | |
| 1825 def loadTheConfigFile(self, configFile=None): | |
| 1826 if not configFile: | |
| 1827 configFile = os.path.join(self.basedir, self.configFileName) | |
| 1828 | |
| 1829 | |
| 1830 Use weakrefs to avoid circular references between status objects. | |
| 1831 | |
| 1832 http://codereview.chromium.org/9991001/ | |
| 1833 | |
| 1834 Index: buildbot/status/master.py | |
| 1835 =================================================================== | |
| 1836 --- buildbot/status/master.py (revision 132430) | |
| 1837 +++ buildbot/status/master.py (working copy) | |
| 1838 @@ -124,7 +124,7 @@ | |
| 1839 | |
| 1840 logs = step.getLogs() | |
| 1841 for i in range(len(logs)): | |
| 1842 - if loog is logs[i]: | |
| 1843 + if loog.getName() == logs[i].getName(): | |
| 1844 break | |
| 1845 else: | |
| 1846 return None | |
| 1847 Index: buildbot/status/buildstep.py | |
| 1848 =================================================================== | |
| 1849 --- buildbot/status/buildstep.py (revision 132430) | |
| 1850 +++ buildbot/status/buildstep.py (working copy) | |
| 1851 @@ -13,7 +13,7 @@ | |
| 1852 # | |
| 1853 # Copyright Buildbot Team Members | |
| 1854 | |
| 1855 -import os | |
| 1856 +import os, weakref | |
| 1857 from zope.interface import implements | |
| 1858 from twisted.persisted import styles | |
| 1859 from twisted.python import log | |
| 1860 @@ -63,7 +63,9 @@ | |
| 1861 | |
| 1862 def __init__(self, parent, step_number): | |
| 1863 assert interfaces.IBuildStatus(parent) | |
| 1864 - self.build = parent | |
| 1865 + self.build = weakref.ref(parent) | |
| 1866 + self.build_number = parent.getNumber() | |
| 1867 + self.builder = parent.getBuilder() | |
| 1868 self.step_number = step_number | |
| 1869 self.logs = [] | |
| 1870 self.urls = {} | |
| 1871 @@ -81,7 +83,12 @@ | |
| 1872 return self.name | |
| 1873 | |
| 1874 def getBuild(self): | |
| 1875 - return self.build | |
| 1876 + result = self.build() | |
| 1877 + if result is not None: | |
| 1878 + return result | |
| 1879 + result = self.builder.getBuildByNumber(self.build_number) | |
| 1880 + self.build = weakref.ref(result) | |
| 1881 + return result | |
| 1882 | |
| 1883 def getTimes(self): | |
| 1884 return (self.started, self.finished) | |
| 1885 @@ -179,7 +186,7 @@ | |
| 1886 def sendETAUpdate(self, receiver, updateInterval): | |
| 1887 self.updates[receiver] = None | |
| 1888 # they might unsubscribe during stepETAUpdate | |
| 1889 - receiver.stepETAUpdate(self.build, self, | |
| 1890 + receiver.stepETAUpdate(self.getBuild(), self, | |
| 1891 self.getETA(), self.getExpectations()) | |
| 1892 if receiver in self.watchers: | |
| 1893 self.updates[receiver] = reactor.callLater(updateInterval, | |
| 1894 @@ -209,19 +216,21 @@ | |
| 1895 | |
| 1896 def stepStarted(self): | |
| 1897 self.started = util.now() | |
| 1898 - if self.build: | |
| 1899 - self.build.stepStarted(self) | |
| 1900 + build = self.getBuild() | |
| 1901 + if build: | |
| 1902 + build.stepStarted(self) | |
| 1903 | |
| 1904 def addLog(self, name): | |
| 1905 assert self.started # addLog before stepStarted won't notify watchers | |
| 1906 - logfilename = self.build.generateLogfileName(self.name, name) | |
| 1907 + build = self.getBuild() | |
| 1908 + logfilename = build.generateLogfileName(self.name, name) | |
| 1909 log = LogFile(self, name, logfilename) | |
| 1910 - log.logMaxSize = self.build.builder.logMaxSize | |
| 1911 - log.logMaxTailSize = self.build.builder.logMaxTailSize | |
| 1912 - log.compressMethod = self.build.builder.logCompressionMethod | |
| 1913 + log.logMaxSize = self.builder.logMaxSize | |
| 1914 + log.logMaxTailSize = self.builder.logMaxTailSize | |
| 1915 + log.compressMethod = self.builder.logCompressionMethod | |
| 1916 self.logs.append(log) | |
| 1917 for w in self.watchers: | |
| 1918 - receiver = w.logStarted(self.build, self, log) | |
| 1919 + receiver = w.logStarted(build, self, log) | |
| 1920 if receiver: | |
| 1921 log.subscribe(receiver, True) | |
| 1922 d = log.waitUntilFinished() | |
| 1923 @@ -232,28 +241,32 @@ | |
| 1924 | |
| 1925 def addHTMLLog(self, name, html): | |
| 1926 assert self.started # addLog before stepStarted won't notify watchers | |
| 1927 - logfilename = self.build.generateLogfileName(self.name, name) | |
| 1928 + build = self.getBuild() | |
| 1929 + logfilename = build.generateLogfileName(self.name, name) | |
| 1930 log = HTMLLogFile(self, name, logfilename, html) | |
| 1931 self.logs.append(log) | |
| 1932 for w in self.watchers: | |
| 1933 - w.logStarted(self.build, self, log) | |
| 1934 - w.logFinished(self.build, self, log) | |
| 1935 + w.logStarted(build, self, log) | |
| 1936 + w.logFinished(build, self, log) | |
| 1937 | |
| 1938 def logFinished(self, log): | |
| 1939 + build = self.getBuild() | |
| 1940 for w in self.watchers: | |
| 1941 - w.logFinished(self.build, self, log) | |
| 1942 + w.logFinished(build, self, log) | |
| 1943 | |
| 1944 def addURL(self, name, url): | |
| 1945 self.urls[name] = url | |
| 1946 | |
| 1947 def setText(self, text): | |
| 1948 self.text = text | |
| 1949 + build = self.getBuild() | |
| 1950 for w in self.watchers: | |
| 1951 - w.stepTextChanged(self.build, self, text) | |
| 1952 + w.stepTextChanged(build, self, text) | |
| 1953 def setText2(self, text): | |
| 1954 self.text2 = text | |
| 1955 + build = self.getBuild() | |
| 1956 for w in self.watchers: | |
| 1957 - w.stepText2Changed(self.build, self, text) | |
| 1958 + w.stepText2Changed(build, self, text) | |
| 1959 | |
| 1960 def setStatistic(self, name, value): | |
| 1961 """Set the given statistic. Usually called by subclasses. | |
| 1962 @@ -267,7 +280,7 @@ | |
| 1963 self.finished = util.now() | |
| 1964 self.results = results | |
| 1965 cld = [] # deferreds for log compression | |
| 1966 - logCompressionLimit = self.build.builder.logCompressionLimit | |
| 1967 + logCompressionLimit = self.builder.logCompressionLimit | |
| 1968 for loog in self.logs: | |
| 1969 if not loog.isFinished(): | |
| 1970 loog.finish() | |
| 1971 @@ -307,6 +320,7 @@ | |
| 1972 def __getstate__(self): | |
| 1973 d = styles.Versioned.__getstate__(self) | |
| 1974 del d['build'] # filled in when loading | |
| 1975 + del d['builder'] # filled in when loading | |
| 1976 if d.has_key('progress'): | |
| 1977 del d['progress'] | |
| 1978 del d['watchers'] | |
| 1979 @@ -316,11 +330,11 @@ | |
| 1980 | |
| 1981 def __setstate__(self, d): | |
| 1982 styles.Versioned.__setstate__(self, d) | |
| 1983 - # self.build must be filled in by our parent | |
| 1984 + # self.build and self.builder must be filled in by our parent | |
| 1985 | |
| 1986 # point the logs to this object | |
| 1987 for loog in self.logs: | |
| 1988 - loog.step = self | |
| 1989 + loog.step = weakref.ref(self) | |
| 1990 self.watchers = [] | |
| 1991 self.finishedWatchers = [] | |
| 1992 self.updates = {} | |
| 1993 @@ -357,8 +371,6 @@ | |
| 1994 result['urls'] = self.getURLs() | |
| 1995 result['step_number'] = self.step_number | |
| 1996 result['logs'] = [[l.getName(), | |
| 1997 - self.build.builder.status.getURLForThing(l)] | |
| 1998 + self.builder.status.getURLForThing(l)] | |
| 1999 for l in self.getLogs()] | |
| 2000 return result | |
| 2001 - | |
| 2002 - | |
| 2003 Index: buildbot/status/build.py | |
| 2004 =================================================================== | |
| 2005 --- buildbot/status/build.py (revision 132430) | |
| 2006 +++ buildbot/status/build.py (working copy) | |
| 2007 @@ -13,7 +13,7 @@ | |
| 2008 # | |
| 2009 # Copyright Buildbot Team Members | |
| 2010 | |
| 2011 -import os, shutil, re | |
| 2012 +import os, shutil, re, weakref | |
| 2013 from cPickle import dump | |
| 2014 from zope.interface import implements | |
| 2015 from twisted.python import log, runtime | |
| 2016 @@ -361,7 +361,7 @@ | |
| 2017 styles.Versioned.__setstate__(self, d) | |
| 2018 # self.builder must be filled in by our parent when loading | |
| 2019 for step in self.steps: | |
| 2020 - step.build = self | |
| 2021 + step.build = weakref.ref(self) | |
| 2022 self.watchers = [] | |
| 2023 self.updates = {} | |
| 2024 self.finishedWatchers = [] | |
| 2025 @@ -459,6 +459,3 @@ | |
| 2026 else: | |
| 2027 result['currentStep'] = None | |
| 2028 return result | |
| 2029 - | |
| 2030 - | |
| 2031 - | |
| 2032 Index: buildbot/status/buildrequest.py | |
| 2033 =================================================================== | |
| 2034 --- buildbot/status/buildrequest.py (revision 132430) | |
| 2035 +++ buildbot/status/buildrequest.py (working copy) | |
| 2036 @@ -70,10 +70,6 @@ | |
| 2037 | |
| 2038 yield self._buildrequest | |
| 2039 | |
| 2040 - def buildStarted(self, build): | |
| 2041 - self.status._buildrequest_buildStarted(build.status) | |
| 2042 - self.builds.append(build.status) | |
| 2043 - | |
| 2044 # methods called by our clients | |
| 2045 @defer.deferredGenerator | |
| 2046 def getSourceStamp(self): | |
| 2047 Index: buildbot/status/logfile.py | |
| 2048 =================================================================== | |
| 2049 --- buildbot/status/logfile.py (revision 132430) | |
| 2050 +++ buildbot/status/logfile.py (working copy) | |
| 2051 @@ -13,7 +13,7 @@ | |
| 2052 # | |
| 2053 # Copyright Buildbot Team Members | |
| 2054 | |
| 2055 -import os | |
| 2056 +import os, weakref | |
| 2057 from cStringIO import StringIO | |
| 2058 from bz2 import BZ2File | |
| 2059 from gzip import GzipFile | |
| 2060 @@ -216,7 +216,10 @@ | |
| 2061 @type logfilename: string | |
| 2062 @param logfilename: the Builder-relative pathname for the saved entries | |
| 2063 """ | |
| 2064 - self.step = parent | |
| 2065 + self.step = weakref.ref(parent) | |
| 2066 + self.step_number = parent.step_number | |
| 2067 + self.build_number = parent.getBuild().getNumber() | |
| 2068 + self.builder = parent.builder | |
| 2069 self.name = name | |
| 2070 self.filename = logfilename | |
| 2071 fn = self.getFilename() | |
| 2072 @@ -236,7 +239,7 @@ | |
| 2073 self.tailBuffer = [] | |
| 2074 | |
| 2075 def getFilename(self): | |
| 2076 - return os.path.join(self.step.build.builder.basedir, self.filename) | |
| 2077 + return os.path.join(self.builder.basedir, self.filename) | |
| 2078 | |
| 2079 def hasContents(self): | |
| 2080 return os.path.exists(self.getFilename() + '.bz2') or \ | |
| 2081 @@ -247,7 +250,13 @@ | |
| 2082 return self.name | |
| 2083 | |
| 2084 def getStep(self): | |
| 2085 - return self.step | |
| 2086 + result = self.step() | |
| 2087 + if result is not None: | |
| 2088 + return result | |
| 2089 + build = self.builder.getBuildByNumber(self.build_number) | |
| 2090 + result = build.getSteps()[self.step_number] | |
| 2091 + self.step = weakref.ref(result) | |
| 2092 + return result | |
| 2093 | |
| 2094 def isFinished(self): | |
| 2095 return self.finished | |
| 2096 @@ -368,7 +377,7 @@ | |
| 2097 if catchup: | |
| 2098 for channel, text in self.getChunks(): | |
| 2099 # TODO: add logChunks(), to send over everything at once? | |
| 2100 - receiver.logChunk(self.step.build, self.step, self, | |
| 2101 + receiver.logChunk(self.getStep().getBuild(), self.getStep(), se
lf, | |
| 2102 channel, text) | |
| 2103 | |
| 2104 def unsubscribe(self, receiver): | |
| 2105 @@ -439,8 +448,10 @@ | |
| 2106 if self.runLength >= self.chunkSize: | |
| 2107 self.merge() | |
| 2108 | |
| 2109 + step = self.getStep() | |
| 2110 + build = step.getBuild() | |
| 2111 for w in self.watchers: | |
| 2112 - w.logChunk(self.step.build, self.step, self, channel, text) | |
| 2113 + w.logChunk(build, step, self, channel, text) | |
| 2114 self.length += len(text) | |
| 2115 | |
| 2116 def addStdout(self, text): | |
| 2117 @@ -526,6 +537,7 @@ | |
| 2118 def __getstate__(self): | |
| 2119 d = self.__dict__.copy() | |
| 2120 del d['step'] # filled in upon unpickling | |
| 2121 + del d['builder'] # filled in upon unpickling | |
| 2122 del d['watchers'] | |
| 2123 del d['finishedWatchers'] | |
| 2124 d['entries'] = [] # let 0.6.4 tolerate the saved log. TODO: really? | |
| 2125 @@ -539,7 +551,7 @@ | |
| 2126 self.__dict__ = d | |
| 2127 self.watchers = [] # probably not necessary | |
| 2128 self.finishedWatchers = [] # same | |
| 2129 - # self.step must be filled in by our parent | |
| 2130 + # self.step and self.builder must be filled in by our parent | |
| 2131 self.finished = True | |
| 2132 | |
| 2133 def upgrade(self, logfilename): | |
| 2134 @@ -561,7 +573,10 @@ | |
| 2135 filename = None | |
| 2136 | |
| 2137 def __init__(self, parent, name, logfilename, html): | |
| 2138 - self.step = parent | |
| 2139 + self.step = weakref.ref(parent) | |
| 2140 + self.step_number = parent.step_number | |
| 2141 + self.build_number = parent.getBuild().getNumber() | |
| 2142 + self.builder = parent.builder | |
| 2143 self.name = name | |
| 2144 self.filename = logfilename | |
| 2145 self.html = html | |
| 2146 @@ -569,7 +584,13 @@ | |
| 2147 def getName(self): | |
| 2148 return self.name # set in BuildStepStatus.addLog | |
| 2149 def getStep(self): | |
| 2150 - return self.step | |
| 2151 + result = self.step() | |
| 2152 + if result is not None: | |
| 2153 + return result | |
| 2154 + build = self.builder.getBuildByNumber(self.build_number) | |
| 2155 + result = build.getSteps()[self.step_number] | |
| 2156 + self.step = weakref.ref(result) | |
| 2157 + return result | |
| 2158 | |
| 2159 def isFinished(self): | |
| 2160 return True | |
| 2161 @@ -596,6 +617,7 @@ | |
| 2162 def __getstate__(self): | |
| 2163 d = self.__dict__.copy() | |
| 2164 del d['step'] | |
| 2165 + del d['builder'] | |
| 2166 return d | |
| 2167 | |
| 2168 def upgrade(self, logfilename): | |
| 2169 @@ -617,4 +639,3 @@ | |
| 2170 else: | |
| 2171 log.msg("giving up on removing %s after over %d seconds" % | |
| 2172 (filename, timeout)) | |
| 2173 - | |
| 2174 Index: buildbot/status/builder.py | |
| 2175 =================================================================== | |
| 2176 --- buildbot/status/builder.py (revision 132430) | |
| 2177 +++ buildbot/status/builder.py (working copy) | |
| 2178 @@ -202,6 +202,10 @@ | |
| 2179 % (self.name, number)) | |
| 2180 build = load(open(filename, "rb")) | |
| 2181 build.builder = self | |
| 2182 + for step in build.getSteps(): | |
| 2183 + step.builder = self | |
| 2184 + for loog in step.getLogs(): | |
| 2185 + loog.builder = self | |
| 2186 | |
| 2187 # (bug #1068) if we need to upgrade, we probably need to rewrite | |
| 2188 # this pickle, too. We determine this by looking at the list of | |
| 2189 | |
| 2190 | |
| 2191 Add nextSlaveAndBuilder to builder config | |
| 2192 | |
| 2193 https://chromiumcodereview.appspot.com/10032026/ | |
| 2194 | |
| 2195 Index: buildbot/config.py | |
| 2196 =================================================================== | |
| 2197 --- buildbot/config.py (revision 132861) | |
| 2198 +++ buildbot/config.py (working copy) | |
| 2199 @@ -45,6 +45,7 @@ | |
| 2200 category=None, | |
| 2201 nextSlave=None, | |
| 2202 nextBuild=None, | |
| 2203 + nextSlaveAndBuild=None, | |
| 2204 locks=None, | |
| 2205 env=None, | |
| 2206 properties=None, | |
| 2207 @@ -95,6 +96,7 @@ | |
| 2208 self.category = category | |
| 2209 self.nextSlave = nextSlave | |
| 2210 self.nextBuild = nextBuild | |
| 2211 + self.nextSlaveAndBuild = nextSlaveAndBuild | |
| 2212 self.locks = locks | |
| 2213 self.env = env | |
| 2214 self.properties = properties | |
| 2215 @@ -114,6 +116,8 @@ | |
| 2216 rv['nextSlave'] = self.nextSlave | |
| 2217 if self.nextBuild: | |
| 2218 rv['nextBuild'] = self.nextBuild | |
| 2219 + if self.nextSlaveAndBuild: | |
| 2220 + rv['nextSlaveAndBuild'] = self.nextSlaveAndBuild | |
| 2221 if self.locks: | |
| 2222 rv['locks'] = self.locks | |
| 2223 if self.env: | |
| 2224 Index: buildbot/process/builder.py | |
| 2225 =================================================================== | |
| 2226 --- buildbot/process/builder.py (revision 132861) | |
| 2227 +++ buildbot/process/builder.py (working copy) | |
| 2228 @@ -102,6 +102,14 @@ | |
| 2229 self.nextBuild = setup.get('nextBuild') | |
| 2230 if self.nextBuild is not None and not callable(self.nextBuild): | |
| 2231 raise ValueError("nextBuild must be callable") | |
| 2232 + self.nextSlaveAndBuild = setup.get('nextSlaveAndBuild') | |
| 2233 + if self.nextSlaveAndBuild is not None: | |
| 2234 + if not callable(self.nextSlaveAndBuild): | |
| 2235 + raise ValueError("nextSlaveAndBuild must be callable") | |
| 2236 + if self.nextBuild or self.nextSlave: | |
| 2237 + raise ValueError("nextSlaveAndBuild cannot be specified" | |
| 2238 + " together with either nextSlave or nextBuild"
) | |
| 2239 + | |
| 2240 self.buildHorizon = setup.get('buildHorizon') | |
| 2241 self.logHorizon = setup.get('logHorizon') | |
| 2242 self.eventHorizon = setup.get('eventHorizon') | |
| 2243 @@ -178,6 +186,8 @@ | |
| 2244 diffs.append('nextSlave changed from %s to %s' % (self.nextSlave, s
etup.get('nextSlave'))) | |
| 2245 if setup.get('nextBuild') != self.nextBuild: | |
| 2246 diffs.append('nextBuild changed from %s to %s' % (self.nextBuild, s
etup.get('nextBuild'))) | |
| 2247 + if setup.get('nextSlaveAndBuild') != self.nextSlaveAndBuild: | |
| 2248 + diffs.append('nextSlaveAndBuild changed from %s to %s' % (self.next
SlaveAndBuild, setup.get('nextSlaveAndBuild'))) | |
| 2249 if setup.get('buildHorizon', None) != self.buildHorizon: | |
| 2250 diffs.append('buildHorizon changed from %s to %s' % (self.buildHori
zon, setup['buildHorizon'])) | |
| 2251 if setup.get('logHorizon', None) != self.logHorizon: | |
| 2252 @@ -605,6 +615,26 @@ | |
| 2253 | |
| 2254 # Build Creation | |
| 2255 | |
| 2256 + def _checkSlaveBuilder(self, slavebuilder, available_slavebuilders): | |
| 2257 + if slavebuilder not in available_slavebuilders: | |
| 2258 + next_func = 'nextSlave' | |
| 2259 + if self.nextSlaveAndBuild: | |
| 2260 + next_func = 'nextSlaveAndBuild' | |
| 2261 + log.msg("%s chose a nonexistent slave for builder '%s'; cannot" | |
| 2262 + " start build" % (next_func, self.name)) | |
| 2263 + return False | |
| 2264 + return True | |
| 2265 + | |
| 2266 + def _checkBrDict(self, brdict, unclaimed_requests): | |
| 2267 + if brdict not in unclaimed_requests: | |
| 2268 + next_func = 'nextBuild' | |
| 2269 + if self.nextSlaveAndBuild: | |
| 2270 + next_func = 'nextSlaveAndBuild' | |
| 2271 + log.msg("%s chose a nonexistent request for builder '%s'; cannot" | |
| 2272 + " start build" % (next_func, self.name)) | |
| 2273 + return False | |
| 2274 + return True | |
| 2275 + | |
| 2276 @defer.deferredGenerator | |
| 2277 def maybeStartBuild(self): | |
| 2278 # This method is called by the botmaster whenever this builder should | |
| 2279 @@ -645,34 +675,53 @@ | |
| 2280 | |
| 2281 # match them up until we're out of options | |
| 2282 while available_slavebuilders and unclaimed_requests: | |
| 2283 - # first, choose a slave (using nextSlave) | |
| 2284 - wfd = defer.waitForDeferred( | |
| 2285 - self._chooseSlave(available_slavebuilders)) | |
| 2286 - yield wfd | |
| 2287 - slavebuilder = wfd.getResult() | |
| 2288 + brdict = None | |
| 2289 + if self.nextSlaveAndBuild: | |
| 2290 + # convert brdicts to BuildRequest objects | |
| 2291 + wfd = defer.waitForDeferred( | |
| 2292 + defer.gatherResults([self._brdictToBuildRequest(brdict) | |
| 2293 + for brdict in unclaimed_requests])
) | |
| 2294 + yield wfd | |
| 2295 + breqs = wfd.getResult() | |
| 2296 | |
| 2297 - if not slavebuilder: | |
| 2298 - break | |
| 2299 + wfd = defer.waitForDeferred(defer.maybeDeferred( | |
| 2300 + self.nextSlaveAndBuild, | |
| 2301 + available_slavebuilders, | |
| 2302 + breqs)) | |
| 2303 + yield wfd | |
| 2304 + slavebuilder, br = wfd.getResult() | |
| 2305 | |
| 2306 - if slavebuilder not in available_slavebuilders: | |
| 2307 - log.msg(("nextSlave chose a nonexistent slave for builder " | |
| 2308 - "'%s'; cannot start build") % self.name) | |
| 2309 - break | |
| 2310 + # Find the corresponding brdict for the returned BuildRequest | |
| 2311 + if br: | |
| 2312 + for brdict_i in unclaimed_requests: | |
| 2313 + if brdict_i['brid'] == br.id: | |
| 2314 + brdict = brdict_i | |
| 2315 + break | |
| 2316 | |
| 2317 - # then choose a request (using nextBuild) | |
| 2318 - wfd = defer.waitForDeferred( | |
| 2319 - self._chooseBuild(unclaimed_requests)) | |
| 2320 - yield wfd | |
| 2321 - brdict = wfd.getResult() | |
| 2322 + if (not self._checkSlaveBuilder(slavebuilder, | |
| 2323 + available_slavebuilders) | |
| 2324 + or not self._checkBrDict(brdict, unclaimed_requests)): | |
| 2325 + break | |
| 2326 + else: | |
| 2327 + # first, choose a slave (using nextSlave) | |
| 2328 + wfd = defer.waitForDeferred( | |
| 2329 + self._chooseSlave(available_slavebuilders)) | |
| 2330 + yield wfd | |
| 2331 + slavebuilder = wfd.getResult() | |
| 2332 | |
| 2333 - if not brdict: | |
| 2334 - break | |
| 2335 + if not self._checkSlaveBuilder(slavebuilder, | |
| 2336 + available_slavebuilders): | |
| 2337 + break | |
| 2338 | |
| 2339 - if brdict not in unclaimed_requests: | |
| 2340 - log.msg(("nextBuild chose a nonexistent request for builder " | |
| 2341 - "'%s'; cannot start build") % self.name) | |
| 2342 - break | |
| 2343 + # then choose a request (using nextBuild) | |
| 2344 + wfd = defer.waitForDeferred( | |
| 2345 + self._chooseBuild(unclaimed_requests)) | |
| 2346 + yield wfd | |
| 2347 + brdict = wfd.getResult() | |
| 2348 | |
| 2349 + if not self._checkBrDict(brdict, unclaimed_requests): | |
| 2350 + break | |
| 2351 + | |
| 2352 # merge the chosen request with any compatible requests in the | |
| 2353 # queue | |
| 2354 wfd = defer.waitForDeferred( | |
| 2355 | |
| 2356 | |
| 2357 Eliminate circular references between JsonResource and HelpResource. | |
| 2358 | |
| 2359 Index: buildbot/status/web/status_json.py | |
| 2360 =================================================================== | |
| 2361 --- buildbot/status/web/status_json.py (revision 132909) | |
| 2362 +++ buildbot/status/web/status_json.py (working copy) | |
| 2363 @@ -131,17 +131,16 @@ | |
| 2364 resource.Resource.__init__(self) | |
| 2365 # buildbot.status.builder.Status | |
| 2366 self.status = status | |
| 2367 - if self.help: | |
| 2368 - pageTitle = '' | |
| 2369 - if self.pageTitle: | |
| 2370 - pageTitle = self.pageTitle + ' help' | |
| 2371 - self.putChild('help', | |
| 2372 - HelpResource(self.help, pageTitle=pageTitle, parent_n
ode=self)) | |
| 2373 | |
| 2374 def getChildWithDefault(self, path, request): | |
| 2375 """Adds transparent support for url ending with /""" | |
| 2376 if path == "" and len(request.postpath) == 0: | |
| 2377 return self | |
| 2378 + if (path == "help" or path == "help/") and self.help: | |
| 2379 + pageTitle = '' | |
| 2380 + if self.pageTitle: | |
| 2381 + pageTitle = self.pageTitle + ' help' | |
| 2382 + return HelpResource(self.help, pageTitle=pageTitle, parent_node=sel
f) | |
| 2383 # Equivalent to resource.Resource.getChildWithDefault() | |
| 2384 if self.children.has_key(path): | |
| 2385 return self.children[path] | |
| 2386 @@ -340,12 +339,13 @@ | |
| 2387 HtmlResource.__init__(self) | |
| 2388 self.text = text | |
| 2389 self.pageTitle = pageTitle | |
| 2390 - self.parent_node = parent_node | |
| 2391 + self.parent_level = parent_node.level | |
| 2392 + self.parent_children = parent_node.children.keys() | |
| 2393 | |
| 2394 def content(self, request, cxt): | |
| 2395 - cxt['level'] = self.parent_node.level | |
| 2396 + cxt['level'] = self.parent_level | |
| 2397 cxt['text'] = ToHtml(self.text) | |
| 2398 - cxt['children'] = [ n for n in self.parent_node.children.keys() if n !=
'help' ] | |
| 2399 + cxt['children'] = [ n for n in self.parent_children if n != 'help' ] | |
| 2400 cxt['flags'] = ToHtml(FLAGS) | |
| 2401 cxt['examples'] = ToHtml(EXAMPLES).replace( | |
| 2402 'href="/json', | |
| 2403 | |
| 2404 | |
| 2405 Don't cache json build pages; that defeats the purpose of | |
| 2406 buildbot.status.builder.buildCache by holding references to all | |
| 2407 builds. | |
| 2408 | |
| 2409 Index: buildbot/status/web/status_json.py | |
| 2410 =================================================================== | |
| 2411 --- buildbot/status/web/status_json.py (revision 133407) | |
| 2412 +++ buildbot/status/web/status_json.py (working copy) | |
| 2413 @@ -451,24 +451,16 @@ | |
| 2414 if isinstance(path, int) or _IS_INT.match(path): | |
| 2415 build_status = self.builder_status.getBuild(int(path)) | |
| 2416 if build_status: | |
| 2417 - build_status_number = str(build_status.getNumber()) | |
| 2418 - # Happens with negative numbers. | |
| 2419 - child = self.children.get(build_status_number) | |
| 2420 - if child: | |
| 2421 - return child | |
| 2422 - # Create it on-demand. | |
| 2423 - child = BuildJsonResource(self.status, build_status) | |
| 2424 - # Cache it. Never cache negative numbers. | |
| 2425 - # TODO(maruel): Cleanup the cache once it's too heavy! | |
| 2426 - self.putChild(build_status_number, child) | |
| 2427 - return child | |
| 2428 + # Don't cache BuildJsonResource; that would defeat the cache-in
g | |
| 2429 + # mechanism in place for BuildStatus objects (in BuilderStatus)
. | |
| 2430 + return BuildJsonResource(self.status, build_status) | |
| 2431 return JsonResource.getChild(self, path, request) | |
| 2432 | |
| 2433 def asDict(self, request): | |
| 2434 results = {} | |
| 2435 - # If max > buildCacheSize, it'll trash the cache... | |
| 2436 + # If max is too big, it'll trash the cache... | |
| 2437 max = int(RequestArg(request, 'max', | |
| 2438 - self.builder_status.buildCacheSize)) | |
| 2439 + self.builder_status.buildCacheSize/2)) | |
| 2440 for i in range(0, max): | |
| 2441 child = self.getChildWithDefault(-i, request) | |
| 2442 if not isinstance(child, BuildJsonResource): | |
| 2443 | |
| 2444 | |
| 2445 | |
| 2446 Fix getLastFinishedBuild() so it works correctly when there are 2 or more | |
| 2447 running builds for a given builder. Previously, it would always return the | |
| 2448 second-to-last build if the first was missing or running. | |
| 2449 | |
| 2450 Index: buildbot/status/builder.py | |
| 2451 =================================================================== | |
| 2452 --- buildbot/status/builder.py | |
| 2453 +++ buildbot/status/builder.py | |
| 2454 @@ -313,10 +313,12 @@ class BuilderStatus(styles.Versioned): | |
| 2455 return self.currentBuilds | |
| 2456 | |
| 2457 def getLastFinishedBuild(self): | |
| 2458 - b = self.getBuild(-1) | |
| 2459 - if not (b and b.isFinished()): | |
| 2460 - b = self.getBuild(-2) | |
| 2461 - return b | |
| 2462 + for build in self.generateFinishedBuilds(num_builds=1): | |
| 2463 + assert build and build.isFinished, \ | |
| 2464 + 'builder %s build %s is not finished' % ( | |
| 2465 + self.getName(), build) | |
| 2466 + return build | |
| 2467 + return None | |
| 2468 | |
| 2469 def getCategory(self): | |
| 2470 return self.category | |
| 2471 | |
| 2472 Fix bug introduced in previous fix to status_json.py (line 2168 in this file). | |
| 2473 | |
| 2474 Index: buildbot/status/web/status_json.py | |
| 2475 =================================================================== | |
| 2476 --- buildbot/status/web/status_json.py (revision 147894) | |
| 2477 +++ buildbot/status/web/status_json.py (working copy) | |
| 2478 @@ -140,7 +140,9 @@ | |
| 2479 pageTitle = '' | |
| 2480 if self.pageTitle: | |
| 2481 pageTitle = self.pageTitle + ' help' | |
| 2482 - return HelpResource(self.help, pageTitle=pageTitle, parent_node=sel
f) | |
| 2483 + res = HelpResource(self.help, pageTitle=pageTitle, parent_node=self
) | |
| 2484 + res.level = self.level + 1 | |
| 2485 + return res | |
| 2486 # Equivalent to resource.Resource.getChildWithDefault() | |
| 2487 if self.children.has_key(path): | |
| 2488 return self.children[path] | |
| 2489 | |
| 2490 | |
| 2491 Make buildbot names in urls from console more readable. | |
| 2492 | |
| 2493 Index: buildbot/status/web/console.py | |
| 2494 =================================================================== | |
| 2495 --- a/scripts/master/chromium_status_bb8.py | |
| 2496 +++ b/scripts/master/chromium_status_bb8.py | |
| 2497 @@ -141,7 +141,7 @@ class HorizontalOneBoxPerBuilder(base.HtmlResource): | |
| 2498 title = builder_name | |
| 2499 show_name = 'off' not in request.args.get('titles', ['off']) | |
| 2500 url = (base.path_to_root(request) + "waterfall?builder=" + | |
| 2501 - urllib.quote(builder_name, safe='')) | |
| 2502 + urllib.quote(builder_name, safe='() ')) | |
| 2503 cxt_builders.append({'outcome': classname, | |
| 2504 'name': title, | |
| 2505 'url': url, | |
| 2506 diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_pa
rty/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 2507 index e70b72d..5bf2725 100644 | |
| 2508 --- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 2509 +++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 2510 @@ -96,7 +96,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2511 def git_init(_): | |
| 2512 log.msg('gitpoller: initializing working dir from %s' % self.repour
l) | |
| 2513 d = utils.getProcessOutputAndValue(self.gitbin, | |
| 2514 - ['init', self.workdir], env=dict(PATH=os.environ['PATH'])) | |
| 2515 + ['init', self.workdir], env=os.environ) | |
| 2516 d.addCallback(self._convert_nonzero_to_failure) | |
| 2517 d.addErrback(self._stop_on_failure) | |
| 2518 return d | |
| 2519 @@ -105,7 +105,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2520 def git_remote_add(_): | |
| 2521 d = utils.getProcessOutputAndValue(self.gitbin, | |
| 2522 ['remote', 'add', 'origin', self.repourl], | |
| 2523 - path=self.workdir, env=dict(PATH=os.environ['PATH'])) | |
| 2524 + path=self.workdir, env=os.environ) | |
| 2525 d.addCallback(self._convert_nonzero_to_failure) | |
| 2526 d.addErrback(self._stop_on_failure) | |
| 2527 return d | |
| 2528 @@ -115,7 +115,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2529 args = ['fetch', 'origin'] | |
| 2530 self._extend_with_fetch_refspec(args) | |
| 2531 d = utils.getProcessOutputAndValue(self.gitbin, args, | |
| 2532 - path=self.workdir, env=dict(PATH=os.environ['PATH'])) | |
| 2533 + path=self.workdir, env=os.environ) | |
| 2534 d.addCallback(self._convert_nonzero_to_failure) | |
| 2535 d.addErrback(self._stop_on_failure) | |
| 2536 return d | |
| 2537 @@ -126,11 +126,11 @@ class GitPoller(base.PollingChangeSource): | |
| 2538 if self.branch == 'master': # repo is already on branch 'master', s
o reset | |
| 2539 d = utils.getProcessOutputAndValue(self.gitbin, | |
| 2540 ['reset', '--hard', 'origin/%s' % self.branch], | |
| 2541 - path=self.workdir, env=dict(PATH=os.environ['PATH'])) | |
| 2542 + path=self.workdir, env=os.environ) | |
| 2543 else: | |
| 2544 d = utils.getProcessOutputAndValue(self.gitbin, | |
| 2545 ['checkout', '-b', self.branch, 'origin/%s' % self.bran
ch], | |
| 2546 - path=self.workdir, env=dict(PATH=os.environ['PATH'])) | |
| 2547 + path=self.workdir, env=os.environ) | |
| 2548 d.addCallback(self._convert_nonzero_to_failure) | |
| 2549 d.addErrback(self._stop_on_failure) | |
| 2550 return d | |
| 2551 @@ -169,7 +169,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2552 | |
| 2553 def _get_commit_comments(self, rev): | |
| 2554 args = ['log', rev, '--no-walk', r'--format=%s%n%b'] | |
| 2555 - d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=di
ct(PATH=os.environ['PATH']), errortoo=False ) | |
| 2556 + d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os
.environ, errortoo=False ) | |
| 2557 def process(git_output): | |
| 2558 stripped_output = git_output.strip().decode(self.encoding) | |
| 2559 if len(stripped_output) == 0: | |
| 2560 @@ -181,7 +181,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2561 def _get_commit_timestamp(self, rev): | |
| 2562 # unix timestamp | |
| 2563 args = ['log', rev, '--no-walk', r'--format=%ct'] | |
| 2564 - d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=di
ct(PATH=os.environ['PATH']), errortoo=False ) | |
| 2565 + d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os
.environ, errortoo=False ) | |
| 2566 def process(git_output): | |
| 2567 stripped_output = git_output.strip() | |
| 2568 if self.usetimestamps: | |
| 2569 @@ -198,7 +198,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2570 | |
| 2571 def _get_commit_files(self, rev): | |
| 2572 args = ['log', rev, '--name-only', '--no-walk', r'--format=%n'] | |
| 2573 - d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=di
ct(PATH=os.environ['PATH']), errortoo=False ) | |
| 2574 + d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os
.environ, errortoo=False ) | |
| 2575 def process(git_output): | |
| 2576 fileList = git_output.split() | |
| 2577 return fileList | |
| 2578 @@ -207,7 +207,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2579 | |
| 2580 def _get_commit_name(self, rev): | |
| 2581 args = ['log', rev, '--no-walk', r'--format=%aE'] | |
| 2582 - d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=di
ct(PATH=os.environ['PATH']), errortoo=False ) | |
| 2583 + d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os
.environ, errortoo=False ) | |
| 2584 def process(git_output): | |
| 2585 stripped_output = git_output.strip().decode(self.encoding) | |
| 2586 if len(stripped_output) == 0: | |
| 2587 @@ -231,7 +231,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2588 # deferred will not use the response. | |
| 2589 d = utils.getProcessOutput(self.gitbin, args, | |
| 2590 path=self.workdir, | |
| 2591 - env=dict(PATH=os.environ['PATH']), errortoo=True ) | |
| 2592 + env=os.environ, errortoo=True ) | |
| 2593 | |
| 2594 return d | |
| 2595 | |
| 2596 @@ -241,7 +241,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2597 revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'-
-format=%H'] | |
| 2598 self.changeCount = 0 | |
| 2599 d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir, | |
| 2600 - env=dict(PATH=os.environ['PATH']), errortoo=
False ) | |
| 2601 + env=os.environ, errortoo=False ) | |
| 2602 wfd = defer.waitForDeferred(d) | |
| 2603 yield wfd | |
| 2604 results = wfd.getResult() | |
| 2605 @@ -307,7 +307,7 @@ class GitPoller(base.PollingChangeSource): | |
| 2606 return | |
| 2607 log.msg('gitpoller: catching up tracking branch') | |
| 2608 args = ['reset', '--hard', 'origin/%s' % (self.branch,)] | |
| 2609 - d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir
, env=dict(PATH=os.environ['PATH'])) | |
| 2610 + d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir
, env=os.environ) | |
| 2611 d.addCallback(self._convert_nonzero_to_failure) | |
| 2612 return d | |
| 2613 | |
| 2614 diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpolle
r.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py | |
| 2615 index 9a47d41..c5e1960 100644 | |
| 2616 --- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py | |
| 2617 +++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py | |
| 2618 @@ -13,6 +13,7 @@ | |
| 2619 # | |
| 2620 # Copyright Buildbot Team Members | |
| 2621 | |
| 2622 +import os | |
| 2623 from twisted.trial import unittest | |
| 2624 from twisted.internet import defer | |
| 2625 from exceptions import Exception | |
| 2626 @@ -20,6 +21,9 @@ from buildbot.changes import gitpoller | |
| 2627 from buildbot.test.util import changesource, gpo | |
| 2628 from buildbot.util import epoch2datetime | |
| 2629 | |
| 2630 +# Test that environment variables get propagated to subprocesses (See #2116) | |
| 2631 +os.environ['TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES'] = 'TRUE' | |
| 2632 + | |
| 2633 class GitOutputParsing(gpo.GetProcessOutputMixin, unittest.TestCase): | |
| 2634 """Test GitPoller methods for parsing git output""" | |
| 2635 def setUp(self): | |
| 2636 @@ -120,6 +124,11 @@ class TestGitPoller(gpo.GetProcessOutputMixin, | |
| 2637 self.assertSubstring("GitPoller", self.poller.describe()) | |
| 2638 | |
| 2639 def test_poll(self): | |
| 2640 + # Test that environment variables get propagated to subprocesses (See #
2116) | |
| 2641 + os.putenv('TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES', 'TRUE') | |
| 2642 + self.addGetProcessOutputExpectEnv({'TEST_THAT_ENVIRONMENT_GETS_PASSED_T
O_SUBPROCESSES': 'TRUE'}) | |
| 2643 + self.addGetProcessOutputAndValueExpectEnv({'TEST_THAT_ENVIRONMENT_GETS_
PASSED_TO_SUBPROCESSES': 'TRUE'}) | |
| 2644 + | |
| 2645 # patch out getProcessOutput and getProcessOutputAndValue for the | |
| 2646 # benefit of the _get_changes method | |
| 2647 self.addGetProcessOutputResult( | |
| 2648 diff --git a/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py b/third_party/
buildbot_8_4p1/buildbot/test/util/gpo.py | |
| 2649 index a23bdb2..c9daf0c 100644 | |
| 2650 --- a/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py | |
| 2651 +++ b/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py | |
| 2652 @@ -39,6 +39,8 @@ class GetProcessOutputMixin: | |
| 2653 self._gpo_patched = False | |
| 2654 self._gpo_patterns = [] | |
| 2655 self._gpoav_patterns = [] | |
| 2656 + self._gpo_expect_env = {} | |
| 2657 + self._gpoav_expect_env = {} | |
| 2658 | |
| 2659 def tearDownGetProcessOutput(self): | |
| 2660 pass | |
| 2661 @@ -54,11 +56,23 @@ class GetProcessOutputMixin: | |
| 2662 | |
| 2663 # these can be overridden if necessary | |
| 2664 def patched_getProcessOutput(self, bin, args, env=None, **kwargs): | |
| 2665 + for var, value in self._gpo_expect_env.items(): | |
| 2666 + if env.get(var) != value: | |
| 2667 + self._flag_error('Expected environment to have %s = %r' % (var,
value)) | |
| 2668 + | |
| 2669 return self._patched(self._gpo_patterns, bin, args, env=env, **kwargs) | |
| 2670 | |
| 2671 def patched_getProcessOutputAndValue(self, bin, args, env=None, **kwargs): | |
| 2672 + for var, value in self._gpoav_expect_env.items(): | |
| 2673 + if env.get(var) != value: | |
| 2674 + self._flag_error('Expected environment to have %s = %r' % (var,
value)) | |
| 2675 + | |
| 2676 return self._patched(self._gpoav_patterns, bin, args, env=env, **kwargs
) | |
| 2677 | |
| 2678 + def _flag_error(self, msg): | |
| 2679 + # print msg | |
| 2680 + assert False, msg | |
| 2681 + | |
| 2682 def _patch_gpo(self): | |
| 2683 if not self._gpo_patched: | |
| 2684 self.patch(utils, "getProcessOutput", | |
| 2685 @@ -67,6 +81,12 @@ class GetProcessOutputMixin: | |
| 2686 self.patched_getProcessOutputAndValue) | |
| 2687 self._gpo_patched = True | |
| 2688 | |
| 2689 + def addGetProcessOutputExpectEnv(self, d): | |
| 2690 + self._gpo_expect_env.update(d) | |
| 2691 + | |
| 2692 + def addGetProcessOutputAndValueExpectEnv(self, d): | |
| 2693 + self._gpoav_expect_env.update(d) | |
| 2694 + | |
| 2695 def addGetProcessOutputResult(self, pattern, result): | |
| 2696 self._patch_gpo() | |
| 2697 self._gpo_patterns.append((pattern, result)) | |
| 2698 | |
| 2699 | |
| 2700 | |
| 2701 | |
| 2702 Correctly escape the committer in the waterfall/help page. | |
| 2703 | |
| 2704 Index: waterfallhelp.html | |
| 2705 =================================================================== | |
| 2706 --- waterfallhelp.html (revision 164735) | |
| 2707 +++ waterfallhelp.html (working copy) | |
| 2708 @@ -99,7 +99,7 @@ | |
| 2709 {% for cn in committers %} | |
| 2710 <tr> | |
| 2711 <td> | |
| 2712 - Show Committer: <input type="text" name="committer" value="{{ cn }}"> | |
| 2713 + Show Committer: <input type="text" name="committer" value="{{ cn|e }}
"> | |
| 2714 </td> | |
| 2715 </tr> | |
| 2716 {% endfor %} | |
| 2717 | |
| 2718 | |
| 2719 commit 1fe8fbcf99cd98546a98d2dd6a3be1206ea590c7 | |
| 2720 Author: Peter Mayo <petermayo@chromium.org> | |
| 2721 Date: Fri Nov 23 12:37:00 2012 -0500 | |
| 2722 | |
| 2723 Optionally add project to changes in waterfall display. | |
| 2724 | |
| 2725 In some waterfalls it is useful to separate different change | |
| 2726 sources visually. This allows an attribute to reflect that to | |
| 2727 be passed though from config to template. | |
| 2728 | |
| 2729 Related reviews: https://codereview.chromium.org/11425002 | |
| 2730 | |
| 2731 diff --git a/master/buildbot/status/web/changes.py b/master/buildbot/status/web/
changes.py | |
| 2732 index 0971bea..e579669 100644 | |
| 2733 --- a/master/buildbot/status/web/changes.py | |
| 2734 +++ b/master/buildbot/status/web/changes.py | |
| 2735 @@ -64,7 +64,8 @@ class ChangeBox(components.Adapter): | |
| 2736 text = template.module.box_contents(url=url, | |
| 2737 who=self.original.getShortAuthor(), | |
| 2738 pageTitle=self.original.comments, | |
| 2739 - revision=self.original.revision) | |
| 2740 + revision=self.original.revision, | |
| 2741 + project=self.original.project) | |
| 2742 return Box([text], class_="Change") | |
| 2743 components.registerAdapter(ChangeBox, Change, IBox) | |
| 2744 | |
| 2745 diff --git a/master/buildbot/status/web/templates/change_macros.html b/master/bu
ildbot/status/web/templates/change_macros.html | |
| 2746 index dc6a9b2..2ca01d9 100644 | |
| 2747 --- a/master/buildbot/status/web/templates/change_macros.html | |
| 2748 +++ b/master/buildbot/status/web/templates/change_macros.html | |
| 2749 @@ -71,6 +71,6 @@ | |
| 2750 {% endif %} | |
| 2751 {%- endmacro %} | |
| 2752 | |
| 2753 -{% macro box_contents(who, url, pageTitle, revision) -%} | |
| 2754 +{% macro box_contents(who, url, pageTitle, revision, project) -%} | |
| 2755 <a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a> | |
| 2756 {%- endmacro %} | |
| 2757 | |
| 2758 | |
| 2759 | |
| 2760 Disable an assert that is causing internal inconsistent state because of | |
| 2761 out-of-order build processing. | |
| 2762 | |
| 2763 --- a/third_party/buildbot_8_4p1/buildbot/status/builder.py | |
| 2764 +++ b/third_party/buildbot_8_4p1/buildbot/status/builder.py | |
| 2765 @@ -502,7 +502,8 @@ class BuilderStatus(styles.Versioned): | |
| 2766 Steps, its ETA, etc), so it is safe to notify our watchers.""" | |
| 2767 | |
| 2768 assert s.builder is self # paranoia | |
| 2769 - assert s.number == self.nextBuildNumber - 1 | |
| 2770 + # They can happen out of order. | |
| 2771 + #assert s.number == self.nextBuildNumber - 1 | |
| 2772 assert s not in self.currentBuilds | |
| 2773 self.currentBuilds.append(s) | |
| 2774 self.buildCache.put(s.number, s) | |
| 2775 | |
| 2776 Change buildbot to use the epoll reactor instead of the default select() | |
| 2777 reaction. Breaks compatability with non linux26 systems. | |
| 2778 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 2779 index 28e46de..7ec0032 100644 | |
| 2780 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 2781 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 2782 @@ -19,6 +19,16 @@ import signal | |
| 2783 import time | |
| 2784 import textwrap | |
| 2785 | |
| 2786 +try: | |
| 2787 + import pyximport | |
| 2788 + pyximport.install() | |
| 2789 + from twisted.internet import epollreactor | |
| 2790 + epollreactor.install() | |
| 2791 +except ImportError: | |
| 2792 + print 'Unable to load the epoll module, falling back to select.' | |
| 2793 + print 'This may be caused by the lack of cython, python-dev, or' | |
| 2794 + print 'you may be on a platform other than linux 2.6' | |
| 2795 + | |
| 2796 from zope.interface import implements | |
| 2797 from twisted.python import log, components | |
| 2798 from twisted.internet import defer, reactor | |
| 2799 | |
| 2800 | |
| 2801 --- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 2802 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 2803 @@ -110,7 +110,7 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin): | |
| 2804 'num_changes' : len(changes), | |
| 2805 }) | |
| 2806 | |
| 2807 - numbuilds = int(req.args.get('numbuilds', ['5'])[0]) | |
| 2808 + numbuilds = int(req.args.get('numbuilds', ['20'])[0]) | |
| 2809 recent = cxt['recent'] = [] | |
| 2810 for build in b.generateFinishedBuilds(num_builds=int(numbuilds)): | |
| 2811 recent.append(self.get_line_values(req, build, False)) | |
| 2812 | |
| 2813 | |
| 2814 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 2815 index 7ec0032..425572e 100644 | |
| 2816 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 2817 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 2818 @@ -295,7 +295,7 @@ class BuildMaster(service.MultiService): | |
| 2819 "logHorizon", "buildHorizon", "changeHorizon", | |
| 2820 "logMaxSize", "logMaxTailSize", "logCompressionMethod
", | |
| 2821 "db_url", "multiMaster", "db_poll_interval", | |
| 2822 - "metrics", "caches" | |
| 2823 + "metrics", "caches", "autoBuildCacheRatio" | |
| 2824 ) | |
| 2825 for k in config.keys(): | |
| 2826 if k not in known_keys: | |
| 2827 @@ -357,6 +357,7 @@ class BuildMaster(service.MultiService): | |
| 2828 | |
| 2829 metrics_config = config.get("metrics") | |
| 2830 caches_config = config.get("caches", {}) | |
| 2831 + autoBuildCacheRatio = config.get("autoBuildCacheRatio", None) | |
| 2832 | |
| 2833 except KeyError: | |
| 2834 log.msg("config dictionary is missing a required parameter") | |
| 2835 @@ -555,6 +556,7 @@ class BuildMaster(service.MultiService): | |
| 2836 self.logHorizon = logHorizon | |
| 2837 self.buildHorizon = buildHorizon | |
| 2838 self.slavePortnum = slavePortnum # TODO: move this to master.config
.slavePortnum | |
| 2839 + self.autoBuildCacheRatio = autoBuildCacheRatio | |
| 2840 | |
| 2841 # Set up the database | |
| 2842 d.addCallback(lambda res: | |
| 2843 @@ -784,6 +786,16 @@ class BuildMaster(service.MultiService): | |
| 2844 for builder in allBuilders.values(): | |
| 2845 builder.builder_status.reconfigFromBuildmaster(self) | |
| 2846 | |
| 2847 + # Adjust the caches if autoBuildCacheRatio is on. Each builder's | |
| 2848 + # build cache is set to (autoBuildCacheRatio * number of slaves). | |
| 2849 + # This assumes that each slave-entry can only execute one concurren
t | |
| 2850 + # build. | |
| 2851 + if self.autoBuildCacheRatio: | |
| 2852 + builder_status = builder.builder_status | |
| 2853 + slavecount = len(builder_status.slavenames) | |
| 2854 + max_size = max(self.autoBuildCacheRatio * slavecount, 15) | |
| 2855 + builder_status.buildCache.set_max_size(max_size) | |
| 2856 + builder_status.buildCacheSize = max_size | |
| 2857 + | |
| 2858 metrics.MetricCountEvent.log("num_builders", | |
| 2859 len(allBuilders), absolute=True) | |
| 2860 | |
| 2861 | |
| 2862 Added error catching so buildbot would not crash during initial config. | |
| 2863 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 2864 index df80ca6..5304401 100644 | |
| 2865 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 2866 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 2867 @@ -18,6 +18,7 @@ import os | |
| 2868 import signal | |
| 2869 import time | |
| 2870 import textwrap | |
| 2871 +import twisted.internet.error | |
| 2872 | |
| 2873 try: | |
| 2874 import pyximport | |
| 2875 @@ -28,6 +29,8 @@ except ImportError: | |
| 2876 print 'Unable to load the epoll module, falling back to select.' | |
| 2877 print 'This may be caused by the lack of cython, python-dev, or' | |
| 2878 print 'you may be on a platform other than linux 2.6' | |
| 2879 +except twisted.internet.error.ReactorAlreadyInstalledError: | |
| 2880 + pass | |
| 2881 | |
| 2882 from zope.interface import implements | |
| 2883 from twisted.python import log, components | |
| 2884 | |
| 2885 Suppress a warning about old name. | |
| 2886 Index: buildbot/changes/svnpoller.py | |
| 2887 =================================================================== | |
| 2888 --- buildbot/changes/svnpoller.py (revision 195053) | |
| 2889 +++ buildbot/changes/svnpoller.py (working copy) | |
| 2890 @@ -352,7 +352,7 @@ | |
| 2891 log.msg("Ignoring deletion of branch '%s'" % branch) | |
| 2892 else: | |
| 2893 chdict = dict( | |
| 2894 - who=author, | |
| 2895 + author=author, | |
| 2896 files=files, | |
| 2897 comments=comments, | |
| 2898 revision=revision, | |
| 2899 | |
| 2900 diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 2901 index ed12ea0..32e1bd3 100644 | |
| 2902 --- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 2903 +++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 2904 @@ -635,8 +635,8 @@ class BuildStep: | |
| 2905 doStepIf = True | |
| 2906 hideStepIf = False | |
| 2907 # like doStepIf, but evaluated at runtime if executing under runbuild.py | |
| 2908 - # we also overload 'False' to signify this isn't a buildrunner step | |
| 2909 - brDoStepIf = False | |
| 2910 + # We use None to signify that a step is not a buildrunner step. | |
| 2911 + brDoStepIf = None | |
| 2912 | |
| 2913 def __init__(self, **kwargs): | |
| 2914 self.factory = (self.__class__, dict(kwargs)) | |
| 2915 | |
| 2916 | |
| 2917 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 2918 index b656563..e48542d 100644 | |
| 2919 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 2920 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 2921 @@ -16,6 +16,7 @@ | |
| 2922 | |
| 2923 """Simple JSON exporter.""" | |
| 2924 | |
| 2925 +import collections | |
| 2926 import datetime | |
| 2927 import os | |
| 2928 import re | |
| 2929 @@ -635,21 +636,38 @@ class SlaveJsonResource(JsonResource): | |
| 2930 self.builders.append(builderName) | |
| 2931 return self.builders | |
| 2932 | |
| 2933 + def getSlaveBuildMap(self, buildcache, buildercache): | |
| 2934 + for builderName in self.getBuilders(): | |
| 2935 + if builderName not in buildercache: | |
| 2936 + buildercache.add(builderName) | |
| 2937 + builder_status = self.status.getBuilder(builderName) | |
| 2938 + for i in range(1, builder_status.buildCacheSize - 1): | |
| 2939 + build_status = builder_status.getBuild(-i) | |
| 2940 + if not build_status or not build_status.isFinished(): | |
| 2941 + # If not finished, it will appear in runningBuilds. | |
| 2942 + break | |
| 2943 + slave = buildcache[build_status.getSlavename()] | |
| 2944 + slave.setdefault(builderName, []).append( | |
| 2945 + build_status.getNumber()) | |
| 2946 + return buildcache[self.name] | |
| 2947 + | |
| 2948 def asDict(self, request): | |
| 2949 + if not hasattr(request, 'custom_data'): | |
| 2950 + request.custom_data = {} | |
| 2951 + if 'buildcache' not in request.custom_data: | |
| 2952 + # buildcache is used to cache build information across multiple | |
| 2953 + # invocations of SlaveJsonResource. It should be set to an empty | |
| 2954 + # collections.defaultdict(dict). | |
| 2955 + request.custom_data['buildcache'] = collections.defaultdict(dict) | |
| 2956 + | |
| 2957 + # Tracks which builders have been stored in the buildcache. | |
| 2958 + request.custom_data['buildercache'] = set() | |
| 2959 + | |
| 2960 results = self.slave_status.asDict() | |
| 2961 - # Enhance it by adding more informations. | |
| 2962 - results['builders'] = {} | |
| 2963 - for builderName in self.getBuilders(): | |
| 2964 - builds = [] | |
| 2965 - builder_status = self.status.getBuilder(builderName) | |
| 2966 - for i in range(1, builder_status.buildCacheSize - 1): | |
| 2967 - build_status = builder_status.getBuild(-i) | |
| 2968 - if not build_status or not build_status.isFinished(): | |
| 2969 - # If not finished, it will appear in runningBuilds. | |
| 2970 - break | |
| 2971 - if build_status.getSlavename() == self.name: | |
| 2972 - builds.append(build_status.getNumber()) | |
| 2973 - results['builders'][builderName] = builds | |
| 2974 + # Enhance it by adding more information. | |
| 2975 + results['builders'] = self.getSlaveBuildMap( | |
| 2976 + request.custom_data['buildcache'], | |
| 2977 + request.custom_data['buildercache']) | |
| 2978 return results | |
| 2979 | |
| 2980 | |
| 2981 | |
| 2982 diff --git a/third_party/buildbot_8_4p1/buildbot/status/builder.py b/third_party
/buildbot_8_4p1/buildbot/status/builder.py | |
| 2983 index 3f42eb6..c5bd1ec 100644 | |
| 2984 --- a/third_party/buildbot_8_4p1/buildbot/status/builder.py | |
| 2985 +++ b/third_party/buildbot_8_4p1/buildbot/status/builder.py | |
| 2986 @@ -17,6 +17,7 @@ | |
| 2987 import weakref | |
| 2988 import gc | |
| 2989 import os, re, itertools | |
| 2990 +import random | |
| 2991 from cPickle import load, dump | |
| 2992 | |
| 2993 from zope.interface import implements | |
| 2994 @@ -323,17 +324,58 @@ class BuilderStatus(styles.Versioned): | |
| 2995 def getCategory(self): | |
| 2996 return self.category | |
| 2997 | |
| 2998 - def getBuild(self, number): | |
| 2999 + def _resolveBuildNumber(self, number): | |
| 3000 if number < 0: | |
| 3001 number = self.nextBuildNumber + number | |
| 3002 if number < 0 or number >= self.nextBuildNumber: | |
| 3003 return None | |
| 3004 + return number | |
| 3005 | |
| 3006 + def _safeGetBuild(self, build_number): | |
| 3007 try: | |
| 3008 - return self.getBuildByNumber(number) | |
| 3009 + return self.getBuildByNumber(build_number) | |
| 3010 except IndexError: | |
| 3011 return None | |
| 3012 | |
| 3013 + def getBuild(self, number): | |
| 3014 + number = self._resolveBuildNumber(number) | |
| 3015 + | |
| 3016 + if number is None: | |
| 3017 + return None | |
| 3018 + | |
| 3019 + return self._safeGetBuild(number) | |
| 3020 + | |
| 3021 + def getBuilds(self, numbers): | |
| 3022 + """Cache-aware method to get multiple builds. | |
| 3023 + | |
| 3024 + Prevents cascading evict/load when multiple builds are requested in | |
| 3025 + succession: requesting build 1 evicts build 2, requesting build 2 evict
s | |
| 3026 + build 3, etc. | |
| 3027 + | |
| 3028 + We query the buildCache and load hits first, then misses. When loading
, | |
| 3029 + we randomize the load order to alleviate the problem when external web | |
| 3030 + requests load builds sequentially (they don't have access to this | |
| 3031 + function). | |
| 3032 + """ | |
| 3033 + | |
| 3034 + numbers = list(enumerate(self._resolveBuildNumber(x) for x in numbers)) | |
| 3035 + random.shuffle(numbers) | |
| 3036 + | |
| 3037 + builds = [None] * len(numbers) | |
| 3038 + misses = [] | |
| 3039 + for idx, build_number in numbers: | |
| 3040 + if build_number is None: | |
| 3041 + continue | |
| 3042 + if build_number in self.buildCache.cache: | |
| 3043 + builds[idx] = self._safeGetBuild(build_number) | |
| 3044 + else: | |
| 3045 + misses.append((idx, build_number)) | |
| 3046 + | |
| 3047 + for idx, build_number in misses: | |
| 3048 + builds[idx] = self._safeGetBuild(build_number) | |
| 3049 + | |
| 3050 + return builds | |
| 3051 + | |
| 3052 def getEvent(self, number): | |
| 3053 try: | |
| 3054 return self.events[number] | |
| 3055 @@ -616,6 +658,11 @@ class BuilderStatus(styles.Versioned): | |
| 3056 # Collect build numbers. | |
| 3057 # Important: Only grab the *cached* builds numbers to reduce I/O. | |
| 3058 current_builds = [b.getNumber() for b in self.currentBuilds] | |
| 3059 + | |
| 3060 + # Populates buildCache with last N builds. | |
| 3061 + buildnums = range(-1, -(self.buildCacheSize - 1), -1) | |
| 3062 + self.getBuilds(buildnums) | |
| 3063 + | |
| 3064 cached_builds = list(set(self.buildCache.cache.keys() + current_builds)
) | |
| 3065 cached_builds.sort() | |
| 3066 result['cachedBuilds'] = cached_builds | |
| 3067 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 3068 index c357939..e7cd932 100644 | |
| 3069 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 3070 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 3071 @@ -641,8 +641,11 @@ class SlaveJsonResource(JsonResource): | |
| 3072 if builderName not in buildercache: | |
| 3073 buildercache.add(builderName) | |
| 3074 builder_status = self.status.getBuilder(builderName) | |
| 3075 - for i in range(1, builder_status.buildCacheSize - 1): | |
| 3076 - build_status = builder_status.getBuild(-i) | |
| 3077 + | |
| 3078 + buildnums = range(-1, -(builder_status.buildCacheSize - 1), -1) | |
| 3079 + builds = builder_status.getBuilds(buildnums) | |
| 3080 + | |
| 3081 + for build_status in builds: | |
| 3082 if not build_status or not build_status.isFinished(): | |
| 3083 # If not finished, it will appear in runningBuilds. | |
| 3084 break | |
| 3085 | |
| 3086 | |
| 3087 diff --git a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py b/third_par
ty/buildbot_8_4p1/buildbot/status/buildstep.py | |
| 3088 index 264b599..f64e0b9 100644 | |
| 3089 --- a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py | |
| 3090 +++ b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py | |
| 3091 @@ -13,7 +13,10 @@ | |
| 3092 # | |
| 3093 # Copyright Buildbot Team Members | |
| 3094 | |
| 3095 +import collections | |
| 3096 import os, weakref | |
| 3097 + | |
| 3098 + | |
| 3099 from zope.interface import implements | |
| 3100 from twisted.persisted import styles | |
| 3101 from twisted.python import log | |
| 3102 @@ -21,6 +24,9 @@ from twisted.internet import reactor, defer | |
| 3103 from buildbot import interfaces, util | |
| 3104 from buildbot.status.logfile import LogFile, HTMLLogFile | |
| 3105 | |
| 3106 +# This allows use of OrderedDict on python 2.6, found in scripts/common. | |
| 3107 +import common.python26_polyfill # pylint: disable=W0611 | |
| 3108 + | |
| 3109 class BuildStepStatus(styles.Versioned): | |
| 3110 """ | |
| 3111 I represent a collection of output status for a | |
| 3112 @@ -70,7 +76,7 @@ class BuildStepStatus(styles.Versioned): | |
| 3113 self.step_number = step_number | |
| 3114 self.hidden = False | |
| 3115 self.logs = [] | |
| 3116 - self.urls = {} | |
| 3117 + self.urls = collections.OrderedDict() | |
| 3118 self.watchers = [] | |
| 3119 self.updates = {} | |
| 3120 self.finishedWatchers = [] | |
| 3121 | |
| 3122 diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_pa
rty/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3123 index 5bf2725..71a0386 100644 | |
| 3124 --- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3125 +++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3126 @@ -238,7 +238,8 @@ class GitPoller(base.PollingChangeSource): | |
| 3127 @defer.deferredGenerator | |
| 3128 def _process_changes(self, unused_output): | |
| 3129 # get the change list | |
| 3130 - revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'-
-format=%H'] | |
| 3131 + revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), | |
| 3132 + r'--format=%H', '--first-parent'] | |
| 3133 self.changeCount = 0 | |
| 3134 d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir, | |
| 3135 env=os.environ, errortoo=False ) | |
| 3136 | |
| 3137 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 3138 index 5304401..c90ad32 100644 | |
| 3139 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 3140 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 3141 @@ -796,7 +796,7 @@ class BuildMaster(service.MultiService): | |
| 3142 if self.autoBuildCacheRatio: | |
| 3143 builder_status = builder.builder_status | |
| 3144 slavecount = len(builder_status.slavenames) | |
| 3145 - max_size = max(self.autoBuildCacheRatio * slavecount, 15) | |
| 3146 + max_size = max(self.autoBuildCacheRatio * slavecount, 30) | |
| 3147 builder_status.buildCache.set_max_size(max_size) | |
| 3148 builder_status.buildCacheSize = max_size | |
| 3149 | |
| 3150 | |
| 3151 *** maruel on 2013-12-05 | |
| 3152 Add support for transferring 'reason' from a build to its triggered build. | |
| 3153 Otherwise the default is 'Triggerable(%(name))' which is not very useful. | |
| 3154 | |
| 3155 --- a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py | |
| 3156 +++ b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py | |
| 3157 @@ -28,7 +28,7 @@ class Triggerable(base.BaseScheduler): | |
| 3158 self._bsc_subscription = None | |
| 3159 self.reason = "Triggerable(%s)" % name | |
| 3160 | |
| 3161 - def trigger(self, ssid, set_props=None): | |
| 3162 + def trigger(self, ssid, set_props=None, reason=None): | |
| 3163 """Trigger this scheduler with the given sourcestamp ID. Returns a | |
| 3164 deferred that will fire when the buildset is finished.""" | |
| 3165 # properties for this buildset are composed of our own properties, | |
| 3166 @@ -42,10 +42,10 @@ class Triggerable(base.BaseScheduler): | |
| 3167 # the duration of interest to the caller is bounded by the lifetime of | |
| 3168 # this process. | |
| 3169 if ssid: | |
| 3170 - d = self.addBuildsetForSourceStamp(reason=self.reason, ssid=ssid, | |
| 3171 + d = self.addBuildsetForSourceStamp(reason=reason or self.reason, ss
id=ssid, | |
| 3172 properties=props) | |
| 3173 else: | |
| 3174 - d = self.addBuildsetForLatest(reason=self.reason, properties=props) | |
| 3175 + d = self.addBuildsetForLatest(reason=reason or self.reason, propert
ies=props) | |
| 3176 def setup_waiter((bsid,brids)): | |
| 3177 self._waiters[bsid] = d = defer.Deferred() | |
| 3178 self._updateWaiters() | |
| 3179 --- a/third_party/buildbot_8_4p1/buildbot/steps/trigger.py | |
| 3180 +++ b/third_party/buildbot_8_4p1/buildbot/steps/trigger.py | |
| 3181 @@ -167,7 +167,7 @@ class Trigger(LoggingBuildStep): | |
| 3182 dl = [] | |
| 3183 for scheduler in triggered_schedulers: | |
| 3184 sch = all_schedulers[scheduler] | |
| 3185 - dl.append(sch.trigger(ssid, set_props=props_to_set)) | |
| 3186 + dl.append(sch.trigger(ssid, set_props=props_to_set, reason=self
.build.reason)) | |
| 3187 self.step_status.setText(['triggered'] + triggered_schedulers) | |
| 3188 | |
| 3189 d = defer.DeferredList(dl, consumeErrors=1) | |
| 3190 | |
| 3191 Hard-disables shutdown hooks for https://code.google.com/p/chromium/issues/detai
l?id=309046 | |
| 3192 =================================================================== | |
| 3193 --- a/third_party/buildbot_8_4p1/buildbot/status/web/root.py | |
| 3194 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/root.py | |
| 3195 @@ -25,17 +25,9 @@ | |
| 3196 status = self.getStatus(request) | |
| 3197 | |
| 3198 if request.path == '/shutdown': | |
| 3199 - if self.getAuthz(request).actionAllowed("cleanShutdown", request): | |
| 3200 - eventually(status.cleanShutdown) | |
| 3201 - return redirectTo("/", request) | |
| 3202 - else: | |
| 3203 - return redirectTo(path_to_authfail(request), request) | |
| 3204 + return redirectTo(path_to_authfail(request), request) | |
| 3205 elif request.path == '/cancel_shutdown': | |
| 3206 - if self.getAuthz(request).actionAllowed("cleanShutdown", request): | |
| 3207 - eventually(status.cancelCleanShutdown) | |
| 3208 - return redirectTo("/", request) | |
| 3209 - else: | |
| 3210 - return redirectTo(path_to_authfail(request), request) | |
| 3211 + return redirectTo(path_to_authfail(request), request) | |
| 3212 | |
| 3213 cxt.update( | |
| 3214 shutting_down = status.shuttingDown, | |
| 3215 | |
| 3216 | |
| 3217 Provide support for messageFormatter to create custom subject lines. | |
| 3218 --- a/third_party/buildbot_8_4p1/buildbot/status/mail.py | |
| 3219 +++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py | |
| 3220 @@ -587,7 +587,12 @@ class MailNotifier(base.StatusReceiverMultiService): | |
| 3221 build=build, results=build.results) | |
| 3222 msgdict['body'] += tmp['body'] | |
| 3223 msgdict['body'] += '\n\n' | |
| 3224 + # This is terrible. Whichever build is iterated over last will | |
| 3225 + # overwrite the subject and type set by all previous builds. | |
| 3226 + # But we never iterate over more than one build, so we'll | |
| 3227 + # just do this anyway and try not to lose any sleep over it. | |
| 3228 msgdict['type'] = tmp['type'] | |
| 3229 + if 'subject' in tmp: | |
| 3230 + msgdict['subject'] = tmp['subject'] | |
| 3231 | |
| 3232 m = self.createEmail(msgdict, name, self.master_status.getTitle(), | |
| 3233 results, builds, patches, logs) | |
| 3234 | |
| 3235 | |
| 3236 Change minimum cache size to 50 to prevent waterfall and console view bustage. | |
| 3237 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 3238 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 3239 @@ -796,7 +796,7 @@ class BuildMaster(service.MultiService): | |
| 3240 if self.autoBuildCacheRatio: | |
| 3241 builder_status = builder.builder_status | |
| 3242 slavecount = len(builder_status.slavenames) | |
| 3243 - max_size = max(self.autoBuildCacheRatio * slavecount, 30) | |
| 3244 + max_size = max(self.autoBuildCacheRatio * slavecount, 50) | |
| 3245 builder_status.buildCache.set_max_size(max_size) | |
| 3246 builder_status.buildCacheSize = max_size | |
| 3247 | |
| 3248 | |
| 3249 Cherry-pick ANSI color codes in logs from upstream buildbot. These commits were
manually | |
| 3250 resolved. | |
| 3251 | |
| 3252 ** https://github.com/buildbot/buildbot/commit/3a563189374737aa2ebf3f6467bf461
bc3ec5443 | |
| 3253 commit 3a563189374737aa2ebf3f6467bf461bc3ec5443 | |
| 3254 Author: Pierre Tardy <pierre.tardy@intel.com> | |
| 3255 Date: Mon Jul 15 18:48:35 2013 +0200 | |
| 3256 | |
| 3257 logs: implement on the fly parsing of ansi code | |
| 3258 | |
| 3259 grunt and js tools have the habit of using ansi | |
| 3260 code a lot, which are ugly inside python log window. | |
| 3261 http://en.wikipedia.org/wiki/ANSI_escape_code | |
| 3262 | |
| 3263 This is a simple enough implementation of ansicode parser | |
| 3264 which integrates well into buildbot's way of displaying logs | |
| 3265 | |
| 3266 Signed-off-by: Pierre Tardy <pierre.tardy@intel.com> | |
| 3267 | |
| 3268 diff --git a/master/buildbot/status/web/files/default.css b/master/buildbot/stat
us/web/files/default.css | |
| 3269 index d109be1..2d24c2b 100644 | |
| 3270 --- a/master/buildbot/status/web/files/default.css | |
| 3271 +++ b/master/buildbot/status/web/files/default.css | |
| 3272 @@ -564,6 +564,30 @@ span.stderr { | |
| 3273 span.header { | |
| 3274 color: blue; | |
| 3275 } | |
| 3276 +span.ansi30 { | |
| 3277 + color: black; | |
| 3278 +} | |
| 3279 +span.ansi31 { | |
| 3280 + color: red; | |
| 3281 +} | |
| 3282 +span.ansi32 { | |
| 3283 + color: green; | |
| 3284 +} | |
| 3285 +span.ansi33 { | |
| 3286 + color: orange; | |
| 3287 +} | |
| 3288 +span.ansi34 { | |
| 3289 + color: yellow; | |
| 3290 +} | |
| 3291 +span.ansi35 { | |
| 3292 + color: purple; | |
| 3293 +} | |
| 3294 +span.ansi36 { | |
| 3295 + color: blue; | |
| 3296 +} | |
| 3297 +span.ansi37 { | |
| 3298 + color: white; | |
| 3299 +} | |
| 3300 | |
| 3301 /* revision & email */ | |
| 3302 .revision .full { | |
| 3303 diff --git a/master/buildbot/status/web/logs.py b/master/buildbot/status/web/log
s.py | |
| 3304 index d7da811..b883dc2 100644 | |
| 3305 --- a/master/buildbot/status/web/logs.py | |
| 3306 +++ b/master/buildbot/status/web/logs.py | |
| 3307 @@ -24,6 +24,9 @@ from buildbot import interfaces | |
| 3308 from buildbot.status import logfile | |
| 3309 from buildbot.status.web.base import IHTMLLog, HtmlResource, path_to_root | |
| 3310 | |
| 3311 +import re | |
| 3312 + | |
| 3313 + | |
| 3314 class ChunkConsumer: | |
| 3315 implements(interfaces.IStatusLogConsumer) | |
| 3316 | |
| 3317 @@ -46,6 +49,7 @@ class ChunkConsumer: | |
| 3318 def finish(self): | |
| 3319 self.textlog.finished() | |
| 3320 | |
| 3321 +ANSI_RE = re.compile(r"^(\d*)(;(\d+))?([a-zA-Z])") | |
| 3322 | |
| 3323 # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname | |
| 3324 class TextLog(Resource): | |
| 3325 @@ -80,9 +84,26 @@ class TextLog(Resource): | |
| 3326 # jinja only works with unicode, or pure ascii, so assume utf-8
in logs | |
| 3327 if not isinstance(entry, unicode): | |
| 3328 entry = unicode(entry, 'utf-8', 'replace') | |
| 3329 - html_entries.append(dict(type = logfile.ChunkTypes[type], | |
| 3330 - text = entry, | |
| 3331 - is_header = is_header)) | |
| 3332 + first_entry = True | |
| 3333 + _type = logfile.ChunkTypes[type] | |
| 3334 + for ansi_entry in entry.split("\033["): | |
| 3335 + code = "" | |
| 3336 + if not first_entry: | |
| 3337 + res = ANSI_RE.search(ansi_entry) | |
| 3338 + if res: | |
| 3339 + mode = res.group(4) | |
| 3340 + ansi_entry = ansi_entry[len(res.group(0)):] | |
| 3341 + if mode == 'm': | |
| 3342 + code = " ansi" + res.group(1) | |
| 3343 + else: | |
| 3344 + # illegal code, restore the CSI | |
| 3345 + ansi_entry = "\033[" + ansi_entry | |
| 3346 + | |
| 3347 + html_entries.append(dict(type=_type + code, | |
| 3348 + text=ansi_entry, | |
| 3349 + is_header=is_header)) | |
| 3350 + first_entry = False | |
| 3351 + | |
| 3352 elif not is_header: | |
| 3353 text_data += entry | |
| 3354 | |
| 3355 ** https://github.com/buildbot/buildbot/commit/c451dd5c7cd377e774c4bf5c5d16d53
986a9ccbf | |
| 3356 commit c451dd5c7cd377e774c4bf5c5d16d53986a9ccbf | |
| 3357 Author: Pierre Tardy <pierre.tardy@intel.com> | |
| 3358 Date: Thu Aug 22 11:48:31 2013 +0200 | |
| 3359 | |
| 3360 ansi codes: add support for multiple sgr classes | |
| 3361 | |
| 3362 Add some unit tests and factorize for potencial reuse | |
| 3363 | |
| 3364 nine plan is to reuse the regex and overall idea, but probably | |
| 3365 rewrite this client side. | |
| 3366 | |
| 3367 Signed-off-by: Pierre Tardy <pierre.tardy@intel.com> | |
| 3368 | |
| 3369 diff --git a/master/buildbot/status/web/logs.py b/master/buildbot/status/web/log
s.py | |
| 3370 index b883dc2..37eaf29 100644 | |
| 3371 --- a/master/buildbot/status/web/logs.py | |
| 3372 +++ b/master/buildbot/status/web/logs.py | |
| 3373 @@ -23,9 +23,7 @@ from twisted.web.resource import Resource, NoResource | |
| 3374 from buildbot import interfaces | |
| 3375 from buildbot.status import logfile | |
| 3376 from buildbot.status.web.base import IHTMLLog, HtmlResource, path_to_root | |
| 3377 - | |
| 3378 -import re | |
| 3379 - | |
| 3380 +from buildbot.util.ansicodes import parse_ansi_sgr | |
| 3381 | |
| 3382 class ChunkConsumer: | |
| 3383 implements(interfaces.IStatusLogConsumer) | |
| 3384 @@ -49,8 +47,6 @@ class ChunkConsumer: | |
| 3385 def finish(self): | |
| 3386 self.textlog.finished() | |
| 3387 | |
| 3388 -ANSI_RE = re.compile(r"^(\d*)(;(\d+))?([a-zA-Z])") | |
| 3389 - | |
| 3390 # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname | |
| 3391 class TextLog(Resource): | |
| 3392 # a new instance of this Resource is created for each client who views | |
| 3393 @@ -77,7 +73,7 @@ class TextLog(Resource): | |
| 3394 if type >= len(logfile.ChunkTypes) or type < 0: | |
| 3395 # non-std channel, don't display | |
| 3396 continue | |
| 3397 - | |
| 3398 + | |
| 3399 is_header = type == logfile.HEADER | |
| 3400 | |
| 3401 if not self.asText: | |
| 3402 @@ -89,16 +85,9 @@ class TextLog(Resource): | |
| 3403 for ansi_entry in entry.split("\033["): | |
| 3404 code = "" | |
| 3405 if not first_entry: | |
| 3406 - res = ANSI_RE.search(ansi_entry) | |
| 3407 - if res: | |
| 3408 - mode = res.group(4) | |
| 3409 - ansi_entry = ansi_entry[len(res.group(0)):] | |
| 3410 - if mode == 'm': | |
| 3411 - code = " ansi" + res.group(1) | |
| 3412 - else: | |
| 3413 - # illegal code, restore the CSI | |
| 3414 - ansi_entry = "\033[" + ansi_entry | |
| 3415 - | |
| 3416 + ansi_entry, ansi_classes = parse_ansi_sgr(ansi_entry) | |
| 3417 + if ansi_classes: | |
| 3418 + code = "".join([" ansi" + i for i in ansi_classes]) | |
| 3419 html_entries.append(dict(type=_type + code, | |
| 3420 text=ansi_entry, | |
| 3421 is_header=is_header)) | |
| 3422 diff --git a/master/buildbot/test/unit/test_util_ansi_codes.py b/master/buildbot
/test/unit/test_util_ansi_codes.py | |
| 3423 new file mode 100644 | |
| 3424 index 0000000..f9ca02c | |
| 3425 --- /dev/null | |
| 3426 +++ b/master/buildbot/test/unit/test_util_ansi_codes.py | |
| 3427 @@ -0,0 +1,39 @@ | |
| 3428 +# This file is part of Buildbot. Buildbot is free software: you can | |
| 3429 +# redistribute it and/or modify it under the terms of the GNU General Public | |
| 3430 +# License as published by the Free Software Foundation, version 2. | |
| 3431 +# | |
| 3432 +# This program is distributed in the hope that it will be useful, but WITHOUT | |
| 3433 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 3434 +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
| 3435 +# details. | |
| 3436 +# | |
| 3437 +# You should have received a copy of the GNU General Public License along with | |
| 3438 +# this program; if not, write to the Free Software Foundation, Inc., 51 | |
| 3439 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 3440 +# | |
| 3441 +# Copyright Buildbot Team Members | |
| 3442 + | |
| 3443 +from twisted.trial import unittest | |
| 3444 +from buildbot.util.ansicodes import parse_ansi_sgr | |
| 3445 + | |
| 3446 + | |
| 3447 +class TestAnsiCodes(unittest.TestCase): | |
| 3448 + | |
| 3449 + def runTest(self, string, expected): | |
| 3450 + ret = parse_ansi_sgr(string) | |
| 3451 + self.assertEqual(ret, expected) | |
| 3452 + | |
| 3453 + def test_ansi1m(self): | |
| 3454 + self.runTest("33mfoo", ("foo", ["33"])) | |
| 3455 + | |
| 3456 + def test_ansi2m(self): | |
| 3457 + self.runTest("1;33mfoo", ("foo", ["1", "33"])) | |
| 3458 + | |
| 3459 + def test_ansi5m(self): | |
| 3460 + self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"])) | |
| 3461 + | |
| 3462 + def test_ansi_notm(self): | |
| 3463 + self.runTest("33xfoo", ("foo", [])) | |
| 3464 + | |
| 3465 + def test_ansi_invalid(self): | |
| 3466 + self.runTest("<>foo", ("\033[<>foo", [])) | |
| 3467 diff --git a/master/buildbot/util/ansicodes.py b/master/buildbot/util/ansicodes.
py | |
| 3468 new file mode 100644 | |
| 3469 index 0000000..bc58066 | |
| 3470 --- /dev/null | |
| 3471 +++ b/master/buildbot/util/ansicodes.py | |
| 3472 @@ -0,0 +1,36 @@ | |
| 3473 +# This file is part of Buildbot. Buildbot is free software: you can | |
| 3474 +# redistribute it and/or modify it under the terms of the GNU General Public | |
| 3475 +# License as published by the Free Software Foundation, version 2. | |
| 3476 +# | |
| 3477 +# This program is distributed in the hope that it will be useful, but WITHOUT | |
| 3478 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 3479 +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
| 3480 +# details. | |
| 3481 +# | |
| 3482 +# You should have received a copy of the GNU General Public License along with | |
| 3483 +# this program; if not, write to the Free Software Foundation, Inc., 51 | |
| 3484 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 3485 +# | |
| 3486 +# Copyright Buildbot Team Members | |
| 3487 + | |
| 3488 +import re | |
| 3489 + | |
| 3490 +ANSI_RE = re.compile(r"^((\d*)(;\d+)*)([a-zA-Z])") | |
| 3491 + | |
| 3492 + | |
| 3493 +def parse_ansi_sgr(ansi_entry): | |
| 3494 + """simple utility to extract ansi sgr (Select Graphic Rendition) codes, | |
| 3495 + and ignore other codes. | |
| 3496 + Invalid codes are restored | |
| 3497 + """ | |
| 3498 + classes = [] | |
| 3499 + res = ANSI_RE.search(ansi_entry) | |
| 3500 + if res: | |
| 3501 + mode = res.group(4) | |
| 3502 + ansi_entry = ansi_entry[len(res.group(0)):] | |
| 3503 + if mode == 'm': | |
| 3504 + classes = res.group(1).split(";") | |
| 3505 + else: | |
| 3506 + # illegal code, restore the CSI | |
| 3507 + ansi_entry = "\033[" + ansi_entry | |
| 3508 + return ansi_entry, classes | |
| 3509 | |
| 3510 ** https://github.com/buildbot/buildbot/commit/94e11e9888ad3933cd5c3fc61d7e78e
d3d0d79d1 | |
| 3511 commit 94e11e9888ad3933cd5c3fc61d7e78ed3d0d79d1 | |
| 3512 Author: Dustin J. Mitchell <dustin@mozilla.com> | |
| 3513 Date: Thu Sep 26 08:34:36 2013 -0400 | |
| 3514 | |
| 3515 render '\e[m' as a class-less SGR; fixes #2565 | |
| 3516 | |
| 3517 diff --git a/master/buildbot/test/unit/test_util_ansi_codes.py b/master/buildbot
/test/unit/test_util_ansi_codes.py | |
| 3518 deleted file mode 100644 | |
| 3519 index bde441f..0000000 | |
| 3520 --- a/master/buildbot/test/unit/test_util_ansi_codes.py | |
| 3521 +++ /dev/null | |
| 3522 @@ -1,42 +0,0 @@ | |
| 3523 -# This file is part of Buildbot. Buildbot is free software: you can | |
| 3524 -# redistribute it and/or modify it under the terms of the GNU General Public | |
| 3525 -# License as published by the Free Software Foundation, version 2. | |
| 3526 -# | |
| 3527 -# This program is distributed in the hope that it will be useful, but WITHOUT | |
| 3528 -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 3529 -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
| 3530 -# details. | |
| 3531 -# | |
| 3532 -# You should have received a copy of the GNU General Public License along with | |
| 3533 -# this program; if not, write to the Free Software Foundation, Inc., 51 | |
| 3534 -# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 3535 -# | |
| 3536 -# Copyright Buildbot Team Members | |
| 3537 - | |
| 3538 -from twisted.trial import unittest | |
| 3539 -from buildbot.util.ansicodes import parse_ansi_sgr | |
| 3540 - | |
| 3541 - | |
| 3542 -class TestAnsiCodes(unittest.TestCase): | |
| 3543 - | |
| 3544 - def runTest(self, string, expected): | |
| 3545 - ret = parse_ansi_sgr(string) | |
| 3546 - self.assertEqual(ret, expected) | |
| 3547 - | |
| 3548 - def test_ansi1m(self): | |
| 3549 - self.runTest("33mfoo", ("foo", ["33"])) | |
| 3550 - | |
| 3551 - def test_ansi2m(self): | |
| 3552 - self.runTest("1;33mfoo", ("foo", ["1", "33"])) | |
| 3553 - | |
| 3554 - def test_ansi5m(self): | |
| 3555 - self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"])) | |
| 3556 - | |
| 3557 - def test_ansi_notm(self): | |
| 3558 - self.runTest("33xfoo", ("foo", [])) | |
| 3559 - | |
| 3560 - def test_ansi_invalid(self): | |
| 3561 - self.runTest("<>foo", ("\033[<>foo", [])) | |
| 3562 - | |
| 3563 - def test_ansi_invalid_start_by_semicolon(self): | |
| 3564 - self.runTest(";3m", ("\033[;3m", [])) | |
| 3565 diff --git a/master/buildbot/test/unit/test_util_ansicodes.py b/master/buildbot/
test/unit/test_util_ansicodes.py | |
| 3566 new file mode 100644 | |
| 3567 index 0000000..e0d1926 | |
| 3568 --- /dev/null | |
| 3569 +++ b/master/buildbot/test/unit/test_util_ansicodes.py | |
| 3570 @@ -0,0 +1,45 @@ | |
| 3571 +# This file is part of Buildbot. Buildbot is free software: you can | |
| 3572 +# redistribute it and/or modify it under the terms of the GNU General Public | |
| 3573 +# License as published by the Free Software Foundation, version 2. | |
| 3574 +# | |
| 3575 +# This program is distributed in the hope that it will be useful, but WITHOUT | |
| 3576 +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| 3577 +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
| 3578 +# details. | |
| 3579 +# | |
| 3580 +# You should have received a copy of the GNU General Public License along with | |
| 3581 +# this program; if not, write to the Free Software Foundation, Inc., 51 | |
| 3582 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| 3583 +# | |
| 3584 +# Copyright Buildbot Team Members | |
| 3585 + | |
| 3586 +from twisted.trial import unittest | |
| 3587 +from buildbot.util.ansicodes import parse_ansi_sgr | |
| 3588 + | |
| 3589 + | |
| 3590 +class TestAnsiCodes(unittest.TestCase): | |
| 3591 + | |
| 3592 + def runTest(self, string, expected): | |
| 3593 + ret = parse_ansi_sgr(string) | |
| 3594 + self.assertEqual(ret, expected) | |
| 3595 + | |
| 3596 + def test_ansi0m(self): | |
| 3597 + self.runTest("mfoo", ("foo", [])) | |
| 3598 + | |
| 3599 + def test_ansi1m(self): | |
| 3600 + self.runTest("33mfoo", ("foo", ["33"])) | |
| 3601 + | |
| 3602 + def test_ansi2m(self): | |
| 3603 + self.runTest("1;33mfoo", ("foo", ["1", "33"])) | |
| 3604 + | |
| 3605 + def test_ansi5m(self): | |
| 3606 + self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"])) | |
| 3607 + | |
| 3608 + def test_ansi_notm(self): | |
| 3609 + self.runTest("33xfoo", ("foo", [])) | |
| 3610 + | |
| 3611 + def test_ansi_invalid(self): | |
| 3612 + self.runTest("<>foo", ("\033[<>foo", [])) | |
| 3613 + | |
| 3614 + def test_ansi_invalid_start_by_semicolon(self): | |
| 3615 + self.runTest(";3m", ("\033[;3m", [])) | |
| 3616 diff --git a/master/buildbot/util/ansicodes.py b/master/buildbot/util/ansicodes.
py | |
| 3617 index ebcf95f..01054ae 100644 | |
| 3618 --- a/master/buildbot/util/ansicodes.py | |
| 3619 +++ b/master/buildbot/util/ansicodes.py | |
| 3620 @@ -15,7 +15,7 @@ | |
| 3621 | |
| 3622 import re | |
| 3623 | |
| 3624 -ANSI_RE = re.compile(r"^((\d+)(;\d+)*)([a-zA-Z])") | |
| 3625 +ANSI_RE = re.compile(r"^((\d+)(;\d+)*)?([a-zA-Z])") | |
| 3626 | |
| 3627 | |
| 3628 def parse_ansi_sgr(ansi_entry): | |
| 3629 @@ -29,7 +29,11 @@ def parse_ansi_sgr(ansi_entry): | |
| 3630 mode = res.group(4) | |
| 3631 ansi_entry = ansi_entry[len(res.group(0)):] | |
| 3632 if mode == 'm': | |
| 3633 - classes = res.group(1).split(";") | |
| 3634 + classes = res.group(1) | |
| 3635 + if classes: | |
| 3636 + classes = res.group(1).split(";") | |
| 3637 + else: | |
| 3638 + classes = [] | |
| 3639 else: | |
| 3640 # illegal code, restore the CSI | |
| 3641 ansi_entry = "\033[" + ansi_entry | |
| 3642 ] | |
| 3643 | |
| 3644 | |
| 3645 GitPoller used .split() instead of .split('\n') to split files. As a | |
| 3646 result, it parsed a file with a space in the name as separate files. | |
| 3647 | |
| 3648 --- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3649 +++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3650 @@ -200,7 +200,7 @@ class GitPoller(base.PollingChangeSource): | |
| 3651 args = ['log', rev, '--name-only', '--no-walk', r'--format=%n'] | |
| 3652 d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os
.environ, errortoo=False ) | |
| 3653 def process(git_output): | |
| 3654 - fileList = git_output.split() | |
| 3655 + fileList = git_output.splitlines() | |
| 3656 return fileList | |
| 3657 d.addCallback(process) | |
| 3658 return d | |
| 3659 | |
| 3660 | |
| 3661 Cherry-pick change to allow serialization of sqlite access to de-flake unittests
. | |
| 3662 See https://github.com/buildbot/buildbot/commit/2c58b952ae93c45ee5bfe37f6cf70043
da8b5d70. | |
| 3663 | |
| 3664 diff --git a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py b/third_pa
rty/buildbot_8_4p1/buildbot/db/enginestrategy.py | |
| 3665 index 3d05cb6..cb711ce 100644 | |
| 3666 --- a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py | |
| 3667 +++ b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py | |
| 3668 @@ -82,6 +82,11 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStra
tegy): | |
| 3669 kwargs['pool_size'] = 1 | |
| 3670 max_conns = 1 | |
| 3671 | |
| 3672 + # allow serializing access to the db | |
| 3673 + if 'serialize_access' in u.query: | |
| 3674 + u.query.pop('serialize_access') | |
| 3675 + max_conns = 1 | |
| 3676 + | |
| 3677 return u, kwargs, max_conns | |
| 3678 | |
| 3679 def set_up_sqlite_engine(self, u, engine): | |
| 3680 | |
| 3681 Enable serialized access from the above patch. | |
| 3682 | |
| 3683 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 3684 index cbebc0f..9a1b6d0 100644 | |
| 3685 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 3686 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 3687 @@ -315,7 +315,8 @@ class BuildMaster(service.MultiService): | |
| 3688 #change_source = config['change_source'] | |
| 3689 | |
| 3690 # optional | |
| 3691 - db_url = config.get("db_url", "sqlite:///state.sqlite") | |
| 3692 + db_url = config.get("db_url", | |
| 3693 + "sqlite:///state.sqlite?serialize_access=1"
) | |
| 3694 db_poll_interval = config.get("db_poll_interval", None) | |
| 3695 debugPassword = config.get('debugPassword') | |
| 3696 manhole = config.get('manhole') | |
| 3697 | |
| 3698 Fix to file parsing. GitPoller produced blank lines | |
| 3699 | |
| 3700 --- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3701 +++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3702 @@ -200,7 +200,8 @@ class GitPoller(base.PollingChangeSource): | |
| 3703 args = ['log', rev, '--name-only', '--no-walk', r'--format=%n'] | |
| 3704 d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os
.environ, errortoo=False ) | |
| 3705 def process(git_output): | |
| 3706 - fileList = git_output.splitlines() | |
| 3707 + fileList = [f.strip() for f in git_output.splitlines()] | |
| 3708 + fileList = [f for f in fileList if f] | |
| 3709 return fileList | |
| 3710 d.addCallback(process) | |
| 3711 return d | |
| 3712 | |
| 3713 GitPoller: made self.master.addChange call overridable, so it can be used in | |
| 3714 TryJobGit. | |
| 3715 | |
| 3716 diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_pa
rty/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3717 index 0b6d388..d5c18ba 100644 | |
| 3718 --- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3719 +++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py | |
| 3720 @@ -282,21 +282,31 @@ class GitPoller(base.PollingChangeSource): | |
| 3721 revlink = self.revlinktmpl % urllib.quote_plus(rev) | |
| 3722 | |
| 3723 timestamp, name, files, comments = [ r[1] for r in results ] | |
| 3724 - d = self.master.addChange( | |
| 3725 + d = self.add_change( | |
| 3726 author=name, | |
| 3727 revision=rev, | |
| 3728 files=files, | |
| 3729 comments=comments, | |
| 3730 when_timestamp=epoch2datetime(timestamp), | |
| 3731 - branch=self.branch, | |
| 3732 - category=self.category, | |
| 3733 - project=self.project, | |
| 3734 - repository=self.repourl, | |
| 3735 revlink=revlink) | |
| 3736 wfd = defer.waitForDeferred(d) | |
| 3737 yield wfd | |
| 3738 results = wfd.getResult() | |
| 3739 | |
| 3740 + def add_change(self, author, revision, files, comments, when_timestamp, | |
| 3741 + revlink): | |
| 3742 + return self.master.addChange( | |
| 3743 + author=author, | |
| 3744 + revision=revision, | |
| 3745 + files=files, | |
| 3746 + comments=comments, | |
| 3747 + when_timestamp=when_timestamp, | |
| 3748 + branch=self.branch, | |
| 3749 + category=self.category, | |
| 3750 + project=self.project, | |
| 3751 + repository=self.repourl, | |
| 3752 + revlink=revlink) | |
| 3753 + | |
| 3754 def _process_changes_failure(self, f): | |
| 3755 log.msg('gitpoller: repo poll failed') | |
| 3756 log.err(f) | |
| 3757 | |
| 3758 | |
| 3759 Fix broken links to step names with '/' characters in them. | |
| 3760 | |
| 3761 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_par
ty/buildbot_8_4p1/buildbot/status/web/build.py | |
| 3762 index c634060..711d36a 100644 | |
| 3763 --- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py | |
| 3764 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py | |
| 3765 @@ -115,7 +115,8 @@ class StatusResourceBuild(HtmlResource): | |
| 3766 | |
| 3767 cxt['steps'].append(step) | |
| 3768 | |
| 3769 - step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName())
) | |
| 3770 + step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName(), | |
| 3771 + safe='')) | |
| 3772 step['text'] = " ".join(s.getText()) | |
| 3773 step['urls'] = map(lambda x:dict(url=x[1],logname=x[0]), s.getURLs(
).items()) | |
| 3774 | |
| 3775 @@ -123,7 +124,7 @@ class StatusResourceBuild(HtmlResource): | |
| 3776 for l in s.getLogs(): | |
| 3777 logname = l.getName() | |
| 3778 step['logs'].append({ 'link': req.childLink("steps/%s/logs/%s"
% | |
| 3779 - (urllib.quote(s.getName()), | |
| 3780 + (urllib.quote(s.getName(), safe=''), | |
| 3781 urllib.quote(logname))), | |
| 3782 'name': logname }) | |
| 3783 | |
| 3784 | |
| 3785 Backport BuildsetsConnectComponent.getRecentBuildsets from upstream | |
| 3786 (1ee6d421be2ea814c11757263eb43152f8c3928e). | |
| 3787 | |
| 3788 Index: third_party/buildbot_8_4p1/buildbot/db/buildsets.py | |
| 3789 diff --git a/third_party/buildbot_8_4p1/buildbot/db/buildsets.py b/third_party/b
uildbot_8_4p1/buildbot/db/buildsets.py | |
| 3790 index e70a51242ca09ba7ec7034e2092f4053c3d0332c..c90af5bec8eb57e5075858623fcc5d59
a62eadd7 100644 | |
| 3791 --- a/third_party/buildbot_8_4p1/buildbot/db/buildsets.py | |
| 3792 +++ b/third_party/buildbot_8_4p1/buildbot/db/buildsets.py | |
| 3793 @@ -190,6 +190,35 @@ class BuildsetsConnectorComponent(base.DBConnectorComponent
): | |
| 3794 return [ self._row2dict(row) for row in res.fetchall() ] | |
| 3795 return self.db.pool.do(thd) | |
| 3796 | |
| 3797 + def getRecentBuildsets(self, count, branch=None, repository=None, | |
| 3798 + complete=None): | |
| 3799 + def thd(conn): | |
| 3800 + bs_tbl = self.db.model.buildsets | |
| 3801 + ch_tbl = self.db.model.changes | |
| 3802 + j = sa.join(self.db.model.buildsets, | |
| 3803 + self.db.model.sourcestamps) | |
| 3804 + j = j.join(self.db.model.sourcestamp_changes) | |
| 3805 + j = j.join(ch_tbl) | |
| 3806 + q = sa.select(columns=[bs_tbl], from_obj=[j], | |
| 3807 + distinct=True) | |
| 3808 + q = q.order_by(sa.desc(bs_tbl.c.id)) | |
| 3809 + q = q.limit(count) | |
| 3810 + | |
| 3811 + if complete is not None: | |
| 3812 + if complete: | |
| 3813 + q = q.where(bs_tbl.c.complete != 0) | |
| 3814 + else: | |
| 3815 + q = q.where((bs_tbl.c.complete == 0) | | |
| 3816 + (bs_tbl.c.complete == None)) | |
| 3817 + if branch: | |
| 3818 + q = q.where(ch_tbl.c.branch == branch) | |
| 3819 + if repository: | |
| 3820 + q = q.where(ch_tbl.c.repository == repository) | |
| 3821 + res = conn.execute(q) | |
| 3822 + return list(reversed([self._row2dict(row) | |
| 3823 + for row in res.fetchall()])) | |
| 3824 + return self.db.pool.do(thd) | |
| 3825 + | |
| 3826 def getBuildsetProperties(self, buildsetid): | |
| 3827 """ | |
| 3828 Return the properties for a buildset, in the same format they were | |
| 3829 Index: third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py | |
| 3830 diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
b/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py | |
| 3831 index a6d4253cae43572992eac8d73b24cead97f63d9e..d28bcc61a72fd875af51c533b4ae1891
72225041 100644 | |
| 3832 --- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py | |
| 3833 +++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py | |
| 3834 @@ -441,3 +441,91 @@ class TestBuildsetsConnectorComponent( | |
| 3835 d.addCallbacks(cb, eb) | |
| 3836 return d | |
| 3837 | |
| 3838 + def insert_test_getRecentBuildsets_data(self): | |
| 3839 + return self.insertTestData([ | |
| 3840 + fakedb.Change(changeid=91, branch='branch_a', repository='repo_a'), | |
| 3841 + fakedb.SourceStamp(id=91, branch='branch_a', repository='repo_a'), | |
| 3842 + fakedb.SourceStampChange(sourcestampid=91, changeid=91), | |
| 3843 + | |
| 3844 + fakedb.Buildset(id=91, sourcestampid=91, complete=0, | |
| 3845 + complete_at=298297875, results=-1, submitted_at=266761875, | |
| 3846 + external_idstring='extid', reason='rsn1'), | |
| 3847 + fakedb.Buildset(id=92, sourcestampid=91, complete=1, | |
| 3848 + complete_at=298297876, results=7, submitted_at=266761876, | |
| 3849 + external_idstring='extid', reason='rsn2'), | |
| 3850 + | |
| 3851 + # buildset unrelated to the change | |
| 3852 + fakedb.Buildset(id=93, sourcestampid=1, complete=1, | |
| 3853 + complete_at=298297877, results=7, submitted_at=266761877, | |
| 3854 + external_idstring='extid', reason='rsn2'), | |
| 3855 + ]) | |
| 3856 + | |
| 3857 + def test_getRecentBuildsets_all(self): | |
| 3858 + d = self.insert_test_getRecentBuildsets_data() | |
| 3859 + d.addCallback(lambda _ : | |
| 3860 + self.db.buildsets.getRecentBuildsets(2, branch='branch_a', | |
| 3861 + repository='repo_a')) | |
| 3862 + def check(bsdictlist): | |
| 3863 + self.assertEqual(bsdictlist, [ | |
| 3864 + dict(external_idstring='extid', reason='rsn1', sourcestampid=91, | |
| 3865 + submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 15, | |
| 3866 + tzinfo=UTC), | |
| 3867 + complete_at=datetime.datetime(1979, 6, 15, 12, 31, 15, | |
| 3868 + tzinfo=UTC), | |
| 3869 + complete=False, results=-1, bsid=91), | |
| 3870 + dict(external_idstring='extid', reason='rsn2', sourcestampid=91, | |
| 3871 + submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 16, | |
| 3872 + tzinfo=UTC), | |
| 3873 + complete_at=datetime.datetime(1979, 6, 15, 12, 31, 16, | |
| 3874 + tzinfo=UTC), | |
| 3875 + complete=True, results=7, bsid=92), | |
| 3876 + ]) | |
| 3877 + d.addCallback(check) | |
| 3878 + return d | |
| 3879 + | |
| 3880 + def test_getRecentBuildsets_one(self): | |
| 3881 + d = self.insert_test_getRecentBuildsets_data() | |
| 3882 + d.addCallback(lambda _ : | |
| 3883 + self.db.buildsets.getRecentBuildsets(1, branch='branch_a', | |
| 3884 + repository='repo_a')) | |
| 3885 + def check(bsdictlist): | |
| 3886 + self.assertEqual(bsdictlist, [ | |
| 3887 + dict(external_idstring='extid', reason='rsn2', sourcestampid=91, | |
| 3888 + submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 16, | |
| 3889 + tzinfo=UTC), | |
| 3890 + complete_at=datetime.datetime(1979, 6, 15, 12, 31, 16, | |
| 3891 + tzinfo=UTC), | |
| 3892 + complete=True, results=7, bsid=92), | |
| 3893 + ]) | |
| 3894 + d.addCallback(check) | |
| 3895 + return d | |
| 3896 + | |
| 3897 + def test_getRecentBuildsets_zero(self): | |
| 3898 + d = self.insert_test_getRecentBuildsets_data() | |
| 3899 + d.addCallback(lambda _ : | |
| 3900 + self.db.buildsets.getRecentBuildsets(0, branch='branch_a', | |
| 3901 + repository='repo_a')) | |
| 3902 + def check(bsdictlist): | |
| 3903 + self.assertEqual(bsdictlist, []) | |
| 3904 + d.addCallback(check) | |
| 3905 + return d | |
| 3906 + | |
| 3907 + def test_getRecentBuildsets_noBranchMatch(self): | |
| 3908 + d = self.insert_test_getRecentBuildsets_data() | |
| 3909 + d.addCallback(lambda _ : | |
| 3910 + self.db.buildsets.getRecentBuildsets(2, branch='bad_branch', | |
| 3911 + repository='repo_a')) | |
| 3912 + def check(bsdictlist): | |
| 3913 + self.assertEqual(bsdictlist, []) | |
| 3914 + d.addCallback(check) | |
| 3915 + return d | |
| 3916 + | |
| 3917 + def test_getRecentBuildsets_noRepoMatch(self): | |
| 3918 + d = self.insert_test_getRecentBuildsets_data() | |
| 3919 + d.addCallback(lambda _ : | |
| 3920 + self.db.buildsets.getRecentBuildsets(2, branch='branch_a', | |
| 3921 + repository='bad_repo')) | |
| 3922 + def check(bsdictlist): | |
| 3923 + self.assertEqual(bsdictlist, []) | |
| 3924 + d.addCallback(check) | |
| 3925 + return d | |
| 3926 | |
| 3927 Date: Mon Jun 9 15:17:46 2014 -0700 | |
| 3928 | |
| 3929 Buildbot fix in triggerable.py | |
| 3930 | |
| 3931 The _waiters field is a dictionary, but it is treated as a list. | |
| 3932 | |
| 3933 R=agable@chromium.org | |
| 3934 BUG=382693 | |
| 3935 | |
| 3936 diff --git a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py b/thi
rd_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py | |
| 3937 index 0619977..19d6c4d 100644 | |
| 3938 --- a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py | |
| 3939 +++ b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py | |
| 3940 @@ -62,7 +62,7 @@ class Triggerable(base.BaseScheduler): | |
| 3941 # and errback any outstanding deferreds | |
| 3942 if self._waiters: | |
| 3943 msg = 'Triggerable scheduler stopped before build was complete' | |
| 3944 - for d in self._waiters: | |
| 3945 + for d in self._waiters.itervalues(): | |
| 3946 d.errback(failure.Failure(RuntimeError(msg))) | |
| 3947 self._waiters = {} | |
| 3948 | |
| 3949 | |
| 3950 Date: 2014-07-16 09:52:34 UTC | |
| 3951 Added logging to the status_json.py | |
| 3952 | |
| 3953 BUG=393856 | |
| 3954 R=agable@chromium.org | |
| 3955 | |
| 3956 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 3957 index e7cd932d10a0f874175a8b03bd0a9e29436e2278..1047dd55a791479f7be6143bc5992890
eb6c4437 100644 | |
| 3958 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 3959 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 3960 @@ -22,6 +22,7 @@ import os | |
| 3961 import re | |
| 3962 | |
| 3963 from twisted.internet import defer | |
| 3964 +from twisted.python import log as twlog | |
| 3965 from twisted.web import html, resource, server | |
| 3966 | |
| 3967 from buildbot.status.web.base import HtmlResource | |
| 3968 @@ -162,6 +163,10 @@ class JsonResource(resource.Resource): | |
| 3969 | |
| 3970 def render_GET(self, request): | |
| 3971 """Renders a HTTP GET at the http request level.""" | |
| 3972 + userAgent = request.requestHeaders.getRawHeaders( | |
| 3973 + 'user-agent', ['unknown'])[0] | |
| 3974 + twlog.msg('Received request for %s from %s, id: %s' % | |
| 3975 + (request.uri, userAgent, id(request))) | |
| 3976 d = defer.maybeDeferred(lambda : self.content(request)) | |
| 3977 def handle(data): | |
| 3978 if isinstance(data, unicode): | |
| 3979 @@ -183,6 +188,7 @@ class JsonResource(resource.Resource): | |
| 3980 return data | |
| 3981 d.addCallback(handle) | |
| 3982 def ok(data): | |
| 3983 + twlog.msg('Finished processing request with id: %s' % id(request)) | |
| 3984 request.write(data) | |
| 3985 request.finish() | |
| 3986 def fail(f): | |
| 3987 | |
| 3988 Date: Thu Jul 31 07:57:54 2014 -0400 | |
| 3989 | |
| 3990 Console page: do revision filtering in the database instead of afterward | |
| 3991 | |
| 3992 Part of a set of changes split out from https://codereview.chromium.org/4175
13003/ | |
| 3993 | |
| 3994 diff --git a/third_party/buildbot_8_4p1/buildbot/db/changes.py b/third_party/bui
ldbot_8_4p1/buildbot/db/changes.py | |
| 3995 index a5b6482..c6516b6 100644 | |
| 3996 --- a/third_party/buildbot_8_4p1/buildbot/db/changes.py | |
| 3997 +++ b/third_party/buildbot_8_4p1/buildbot/db/changes.py | |
| 3998 @@ -194,7 +194,8 @@ class ChangesConnectorComponent(base.DBConnectorComponent): | |
| 3999 d = self.db.pool.do(thd) | |
| 4000 return d | |
| 4001 | |
| 4002 - def getRecentChanges(self, count): | |
| 4003 + def getRecentChanges(self, count, repository=None, author=None, | |
| 4004 + branch=None): | |
| 4005 """ | |
| 4006 Get a list of the C{count} most recent changes, represented as | |
| 4007 dictionaies; returns fewer if that many do not exist. | |
| 4008 @@ -207,8 +208,15 @@ class ChangesConnectorComponent(base.DBConnectorComponent): | |
| 4009 # get the changeids from the 'changes' table | |
| 4010 changes_tbl = self.db.model.changes | |
| 4011 q = sa.select([changes_tbl.c.changeid], | |
| 4012 - order_by=[sa.desc(changes_tbl.c.changeid)], | |
| 4013 - limit=count) | |
| 4014 + order_by=[sa.desc(changes_tbl.c.changeid)]) | |
| 4015 + if repository: | |
| 4016 + q = q.where(changes_tbl.c.repository == repository) | |
| 4017 + if author: | |
| 4018 + q = q.where(changes_tbl.c.author == author) | |
| 4019 + if branch: | |
| 4020 + q = q.where(changes_tbl.c.branch == branch) | |
| 4021 + if count: | |
| 4022 + q = q.limit(count) | |
| 4023 rp = conn.execute(q) | |
| 4024 changeids = [ row.changeid for row in rp ] | |
| 4025 rp.close() | |
| 4026 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4027 index de922a4..fd4bd11 100644 | |
| 4028 --- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4029 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4030 @@ -173,9 +173,10 @@ class ConsoleStatusResource(HtmlResource): | |
| 4031 Every change is a line in the page, and it shows the result of the first | |
| 4032 build with this change for each slave.""" | |
| 4033 | |
| 4034 - def __init__(self, orderByTime=False): | |
| 4035 + def __init__(self, orderByTime=False, repository=None): | |
| 4036 HtmlResource.__init__(self) | |
| 4037 | |
| 4038 + self.repository = repository | |
| 4039 self.status = None | |
| 4040 self.cache = CacheStatus() | |
| 4041 | |
| 4042 @@ -244,11 +245,11 @@ class ConsoleStatusResource(HtmlResource): | |
| 4043 return allChanges | |
| 4044 | |
| 4045 @defer.deferredGenerator | |
| 4046 - def getAllChanges(self, request, status, debugInfo): | |
| 4047 + def getAllChanges(self, request, status, debugInfo, **kwargs): | |
| 4048 master = request.site.buildbot_service.master | |
| 4049 limit = min(100, max(1, int(request.args.get('limit', [25])[0]))) | |
| 4050 wfd = defer.waitForDeferred( | |
| 4051 - master.db.changes.getRecentChanges(limit)) | |
| 4052 + master.db.changes.getRecentChanges(limit, **kwargs)) | |
| 4053 yield wfd | |
| 4054 chdicts = wfd.getResult() | |
| 4055 | |
| 4056 @@ -762,19 +763,14 @@ class ConsoleStatusResource(HtmlResource): | |
| 4057 | |
| 4058 # Get all changes we can find. This is a DB operation, so it must use | |
| 4059 # a deferred. | |
| 4060 - d = self.getAllChanges(request, status, debugInfo) | |
| 4061 + filter_branch = None if branch == ANYBRANCH else branch | |
| 4062 + filter_repo = repository if repository else self.repository | |
| 4063 + filter_author = devName[0] if devName else None | |
| 4064 + d = self.getAllChanges(request, status, debugInfo, author=filter_author
, | |
| 4065 + branch=filter_branch, repository=filter_repo) | |
| 4066 def got_changes(allChanges): | |
| 4067 debugInfo["source_all"] = len(allChanges) | |
| 4068 - | |
| 4069 - revFilter = {} | |
| 4070 - if branch != ANYBRANCH: | |
| 4071 - revFilter['branch'] = branch | |
| 4072 - if devName: | |
| 4073 - revFilter['who'] = devName | |
| 4074 - if repository: | |
| 4075 - revFilter['repository'] = repository | |
| 4076 - revisions = list(self.filterRevisions(allChanges, max_revs=numRevs, | |
| 4077 - filter=revFilter)) | |
| 4078 + revisions = list(self.filterRevisions(allChanges, max_revs=numRevs)
) | |
| 4079 debugInfo["revision_final"] = len(revisions) | |
| 4080 | |
| 4081 # Fetch all the builds for all builders until we get the next build | |
| 4082 | |
| 4083 Date: Wed Jul 30 12:11:07 2014 -0400 | |
| 4084 | |
| 4085 Add optional builder filter function to console page | |
| 4086 | |
| 4087 This is needed for client.skia, where we want to hide trybots from the conso
le. | |
| 4088 | |
| 4089 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4090 index fd4bd11..60383f4 100644 | |
| 4091 --- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4092 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4093 @@ -173,10 +173,12 @@ class ConsoleStatusResource(HtmlResource): | |
| 4094 Every change is a line in the page, and it shows the result of the first | |
| 4095 build with this change for each slave.""" | |
| 4096 | |
| 4097 - def __init__(self, orderByTime=False, repository=None): | |
| 4098 + def __init__(self, orderByTime=False, repository=None, | |
| 4099 + builder_filter_fn=lambda builderName: True): | |
| 4100 HtmlResource.__init__(self) | |
| 4101 | |
| 4102 self.repository = repository | |
| 4103 + self.builder_filter_fn = builder_filter_fn | |
| 4104 self.status = None | |
| 4105 self.cache = CacheStatus() | |
| 4106 | |
| 4107 @@ -228,6 +230,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 4108 allChanges = list() | |
| 4109 build_count = 0 | |
| 4110 for builderName in status.getBuilderNames()[:]: | |
| 4111 + if not self.builder_filter_fn(builderName): | |
| 4112 + continue | |
| 4113 if build_count > max_builds: | |
| 4114 break | |
| 4115 | |
| 4116 @@ -407,6 +411,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 4117 continue | |
| 4118 if builders and builderName not in builders: | |
| 4119 continue | |
| 4120 + if not self.builder_filter_fn(builderName): | |
| 4121 + continue | |
| 4122 | |
| 4123 # We want to display this builder. | |
| 4124 category = builder.category or "default" | |
| 4125 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 4126 index 4a4f19d..92c9544 100644 | |
| 4127 --- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 4128 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 4129 @@ -132,7 +132,8 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin): | |
| 4130 | |
| 4131 cxt['authz'] = self.getAuthz(req) | |
| 4132 cxt['builder_url'] = path_to_builder(req, b) | |
| 4133 - | |
| 4134 + cxt['mastername'] = ( | |
| 4135 + req.site.buildbot_service.master.properties['mastername']) | |
| 4136 template = req.site.buildbot_service.templates.get_template("builder.ht
ml") | |
| 4137 yield template.render(**cxt) | |
| 4138 | |
| 4139 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4140 index 60383f4..2d48aca 100644 | |
| 4141 --- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4142 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 4143 @@ -77,14 +77,15 @@ class ANYBRANCH: pass # a flag value, used below | |
| 4144 | |
| 4145 class CachedStatusBox(object): | |
| 4146 """Basic data class to remember the information for a box on the console.""
" | |
| 4147 - def __init__(self, color, pageTitle, details, url, tag, builderName): | |
| 4148 + def __init__(self, color, pageTitle, details, url, tag, builderName, | |
| 4149 + buildNumber=None): | |
| 4150 self.color = color | |
| 4151 self.pageTitle = pageTitle | |
| 4152 self.details = details | |
| 4153 self.url = url | |
| 4154 self.tag = tag | |
| 4155 self.builderName = builderName | |
| 4156 - | |
| 4157 + self.buildNumber = buildNumber | |
| 4158 | |
| 4159 class CacheStatus(object): | |
| 4160 """Basic cache of CachedStatusBox based on builder names and revisions. | |
| 4161 @@ -105,9 +106,11 @@ class CacheStatus(object): | |
| 4162 self.allBoxes[builder][revision].color) | |
| 4163 return data | |
| 4164 | |
| 4165 - def insert(self, builderName, revision, color, pageTitle, details, url, tag
): | |
| 4166 + def insert(self, builderName, revision, color, pageTitle, details, url, | |
| 4167 + tag, buildNumber=None): | |
| 4168 """Insert a new build into the cache.""" | |
| 4169 - box = CachedStatusBox(color, pageTitle, details, url, tag, builderName) | |
| 4170 + box = CachedStatusBox(color, pageTitle, details, url, tag, builderName, | |
| 4171 + buildNumber) | |
| 4172 if not self.allBoxes.get(builderName): | |
| 4173 self.allBoxes[builderName] = {} | |
| 4174 | |
| 4175 @@ -285,7 +288,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4176 details = {} | |
| 4177 if not build.getLogs(): | |
| 4178 return details | |
| 4179 - | |
| 4180 + | |
| 4181 for step in build.getSteps(): | |
| 4182 (result, reason) = step.getResults() | |
| 4183 if result == builder.FAILURE: | |
| 4184 @@ -294,7 +297,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4185 # Remove html tags from the error text. | |
| 4186 stripHtml = re.compile(r'<.*?>') | |
| 4187 strippedDetails = stripHtml.sub('', ' '.join(step.getText())) | |
| 4188 - | |
| 4189 + | |
| 4190 details['buildername'] = builderName | |
| 4191 details['status'] = strippedDetails | |
| 4192 details['reason'] = reason | |
| 4193 @@ -304,7 +307,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4194 for log in step.getLogs(): | |
| 4195 logname = log.getName() | |
| 4196 logurl = request.childLink( | |
| 4197 - "../builders/%s/builds/%s/steps/%s/logs/%s" % | |
| 4198 + "../builders/%s/builds/%s/steps/%s/logs/%s" % | |
| 4199 (urllib.quote(builderName, safe=''), | |
| 4200 build.getNumber(), | |
| 4201 urllib.quote(name, safe=''), | |
| 4202 @@ -371,7 +374,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4203 def getChangeForBuild(self, build, revision): | |
| 4204 if not build or not build.getChanges(): # Forced build | |
| 4205 return DevBuild(revision, build, None) | |
| 4206 - | |
| 4207 + | |
| 4208 for change in build.getChanges(): | |
| 4209 if change.revision == revision: | |
| 4210 return change | |
| 4211 @@ -380,7 +383,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4212 changes = list(build.getChanges()) | |
| 4213 changes.sort(key=self.comparator.getSortingKey()) | |
| 4214 return changes[-1] | |
| 4215 - | |
| 4216 + | |
| 4217 def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds, | |
| 4218 categories, builders, debugInfo): | |
| 4219 """Returns a dictionary of builds we need to inspect to be able to | |
| 4220 @@ -451,9 +454,9 @@ class ConsoleStatusResource(HtmlResource): | |
| 4221 | |
| 4222 categories = builderList.keys() | |
| 4223 categories.sort() | |
| 4224 - | |
| 4225 + | |
| 4226 cs = [] | |
| 4227 - | |
| 4228 + | |
| 4229 for category in categories: | |
| 4230 c = {} | |
| 4231 | |
| 4232 @@ -526,14 +529,14 @@ class ConsoleStatusResource(HtmlResource): | |
| 4233 # Sort the categories. | |
| 4234 categories = builderList.keys() | |
| 4235 categories.sort() | |
| 4236 - | |
| 4237 + | |
| 4238 builds = {} | |
| 4239 - | |
| 4240 + | |
| 4241 # Display the boxes by category group. | |
| 4242 for category in categories: | |
| 4243 - | |
| 4244 + | |
| 4245 builds[category] = [] | |
| 4246 - | |
| 4247 + | |
| 4248 # Display the boxes for each builder in this category. | |
| 4249 for builder in builderList[category]: | |
| 4250 introducedIn = None | |
| 4251 @@ -549,6 +552,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4252 b["color"] = cached_value.color | |
| 4253 b["tag"] = cached_value.tag | |
| 4254 b["builderName"] = cached_value.builderName | |
| 4255 + b["buildNumber"] = cached_value.buildNumber | |
| 4256 | |
| 4257 builds[category].append(b) | |
| 4258 | |
| 4259 @@ -564,7 +568,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4260 break | |
| 4261 else: | |
| 4262 introducedIn = build | |
| 4263 - | |
| 4264 + | |
| 4265 # Get the results of the first build with the revision, and the | |
| 4266 # first build that does not include the revision. | |
| 4267 results = None | |
| 4268 @@ -584,11 +588,13 @@ class ConsoleStatusResource(HtmlResource): | |
| 4269 pageTitle = builder | |
| 4270 tag = "" | |
| 4271 current_details = {} | |
| 4272 + buildNumber = None | |
| 4273 if introducedIn: | |
| 4274 current_details = introducedIn.details or "" | |
| 4275 url = "./buildstatus?builder=%s&number=%s" % ( | |
| 4276 urllib.quote(builder, safe=''), | |
| 4277 introducedIn.number) | |
| 4278 + buildNumber = introducedIn.number | |
| 4279 pageTitle += " " | |
| 4280 pageTitle += urllib.quote(' '.join(introducedIn.text), ' \n
\\/:') | |
| 4281 | |
| 4282 @@ -600,17 +606,17 @@ class ConsoleStatusResource(HtmlResource): | |
| 4283 | |
| 4284 if isRunning: | |
| 4285 pageTitle += ' ETA: %ds' % (introducedIn.eta or 0) | |
| 4286 - | |
| 4287 + | |
| 4288 resultsClass = getResultsClass(results, previousResults, isRunn
ing, | |
| 4289 inProgressResults) | |
| 4290 | |
| 4291 - b = {} | |
| 4292 + b = {} | |
| 4293 b["url"] = url | |
| 4294 b["pageTitle"] = pageTitle | |
| 4295 b["color"] = resultsClass | |
| 4296 b["tag"] = tag | |
| 4297 b["builderName"] = builder | |
| 4298 - | |
| 4299 + b["buildNumber"] = buildNumber | |
| 4300 builds[category].append(b) | |
| 4301 | |
| 4302 # If the box is red, we add the explaination in the details | |
| 4303 @@ -624,7 +630,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 4304 "notstarted"): | |
| 4305 debugInfo["added_blocks"] += 1 | |
| 4306 self.cache.insert(builder, revision.revision, resultsClass, | |
| 4307 - pageTitle, current_details, url, tag) | |
| 4308 + pageTitle, current_details, url, tag, | |
| 4309 + buildNumber) | |
| 4310 | |
| 4311 return (builds, details) | |
| 4312 | |
| 4313 @@ -685,10 +692,10 @@ class ConsoleStatusResource(HtmlResource): | |
| 4314 # For each revision we show one line | |
| 4315 for revision in revisions: | |
| 4316 r = {} | |
| 4317 - | |
| 4318 + | |
| 4319 # Fill the dictionary with this new information | |
| 4320 r['id'] = revision.revision | |
| 4321 - r['link'] = revision.revlink | |
| 4322 + r['link'] = revision.revlink | |
| 4323 r['who'] = revision.who | |
| 4324 r['date'] = revision.date | |
| 4325 r['comments'] = revision.comments | |
| 4326 @@ -735,7 +742,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 4327 if not reload_time: | |
| 4328 reload_time = 60 | |
| 4329 | |
| 4330 - # Append the tag to refresh the page. | |
| 4331 + # Append the tag to refresh the page. | |
| 4332 if reload_time is not None and reload_time != 0: | |
| 4333 cxt['refresh'] = reload_time | |
| 4334 | |
| 4335 @@ -810,6 +817,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 4336 | |
| 4337 templates = request.site.buildbot_service.templates | |
| 4338 template = templates.get_template("console.html") | |
| 4339 + cxt['mastername'] = ( | |
| 4340 + request.site.buildbot_service.master.properties['mastername']) | |
| 4341 data = template.render(cxt) | |
| 4342 | |
| 4343 # Clean up the cache. | |
| 4344 @@ -825,9 +834,9 @@ class RevisionComparator(object): | |
| 4345 VCS use a plain counter for revisions (like SVN) | |
| 4346 while others use different concepts (see Git). | |
| 4347 """ | |
| 4348 - | |
| 4349 + | |
| 4350 # TODO (avivby): Should this be a zope interface? | |
| 4351 - | |
| 4352 + | |
| 4353 def isRevisionEarlier(self, first_change, second_change): | |
| 4354 """Used for comparing 2 changes""" | |
| 4355 raise NotImplementedError | |
| 4356 @@ -838,7 +847,7 @@ class RevisionComparator(object): | |
| 4357 | |
| 4358 def getSortingKey(self): | |
| 4359 raise NotImplementedError | |
| 4360 - | |
| 4361 + | |
| 4362 class TimeRevisionComparator(RevisionComparator): | |
| 4363 def isRevisionEarlier(self, first, second): | |
| 4364 return first.when < second.when | |
| 4365 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py b/third
_party/buildbot_8_4p1/buildbot/status/web/waterfall.py | |
| 4366 index 923fe0d..3262d1c 100644 | |
| 4367 --- a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py | |
| 4368 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py | |
| 4369 @@ -172,7 +172,7 @@ class StepBox(components.Adapter): | |
| 4370 text = [] | |
| 4371 text = text[:] | |
| 4372 logs = self.original.getLogs() | |
| 4373 - | |
| 4374 + | |
| 4375 cxt = dict(text=text, logs=[], urls=[]) | |
| 4376 | |
| 4377 for num in range(len(logs)): | |
| 4378 @@ -188,9 +188,12 @@ class StepBox(components.Adapter): | |
| 4379 | |
| 4380 template = req.site.buildbot_service.templates.get_template("box_macros
.html") | |
| 4381 text = template.module.step_box(**cxt) | |
| 4382 - | |
| 4383 + | |
| 4384 class_ = "BuildStep " + build_get_class(self.original) | |
| 4385 - return Box(text, class_=class_) | |
| 4386 + return Box(text, class_=class_, | |
| 4387 + buildNumber=self.original.build_number, | |
| 4388 + builder=self.original.builder.getName(), | |
| 4389 + stepName=self.original.getName()) | |
| 4390 components.registerAdapter(StepBox, buildstep.BuildStepStatus, IBox) | |
| 4391 | |
| 4392 | |
| 4393 @@ -202,7 +205,7 @@ class EventBox(components.Adapter): | |
| 4394 class_ = "Event" | |
| 4395 return Box(text, class_=class_) | |
| 4396 components.registerAdapter(EventBox, builder.Event, IBox) | |
| 4397 - | |
| 4398 + | |
| 4399 | |
| 4400 class Spacer: | |
| 4401 implements(interfaces.IStatusEvent) | |
| 4402 @@ -301,7 +304,7 @@ class WaterfallHelp(HtmlResource): | |
| 4403 times.insert(0, (current_reload_time, current_reload_time) ) | |
| 4404 | |
| 4405 cxt['times'] = times | |
| 4406 - cxt['current_reload_time'] = current_reload_time | |
| 4407 + cxt['current_reload_time'] = current_reload_time | |
| 4408 | |
| 4409 template = request.site.buildbot_service.templates.get_template("waterf
allhelp.html") | |
| 4410 return template.render(**cxt) | |
| 4411 @@ -459,10 +462,10 @@ class WaterfallStatusResource(HtmlResource): | |
| 4412 failuresOnly = request.args.get("failures_only", ["false"])[0] | |
| 4413 if failuresOnly.lower() == "true": | |
| 4414 builders = [b for b in builders if not self.isSuccess(b)] | |
| 4415 - | |
| 4416 + | |
| 4417 (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \ | |
| 4418 self.buildGrid(request, builders, changes) | |
| 4419 - | |
| 4420 + | |
| 4421 # start the table: top-header material | |
| 4422 locale_enc = locale.getdefaultlocale()[1] | |
| 4423 if locale_enc is not None: | |
| 4424 @@ -471,19 +474,20 @@ class WaterfallStatusResource(HtmlResource): | |
| 4425 locale_tz = unicode(time.tzname[time.localtime()[-1]]) | |
| 4426 ctx['tz'] = locale_tz | |
| 4427 ctx['changes_url'] = request.childLink("../changes") | |
| 4428 - | |
| 4429 + | |
| 4430 bn = ctx['builders'] = [] | |
| 4431 - | |
| 4432 + | |
| 4433 for name in builderNames: | |
| 4434 builder = status.getBuilder(name) | |
| 4435 top_box = ITopBox(builder).getBox(request) | |
| 4436 current_box = ICurrentBox(builder).getBox(status, brcounts) | |
| 4437 bn.append({'name': name, | |
| 4438 - 'url': request.childLink("../builders/%s" % urllib.quote
(name, safe='')), | |
| 4439 - 'top': top_box.text, | |
| 4440 + 'category': builder.category, | |
| 4441 + 'url': request.childLink("../builders/%s" % urllib.quote
(name, safe='')), | |
| 4442 + 'top': top_box.text, | |
| 4443 'top_class': top_box.class_, | |
| 4444 'status': current_box.text, | |
| 4445 - 'status_class': current_box.class_, | |
| 4446 + 'status_class': current_box.class_, | |
| 4447 }) | |
| 4448 | |
| 4449 ctx.update(self.phase2(request, changeNames + builderNames, timestamps,
eventGrid, | |
| 4450 @@ -528,9 +532,11 @@ class WaterfallStatusResource(HtmlResource): | |
| 4451 ctx['no_reload_page'] = with_args(request, remove_args=["reload"]) | |
| 4452 | |
| 4453 template = request.site.buildbot_service.templates.get_template("waterf
all.html") | |
| 4454 + ctx['mastername'] = ( | |
| 4455 + request.site.buildbot_service.master.properties['mastername']) | |
| 4456 data = template.render(**ctx) | |
| 4457 return data | |
| 4458 - | |
| 4459 + | |
| 4460 def buildGrid(self, request, builders, changes): | |
| 4461 debug = False | |
| 4462 # TODO: see if we can use a cached copy | |
| 4463 @@ -677,21 +683,21 @@ class WaterfallStatusResource(HtmlResource): | |
| 4464 | |
| 4465 if len(timestamps) > maxPageLen: | |
| 4466 break | |
| 4467 - | |
| 4468 - | |
| 4469 + | |
| 4470 + | |
| 4471 # now loop | |
| 4472 - | |
| 4473 + | |
| 4474 # loop is finished. now we have eventGrid[] and timestamps[] | |
| 4475 if debugGather: log.msg("finished loop") | |
| 4476 assert(len(timestamps) == len(eventGrid)) | |
| 4477 return (changeNames, builderNames, timestamps, eventGrid, sourceEvents) | |
| 4478 - | |
| 4479 + | |
| 4480 def phase2(self, request, sourceNames, timestamps, eventGrid, | |
| 4481 sourceEvents): | |
| 4482 | |
| 4483 if not timestamps: | |
| 4484 return dict(grid=[], gridlen=0) | |
| 4485 - | |
| 4486 + | |
| 4487 # first pass: figure out the height of the chunks, populate grid | |
| 4488 grid = [] | |
| 4489 for i in range(1+len(sourceNames)): | |
| 4490 diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_p
arty/buildbot_8_4p1/buildbot/status/status_push.py | |
| 4491 index ca83fdb..af764f1 100644 | |
| 4492 --- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py | |
| 4493 +++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py | |
| 4494 @@ -36,6 +36,7 @@ from buildbot.status.persistent_queue import DiskQueue, Indexe
dQueue, \ | |
| 4495 from buildbot.status.web.status_json import FilterOut | |
| 4496 from twisted.internet import defer, reactor | |
| 4497 from twisted.python import log | |
| 4498 +from twisted.python.logfile import LogFile | |
| 4499 from twisted.web import client | |
| 4500 | |
| 4501 | |
| 4502 @@ -109,6 +110,9 @@ class StatusPush(StatusReceiverMultiService): | |
| 4503 # Last shutdown was not clean, don't wait to send events. | |
| 4504 self.queueNextServerPush() | |
| 4505 | |
| 4506 + self.verboseLog = LogFile.fromFullPath( | |
| 4507 + 'status_push.log', rotateLength=10*1024*1024, maxRotatedFiles=14) | |
| 4508 + | |
| 4509 def startService(self): | |
| 4510 """Starting up.""" | |
| 4511 StatusReceiverMultiService.startService(self) | |
| 4512 @@ -390,6 +394,10 @@ class HttpStatusPush(StatusPush): | |
| 4513 # This packet is just too large. Drop this packet. | |
| 4514 log.msg("ERROR: packet %s was dropped, too large: %d > %d" % | |
| 4515 (items[0]['id'], len(data), self.maxHttpRequestSize)) | |
| 4516 + self.verboseLog.write( | |
| 4517 + "ERROR: packet %s was dropped, too large: %d > %d; %s" % | |
| 4518 + (items[0]['id'], len(data), self.maxHttpRequestSize, | |
| 4519 + json.dumps(items, indent=2, sort_keys=True))) | |
| 4520 chunkSize = self.chunkSize | |
| 4521 else: | |
| 4522 # Try with half the packets. | |
| 4523 @@ -405,6 +413,9 @@ class HttpStatusPush(StatusPush): | |
| 4524 def Success(result): | |
| 4525 """Queue up next push.""" | |
| 4526 log.msg('Sent %d events to %s' % (len(items), self.serverUrl)) | |
| 4527 + self.verboseLog.write('Sent %d events to %s; %s' % ( | |
| 4528 + len(items), self.serverUrl, | |
| 4529 + json.dumps(items, indent=2, sort_keys=True))) | |
| 4530 self.lastPushWasSuccessful = True | |
| 4531 return self.queueNextServerPush() | |
| 4532 | |
| 4533 @@ -413,6 +424,9 @@ class HttpStatusPush(StatusPush): | |
| 4534 # Server is now down. | |
| 4535 log.msg('Failed to push %d events to %s: %s' % | |
| 4536 (len(items), self.serverUrl, str(result))) | |
| 4537 + self.verboseLog.write('Failed to push %d events to %s: %s; %s' % | |
| 4538 + (len(items), self.serverUrl, str(result), | |
| 4539 + json.dumps(items, indent=2, sort_keys=True))) | |
| 4540 self.queue.insertBackChunk(items) | |
| 4541 if self.stopped: | |
| 4542 # Bad timing, was being called on shutdown and the server died | |
| 4543 diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_p
arty/buildbot_8_4p1/buildbot/status/status_push.py | |
| 4544 index af764f1..e8319e0 100644 | |
| 4545 --- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py | |
| 4546 +++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py | |
| 4547 @@ -51,7 +51,7 @@ class StatusPush(StatusReceiverMultiService): | |
| 4548 """ | |
| 4549 | |
| 4550 def __init__(self, serverPushCb, queue=None, path=None, filter=True, | |
| 4551 - bufferDelay=1, retryDelay=5, blackList=None): | |
| 4552 + bufferDelay=1, retryDelay=5, blackList=None, filterFunc=None): | |
| 4553 """ | |
| 4554 @serverPushCb: callback to be used. It receives 'self' as parameter. It | |
| 4555 should call self.queueNextServerPush() when it's done to queue the next | |
| 4556 @@ -67,6 +67,7 @@ class StatusPush(StatusReceiverMultiService): | |
| 4557 @retryDelay: amount of time between retries when no items were pushed o
n | |
| 4558 last serverPushCb call. | |
| 4559 @blackList: events that shouldn't be sent. | |
| 4560 + @filterFunc: optional function applied to items added to packet payload | |
| 4561 """ | |
| 4562 StatusReceiverMultiService.__init__(self) | |
| 4563 | |
| 4564 @@ -90,6 +91,7 @@ class StatusPush(StatusReceiverMultiService): | |
| 4565 return serverPushCb(self) | |
| 4566 self.serverPushCb = hookPushCb | |
| 4567 self.blackList = blackList | |
| 4568 + self.filterFunc = filterFunc | |
| 4569 | |
| 4570 # Other defaults. | |
| 4571 # IDelayedCall object that represents the next queued push. | |
| 4572 @@ -234,6 +236,8 @@ class StatusPush(StatusReceiverMultiService): | |
| 4573 obj = obj.asDict() | |
| 4574 if self.filter: | |
| 4575 obj = FilterOut(obj) | |
| 4576 + if self.filterFunc: | |
| 4577 + obj = self.filterFunc(obj) | |
| 4578 packet['payload'][obj_name] = obj | |
| 4579 self.queue.pushItem(packet) | |
| 4580 if self.task is None or not self.task.active(): | |
| 4581 | |
| 4582 diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 4583 index 9099a08..f5b4f43 100644 | |
| 4584 --- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 4585 +++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 4586 @@ -272,6 +272,7 @@ class LoggedRemoteCommand(RemoteCommand): | |
| 4587 def __init__(self, *args, **kwargs): | |
| 4588 self.logs = {} | |
| 4589 self.delayedLogs = {} | |
| 4590 + self.wasTimeout = False | |
| 4591 self._closeWhenFinished = {} | |
| 4592 RemoteCommand.__init__(self, *args, **kwargs) | |
| 4593 | |
| 4594 @@ -375,6 +376,8 @@ class LoggedRemoteCommand(RemoteCommand): | |
| 4595 self.addHeader("program finished with exit code %d\n" % rc) | |
| 4596 if update.has_key('elapsed'): | |
| 4597 self._remoteElapsed = update['elapsed'] | |
| 4598 + if update.has_key('timeout'): | |
| 4599 + self.wasTimeout = self.wasTimeout or update['timeout'] | |
| 4600 | |
| 4601 for k in update: | |
| 4602 if k not in ('stdout', 'stderr', 'header', 'rc'): | |
| 4603 @@ -1241,6 +1244,8 @@ class LoggingBuildStep(BuildStep): | |
| 4604 | |
| 4605 if self.log_eval_func: | |
| 4606 return self.log_eval_func(cmd, self.step_status) | |
| 4607 + if getattr(cmd, 'wasTimeout', False): | |
| 4608 + return EXCEPTION | |
| 4609 if cmd.rc != 0: | |
| 4610 return FAILURE | |
| 4611 return SUCCESS | |
| 4612 | |
| 4613 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.htm
l b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html | |
| 4614 index e988807..3a582ef 100644 | |
| 4615 --- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html | |
| 4616 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html | |
| 4617 @@ -60,5 +60,15 @@ | |
| 4618 Page built: <b>{{ time }}</b> ({{ tz }}) | |
| 4619 </div> | |
| 4620 {% endblock -%} | |
| 4621 + <script> | |
| 4622 + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function
(){ | |
| 4623 + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElemen
t(o), | |
| 4624 + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefor
e(a,m) | |
| 4625 + })(window,document,'script','//www.google-analytics.com/analytics.js','ga
'); | |
| 4626 + | |
| 4627 + ga('create', 'UA-55762617-2', 'auto'); | |
| 4628 + ga('send', 'pageview'); | |
| 4629 + | |
| 4630 + </script> | |
| 4631 </body> | |
| 4632 </html> | |
| 4633 diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 4634 index f5b4f43..160a4de 100644 | |
| 4635 --- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 4636 +++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py | |
| 4637 @@ -1198,7 +1198,8 @@ class LoggingBuildStep(BuildStep): | |
| 4638 self.step_status.setText(self.describe(True) + | |
| 4639 ["exception", "slave", "lost"]) | |
| 4640 self.step_status.setText2(["exception", "slave", "lost"]) | |
| 4641 - return self.finished(RETRY) | |
| 4642 + # Retry if we're stopping the reactor (resarting the master) | |
| 4643 + return self.finished(RETRY if reactor._stopped else EXCEPTION) | |
| 4644 | |
| 4645 # to refine the status output, override one or more of the following | |
| 4646 # methods. Change as little as possible: start with the first ones on | |
| 4647 | |
| 4648 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html | |
| 4649 index 116bbd6..99708b4 100644 | |
| 4650 --- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html | |
| 4651 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html | |
| 4652 @@ -4,6 +4,16 @@ | |
| 4653 <html> | |
| 4654 <head><title>{{ pageTitle }}</title> | |
| 4655 <link rel="stylesheet" href="{{ path_to_root }}default.css" type="text/css" /
> | |
| 4656 + <script> | |
| 4657 + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function()
{ | |
| 4658 + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(
o), | |
| 4659 + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(
a,m) | |
| 4660 + })(window,document,'script','//www.google-analytics.com/analytics.js','ga')
; | |
| 4661 + | |
| 4662 + ga('create', 'UA-55762617-2', 'auto'); | |
| 4663 + ga('send', 'pageview'); | |
| 4664 + | |
| 4665 + </script> | |
| 4666 </head> | |
| 4667 <body class='log'> | |
| 4668 <a href="{{ texturl }}">(view as text)</a><br/> | |
| 4669 | |
| 4670 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.htm
l b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html | |
| 4671 index 3a582ef..a8a449e 100644 | |
| 4672 --- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html | |
| 4673 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html | |
| 4674 @@ -66,7 +66,7 @@ | |
| 4675 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefor
e(a,m) | |
| 4676 })(window,document,'script','//www.google-analytics.com/analytics.js','ga
'); | |
| 4677 | |
| 4678 - ga('create', 'UA-55762617-2', 'auto'); | |
| 4679 + ga('create', 'UA-55762617-2', {'siteSpeedSampleRate': 100}); | |
| 4680 ga('send', 'pageview'); | |
| 4681 | |
| 4682 </script> | |
| 4683 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html | |
| 4684 index 99708b4..bdd35c6 100644 | |
| 4685 --- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html | |
| 4686 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html | |
| 4687 @@ -10,7 +10,7 @@ | |
| 4688 m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(
a,m) | |
| 4689 })(window,document,'script','//www.google-analytics.com/analytics.js','ga')
; | |
| 4690 | |
| 4691 - ga('create', 'UA-55762617-2', 'auto'); | |
| 4692 + ga('create', 'UA-55762617-2', {'siteSpeedSampleRate': 100}); | |
| 4693 ga('send', 'pageview'); | |
| 4694 | |
| 4695 </script> | |
| 4696 | |
| 4697 diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4698 index 3ac7bbc..7703e57 100644 | |
| 4699 --- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4700 +++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4701 @@ -602,11 +602,11 @@ class DuplicateSlaveArbitrator(object): | |
| 4702 # slave to connect. If this does not kill it, then we disconnect | |
| 4703 # the new slave. | |
| 4704 self.ping_old_slave_done = False | |
| 4705 + self.ping_new_slave_done = False | |
| 4706 self.old_slave_connected = True | |
| 4707 self.ping_old_slave(new_tport.getPeer()) | |
| 4708 | |
| 4709 # Print a message on the new slave, if possible. | |
| 4710 - self.ping_new_slave_done = False | |
| 4711 self.ping_new_slave() | |
| 4712 | |
| 4713 return self.new_slave_d | |
| 4714 | |
| 4715 diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4716 index 7703e57..7a83ed7 100644 | |
| 4717 --- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4718 +++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4719 @@ -634,8 +634,14 @@ class DuplicateSlaveArbitrator(object): | |
| 4720 self.ping_old_slave_timeout = reactor.callLater(self.PING_TIMEOUT, time
out) | |
| 4721 self.ping_old_slave_timed_out = False | |
| 4722 | |
| 4723 - d = self.old_slave.slave.callRemote("print", | |
| 4724 - "master got a duplicate connection from %s; keeping this one" % new
_peer) | |
| 4725 + try: | |
| 4726 + d = self.old_slave.slave.callRemote( | |
| 4727 + "print", | |
| 4728 + "master got a duplicate connection from %s; keeping this one" | |
| 4729 + % new_peer) | |
| 4730 + except pb.DeadReferenceError(): | |
| 4731 + timeout() | |
| 4732 + return | |
| 4733 | |
| 4734 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 4735 index 9a1b6d0..3618b3c 100644 | |
| 4736 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 4737 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 4738 @@ -217,16 +217,8 @@ class BuildMaster(service.MultiService): | |
| 4739 bname, number_cancelled_builds)) | |
| 4740 | |
| 4741 def noNewBuilds(self): | |
| 4742 - log.msg("stopping schedulers") | |
| 4743 - self.loadConfig_Schedulers([]) | |
| 4744 - log.msg("stopping sources") | |
| 4745 - self.loadConfig_Sources([]) | |
| 4746 - d = self.cancelAllPendingBuilds() | |
| 4747 - def doneStopping(res): | |
| 4748 - log.msg("new builds stopped") | |
| 4749 - return res | |
| 4750 - d.addCallback(doneStopping) | |
| 4751 - return d | |
| 4752 + log.msg("stopping build request driver") | |
| 4753 + return self.botmaster.brd.stopService() | |
| 4754 | |
| 4755 def loadTheConfigFile(self, configFile=None): | |
| 4756 if not configFile: | |
| 4757 | |
| 4758 diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbo
t_8_4p1/buildbot/master.py | |
| 4759 index 3618b3c..d045b1e 100644 | |
| 4760 --- a/third_party/buildbot_8_4p1/buildbot/master.py | |
| 4761 +++ b/third_party/buildbot_8_4p1/buildbot/master.py | |
| 4762 @@ -995,7 +995,8 @@ class BuildMaster(service.MultiService): | |
| 4763 """ | |
| 4764 d = self.db.buildsets.addBuildset(**kwargs) | |
| 4765 def notify((bsid,brids)): | |
| 4766 - log.msg("added buildset %d to database" % bsid) | |
| 4767 + log.msg("added buildset %d to database (build requests: %s)" % | |
| 4768 + (bsid, brids)) | |
| 4769 # note that buildset additions are only reported on this master | |
| 4770 self._new_buildset_subs.deliver(bsid=bsid, **kwargs) | |
| 4771 # only deliver messages immediately if we're not polling | |
| 4772 diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_pa
rty/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4773 index 7a83ed7..0c0bcef 100644 | |
| 4774 --- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4775 +++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py | |
| 4776 @@ -305,6 +305,7 @@ class BotMaster(service.MultiService): | |
| 4777 | |
| 4778 def startService(self): | |
| 4779 def buildRequestAdded(notif): | |
| 4780 + log.msg("Processing new build request: %s" % notif) | |
| 4781 self.maybeStartBuildsForBuilder(notif['buildername']) | |
| 4782 self.buildrequest_sub = \ | |
| 4783 self.master.subscribeToBuildRequests(buildRequestAdded) | |
| 4784 diff --git a/third_party/buildbot_8_4p1/buildbot/status/slave.py b/third_party/b
uildbot_8_4p1/buildbot/status/slave.py | |
| 4785 index dbb8aab..21f7fb8 100644 | |
| 4786 --- a/third_party/buildbot_8_4p1/buildbot/status/slave.py | |
| 4787 +++ b/third_party/buildbot_8_4p1/buildbot/status/slave.py | |
| 4788 @@ -54,6 +54,8 @@ class SlaveStatus: | |
| 4789 def getConnectCount(self): | |
| 4790 then = time.time() - 3600 | |
| 4791 return len([ t for t in self.connect_times if t > then ]) | |
| 4792 + def getConnectTimes(self): | |
| 4793 + return self.connect_times | |
| 4794 | |
| 4795 def setAdmin(self, admin): | |
| 4796 self.admin = admin | |
| 4797 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 4798 index 9eb0858..1b36daf 100644 | |
| 4799 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 4800 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 4801 @@ -27,7 +27,7 @@ from twisted.web import html, resource, server | |
| 4802 | |
| 4803 from buildbot.changes import changes | |
| 4804 from buildbot.status.web.base import HtmlResource | |
| 4805 -from buildbot.util import json | |
| 4806 +from buildbot.util import json, now | |
| 4807 | |
| 4808 | |
| 4809 _IS_INT = re.compile('^[-+]?\d+$') | |
| 4810 @@ -82,6 +82,8 @@ EXAMPLES = """\ | |
| 4811 - Builder information plus details information about its slaves. Neat eh? | |
| 4812 - /json/slaves/<A_SLAVE> | |
| 4813 - A specific slave. | |
| 4814 + - /json/buildstate | |
| 4815 + - The current build state. | |
| 4816 - /json?select=slaves/<A_SLAVE>/&select=project&select=builders/<A_BUILDER>/b
uilds/<A_BUILD> | |
| 4817 - A selection of random unrelated stuff as an random example. :) | |
| 4818 """ | |
| 4819 @@ -265,7 +267,7 @@ class JsonResource(resource.Resource): | |
| 4820 if callback: | |
| 4821 # Only accept things that look like identifiers for now | |
| 4822 callback = callback[0] | |
| 4823 - if re.match(r'^[a-zA-Z$][a-zA-Z$0-9.]*$', callback): | |
| 4824 + if re.match(r'^[_a-zA-Z$][_a-zA-Z$0-9.]*$', callback): | |
| 4825 data = '%s(%s);' % (callback, data) | |
| 4826 yield data | |
| 4827 | |
| 4828 @@ -349,6 +351,7 @@ class HelpResource(HtmlResource): | |
| 4829 HtmlResource.__init__(self) | |
| 4830 self.text = text | |
| 4831 self.pageTitle = pageTitle | |
| 4832 + self.flags = getattr(parent_node, 'FLAGS', None) or FLAGS | |
| 4833 self.parent_level = parent_node.level | |
| 4834 self.parent_children = parent_node.children.keys() | |
| 4835 | |
| 4836 @@ -356,7 +359,7 @@ class HelpResource(HtmlResource): | |
| 4837 cxt['level'] = self.parent_level | |
| 4838 cxt['text'] = ToHtml(self.text) | |
| 4839 cxt['children'] = [ n for n in self.parent_children if n != 'help' ] | |
| 4840 - cxt['flags'] = ToHtml(FLAGS) | |
| 4841 + cxt['flags'] = ToHtml(self.flags) | |
| 4842 cxt['examples'] = ToHtml(EXAMPLES).replace( | |
| 4843 'href="/json', | |
| 4844 'href="%sjson' % (self.level * '../')) | |
| 4845 @@ -745,6 +748,204 @@ class MetricsJsonResource(JsonResource): | |
| 4846 return None | |
| 4847 | |
| 4848 | |
| 4849 +class BuildStateJsonResource(JsonResource): | |
| 4850 + help = ('Holistic build state JSON endpoint.\n\n' | |
| 4851 + 'This endpoint is a fast (unless otherwise noted) and ' | |
| 4852 + 'comprehensive source for full BuildBot state queries. Any ' | |
| 4853 + 'augmentations to this endpoint MUST keep this in mind.') | |
| 4854 + | |
| 4855 + pageTitle = 'Build State JSON' | |
| 4856 + | |
| 4857 + # Keyword for 'completed_builds' to indicate that all cached builds should | |
| 4858 + # be returned. | |
| 4859 + CACHED = 'cached' | |
| 4860 + | |
| 4861 + EXTRA_FLAGS = """\ | |
| 4862 + - builder | |
| 4863 + - A builder name to explicitly include in the results. This can be supplied | |
| 4864 + multiple times. If omitted, data for all builders will be returned. | |
| 4865 + - current_builds | |
| 4866 + - Controls whether the builder's current-running build data will be | |
| 4867 + returned. By default, no current build data will be returned; setting | |
| 4868 + current_builds=1 will enable this. | |
| 4869 + - completed_builds | |
| 4870 + - Controls whether the builder's completed build data will be returned. By | |
| 4871 + default, no completed build data will be retured. Setting | |
| 4872 + completed_builds=cached will return build data for all cached builds. | |
| 4873 + Setting it to a positive integer 'N' (e.g., completed_builds=3) will caus
e | |
| 4874 + data for the latest 'N' completed builds to be returned. | |
| 4875 + - pending_builds | |
| 4876 + - Controls whether the builder's pending build data will be | |
| 4877 + returned. By default, no pending build data will be returned; setting | |
| 4878 + pending_builds=1 will enable this. | |
| 4879 + - slaves | |
| 4880 + - Controls whether the builder's slave data will be returned. By default, n
o | |
| 4881 + slave build data will be returned; setting slaves=1 will enable this. | |
| 4882 +""" | |
| 4883 + def __init__(self, status): | |
| 4884 + JsonResource.__init__(self, status) | |
| 4885 + self.FLAGS = FLAGS + self.EXTRA_FLAGS | |
| 4886 + | |
| 4887 + self.putChild('project', ProjectJsonResource(status)) | |
| 4888 + | |
| 4889 + @classmethod | |
| 4890 + def _CountOrCachedRequestArg(cls, request, arg): | |
| 4891 + value = RequestArg(request, arg, 0) | |
| 4892 + if value == cls.CACHED: | |
| 4893 + return value | |
| 4894 + try: | |
| 4895 + value = int(value) | |
| 4896 + except ValueError: | |
| 4897 + return 0 | |
| 4898 + return max(0, value) | |
| 4899 + | |
| 4900 + @defer.deferredGenerator | |
| 4901 + def asDict(self, request): | |
| 4902 + builders = request.args.get('builder', ()) | |
| 4903 + current_builds = RequestArgToBool(request, 'current_builds', False) | |
| 4904 + completed_builds = self._CountOrCachedRequestArg(request, | |
| 4905 + 'completed_builds') | |
| 4906 + pending_builds = RequestArgToBool(request, 'pending_builds', False) | |
| 4907 + slaves = RequestArgToBool(request, 'slaves', False) | |
| 4908 + | |
| 4909 + builder_names = self.status.getBuilderNames() | |
| 4910 + if builders: | |
| 4911 + builder_names = [b for b in builder_names | |
| 4912 + if b in set(builders)] | |
| 4913 + | |
| 4914 + # Collect child endpoint data. | |
| 4915 + wfd = defer.waitForDeferred( | |
| 4916 + defer.maybeDeferred(JsonResource.asDict, self, request)) | |
| 4917 + yield wfd | |
| 4918 + response = wfd.getResult() | |
| 4919 + | |
| 4920 + # Collect builder data. | |
| 4921 + wfd = defer.waitForDeferred( | |
| 4922 + defer.gatherResults( | |
| 4923 + [self._getBuilderData(self.status.getBuilder(builder_name), | |
| 4924 + current_builds, completed_builds, | |
| 4925 + pending_builds) | |
| 4926 + for builder_name in builder_names])) | |
| 4927 + yield wfd | |
| 4928 + response['builders'] = wfd.getResult() | |
| 4929 + | |
| 4930 + # Add slave data. | |
| 4931 + if slaves: | |
| 4932 + response['slaves'] = self._getAllSlavesData() | |
| 4933 + | |
| 4934 + # Add timestamp and return. | |
| 4935 + response['timestamp'] = now() | |
| 4936 + yield response | |
| 4937 + | |
| 4938 + @defer.deferredGenerator | |
| 4939 + def _getBuilderData(self, builder, current_builds, completed_builds, | |
| 4940 + pending_builds): | |
| 4941 + # Load the builder dictionary. We use the synchronous path, since the | |
| 4942 + # asynchronous waits for pending builds to load. We handle that path | |
| 4943 + # explicitly via the 'pending_builds' option. | |
| 4944 + # | |
| 4945 + # This also causes the cache to be updated with recent builds, so we | |
| 4946 + # will call it first. | |
| 4947 + response = builder.asDict() | |
| 4948 + tasks = [] | |
| 4949 + | |
| 4950 + # Get current/completed builds. | |
| 4951 + if current_builds or completed_builds: | |
| 4952 + tasks.append( | |
| 4953 + defer.maybeDeferred(self._loadBuildData, builder, | |
| 4954 + current_builds, completed_builds)) | |
| 4955 + | |
| 4956 + # Get pending builds. | |
| 4957 + if pending_builds: | |
| 4958 + tasks.append( | |
| 4959 + self._loadPendingBuildData(builder)) | |
| 4960 + | |
| 4961 + # Collect a set of build data dictionaries to combine. | |
| 4962 + wfd = defer.waitForDeferred( | |
| 4963 + defer.gatherResults(tasks)) | |
| 4964 + yield wfd | |
| 4965 + build_data_entries = wfd.getResult() | |
| 4966 + | |
| 4967 + # Construct our build data from the various task entries. | |
| 4968 + build_state = response.setdefault('buildState', {}) | |
| 4969 + for build_data_entry in build_data_entries: | |
| 4970 + build_state.update(build_data_entry) | |
| 4971 + yield response | |
| 4972 + | |
| 4973 + def _loadBuildData(self, builder, current_builds, completed_builds): | |
| 4974 + build_state = {} | |
| 4975 + builds = set() | |
| 4976 + build_data_entries = [] | |
| 4977 + | |
| 4978 + current_build_numbers = set(b.getNumber() | |
| 4979 + for b in builder.currentBuilds) | |
| 4980 + if current_builds: | |
| 4981 + builds.update(current_build_numbers) | |
| 4982 + build_data_entries.append(('current', current_build_numbers)) | |
| 4983 + | |
| 4984 + if completed_builds: | |
| 4985 + if completed_builds == self.CACHED: | |
| 4986 + build_numbers = set(builder.buildCache.cache.keys()) | |
| 4987 + build_numbers.difference_update(current_build_numbers) | |
| 4988 + else: | |
| 4989 + build_numbers = [] | |
| 4990 + candidate = -1 | |
| 4991 + while len(build_numbers) < completed_builds: | |
| 4992 + build_number = builder._resolveBuildNumber(candidate) | |
| 4993 + if not build_number: | |
| 4994 + break | |
| 4995 + | |
| 4996 + candidate -= 1 | |
| 4997 + if build_number in current_build_numbers: | |
| 4998 + continue | |
| 4999 + build_numbers.append(build_number) | |
| 5000 + builds.update(build_numbers) | |
| 5001 + build_data_entries.append(('completed', build_numbers)) | |
| 5002 + | |
| 5003 + # Load all builds referenced by 'builds'. | |
| 5004 + builds = builder.getBuilds(builds) | |
| 5005 + build_map = dict((build_dict['number'], build_dict) | |
| 5006 + for build_dict in [build.asDict() | |
| 5007 + for build in builds | |
| 5008 + if build]) | |
| 5009 + | |
| 5010 + # Map the collected builds to their repective keys. This dictionary | |
| 5011 + # takes the form: Build# => BuildDict. | |
| 5012 + for key, build_numbers in build_data_entries: | |
| 5013 + build_state[key] = dict((number, build_map.get(number, {})) | |
| 5014 + for number in build_numbers) | |
| 5015 + return build_state | |
| 5016 + | |
| 5017 + def _loadPendingBuildData(self, builder): | |
| 5018 + d = builder.getPendingBuildRequestStatuses() | |
| 5019 + | |
| 5020 + def cb_load_status(statuses): | |
| 5021 + statuses.sort(key=lambda s: s.getSubmitTime()) | |
| 5022 + return defer.gatherResults([status.asDict_async() | |
| 5023 + for status in statuses]) | |
| 5024 + d.addCallback(cb_load_status) | |
| 5025 + | |
| 5026 + def cb_collect(status_dicts): | |
| 5027 + return { | |
| 5028 + 'pending': status_dicts, | |
| 5029 + } | |
| 5030 + d.addCallback(cb_collect) | |
| 5031 + | |
| 5032 + return d | |
| 5033 + | |
| 5034 + def _getAllSlavesData(self): | |
| 5035 + return dict((slavename, self._getSlaveData(slavename)) | |
| 5036 + for slavename in self.status.getSlaveNames()) | |
| 5037 + | |
| 5038 + def _getSlaveData(self, slavename): | |
| 5039 + slave = self.status.getSlave(slavename) | |
| 5040 + return { | |
| 5041 + 'host': slave.getHost(), | |
| 5042 + 'connected': slave.isConnected(), | |
| 5043 + 'connect_times': slave.getConnectTimes(), | |
| 5044 + 'last_message_received': slave.lastMessageReceived(), | |
| 5045 + } | |
| 5046 + | |
| 5047 | |
| 5048 class JsonStatusResource(JsonResource): | |
| 5049 """Retrieves all json data.""" | |
| 5050 @@ -766,6 +967,7 @@ For help on any sub directory, use url /child/help | |
| 5051 self.putChild('project', ProjectJsonResource(status)) | |
| 5052 self.putChild('slaves', SlavesJsonResource(status)) | |
| 5053 self.putChild('metrics', MetricsJsonResource(status)) | |
| 5054 + self.putChild('buildstate', BuildStateJsonResource(status)) | |
| 5055 # This needs to be called before the first HelpResource().body call. | |
| 5056 self.hackExamples() | |
| 5057 | |
| 5058 diff --git a/third_party/buildbot_8_4p1/buildbot/status/build.py b/third_party/b
uildbot_8_4p1/buildbot/status/build.py | |
| 5059 index ab3150f..723f6ad 100644 | |
| 5060 --- a/third_party/buildbot_8_4p1/buildbot/status/build.py | |
| 5061 +++ b/third_party/buildbot_8_4p1/buildbot/status/build.py | |
| 5062 @@ -433,29 +433,36 @@ class BuildStatus(styles.Versioned): | |
| 5063 self.number)) | |
| 5064 log.err() | |
| 5065 | |
| 5066 - def asDict(self): | |
| 5067 + def asDict(self, blame=True, logs=True, properties=True, steps=True, | |
| 5068 + sourceStamp=True, eta=True): | |
| 5069 result = {} | |
| 5070 # Constant | |
| 5071 result['builderName'] = self.builder.name | |
| 5072 result['number'] = self.getNumber() | |
| 5073 - result['sourceStamp'] = self.getSourceStamp().asDict() | |
| 5074 + if sourceStamp: | |
| 5075 + result['sourceStamp'] = self.getSourceStamp().asDict() | |
| 5076 result['reason'] = self.getReason() | |
| 5077 - result['blame'] = self.getResponsibleUsers() | |
| 5078 + if blame: | |
| 5079 + result['blame'] = self.getResponsibleUsers() | |
| 5080 | |
| 5081 # Transient | |
| 5082 - result['properties'] = self.getProperties().asList() | |
| 5083 + if properties: | |
| 5084 + result['properties'] = self.getProperties().asList() | |
| 5085 result['times'] = self.getTimes() | |
| 5086 result['text'] = self.getText() | |
| 5087 result['results'] = self.getResults() | |
| 5088 result['slave'] = self.getSlavename() | |
| 5089 # TODO(maruel): Add. | |
| 5090 #result['test_results'] = self.getTestResults() | |
| 5091 - result['logs'] = [[l.getName(), | |
| 5092 - self.builder.status.getURLForThing(l)] for l in self.getLogs()] | |
| 5093 - result['eta'] = self.getETA() | |
| 5094 - result['steps'] = [bss.asDict() for bss in self.steps] | |
| 5095 - if self.getCurrentStep(): | |
| 5096 - result['currentStep'] = self.getCurrentStep().asDict() | |
| 5097 - else: | |
| 5098 - result['currentStep'] = None | |
| 5099 + if logs: | |
| 5100 + result['logs'] = [[l.getName(), | |
| 5101 + self.builder.status.getURLForThing(l)] for l in self.getLogs()] | |
| 5102 + if eta: | |
| 5103 + result['eta'] = self.getETA() | |
| 5104 + if steps: | |
| 5105 + result['steps'] = [bss.asDict() for bss in self.steps] | |
| 5106 + if self.getCurrentStep(): | |
| 5107 + result['currentStep'] = self.getCurrentStep().asDict() | |
| 5108 + else: | |
| 5109 + result['currentStep'] = None | |
| 5110 return result | |
| 5111 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5112 index 1b36daf..9fe46ef 100644 | |
| 5113 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5114 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5115 @@ -760,6 +760,14 @@ class BuildStateJsonResource(JsonResource): | |
| 5116 # be returned. | |
| 5117 CACHED = 'cached' | |
| 5118 | |
| 5119 + # Specific build fields that will be stripped unless requested. These map | |
| 5120 + # user-specified query parameters (lowercase) to | |
| 5121 + # buildbot.status.Build.asDict keyword arguments. | |
| 5122 + BUILD_FIELDS = ('blame', 'logs', 'sourceStamp', 'properties', 'steps', | |
| 5123 + 'eta',) | |
| 5124 + # Special build field to indicate all fields should be returned. | |
| 5125 + BUILD_FIELDS_ALL = 'all' | |
| 5126 + | |
| 5127 EXTRA_FLAGS = """\ | |
| 5128 - builder | |
| 5129 - A builder name to explicitly include in the results. This can be supplied | |
| 5130 @@ -771,20 +779,32 @@ class BuildStateJsonResource(JsonResource): | |
| 5131 - completed_builds | |
| 5132 - Controls whether the builder's completed build data will be returned. By | |
| 5133 default, no completed build data will be retured. Setting | |
| 5134 - completed_builds=cached will return build data for all cached builds. | |
| 5135 - Setting it to a positive integer 'N' (e.g., completed_builds=3) will caus
e | |
| 5136 - data for the latest 'N' completed builds to be returned. | |
| 5137 + completed_builds=%(completed_builds_cached)s will return build data for | |
| 5138 + all cached builds. Setting it to a positive integer 'N' (e.g., | |
| 5139 + completed_builds=3) will cause data for the latest 'N' completed builds t
o | |
| 5140 + be returned. | |
| 5141 - pending_builds | |
| 5142 - Controls whether the builder's pending build data will be | |
| 5143 returned. By default, no pending build data will be returned; setting | |
| 5144 pending_builds=1 will enable this. | |
| 5145 + - build_field | |
| 5146 + - The specific build fields to include. Collecting and packaging more field
s | |
| 5147 + will take more time. This can be supplied multiple times to request more | |
| 5148 + than one field. If '%(build_fields_all)s' is supplied, all build fields | |
| 5149 + will be included. Available individual fields are: %(build_fields)s. | |
| 5150 - slaves | |
| 5151 - Controls whether the builder's slave data will be returned. By default, n
o | |
| 5152 slave build data will be returned; setting slaves=1 will enable this. | |
| 5153 """ | |
| 5154 def __init__(self, status): | |
| 5155 JsonResource.__init__(self, status) | |
| 5156 - self.FLAGS = FLAGS + self.EXTRA_FLAGS | |
| 5157 + context = { | |
| 5158 + 'completed_builds_cached': self.CACHED, | |
| 5159 + 'build_fields_all': self.BUILD_FIELDS_ALL, | |
| 5160 + 'build_fields': ', '.join(sorted([f.lower() | |
| 5161 + for f in self.BUILD_FIELDS]))
, | |
| 5162 + } | |
| 5163 + self.FLAGS = FLAGS + self.EXTRA_FLAGS % context | |
| 5164 | |
| 5165 self.putChild('project', ProjectJsonResource(status)) | |
| 5166 | |
| 5167 @@ -802,6 +822,7 @@ class BuildStateJsonResource(JsonResource): | |
| 5168 @defer.deferredGenerator | |
| 5169 def asDict(self, request): | |
| 5170 builders = request.args.get('builder', ()) | |
| 5171 + build_fields = request.args.get('build_field', ()) | |
| 5172 current_builds = RequestArgToBool(request, 'current_builds', False) | |
| 5173 completed_builds = self._CountOrCachedRequestArg(request, | |
| 5174 'completed_builds') | |
| 5175 @@ -824,7 +845,7 @@ class BuildStateJsonResource(JsonResource): | |
| 5176 defer.gatherResults( | |
| 5177 [self._getBuilderData(self.status.getBuilder(builder_name), | |
| 5178 current_builds, completed_builds, | |
| 5179 - pending_builds) | |
| 5180 + pending_builds, build_fields) | |
| 5181 for builder_name in builder_names])) | |
| 5182 yield wfd | |
| 5183 response['builders'] = wfd.getResult() | |
| 5184 @@ -839,7 +860,7 @@ class BuildStateJsonResource(JsonResource): | |
| 5185 | |
| 5186 @defer.deferredGenerator | |
| 5187 def _getBuilderData(self, builder, current_builds, completed_builds, | |
| 5188 - pending_builds): | |
| 5189 + pending_builds, build_fields): | |
| 5190 # Load the builder dictionary. We use the synchronous path, since the | |
| 5191 # asynchronous waits for pending builds to load. We handle that path | |
| 5192 # explicitly via the 'pending_builds' option. | |
| 5193 @@ -853,7 +874,8 @@ class BuildStateJsonResource(JsonResource): | |
| 5194 if current_builds or completed_builds: | |
| 5195 tasks.append( | |
| 5196 defer.maybeDeferred(self._loadBuildData, builder, | |
| 5197 - current_builds, completed_builds)) | |
| 5198 + current_builds, completed_builds, | |
| 5199 + build_fields)) | |
| 5200 | |
| 5201 # Get pending builds. | |
| 5202 if pending_builds: | |
| 5203 @@ -872,7 +894,8 @@ class BuildStateJsonResource(JsonResource): | |
| 5204 build_state.update(build_data_entry) | |
| 5205 yield response | |
| 5206 | |
| 5207 - def _loadBuildData(self, builder, current_builds, completed_builds): | |
| 5208 + def _loadBuildData(self, builder, current_builds, completed_builds, | |
| 5209 + build_fields): | |
| 5210 build_state = {} | |
| 5211 builds = set() | |
| 5212 build_data_entries = [] | |
| 5213 @@ -902,10 +925,18 @@ class BuildStateJsonResource(JsonResource): | |
| 5214 builds.update(build_numbers) | |
| 5215 build_data_entries.append(('completed', build_numbers)) | |
| 5216 | |
| 5217 + # Determine which build fields to request. | |
| 5218 + build_fields = [f.lower() for f in build_fields] | |
| 5219 + build_field_kwargs = {} | |
| 5220 + if self.BUILD_FIELDS_ALL not in build_fields: | |
| 5221 + build_field_kwargs.update(dict( | |
| 5222 + (kwarg, kwarg.lower() in build_fields) | |
| 5223 + for kwarg in self.BUILD_FIELDS)) | |
| 5224 + | |
| 5225 # Load all builds referenced by 'builds'. | |
| 5226 builds = builder.getBuilds(builds) | |
| 5227 build_map = dict((build_dict['number'], build_dict) | |
| 5228 - for build_dict in [build.asDict() | |
| 5229 + for build_dict in [build.asDict(**build_field_kwargs) | |
| 5230 for build in builds | |
| 5231 if build]) | |
| 5232 | |
| 5233 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5234 index 9fe46ef..6c701ad 100644 | |
| 5235 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5236 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5237 @@ -868,6 +868,7 @@ class BuildStateJsonResource(JsonResource): | |
| 5238 # This also causes the cache to be updated with recent builds, so we | |
| 5239 # will call it first. | |
| 5240 response = builder.asDict() | |
| 5241 + response['builderName'] = builder.getName() | |
| 5242 tasks = [] | |
| 5243 | |
| 5244 # Get current/completed builds. | |
| 5245 | |
| 5246 Trim long comment fields to 1024 characters, but preserve important tags | |
| 5247 | |
| 5248 BUG=407345 | |
| 5249 R=agable@chromium.org,iannucci@chromium.org | |
| 5250 | |
| 5251 Index: third_party/buildbot_8_4p1/buildbot/db/changes.py | |
| 5252 diff --git a/third_party/buildbot_8_4p1/buildbot/db/changes.py b/third_party/bui
ldbot_8_4p1/buildbot/db/changes.py | |
| 5253 index c6516b6fd26c86bedee27d35fbaeca667ae5697a..eff93e417e28db0e4a57c592e1cb9c70
47433017 100644 | |
| 5254 --- a/third_party/buildbot_8_4p1/buildbot/db/changes.py | |
| 5255 +++ b/third_party/buildbot_8_4p1/buildbot/db/changes.py | |
| 5256 @@ -127,10 +127,17 @@ class ChangesConnectorComponent(base.DBConnectorComponent)
: | |
| 5257 | |
| 5258 transaction = conn.begin() | |
| 5259 | |
| 5260 + # Trim long comment fields to 1024 characters, but preserve header | |
| 5261 + # and footer with important tags such as Cr-Commit-Position. | |
| 5262 + trimmed_comments = comments | |
| 5263 + if len(trimmed_comments) > 1024: | |
| 5264 + header, footer = trimmed_comments[:506], trimmed_comments[-506:] | |
| 5265 + trimmed_comments = '%s\n...skip...\n%s' % (header, footer) | |
| 5266 + | |
| 5267 ins = self.db.model.changes.insert() | |
| 5268 r = conn.execute(ins, dict( | |
| 5269 author=author, | |
| 5270 - comments=comments[:1024], | |
| 5271 + comments=trimmed_comments, | |
| 5272 is_dir=is_dir, | |
| 5273 branch=branch, | |
| 5274 revision=revision, | |
| 5275 Index: third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py | |
| 5276 diff --git a/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial
.py b/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py | |
| 5277 index 875c5ea0d6f33190c53a36a1e4790d441ef12493..c51a06f5f0769ddafe6fc184d859b608
0fcacde7 100644 | |
| 5278 --- a/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py | |
| 5279 +++ b/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py | |
| 5280 @@ -213,10 +213,17 @@ def import_changes(migrate_engine): | |
| 5281 if not c.revision: | |
| 5282 continue | |
| 5283 try: | |
| 5284 + # Trim long comment fields to 1024 characters, but preserve header | |
| 5285 + # and footer with important tags such as Cr-Commit-Position. | |
| 5286 + trimmed_comments = c.comments | |
| 5287 + if len(trimmed_comments) > 1024: | |
| 5288 + header, footer = trimmed_comments[:506], trimmed_comments[-506:] | |
| 5289 + trimmed_comments = '%s\n...skip...\n%s' % (header, footer) | |
| 5290 + | |
| 5291 values = dict( | |
| 5292 changeid=c.number, | |
| 5293 author=c.who[:256], | |
| 5294 - comments=c.comments[:1024], | |
| 5295 + comments=trimmed_comments, | |
| 5296 is_dir=c.isdir, | |
| 5297 branch=c.branch[:256], | |
| 5298 revision=c.revision[:256], | |
| 5299 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5300 index 6c701ad..36f8806 100644 | |
| 5301 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5302 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5303 @@ -18,6 +18,7 @@ | |
| 5304 | |
| 5305 import collections | |
| 5306 import datetime | |
| 5307 +import fnmatch | |
| 5308 import os | |
| 5309 import re | |
| 5310 | |
| 5311 @@ -772,6 +773,7 @@ class BuildStateJsonResource(JsonResource): | |
| 5312 - builder | |
| 5313 - A builder name to explicitly include in the results. This can be supplied | |
| 5314 multiple times. If omitted, data for all builders will be returned. | |
| 5315 + Globbing via asterisk is permitted (e.g., "*_rel") | |
| 5316 - current_builds | |
| 5317 - Controls whether the builder's current-running build data will be | |
| 5318 returned. By default, no current build data will be returned; setting | |
| 5319 @@ -831,8 +833,10 @@ class BuildStateJsonResource(JsonResource): | |
| 5320 | |
| 5321 builder_names = self.status.getBuilderNames() | |
| 5322 if builders: | |
| 5323 + builder_regex = re.compile('|'.join(fnmatch.translate(b) | |
| 5324 + for b in builders)) | |
| 5325 builder_names = [b for b in builder_names | |
| 5326 - if b in set(builders)] | |
| 5327 + if builder_regex.match(b)] | |
| 5328 | |
| 5329 # Collect child endpoint data. | |
| 5330 wfd = defer.waitForDeferred( | |
| 5331 | |
| 5332 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5333 index 36f8806..36b1899 100644 | |
| 5334 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5335 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5336 @@ -678,7 +678,7 @@ class SlaveJsonResource(JsonResource): | |
| 5337 for build_status in builds: | |
| 5338 if not build_status or not build_status.isFinished(): | |
| 5339 # If not finished, it will appear in runningBuilds. | |
| 5340 - break | |
| 5341 + continue | |
| 5342 slave = buildcache[build_status.getSlavename()] | |
| 5343 slave.setdefault(builderName, []).append( | |
| 5344 build_status.getNumber()) | |
| 5345 | |
| 5346 | |
| 5347 commit c956c1bf5b7c3726d80675d311e0a61d6d3f759c | |
| 5348 Author: Ryan Kubiak <rkubiak@google.com> | |
| 5349 Date: Thu Jan 22 17:19:49 2015 -0800 | |
| 5350 | |
| 5351 Fix MailNotifier bug for buildsets | |
| 5352 | |
| 5353 Implementing a fix from buildbot 0.8.6p1. | |
| 5354 http://trac.buildbot.net/ticket/2254 | |
| 5355 https://github.com/buildbot/buildbot/commit/4d3b0318025ca38c148a09e2fec5f2f9
7f61be57 | |
| 5356 | |
| 5357 I wasn't entirely sure of the implications of removing self.db from | |
| 5358 status/master.py. I found two files using status.master.db | |
| 5359 (buildbot/status/builder.py, buildbot/status/web/status_json.py). | |
| 5360 Neither of these are imports and they both seem to still work by | |
| 5361 inherting status from their parent. | |
| 5362 | |
| 5363 I've run this patch on a testing instance and everything seems to still | |
| 5364 work, although I have not tested the MailNotifier. | |
| 5365 | |
| 5366 diff --git a/third_party/buildbot_8_4p1/buildbot/status/mail.py b/third_party/bu
ildbot_8_4p1/buildbot/status/mail.py | |
| 5367 index fd62b52..c03f609 100644 | |
| 5368 --- a/third_party/buildbot_8_4p1/buildbot/status/mail.py | |
| 5369 +++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py | |
| 5370 @@ -332,12 +332,13 @@ class MailNotifier(base.StatusReceiverMultiService): | |
| 5371 def setup(self): | |
| 5372 self.master_status = self.parent.getStatus() | |
| 5373 self.master_status.subscribe(self) | |
| 5374 + self.master = self.master_status.master | |
| 5375 | |
| 5376 | |
| 5377 def startService(self): | |
| 5378 if self.buildSetSummary: | |
| 5379 self.buildSetSubscription = \ | |
| 5380 - self.parent.subscribeToBuildsetCompletions(self.buildsetFinished) | |
| 5381 + self.master.subscribeToBuildsetCompletions(self.buildsetFinished) | |
| 5382 | |
| 5383 base.StatusReceiverMultiService.startService(self) | |
| 5384 | |
| 5385 @@ -428,18 +429,18 @@ class MailNotifier(base.StatusReceiverMultiService): | |
| 5386 for breq in breqs: | |
| 5387 buildername = breq['buildername'] | |
| 5388 builders.append(self.master_status.getBuilder(buildername)) | |
| 5389 - d = self.parent.db.builds.getBuildsForRequest(breq['brid']) | |
| 5390 + d = self.master.db.builds.getBuildsForRequest(breq['brid']) | |
| 5391 d.addCallback(builddicts.append) | |
| 5392 dl.append(d) | |
| 5393 d = defer.DeferredList(dl) | |
| 5394 d.addCallback(self._gotBuilds, builddicts, buildset, builders) | |
| 5395 | |
| 5396 def _gotBuildSet(self, buildset, bsid): | |
| 5397 - d = self.parent.db.buildrequests.getBuildRequests(bsid=bsid) | |
| 5398 + d = self.master.db.buildrequests.getBuildRequests(bsid=bsid) | |
| 5399 d.addCallback(self._gotBuildRequests, buildset) | |
| 5400 | |
| 5401 def buildsetFinished(self, bsid, result): | |
| 5402 - d = self.parent.db.buildsets.getBuildset(bsid=bsid) | |
| 5403 + d = self.master.db.buildsets.getBuildset(bsid=bsid) | |
| 5404 d.addCallback(self._gotBuildSet, bsid) | |
| 5405 | |
| 5406 return d | |
| 5407 diff --git a/third_party/buildbot_8_4p1/buildbot/status/master.py b/third_party/
buildbot_8_4p1/buildbot/status/master.py | |
| 5408 index 1df47a0..7864e2a 100644 | |
| 5409 --- a/third_party/buildbot_8_4p1/buildbot/status/master.py | |
| 5410 +++ b/third_party/buildbot_8_4p1/buildbot/status/master.py | |
| 5411 @@ -34,7 +34,6 @@ class Status: | |
| 5412 def __init__(self, master): | |
| 5413 self.master = master | |
| 5414 self.botmaster = master.botmaster | |
| 5415 - self.db = None | |
| 5416 self.basedir = master.basedir | |
| 5417 self.watchers = [] | |
| 5418 # compress logs bigger than 4k, a good default on linux | |
| 5419 diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_Mail
Notifier.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_Mai
lNotifier.py | |
| 5420 index 3c8a70b..3ee4f49 100644 | |
| 5421 --- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifie
r.py | |
| 5422 +++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifie
r.py | |
| 5423 @@ -140,7 +140,7 @@ class TestMailNotifier(unittest.TestCase): | |
| 5424 buildername='Builder'), | |
| 5425 fakedb.Build(number=0, brid=11, results=SUCCESS
) | |
| 5426 ]) | |
| 5427 - mn.parent = self | |
| 5428 + mn.master = self # FIXME: Should be FakeMaster | |
| 5429 | |
| 5430 self.status = Mock() | |
| 5431 mn.master_status = Mock() | |
| 5432 | |
| 5433 | |
| 5434 commit eabc3bfef4de0456ef66a3eecac1689b4c18b4ea | |
| 5435 Author: Ryan Kubiak <rkubiak@google.com> | |
| 5436 Date: Mon Jan 26 12:10:52 2015 -0800 | |
| 5437 | |
| 5438 Fix MailNotifier startup when buildsets are enabled | |
| 5439 | |
| 5440 diff --git a/third_party/buildbot_8_4p1/buildbot/status/mail.py b/third_party/bu
ildbot_8_4p1/buildbot/status/mail.py | |
| 5441 index c03f609..cd86e86 100644 | |
| 5442 --- a/third_party/buildbot_8_4p1/buildbot/status/mail.py | |
| 5443 +++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py | |
| 5444 @@ -327,22 +327,20 @@ class MailNotifier(base.StatusReceiverMultiService): | |
| 5445 @type parent: L{buildbot.master.BuildMaster} | |
| 5446 """ | |
| 5447 base.StatusReceiverMultiService.setServiceParent(self, parent) | |
| 5448 - self.setup() | |
| 5449 | |
| 5450 def setup(self): | |
| 5451 self.master_status = self.parent.getStatus() | |
| 5452 self.master_status.subscribe(self) | |
| 5453 self.master = self.master_status.master | |
| 5454 | |
| 5455 - | |
| 5456 def startService(self): | |
| 5457 + self.setup() | |
| 5458 if self.buildSetSummary: | |
| 5459 self.buildSetSubscription = \ | |
| 5460 self.master.subscribeToBuildsetCompletions(self.buildsetFinished) | |
| 5461 | |
| 5462 base.StatusReceiverMultiService.startService(self) | |
| 5463 | |
| 5464 - | |
| 5465 def stopService(self): | |
| 5466 if self.buildSetSubscription is not None: | |
| 5467 self.buildSetSubscription.unsubscribe() | |
| 5468 | |
| 5469 | |
| 5470 commit 0832eba0b0d223ef028a84cd93956addc9173b0e | |
| 5471 Author: Mike Stipicevic <stip@chromium.org> | |
| 5472 Date: Wed Feb 11 17:23:44 2015 -0800 | |
| 5473 | |
| 5474 Add server start time, current time, and uptime to json endpoint. | |
| 5475 | |
| 5476 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5477 index 36b1899..ff2758f 100644 | |
| 5478 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5479 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5480 @@ -31,6 +31,22 @@ from buildbot.status.web.base import HtmlResource | |
| 5481 from buildbot.util import json, now | |
| 5482 | |
| 5483 | |
| 5484 +def get_timeblock(): | |
| 5485 + def dt_to_ts(date): | |
| 5486 + return (date - datetime.datetime.utcfromtimestamp(0)).total_seconds() | |
| 5487 + | |
| 5488 + utcnow = datetime.datetime.utcnow() | |
| 5489 + | |
| 5490 + return { | |
| 5491 + 'local': datetime.datetime.now().isoformat(), | |
| 5492 + 'utc': utcnow.isoformat(), | |
| 5493 + 'utc_ts': dt_to_ts(utcnow), | |
| 5494 + } | |
| 5495 + | |
| 5496 + | |
| 5497 +SERVER_STARTED = get_timeblock() | |
| 5498 + | |
| 5499 + | |
| 5500 _IS_INT = re.compile('^[-+]?\d+$') | |
| 5501 | |
| 5502 | |
| 5503 @@ -983,6 +999,23 @@ class BuildStateJsonResource(JsonResource): | |
| 5504 } | |
| 5505 | |
| 5506 | |
| 5507 +class MasterClockResource(JsonResource): | |
| 5508 + help = """Show current time, boot time and uptime of the master.""" | |
| 5509 + pageTitle = 'MasterClock' | |
| 5510 + | |
| 5511 + def asDict(self, _request): | |
| 5512 + # The only reason we include local time is because the buildbot UI | |
| 5513 + # displays it. Any computations on time should be done in UTC. | |
| 5514 + current_timeblock = get_timeblock() | |
| 5515 + | |
| 5516 + return { | |
| 5517 + 'server_started': SERVER_STARTED, | |
| 5518 + 'current': current_timeblock, | |
| 5519 + 'server_uptime': ( | |
| 5520 + current_timeblock['utc_ts'] - SERVER_STARTED['utc_ts']) | |
| 5521 + } | |
| 5522 + | |
| 5523 + | |
| 5524 class JsonStatusResource(JsonResource): | |
| 5525 """Retrieves all json data.""" | |
| 5526 help = """JSON status | |
| 5527 @@ -1003,6 +1036,7 @@ For help on any sub directory, use url /child/help | |
| 5528 self.putChild('project', ProjectJsonResource(status)) | |
| 5529 self.putChild('slaves', SlavesJsonResource(status)) | |
| 5530 self.putChild('metrics', MetricsJsonResource(status)) | |
| 5531 + self.putChild('clock', MasterClockResource(status)) | |
| 5532 self.putChild('buildstate', BuildStateJsonResource(status)) | |
| 5533 # This needs to be called before the first HelpResource().body call. | |
| 5534 self.hackExamples() | |
| 5535 | |
| 5536 commit 343c20bf0aff6a5240670a415e3ee41d7c488e8a | |
| 5537 Author: Mike Stipicevic <stip@chromium.org> | |
| 5538 Date: Fri Feb 20 14:44:41 2015 -0800 | |
| 5539 | |
| 5540 Record submittedAt as a buildproperty. | |
| 5541 | |
| 5542 diff --git a/third_party/buildbot_8_4p1/buildbot/process/build.py b/third_party/
buildbot_8_4p1/buildbot/process/build.py | |
| 5543 index 04cf472..3376b5b 100644 | |
| 5544 --- a/third_party/buildbot_8_4p1/buildbot/process/build.py | |
| 5545 +++ b/third_party/buildbot_8_4p1/buildbot/process/build.py | |
| 5546 @@ -60,11 +60,15 @@ class Build: | |
| 5547 finished = False | |
| 5548 results = None | |
| 5549 stopped = False | |
| 5550 + requestedAt = None | |
| 5551 | |
| 5552 def __init__(self, requests): | |
| 5553 self.requests = requests | |
| 5554 self.locks = [] | |
| 5555 | |
| 5556 + self.requestedAt = ( | |
| 5557 + sorted(r.submittedAt for r in requests) or [None])[0] | |
| 5558 + | |
| 5559 # build a source stamp | |
| 5560 self.source = requests[0].mergeWith(requests[1:]) | |
| 5561 self.reason = requests[0].mergeReasons(requests[1:]) | |
| 5562 @@ -183,6 +187,7 @@ class Build: | |
| 5563 props.setProperty("revision", self.source.revision, "Build") | |
| 5564 props.setProperty("repository", self.source.repository, "Build") | |
| 5565 props.setProperty("project", self.source.project, "Build") | |
| 5566 + props.setProperty("requestedAt", self.requestedAt, "Build") | |
| 5567 self.builder.setupProperties(props) | |
| 5568 | |
| 5569 def setupSlaveBuilder(self, slavebuilder): | |
| 5570 | |
| 5571 | |
| 5572 commit d543bc773c6066d1a06ccd147020384429f67c57 | |
| 5573 Author: Mike Stipicevic <stip@chromium.org> | |
| 5574 Date: Wed Feb 25 15:16:54 2015 -0800 | |
| 5575 | |
| 5576 Report no-new-builds status. | |
| 5577 | |
| 5578 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5579 index ff2758f..8469c25 100644 | |
| 5580 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5581 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5582 @@ -1016,6 +1016,16 @@ class MasterClockResource(JsonResource): | |
| 5583 } | |
| 5584 | |
| 5585 | |
| 5586 +class AcceptingBuildsResource(JsonResource): | |
| 5587 + help = """Show whether the master is scheduling new builds.""" | |
| 5588 + pageTitle = 'AcceptingBuilds' | |
| 5589 + | |
| 5590 + def asDict(self, _request): | |
| 5591 + return { | |
| 5592 + 'accepting_builds': bool(self.status.master.botmaster.brd.running), | |
| 5593 + } | |
| 5594 + | |
| 5595 + | |
| 5596 class JsonStatusResource(JsonResource): | |
| 5597 """Retrieves all json data.""" | |
| 5598 help = """JSON status | |
| 5599 @@ -1037,6 +1047,7 @@ For help on any sub directory, use url /child/help | |
| 5600 self.putChild('slaves', SlavesJsonResource(status)) | |
| 5601 self.putChild('metrics', MetricsJsonResource(status)) | |
| 5602 self.putChild('clock', MasterClockResource(status)) | |
| 5603 + self.putChild('accepting_builds', AcceptingBuildsResource(status)) | |
| 5604 self.putChild('buildstate', BuildStateJsonResource(status)) | |
| 5605 # This needs to be called before the first HelpResource().body call. | |
| 5606 self.hackExamples() | |
| 5607 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5608 index 8469c25..531199b 100644 | |
| 5609 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5610 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5611 @@ -876,6 +876,8 @@ class BuildStateJsonResource(JsonResource): | |
| 5612 | |
| 5613 # Add timestamp and return. | |
| 5614 response['timestamp'] = now() | |
| 5615 + response['accepting_builds'] = bool( | |
| 5616 + self.status.master.botmaster.brd.running) | |
| 5617 yield response | |
| 5618 | |
| 5619 @defer.deferredGenerator | |
| 5620 | |
| 5621 | |
| 5622 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 5623 index 92c9544..f9d5b82 100644 | |
| 5624 --- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 5625 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py | |
| 5626 @@ -155,6 +155,10 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin): | |
| 5627 if not self.getAuthz(req).actionAllowed('forceBuild', req, self.bui
lder_status): | |
| 5628 log.msg("..but not authorized") | |
| 5629 return Redirect(path_to_authfail(req)) | |
| 5630 + # ensure that they've filled out the username field at least. | |
| 5631 + if name == "<unknown>": | |
| 5632 + log.msg("..but didn't include a username to blame") | |
| 5633 + return Redirect(path_to_authfail(req)) | |
| 5634 | |
| 5635 # keep weird stuff out of the branch revision, and property strings. | |
| 5636 # TODO: centralize this somewhere. | |
| 5637 | |
| 5638 | |
| 5639 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave
.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html | |
| 5640 index 0689e87..7ae3c9a 100644 | |
| 5641 --- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html | |
| 5642 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html | |
| 5643 @@ -12,10 +12,9 @@ | |
| 5644 <ul> | |
| 5645 {% for b in current %} | |
| 5646 <li>{{ build_line(b, True) }} | |
| 5647 - <form method="post" action="{{ b.buildurl }}/stop" class="command stopbuil
d" style="display:inline"> | |
| 5648 - <input type="submit" value="Stop Build" /> | |
| 5649 - <input type="hidden" name="url" value="{{ this_url }}" /> | |
| 5650 - </form> | |
| 5651 + {% if authz.advertiseAction('stopBuild') %} | |
| 5652 + {{ forms.stop_build(b.buildurl + '/stop', authz, on_all=False, short=True
, label='Build') }} | |
| 5653 + {% endif %} | |
| 5654 </li> | |
| 5655 {% endfor %} | |
| 5656 </ul> | |
| 5657 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5658 index 531199b..8675da9 100644 | |
| 5659 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5660 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5661 @@ -293,20 +293,17 @@ class JsonResource(resource.Resource): | |
| 5662 """Generates the json dictionary. | |
| 5663 | |
| 5664 By default, renders every childs.""" | |
| 5665 - if self.children: | |
| 5666 - data = {} | |
| 5667 - for name in self.children: | |
| 5668 - child = self.getChildWithDefault(name, request) | |
| 5669 - if isinstance(child, JsonResource): | |
| 5670 - wfd = defer.waitForDeferred( | |
| 5671 - defer.maybeDeferred(lambda : | |
| 5672 - child.asDict(request))) | |
| 5673 - yield wfd | |
| 5674 - data[name] = wfd.getResult() | |
| 5675 - # else silently pass over non-json resources. | |
| 5676 - yield data | |
| 5677 - else: | |
| 5678 - raise NotImplementedError() | |
| 5679 + data = {} | |
| 5680 + for name in self.children: | |
| 5681 + child = self.getChildWithDefault(name, request) | |
| 5682 + if isinstance(child, JsonResource): | |
| 5683 + wfd = defer.waitForDeferred( | |
| 5684 + defer.maybeDeferred(lambda : | |
| 5685 + child.asDict(request))) | |
| 5686 + yield wfd | |
| 5687 + data[name] = wfd.getResult() | |
| 5688 + # else silently pass over non-json resources. | |
| 5689 + yield data | |
| 5690 | |
| 5691 | |
| 5692 def ToHtml(text): | |
| 5693 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5694 index 8675da9..082b098 100644 | |
| 5695 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5696 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5697 @@ -1025,6 +1025,42 @@ class AcceptingBuildsResource(JsonResource): | |
| 5698 } | |
| 5699 | |
| 5700 | |
| 5701 +class VarzResource(JsonResource): | |
| 5702 + help = 'Minimal set of metrics that are scraped frequently for monitoring.' | |
| 5703 + pageTitle = 'Varz' | |
| 5704 + | |
| 5705 + @defer.deferredGenerator | |
| 5706 + def asDict(self, _request): | |
| 5707 + builders = {} | |
| 5708 + for builder_name in self.status.getBuilderNames(): | |
| 5709 + builder = self.status.getBuilder(builder_name) | |
| 5710 + slaves = builder.getSlaves() | |
| 5711 + builders[builder_name] = { | |
| 5712 + 'connected_slaves': sum(1 for x in slaves if x.connected), | |
| 5713 + 'current_builds': len(builder.getCurrentBuilds()), | |
| 5714 + 'pending_builds': 0, | |
| 5715 + 'state': builder.currentBigState, | |
| 5716 + 'total_slaves': len(slaves), | |
| 5717 + } | |
| 5718 + | |
| 5719 + # Get pending build requests directly from the db for all builders at | |
| 5720 + # once. | |
| 5721 + d = self.status.master.db.buildrequests.getBuildRequests(claimed=False) | |
| 5722 + def pending_builds_callback(brdicts): | |
| 5723 + for brdict in brdicts: | |
| 5724 + if brdict['buildername'] in builders: | |
| 5725 + builders[brdict['buildername']]['pending_builds'] += 1 | |
| 5726 + d.addCallback(pending_builds_callback) | |
| 5727 + yield defer.waitForDeferred(d) | |
| 5728 + | |
| 5729 + yield { | |
| 5730 + 'accepting_builds': bool(self.status.master.botmaster.brd.running), | |
| 5731 + 'builders': builders, | |
| 5732 + 'server_uptime': ( | |
| 5733 + get_timeblock()['utc_ts'] - SERVER_STARTED['utc_ts']), | |
| 5734 + } | |
| 5735 + | |
| 5736 + | |
| 5737 class JsonStatusResource(JsonResource): | |
| 5738 """Retrieves all json data.""" | |
| 5739 help = """JSON status | |
| 5740 @@ -1048,6 +1084,7 @@ For help on any sub directory, use url /child/help | |
| 5741 self.putChild('clock', MasterClockResource(status)) | |
| 5742 self.putChild('accepting_builds', AcceptingBuildsResource(status)) | |
| 5743 self.putChild('buildstate', BuildStateJsonResource(status)) | |
| 5744 + self.putChild('varz', VarzResource(status)) | |
| 5745 # This needs to be called before the first HelpResource().body call. | |
| 5746 self.hackExamples() | |
| 5747 | |
| 5748 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5749 index 082b098..84c0e09 100644 | |
| 5750 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5751 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5752 @@ -27,6 +27,7 @@ from twisted.python import log as twlog | |
| 5753 from twisted.web import html, resource, server | |
| 5754 | |
| 5755 from buildbot.changes import changes | |
| 5756 +from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE | |
| 5757 from buildbot.status.web.base import HtmlResource | |
| 5758 from buildbot.util import json, now | |
| 5759 | |
| 5760 @@ -1029,15 +1030,49 @@ class VarzResource(JsonResource): | |
| 5761 help = 'Minimal set of metrics that are scraped frequently for monitoring.' | |
| 5762 pageTitle = 'Varz' | |
| 5763 | |
| 5764 + RECENT_BUILDS_COUNT = 50 | |
| 5765 + | |
| 5766 @defer.deferredGenerator | |
| 5767 def asDict(self, _request): | |
| 5768 builders = {} | |
| 5769 for builder_name in self.status.getBuilderNames(): | |
| 5770 builder = self.status.getBuilder(builder_name) | |
| 5771 + | |
| 5772 + build_range = range(-1, -self.RECENT_BUILDS_COUNT - 1, -1) | |
| 5773 + recent_builds = [b for b in builder.getBuilds(build_range) | |
| 5774 + if b is not None] | |
| 5775 + recent_pending_builds = [b for b in recent_builds | |
| 5776 + if not b.isFinished()] | |
| 5777 + recent_finished_builds = [b for b in recent_builds | |
| 5778 + if b.isFinished()] | |
| 5779 + recent_successful_builds = [ | |
| 5780 + b for b in recent_finished_builds | |
| 5781 + if b.getResults() in (SUCCESS, WARNINGS)] | |
| 5782 + recent_infra_failed_builds = [ | |
| 5783 + b for b in recent_finished_builds | |
| 5784 + if b.getResults() not in (SUCCESS, WARNINGS, FAILURE)] | |
| 5785 + recent_failed_builds = [ | |
| 5786 + b for b in recent_finished_builds | |
| 5787 + if b.getResults() == FAILURE] | |
| 5788 + | |
| 5789 + recent_successful_build_times = [ | |
| 5790 + int(b.getTimes()[1] - b.getTimes()[0]) | |
| 5791 + for b in recent_successful_builds] | |
| 5792 + recent_finished_build_times = [ | |
| 5793 + int(b.getTimes()[1] - b.getTimes()[0]) | |
| 5794 + for b in recent_finished_builds] | |
| 5795 + | |
| 5796 slaves = builder.getSlaves() | |
| 5797 builders[builder_name] = { | |
| 5798 'connected_slaves': sum(1 for x in slaves if x.connected), | |
| 5799 'current_builds': len(builder.getCurrentBuilds()), | |
| 5800 + 'recent_builds': len(recent_builds), | |
| 5801 + 'recent_pending_builds': len(recent_pending_builds), | |
| 5802 + 'recent_successful_builds': len(recent_successful_builds), | |
| 5803 + 'recent_infra_failed_builds': len(recent_infra_failed_builds), | |
| 5804 + 'recent_failed_builds': len(recent_failed_builds), | |
| 5805 + 'recent_successful_build_times': recent_successful_build_times, | |
| 5806 + 'recent_finished_build_times': recent_finished_build_times, | |
| 5807 'pending_builds': 0, | |
| 5808 'state': builder.currentBigState, | |
| 5809 'total_slaves': len(slaves), | |
| 5810 | |
| 5811 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/thi
rd_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5812 index 84c0e09..70cf371 100644 | |
| 5813 --- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5814 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py | |
| 5815 @@ -1030,30 +1030,37 @@ class VarzResource(JsonResource): | |
| 5816 help = 'Minimal set of metrics that are scraped frequently for monitoring.' | |
| 5817 pageTitle = 'Varz' | |
| 5818 | |
| 5819 - RECENT_BUILDS_COUNT = 50 | |
| 5820 + RECENT_BUILDS_COUNT_DEFAULT = 50 | |
| 5821 + RECENT_BUILDS_COUNT_LIMIT = 200 | |
| 5822 | |
| 5823 @defer.deferredGenerator | |
| 5824 - def asDict(self, _request): | |
| 5825 + def asDict(self, request): | |
| 5826 + recent_builds_count = int(RequestArg( | |
| 5827 + request, 'recent_builds_count', self.RECENT_BUILDS_COUNT_DEFAULT)) | |
| 5828 + | |
| 5829 + # Enforce a hard limit to avoid DoS-ing buildbot with a heavy request. | |
| 5830 + recent_builds_count = min( | |
| 5831 + recent_builds_count, self.RECENT_BUILDS_COUNT_LIMIT) | |
| 5832 + | |
| 5833 builders = {} | |
| 5834 for builder_name in self.status.getBuilderNames(): | |
| 5835 builder = self.status.getBuilder(builder_name) | |
| 5836 | |
| 5837 - build_range = range(-1, -self.RECENT_BUILDS_COUNT - 1, -1) | |
| 5838 + build_range = range(-1, -recent_builds_count - 1, -1) | |
| 5839 recent_builds = [b for b in builder.getBuilds(build_range) | |
| 5840 if b is not None] | |
| 5841 - recent_pending_builds = [b for b in recent_builds | |
| 5842 + recent_running_builds = [b for b in recent_builds | |
| 5843 if not b.isFinished()] | |
| 5844 recent_finished_builds = [b for b in recent_builds | |
| 5845 if b.isFinished()] | |
| 5846 recent_successful_builds = [ | |
| 5847 b for b in recent_finished_builds | |
| 5848 if b.getResults() in (SUCCESS, WARNINGS)] | |
| 5849 - recent_infra_failed_builds = [ | |
| 5850 - b for b in recent_finished_builds | |
| 5851 - if b.getResults() not in (SUCCESS, WARNINGS, FAILURE)] | |
| 5852 - recent_failed_builds = [ | |
| 5853 - b for b in recent_finished_builds | |
| 5854 - if b.getResults() == FAILURE] | |
| 5855 + | |
| 5856 + recent_builds_by_status = collections.defaultdict(int) | |
| 5857 + recent_builds_by_status['running'] = len(recent_running_builds) | |
| 5858 + for b in recent_finished_builds: | |
| 5859 + recent_builds_by_status[b.getResults()] += 1 | |
| 5860 | |
| 5861 recent_successful_build_times = [ | |
| 5862 int(b.getTimes()[1] - b.getTimes()[0]) | |
| 5863 @@ -1066,11 +1073,7 @@ class VarzResource(JsonResource): | |
| 5864 builders[builder_name] = { | |
| 5865 'connected_slaves': sum(1 for x in slaves if x.connected), | |
| 5866 'current_builds': len(builder.getCurrentBuilds()), | |
| 5867 - 'recent_builds': len(recent_builds), | |
| 5868 - 'recent_pending_builds': len(recent_pending_builds), | |
| 5869 - 'recent_successful_builds': len(recent_successful_builds), | |
| 5870 - 'recent_infra_failed_builds': len(recent_infra_failed_builds), | |
| 5871 - 'recent_failed_builds': len(recent_failed_builds), | |
| 5872 + 'recent_builds_by_status': recent_builds_by_status, | |
| 5873 'recent_successful_build_times': recent_successful_build_times, | |
| 5874 'recent_finished_build_times': recent_finished_build_times, | |
| 5875 'pending_builds': 0, | |
| 5876 | |
| 5877 commit 51ce3a66aadbbe847dd0501ad023f4598601f793 | |
| 5878 Author: Rico Wind <ricow@google.com> | |
| 5879 Date: Fri Aug 21 13:08:28 2015 +0200 | |
| 5880 | |
| 5881 Fix buildbot console view in order_by_time mode | |
| 5882 | |
| 5883 Applying a rebased version of http://trac.buildbot.net/ticket/2364 | |
| 5884 | |
| 5885 diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_p
arty/buildbot_8_4p1/buildbot/status/web/console.py | |
| 5886 index 2d48aca..36def52 100644 | |
| 5887 --- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 5888 +++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py | |
| 5889 @@ -158,7 +158,8 @@ class DevRevision: | |
| 5890 class DevBuild: | |
| 5891 """Helper class that contains all the information we need for a build.""" | |
| 5892 | |
| 5893 - def __init__(self, revision, build, details, inProgressResults=None): | |
| 5894 + def __init__(self, revision, build, details, inProgressResults=None, | |
| 5895 + revisions=[]): | |
| 5896 self.revision = revision | |
| 5897 self.results = build.getResults() | |
| 5898 self.number = build.getNumber() | |
| 5899 @@ -169,6 +170,10 @@ class DevBuild: | |
| 5900 self.when = build.getTimes()[0] | |
| 5901 self.source = build.getSourceStamp() | |
| 5902 self.inProgressResults = inProgressResults | |
| 5903 + for rev in revisions: | |
| 5904 + if rev.revision == revision: | |
| 5905 + self.when = rev.when | |
| 5906 + break | |
| 5907 | |
| 5908 | |
| 5909 class ConsoleStatusResource(HtmlResource): | |
| 5910 @@ -316,7 +321,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 5911 return details | |
| 5912 | |
| 5913 def getBuildsForRevision(self, request, builder, builderName, lastRevision, | |
| 5914 - numBuilds, debugInfo): | |
| 5915 + numBuilds, debugInfo, revisions): | |
| 5916 """Return the list of all the builds for a given builder that we will | |
| 5917 need to be able to display the console page. We start by the most recen
t | |
| 5918 build, and we go down until we find a build that was built prior to the | |
| 5919 @@ -357,7 +362,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 5920 if got_rev and got_rev != -1: | |
| 5921 details = self.getBuildDetails(request, builderName, build) | |
| 5922 devBuild = DevBuild(got_rev, build, details, | |
| 5923 - getInProgressResults(build)) | |
| 5924 + getInProgressResults(build), revisions) | |
| 5925 builds.append(devBuild) | |
| 5926 | |
| 5927 # Now break if we have enough builds. | |
| 5928 @@ -385,7 +390,7 @@ class ConsoleStatusResource(HtmlResource): | |
| 5929 return changes[-1] | |
| 5930 | |
| 5931 def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds, | |
| 5932 - categories, builders, debugInfo): | |
| 5933 + categories, builders, debugInfo, revisions): | |
| 5934 """Returns a dictionary of builds we need to inspect to be able to | |
| 5935 display the console page. The key is the builder name, and the value is | |
| 5936 an array of build we care about. We also returns a dictionary of | |
| 5937 @@ -436,7 +441,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 5938 builderName, | |
| 5939 lastRevision, | |
| 5940 numBuilds, | |
| 5941 - debugInfo) | |
| 5942 + debugInfo, | |
| 5943 + revisions) | |
| 5944 | |
| 5945 return (builderList, allBuilds) | |
| 5946 | |
| 5947 @@ -800,7 +806,8 @@ class ConsoleStatusResource(HtmlResource): | |
| 5948 numBuilds, | |
| 5949 categories, | |
| 5950 builders, | |
| 5951 - debugInfo) | |
| 5952 + debugInfo, | |
| 5953 + revisions) | |
| 5954 | |
| 5955 debugInfo["added_blocks"] = 0 | |
| 5956 debugInfo["from_cache"] = 0 | |
| OLD | NEW |