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

Side by Side Diff: tools/bisect-builds.py

Issue 9232057: [tools] Add --times option to bisect-builds.py (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: review comments Created 8 years, 10 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) 2011 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2011 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,
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after
235 235
236 download_url = context.GetDownloadURL(rev) 236 download_url = context.GetDownloadURL(rev)
237 try: 237 try:
238 urllib.urlretrieve(download_url, filename, ReportHook) 238 urllib.urlretrieve(download_url, filename, ReportHook)
239 if progress_event and progress_event.isSet(): 239 if progress_event and progress_event.isSet():
240 print 240 print
241 except RuntimeError, e: 241 except RuntimeError, e:
242 pass 242 pass
243 243
244 244
245 def RunRevision(context, revision, zipfile, profile, args): 245 def RunRevision(context, revision, zipfile, profile, num_runs, args):
246 """Given a zipped revision, unzip it and run the test.""" 246 """Given a zipped revision, unzip it and run the test."""
247 print "Trying revision %d..." % revision 247 print "Trying revision %d..." % revision
248 248
249 # Create a temp directory and unzip the revision into it. 249 # Create a temp directory and unzip the revision into it.
250 cwd = os.getcwd() 250 cwd = os.getcwd()
251 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') 251 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
252 UnzipFilenameToDir(zipfile, tempdir) 252 UnzipFilenameToDir(zipfile, tempdir)
253 os.chdir(tempdir) 253 os.chdir(tempdir)
254 254
255 # Run the build. 255 # Run the build as many times as specified.
256 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args 256 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args
257 subproc = subprocess.Popen(testargs, 257 for i in range(0, num_runs):
258 bufsize=-1, 258 subproc = subprocess.Popen(testargs,
259 stdout=subprocess.PIPE, 259 bufsize=-1,
260 stderr=subprocess.PIPE) 260 stdout=subprocess.PIPE,
261 (stdout, stderr) = subproc.communicate() 261 stderr=subprocess.PIPE)
262 (stdout, stderr) = subproc.communicate()
262 263
263 os.chdir(cwd) 264 os.chdir(cwd)
264 try: 265 try:
265 shutil.rmtree(tempdir, True) 266 shutil.rmtree(tempdir, True)
266 except Exception, e: 267 except Exception, e:
267 pass 268 pass
268 269
269 return (subproc.returncode, stdout, stderr) 270 return (subproc.returncode, stdout, stderr)
270 271
271 272
272 def AskIsGoodBuild(rev, status, stdout, stderr): 273 def AskIsGoodBuild(rev, status, stdout, stderr):
273 """Ask the user whether build |rev| is good or bad.""" 274 """Ask the user whether build |rev| is good or bad."""
274 # Loop until we get a response that we can parse. 275 # Loop until we get a response that we can parse.
275 while True: 276 while True:
276 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) 277 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev))
277 if response and response in ('g', 'b'): 278 if response and response in ('g', 'b'):
278 return response == 'g' 279 return response == 'g'
279 if response and response == 'q': 280 if response and response == 'q':
280 raise SystemExit() 281 raise SystemExit()
281 282
282 283
283 def Bisect(platform, 284 def Bisect(platform,
284 good_rev=0, 285 good_rev=0,
285 bad_rev=0, 286 bad_rev=0,
287 num_runs=1,
286 try_args=(), 288 try_args=(),
287 profile=None, 289 profile=None,
288 predicate=AskIsGoodBuild): 290 predicate=AskIsGoodBuild):
289 """Given known good and known bad revisions, run a binary search on all 291 """Given known good and known bad revisions, run a binary search on all
290 archived revisions to determine the last known good revision. 292 archived revisions to determine the last known good revision.
291 293
292 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 294 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
293 @param good_rev Number/tag of the last known good revision. 295 @param good_rev Number/tag of the last known good revision.
294 @param bad_rev Number/tag of the first known bad revision. 296 @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.
295 @param try_args A tuple of arguments to pass to the test application. 298 @param try_args A tuple of arguments to pass to the test application.
296 @param profile The name of the user profile to run with. 299 @param profile The name of the user profile to run with.
297 @param predicate A predicate function which returns True iff the argument 300 @param predicate A predicate function which returns True iff the argument
298 chromium revision is good. 301 chromium revision is good.
299 302
300 Threading is used to fetch Chromium revisions in the background, speeding up 303 Threading is used to fetch Chromium revisions in the background, speeding up
301 the user's experience. For example, suppose the bounds of the search are 304 the user's experience. For example, suppose the bounds of the search are
302 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on 305 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
303 whether revision 50 is good or bad, the next revision to check will be either 306 whether revision 50 is good or bad, the next revision to check will be either
304 25 or 75. So, while revision 50 is being checked, the script will download 307 25 or 75. So, while revision 50 is being checked, the script will download
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
381 up_thread = threading.Thread(target=FetchRevision, 384 up_thread = threading.Thread(target=FetchRevision,
382 name='up_fetch', 385 name='up_fetch',
383 args=fetchargs) 386 args=fetchargs)
384 up_thread.start() 387 up_thread.start()
385 388
386 # Run test on the pivot revision. 389 # Run test on the pivot revision.
387 (status, stdout, stderr) = RunRevision(context, 390 (status, stdout, stderr) = RunRevision(context,
388 rev, 391 rev,
389 zipfile, 392 zipfile,
390 profile, 393 profile,
394 num_runs,
391 try_args) 395 try_args)
392 os.unlink(zipfile) 396 os.unlink(zipfile)
393 zipfile = None 397 zipfile = None
394 398
395 # Call the predicate function to see if the current revision is good or bad. 399 # Call the predicate function to see if the current revision is good or bad.
396 # On that basis, kill one of the background downloads and complete the 400 # On that basis, kill one of the background downloads and complete the
397 # other, as described in the comments above. 401 # other, as described in the comments above.
398 try: 402 try:
399 if predicate(rev, status, stdout, stderr): 403 if predicate(rev, status, stdout, stderr):
400 good = pivot 404 good = pivot
(...skipping 14 matching lines...) Expand all
415 up_thread.join() 419 up_thread.join()
416 os.unlink(up_zipfile) 420 os.unlink(up_zipfile)
417 if down_thread: 421 if down_thread:
418 print "Downloading revision %d..." % down_rev 422 print "Downloading revision %d..." % down_rev
419 down_progress_event.set() # Display progress of download. 423 down_progress_event.set() # Display progress of download.
420 down_thread.join() # Wait for older revision to finish downloading. 424 down_thread.join() # Wait for older revision to finish downloading.
421 pivot = down_pivot 425 pivot = down_pivot
422 zipfile = down_zipfile 426 zipfile = down_zipfile
423 except SystemExit: 427 except SystemExit:
424 print "Cleaning up..." 428 print "Cleaning up..."
425 for f in [down_zipfile, up_zipfile]: 429 for f in [_GetDownloadPath(revlist[down_pivot]),
430 _GetDownloadPath(revlist[up_pivot])]:
426 try: 431 try:
427 os.unlink(f) 432 os.unlink(f)
428 except OSError: 433 except OSError:
429 pass 434 pass
430 sys.exit(0) 435 sys.exit(0)
431 436
432 rev = revlist[pivot] 437 rev = revlist[pivot]
433 438
434 return (revlist[good], revlist[bad]) 439 return (revlist[good], revlist[bad])
435 440
(...skipping 25 matching lines...) Expand all
461 choices = choices, 466 choices = choices,
462 help = 'The buildbot archive to bisect [%s].' % 467 help = 'The buildbot archive to bisect [%s].' %
463 '|'.join(choices)) 468 '|'.join(choices))
464 parser.add_option('-b', '--bad', type = 'int', 469 parser.add_option('-b', '--bad', type = 'int',
465 help = 'The bad revision to bisect to.') 470 help = 'The bad revision to bisect to.')
466 parser.add_option('-g', '--good', type = 'int', 471 parser.add_option('-g', '--good', type = 'int',
467 help = 'The last known good revision to bisect from.') 472 help = 'The last known good revision to bisect from.')
468 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 473 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
469 help = 'Profile to use; this will not reset every run. ' + 474 help = 'Profile to use; this will not reset every run. ' +
470 'Defaults to a clean profile.', default = 'profile') 475 'Defaults to a clean profile.', default = 'profile')
476 parser.add_option('-t', '--times', type = 'int',
477 help = 'Number of times to run each build before asking ' +
478 'if it\'s good or bad. Temporary profiles are reused.',
479 default = 1)
471 (opts, args) = parser.parse_args() 480 (opts, args) = parser.parse_args()
472 481
473 if opts.archive is None: 482 if opts.archive is None:
474 print 'Error: missing required parameter: --archive' 483 print 'Error: missing required parameter: --archive'
475 print 484 print
476 parser.print_help() 485 parser.print_help()
477 return 1 486 return 1
478 487
479 if opts.bad and opts.good and (opts.good > opts.bad): 488 if opts.bad and opts.good and (opts.good > opts.bad):
480 print ('The good revision (%d) must precede the bad revision (%d).\n' % 489 print ('The good revision (%d) must precede the bad revision (%d).\n' %
(...skipping 25 matching lines...) Expand all
506 # Find out when we were good. 515 # Find out when we were good.
507 if opts.good: 516 if opts.good:
508 good_rev = opts.good 517 good_rev = opts.good
509 else: 518 else:
510 good_rev = 0 519 good_rev = 0
511 try: 520 try:
512 good_rev = int(raw_input('Last known good [0]: ')) 521 good_rev = int(raw_input('Last known good [0]: '))
513 except Exception, e: 522 except Exception, e:
514 pass 523 pass
515 524
525 if opts.times < 1:
526 print('Number of times to run (%d) must be greater than or equal to 1.' %
527 opts.times)
528 parser.print_help()
529 return 1
530
516 (last_known_good_rev, first_known_bad_rev) = Bisect( 531 (last_known_good_rev, first_known_bad_rev) = Bisect(
517 opts.archive, good_rev, bad_rev, args, opts.profile) 532 opts.archive, good_rev, bad_rev, opts.times, args, opts.profile)
518 533
519 # Get corresponding webkit revisions. 534 # Get corresponding webkit revisions.
520 try: 535 try:
521 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision( 536 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision(
522 last_known_good_rev) 537 last_known_good_rev)
523 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision( 538 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision(
524 first_known_bad_rev) 539 first_known_bad_rev)
525 except Exception, e: 540 except Exception, e:
526 # Silently ignore the failure. 541 # Silently ignore the failure.
527 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0 542 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0
528 543
529 # We're done. Let the user know the results in an official manner. 544 # We're done. Let the user know the results in an official manner.
530 print('You are probably looking for build %d.' % first_known_bad_rev) 545 print('You are probably looking for build %d.' % first_known_bad_rev)
531 if last_known_good_webkit_rev != first_known_bad_webkit_rev: 546 if last_known_good_webkit_rev != first_known_bad_webkit_rev:
532 print 'WEBKIT CHANGELOG URL:' 547 print 'WEBKIT CHANGELOG URL:'
533 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 548 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
534 last_known_good_webkit_rev) 549 last_known_good_webkit_rev)
535 print 'CHANGELOG URL:' 550 print 'CHANGELOG URL:'
536 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 551 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
537 print 'Built at revision:' 552 print 'Built at revision:'
538 print BUILD_VIEWVC_URL % first_known_bad_rev 553 print BUILD_VIEWVC_URL % first_known_bad_rev
539 554
540 555
541 if __name__ == '__main__': 556 if __name__ == '__main__':
542 sys.exit(main()) 557 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