Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/python2.5 | |
| 2 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 """Snapshot Build Bisect Tool | |
| 7 | |
| 8 This script bisects the Mac snapshot archive using binary search. It starts at | |
| 9 a bad revision (it will try to guess HEAD) and asks for a last known-good | |
| 10 revision. It will then binary search across this revision range by downloading, | |
| 11 unzipping, and opening Chromium for you. After testing the specific revision, | |
| 12 it will ask you whether it is good or bad before continuing the search. | |
| 13 | |
| 14 Currently this only works on Mac, but with some effort it could be ported to | |
| 15 other platforms. | |
| 16 """ | |
| 17 | |
| 18 # Base URL to download snapshots from. | |
| 19 BUILD_BASE_URL = \ | |
| 20 "http://build.chromium.org/buildbot/snapshots/chromium-rel-mac" | |
| 21 | |
| 22 # Location of the latest build revision number | |
| 23 BUILD_LATEST_URL = "%s/LATEST" % BUILD_BASE_URL | |
| 24 | |
| 25 # The location of the builds. | |
| 26 BUILD_ARCHIVE_URL = "/%d/" | |
| 27 | |
| 28 # Name of the build archive. | |
| 29 BUILD_ZIP_NAME = "chrome-mac.zip" | |
| 30 | |
| 31 # Directory name inside the archive. | |
| 32 BUILD_DIR_NAME = "chrome-mac" | |
| 33 | |
| 34 # Name of the executable. | |
| 35 BUILD_EXE_NAME = "Chromium.app" | |
| 36 | |
| 37 # URL to the ViewVC commit page. | |
| 38 BUILD_VIEWVC_URL = "http://src.chromium.org/viewvc/chrome?view=rev&revision=%d" | |
| 39 | |
| 40 ############################################################################### | |
| 41 | |
| 42 import math | |
| 43 import os | |
| 44 import re | |
| 45 import shutil | |
| 46 import sys | |
| 47 import urllib | |
| 48 | |
| 49 def ParseDirectoryIndex(url): | |
| 50 """Parses the HTML directory listing into a list of revision numbers.""" | |
| 51 handle = urllib.urlopen(url) | |
| 52 dirindex = handle.read() | |
| 53 handle.close() | |
| 54 return re.findall(r'<a href="([0-9]*)/">\1/</a>', dirindex) | |
| 55 | |
| 56 def GetRevList(good, bad): | |
| 57 """Gets the list of revision numbers between |good| and |bad|.""" | |
| 58 # Download the main revlist. | |
| 59 revlist = ParseDirectoryIndex(BUILD_BASE_URL) | |
| 60 revlist = map(int, revlist) | |
| 61 revlist = filter(lambda r: range(good, bad).__contains__(int(r)), revlist) | |
| 62 revlist.sort() | |
| 63 return revlist | |
| 64 | |
| 65 def TryRevision(rev): | |
| 66 """Downloads revision |rev|, unzips it, and opens it for the user to test.""" | |
| 67 # Clear anything that's currently there. | |
| 68 try: | |
| 69 os.remove(BUILD_ZIP_NAME) | |
| 70 shutil.rmtree(BUILD_DIR_NAME, True) | |
| 71 except Exception, e: | |
| 72 pass | |
| 73 | |
| 74 # Download the file. | |
| 75 download_url = BUILD_BASE_URL + (BUILD_ARCHIVE_URL % rev) + BUILD_ZIP_NAME | |
| 76 try: | |
| 77 urllib.urlretrieve(download_url, BUILD_ZIP_NAME) | |
| 78 except Exception, e: | |
| 79 print("Could not retrieve the download. Sorry.") | |
| 80 print("Tried to get: %s" % download_url) | |
| 81 sys.exit(-1) | |
| 82 | |
| 83 # Unzip the file. | |
| 84 os.system("unzip -q %s" % BUILD_ZIP_NAME) | |
| 85 | |
| 86 # Tell Finder to open the app. | |
| 87 os.system("open %s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME)) | |
| 88 | |
| 89 def AskIsGoodBuild(rev): | |
| 90 """Annoyingly ask the user whether build |rev| is good or bad.""" | |
| 91 while True: | |
| 92 check = raw_input("Build %d [g/b]: " % int(rev))[0] | |
| 93 if (check == "g" or check == "b"): | |
| 94 return (check == "g") | |
| 95 else: | |
| 96 print("Just answer the question...") | |
| 97 | |
| 98 def main(): | |
| 99 print("chrome-bisect: Perform binary search on the snapshot builds") | |
| 100 | |
| 101 # Pick a starting point, try to get HEAD for this. | |
| 102 bad_rev = 0 | |
| 103 try: | |
| 104 nh = urllib.urlopen(BUILD_LATEST_URL) | |
| 105 latest = int(nh.read()) | |
| 106 nh.close() | |
| 107 bad_rev = raw_input("Bad revision [HEAD:%d]: " % latest) | |
| 108 if (bad_rev == ""): | |
| 109 bad_rev = latest | |
| 110 bad_rev = int(bad_rev) | |
| 111 except Exception, e: | |
| 112 print("Could not determine latest revision. This could be bad...") | |
| 113 bad_rev = int(raw_input("Bad revision: ")) | |
| 114 | |
| 115 # Find out when we were good. | |
| 116 good_rev = 0 | |
| 117 try: | |
| 118 good_rev = int(raw_input("Last known good [0]: ")) | |
| 119 except Exception, e: | |
| 120 pass | |
| 121 | |
| 122 # Get a list of revisions to bisect across. | |
| 123 revlist = GetRevList(good_rev, bad_rev) | |
| 124 | |
| 125 # If we don't have a |good_rev|, set it to be the first revision possible. | |
| 126 if good_rev == 0: | |
| 127 good_rev = revlist[0] | |
| 128 | |
| 129 # These are indexes of |revlist|. | |
| 130 good = 0 | |
| 131 bad = len(revlist) - 1 | |
| 132 | |
| 133 # Binary search time! | |
| 134 while good < bad: | |
| 135 candidates = revlist[good:bad] | |
| 136 num_poss = len(candidates) | |
| 137 if num_poss > 10: | |
| 138 print("%d candidates. %d tries left." % | |
| 139 (num_poss, round(math.log(num_poss, 2)))) | |
| 140 else: | |
| 141 print("Candidates: %s" % revlist[good:bad]) | |
| 142 | |
| 143 # Cut the problem in half... | |
| 144 test = int((bad - good) / 2) + good | |
| 145 test_rev = revlist[test] | |
| 146 | |
| 147 # Let the user give this revision a spin. | |
| 148 TryRevision(test_rev) | |
| 149 if AskIsGoodBuild(test_rev): | |
| 150 good = test + 1 | |
| 151 else: | |
| 152 bad = test | |
| 153 | |
| 154 # We're done. Let the user know the results in an official manner. | |
| 155 print("You are probably looking for build %d." % revlist[bad]) | |
|
Evan Martin
2009/09/03 20:18:49
Maybe point out that this build is the earliest *b
| |
| 156 print("This is the ViewVC URL for the potential bustage:") | |
| 157 print(BUILD_VIEWVC_URL % revlist[bad]) | |
| 158 | |
| 159 if __name__ == '__main__': | |
| 160 main() | |
| OLD | NEW |