Index: tools/bisect-builds.py |
=================================================================== |
--- tools/bisect-builds.py (revision 139824) |
+++ tools/bisect-builds.py (working copy) |
@@ -341,13 +341,52 @@ |
"""Ask the user whether build |rev| is good or bad.""" |
# Loop until we get a response that we can parse. |
while True: |
- response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev)) |
- if response and response in ('g', 'b'): |
- return response == 'g' |
+ response = raw_input('Revision %s is [(g)ood/(b)ad/(u)nknown/(q)uit]: ' % |
+ str(rev)) |
+ if response and response in ('g', 'b', 'u'): |
+ return response |
if response and response == 'q': |
raise SystemExit() |
+class DownloadJob(object): |
+ """DownloadJob represents a task to download a given Chromium revision.""" |
+ def __init__(self, context, name, rev, zipfile): |
+ super(DownloadJob, self).__init__() |
+ # Store off the input parameters. |
+ self.context = context |
+ self.name = name |
+ self.rev = rev |
+ self.zipfile = zipfile |
+ self.quit_event = threading.Event() |
+ self.progress_event = threading.Event() |
+ |
+ def Start(self): |
+ """Starts the download.""" |
+ fetchargs = (self.context, |
+ self.rev, |
+ self.zipfile, |
+ self.quit_event, |
+ self.progress_event) |
+ self.thread = threading.Thread(target=FetchRevision, |
+ name=self.name, |
+ args=fetchargs) |
+ self.thread.start() |
+ |
+ def Stop(self): |
+ """Stops the download which must have been started previously.""" |
+ self.quit_event.set() |
+ self.thread.join() |
+ os.unlink(self.zipfile) |
+ |
+ def WaitFor(self): |
+ """Prints a message and waits for the download to complete. The download |
+ must have been started previously.""" |
+ print "Downloading revision %s..." % str(self.rev) |
+ self.progress_event.set() # Display progress of download. |
+ self.thread.join() |
+ |
+ |
def Bisect(platform, |
official_builds, |
good_rev=0, |
@@ -355,7 +394,7 @@ |
num_runs=1, |
try_args=(), |
profile=None, |
- predicate=AskIsGoodBuild): |
+ evaluate=AskIsGoodBuild): |
"""Given known good and known bad revisions, run a binary search on all |
archived revisions to determine the last known good revision. |
@@ -366,8 +405,8 @@ |
@param num_runs Number of times to run each build for asking good/bad. |
@param try_args A tuple of arguments to pass to the test application. |
@param profile The name of the user profile to run with. |
- @param predicate A predicate function which returns True iff the argument |
- chromium revision is good. |
+ @param evaluate A function which returns 'g' if the argument build is good, |
+ 'b' if it's bad or 'u' if unknown. |
Threading is used to fetch Chromium revisions in the background, speeding up |
the user's experience. For example, suppose the bounds of the search are |
@@ -411,11 +450,9 @@ |
pivot = bad / 2 |
rev = revlist[pivot] |
zipfile = _GetDownloadPath(rev) |
- progress_event = threading.Event() |
- progress_event.set() |
- print "Downloading revision %s..." % str(rev) |
- FetchRevision(context, rev, zipfile, |
- quit_event=None, progress_event=progress_event) |
+ initial_fetch = DownloadJob(context, 'initial_fetch', rev, zipfile) |
+ initial_fetch.Start() |
+ initial_fetch.WaitFor() |
# Binary search time! |
while zipfile and bad - good > 1: |
@@ -425,38 +462,20 @@ |
# - up_pivot is the next revision to check if the current revision turns |
# out to be good. |
down_pivot = int((pivot - good) / 2) + good |
- down_thread = None |
+ down_fetch = None |
if down_pivot != pivot and down_pivot != good: |
down_rev = revlist[down_pivot] |
- down_zipfile = _GetDownloadPath(down_rev) |
- down_quit_event = threading.Event() |
- down_progress_event = threading.Event() |
- fetchargs = (context, |
- down_rev, |
- down_zipfile, |
- down_quit_event, |
- down_progress_event) |
- down_thread = threading.Thread(target=FetchRevision, |
- name='down_fetch', |
- args=fetchargs) |
- down_thread.start() |
+ down_fetch = DownloadJob(context, 'down_fetch', down_rev, |
+ _GetDownloadPath(down_rev)) |
+ down_fetch.Start() |
up_pivot = int((bad - pivot) / 2) + pivot |
- up_thread = None |
+ up_fetch = None |
if up_pivot != pivot and up_pivot != bad: |
up_rev = revlist[up_pivot] |
- up_zipfile = _GetDownloadPath(up_rev) |
- up_quit_event = threading.Event() |
- up_progress_event = threading.Event() |
- fetchargs = (context, |
- up_rev, |
- up_zipfile, |
- up_quit_event, |
- up_progress_event) |
- up_thread = threading.Thread(target=FetchRevision, |
- name='up_fetch', |
- args=fetchargs) |
- up_thread.start() |
+ up_fetch = DownloadJob(context, 'up_fetch', up_rev, |
+ _GetDownloadPath(up_rev)) |
+ up_fetch.Start() |
# Run test on the pivot revision. |
(status, stdout, stderr) = RunRevision(context, |
@@ -468,34 +487,58 @@ |
os.unlink(zipfile) |
zipfile = None |
- # Call the predicate function to see if the current revision is good or bad. |
+ # Call the evaluate function to see if the current revision is good or bad. |
# On that basis, kill one of the background downloads and complete the |
# other, as described in the comments above. |
try: |
- if predicate(rev, official_builds, status, stdout, stderr): |
+ answer = evaluate(rev, official_builds, status, stdout, stderr) |
+ if answer == 'g': |
good = pivot |
- if down_thread: |
- down_quit_event.set() # Kill the download of older revision. |
- down_thread.join() |
- os.unlink(down_zipfile) |
- if up_thread: |
- print "Downloading revision %s..." % str(up_rev) |
- up_progress_event.set() # Display progress of download. |
- up_thread.join() # Wait for newer revision to finish downloading. |
+ if down_fetch: |
+ down_fetch.Stop() # Kill the download of the older revision. |
+ if up_fetch: |
+ up_fetch.WaitFor() |
pivot = up_pivot |
- zipfile = up_zipfile |
- else: |
+ zipfile = up_fetch.zipfile |
+ elif answer == 'b': |
bad = pivot |
- if up_thread: |
- up_quit_event.set() # Kill download of newer revision. |
- up_thread.join() |
- os.unlink(up_zipfile) |
- if down_thread: |
- print "Downloading revision %s..." % str(down_rev) |
- down_progress_event.set() # Display progress of download. |
- down_thread.join() # Wait for older revision to finish downloading. |
+ if up_fetch: |
+ up_fetch.Stop() # Kill the download of the newer revision. |
+ if down_fetch: |
+ down_fetch.WaitFor() |
pivot = down_pivot |
- zipfile = down_zipfile |
+ zipfile = down_fetch.zipfile |
+ elif answer == 'u': |
+ # Nuke the revision from the revlist and choose a new pivot. |
+ revlist.pop(pivot) |
+ bad -= 1 # Assumes bad >= pivot. |
+ |
+ fetch = None |
+ if bad - good > 1: |
+ # Alternate between using down_pivot or up_pivot for the new pivot |
+ # point, without affecting the range. Do this instead of setting the |
+ # pivot to the midpoint of the new range because adjacent revisions |
+ # are likely affected by the same issue that caused the (u)nknown |
+ # response. |
+ if up_fetch and down_fetch: |
+ fetch = [up_fetch, down_fetch][len(revlist) % 2] |
+ elif up_fetch: |
+ fetch = up_fetch |
+ else: |
+ fetch = down_fetch |
+ fetch.WaitFor() |
+ if fetch == up_fetch: |
+ pivot = up_pivot - 1 # Subtracts 1 because revlist was resized. |
+ else: |
+ pivot = down_pivot |
+ zipfile = fetch.zipfile |
+ |
+ if down_fetch and fetch != down_fetch: |
+ down_fetch.Stop() |
+ if up_fetch and fetch != up_fetch: |
+ up_fetch.Stop() |
+ else: |
+ assert False, "Unexpected return value from evaluate(): " + answer |
except SystemExit: |
print "Cleaning up..." |
for f in [_GetDownloadPath(revlist[down_pivot]), |