blob: 6eddf6dc6f91b7295a3903d793c37b0a78b39cc2 [file] [log] [blame]
[email protected]67e0bc62009-09-03 22:06:091#!/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
[email protected]7ad66a72009-09-04 17:52:338This script bisects a snapshot archive using binary search. It starts at
[email protected]67e0bc62009-09-03 22:06:099a bad revision (it will try to guess HEAD) and asks for a last known-good
10revision. It will then binary search across this revision range by downloading,
11unzipping, and opening Chromium for you. After testing the specific revision,
12it will ask you whether it is good or bad before continuing the search.
[email protected]67e0bc62009-09-03 22:06:0913"""
14
15# Base URL to download snapshots from.
[email protected]7ad66a72009-09-04 17:52:3316BUILD_BASE_URL = "http://build.chromium.org/buildbot/snapshots/"
[email protected]67e0bc62009-09-03 22:06:0917
[email protected]7ad66a72009-09-04 17:52:3318# The type (platform) of the build archive. This is what's passed in to the
19# '-a/--archive' option.
20BUILD_ARCHIVE_TYPE = ''
21
22# The selected archive to bisect.
23BUILD_ARCHIVE_DIR = ''
[email protected]67e0bc62009-09-03 22:06:0924
25# The location of the builds.
26BUILD_ARCHIVE_URL = "/%d/"
27
28# Name of the build archive.
[email protected]7ad66a72009-09-04 17:52:3329BUILD_ZIP_NAME = ''
[email protected]67e0bc62009-09-03 22:06:0930
31# Directory name inside the archive.
[email protected]7ad66a72009-09-04 17:52:3332BUILD_DIR_NAME = ''
[email protected]67e0bc62009-09-03 22:06:0933
34# Name of the executable.
[email protected]7ad66a72009-09-04 17:52:3335BUILD_EXE_NAME = ''
[email protected]67e0bc62009-09-03 22:06:0936
37# URL to the ViewVC commit page.
38BUILD_VIEWVC_URL = "http://src.chromium.org/viewvc/chrome?view=rev&revision=%d"
39
40###############################################################################
41
42import math
[email protected]7ad66a72009-09-04 17:52:3343import optparse
[email protected]67e0bc62009-09-03 22:06:0944import os
45import re
46import shutil
47import sys
[email protected]7ad66a72009-09-04 17:52:3348import tempfile
[email protected]67e0bc62009-09-03 22:06:0949import urllib
50
[email protected]7ad66a72009-09-04 17:52:3351def SetArchiveVars(archive):
52 """Set a bunch of global variables appropriate for the specified archive."""
53 global BUILD_ARCHIVE_TYPE
54 global BUILD_ARCHIVE_DIR
55 global BUILD_ZIP_NAME
56 global BUILD_DIR_NAME
57 global BUILD_EXE_NAME
58 global BUILD_BASE_URL
59
60 BUILD_ARCHIVE_TYPE = archive
61 BUILD_ARCHIVE_DIR = 'chromium-rel-' + BUILD_ARCHIVE_TYPE
62
63 if BUILD_ARCHIVE_TYPE in ('linux', 'linux-64'):
64 BUILD_ZIP_NAME = 'chrome-linux.zip'
65 BUILD_DIR_NAME = 'chrome-linux'
66 BUILD_EXE_NAME = "chrome"
67 elif BUILD_ARCHIVE_TYPE in ('mac'):
68 BUILD_ZIP_NAME = 'chrome-mac.zip'
69 BUILD_DIR_NAME = 'chrome-mac'
70 BUILD_EXE_NAME = "Chromium.app"
71 elif BUILD_ARCHIVE_TYPE in ('xp'):
72 BUILD_ZIP_NAME = 'chrome-win32.zip'
73 BUILD_DIR_NAME = 'chrome-win32'
74 BUILD_EXE_NAME = "chrome.exe"
75
76 BUILD_BASE_URL += BUILD_ARCHIVE_DIR
77
[email protected]67e0bc62009-09-03 22:06:0978def ParseDirectoryIndex(url):
79 """Parses the HTML directory listing into a list of revision numbers."""
80 handle = urllib.urlopen(url)
81 dirindex = handle.read()
82 handle.close()
83 return re.findall(r'<a href="([0-9]*)/">\1/</a>', dirindex)
84
85def GetRevList(good, bad):
86 """Gets the list of revision numbers between |good| and |bad|."""
87 # Download the main revlist.
88 revlist = ParseDirectoryIndex(BUILD_BASE_URL)
89 revlist = map(int, revlist)
90 revlist = filter(lambda r: range(good, bad).__contains__(int(r)), revlist)
91 revlist.sort()
92 return revlist
93
94def TryRevision(rev):
95 """Downloads revision |rev|, unzips it, and opens it for the user to test."""
[email protected]7ad66a72009-09-04 17:52:3396 # Do this in a temp dir so we don't collide with user files.
97 cwd = os.getcwd()
98 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
99 os.chdir(tempdir)
[email protected]67e0bc62009-09-03 22:06:09100
101 # Download the file.
102 download_url = BUILD_BASE_URL + (BUILD_ARCHIVE_URL % rev) + BUILD_ZIP_NAME
103 try:
[email protected]7ad66a72009-09-04 17:52:33104 print 'Fetching ' + download_url
[email protected]67e0bc62009-09-03 22:06:09105 urllib.urlretrieve(download_url, BUILD_ZIP_NAME)
106 except Exception, e:
107 print("Could not retrieve the download. Sorry.")
[email protected]67e0bc62009-09-03 22:06:09108 sys.exit(-1)
109
110 # Unzip the file.
[email protected]7ad66a72009-09-04 17:52:33111 print 'Unzipping ...'
[email protected]67e0bc62009-09-03 22:06:09112 os.system("unzip -q %s" % BUILD_ZIP_NAME)
113
[email protected]7ad66a72009-09-04 17:52:33114 # Tell the system to open the app.
115 print 'Running %s/%s/%s' % (os.getcwd(), BUILD_DIR_NAME, BUILD_EXE_NAME)
116 if BUILD_ARCHIVE_TYPE in ('linux', 'linux-64'):
117 os.system("%s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME))
118 elif BUILD_ARCHIVE_TYPE in ('mac'):
119 os.system("open %s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME))
120 elif BUILD_ARCHIVE_TYPE in ('xp'):
121 # TODO(mmoss) Does Windows need 'start' or something?
122 os.system("%s/%s" % (BUILD_DIR_NAME, BUILD_EXE_NAME))
123
124 os.chdir(cwd)
125 print 'Cleaning temp dir ...'
126 try:
127 shutil.rmtree(tempdir, True)
128 except Exception, e:
129 pass
[email protected]67e0bc62009-09-03 22:06:09130
131def AskIsGoodBuild(rev):
132 """Annoyingly ask the user whether build |rev| is good or bad."""
133 while True:
[email protected]7ad66a72009-09-04 17:52:33134 check = raw_input("\nBuild %d is [(g)ood/(b)ad]: " % int(rev))[0]
[email protected]67e0bc62009-09-03 22:06:09135 if (check == "g" or check == "b"):
136 return (check == "g")
137 else:
138 print("Just answer the question...")
139
140def main():
[email protected]7ad66a72009-09-04 17:52:33141 usage = ('%prog [options]\n'
142 'Perform binary search on the snapshot builds.')
143 parser = optparse.OptionParser(usage=usage)
144 parser.add_option('-a', '--archive',
145 choices = ['mac', 'xp', 'linux', 'linux-64'],
146 help = 'The buildbot archive to bisect.')
147 parser.add_option('-b', '--bad', type = 'int',
148 help = 'The bad revision to bisect to.')
149 parser.add_option('-g', '--good', type = 'int',
150 help = 'The last known good revision to bisect from.')
151 (opts, args) = parser.parse_args()
152
153 if opts.archive is None:
154 parser.print_help()
155 return 1
156
157 if opts.bad and opts.good and (opts.good > opts.bad):
158 print ('The good revision (%d) must precede the bad revision (%d).\n' %
159 (opts.good, opts.bad))
160 parser.print_help()
161 return 1
162
163 SetArchiveVars(opts.archive)
[email protected]67e0bc62009-09-03 22:06:09164
165 # Pick a starting point, try to get HEAD for this.
[email protected]7ad66a72009-09-04 17:52:33166 if opts.bad:
167 bad_rev = opts.bad
168 else:
169 bad_rev = 0
170 try:
171 # Location of the latest build revision number
172 BUILD_LATEST_URL = "%s/LATEST" % (BUILD_BASE_URL)
173 nh = urllib.urlopen(BUILD_LATEST_URL)
174 latest = int(nh.read())
175 nh.close()
176 bad_rev = raw_input("Bad revision [HEAD:%d]: " % latest)
177 if (bad_rev == ""):
178 bad_rev = latest
179 bad_rev = int(bad_rev)
180 except Exception, e:
181 print("Could not determine latest revision. This could be bad...")
182 bad_rev = int(raw_input("Bad revision: "))
[email protected]67e0bc62009-09-03 22:06:09183
184 # Find out when we were good.
[email protected]7ad66a72009-09-04 17:52:33185 if opts.good:
186 good_rev = opts.good
187 else:
188 good_rev = 0
189 try:
190 good_rev = int(raw_input("Last known good [0]: "))
191 except Exception, e:
192 pass
[email protected]67e0bc62009-09-03 22:06:09193
194 # Get a list of revisions to bisect across.
195 revlist = GetRevList(good_rev, bad_rev)
196
197 # If we don't have a |good_rev|, set it to be the first revision possible.
198 if good_rev == 0:
199 good_rev = revlist[0]
200
201 # These are indexes of |revlist|.
202 good = 0
203 bad = len(revlist) - 1
204
205 # Binary search time!
206 while good < bad:
207 candidates = revlist[good:bad]
208 num_poss = len(candidates)
209 if num_poss > 10:
210 print("%d candidates. %d tries left." %
211 (num_poss, round(math.log(num_poss, 2))))
212 else:
213 print("Candidates: %s" % revlist[good:bad])
214
215 # Cut the problem in half...
216 test = int((bad - good) / 2) + good
217 test_rev = revlist[test]
218
219 # Let the user give this revision a spin.
220 TryRevision(test_rev)
221 if AskIsGoodBuild(test_rev):
222 good = test + 1
223 else:
224 bad = test
225
226 # We're done. Let the user know the results in an official manner.
227 print("You are probably looking for build %d." % revlist[bad])
228 print("This is the ViewVC URL for the potential bustage:")
229 print(BUILD_VIEWVC_URL % revlist[bad])
230
231if __name__ == '__main__':
[email protected]7ad66a72009-09-04 17:52:33232 sys.exit(main())