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 |