Modified: trunk/Tools/Scripts/bisect-builds (220536 => 220537)
--- trunk/Tools/Scripts/bisect-builds 2017-08-10 18:47:56 UTC (rev 220536)
+++ trunk/Tools/Scripts/bisect-builds 2017-08-10 18:57:22 UTC (rev 220537)
@@ -240,487 +240,3 @@
exit("Aborting.")
finally:
shutil.rmtree(webkit_output_dir, ignore_errors=True)
-#!/usr/bin/env python
-
-# Copyright (C) 2017 Apple Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3. Neither the name of Apple Inc. ("Apple") nor the names of
-# its contributors may be used to endorse or promote products derived
-# from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
-# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import argparse
-import bisect
-import math
-import os
-import requests
-import shutil
-import subprocess
-import sys
-import tempfile
-import urlparse
-
-REST_API_URL = 'https://q1tzqfy48e.execute-api.us-west-2.amazonaws.com/v1/'
-REST_API_ENDPOINT = 'archives/'
-REST_API_MINIFIED_ENDPOINT = 'minified-archives/'
-
-
-def bisect_builds(revision_list, start_index, end_index, options):
- while True:
- index_to_test = pick_next_build(revision_list, start_index, end_index)
- if index_to_test == None:
- print('No more builds to test...')
- exit(1)
- download_archive(options, revision_list[index_to_test])
- extract_archive(options)
- reproduces = test_archive(options, revision_list[index_to_test])
-
- if reproduces: # bisect left
- index_to_test -= 1 # We can remove this from the end of the list of builds to test
- bisect_builds(revision_list, start_index, index_to_test, options)
- if not reproduces: # bisect right
- index_to_test += 1 # We can remove this from the start of the list of builds to test
- bisect_builds(revision_list, index_to_test, end_index, options)
-
-
-def download_archive(options, revision):
- api_url = get_api_url(options)
- s3_url = get_s3_location_for_revision(api_url, revision)
- print('Archive URL: {}'.format(s3_url))
- command = ['python', '../BuildSlaveSupport/download-built-product', '--{}'.format(options.configuration), '--platform', options.platform, s3_url]
- print('Downloading revision: {}'.format(revision))
- subprocess.check_call(command)
-
-
-def extract_archive(options):
- command = ['python', '../BuildSlaveSupport/built-product-archive', '--platform', options.platform, '--%s' % options.configuration, 'extract']
- print('Extracting archive: {}'.format(command))
- subprocess.check_call(command)
-
-
-# ---- bisect helpers from https://docs.python.org/2/library/bisect.html ----
-def find_le(a, x):
- """Find rightmost value less than or equal to x"""
- i = bisect.bisect_right(a, x)
- if i:
- return i - 1
- raise ValueError
-
-
-def find_ge(a, x):
- """Find leftmost item greater than or equal to x"""
- i = bisect.bisect_left(a, x)
- if i != len(a):
- return i
- raise ValueError
-# ---- end bisect helpers ----
-
-
-def get_api_url(options):
- if options.full:
- base_url = urlparse.urljoin(REST_API_URL, REST_API_ENDPOINT)
- else:
- base_url = urlparse.urljoin(REST_API_URL, REST_API_MINIFIED_ENDPOINT)
-
- api_url = urlparse.urljoin(base_url, '-'.join([options.platform, options.architecture, options.configuration]))
- return api_url
-
-
-def get_indices_from_revisions(revision_list, start_revision, end_revision):
- if start_revision is None:
- print('WARNING: No starting revision was given, defaulting to first available for this configuration')
- start_index = 0
- else:
- start_index = find_ge(revision_list, start_revision)
-
- if end_revision is None:
- print('WARNING: No ending revision was given, defaulting to last avialable for this configuration')
- end_index = len(revision_list) - 1
- else:
- end_index = find_le(revision_list, end_revision)
-
- return start_index, end_index
-
-
-def get_sorted_revisions(revisions_dict):
- revisions = [int(revision['revision']) for revision in revisions_dict['revisions']]
- return sorted(revisions)
-
-
-def get_s3_location_for_revision(url, revision):
- url = ''.join([url, str(revision)])
- r = requests.get(url)
- for archive in r.json()['archive']:
- s3_url = archive['s3_url']
- return s3_url
-
-
-def parse_args(args):
- parser = argparse.ArgumentParser(description='Perform a bisection against existing WebKit archives.')
- parser.add_argument('-c', '--configuration', default='release', help='The configuration to query [release | debug]')
- parser.add_argument('-a', '--architecture', default='x86_64', help='The architecture to query [x86_64 | i386]')
- parser.add_argument('-p', '--platform', default='None', required=True, help='The platform to query [mac-sierra | gtk | ios-simulator | win]')
- parser.add_argument('-f', '--full', action='', default=False, help='Use full archives containing debug symbols. These are significantly larger files!')
- parser.add_argument('-s', '--start', default=None, type=int, help='The starting revision to bisect.')
- parser.add_argument('-e', '--end', default=None, type=int, help='The ending revision to bisect')
- return parser.parse_args(args)
-
-
-def pick_next_build(revision_list, start_index, end_index):
- revisions_remaining = (end_index - start_index) + 1
- print('Found {} revisions in this range to test...'.format(revisions_remaining))
-
- if start_index >= end_index:
- print('No archives available between {} and {}'.format(revision_list[end_index], revision_list[start_index]))
- return None
-
- middleIndex = (start_index + end_index) / 2
- return int(math.ceil(middleIndex))
-
-
-def prompt_did_reproduce():
- var = raw_input('\nDid the error reproduce? [y/n]: ')
- var = var.lower()
- if 'y' in var:
- return True
- if 'n' in var:
- return False
- else:
- prompt_did_reproduce()
-
-
-def set_webkit_output_dir(temp_dir):
- print('Setting environment variable WEBKIT_OUTPUTDIR to {}'.format(temp_dir))
- os.environ['WEBKIT_OUTPUTDIR'] = temp_dir
-
-
-def test_archive(options, revision):
- print('Testing revision {}...'.format(revision))
- command = []
- if 'mac' in options.platform:
- command = ['./run-safari']
- elif 'ios' in options.platform:
- command = ['./run-safari', '--simulator']
- else:
- print('Default test behavior for this platform is not implemented...'.format(options.platform))
-
- if command:
- subprocess.call(command)
- return prompt_did_reproduce()
-
-
-def minified_platforms():
- # FIXME: query this dynamically from API
- return ['mac-elcapitan', 'mac-sierra']
-
-
-def unminified_platforms():
- # FIXME: query this dynamically from API
- return ['gtk', 'ios-simulator-10', 'mac-elcapitan', 'mac-sierra', 'win', 'wpe']
-
-
-def is_supported_platform(options):
- if options.full:
- return options.platform in unminified_platforms()
- return options.platform in minified_platforms()
-
-
-def validate_options(options):
- if not is_supported_platform(options):
- print('Unsupported platform: [{}], exiting...'.format(options.platform))
- if options.full:
- print('Available Unminified platforms: {}'.format(unminified_platforms()))
- else:
- print('Available Minified platforms: {}'.format(minified_platforms()))
- print('INFO: pass --full to try against full archives')
- exit(1)
-
-
-def main(options):
- validate_options(options)
-
- url = ""
- r = requests.get(url)
- revision_list = get_sorted_revisions(r.json())
-
- start_index, end_index = get_indices_from_revisions(revision_list, options.start, options.end)
- print('Bisecting between {} and {}'.format(revision_list[start_index], revision_list[end_index]))
-
- # from here forward, use indices instead of revisions
- bisect_builds(revision_list, start_index, end_index, options)
-
-
-if __name__ == '__main__':
- options = parse_args(sys.argv[1:])
- script_path = os.path.abspath(__file__)
- script_directory = os.path.dirname(script_path)
- os.chdir(script_directory)
- webkit_output_dir = tempfile.mkdtemp()
- set_webkit_output_dir(webkit_output_dir)
- try:
- main(options)
- except KeyboardInterrupt:
- exit("Aborting.")
- finally:
- shutil.rmtree(webkit_output_dir, ignore_errors=True)
-#!/usr/bin/env python
-
-# Copyright (C) 2017 Apple Inc. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-# 3. Neither the name of Apple Inc. ("Apple") nor the names of
-# its contributors may be used to endorse or promote products derived
-# from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
-# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import argparse
-import bisect
-import math
-import os
-import requests
-import shutil
-import subprocess
-import sys
-import tempfile
-import urlparse
-
-REST_API_URL = 'https://q1tzqfy48e.execute-api.us-west-2.amazonaws.com/v1/'
-REST_API_ENDPOINT = 'archives/'
-REST_API_MINIFIED_ENDPOINT = 'minified-archives/'
-
-
-def bisect_builds(revision_list, start_index, end_index, options):
- while True:
- index_to_test = pick_next_build(revision_list, start_index, end_index)
- if index_to_test == None:
- print('No more builds to test...')
- exit(1)
- download_archive(options, revision_list[index_to_test])
- extract_archive(options)
- reproduces = test_archive(options, revision_list[index_to_test])
-
- if reproduces: # bisect left
- index_to_test -= 1 # We can remove this from the end of the list of builds to test
- bisect_builds(revision_list, start_index, index_to_test, options)
- if not reproduces: # bisect right
- index_to_test += 1 # We can remove this from the start of the list of builds to test
- bisect_builds(revision_list, index_to_test, end_index, options)
-
-
-def download_archive(options, revision):
- api_url = get_api_url(options)
- s3_url = get_s3_location_for_revision(api_url, revision)
- print('Archive URL: {}'.format(s3_url))
- command = ['python', '../BuildSlaveSupport/download-built-product', '--{}'.format(options.configuration), '--platform', options.platform, s3_url]
- print('Downloading revision: {}'.format(revision))
- subprocess.check_call(command)
-
-
-def extract_archive(options):
- command = ['python', '../BuildSlaveSupport/built-product-archive', '--platform', options.platform, '--%s' % options.configuration, 'extract']
- print('Extracting archive: {}'.format(command))
- subprocess.check_call(command)
-
-
-# ---- bisect helpers from https://docs.python.org/2/library/bisect.html ----
-def find_le(a, x):
- """Find rightmost value less than or equal to x"""
- i = bisect.bisect_right(a, x)
- if i:
- return i - 1
- raise ValueError
-
-
-def find_ge(a, x):
- """Find leftmost item greater than or equal to x"""
- i = bisect.bisect_left(a, x)
- if i != len(a):
- return i
- raise ValueError
-# ---- end bisect helpers ----
-
-
-def get_api_url(options):
- if options.full:
- base_url = urlparse.urljoin(REST_API_URL, REST_API_ENDPOINT)
- else:
- base_url = urlparse.urljoin(REST_API_URL, REST_API_MINIFIED_ENDPOINT)
-
- api_url = urlparse.urljoin(base_url, '-'.join([options.platform, options.architecture, options.configuration]))
- return api_url
-
-
-def get_indices_from_revisions(revision_list, start_revision, end_revision):
- if start_revision is None:
- print('WARNING: No starting revision was given, defaulting to first available for this configuration')
- start_index = 0
- else:
- start_index = find_ge(revision_list, start_revision)
-
- if end_revision is None:
- print('WARNING: No ending revision was given, defaulting to last avialable for this configuration')
- end_index = len(revision_list) - 1
- else:
- end_index = find_le(revision_list, end_revision)
-
- return start_index, end_index
-
-
-def get_sorted_revisions(revisions_dict):
- revisions = [int(revision['revision']) for revision in revisions_dict['revisions']]
- return sorted(revisions)
-
-
-def get_s3_location_for_revision(url, revision):
- url = ''.join([url, str(revision)])
- r = requests.get(url)
- for archive in r.json()['archive']:
- s3_url = archive['s3_url']
- return s3_url
-
-
-def parse_args(args):
- parser = argparse.ArgumentParser(description='Perform a bisection against existing WebKit archives.')
- parser.add_argument('-c', '--configuration', default='release', help='The configuration to query [release | debug]')
- parser.add_argument('-a', '--architecture', default='x86_64', help='The architecture to query [x86_64 | i386]')
- parser.add_argument('-p', '--platform', default='None', required=True, help='The platform to query [mac-sierra | gtk | ios-simulator | win]')
- parser.add_argument('-f', '--full', action='', default=False, help='Use full archives containing debug symbols. These are significantly larger files!')
- parser.add_argument('-s', '--start', default=None, type=int, help='The starting revision to bisect.')
- parser.add_argument('-e', '--end', default=None, type=int, help='The ending revision to bisect')
- return parser.parse_args(args)
-
-
-def pick_next_build(revision_list, start_index, end_index):
- revisions_remaining = (end_index - start_index) + 1
- print('Found {} revisions in this range to test...'.format(revisions_remaining))
-
- if start_index >= end_index:
- print('No archives available between {} and {}'.format(revision_list[end_index], revision_list[start_index]))
- return None
-
- middleIndex = (start_index + end_index) / 2
- return int(math.ceil(middleIndex))
-
-
-def prompt_did_reproduce():
- var = raw_input('\nDid the error reproduce? [y/n]: ')
- var = var.lower()
- if 'y' in var:
- return True
- if 'n' in var:
- return False
- else:
- prompt_did_reproduce()
-
-
-def set_webkit_output_dir(temp_dir):
- print('Setting environment variable WEBKIT_OUTPUTDIR to {}'.format(temp_dir))
- os.environ['WEBKIT_OUTPUTDIR'] = temp_dir
-
-
-def test_archive(options, revision):
- print('Testing revision {}...'.format(revision))
- command = []
- if 'mac' in options.platform:
- command = ['./run-safari']
- elif 'ios' in options.platform:
- command = ['./run-safari', '--simulator']
- else:
- print('Default test behavior for this platform is not implemented...'.format(options.platform))
-
- if command:
- subprocess.call(command)
- return prompt_did_reproduce()
-
-
-def minified_platforms():
- # FIXME: query this dynamically from API
- return ['mac-elcapitan', 'mac-sierra']
-
-
-def unminified_platforms():
- # FIXME: query this dynamically from API
- return ['gtk', 'ios-simulator-10', 'mac-elcapitan', 'mac-sierra', 'win', 'wpe']
-
-
-def is_supported_platform(options):
- if options.full:
- return options.platform in unminified_platforms()
- return options.platform in minified_platforms()
-
-
-def validate_options(options):
- if not is_supported_platform(options):
- print('Unsupported platform: [{}], exiting...'.format(options.platform))
- if options.full:
- print('Available Unminified platforms: {}'.format(unminified_platforms()))
- else:
- print('Available Minified platforms: {}'.format(minified_platforms()))
- print('INFO: pass --full to try against full archives')
- exit(1)
-
-
-def main(options):
- validate_options(options)
-
- url = ""
- r = requests.get(url)
- revision_list = get_sorted_revisions(r.json())
-
- start_index, end_index = get_indices_from_revisions(revision_list, options.start, options.end)
- print('Bisecting between {} and {}'.format(revision_list[start_index], revision_list[end_index]))
-
- # from here forward, use indices instead of revisions
- bisect_builds(revision_list, start_index, end_index, options)
-
-
-if __name__ == '__main__':
- options = parse_args(sys.argv[1:])
- script_path = os.path.abspath(__file__)
- script_directory = os.path.dirname(script_path)
- os.chdir(script_directory)
- webkit_output_dir = tempfile.mkdtemp()
- set_webkit_output_dir(webkit_output_dir)
- try:
- main(options)
- except KeyboardInterrupt:
- exit("Aborting.")
- finally:
- shutil.rmtree(webkit_output_dir, ignore_errors=True)