[SCM] WebKit Debian packaging branch, webkit-1.2, updated. upstream/1.1.90-6072-g9a69373

eric at webkit.org eric at webkit.org
Thu Apr 8 02:23:24 UTC 2010


The following commit has been merged in the webkit-1.2 branch:
commit b7cbdb9f92a17071dfbe2962c5d7ec91f711965b
Author: eric at webkit.org <eric at webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Date:   Tue Mar 16 07:11:32 2010 +0000

    2010-03-15  Eric Seidel  <eric at webkit.org>
    
            Reviewed by Adam Barth.
    
            Add "what-broke" command for debugging when the tree broke
            https://bugs.webkit.org/show_bug.cgi?id=36157
    
            This is another step towards automated sheriffing of the webkit tree.
            With this logic our scripts are able to determine what revision broke the
            tree.  Buildbot should do this for us, but unfortunately buildbot doesn't
            expose this kind of aggregate information.
    
            * Scripts/webkitpy/buildbot.py:
             - Add new Builder and Build classes (which will eventually replace the custom dictionaries previously used).
             - Split out more network logic into _fetch methods which will eventually be their own class for mocking.
             - Use XMLRPC to communicate with the buildbot master instead of scraping build pages.
            * Scripts/webkitpy/buildbot_unittest.py:
             - Test the newly added buildbot classes.
            * Scripts/webkitpy/commands/queries.py:
             - Add an experimental what-broke command.
    
    git-svn-id: http://svn.webkit.org/repository/webkit/trunk@56045 268f45cc-cd09-0410-ab3c-d52691b4dbfc

diff --git a/WebKitTools/ChangeLog b/WebKitTools/ChangeLog
index 4f7d05e..91821e4 100644
--- a/WebKitTools/ChangeLog
+++ b/WebKitTools/ChangeLog
@@ -1,3 +1,24 @@
+2010-03-15  Eric Seidel  <eric at webkit.org>
+
+        Reviewed by Adam Barth.
+
+        Add "what-broke" command for debugging when the tree broke
+        https://bugs.webkit.org/show_bug.cgi?id=36157
+
+        This is another step towards automated sheriffing of the webkit tree.
+        With this logic our scripts are able to determine what revision broke the
+        tree.  Buildbot should do this for us, but unfortunately buildbot doesn't
+        expose this kind of aggregate information.
+
+        * Scripts/webkitpy/buildbot.py:
+         - Add new Builder and Build classes (which will eventually replace the custom dictionaries previously used).
+         - Split out more network logic into _fetch methods which will eventually be their own class for mocking.
+         - Use XMLRPC to communicate with the buildbot master instead of scraping build pages.
+        * Scripts/webkitpy/buildbot_unittest.py:
+         - Test the newly added buildbot classes.
+        * Scripts/webkitpy/commands/queries.py:
+         - Add an experimental what-broke command.
+
 2010-03-15  Daniel Bates  <dbates at rim.com>
 
         Reviewed by Eric Seidel.
diff --git a/WebKitTools/Scripts/webkitpy/buildbot.py b/WebKitTools/Scripts/webkitpy/buildbot.py
index 38d103a..9c61756 100644
--- a/WebKitTools/Scripts/webkitpy/buildbot.py
+++ b/WebKitTools/Scripts/webkitpy/buildbot.py
@@ -29,7 +29,9 @@
 # WebKit's Python module for interacting with WebKit's buildbot
 
 import re
+import urllib
 import urllib2
+import xmlrpclib
 
 # Import WebKit-specific modules.
 from webkitpy.webkit_logging import log
@@ -39,13 +41,63 @@ from webkitpy.webkit_logging import log
 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
 
 
-class BuildBot:
+class Builder(object):
+    def __init__(self, name, buildbot):
+        self._name = unicode(name)
+        self._buildbot = buildbot
+        self._builds_cache = {}
+
+    def name(self):
+        return self._name
+
+    def url(self):
+        return "http://%s/builders/%s" % (self._buildbot.buildbot_host, urllib.quote(self._name))
+
+    def build(self, build_number):
+        cached_build = self._builds_cache.get(build_number)
+        if cached_build:
+            return cached_build
+
+        build_dictionary = self._buildbot._fetch_xmlrpc_build_dictionary(self._name, build_number)
+        if not build_dictionary:
+            return None
+        build = Build(build_dictionary, self)
+        self._builds_cache[build_number] = build
+        return build
+
+
+class Build(object):
+    def __init__(self, build_dictionary, builder):
+        self._builder = builder
+        # FIXME: Knowledge of the XMLRPC specifics should probably not go here.
+        self._revision = int(build_dictionary['revision'])
+        self._number = int(build_dictionary['number'])
+        self._is_green = (build_dictionary['results'] == 0) # Undocumented, buildbot XMLRPC
+
+    def url(self):
+        return "%s/builds/%s" % (self.builder().url(), self._number)
+
+    def builder(self):
+        return self._builder
+
+    def revision(self):
+        return self._revision
+
+    def is_green(self):
+        return self._is_green
+
+    def previous_build(self):
+        # previous_build() allows callers to avoid assuming build numbers are sequential.
+        # They may not be sequential across all master changes, or when non-trunk builds are made.
+        return self._builder.build(self._number - 1)
+
+
+class BuildBot(object):
 
     default_host = "build.webkit.org"
 
     def __init__(self, host=default_host):
         self.buildbot_host = host
-        self.buildbot_server_url = "http://%s/" % self.buildbot_host
 
         # If any Leopard builder/tester, Windows builder or Chromium builder is
         # red we should not be landing patches.  Other builders should be added
@@ -57,6 +109,8 @@ class BuildBot:
             "Chromium",
         ]
 
+    # FIXME: This should create and return Buidler and Build objects instead
+    # of a custom dictionary.
     def _parse_builder_status_from_row(self, status_row):
         # If WebKit's buildbot has an XMLRPC interface we could use, we could
         # do something more sophisticated here.  For now we just parse out the
@@ -66,9 +120,6 @@ class BuildBot:
 
         name_link = status_cells[0].find('a')
         builder['name'] = name_link.string
-        # We could generate the builder_url from the name in a future version
-        # of this code.
-        builder['builder_url'] = self.buildbot_server_url + name_link['href']
 
         status_link = status_cells[1].find('a')
         if not status_link:
@@ -86,12 +137,12 @@ class BuildBot:
                                     else None
         builder['is_green'] = not re.search('fail',
                                             status_cells[1].renderContents())
-        # We could parse out the build number instead, but for now just store
-        # the URL.
-        builder['build_url'] = self.buildbot_server_url + status_link['href']
 
-        # We could parse out the current activity too.
+        status_link_regexp = r"builders/(?P<builder_name>.*)/builds/(?P<build_number>\d+)"
+        link_match = re.match(status_link_regexp, status_link['href'])
+        builder['build_number'] = int(link_match.group("build_number"))
 
+        # We could parse out the current activity too.
         return builder
 
     def _builder_statuses_with_names_matching_regexps(self,
@@ -120,14 +171,29 @@ class BuildBot:
     def core_builders_are_green(self):
         return not self.red_core_builders()
 
-    def builder_statuses(self):
-        build_status_url = self.buildbot_server_url + 'one_box_per_builder'
-        page = urllib2.urlopen(build_status_url)
-        soup = BeautifulSoup(page)
+    # FIXME: These _fetch methods should move to a networking class.
+    def _fetch_xmlrpc_build_dictionary(self, builder_name, build_number):
+        # The buildbot XMLRPC API is super-limited.
+        # For one, you cannot fetch info on builds which are incomplete.
+        proxy = xmlrpclib.ServerProxy("http://%s/xmlrpc" % self.buildbot_host)
+        try:
+            return proxy.getBuild(builder_name, int(build_number))
+        except xmlrpclib.Fault, err:
+            log("Error fetching data for build %s" % build_number)
+            return None
+
+    def _fetch_one_box_per_builder(self):
+        build_status_url = "http://%s/one_box_per_builder" % self.buildbot_host
+        return urllib2.urlopen(build_status_url)
 
+    def builder_statuses(self):
         builders = []
+        soup = BeautifulSoup(self._fetch_one_box_per_builder())
         status_table = soup.find('table')
         for status_row in status_table.findAll('tr'):
             builder = self._parse_builder_status_from_row(status_row)
             builders.append(builder)
         return builders
+
+    def builder_with_name(self, name):
+        return Builder(name, self)
diff --git a/WebKitTools/Scripts/webkitpy/buildbot_unittest.py b/WebKitTools/Scripts/webkitpy/buildbot_unittest.py
index 21dfd11..e8cf6ec 100644
--- a/WebKitTools/Scripts/webkitpy/buildbot_unittest.py
+++ b/WebKitTools/Scripts/webkitpy/buildbot_unittest.py
@@ -32,6 +32,7 @@ from webkitpy.buildbot import BuildBot
 
 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup
 
+
 class BuildBotTest(unittest.TestCase):
 
     _example_one_box_status = '''
@@ -52,21 +53,18 @@ class BuildBotTest(unittest.TestCase):
 '''
     _expected_example_one_box_parsings = [
         {
-            'builder_url': u'http://build.webkit.org/builders/Windows%20Debug%20%28Tests%29',
-            'build_url': u'http://build.webkit.org/builders/Windows%20Debug%20%28Tests%29/builds/3693',
             'is_green': True,
+            'build_number' : 3693,
             'name': u'Windows Debug (Tests)',
             'built_revision': 47380
         },
         {
-            'builder_url': u'http://build.webkit.org/builders/SnowLeopard%20Intel%20Release',
             'is_green': False,
             'name': u'SnowLeopard Intel Release',
         },
         {
-            'builder_url': u'http://build.webkit.org/builders/Qt%20Linux%20Release',
-            'build_url': u'http://build.webkit.org/builders/Qt%20Linux%20Release/builds/654',
             'is_green': False,
+            'build_number' : 654,
             'name': u'Qt Linux Release',
             'built_revision': 47383
         },
@@ -151,5 +149,35 @@ class BuildBotTest(unittest.TestCase):
         builders = buildbot._builder_statuses_with_names_matching_regexps(example_builders, name_regexps)
         self.assertEquals(builders, expected_builders)
 
+    def test_builder_with_name(self):
+        buildbot = BuildBot()
+
+        builder = buildbot.builder_with_name("Test Builder")
+        self.assertEqual(builder.name(), "Test Builder")
+        self.assertEqual(builder.url(), "http://build.webkit.org/builders/Test%20Builder")
+
+        # Override _fetch_xmlrpc_build_dictionary function to not touch the network.
+        def mock_fetch_xmlrpc_build_dictionary(self, build_number):
+            build_dictionary = {
+                "revision" : 2 * build_number,
+                "number" : int(build_number),
+                "results" : build_number % 2, # 0 means pass
+            }
+            return build_dictionary
+        buildbot._fetch_xmlrpc_build_dictionary = mock_fetch_xmlrpc_build_dictionary
+
+        build = builder.build(10)
+        self.assertEqual(build.builder(), builder)
+        self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/10")
+        self.assertEqual(build.revision(), 20)
+        self.assertEqual(build.is_green(), True)
+
+        build = build.previous_build()
+        self.assertEqual(build.builder(), builder)
+        self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/9")
+        self.assertEqual(build.revision(), 18)
+        self.assertEqual(build.is_green(), False)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/WebKitTools/Scripts/webkitpy/commands/queries.py b/WebKitTools/Scripts/webkitpy/commands/queries.py
index 3ca4f42..1cc4a96 100644
--- a/WebKitTools/Scripts/webkitpy/commands/queries.py
+++ b/WebKitTools/Scripts/webkitpy/commands/queries.py
@@ -103,6 +103,60 @@ class PatchesToReview(AbstractDeclarativeCommand):
         for patch_id in patch_ids:
             print patch_id
 
+class WhatBroke(AbstractDeclarativeCommand):
+    name = "what-broke"
+    help_text = "Print the status of the %s buildbots" % BuildBot.default_host
+    long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder
+and displayes the status of each builder."""
+
+    def _longest_builder_name(self, builders):
+        return max(map(lambda builder: len(builder["name"]), builders))
+
+    def _find_green_to_red_transition(self, builder_status, look_back_limit=30):
+        # walk backwards until we find a green build
+        builder = self.tool.buildbot.builder_with_name(builder_status["name"])
+        red_build = builder.build(builder_status["build_number"])
+        green_build = None
+        look_back_count = 0
+        while True:
+            if look_back_count >= look_back_limit:
+                break
+            # Use a previous_build() method to avoid assuming build numbers are sequential.
+            before_red_build = red_build.previous_build()
+            if not before_red_build:
+                break
+            if before_red_build.is_green():
+                green_build = before_red_build
+                break
+            red_build = before_red_build
+            look_back_count += 1
+        return (green_build, red_build)
+
+    def _print_builder_line(self, builder_name, max_name_width, status_message):
+        print "%s : %s" % (builder_name.ljust(max_name_width), status_message)
+
+    def _print_status_for_builder(self, builder_status, name_width):
+        # If the builder is green, print OK, exit.
+        if builder_status["is_green"]:
+            self._print_builder_line(builder_status["name"], name_width, "ok")
+            return
+
+        (last_green_build, first_red_build) = self._find_green_to_red_transition(builder_status)
+        if not last_green_build:
+            self._print_builder_line(builder_status["name"], name_width, "FAIL (blame-list: sometime before %s?)" % first_red_build.revision())
+            return
+
+        suspect_revisions = range(first_red_build.revision(), last_green_build.revision(), -1)
+        suspect_revisions.reverse()
+        # FIXME: Parse reviewer and committer from red checkin
+        self._print_builder_line(builder_status["name"], name_width, "FAIL (blame-list: %s)" % suspect_revisions)
+
+    def execute(self, options, args, tool):
+        builders = tool.buildbot.builder_statuses()
+        longest_builder_name = self._longest_builder_name(builders)
+        for builder in builders:
+            self._print_status_for_builder(builder, name_width=longest_builder_name)
+
 
 class TreeStatus(AbstractDeclarativeCommand):
     name = "tree-status"

-- 
WebKit Debian packaging



More information about the Pkg-webkit-commits mailing list