1dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Copyright (c) 2009, Google Inc. All rights reserved. 2dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 3dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Redistribution and use in source and binary forms, with or without 4dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# modification, are permitted provided that the following conditions are 5dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# met: 6dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 7dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# * Redistributions of source code must retain the above copyright 8dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# notice, this list of conditions and the following disclaimer. 9dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# * Redistributions in binary form must reproduce the above 10dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# copyright notice, this list of conditions and the following disclaimer 11dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# in the documentation and/or other materials provided with the 12dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# distribution. 13dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# * Neither the name of Google Inc. nor the names of its 14dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# contributors may be used to endorse or promote products derived from 15dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# this software without specific prior written permission. 16dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 17dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# 29dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# WebKit's Python module for interacting with WebKit's buildbot 30dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 31f05b935882198ccf7d81675736e3aeb089c5113aBen Murdochtry: 32f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch import json 33f05b935882198ccf7d81675736e3aeb089c5113aBen Murdochexcept ImportError: 34f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch # python 2.5 compatibility 35f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch import webkitpy.thirdparty.simplejson as json 36f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch 37dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockimport operator 38dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockimport re 39dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockimport urllib 40dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockimport urllib2 41dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 42bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsenfrom webkitpy.common.net.failuremap import FailureMap 43a94275402997c11dd2e778633dacf4b7e630a35dBen Murdochfrom webkitpy.common.net.layouttestresults import LayoutTestResults 44bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsenfrom webkitpy.common.net.regressionwindow import RegressionWindow 452bde8e466a4451c7319e3a072d118917957d6554Steve Blockfrom webkitpy.common.net.testoutputset import TestOutputSet 46dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.system.logutils import get_logger 472bde8e466a4451c7319e3a072d118917957d6554Steve Blockfrom webkitpy.common.system.zipfileset import ZipFileSet 48dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.thirdparty.BeautifulSoup import BeautifulSoup 492bde8e466a4451c7319e3a072d118917957d6554Steve Blockfrom webkitpy.thirdparty.autoinstalled.mechanize import Browser 50dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 51dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block_log = get_logger(__file__) 52dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 53dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 54dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass Builder(object): 55dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def __init__(self, name, buildbot): 5621939df44de1705786c545cd1bf519d47250322dBen Murdoch self._name = name 57dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._buildbot = buildbot 58dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._builds_cache = {} 59dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._revision_to_build_number = None 60dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._browser = Browser() 61dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._browser.set_handle_robots(False) # The builder pages are excluded by robots.txt 62dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 63dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def name(self): 64dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._name 65dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 66dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def results_url(self): 67dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return "http://%s/results/%s" % (self._buildbot.buildbot_host, self.url_encoded_name()) 68dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 69dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def url_encoded_name(self): 70dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return urllib.quote(self._name) 71dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 72dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def url(self): 73dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return "http://%s/builders/%s" % (self._buildbot.buildbot_host, self.url_encoded_name()) 74dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 75dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # This provides a single place to mock 76dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _fetch_build(self, build_number): 77f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch build_dictionary = self._buildbot._fetch_build_dictionary(self, build_number) 78dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not build_dictionary: 79dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return None 80dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return Build(self, 81dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_number=int(build_dictionary['number']), 82f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch revision=int(build_dictionary['sourceStamp']['revision']), 83f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch is_green=(build_dictionary['results'] == 0) # Undocumented, 0 seems to mean "pass" 84dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block ) 85dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 86dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def build(self, build_number): 87dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not build_number: 88dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return None 89dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block cached_build = self._builds_cache.get(build_number) 90dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if cached_build: 91dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return cached_build 92dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 93dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build = self._fetch_build(build_number) 94dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._builds_cache[build_number] = build 95dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return build 96dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 972bde8e466a4451c7319e3a072d118917957d6554Steve Block def latest_cached_build(self): 982bde8e466a4451c7319e3a072d118917957d6554Steve Block revision_build_pairs = self.revision_build_pairs_with_results() 992bde8e466a4451c7319e3a072d118917957d6554Steve Block revision_build_pairs.sort(key=lambda i: i[1]) 1002bde8e466a4451c7319e3a072d118917957d6554Steve Block latest_build_number = revision_build_pairs[-1][1] 1012bde8e466a4451c7319e3a072d118917957d6554Steve Block return self.build(latest_build_number) 1022bde8e466a4451c7319e3a072d118917957d6554Steve Block 103dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def force_build(self, username="webkit-patch", comments=None): 104dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def predicate(form): 105dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block try: 106dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return form.find_control("username") 107dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block except Exception, e: 108dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return False 109dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._browser.open(self.url()) 110dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._browser.select_form(predicate=predicate) 111dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._browser["username"] = username 112dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if comments: 113dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._browser["comments"] = comments 114dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._browser.submit() 115dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 116dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block file_name_regexp = re.compile(r"r(?P<revision>\d+) \((?P<build_number>\d+)\)") 117dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _revision_and_build_for_filename(self, filename): 118dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Example: "r47483 (1)/" or "r47483 (1).zip" 119dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block match = self.file_name_regexp.match(filename) 120dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return (int(match.group("revision")), int(match.group("build_number"))) 121dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 122dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _fetch_revision_to_build_map(self): 123dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # All _fetch requests go through _buildbot for easier mocking 124a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch # FIXME: This should use NetworkTransaction's 404 handling instead. 125dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block try: 126dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # FIXME: This method is horribly slow due to the huge network load. 127dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # FIXME: This is a poor way to do revision -> build mapping. 128dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Better would be to ask buildbot through some sort of API. 129dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block print "Loading revision/build list from %s." % self.results_url() 130dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block print "This may take a while..." 131dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block result_files = self._buildbot._fetch_twisted_directory_listing(self.results_url()) 132dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block except urllib2.HTTPError, error: 133dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if error.code != 404: 134dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block raise 135dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block result_files = [] 136dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 137dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # This assumes there was only one build per revision, which is false but we don't care for now. 138dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return dict([self._revision_and_build_for_filename(file_info["filename"]) for file_info in result_files]) 139dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 140dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _revision_to_build_map(self): 141dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not self._revision_to_build_number: 142dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._revision_to_build_number = self._fetch_revision_to_build_map() 143dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._revision_to_build_number 144dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 145dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def revision_build_pairs_with_results(self): 146dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._revision_to_build_map().items() 147dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 148dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # This assumes there can be only one build per revision, which is false, but we don't care for now. 149dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def build_for_revision(self, revision, allow_failed_lookups=False): 150dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # NOTE: This lookup will fail if that exact revision was never built. 151dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_number = self._revision_to_build_map().get(int(revision)) 152dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not build_number: 153dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return None 154dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build = self.build(build_number) 155dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not build and allow_failed_lookups: 156f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch # Builds for old revisions with fail to lookup via buildbot's json api. 157dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build = Build(self, 158dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_number=build_number, 159dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block revision=revision, 160dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block is_green=False, 161dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block ) 162dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return build 163dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 164bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen def find_regression_window(self, red_build, look_back_limit=30): 165dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not red_build or red_build.is_green(): 166bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return RegressionWindow(None, None) 167dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block common_failures = None 168dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block current_build = red_build 169dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_after_current_build = None 170dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block look_back_count = 0 171dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block while current_build: 172dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if current_build.is_green(): 173dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # current_build can't possibly have any failures in common 174dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # with red_build because it's green. 175dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block break 176dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block results = current_build.layout_test_results() 177dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # We treat a lack of results as if all the test failed. 178dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # This occurs, for example, when we can't compile at all. 179dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if results: 180dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block failures = set(results.failing_tests()) 181dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if common_failures == None: 182dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block common_failures = failures 183a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch else: 184a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch common_failures = common_failures.intersection(failures) 185a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch if not common_failures: 186a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch # current_build doesn't have any failures in common with 187a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch # the red build we're worried about. We assume that any 188a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch # failures in current_build were due to flakiness. 189a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch break 190dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block look_back_count += 1 191dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if look_back_count > look_back_limit: 192a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return RegressionWindow(None, current_build, failing_tests=common_failures) 193dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_after_current_build = current_build 194dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block current_build = current_build.previous_build() 195dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # We must iterate at least once because red_build is red. 196dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block assert(build_after_current_build) 197dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Current build must either be green or have no failures in common 198dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # with red build, so we've found our failure transition. 199a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return RegressionWindow(current_build, build_after_current_build, failing_tests=common_failures) 200dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 201bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen def find_blameworthy_regression_window(self, red_build_number, look_back_limit=30, avoid_flakey_tests=True): 202dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block red_build = self.build(red_build_number) 203bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen regression_window = self.find_regression_window(red_build, look_back_limit) 204bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen if not regression_window.build_before_failure(): 205bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return None # We ran off the limit of our search 206dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # If avoid_flakey_tests, require at least 2 bad builds before we 207dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # suspect a real failure transition. 208bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen if avoid_flakey_tests and regression_window.failing_build() == red_build: 209bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return None 210bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return regression_window 211dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 212dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 213dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass Build(object): 214dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def __init__(self, builder, build_number, revision, is_green): 215dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._builder = builder 216dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._number = build_number 217dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._revision = revision 218dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._is_green = is_green 219dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._layout_test_results = None 220dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 221dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block @staticmethod 222dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def build_url(builder, build_number): 223dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return "%s/builds/%s" % (builder.url(), build_number) 224dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 225dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def url(self): 226dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self.build_url(self.builder(), self._number) 227dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 228dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def results_url(self): 229dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block results_directory = "r%s (%s)" % (self.revision(), self._number) 230dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return "%s/%s" % (self._builder.results_url(), urllib.quote(results_directory)) 231dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 2322bde8e466a4451c7319e3a072d118917957d6554Steve Block def results_zip_url(self): 2332bde8e466a4451c7319e3a072d118917957d6554Steve Block return "%s.zip" % self.results_url() 2342bde8e466a4451c7319e3a072d118917957d6554Steve Block 2352bde8e466a4451c7319e3a072d118917957d6554Steve Block def results(self): 2362bde8e466a4451c7319e3a072d118917957d6554Steve Block return TestOutputSet(self._builder.name(), None, ZipFileSet(self.results_zip_url()), include_expected=False) 2372bde8e466a4451c7319e3a072d118917957d6554Steve Block 238a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch def _fetch_results_html(self): 239a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch results_html = "%s/results.html" % (self.results_url()) 240a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch # FIXME: This should use NetworkTransaction's 404 handling instead. 241a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch try: 2426b70adc33054f8aee8c54d0f460458a9df11b8a5Russell Brenner # It seems this can return None if the url redirects and then returns 404. 243a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch return urllib2.urlopen(results_html) 244a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch except urllib2.HTTPError, error: 245a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch if error.code != 404: 246a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch raise 247a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch 248dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def layout_test_results(self): 249dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not self._layout_test_results: 250a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch # FIXME: This should cache that the result was a 404 and stop hitting the network. 251a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch self._layout_test_results = LayoutTestResults.results_from_string(self._fetch_results_html()) 252dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._layout_test_results 253dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 254dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def builder(self): 255dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._builder 256dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 257dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def revision(self): 258dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._revision 259dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 260dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def is_green(self): 261dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._is_green 262dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 263dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def previous_build(self): 264dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # previous_build() allows callers to avoid assuming build numbers are sequential. 265dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # They may not be sequential across all master changes, or when non-trunk builds are made. 266dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._builder.build(self._number - 1) 267dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 268dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 269dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass BuildBot(object): 2702daae5fd11344eaa88a0d92b0f6d65f8d2255c00Ben Murdoch # FIXME: This should move into common.config.urls. 271dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block default_host = "build.webkit.org" 272dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 273dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def __init__(self, host=default_host): 274dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self.buildbot_host = host 275dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._builder_by_name = {} 276dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 277dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # If any core builder is red we should not be landing patches. Other 278dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # builders should be added to this list once they are known to be 279dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # reliable. 280dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # See https://bugs.webkit.org/show_bug.cgi?id=33296 and related bugs. 281dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self.core_builder_names_regexps = [ 282dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block "SnowLeopard.*Build", 2832fc2651226baac27029e38c9d6ef883fa32084dbSteve Block "SnowLeopard.*\(Test", 2842fc2651226baac27029e38c9d6ef883fa32084dbSteve Block "SnowLeopard.*\(WebKit2 Test", 2852bde8e466a4451c7319e3a072d118917957d6554Steve Block "Leopard.*", 286dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block "Windows.*Build", 2872bde8e466a4451c7319e3a072d118917957d6554Steve Block "Windows.*\(Test", 2882bde8e466a4451c7319e3a072d118917957d6554Steve Block "WinCairo", 28981bc750723a18f21cd17d1b173cd2a4dda9cea6eBen Murdoch "WinCE", 2902fc2651226baac27029e38c9d6ef883fa32084dbSteve Block "EFL", 29106ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen "GTK.*32", 29206ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen "GTK.*64.*Debug", # Disallow the 64-bit Release bot which is broken. 293dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block "Qt", 29406ea8e899e48f1f2f396b70e63fae369f2f23232Kristian Monsen "Chromium.*Release$", 295dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block ] 296dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 297dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _parse_last_build_cell(self, builder, cell): 298dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block status_link = cell.find('a') 299dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if status_link: 300dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Will be either a revision number or a build number 301dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block revision_string = status_link.string 302dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # If revision_string has non-digits assume it's not a revision number. 303dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder['built_revision'] = int(revision_string) \ 304dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not re.match('\D', revision_string) \ 305dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block else None 306545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch 307545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch # FIXME: We treat slave lost as green even though it is not to 308545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch # work around the Qts bot being on a broken internet connection. 309545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch # The real fix is https://bugs.webkit.org/show_bug.cgi?id=37099 310545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch builder['is_green'] = not re.search('fail', cell.renderContents()) or \ 311545e470e52f0ac6a3a072bf559c796b42c6066b6Ben Murdoch not not re.search('lost', cell.renderContents()) 312dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 313dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block status_link_regexp = r"builders/(?P<builder_name>.*)/builds/(?P<build_number>\d+)" 314dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block link_match = re.match(status_link_regexp, status_link['href']) 315dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder['build_number'] = int(link_match.group("build_number")) 316dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block else: 317dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # We failed to find a link in the first cell, just give up. This 318dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # can happen if a builder is just-added, the first cell will just 319dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # be "no build" 320dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Other parts of the code depend on is_green being present. 321dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder['is_green'] = False 322dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder['built_revision'] = None 323dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder['build_number'] = None 324dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 325dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _parse_current_build_cell(self, builder, cell): 326dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block activity_lines = cell.renderContents().split("<br />") 327dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder["activity"] = activity_lines[0] # normally "building" or "idle" 328dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # The middle lines document how long left for any current builds. 329dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block match = re.match("(?P<pending_builds>\d) pending", activity_lines[-1]) 330dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder["pending_builds"] = int(match.group("pending_builds")) if match else 0 331dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 332dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _parse_builder_status_from_row(self, status_row): 333dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block status_cells = status_row.findAll('td') 334dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder = {} 335dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 336dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # First cell is the name 337dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block name_link = status_cells[0].find('a') 33821939df44de1705786c545cd1bf519d47250322dBen Murdoch builder["name"] = unicode(name_link.string) 339dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 340dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._parse_last_build_cell(builder, status_cells[1]) 341dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._parse_current_build_cell(builder, status_cells[2]) 342dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return builder 343dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 344dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _matches_regexps(self, builder_name, name_regexps): 345dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block for name_regexp in name_regexps: 346dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if re.match(name_regexp, builder_name): 347dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return True 348dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return False 349dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 350dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # FIXME: Should move onto Builder 351dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _is_core_builder(self, builder_name): 352dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._matches_regexps(builder_name, self.core_builder_names_regexps) 353dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 354dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # FIXME: This method needs to die, but is used by a unit test at the moment. 355dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _builder_statuses_with_names_matching_regexps(self, builder_statuses, name_regexps): 356dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [builder for builder in builder_statuses if self._matches_regexps(builder["name"], name_regexps)] 357dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 358dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def red_core_builders(self): 359dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [builder for builder in self.core_builder_statuses() if not builder["is_green"]] 360dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 361dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def red_core_builders_names(self): 362dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [builder["name"] for builder in self.red_core_builders()] 363dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 364dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def idle_red_core_builders(self): 365dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [builder for builder in self.red_core_builders() if builder["activity"] == "idle"] 366dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 367dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def core_builders_are_green(self): 368dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return not self.red_core_builders() 369dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 370dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # FIXME: These _fetch methods should move to a networking class. 371f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch def _fetch_build_dictionary(self, builder, build_number): 372dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block try: 373f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch base = "http://%s" % self.buildbot_host 374f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch path = urllib.quote("json/builders/%s/builds/%s" % (builder.name(), 375f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch build_number)) 376f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch url = "%s/%s" % (base, path) 377f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch jsondata = urllib2.urlopen(url) 378f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch return json.load(jsondata) 379f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch except urllib2.URLError, err: 380dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_url = Build.build_url(builder, build_number) 381dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block _log.error("Error fetching data for %s build %s (%s): %s" % (builder.name(), build_number, build_url, err)) 382dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return None 383f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch except ValueError, err: 384f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch build_url = Build.build_url(builder, build_number) 385f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch _log.error("Error decoding json data from %s: %s" % (build_url, err)) 386f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch return None 387dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 388dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _fetch_one_box_per_builder(self): 389dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build_status_url = "http://%s/one_box_per_builder" % self.buildbot_host 390dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return urllib2.urlopen(build_status_url) 391dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 392bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen def _file_cell_text(self, file_cell): 393bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen """Traverses down through firstChild elements until one containing a string is found, then returns that string""" 394bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen element = file_cell 395bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen while element.string is None and element.contents: 396bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen element = element.contents[0] 397bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return element.string 398bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen 399dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _parse_twisted_file_row(self, file_row): 400bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen string_or_empty = lambda string: unicode(string) if string else u"" 401dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block file_cells = file_row.findAll('td') 402dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return { 403bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen "filename": string_or_empty(self._file_cell_text(file_cells[0])), 404bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen "size": string_or_empty(self._file_cell_text(file_cells[1])), 405bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen "type": string_or_empty(self._file_cell_text(file_cells[2])), 406bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen "encoding": string_or_empty(self._file_cell_text(file_cells[3])), 407dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block } 408dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 409dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _parse_twisted_directory_listing(self, page): 410dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block soup = BeautifulSoup(page) 411dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # HACK: Match only table rows with a class to ignore twisted header/footer rows. 412bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen file_rows = soup.find('table').findAll('tr', {'class': re.compile(r'\b(?:directory|file)\b')}) 413dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [self._parse_twisted_file_row(file_row) for file_row in file_rows] 414dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 415dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # FIXME: There should be a better way to get this information directly from twisted. 416dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _fetch_twisted_directory_listing(self, url): 417dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return self._parse_twisted_directory_listing(urllib2.urlopen(url)) 418dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 419dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def builders(self): 420dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [self.builder_with_name(status["name"]) for status in self.builder_statuses()] 421dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 422dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # This method pulls from /one_box_per_builder as an efficient way to get information about 423dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def builder_statuses(self): 424dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block soup = BeautifulSoup(self._fetch_one_box_per_builder()) 425dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [self._parse_builder_status_from_row(status_row) for status_row in soup.find('table').findAll('tr')] 426dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 427dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def core_builder_statuses(self): 428dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [builder for builder in self.builder_statuses() if self._is_core_builder(builder["name"])] 429dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 430dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def builder_with_name(self, name): 431dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder = self._builder_by_name.get(name) 432dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not builder: 433dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder = Builder(name, self) 434dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block self._builder_by_name[name] = builder 435dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return builder 436dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 437bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen def failure_map(self, only_core_builders=True): 438dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder_statuses = self.core_builder_statuses() if only_core_builders else self.builder_statuses() 439bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen failure_map = FailureMap() 440dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block revision_to_failing_bots = {} 441dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block for builder_status in builder_statuses: 442dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if builder_status["is_green"]: 443dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block continue 444dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder = self.builder_with_name(builder_status["name"]) 445bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen regression_window = builder.find_blameworthy_regression_window(builder_status["build_number"]) 446a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch if regression_window: 447a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch failure_map.add_regression_window(builder, regression_window) 448bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen return failure_map 449dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 450dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # This makes fewer requests than calling Builder.latest_build would. It grabs all builder 451dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # statuses in one request using self.builder_statuses (fetching /one_box_per_builder instead of builder pages). 452dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _latest_builds_from_builders(self, only_core_builders=True): 453dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builder_statuses = self.core_builder_statuses() if only_core_builders else self.builder_statuses() 454dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return [self.builder_with_name(status["name"]).build(status["build_number"]) for status in builder_statuses] 455dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 456dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def _build_at_or_before_revision(self, build, revision): 457dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block while build: 458dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if build.revision() <= revision: 459dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return build 460dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block build = build.previous_build() 461dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block 462dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block def last_green_revision(self, only_core_builders=True): 463dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builds = self._latest_builds_from_builders(only_core_builders) 464dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block target_revision = builds[0].revision() 465dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # An alternate way to do this would be to start at one revision and walk backwards 466dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # checking builder.build_for_revision, however build_for_revision is very slow on first load. 467dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block while True: 468dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Make builds agree on revision 469dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block builds = [self._build_at_or_before_revision(build, target_revision) for build in builds] 470dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if None in builds: # One of the builds failed to load from the server. 471dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return None 472dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block min_revision = min(map(lambda build: build.revision(), builds)) 473dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if min_revision != target_revision: 474dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block target_revision = min_revision 475dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block continue # Builds don't all agree on revision, keep searching 476dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block # Check to make sure they're all green 477dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block all_are_green = reduce(operator.and_, map(lambda build: build.is_green(), builds)) 478dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block if not all_are_green: 479dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block target_revision -= 1 480dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block continue 481dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block return min_revision 482