Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(502)

Side by Side Diff: third_party/buildbot_8_4p1/README.chromium

Issue 1383143003: Remove deprecated metrics from buildbot /varz. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | third_party/buildbot_8_4p1/buildbot/status/web/status_json.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
OLDNEW
« no previous file with comments | « no previous file | third_party/buildbot_8_4p1/buildbot/status/web/status_json.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698