Changeset 56045 in webkit
- Timestamp:
- Mar 16, 2010 12:11:32 AM (14 years ago)
- Location:
- trunk/WebKitTools
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/WebKitTools/ChangeLog
r56044 r56045 1 2010-03-15 Eric Seidel <eric@webkit.org> 2 3 Reviewed by Adam Barth. 4 5 Add "what-broke" command for debugging when the tree broke 6 https://bugs.webkit.org/show_bug.cgi?id=36157 7 8 This is another step towards automated sheriffing of the webkit tree. 9 With this logic our scripts are able to determine what revision broke the 10 tree. Buildbot should do this for us, but unfortunately buildbot doesn't 11 expose this kind of aggregate information. 12 13 * Scripts/webkitpy/buildbot.py: 14 - Add new Builder and Build classes (which will eventually replace the custom dictionaries previously used). 15 - Split out more network logic into _fetch methods which will eventually be their own class for mocking. 16 - Use XMLRPC to communicate with the buildbot master instead of scraping build pages. 17 * Scripts/webkitpy/buildbot_unittest.py: 18 - Test the newly added buildbot classes. 19 * Scripts/webkitpy/commands/queries.py: 20 - Add an experimental what-broke command. 21 1 22 2010-03-15 Daniel Bates <dbates@rim.com> 2 23 -
trunk/WebKitTools/Scripts/webkitpy/buildbot.py
r55055 r56045 30 30 31 31 import re 32 import urllib 32 33 import urllib2 34 import xmlrpclib 33 35 34 36 # Import WebKit-specific modules. … … 40 42 41 43 42 class BuildBot: 44 class Builder(object): 45 def __init__(self, name, buildbot): 46 self._name = unicode(name) 47 self._buildbot = buildbot 48 self._builds_cache = {} 49 50 def name(self): 51 return self._name 52 53 def url(self): 54 return "http://%s/builders/%s" % (self._buildbot.buildbot_host, urllib.quote(self._name)) 55 56 def build(self, build_number): 57 cached_build = self._builds_cache.get(build_number) 58 if cached_build: 59 return cached_build 60 61 build_dictionary = self._buildbot._fetch_xmlrpc_build_dictionary(self._name, build_number) 62 if not build_dictionary: 63 return None 64 build = Build(build_dictionary, self) 65 self._builds_cache[build_number] = build 66 return build 67 68 69 class Build(object): 70 def __init__(self, build_dictionary, builder): 71 self._builder = builder 72 # FIXME: Knowledge of the XMLRPC specifics should probably not go here. 73 self._revision = int(build_dictionary['revision']) 74 self._number = int(build_dictionary['number']) 75 self._is_green = (build_dictionary['results'] == 0) # Undocumented, buildbot XMLRPC 76 77 def url(self): 78 return "%s/builds/%s" % (self.builder().url(), self._number) 79 80 def builder(self): 81 return self._builder 82 83 def revision(self): 84 return self._revision 85 86 def is_green(self): 87 return self._is_green 88 89 def previous_build(self): 90 # previous_build() allows callers to avoid assuming build numbers are sequential. 91 # They may not be sequential across all master changes, or when non-trunk builds are made. 92 return self._builder.build(self._number - 1) 93 94 95 class BuildBot(object): 43 96 44 97 default_host = "build.webkit.org" … … 46 99 def __init__(self, host=default_host): 47 100 self.buildbot_host = host 48 self.buildbot_server_url = "http://%s/" % self.buildbot_host49 101 50 102 # If any Leopard builder/tester, Windows builder or Chromium builder is … … 58 110 ] 59 111 112 # FIXME: This should create and return Buidler and Build objects instead 113 # of a custom dictionary. 60 114 def _parse_builder_status_from_row(self, status_row): 61 115 # If WebKit's buildbot has an XMLRPC interface we could use, we could … … 67 121 name_link = status_cells[0].find('a') 68 122 builder['name'] = name_link.string 69 # We could generate the builder_url from the name in a future version70 # of this code.71 builder['builder_url'] = self.buildbot_server_url + name_link['href']72 123 73 124 status_link = status_cells[1].find('a') … … 87 138 builder['is_green'] = not re.search('fail', 88 139 status_cells[1].renderContents()) 89 # We could parse out the build number instead, but for now just store 90 # the URL. 91 builder['build_url'] = self.buildbot_server_url + status_link['href'] 140 141 status_link_regexp = r"builders/(?P<builder_name>.*)/builds/(?P<build_number>\d+)" 142 link_match = re.match(status_link_regexp, status_link['href']) 143 builder['build_number'] = int(link_match.group("build_number")) 92 144 93 145 # We could parse out the current activity too. 94 95 146 return builder 96 147 … … 121 172 return not self.red_core_builders() 122 173 174 # FIXME: These _fetch methods should move to a networking class. 175 def _fetch_xmlrpc_build_dictionary(self, builder_name, build_number): 176 # The buildbot XMLRPC API is super-limited. 177 # For one, you cannot fetch info on builds which are incomplete. 178 proxy = xmlrpclib.ServerProxy("http://%s/xmlrpc" % self.buildbot_host) 179 try: 180 return proxy.getBuild(builder_name, int(build_number)) 181 except xmlrpclib.Fault, err: 182 log("Error fetching data for build %s" % build_number) 183 return None 184 185 def _fetch_one_box_per_builder(self): 186 build_status_url = "http://%s/one_box_per_builder" % self.buildbot_host 187 return urllib2.urlopen(build_status_url) 188 123 189 def builder_statuses(self): 124 build_status_url = self.buildbot_server_url + 'one_box_per_builder'125 page = urllib2.urlopen(build_status_url)126 soup = BeautifulSoup(page)127 128 190 builders = [] 191 soup = BeautifulSoup(self._fetch_one_box_per_builder()) 129 192 status_table = soup.find('table') 130 193 for status_row in status_table.findAll('tr'): … … 132 195 builders.append(builder) 133 196 return builders 197 198 def builder_with_name(self, name): 199 return Builder(name, self) -
trunk/WebKitTools/Scripts/webkitpy/buildbot_unittest.py
r55055 r56045 33 33 from webkitpy.thirdparty.BeautifulSoup import BeautifulSoup 34 34 35 35 36 class BuildBotTest(unittest.TestCase): 36 37 … … 53 54 _expected_example_one_box_parsings = [ 54 55 { 55 'builder_url': u'http://build.webkit.org/builders/Windows%20Debug%20%28Tests%29',56 'build_url': u'http://build.webkit.org/builders/Windows%20Debug%20%28Tests%29/builds/3693',57 56 'is_green': True, 57 'build_number' : 3693, 58 58 'name': u'Windows Debug (Tests)', 59 59 'built_revision': 47380 60 60 }, 61 61 { 62 'builder_url': u'http://build.webkit.org/builders/SnowLeopard%20Intel%20Release',63 62 'is_green': False, 64 63 'name': u'SnowLeopard Intel Release', 65 64 }, 66 65 { 67 'builder_url': u'http://build.webkit.org/builders/Qt%20Linux%20Release',68 'build_url': u'http://build.webkit.org/builders/Qt%20Linux%20Release/builds/654',69 66 'is_green': False, 67 'build_number' : 654, 70 68 'name': u'Qt Linux Release', 71 69 'built_revision': 47383 … … 152 150 self.assertEquals(builders, expected_builders) 153 151 152 def test_builder_with_name(self): 153 buildbot = BuildBot() 154 155 builder = buildbot.builder_with_name("Test Builder") 156 self.assertEqual(builder.name(), "Test Builder") 157 self.assertEqual(builder.url(), "http://build.webkit.org/builders/Test%20Builder") 158 159 # Override _fetch_xmlrpc_build_dictionary function to not touch the network. 160 def mock_fetch_xmlrpc_build_dictionary(self, build_number): 161 build_dictionary = { 162 "revision" : 2 * build_number, 163 "number" : int(build_number), 164 "results" : build_number % 2, # 0 means pass 165 } 166 return build_dictionary 167 buildbot._fetch_xmlrpc_build_dictionary = mock_fetch_xmlrpc_build_dictionary 168 169 build = builder.build(10) 170 self.assertEqual(build.builder(), builder) 171 self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/10") 172 self.assertEqual(build.revision(), 20) 173 self.assertEqual(build.is_green(), True) 174 175 build = build.previous_build() 176 self.assertEqual(build.builder(), builder) 177 self.assertEqual(build.url(), "http://build.webkit.org/builders/Test%20Builder/builds/9") 178 self.assertEqual(build.revision(), 18) 179 self.assertEqual(build.is_green(), False) 180 181 154 182 if __name__ == '__main__': 155 183 unittest.main() -
trunk/WebKitTools/Scripts/webkitpy/commands/queries.py
r53414 r56045 104 104 print patch_id 105 105 106 class WhatBroke(AbstractDeclarativeCommand): 107 name = "what-broke" 108 help_text = "Print the status of the %s buildbots" % BuildBot.default_host 109 long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder 110 and displayes the status of each builder.""" 111 112 def _longest_builder_name(self, builders): 113 return max(map(lambda builder: len(builder["name"]), builders)) 114 115 def _find_green_to_red_transition(self, builder_status, look_back_limit=30): 116 # walk backwards until we find a green build 117 builder = self.tool.buildbot.builder_with_name(builder_status["name"]) 118 red_build = builder.build(builder_status["build_number"]) 119 green_build = None 120 look_back_count = 0 121 while True: 122 if look_back_count >= look_back_limit: 123 break 124 # Use a previous_build() method to avoid assuming build numbers are sequential. 125 before_red_build = red_build.previous_build() 126 if not before_red_build: 127 break 128 if before_red_build.is_green(): 129 green_build = before_red_build 130 break 131 red_build = before_red_build 132 look_back_count += 1 133 return (green_build, red_build) 134 135 def _print_builder_line(self, builder_name, max_name_width, status_message): 136 print "%s : %s" % (builder_name.ljust(max_name_width), status_message) 137 138 def _print_status_for_builder(self, builder_status, name_width): 139 # If the builder is green, print OK, exit. 140 if builder_status["is_green"]: 141 self._print_builder_line(builder_status["name"], name_width, "ok") 142 return 143 144 (last_green_build, first_red_build) = self._find_green_to_red_transition(builder_status) 145 if not last_green_build: 146 self._print_builder_line(builder_status["name"], name_width, "FAIL (blame-list: sometime before %s?)" % first_red_build.revision()) 147 return 148 149 suspect_revisions = range(first_red_build.revision(), last_green_build.revision(), -1) 150 suspect_revisions.reverse() 151 # FIXME: Parse reviewer and committer from red checkin 152 self._print_builder_line(builder_status["name"], name_width, "FAIL (blame-list: %s)" % suspect_revisions) 153 154 def execute(self, options, args, tool): 155 builders = tool.buildbot.builder_statuses() 156 longest_builder_name = self._longest_builder_name(builders) 157 for builder in builders: 158 self._print_status_for_builder(builder, name_width=longest_builder_name) 159 106 160 107 161 class TreeStatus(AbstractDeclarativeCommand):
Note: See TracChangeset
for help on using the changeset viewer.