Changeset 220537 in webkit
- Timestamp:
- Aug 10, 2017 11:57:22 AM (7 years ago)
- Location:
- trunk/Tools
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Tools/ChangeLog
r220536 r220537 1 2017-08-10 Lucas Forschler <lforschler@apple.com> 2 3 Fix duplicated code in r220534. 4 5 Unreviewed cleanup. 6 7 * Scripts/bisect-builds: 8 1 9 2017-08-08 Jiewen Tan <jiewen_tan@apple.com> 2 10 -
trunk/Tools/Scripts/bisect-builds
r220534 r220537 241 241 finally: 242 242 shutil.rmtree(webkit_output_dir, ignore_errors=True) 243 #!/usr/bin/env python244 245 # Copyright (C) 2017 Apple Inc. All rights reserved.246 #247 # Redistribution and use in source and binary forms, with or without248 # modification, are permitted provided that the following conditions249 # are met:250 #251 # 1. Redistributions of source code must retain the above copyright252 # notice, this list of conditions and the following disclaimer.253 # 2. Redistributions in binary form must reproduce the above copyright254 # notice, this list of conditions and the following disclaimer in the255 # documentation and/or other materials provided with the distribution.256 # 3. Neither the name of Apple Inc. ("Apple") nor the names of257 # its contributors may be used to endorse or promote products derived258 # from this software without specific prior written permission.259 #260 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY261 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED262 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE263 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY264 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES265 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;266 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND267 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT268 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF269 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.270 271 import argparse272 import bisect273 import math274 import os275 import requests276 import shutil277 import subprocess278 import sys279 import tempfile280 import urlparse281 282 REST_API_URL = 'https://q1tzqfy48e.execute-api.us-west-2.amazonaws.com/v1/'283 REST_API_ENDPOINT = 'archives/'284 REST_API_MINIFIED_ENDPOINT = 'minified-archives/'285 286 287 def bisect_builds(revision_list, start_index, end_index, options):288 while True:289 index_to_test = pick_next_build(revision_list, start_index, end_index)290 if index_to_test == None:291 print('No more builds to test...')292 exit(1)293 download_archive(options, revision_list[index_to_test])294 extract_archive(options)295 reproduces = test_archive(options, revision_list[index_to_test])296 297 if reproduces: # bisect left298 index_to_test -= 1 # We can remove this from the end of the list of builds to test299 bisect_builds(revision_list, start_index, index_to_test, options)300 if not reproduces: # bisect right301 index_to_test += 1 # We can remove this from the start of the list of builds to test302 bisect_builds(revision_list, index_to_test, end_index, options)303 304 305 def download_archive(options, revision):306 api_url = get_api_url(options)307 s3_url = get_s3_location_for_revision(api_url, revision)308 print('Archive URL: {}'.format(s3_url))309 command = ['python', '../BuildSlaveSupport/download-built-product', '--{}'.format(options.configuration), '--platform', options.platform, s3_url]310 print('Downloading revision: {}'.format(revision))311 subprocess.check_call(command)312 313 314 def extract_archive(options):315 command = ['python', '../BuildSlaveSupport/built-product-archive', '--platform', options.platform, '--%s' % options.configuration, 'extract']316 print('Extracting archive: {}'.format(command))317 subprocess.check_call(command)318 319 320 # ---- bisect helpers from https://docs.python.org/2/library/bisect.html ----321 def find_le(a, x):322 """Find rightmost value less than or equal to x"""323 i = bisect.bisect_right(a, x)324 if i:325 return i - 1326 raise ValueError327 328 329 def find_ge(a, x):330 """Find leftmost item greater than or equal to x"""331 i = bisect.bisect_left(a, x)332 if i != len(a):333 return i334 raise ValueError335 # ---- end bisect helpers ----336 337 338 def get_api_url(options):339 if options.full:340 base_url = urlparse.urljoin(REST_API_URL, REST_API_ENDPOINT)341 else:342 base_url = urlparse.urljoin(REST_API_URL, REST_API_MINIFIED_ENDPOINT)343 344 api_url = urlparse.urljoin(base_url, '-'.join([options.platform, options.architecture, options.configuration]))345 return api_url346 347 348 def get_indices_from_revisions(revision_list, start_revision, end_revision):349 if start_revision is None:350 print('WARNING: No starting revision was given, defaulting to first available for this configuration')351 start_index = 0352 else:353 start_index = find_ge(revision_list, start_revision)354 355 if end_revision is None:356 print('WARNING: No ending revision was given, defaulting to last avialable for this configuration')357 end_index = len(revision_list) - 1358 else:359 end_index = find_le(revision_list, end_revision)360 361 return start_index, end_index362 363 364 def get_sorted_revisions(revisions_dict):365 revisions = [int(revision['revision']) for revision in revisions_dict['revisions']]366 return sorted(revisions)367 368 369 def get_s3_location_for_revision(url, revision):370 url = '/'.join([url, str(revision)])371 r = requests.get(url)372 for archive in r.json()['archive']:373 s3_url = archive['s3_url']374 return s3_url375 376 377 def parse_args(args):378 parser = argparse.ArgumentParser(description='Perform a bisection against existing WebKit archives.')379 parser.add_argument('-c', '--configuration', default='release', help='The configuration to query [release | debug]')380 parser.add_argument('-a', '--architecture', default='x86_64', help='The architecture to query [x86_64 | i386]')381 parser.add_argument('-p', '--platform', default='None', required=True, help='The platform to query [mac-sierra | gtk | ios-simulator | win]')382 parser.add_argument('-f', '--full', action='store_true', default=False, help='Use full archives containing debug symbols. These are significantly larger files!')383 parser.add_argument('-s', '--start', default=None, type=int, help='The starting revision to bisect.')384 parser.add_argument('-e', '--end', default=None, type=int, help='The ending revision to bisect')385 return parser.parse_args(args)386 387 388 def pick_next_build(revision_list, start_index, end_index):389 revisions_remaining = (end_index - start_index) + 1390 print('Found {} revisions in this range to test...'.format(revisions_remaining))391 392 if start_index >= end_index:393 print('No archives available between {} and {}'.format(revision_list[end_index], revision_list[start_index]))394 return None395 396 middleIndex = (start_index + end_index) / 2397 return int(math.ceil(middleIndex))398 399 400 def prompt_did_reproduce():401 var = raw_input('\nDid the error reproduce? [y/n]: ')402 var = var.lower()403 if 'y' in var:404 return True405 if 'n' in var:406 return False407 else:408 prompt_did_reproduce()409 410 411 def set_webkit_output_dir(temp_dir):412 print('Setting environment variable WEBKIT_OUTPUTDIR to {}'.format(temp_dir))413 os.environ['WEBKIT_OUTPUTDIR'] = temp_dir414 415 416 def test_archive(options, revision):417 print('Testing revision {}...'.format(revision))418 command = []419 if 'mac' in options.platform:420 command = ['./run-safari']421 elif 'ios' in options.platform:422 command = ['./run-safari', '--simulator']423 else:424 print('Default test behavior for this platform is not implemented...'.format(options.platform))425 426 if command:427 subprocess.call(command)428 return prompt_did_reproduce()429 430 431 def minified_platforms():432 # FIXME: query this dynamically from API433 return ['mac-elcapitan', 'mac-sierra']434 435 436 def unminified_platforms():437 # FIXME: query this dynamically from API438 return ['gtk', 'ios-simulator-10', 'mac-elcapitan', 'mac-sierra', 'win', 'wpe']439 440 441 def is_supported_platform(options):442 if options.full:443 return options.platform in unminified_platforms()444 return options.platform in minified_platforms()445 446 447 def validate_options(options):448 if not is_supported_platform(options):449 print('Unsupported platform: [{}], exiting...'.format(options.platform))450 if options.full:451 print('Available Unminified platforms: {}'.format(unminified_platforms()))452 else:453 print('Available Minified platforms: {}'.format(minified_platforms()))454 print('INFO: pass --full to try against full archives')455 exit(1)456 457 458 def main(options):459 validate_options(options)460 461 url = get_api_url(options)462 r = requests.get(url)463 revision_list = get_sorted_revisions(r.json())464 465 start_index, end_index = get_indices_from_revisions(revision_list, options.start, options.end)466 print('Bisecting between {} and {}'.format(revision_list[start_index], revision_list[end_index]))467 468 # from here forward, use indices instead of revisions469 bisect_builds(revision_list, start_index, end_index, options)470 471 472 if __name__ == '__main__':473 options = parse_args(sys.argv[1:])474 script_path = os.path.abspath(__file__)475 script_directory = os.path.dirname(script_path)476 os.chdir(script_directory)477 webkit_output_dir = tempfile.mkdtemp()478 set_webkit_output_dir(webkit_output_dir)479 try:480 main(options)481 except KeyboardInterrupt:482 exit("Aborting.")483 finally:484 shutil.rmtree(webkit_output_dir, ignore_errors=True)485 #!/usr/bin/env python486 487 # Copyright (C) 2017 Apple Inc. All rights reserved.488 #489 # Redistribution and use in source and binary forms, with or without490 # modification, are permitted provided that the following conditions491 # are met:492 #493 # 1. Redistributions of source code must retain the above copyright494 # notice, this list of conditions and the following disclaimer.495 # 2. Redistributions in binary form must reproduce the above copyright496 # notice, this list of conditions and the following disclaimer in the497 # documentation and/or other materials provided with the distribution.498 # 3. Neither the name of Apple Inc. ("Apple") nor the names of499 # its contributors may be used to endorse or promote products derived500 # from this software without specific prior written permission.501 #502 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY503 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED504 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE505 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY506 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES507 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;508 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND509 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT510 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF511 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.512 513 import argparse514 import bisect515 import math516 import os517 import requests518 import shutil519 import subprocess520 import sys521 import tempfile522 import urlparse523 524 REST_API_URL = 'https://q1tzqfy48e.execute-api.us-west-2.amazonaws.com/v1/'525 REST_API_ENDPOINT = 'archives/'526 REST_API_MINIFIED_ENDPOINT = 'minified-archives/'527 528 529 def bisect_builds(revision_list, start_index, end_index, options):530 while True:531 index_to_test = pick_next_build(revision_list, start_index, end_index)532 if index_to_test == None:533 print('No more builds to test...')534 exit(1)535 download_archive(options, revision_list[index_to_test])536 extract_archive(options)537 reproduces = test_archive(options, revision_list[index_to_test])538 539 if reproduces: # bisect left540 index_to_test -= 1 # We can remove this from the end of the list of builds to test541 bisect_builds(revision_list, start_index, index_to_test, options)542 if not reproduces: # bisect right543 index_to_test += 1 # We can remove this from the start of the list of builds to test544 bisect_builds(revision_list, index_to_test, end_index, options)545 546 547 def download_archive(options, revision):548 api_url = get_api_url(options)549 s3_url = get_s3_location_for_revision(api_url, revision)550 print('Archive URL: {}'.format(s3_url))551 command = ['python', '../BuildSlaveSupport/download-built-product', '--{}'.format(options.configuration), '--platform', options.platform, s3_url]552 print('Downloading revision: {}'.format(revision))553 subprocess.check_call(command)554 555 556 def extract_archive(options):557 command = ['python', '../BuildSlaveSupport/built-product-archive', '--platform', options.platform, '--%s' % options.configuration, 'extract']558 print('Extracting archive: {}'.format(command))559 subprocess.check_call(command)560 561 562 # ---- bisect helpers from https://docs.python.org/2/library/bisect.html ----563 def find_le(a, x):564 """Find rightmost value less than or equal to x"""565 i = bisect.bisect_right(a, x)566 if i:567 return i - 1568 raise ValueError569 570 571 def find_ge(a, x):572 """Find leftmost item greater than or equal to x"""573 i = bisect.bisect_left(a, x)574 if i != len(a):575 return i576 raise ValueError577 # ---- end bisect helpers ----578 579 580 def get_api_url(options):581 if options.full:582 base_url = urlparse.urljoin(REST_API_URL, REST_API_ENDPOINT)583 else:584 base_url = urlparse.urljoin(REST_API_URL, REST_API_MINIFIED_ENDPOINT)585 586 api_url = urlparse.urljoin(base_url, '-'.join([options.platform, options.architecture, options.configuration]))587 return api_url588 589 590 def get_indices_from_revisions(revision_list, start_revision, end_revision):591 if start_revision is None:592 print('WARNING: No starting revision was given, defaulting to first available for this configuration')593 start_index = 0594 else:595 start_index = find_ge(revision_list, start_revision)596 597 if end_revision is None:598 print('WARNING: No ending revision was given, defaulting to last avialable for this configuration')599 end_index = len(revision_list) - 1600 else:601 end_index = find_le(revision_list, end_revision)602 603 return start_index, end_index604 605 606 def get_sorted_revisions(revisions_dict):607 revisions = [int(revision['revision']) for revision in revisions_dict['revisions']]608 return sorted(revisions)609 610 611 def get_s3_location_for_revision(url, revision):612 url = '/'.join([url, str(revision)])613 r = requests.get(url)614 for archive in r.json()['archive']:615 s3_url = archive['s3_url']616 return s3_url617 618 619 def parse_args(args):620 parser = argparse.ArgumentParser(description='Perform a bisection against existing WebKit archives.')621 parser.add_argument('-c', '--configuration', default='release', help='The configuration to query [release | debug]')622 parser.add_argument('-a', '--architecture', default='x86_64', help='The architecture to query [x86_64 | i386]')623 parser.add_argument('-p', '--platform', default='None', required=True, help='The platform to query [mac-sierra | gtk | ios-simulator | win]')624 parser.add_argument('-f', '--full', action='store_true', default=False, help='Use full archives containing debug symbols. These are significantly larger files!')625 parser.add_argument('-s', '--start', default=None, type=int, help='The starting revision to bisect.')626 parser.add_argument('-e', '--end', default=None, type=int, help='The ending revision to bisect')627 return parser.parse_args(args)628 629 630 def pick_next_build(revision_list, start_index, end_index):631 revisions_remaining = (end_index - start_index) + 1632 print('Found {} revisions in this range to test...'.format(revisions_remaining))633 634 if start_index >= end_index:635 print('No archives available between {} and {}'.format(revision_list[end_index], revision_list[start_index]))636 return None637 638 middleIndex = (start_index + end_index) / 2639 return int(math.ceil(middleIndex))640 641 642 def prompt_did_reproduce():643 var = raw_input('\nDid the error reproduce? [y/n]: ')644 var = var.lower()645 if 'y' in var:646 return True647 if 'n' in var:648 return False649 else:650 prompt_did_reproduce()651 652 653 def set_webkit_output_dir(temp_dir):654 print('Setting environment variable WEBKIT_OUTPUTDIR to {}'.format(temp_dir))655 os.environ['WEBKIT_OUTPUTDIR'] = temp_dir656 657 658 def test_archive(options, revision):659 print('Testing revision {}...'.format(revision))660 command = []661 if 'mac' in options.platform:662 command = ['./run-safari']663 elif 'ios' in options.platform:664 command = ['./run-safari', '--simulator']665 else:666 print('Default test behavior for this platform is not implemented...'.format(options.platform))667 668 if command:669 subprocess.call(command)670 return prompt_did_reproduce()671 672 673 def minified_platforms():674 # FIXME: query this dynamically from API675 return ['mac-elcapitan', 'mac-sierra']676 677 678 def unminified_platforms():679 # FIXME: query this dynamically from API680 return ['gtk', 'ios-simulator-10', 'mac-elcapitan', 'mac-sierra', 'win', 'wpe']681 682 683 def is_supported_platform(options):684 if options.full:685 return options.platform in unminified_platforms()686 return options.platform in minified_platforms()687 688 689 def validate_options(options):690 if not is_supported_platform(options):691 print('Unsupported platform: [{}], exiting...'.format(options.platform))692 if options.full:693 print('Available Unminified platforms: {}'.format(unminified_platforms()))694 else:695 print('Available Minified platforms: {}'.format(minified_platforms()))696 print('INFO: pass --full to try against full archives')697 exit(1)698 699 700 def main(options):701 validate_options(options)702 703 url = get_api_url(options)704 r = requests.get(url)705 revision_list = get_sorted_revisions(r.json())706 707 start_index, end_index = get_indices_from_revisions(revision_list, options.start, options.end)708 print('Bisecting between {} and {}'.format(revision_list[start_index], revision_list[end_index]))709 710 # from here forward, use indices instead of revisions711 bisect_builds(revision_list, start_index, end_index, options)712 713 714 if __name__ == '__main__':715 options = parse_args(sys.argv[1:])716 script_path = os.path.abspath(__file__)717 script_directory = os.path.dirname(script_path)718 os.chdir(script_directory)719 webkit_output_dir = tempfile.mkdtemp()720 set_webkit_output_dir(webkit_output_dir)721 try:722 main(options)723 except KeyboardInterrupt:724 exit("Aborting.")725 finally:726 shutil.rmtree(webkit_output_dir, ignore_errors=True)
Note: See TracChangeset
for help on using the changeset viewer.