tools/bisect-builds.py
def Bisect(context,
           num_runs=1,
           command='%p %a',
           try_args=(),
           profile=None,
           evaluate=AskIsGoodBuild,
           verify_range=False,
           archive=None):
  """Given known good and known bad revisions, run a binary search on all
  archived revisions to determine the last known good revision.

  @param context PathContext object initialized with user provided parameters.
  @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 evaluate A function which returns 'g' if the argument build is good,
                  'b' if it's bad or 'u' if unknown.
  @param verify_range If true, tests the first and last revisions in the range
                      before proceeding with the bisect.

  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
  good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
  whether revision 50 is good or bad, the next revision to check will be either
  25 or 75. So, while revision 50 is being checked, the script will download
  revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
  known:

    - If rev 50 is good, the download of rev 25 is cancelled, and the next test
      is run on rev 75.

    - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
      is run on rev 25.
  """

  if not profile:
    profile = 'profile'

  good_rev = context.good_revision
  bad_rev = context.bad_revision
  cwd = os.getcwd()

  print('Downloading list of known revisions...', end=' ')
  if not context.use_local_cache:
    print('(use --use-local-cache to cache and re-use the list of revisions)')
  else:
    print()
  _GetDownloadPath = lambda rev: os.path.join(cwd,
      '%s-%s' % (str(rev), context.archive_name))

  # Get a list of revisions to bisect across.
  revlist = context.GetRevList(archive)

  # Figure out our bookends and first pivot point; fetch the pivot revision.
  minrev = 0
  maxrev = len(revlist) - 1
  pivot = int(maxrev / 2)
  rev = revlist[pivot]
  fetch = DownloadJob(context, 'initial_fetch', rev, _GetDownloadPath(rev))
  fetch.Start()

  if verify_range:
    minrev_fetch = DownloadJob(
        context, 'minrev_fetch', revlist[minrev],
        _GetDownloadPath(revlist[minrev]))
    maxrev_fetch = DownloadJob(
        context, 'maxrev_fetch', revlist[maxrev],
        _GetDownloadPath(revlist[maxrev]))
    minrev_fetch.Start()
    maxrev_fetch.Start()
    try:
      VerifyEndpoint(minrev_fetch, context, revlist[minrev], profile, num_runs,
          command, try_args, evaluate, 'b' if bad_rev < good_rev else 'g')
      VerifyEndpoint(maxrev_fetch, context, revlist[maxrev], profile, num_runs,
          command, try_args, evaluate, 'g' if bad_rev < good_rev else 'b')
    except (KeyboardInterrupt, SystemExit):
      print('Cleaning up...')
      fetch.Stop()
      sys.exit(0)
    finally:
      minrev_fetch.Stop()
      maxrev_fetch.Stop()

  fetch.WaitFor()

  # Binary search time!
  prefetch_revisions = True
  while fetch and fetch.zip_file and maxrev - minrev > 1:
    if bad_rev < good_rev:
      min_str, max_str = 'bad', 'good'
    else:
      min_str, max_str = 'good', 'bad'
    print(
        'Bisecting range [%s (%s), %s (%s)], '
        'roughly %d steps left.' % (revlist[minrev], min_str, revlist[maxrev],
                                    max_str, int(maxrev - minrev).bit_length()))

    # Pre-fetch next two possible pivots
    #   - down_pivot is the next revision to check if the current revision turns
    #     out to be bad.
    #   - up_pivot is the next revision to check if the current revision turns
    #     out to be good.
    down_pivot = int((pivot - minrev) / 2) + minrev
    if prefetch_revisions:
      down_fetch = None
      if down_pivot != pivot and down_pivot != minrev:
        down_rev = revlist[down_pivot]
        down_fetch = DownloadJob(context, 'down_fetch', down_rev,
                                 _GetDownloadPath(down_rev))
        down_fetch.Start()

    up_pivot = int((maxrev - pivot) / 2) + pivot
    if prefetch_revisions:
      up_fetch = None
      if up_pivot != pivot and up_pivot != maxrev:
        up_rev = revlist[up_pivot]
        up_fetch = DownloadJob(context, 'up_fetch', up_rev,
                               _GetDownloadPath(up_rev))
        up_fetch.Start()

    # Run test on the pivot revision.
    exit_status = None
    stdout = None
    stderr = None
    try:
      (exit_status, stdout, stderr) = RunRevision(
          context, rev, fetch.zip_file, profile, num_runs, command, try_args)
    except Exception as e:
      print(e, file=sys.stderr)

    # 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:
      answer = evaluate(rev, exit_status, stdout, stderr)
      prefetch_revisions = True
      if ((answer == 'g' and good_rev < bad_rev)
          or (answer == 'b' and bad_rev < good_rev)):
        fetch.Stop()
        minrev = pivot
        if down_fetch:
          down_fetch.Stop()  # Kill the download of the older revision.
          fetch = None
        if up_fetch:
          up_fetch.WaitFor()
          pivot = up_pivot
          fetch = up_fetch
      elif ((answer == 'b' and good_rev < bad_rev)
            or (answer == 'g' and bad_rev < good_rev)):
        fetch.Stop()
        maxrev = pivot
        if up_fetch:
          up_fetch.Stop()  # Kill the download of the newer revision.
          fetch = None
        if down_fetch:
          down_fetch.WaitFor()
          pivot = down_pivot
          fetch = down_fetch
      elif answer == 'r':
        # Don't redundantly prefetch.
        prefetch_revisions = False
      elif answer == 'u':
        # Nuke the revision from the revlist and choose a new pivot.
        fetch.Stop()
        revlist.pop(pivot)
        maxrev -= 1  # Assumes maxrev >= pivot.

        if maxrev - minrev > 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

        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 (KeyboardInterrupt, SystemExit):
      print('Cleaning up...')
      for f in [_GetDownloadPath(rev),
                _GetDownloadPath(revlist[down_pivot]),
                _GetDownloadPath(revlist[up_pivot])]:
        try:
          os.unlink(f)
        except OSError:
          pass
      sys.exit(0)

    rev = revlist[pivot]

  return (revlist[minrev], revlist[maxrev], context)
Given known good and known bad revisions, run a binary search on all
  archived revisions to determine the last known good revision.

  @param context PathContext object initialized with user provided parameters.
  @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 evaluate A function which returns 'g' if the argument build is good,
                  'b' if it's bad or 'u' if unknown.
  @param verify_range If true, tests the first and last revisions in the range
                      before proceeding with the bisect.

  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
  good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
  whether revision 50 is good or bad, the next revision to check will be either
  25 or 75. So, while revision 50 is being checked, the script will download
  revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
  known:

    - If rev 50 is good, the download of rev 25 is cancelled, and the next test
      is run on rev 75.

    - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
      is run on rev 25.
  
Given known good and known bad revisions, run a binary search on all
archived revisions to determine the last known good revision.

@param context PathContext object initialized with user provided parameters.
@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.

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
good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
whether revision 50 is good or bad, the next revision to check will be either
25 or 75. So, while revision 50 is being checked, the script will download
revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
known.

If rev 50 is good, the download of rev 25 is cancelled, and the next test
is run on rev 75.

If rev 50 is bad, the download of rev 75 is cancelled, and the next test
is run on rev 25.
Given known good and known bad revisions run a binary search on all archived revisions to determine the last known good revision . @param context PathContext object initialized with user provided parameters . @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 . 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 good_rev = 0 bad_rev = 100 . The first revision to be checked is 50 . Depending on whether revision 50 is good or bad the next revision to check will be either 25 or 75 . So while revision 50 is being checked the script will download revisions 25 and 75 in the background . Once the good / bad verdict on rev 50 is known . If rev 50 is good the download of rev 25 is cancelled and the next test is run on rev 75 . If rev 50 is bad the download of rev 75 is cancelled and the next test is run on rev 25 .
def Bisect(context,
           num_runs=1,
           command='%p %a',
           try_args=(),
           profile=None,
           evaluate=AskIsGoodBuild,
           verify_range=False,
           archive=None):
  if not profile:
    profile = 'profile'
  good_rev = context.good_revision
  bad_rev = context.bad_revision
  cwd = os.getcwd()
  print('Downloading list of known revisions...', end=' ')
  if not context.use_local_cache:
    print('(use --use-local-cache to cache and re-use the list of revisions)')
  else:
    print()
  _GetDownloadPath = lambda rev: os.path.join(cwd,
      '%s-%s' % (str(rev), context.archive_name))
  revlist = context.GetRevList(archive)
  minrev = 0
  maxrev = len(revlist) - 1
  pivot = int(maxrev / 2)
  rev = revlist[pivot]
  fetch = DownloadJob(context, 'initial_fetch', rev, _GetDownloadPath(rev))
  fetch.Start()
  if verify_range:
    minrev_fetch = DownloadJob(
        context, 'minrev_fetch', revlist[minrev],
        _GetDownloadPath(revlist[minrev]))
    maxrev_fetch = DownloadJob(
        context, 'maxrev_fetch', revlist[maxrev],
        _GetDownloadPath(revlist[maxrev]))
    minrev_fetch.Start()
    maxrev_fetch.Start()
    try:
      VerifyEndpoint(minrev_fetch, context, revlist[minrev], profile, num_runs,
          command, try_args, evaluate, 'b' if bad_rev < good_rev else 'g')
      VerifyEndpoint(maxrev_fetch, context, revlist[maxrev], profile, num_runs,
          command, try_args, evaluate, 'g' if bad_rev < good_rev else 'b')
    except (KeyboardInterrupt, SystemExit):
      print('Cleaning up...')
      fetch.Stop()
      sys.exit(0)
    finally:
      minrev_fetch.Stop()
      maxrev_fetch.Stop()
  fetch.WaitFor()
  prefetch_revisions = True
  while fetch and fetch.zip_file and maxrev - minrev > 1:
    if bad_rev < good_rev:
      min_str, max_str = 'bad', 'good'
    else:
      min_str, max_str = 'good', 'bad'
    print(
        'Bisecting range [%s (%s), %s (%s)], '
        'roughly %d steps left.' % (revlist[minrev], min_str, revlist[maxrev],
                                    max_str, int(maxrev - minrev).bit_length()))
    down_pivot = int((pivot - minrev) / 2) + minrev
    if prefetch_revisions:
      down_fetch = None
      if down_pivot != pivot and down_pivot != minrev:
        down_rev = revlist[down_pivot]
        down_fetch = DownloadJob(context, 'down_fetch', down_rev,
                                 _GetDownloadPath(down_rev))
        down_fetch.Start()
    up_pivot = int((maxrev - pivot) / 2) + pivot
    if prefetch_revisions:
      up_fetch = None
      if up_pivot != pivot and up_pivot != maxrev:
        up_rev = revlist[up_pivot]
        up_fetch = DownloadJob(context, 'up_fetch', up_rev,
                               _GetDownloadPath(up_rev))
        up_fetch.Start()
    exit_status = None
    stdout = None
    stderr = None
    try:
      (exit_status, stdout, stderr) = RunRevision(
          context, rev, fetch.zip_file, profile, num_runs, command, try_args)
    except Exception as e:
      print(e, file=sys.stderr)
    try:
      answer = evaluate(rev, exit_status, stdout, stderr)
      prefetch_revisions = True
      if ((answer == 'g' and good_rev < bad_rev)
          or (answer == 'b' and bad_rev < good_rev)):
        fetch.Stop()
        minrev = pivot
        if down_fetch:
          down_fetch.Stop()  
          fetch = None
        if up_fetch:
          up_fetch.WaitFor()
          pivot = up_pivot
          fetch = up_fetch
      elif ((answer == 'b' and good_rev < bad_rev)
            or (answer == 'g' and bad_rev < good_rev)):
        fetch.Stop()
        maxrev = pivot
        if up_fetch:
          up_fetch.Stop()  
          fetch = None
        if down_fetch:
          down_fetch.WaitFor()
          pivot = down_pivot
          fetch = down_fetch
      elif answer == 'r':
        prefetch_revisions = False
      elif answer == 'u':
        fetch.Stop()
        revlist.pop(pivot)
        maxrev -= 1  
        if maxrev - minrev > 1:
          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  
          else:
            pivot = down_pivot
        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 (KeyboardInterrupt, SystemExit):
      print('Cleaning up...')
      for f in [_GetDownloadPath(rev),
                _GetDownloadPath(revlist[down_pivot]),
                _GetDownloadPath(revlist[up_pivot])]:
        try:
          os.unlink(f)
        except OSError:
          pass
      sys.exit(0)
    rev = revlist[pivot]
  return (revlist[minrev], revlist[maxrev], context)
def Bisect ( context , num_runs = 1 , command = '%p %a' , try_args = ( ) , profile = None , evaluate = AskIsGoodBuild , verify_range = False , archive = None ) : if not profile : profile = 'profile' good_rev = context . good_revision bad_rev = context . bad_revision cwd = os . getcwd ( ) print ( 'Downloading list of known revisions...' , end = ' ' ) if not context . use_local_cache : print ( '(use --use-local-cache to cache and re-use the list of revisions)' ) else : print ( ) _GetDownloadPath = lambda rev : os . path . join ( cwd , '%s-%s' % ( str ( rev ) , context . archive_name ) ) revlist = context . GetRevList ( archive ) minrev = 0 maxrev = len ( revlist ) - 1 pivot = int ( maxrev / 2 ) rev = revlist [ pivot ] fetch = DownloadJob ( context , 'initial_fetch' , rev , _GetDownloadPath ( rev ) ) fetch . Start ( ) if verify_range : minrev_fetch = DownloadJob ( context , 'minrev_fetch' , revlist [ minrev ] , _GetDownloadPath ( revlist [ minrev ] ) ) maxrev_fetch = DownloadJob ( context , 'maxrev_fetch' , revlist [ maxrev ] , _GetDownloadPath ( revlist [ maxrev ] ) ) minrev_fetch . Start ( ) maxrev_fetch . Start ( ) try : VerifyEndpoint ( minrev_fetch , context , revlist [ minrev ] , profile , num_runs , command , try_args , evaluate , 'b' if bad_rev < good_rev else 'g' ) VerifyEndpoint ( maxrev_fetch , context , revlist [ maxrev ] , profile , num_runs , command , try_args , evaluate , 'g' if bad_rev < good_rev else 'b' ) except ( KeyboardInterrupt , SystemExit ) : print ( 'Cleaning up...' ) fetch . Stop ( ) sys . exit ( 0 ) finally : minrev_fetch . Stop ( ) maxrev_fetch . Stop ( ) fetch . WaitFor ( ) prefetch_revisions = True while fetch and fetch . zip_file and maxrev - minrev > 1 : if bad_rev < good_rev : min_str , max_str = 'bad' , 'good' else : min_str , max_str = 'good' , 'bad' print ( 'Bisecting range [%s (%s), %s (%s)], ' 'roughly %d steps left.' % ( revlist [ minrev ] , min_str , revlist [ maxrev ] , max_str , int ( maxrev - minrev ) . bit_length ( ) ) ) down_pivot = int ( ( pivot - minrev ) / 2 ) + minrev if prefetch_revisions : down_fetch = None if down_pivot != pivot and down_pivot != minrev : down_rev = revlist [ down_pivot ] down_fetch = DownloadJob ( context , 'down_fetch' , down_rev , _GetDownloadPath ( down_rev ) ) down_fetch . Start ( ) up_pivot = int ( ( maxrev - pivot ) / 2 ) + pivot if prefetch_revisions : up_fetch = None if up_pivot != pivot and up_pivot != maxrev : up_rev = revlist [ up_pivot ] up_fetch = DownloadJob ( context , 'up_fetch' , up_rev , _GetDownloadPath ( up_rev ) ) up_fetch . Start ( ) exit_status = None stdout = None stderr = None try : ( exit_status , stdout , stderr ) = RunRevision ( context , rev , fetch . zip_file , profile , num_runs , command , try_args ) except Exception as e : print ( e , file = sys . stderr ) try : answer = evaluate ( rev , exit_status , stdout , stderr ) prefetch_revisions = True if ( ( answer == 'g' and good_rev < bad_rev ) or ( answer == 'b' and bad_rev < good_rev ) ) : fetch . Stop ( ) minrev = pivot if down_fetch : down_fetch . Stop ( ) fetch = None if up_fetch : up_fetch . WaitFor ( ) pivot = up_pivot fetch = up_fetch elif ( ( answer == 'b' and good_rev < bad_rev ) or ( answer == 'g' and bad_rev < good_rev ) ) : fetch . Stop ( ) maxrev = pivot if up_fetch : up_fetch . Stop ( ) fetch = None if down_fetch : down_fetch . WaitFor ( ) pivot = down_pivot fetch = down_fetch elif answer == 'r' : prefetch_revisions = False elif answer == 'u' : fetch . Stop ( ) revlist . pop ( pivot ) maxrev -= 1 if maxrev - minrev > 1 : 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 else : pivot = down_pivot 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 ( KeyboardInterrupt , SystemExit ) : print ( 'Cleaning up...' ) for f in [ _GetDownloadPath ( rev ) , _GetDownloadPath ( revlist [ down_pivot ] ) , _GetDownloadPath ( revlist [ up_pivot ] ) ] : try : os . unlink ( f ) except OSError : pass sys . exit ( 0 ) rev = revlist [ pivot ] return ( revlist [ minrev ] , revlist [ maxrev ] , context )
Given known good and known bad revisions, run a binary search on all
archived revisions to determine the last known good revision.
Given known good and known bad revisions run a binary search on all archived revisions to determine the last known good revision .
"""Given known good and known bad revisions, run a binary search on all
  archived revisions to determine the last known good revision.

  @param context PathContext object initialized with user provided parameters.
  @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 evaluate A function which returns 'g' if the argument build is good,
                  'b' if it's bad or 'u' if unknown.
  @param verify_range If true, tests the first and last revisions in the range
                      before proceeding with the bisect.

  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
  good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
  whether revision 50 is good or bad, the next revision to check will be either
  25 or 75. So, while revision 50 is being checked, the script will download
  revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
  known:

    - If rev 50 is good, the download of rev 25 is cancelled, and the next test
      is run on rev 75.

    - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
      is run on rev 25.
  """ # Get a list of revisions to bisect across. # Figure out our bookends and first pivot point; fetch the pivot revision. # Binary search time! # Pre-fetch next two possible pivots #   - down_pivot is the next revision to check if the current revision turns #     out to be bad. #   - up_pivot is the next revision to check if the current revision turns #     out to be good. # Run test on the pivot revision. # 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. # Kill the download of the older revision. # Kill the download of the newer revision. # Don't redundantly prefetch. # Nuke the revision from the revlist and choose a new pivot. # Assumes maxrev >= pivot. # 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. # Subtracts 1 because revlist was resized.
context
num_runs
command
try_args
profile
evaluate
verify_range
archive