Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(148)

Side by Side Diff: bisect-builds.py

Issue 10408045: Modified the bisect script to work for official Chrome builds. (Closed) Base URL: http://src.chromium.org/svn/trunk/src/tools/
Patch Set: Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Snapshot Build Bisect Tool 6 """Snapshot Build Bisect Tool
7 7
8 This script bisects a snapshot archive using binary search. It starts at 8 This script bisects a 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 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, 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, 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. 12 it will ask you whether it is good or bad before continuing the search.
13 """ 13 """
14 14
15 # The root URL for storage. 15 # The root URL for storage.
16 BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-snapshots' 16 BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-snapshots'
17 17
18 # The root URL for official builds.
19 OFFICIAL_BASE_URL = 'http://chrome4linux.mtv.corp.google.com/archives'
20
18 # URL to the ViewVC commit page. 21 # URL to the ViewVC commit page.
19 BUILD_VIEWVC_URL = 'http://src.chromium.org/viewvc/chrome?view=rev&revision=%d' 22 BUILD_VIEWVC_URL = 'http://src.chromium.org/viewvc/chrome?view=rev&revision=%d'
20 23
21 # Changelogs URL. 24 # Changelogs URL.
22 CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \ 25 CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \
23 'perf/dashboard/ui/changelog.html?url=/trunk/src&range=%d:%d' 26 'perf/dashboard/ui/changelog.html?url=/trunk/src&range=%d:%d'
Robert Sesek 2012/05/23 21:24:01 nit: blank line after
anantha 2012/05/24 18:32:11 Done.
27 # Official Changelogs URL.
28 OFFICIAL_CHANGELOG_URL = 'http://omahaproxy.appspot.com/'\
29 'changelog?old_version=%s&new_version=%s'
24 30
25 # DEPS file URL. 31 # DEPS file URL.
26 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' 32 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d'
27
28 # WebKit Changelogs URL. 33 # WebKit Changelogs URL.
29 WEBKIT_CHANGELOG_URL = 'http://trac.webkit.org/log/' \ 34 WEBKIT_CHANGELOG_URL = 'http://trac.webkit.org/log/' \
30 'trunk/?rev=%d&stop_rev=%d&verbose=on' 35 'trunk/?rev=%d&stop_rev=%d&verbose=on'
31 36
32 ############################################################################### 37 ###############################################################################
33 38
34 import math 39 import math
35 import optparse 40 import optparse
36 import os 41 import os
37 import pipes 42 import pipes
38 import re 43 import re
39 import shutil 44 import shutil
40 import subprocess 45 import subprocess
41 import sys 46 import sys
42 import tempfile 47 import tempfile
43 import threading 48 import threading
44 import urllib 49 import urllib
50 from distutils.version import LooseVersion
45 from xml.etree import ElementTree 51 from xml.etree import ElementTree
46 import zipfile 52 import zipfile
47 53
48 54
49 class PathContext(object): 55 class PathContext(object):
50 """A PathContext is used to carry the information used to construct URLs and 56 """A PathContext is used to carry the information used to construct URLs and
51 paths when dealing with the storage server and archives.""" 57 paths when dealing with the storage server and archives."""
52 def __init__(self, platform, good_revision, bad_revision): 58 def __init__(self, platform, good_revision, bad_revision, is_official):
53 super(PathContext, self).__init__() 59 super(PathContext, self).__init__()
54 # Store off the input parameters. 60 # Store off the input parameters.
55 self.platform = platform # What's passed in to the '-a/--archive' option. 61 self.platform = platform # What's passed in to the '-a/--archive' option.
56 self.good_revision = good_revision 62 self.good_revision = good_revision
57 self.bad_revision = bad_revision 63 self.bad_revision = bad_revision
64 self.is_official = is_official
58 65
59 # The name of the ZIP file in a revision directory on the server. 66 # The name of the ZIP file in a revision directory on the server.
60 self.archive_name = None 67 self.archive_name = None
61 68
62 # Set some internal members: 69 # Set some internal members:
63 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. 70 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'.
64 # _archive_extract_dir = Uncompressed directory in the archive_name file. 71 # _archive_extract_dir = Uncompressed directory in the archive_name file.
65 # _binary_name = The name of the executable to run. 72 # _binary_name = The name of the executable to run.
66 if self.platform == 'linux' or self.platform == 'linux64': 73 if self.platform == 'linux' or self.platform == 'linux64':
67 self._listing_platform_dir = 'Linux/'
68 self.archive_name = 'chrome-linux.zip'
69 self._archive_extract_dir = 'chrome-linux'
70 self._binary_name = 'chrome' 74 self._binary_name = 'chrome'
71 # Linux and x64 share all the same path data except for the archive dir.
72 if self.platform == 'linux64':
73 self._listing_platform_dir = 'Linux_x64/'
74 elif self.platform == 'mac': 75 elif self.platform == 'mac':
75 self._listing_platform_dir = 'Mac/'
76 self.archive_name = 'chrome-mac.zip' 76 self.archive_name = 'chrome-mac.zip'
77 self._archive_extract_dir = 'chrome-mac' 77 self._archive_extract_dir = 'chrome-mac'
78 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
79 elif self.platform == 'win': 78 elif self.platform == 'win':
80 self._listing_platform_dir = 'Win/'
81 self.archive_name = 'chrome-win32.zip' 79 self.archive_name = 'chrome-win32.zip'
82 self._archive_extract_dir = 'chrome-win32' 80 self._archive_extract_dir = 'chrome-win32'
83 self._binary_name = 'chrome.exe' 81 self._binary_name = 'chrome.exe'
84 else: 82 else:
85 raise Exception('Invalid platform: %s' % self.platform) 83 raise Exception('Invalid platform: %s' % self.platform)
86 84
85 if is_official:
86 if self.platform == 'linux':
87 self._listing_platform_dir = 'lucid32bit/'
88 self.archive_name = 'chrome-lucid32bit.zip'
89 self._archive_extract_dir = 'chrome-lucid32bit'
90 elif self.platform == 'linux64':
91 self._listing_platform_dir = 'lucid64bit/'
92 self.archive_name = 'chrome-lucid64bit.zip'
93 self._archive_extract_dir = 'chrome-lucid64bit'
94 elif self.platform == 'mac':
95 self._listing_platform_dir = 'mac/'
96 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome'
97 elif self.platform == 'win':
98 self._listing_platform_dir = 'win/'
99 else:
100 if self.platform == 'linux' or self.platform == 'linux64':
101 self.archive_name = 'chrome-linux.zip'
102 self._archive_extract_dir = 'chrome-linux'
103 if self.platform == 'linux':
104 self._listing_platform_dir = 'Linux/'
105 elif self.platform == 'linux64':
106 self._listing_platform_dir = 'Linux_x64/'
107 elif self.platform == 'mac':
108 self._listing_platform_dir = 'Mac/'
109 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
110 elif self.platform == 'win':
111 self._listing_platform_dir = 'Win/'
112
87 def GetListingURL(self, marker=None): 113 def GetListingURL(self, marker=None):
88 """Returns the URL for a directory listing, with an optional marker.""" 114 """Returns the URL for a directory listing, with an optional marker."""
89 marker_param = '' 115 marker_param = ''
90 if marker: 116 if marker:
91 marker_param = '&marker=' + str(marker) 117 marker_param = '&marker=' + str(marker)
92 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \ 118 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \
93 marker_param 119 marker_param
94 120
95 def GetDownloadURL(self, revision): 121 def GetDownloadURL(self, revision):
96 """Gets the download URL for a build archive of a specific revision.""" 122 """Gets the download URL for a build archive of a specific revision."""
97 return "%s/%s%d/%s" % ( 123 if self.is_official:
98 BASE_URL, self._listing_platform_dir, revision, self.archive_name) 124 return "%s/%s/%s%s" % (
125 OFFICIAL_BASE_URL, revision, self._listing_platform_dir,
126 self.archive_name)
127 else:
128 return "%s/%s%s/%s" % (
129 BASE_URL, self._listing_platform_dir, revision, self.archive_name)
99 130
100 def GetLastChangeURL(self): 131 def GetLastChangeURL(self):
101 """Returns a URL to the LAST_CHANGE file.""" 132 """Returns a URL to the LAST_CHANGE file."""
102 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE' 133 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE'
103 134
104 def GetLaunchPath(self): 135 def GetLaunchPath(self):
105 """Returns a relative path (presumably from the archive extraction location) 136 """Returns a relative path (presumably from the archive extraction location)
106 that is used to run the executable.""" 137 that is used to run the executable."""
107 return os.path.join(self._archive_extract_dir, self._binary_name) 138 return os.path.join(self._archive_extract_dir, self._binary_name)
108 139
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 # trailing slash to just have a number. 174 # trailing slash to just have a number.
144 revisions = [] 175 revisions = []
145 for prefix in all_prefixes: 176 for prefix in all_prefixes:
146 revnum = prefix.text[prefix_len:-1] 177 revnum = prefix.text[prefix_len:-1]
147 try: 178 try:
148 revnum = int(revnum) 179 revnum = int(revnum)
149 revisions.append(revnum) 180 revisions.append(revnum)
150 except ValueError: 181 except ValueError:
151 pass 182 pass
152 return (revisions, next_marker) 183 return (revisions, next_marker)
153
154 # Fetch the first list of revisions. 184 # Fetch the first list of revisions.
155 (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) 185 (revisions, next_marker) = _FetchAndParse(self.GetListingURL())
156 186
157 # If the result list was truncated, refetch with the next marker. Do this 187 # If the result list was truncated, refetch with the next marker. Do this
158 # until an entire directory listing is done. 188 # until an entire directory listing is done.
159 while next_marker: 189 while next_marker:
160 next_url = self.GetListingURL(next_marker) 190 next_url = self.GetListingURL(next_marker)
161 (new_revisions, next_marker) = _FetchAndParse(next_url) 191 (new_revisions, next_marker) = _FetchAndParse(next_url)
162 revisions.extend(new_revisions) 192 revisions.extend(new_revisions)
163
164 return revisions 193 return revisions
165 194
166 def GetRevList(self): 195 def GetRevList(self):
167 """Gets the list of revision numbers between self.good_revision and 196 """Gets the list of revision numbers between self.good_revision and
168 self.bad_revision.""" 197 self.bad_revision."""
169 # Download the revlist and filter for just the range between good and bad. 198 # Download the revlist and filter for just the range between good and bad.
170 minrev = self.good_revision 199 minrev = self.good_revision
171 maxrev = self.bad_revision 200 maxrev = self.bad_revision
172 revlist = map(int, self.ParseDirectoryIndex()) 201 revlist = map(int, self.ParseDirectoryIndex())
173 revlist = [x for x in revlist if x >= minrev and x <= maxrev] 202 revlist = [x for x in revlist if x >= int(minrev) and x <= int(maxrev)]
174 revlist.sort() 203 revlist.sort()
175 return revlist 204 return revlist
176 205
206 def GetOfficialBuildsList(self):
207 """Gets the list of official build numbers between self.good_revision and
208 self.bad_revision."""
209 # Download the revlist and filter for just the range between good and bad.
210 minrev = self.good_revision
211 maxrev = self.bad_revision
212 handle = urllib.urlopen(OFFICIAL_BASE_URL)
213 dirindex = handle.read()
214 handle.close()
215 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex)
216 final_list = []
217 start_index = '0'
218 end_index = '0'
219 i = 0
220
221 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
222 for build_number in sorted(parsed_build_numbers):
223 path = OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + \
224 self._listing_platform_dir + self.archive_name
225 i = i+1
Robert Sesek 2012/05/23 21:24:01 nit: spaces around operators
anantha 2012/05/24 18:32:11 Done.
226 try:
227 connection = urllib.urlopen(path)
Robert Sesek 2012/05/23 21:24:01 What's this for? To test that the file exists?
anantha 2012/05/24 18:32:11 Correct. On 2012/05/23 21:24:01, rsesek wrote:
228 connection.close()
229 final_list.append(str(build_number))
230 if str(build_number) == minrev:
231 start_index = i
232 if str(build_number) == maxrev:
233 end_index = i
234 except urllib.HTTPError, e:
235 print e.getcode()
Robert Sesek 2012/05/23 21:24:01 This won't be a helpful error message.
anantha 2012/05/24 18:32:11 changed it to pass. On 2012/05/23 21:24:01, rsesek
236 return final_list[start_index:end_index]
177 237
178 def UnzipFilenameToDir(filename, dir): 238 def UnzipFilenameToDir(filename, dir):
179 """Unzip |filename| to directory |dir|.""" 239 """Unzip |filename| to directory |dir|."""
180 cwd = os.getcwd() 240 cwd = os.getcwd()
181 if not os.path.isabs(filename): 241 if not os.path.isabs(filename):
182 filename = os.path.join(cwd, filename) 242 filename = os.path.join(cwd, filename)
183 zf = zipfile.ZipFile(filename) 243 zf = zipfile.ZipFile(filename)
184 # Make base. 244 # Make base.
185 try: 245 try:
186 if not os.path.isdir(dir): 246 if not os.path.isdir(dir):
(...skipping 26 matching lines...) Expand all
213 @param rev The Chromium revision number/tag to download. 273 @param rev The Chromium revision number/tag to download.
214 @param filename The destination for the downloaded file. 274 @param filename The destination for the downloaded file.
215 @param quit_event A threading.Event which will be set by the master thread to 275 @param quit_event A threading.Event which will be set by the master thread to
216 indicate that the download should be aborted. 276 indicate that the download should be aborted.
217 @param progress_event A threading.Event which will be set by the master thread 277 @param progress_event A threading.Event which will be set by the master thread
218 to indicate that the progress of the download should be 278 to indicate that the progress of the download should be
219 displayed. 279 displayed.
220 """ 280 """
221 def ReportHook(blocknum, blocksize, totalsize): 281 def ReportHook(blocknum, blocksize, totalsize):
222 if quit_event and quit_event.isSet(): 282 if quit_event and quit_event.isSet():
223 raise RuntimeError("Aborting download of revision %d" % rev) 283 raise RuntimeError("Aborting download of revision %s" % str(rev))
224 if progress_event and progress_event.isSet(): 284 if progress_event and progress_event.isSet():
225 size = blocknum * blocksize 285 size = blocknum * blocksize
226 if totalsize == -1: # Total size not known. 286 if totalsize == -1: # Total size not known.
227 progress = "Received %d bytes" % size 287 progress = "Received %d bytes" % size
228 else: 288 else:
229 size = min(totalsize, size) 289 size = min(totalsize, size)
230 progress = "Received %d of %d bytes, %.2f%%" % ( 290 progress = "Received %d of %d bytes, %.2f%%" % (
231 size, totalsize, 100.0 * size / totalsize) 291 size, totalsize, 100.0 * size / totalsize)
232 # Send a \r to let all progress messages use just one line of output. 292 # Send a \r to let all progress messages use just one line of output.
233 sys.stdout.write("\r" + progress) 293 sys.stdout.write("\r" + progress)
234 sys.stdout.flush() 294 sys.stdout.flush()
235 295
236 download_url = context.GetDownloadURL(rev) 296 download_url = context.GetDownloadURL(rev)
237 try: 297 try:
238 urllib.urlretrieve(download_url, filename, ReportHook) 298 urllib.urlretrieve(download_url, filename, ReportHook)
239 if progress_event and progress_event.isSet(): 299 if progress_event and progress_event.isSet():
240 print 300 print
241 except RuntimeError, e: 301 except RuntimeError, e:
242 pass 302 pass
243 303
244 304
245 def RunRevision(context, revision, zipfile, profile, num_runs, args): 305 def RunRevision(context, revision, zipfile, profile, num_runs, args):
246 """Given a zipped revision, unzip it and run the test.""" 306 """Given a zipped revision, unzip it and run the test."""
247 print "Trying revision %d..." % revision 307 print "Trying revision %s..." % str(revision)
248 308
249 # Create a temp directory and unzip the revision into it. 309 # Create a temp directory and unzip the revision into it.
250 cwd = os.getcwd() 310 cwd = os.getcwd()
251 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') 311 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
252 UnzipFilenameToDir(zipfile, tempdir) 312 UnzipFilenameToDir(zipfile, tempdir)
253 os.chdir(tempdir) 313 os.chdir(tempdir)
254 314
255 # Run the build as many times as specified. 315 if context.platform == 'linux' or context.platform == 'linux64':
Robert Sesek 2012/05/23 21:24:01 This can be simplified to just: testargs = [conte
anantha 2012/05/24 18:32:11 Done.
256 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args 316 if context.is_official:
317 # Run the build as many times as specified.
318 testargs = [context.GetLaunchPath(), '--no-sandbox',
319 '--user-data-dir=%s' % profile] + args
320 else:
321 # Run the build as many times as specified.
322 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + \
323 args
324 else:
325 # Run the build as many times as specified.
326 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args
327
257 for i in range(0, num_runs): 328 for i in range(0, num_runs):
258 subproc = subprocess.Popen(testargs, 329 subproc = subprocess.Popen(testargs,
259 bufsize=-1, 330 bufsize=-1,
260 stdout=subprocess.PIPE, 331 stdout=subprocess.PIPE,
261 stderr=subprocess.PIPE) 332 stderr=subprocess.PIPE)
262 (stdout, stderr) = subproc.communicate() 333 (stdout, stderr) = subproc.communicate()
263 334
264 os.chdir(cwd) 335 os.chdir(cwd)
265 try: 336 try:
266 shutil.rmtree(tempdir, True) 337 shutil.rmtree(tempdir, True)
267 except Exception, e: 338 except Exception, e:
268 pass 339 pass
269 340
270 return (subproc.returncode, stdout, stderr) 341 return (subproc.returncode, stdout, stderr)
271 342
272 343
273 def AskIsGoodBuild(rev, status, stdout, stderr): 344 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
274 """Ask the user whether build |rev| is good or bad.""" 345 """Ask the user whether build |rev| is good or bad."""
275 # Loop until we get a response that we can parse. 346 # Loop until we get a response that we can parse.
276 while True: 347 while True:
277 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) 348 response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev))
278 if response and response in ('g', 'b'): 349 if response and response in ('g', 'b'):
279 return response == 'g' 350 return response == 'g'
280 if response and response == 'q': 351 if response and response == 'q':
281 raise SystemExit() 352 raise SystemExit()
282 353
283 354
284 def Bisect(platform, 355 def Bisect(platform,
356 official_builds,
285 good_rev=0, 357 good_rev=0,
286 bad_rev=0, 358 bad_rev=0,
287 num_runs=1, 359 num_runs=1,
288 try_args=(), 360 try_args=(),
289 profile=None, 361 profile=None,
290 predicate=AskIsGoodBuild): 362 predicate=AskIsGoodBuild):
291 """Given known good and known bad revisions, run a binary search on all 363 """Given known good and known bad revisions, run a binary search on all
292 archived revisions to determine the last known good revision. 364 archived revisions to determine the last known good revision.
293 365
294 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 366 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
295 @param good_rev Number/tag of the last known good revision. 367 @param good_rev Number/tag of the last known good revision.
Robert Sesek 2012/05/23 21:24:01 Need a @param offiical_builds ... before this
anantha 2012/05/24 18:32:11 Done.
296 @param bad_rev Number/tag of the first known bad revision. 368 @param bad_rev Number/tag of the first known bad revision.
297 @param num_runs Number of times to run each build for asking good/bad. 369 @param num_runs Number of times to run each build for asking good/bad.
298 @param try_args A tuple of arguments to pass to the test application. 370 @param try_args A tuple of arguments to pass to the test application.
299 @param profile The name of the user profile to run with. 371 @param profile The name of the user profile to run with.
300 @param predicate A predicate function which returns True iff the argument 372 @param predicate A predicate function which returns True iff the argument
301 chromium revision is good. 373 chromium revision is good.
302 374
303 Threading is used to fetch Chromium revisions in the background, speeding up 375 Threading is used to fetch Chromium revisions in the background, speeding up
304 the user's experience. For example, suppose the bounds of the search are 376 the user's experience. For example, suppose the bounds of the search are
305 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on 377 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
306 whether revision 50 is good or bad, the next revision to check will be either 378 whether revision 50 is good or bad, the next revision to check will be either
307 25 or 75. So, while revision 50 is being checked, the script will download 379 25 or 75. So, while revision 50 is being checked, the script will download
308 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is 380 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
309 known: 381 known:
310 382
311 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 383 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
312 is run on rev 75. 384 is run on rev 75.
313 385
314 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test 386 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
315 is run on rev 25. 387 is run on rev 25.
316 """ 388 """
317 389
318 if not profile: 390 if not profile:
319 profile = 'profile' 391 profile = 'profile'
320 392
321 context = PathContext(platform, good_rev, bad_rev) 393 context = PathContext(platform, good_rev, bad_rev, official_builds)
322 cwd = os.getcwd() 394 cwd = os.getcwd()
323 395
324 _GetDownloadPath = lambda rev: os.path.join(cwd, 396
325 '%d-%s' % (rev, context.archive_name))
326 397
327 print "Downloading list of known revisions..." 398 print "Downloading list of known revisions..."
328 399 _GetDownloadPath = lambda rev: os.path.join(cwd,
329 revlist = context.GetRevList() 400 '%s-%s' % (str(rev), context.archive_name))
Robert Sesek 2012/05/23 21:24:01 nit: continuations are indented 4
anantha 2012/05/24 18:32:11 Done.
401 if official_builds:
402 revlist = context.GetOfficialBuildsList()
403 else:
404 revlist = context.GetRevList()
330 405
331 # Get a list of revisions to bisect across. 406 # Get a list of revisions to bisect across.
332 if len(revlist) < 2: # Don't have enough builds to bisect. 407 if len(revlist) < 2: # Don't have enough builds to bisect.
333 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 408 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
334 raise RuntimeError(msg) 409 raise RuntimeError(msg)
335 410
336 # Figure out our bookends and first pivot point; fetch the pivot revision. 411 # Figure out our bookends and first pivot point; fetch the pivot revision.
337 good = 0 412 good = 0
338 bad = len(revlist) - 1 413 bad = len(revlist) - 1
339 pivot = bad / 2 414 pivot = bad / 2
340 rev = revlist[pivot] 415 rev = revlist[pivot]
341 zipfile = _GetDownloadPath(rev) 416 zipfile = _GetDownloadPath(rev)
342 progress_event = threading.Event() 417 progress_event = threading.Event()
343 progress_event.set() 418 progress_event.set()
344 print "Downloading revision %d..." % rev 419 print "Downloading revision %s..." % str(rev)
345 FetchRevision(context, rev, zipfile, 420 FetchRevision(context, rev, zipfile,
346 quit_event=None, progress_event=progress_event) 421 quit_event=None, progress_event=progress_event)
347 422
348 # Binary search time! 423 # Binary search time!
349 while zipfile and bad - good > 1: 424 while zipfile and bad - good > 1:
350 # Pre-fetch next two possible pivots 425 # Pre-fetch next two possible pivots
351 # - down_pivot is the next revision to check if the current revision turns 426 # - down_pivot is the next revision to check if the current revision turns
352 # out to be bad. 427 # out to be bad.
353 # - up_pivot is the next revision to check if the current revision turns 428 # - up_pivot is the next revision to check if the current revision turns
354 # out to be good. 429 # out to be good.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 profile, 468 profile,
394 num_runs, 469 num_runs,
395 try_args) 470 try_args)
396 os.unlink(zipfile) 471 os.unlink(zipfile)
397 zipfile = None 472 zipfile = None
398 473
399 # Call the predicate function to see if the current revision is good or bad. 474 # Call the predicate function to see if the current revision is good or bad.
400 # On that basis, kill one of the background downloads and complete the 475 # On that basis, kill one of the background downloads and complete the
401 # other, as described in the comments above. 476 # other, as described in the comments above.
402 try: 477 try:
403 if predicate(rev, status, stdout, stderr): 478 if predicate(rev, official_builds, status, stdout, stderr):
404 good = pivot 479 good = pivot
405 if down_thread: 480 if down_thread:
406 down_quit_event.set() # Kill the download of older revision. 481 down_quit_event.set() # Kill the download of older revision.
407 down_thread.join() 482 down_thread.join()
408 os.unlink(down_zipfile) 483 os.unlink(down_zipfile)
409 if up_thread: 484 if up_thread:
410 print "Downloading revision %d..." % up_rev 485 print "Downloading revision %s..." % str(up_rev)
411 up_progress_event.set() # Display progress of download. 486 up_progress_event.set() # Display progress of download.
412 up_thread.join() # Wait for newer revision to finish downloading. 487 up_thread.join() # Wait for newer revision to finish downloading.
413 pivot = up_pivot 488 pivot = up_pivot
414 zipfile = up_zipfile 489 zipfile = up_zipfile
415 else: 490 else:
416 bad = pivot 491 bad = pivot
417 if up_thread: 492 if up_thread:
418 up_quit_event.set() # Kill download of newer revision. 493 up_quit_event.set() # Kill download of newer revision.
419 up_thread.join() 494 up_thread.join()
420 os.unlink(up_zipfile) 495 os.unlink(up_zipfile)
421 if down_thread: 496 if down_thread:
422 print "Downloading revision %d..." % down_rev 497 print "Downloading revision %s..." % str(down_rev)
423 down_progress_event.set() # Display progress of download. 498 down_progress_event.set() # Display progress of download.
424 down_thread.join() # Wait for older revision to finish downloading. 499 down_thread.join() # Wait for older revision to finish downloading.
425 pivot = down_pivot 500 pivot = down_pivot
426 zipfile = down_zipfile 501 zipfile = down_zipfile
427 except SystemExit: 502 except SystemExit:
428 print "Cleaning up..." 503 print "Cleaning up..."
429 for f in [_GetDownloadPath(revlist[down_pivot]), 504 for f in [_GetDownloadPath(revlist[down_pivot]),
430 _GetDownloadPath(revlist[up_pivot])]: 505 _GetDownloadPath(revlist[up_pivot])]:
431 try: 506 try:
432 os.unlink(f) 507 os.unlink(f)
(...skipping 26 matching lines...) Expand all
459 '\n' 534 '\n'
460 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 535 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
461 parser = optparse.OptionParser(usage=usage) 536 parser = optparse.OptionParser(usage=usage)
462 # Strangely, the default help output doesn't include the choice list. 537 # Strangely, the default help output doesn't include the choice list.
463 choices = ['mac', 'win', 'linux', 'linux64'] 538 choices = ['mac', 'win', 'linux', 'linux64']
464 # linux-chromiumos lacks a continuous archive http://crbug.com/78158 539 # linux-chromiumos lacks a continuous archive http://crbug.com/78158
465 parser.add_option('-a', '--archive', 540 parser.add_option('-a', '--archive',
466 choices = choices, 541 choices = choices,
467 help = 'The buildbot archive to bisect [%s].' % 542 help = 'The buildbot archive to bisect [%s].' %
468 '|'.join(choices)) 543 '|'.join(choices))
469 parser.add_option('-b', '--bad', type = 'int', 544 parser.add_option('-o', action="store_true", dest='official_builds',
545 help = 'Specify if it is a chromium or official build.')
Robert Sesek 2012/05/23 21:24:01 help = 'Bisect across official Chrome builds (inte
anantha 2012/05/24 18:32:11 Done.
546 parser.add_option('-b', '--bad', type = 'str',
470 help = 'The bad revision to bisect to.') 547 help = 'The bad revision to bisect to.')
471 parser.add_option('-g', '--good', type = 'int', 548 parser.add_option('-g', '--good', type = 'str',
472 help = 'The last known good revision to bisect from.') 549 help = 'The last known good revision to bisect from.')
473 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 550 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
474 help = 'Profile to use; this will not reset every run. ' + 551 help = 'Profile to use; this will not reset every run. ' +
475 'Defaults to a clean profile.', default = 'profile') 552 'Defaults to a clean profile.', default = 'profile')
476 parser.add_option('-t', '--times', type = 'int', 553 parser.add_option('-t', '--times', type = 'int',
477 help = 'Number of times to run each build before asking ' + 554 help = 'Number of times to run each build before asking ' +
478 'if it\'s good or bad. Temporary profiles are reused.', 555 'if it\'s good or bad. Temporary profiles are reused.',
479 default = 1) 556 default = 1)
480 (opts, args) = parser.parse_args() 557 (opts, args) = parser.parse_args()
481 558
482 if opts.archive is None: 559 if opts.archive is None:
483 print 'Error: missing required parameter: --archive' 560 print 'Error: missing required parameter: --archive'
484 print 561 print
485 parser.print_help() 562 parser.print_help()
486 return 1 563 return 1
487 564
488 if opts.bad and opts.good and (opts.good > opts.bad): 565 if opts.bad and opts.good and (opts.good > opts.bad):
489 print ('The good revision (%d) must precede the bad revision (%d).\n' % 566 print ('The good revision (%d) must precede the bad revision (%d).\n' %
490 (opts.good, opts.bad)) 567 (opts.good, opts.bad))
491 parser.print_help() 568 parser.print_help()
492 return 1 569 return 1
493 570
494 # Create the context. Initialize 0 for the revisions as they are set below. 571 # Create the context. Initialize 0 for the revisions as they are set below.
495 context = PathContext(opts.archive, 0, 0) 572 context = PathContext(opts.archive, 0, 0, opts.official_builds)
573
574 if opts.official_builds and opts.bad is None:
575 print ' Needs a bad build range'
576 print
577 parser.print_help()
578 return 1
496 579
497 # Pick a starting point, try to get HEAD for this. 580 # Pick a starting point, try to get HEAD for this.
498 if opts.bad: 581 if opts.bad:
499 bad_rev = opts.bad 582 bad_rev = opts.bad
500 else: 583 else:
501 bad_rev = 0 584 bad_rev = 0
502 try: 585 try:
503 # Location of the latest build revision number 586 # Location of the latest build revision number
504 nh = urllib.urlopen(context.GetLastChangeURL()) 587 nh = urllib.urlopen(context.GetLastChangeURL())
505 latest = int(nh.read()) 588 latest = int(nh.read())
(...skipping 16 matching lines...) Expand all
522 except Exception, e: 605 except Exception, e:
523 pass 606 pass
524 607
525 if opts.times < 1: 608 if opts.times < 1:
526 print('Number of times to run (%d) must be greater than or equal to 1.' % 609 print('Number of times to run (%d) must be greater than or equal to 1.' %
527 opts.times) 610 opts.times)
528 parser.print_help() 611 parser.print_help()
529 return 1 612 return 1
530 613
531 (last_known_good_rev, first_known_bad_rev) = Bisect( 614 (last_known_good_rev, first_known_bad_rev) = Bisect(
532 opts.archive, good_rev, bad_rev, opts.times, args, opts.profile) 615 opts.archive, opts.official_builds, good_rev, bad_rev, opts.times, args,
616 opts.profile)
533 617
534 # Get corresponding webkit revisions. 618 # Get corresponding webkit revisions.
535 try: 619 try:
536 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision( 620 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision(
537 last_known_good_rev) 621 last_known_good_rev)
538 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision( 622 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision(
539 first_known_bad_rev) 623 first_known_bad_rev)
540 except Exception, e: 624 except Exception, e:
541 # Silently ignore the failure. 625 # Silently ignore the failure.
542 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0 626 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0
543 627
544 # We're done. Let the user know the results in an official manner. 628 # We're done. Let the user know the results in an official manner.
545 print('You are probably looking for build %d.' % first_known_bad_rev) 629 print('You are probably looking for build %s.' % str(first_known_bad_rev))
630
546 if last_known_good_webkit_rev != first_known_bad_webkit_rev: 631 if last_known_good_webkit_rev != first_known_bad_webkit_rev:
547 print 'WEBKIT CHANGELOG URL:' 632 print 'WEBKIT CHANGELOG URL:'
548 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 633 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
549 last_known_good_webkit_rev) 634 last_known_good_webkit_rev)
550 print 'CHANGELOG URL:' 635 print 'CHANGELOG URL:'
551 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 636 if opts.official_builds:
552 print 'Built at revision:' 637 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
553 print BUILD_VIEWVC_URL % first_known_bad_rev 638 else:
639 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
640 print 'Built at revision:'
641 print BUILD_VIEWVC_URL % first_known_bad_rev
554 642
555 643
556 if __name__ == '__main__': 644 if __name__ == '__main__':
557 sys.exit(main()) 645 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698