blob: 932603e1fe41f078dc5f666e7efdf30363492fa1 [file] [log] [blame]
[email protected]cb155a82011-11-29 17:25:341#!/usr/bin/env python
[email protected]5e93cf162012-01-28 02:16:562# Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]67e0bc62009-09-03 22:06:093# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Snapshot Build Bisect Tool
7
[email protected]7ad66a72009-09-04 17:52:338This script bisects a snapshot archive using binary search. It starts at
[email protected]67e0bc62009-09-03 22:06:099a bad revision (it will try to guess HEAD) and asks for a last known-good
10revision. It will then binary search across this revision range by downloading,
11unzipping, and opening Chromium for you. After testing the specific revision,
12it will ask you whether it is good or bad before continuing the search.
[email protected]67e0bc62009-09-03 22:06:0913"""
14
Raul Tambre57e09d62019-09-22 17:18:5215from __future__ import print_function
16
[email protected]4df583c2014-07-31 17:11:5517# The base URL for stored build archives.
18CHROMIUM_BASE_URL = ('http://commondatastorage.googleapis.com'
19 '/chromium-browser-snapshots')
20WEBKIT_BASE_URL = ('http://commondatastorage.googleapis.com'
21 '/chromium-webkit-snapshots')
[email protected]011886692014-08-01 21:00:2122ASAN_BASE_URL = ('http://commondatastorage.googleapis.com'
23 '/chromium-browser-asan')
[email protected]67e0bc62009-09-03 22:06:0924
[email protected]4df583c2014-07-31 17:11:5525# URL template for viewing changelogs between revisions.
pshenoy9ce271f2014-09-02 22:14:0526CHANGELOG_URL = ('https://chromium.googlesource.com/chromium/src/+log/%s..%s')
27
28# URL to convert SVN revision to git hash.
pshenoy13cb79e02014-09-05 01:42:5329CRREV_URL = ('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/')
[email protected]f6a71a72009-10-08 19:55:3830
[email protected]b2fe7f22011-10-25 22:58:3131# DEPS file URL.
Di Mu08c59682016-07-11 23:05:0732DEPS_FILE = ('https://chromium.googlesource.com/chromium/src/+/%s/DEPS')
[email protected]b2fe7f22011-10-25 22:58:3133
[email protected]4df583c2014-07-31 17:11:5534# Blink changelogs URL.
35BLINK_CHANGELOG_URL = ('http://build.chromium.org'
36 '/f/chromium/perf/dashboard/ui/changelog_blink.html'
37 '?url=/trunk&range=%d%%3A%d')
38
39DONE_MESSAGE_GOOD_MIN = ('You are probably looking for a change made after %s ('
40 'known good), but no later than %s (first known bad).')
41DONE_MESSAGE_GOOD_MAX = ('You are probably looking for a change made after %s ('
42 'known bad), but no later than %s (first known good).')
[email protected]05ff3fd2012-04-17 23:24:0643
[email protected]3e7c85322014-06-27 20:27:3644CHROMIUM_GITHASH_TO_SVN_URL = (
45 'https://chromium.googlesource.com/chromium/src/+/%s?format=json')
[email protected]4df583c2014-07-31 17:11:5546
[email protected]3e7c85322014-06-27 20:27:3647BLINK_GITHASH_TO_SVN_URL = (
48 'https://chromium.googlesource.com/chromium/blink/+/%s?format=json')
[email protected]4df583c2014-07-31 17:11:5549
50GITHASH_TO_SVN_URL = {
51 'chromium': CHROMIUM_GITHASH_TO_SVN_URL,
52 'blink': BLINK_GITHASH_TO_SVN_URL,
53}
54
Bruce Dawsoncd63ea22020-10-26 16:37:4155VERSION_HISTORY_URL = ('https://versionhistory.googleapis.com/v1/chrome'
56 '/platforms/win/channels/stable/versions/all/releases')
57
58OMAHA_REVISIONS_URL = ('https://omahaproxy.appspot.com/deps.json?version=%s')
59
[email protected]4df583c2014-07-31 17:11:5560# Search pattern to be matched in the JSON output from
[email protected]3e7c85322014-06-27 20:27:3661# CHROMIUM_GITHASH_TO_SVN_URL to get the chromium revision (svn revision).
pshenoyb23a1452014-09-05 22:52:0562CHROMIUM_SEARCH_PATTERN_OLD = (
[email protected]3e7c85322014-06-27 20:27:3663 r'.*git-svn-id: svn://svn.chromium.org/chrome/trunk/src@(\d+) ')
pshenoyb23a1452014-09-05 22:52:0564CHROMIUM_SEARCH_PATTERN = (
65 r'Cr-Commit-Position: refs/heads/master@{#(\d+)}')
[email protected]4df583c2014-07-31 17:11:5566
[email protected]3e7c85322014-06-27 20:27:3667# Search pattern to be matched in the json output from
68# BLINK_GITHASH_TO_SVN_URL to get the blink revision (svn revision).
69BLINK_SEARCH_PATTERN = (
70 r'.*git-svn-id: svn://svn.chromium.org/blink/trunk@(\d+) ')
[email protected]4df583c2014-07-31 17:11:5571
72SEARCH_PATTERN = {
73 'chromium': CHROMIUM_SEARCH_PATTERN,
74 'blink': BLINK_SEARCH_PATTERN,
75}
[email protected]3e7c85322014-06-27 20:27:3676
[email protected]480369782014-08-22 20:15:5877CREDENTIAL_ERROR_MESSAGE = ('You are attempting to access protected data with '
78 'no configured credentials')
79
[email protected]67e0bc62009-09-03 22:06:0980###############################################################################
81
Dominic Mazzoni215e80b2017-11-29 20:05:2782import glob
[email protected]83048502014-08-21 16:48:4483import httplib
[email protected]4c6fec6b2013-09-17 17:44:0884import json
[email protected]7ad66a72009-09-04 17:52:3385import optparse
[email protected]67e0bc62009-09-03 22:06:0986import os
87import re
[email protected]61ea90a2013-09-26 10:17:3488import shlex
[email protected]67e0bc62009-09-03 22:06:0989import shutil
[email protected]afe30662011-07-30 01:05:5290import subprocess
[email protected]67e0bc62009-09-03 22:06:0991import sys
[email protected]7ad66a72009-09-04 17:52:3392import tempfile
[email protected]afe30662011-07-30 01:05:5293import threading
[email protected]67e0bc62009-09-03 22:06:0994import urllib
[email protected]d0149c5c2012-05-29 21:12:1195from distutils.version import LooseVersion
[email protected]183706d92011-06-10 13:06:2296from xml.etree import ElementTree
[email protected]bd8dcb92010-03-31 01:05:2497import zipfile
98
[email protected]cb155a82011-11-29 17:25:3499
[email protected]183706d92011-06-10 13:06:22100class PathContext(object):
101 """A PathContext is used to carry the information used to construct URLs and
102 paths when dealing with the storage server and archives."""
[email protected]4c6fec6b2013-09-17 17:44:08103 def __init__(self, base_url, platform, good_revision, bad_revision,
Jason Kersey97bb027a2016-05-11 20:10:43104 is_asan, use_local_cache, flash_path = None):
[email protected]183706d92011-06-10 13:06:22105 super(PathContext, self).__init__()
106 # Store off the input parameters.
[email protected]4c6fec6b2013-09-17 17:44:08107 self.base_url = base_url
[email protected]183706d92011-06-10 13:06:22108 self.platform = platform # What's passed in to the '-a/--archive' option.
109 self.good_revision = good_revision
110 self.bad_revision = bad_revision
[email protected]011886692014-08-01 21:00:21111 self.is_asan = is_asan
112 self.build_type = 'release'
[email protected]fc3702e2013-11-09 04:23:00113 self.flash_path = flash_path
[email protected]3e7c85322014-06-27 20:27:36114 # Dictionary which stores svn revision number as key and it's
115 # corresponding git hash as value. This data is populated in
116 # _FetchAndParse and used later in GetDownloadURL while downloading
117 # the build.
118 self.githash_svn_dict = {}
[email protected]183706d92011-06-10 13:06:22119 # The name of the ZIP file in a revision directory on the server.
120 self.archive_name = None
121
rob724c9062015-01-22 00:26:42122 # Whether to cache and use the list of known revisions in a local file to
123 # speed up the initialization of the script at the next run.
124 self.use_local_cache = use_local_cache
125
126 # Locate the local checkout to speed up the script by using locally stored
127 # metadata.
128 abs_file_path = os.path.abspath(os.path.realpath(__file__))
129 local_src_path = os.path.join(os.path.dirname(abs_file_path), '..')
130 if abs_file_path.endswith(os.path.join('tools', 'bisect-builds.py')) and\
131 os.path.exists(os.path.join(local_src_path, '.git')):
132 self.local_src_path = os.path.normpath(local_src_path)
133 else:
134 self.local_src_path = None
[email protected]6a7a5d62014-07-09 04:45:50135
[email protected]183706d92011-06-10 13:06:22136 # Set some internal members:
137 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'.
138 # _archive_extract_dir = Uncompressed directory in the archive_name file.
139 # _binary_name = The name of the executable to run.
dmazzoni76e907d2015-01-22 08:14:49140 if self.platform in ('linux', 'linux64', 'linux-arm', 'chromeos'):
[email protected]183706d92011-06-10 13:06:22141 self._binary_name = 'chrome'
[email protected]480369782014-08-22 20:15:58142 elif self.platform in ('mac', 'mac64'):
[email protected]183706d92011-06-10 13:06:22143 self.archive_name = 'chrome-mac.zip'
144 self._archive_extract_dir = 'chrome-mac'
[email protected]480369782014-08-22 20:15:58145 elif self.platform in ('win', 'win64'):
Dominic Mazzonie84e40b2018-10-08 06:44:45146 # Note: changed at revision 591483; see GetDownloadURL and GetLaunchPath
147 # below where these are patched.
[email protected]183706d92011-06-10 13:06:22148 self.archive_name = 'chrome-win32.zip'
149 self._archive_extract_dir = 'chrome-win32'
150 self._binary_name = 'chrome.exe'
151 else:
[email protected]afe30662011-07-30 01:05:52152 raise Exception('Invalid platform: %s' % self.platform)
[email protected]183706d92011-06-10 13:06:22153
Jason Kersey97bb027a2016-05-11 20:10:43154 if self.platform in ('linux', 'linux64', 'linux-arm', 'chromeos'):
Dominic Mazzonie84e40b2018-10-08 06:44:45155 # Note: changed at revision 591483; see GetDownloadURL and GetLaunchPath
156 # below where these are patched.
Jason Kersey97bb027a2016-05-11 20:10:43157 self.archive_name = 'chrome-linux.zip'
158 self._archive_extract_dir = 'chrome-linux'
[email protected]d0149c5c2012-05-29 21:12:11159 if self.platform == 'linux':
Jason Kersey97bb027a2016-05-11 20:10:43160 self._listing_platform_dir = 'Linux/'
[email protected]d0149c5c2012-05-29 21:12:11161 elif self.platform == 'linux64':
Jason Kersey97bb027a2016-05-11 20:10:43162 self._listing_platform_dir = 'Linux_x64/'
163 elif self.platform == 'linux-arm':
164 self._listing_platform_dir = 'Linux_ARM_Cross-Compile/'
165 elif self.platform == 'chromeos':
166 self._listing_platform_dir = 'Linux_ChromiumOS_Full/'
167 elif self.platform in ('mac', 'mac64'):
168 self._listing_platform_dir = 'Mac/'
169 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
170 elif self.platform == 'win':
171 self._listing_platform_dir = 'Win/'
jiawei.shao734efbc92016-09-23 02:11:45172 elif self.platform == 'win64':
173 self._listing_platform_dir = 'Win_x64/'
[email protected]d0149c5c2012-05-29 21:12:11174
[email protected]011886692014-08-01 21:00:21175 def GetASANPlatformDir(self):
176 """ASAN builds are in directories like "linux-release", or have filenames
177 like "asan-win32-release-277079.zip". This aligns to our platform names
178 except in the case of Windows where they use "win32" instead of "win"."""
179 if self.platform == 'win':
180 return 'win32'
181 else:
182 return self.platform
183
[email protected]183706d92011-06-10 13:06:22184 def GetListingURL(self, marker=None):
185 """Returns the URL for a directory listing, with an optional marker."""
186 marker_param = ''
187 if marker:
188 marker_param = '&marker=' + str(marker)
[email protected]011886692014-08-01 21:00:21189 if self.is_asan:
190 prefix = '%s-%s' % (self.GetASANPlatformDir(), self.build_type)
191 return self.base_url + '/?delimiter=&prefix=' + prefix + marker_param
192 else:
193 return (self.base_url + '/?delimiter=/&prefix=' +
194 self._listing_platform_dir + marker_param)
[email protected]183706d92011-06-10 13:06:22195
196 def GetDownloadURL(self, revision):
197 """Gets the download URL for a build archive of a specific revision."""
[email protected]011886692014-08-01 21:00:21198 if self.is_asan:
199 return '%s/%s-%s/%s-%d.zip' % (
200 ASAN_BASE_URL, self.GetASANPlatformDir(), self.build_type,
201 self.GetASANBaseName(), revision)
Jason Kersey97bb027a2016-05-11 20:10:43202 if str(revision) in self.githash_svn_dict:
203 revision = self.githash_svn_dict[str(revision)]
Dominic Mazzonie84e40b2018-10-08 06:44:45204 archive_name = self.archive_name
205
206 # At revision 591483, the names of two of the archives changed
207 # due to: https://chromium-review.googlesource.com/#/q/1226086
208 # See: http://crbug.com/789612
209 if revision >= 591483:
210 if self.platform == 'chromeos':
211 archive_name = 'chrome-chromeos.zip'
212 elif self.platform in ('win', 'win64'):
213 archive_name = 'chrome-win.zip'
214
Jason Kersey97bb027a2016-05-11 20:10:43215 return '%s/%s%s/%s' % (self.base_url, self._listing_platform_dir,
Dominic Mazzonie84e40b2018-10-08 06:44:45216 revision, archive_name)
[email protected]183706d92011-06-10 13:06:22217
218 def GetLastChangeURL(self):
219 """Returns a URL to the LAST_CHANGE file."""
[email protected]4c6fec6b2013-09-17 17:44:08220 return self.base_url + '/' + self._listing_platform_dir + 'LAST_CHANGE'
[email protected]183706d92011-06-10 13:06:22221
[email protected]011886692014-08-01 21:00:21222 def GetASANBaseName(self):
223 """Returns the base name of the ASAN zip file."""
224 if 'linux' in self.platform:
225 return 'asan-symbolized-%s-%s' % (self.GetASANPlatformDir(),
226 self.build_type)
227 else:
228 return 'asan-%s-%s' % (self.GetASANPlatformDir(), self.build_type)
229
230 def GetLaunchPath(self, revision):
[email protected]183706d92011-06-10 13:06:22231 """Returns a relative path (presumably from the archive extraction location)
232 that is used to run the executable."""
[email protected]011886692014-08-01 21:00:21233 if self.is_asan:
234 extract_dir = '%s-%d' % (self.GetASANBaseName(), revision)
235 else:
236 extract_dir = self._archive_extract_dir
Dominic Mazzonie84e40b2018-10-08 06:44:45237
238 # At revision 591483, the names of two of the archives changed
239 # due to: https://chromium-review.googlesource.com/#/q/1226086
240 # See: http://crbug.com/789612
241 if revision >= 591483:
242 if self.platform == 'chromeos':
243 extract_dir = 'chrome-chromeos'
244 elif self.platform in ('win', 'win64'):
Lei Zhang1c8c6f7e2018-11-09 16:46:30245 extract_dir = 'chrome-win'
Dominic Mazzonie84e40b2018-10-08 06:44:45246
[email protected]011886692014-08-01 21:00:21247 return os.path.join(extract_dir, self._binary_name)
[email protected]183706d92011-06-10 13:06:22248
rob724c9062015-01-22 00:26:42249 def ParseDirectoryIndex(self, last_known_rev):
[email protected]afe30662011-07-30 01:05:52250 """Parses the Google Storage directory listing into a list of revision
[email protected]eadd95d2012-11-02 22:42:09251 numbers."""
[email protected]afe30662011-07-30 01:05:52252
rob724c9062015-01-22 00:26:42253 def _GetMarkerForRev(revision):
254 if self.is_asan:
255 return '%s-%s/%s-%d.zip' % (
256 self.GetASANPlatformDir(), self.build_type,
257 self.GetASANBaseName(), revision)
258 return '%s%d' % (self._listing_platform_dir, revision)
259
[email protected]afe30662011-07-30 01:05:52260 def _FetchAndParse(url):
261 """Fetches a URL and returns a 2-Tuple of ([revisions], next-marker). If
262 next-marker is not None, then the listing is a partial listing and another
263 fetch should be performed with next-marker being the marker= GET
264 parameter."""
265 handle = urllib.urlopen(url)
266 document = ElementTree.parse(handle)
267
268 # All nodes in the tree are namespaced. Get the root's tag name to extract
269 # the namespace. Etree does namespaces as |{namespace}tag|.
270 root_tag = document.getroot().tag
271 end_ns_pos = root_tag.find('}')
272 if end_ns_pos == -1:
[email protected]4df583c2014-07-31 17:11:55273 raise Exception('Could not locate end namespace for directory index')
[email protected]afe30662011-07-30 01:05:52274 namespace = root_tag[:end_ns_pos + 1]
275
276 # Find the prefix (_listing_platform_dir) and whether or not the list is
277 # truncated.
278 prefix_len = len(document.find(namespace + 'Prefix').text)
279 next_marker = None
280 is_truncated = document.find(namespace + 'IsTruncated')
281 if is_truncated is not None and is_truncated.text.lower() == 'true':
282 next_marker = document.find(namespace + 'NextMarker').text
[email protected]afe30662011-07-30 01:05:52283 # Get a list of all the revisions.
[email protected]afe30662011-07-30 01:05:52284 revisions = []
[email protected]3e7c85322014-06-27 20:27:36285 githash_svn_dict = {}
[email protected]011886692014-08-01 21:00:21286 if self.is_asan:
287 asan_regex = re.compile(r'.*%s-(\d+)\.zip$' % (self.GetASANBaseName()))
288 # Non ASAN builds are in a <revision> directory. The ASAN builds are
289 # flat
290 all_prefixes = document.findall(namespace + 'Contents/' +
291 namespace + 'Key')
292 for prefix in all_prefixes:
293 m = asan_regex.match(prefix.text)
294 if m:
295 try:
296 revisions.append(int(m.group(1)))
297 except ValueError:
298 pass
299 else:
300 all_prefixes = document.findall(namespace + 'CommonPrefixes/' +
301 namespace + 'Prefix')
302 # The <Prefix> nodes have content of the form of
303 # |_listing_platform_dir/revision/|. Strip off the platform dir and the
304 # trailing slash to just have a number.
305 for prefix in all_prefixes:
306 revnum = prefix.text[prefix_len:-1]
307 try:
dimua1dfa0ce2016-03-31 01:08:45308 revnum = int(revnum)
309 revisions.append(revnum)
310 # Notes:
311 # Ignore hash in chromium-browser-snapshots as they are invalid
312 # Resulting in 404 error in fetching pages:
313 # https://chromium.googlesource.com/chromium/src/+/[rev_hash]
[email protected]011886692014-08-01 21:00:21314 except ValueError:
315 pass
[email protected]3e7c85322014-06-27 20:27:36316 return (revisions, next_marker, githash_svn_dict)
[email protected]9639b002013-08-30 14:45:52317
[email protected]afe30662011-07-30 01:05:52318 # Fetch the first list of revisions.
rob724c9062015-01-22 00:26:42319 if last_known_rev:
320 revisions = []
321 # Optimization: Start paging at the last known revision (local cache).
322 next_marker = _GetMarkerForRev(last_known_rev)
323 # Optimization: Stop paging at the last known revision (remote).
324 last_change_rev = GetChromiumRevision(self, self.GetLastChangeURL())
325 if last_known_rev == last_change_rev:
326 return []
327 else:
328 (revisions, next_marker, new_dict) = _FetchAndParse(self.GetListingURL())
329 self.githash_svn_dict.update(new_dict)
330 last_change_rev = None
331
[email protected]afe30662011-07-30 01:05:52332 # If the result list was truncated, refetch with the next marker. Do this
333 # until an entire directory listing is done.
334 while next_marker:
rob724c9062015-01-22 00:26:42335 sys.stdout.write('\rFetching revisions at marker %s' % next_marker)
336 sys.stdout.flush()
337
[email protected]afe30662011-07-30 01:05:52338 next_url = self.GetListingURL(next_marker)
[email protected]3e7c85322014-06-27 20:27:36339 (new_revisions, next_marker, new_dict) = _FetchAndParse(next_url)
[email protected]afe30662011-07-30 01:05:52340 revisions.extend(new_revisions)
[email protected]3e7c85322014-06-27 20:27:36341 self.githash_svn_dict.update(new_dict)
rob724c9062015-01-22 00:26:42342 if last_change_rev and last_change_rev in new_revisions:
343 break
344 sys.stdout.write('\r')
345 sys.stdout.flush()
[email protected]afe30662011-07-30 01:05:52346 return revisions
347
[email protected]6a7a5d62014-07-09 04:45:50348 def _GetSVNRevisionFromGitHashWithoutGitCheckout(self, git_sha1, depot):
[email protected]3e7c85322014-06-27 20:27:36349 json_url = GITHASH_TO_SVN_URL[depot] % git_sha1
[email protected]2e0f2672014-08-13 20:32:58350 response = urllib.urlopen(json_url)
351 if response.getcode() == 200:
352 try:
353 data = json.loads(response.read()[4:])
354 except ValueError:
Raul Tambre57e09d62019-09-22 17:18:52355 print('ValueError for JSON URL: %s' % json_url)
[email protected]2e0f2672014-08-13 20:32:58356 raise ValueError
357 else:
358 raise ValueError
[email protected]3e7c85322014-06-27 20:27:36359 if 'message' in data:
360 message = data['message'].split('\n')
361 message = [line for line in message if line.strip()]
362 search_pattern = re.compile(SEARCH_PATTERN[depot])
363 result = search_pattern.search(message[len(message)-1])
364 if result:
365 return result.group(1)
pshenoyb23a1452014-09-05 22:52:05366 else:
367 if depot == 'chromium':
368 result = re.search(CHROMIUM_SEARCH_PATTERN_OLD,
369 message[len(message)-1])
370 if result:
371 return result.group(1)
Raul Tambre57e09d62019-09-22 17:18:52372 print('Failed to get svn revision number for %s' % git_sha1)
[email protected]1f99f4d2014-07-23 16:44:14373 raise ValueError
[email protected]3e7c85322014-06-27 20:27:36374
[email protected]6a7a5d62014-07-09 04:45:50375 def _GetSVNRevisionFromGitHashFromGitCheckout(self, git_sha1, depot):
376 def _RunGit(command, path):
377 command = ['git'] + command
[email protected]6a7a5d62014-07-09 04:45:50378 shell = sys.platform.startswith('win')
379 proc = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE,
rob724c9062015-01-22 00:26:42380 stderr=subprocess.PIPE, cwd=path)
[email protected]6a7a5d62014-07-09 04:45:50381 (output, _) = proc.communicate()
[email protected]6a7a5d62014-07-09 04:45:50382 return (output, proc.returncode)
383
rob724c9062015-01-22 00:26:42384 path = self.local_src_path
[email protected]6a7a5d62014-07-09 04:45:50385 if depot == 'blink':
rob724c9062015-01-22 00:26:42386 path = os.path.join(self.local_src_path, 'third_party', 'WebKit')
387 revision = None
388 try:
[email protected]6a7a5d62014-07-09 04:45:50389 command = ['svn', 'find-rev', git_sha1]
390 (git_output, return_code) = _RunGit(command, path)
391 if not return_code:
rob724c9062015-01-22 00:26:42392 revision = git_output.strip('\n')
393 except ValueError:
394 pass
395 if not revision:
396 command = ['log', '-n1', '--format=%s', git_sha1]
397 (git_output, return_code) = _RunGit(command, path)
398 if not return_code:
399 revision = re.match('SVN changes up to revision ([0-9]+)', git_output)
400 revision = revision.group(1) if revision else None
401 if revision:
402 return revision
403 raise ValueError
[email protected]6a7a5d62014-07-09 04:45:50404
405 def GetSVNRevisionFromGitHash(self, git_sha1, depot='chromium'):
rob724c9062015-01-22 00:26:42406 if not self.local_src_path:
[email protected]6a7a5d62014-07-09 04:45:50407 return self._GetSVNRevisionFromGitHashWithoutGitCheckout(git_sha1, depot)
408 else:
409 return self._GetSVNRevisionFromGitHashFromGitCheckout(git_sha1, depot)
410
[email protected]afe30662011-07-30 01:05:52411 def GetRevList(self):
412 """Gets the list of revision numbers between self.good_revision and
413 self.bad_revision."""
rob724c9062015-01-22 00:26:42414
415 cache = {}
416 # The cache is stored in the same directory as bisect-builds.py
417 cache_filename = os.path.join(
418 os.path.abspath(os.path.dirname(__file__)),
419 '.bisect-builds-cache.json')
420 cache_dict_key = self.GetListingURL()
421
422 def _LoadBucketFromCache():
423 if self.use_local_cache:
424 try:
425 with open(cache_filename) as cache_file:
rob1c836052015-05-18 16:34:02426 for (key, value) in json.load(cache_file).items():
427 cache[key] = value
rob724c9062015-01-22 00:26:42428 revisions = cache.get(cache_dict_key, [])
429 githash_svn_dict = cache.get('githash_svn_dict', {})
430 if revisions:
Raul Tambre57e09d62019-09-22 17:18:52431 print('Loaded revisions %d-%d from %s' %
432 (revisions[0], revisions[-1], cache_filename))
rob724c9062015-01-22 00:26:42433 return (revisions, githash_svn_dict)
434 except (EnvironmentError, ValueError):
435 pass
436 return ([], {})
437
438 def _SaveBucketToCache():
439 """Save the list of revisions and the git-svn mappings to a file.
440 The list of revisions is assumed to be sorted."""
441 if self.use_local_cache:
442 cache[cache_dict_key] = revlist_all
443 cache['githash_svn_dict'] = self.githash_svn_dict
444 try:
445 with open(cache_filename, 'w') as cache_file:
446 json.dump(cache, cache_file)
Raul Tambre57e09d62019-09-22 17:18:52447 print('Saved revisions %d-%d to %s' %
448 (revlist_all[0], revlist_all[-1], cache_filename))
rob724c9062015-01-22 00:26:42449 except EnvironmentError:
450 pass
451
[email protected]afe30662011-07-30 01:05:52452 # Download the revlist and filter for just the range between good and bad.
[email protected]eadd95d2012-11-02 22:42:09453 minrev = min(self.good_revision, self.bad_revision)
454 maxrev = max(self.good_revision, self.bad_revision)
rob724c9062015-01-22 00:26:42455
456 (revlist_all, self.githash_svn_dict) = _LoadBucketFromCache()
457 last_known_rev = revlist_all[-1] if revlist_all else 0
458 if last_known_rev < maxrev:
459 revlist_all.extend(map(int, self.ParseDirectoryIndex(last_known_rev)))
460 revlist_all = list(set(revlist_all))
461 revlist_all.sort()
462 _SaveBucketToCache()
[email protected]37ed3172013-09-24 23:49:30463
464 revlist = [x for x in revlist_all if x >= int(minrev) and x <= int(maxrev)]
[email protected]37ed3172013-09-24 23:49:30465
466 # Set good and bad revisions to be legit revisions.
467 if revlist:
468 if self.good_revision < self.bad_revision:
469 self.good_revision = revlist[0]
470 self.bad_revision = revlist[-1]
471 else:
472 self.bad_revision = revlist[0]
473 self.good_revision = revlist[-1]
474
475 # Fix chromium rev so that the deps blink revision matches REVISIONS file.
476 if self.base_url == WEBKIT_BASE_URL:
477 revlist_all.sort()
478 self.good_revision = FixChromiumRevForBlink(revlist,
479 revlist_all,
480 self,
481 self.good_revision)
482 self.bad_revision = FixChromiumRevForBlink(revlist,
483 revlist_all,
484 self,
485 self.bad_revision)
[email protected]afe30662011-07-30 01:05:52486 return revlist
487
prasadv2375e6d2017-03-20 19:23:23488
489def IsMac():
490 return sys.platform.startswith('darwin')
491
492
[email protected]fc3702e2013-11-09 04:23:00493def UnzipFilenameToDir(filename, directory):
494 """Unzip |filename| to |directory|."""
[email protected]afe30662011-07-30 01:05:52495 cwd = os.getcwd()
496 if not os.path.isabs(filename):
497 filename = os.path.join(cwd, filename)
[email protected]bd8dcb92010-03-31 01:05:24498 # Make base.
[email protected]fc3702e2013-11-09 04:23:00499 if not os.path.isdir(directory):
500 os.mkdir(directory)
501 os.chdir(directory)
prasadv2375e6d2017-03-20 19:23:23502
503 # The Python ZipFile does not support symbolic links, which makes it
504 # unsuitable for Mac builds. so use ditto instead.
505 if IsMac():
506 unzip_cmd = ['ditto', '-x', '-k', filename, '.']
507 proc = subprocess.Popen(unzip_cmd, bufsize=0, stdout=subprocess.PIPE,
508 stderr=subprocess.PIPE)
509 proc.communicate()
510 os.chdir(cwd)
511 return
512
513 zf = zipfile.ZipFile(filename)
[email protected]e29c08c2012-09-17 20:50:50514 # Extract files.
515 for info in zf.infolist():
516 name = info.filename
517 if name.endswith('/'): # dir
518 if not os.path.isdir(name):
519 os.makedirs(name)
520 else: # file
[email protected]fc3702e2013-11-09 04:23:00521 directory = os.path.dirname(name)
John Budorick06e5df12015-02-27 17:44:27522 if not os.path.isdir(directory):
[email protected]fc3702e2013-11-09 04:23:00523 os.makedirs(directory)
[email protected]e29c08c2012-09-17 20:50:50524 out = open(name, 'wb')
525 out.write(zf.read(name))
526 out.close()
527 # Set permissions. Permission info in external_attr is shifted 16 bits.
528 os.chmod(name, info.external_attr >> 16L)
529 os.chdir(cwd)
[email protected]bd8dcb92010-03-31 01:05:24530
[email protected]67e0bc62009-09-03 22:06:09531
[email protected]468a9772011-08-09 18:42:00532def FetchRevision(context, rev, filename, quit_event=None, progress_event=None):
[email protected]afe30662011-07-30 01:05:52533 """Downloads and unzips revision |rev|.
534 @param context A PathContext instance.
535 @param rev The Chromium revision number/tag to download.
536 @param filename The destination for the downloaded file.
537 @param quit_event A threading.Event which will be set by the master thread to
538 indicate that the download should be aborted.
[email protected]468a9772011-08-09 18:42:00539 @param progress_event A threading.Event which will be set by the master thread
540 to indicate that the progress of the download should be
541 displayed.
[email protected]afe30662011-07-30 01:05:52542 """
543 def ReportHook(blocknum, blocksize, totalsize):
[email protected]946be752011-10-25 23:34:21544 if quit_event and quit_event.isSet():
[email protected]4df583c2014-07-31 17:11:55545 raise RuntimeError('Aborting download of revision %s' % str(rev))
[email protected]946be752011-10-25 23:34:21546 if progress_event and progress_event.isSet():
[email protected]468a9772011-08-09 18:42:00547 size = blocknum * blocksize
548 if totalsize == -1: # Total size not known.
[email protected]4df583c2014-07-31 17:11:55549 progress = 'Received %d bytes' % size
[email protected]468a9772011-08-09 18:42:00550 else:
551 size = min(totalsize, size)
[email protected]4df583c2014-07-31 17:11:55552 progress = 'Received %d of %d bytes, %.2f%%' % (
[email protected]468a9772011-08-09 18:42:00553 size, totalsize, 100.0 * size / totalsize)
554 # Send a \r to let all progress messages use just one line of output.
[email protected]4df583c2014-07-31 17:11:55555 sys.stdout.write('\r' + progress)
[email protected]468a9772011-08-09 18:42:00556 sys.stdout.flush()
[email protected]afe30662011-07-30 01:05:52557 download_url = context.GetDownloadURL(rev)
558 try:
John Budorick06e5df12015-02-27 17:44:27559 urllib.urlretrieve(download_url, filename, ReportHook)
[email protected]946be752011-10-25 23:34:21560 if progress_event and progress_event.isSet():
Raul Tambre57e09d62019-09-22 17:18:52561 print()
mikecasee2b6ce82015-02-06 18:22:39562
[email protected]4df583c2014-07-31 17:11:55563 except RuntimeError:
[email protected]afe30662011-07-30 01:05:52564 pass
[email protected]7ad66a72009-09-04 17:52:33565
[email protected]7ad66a72009-09-04 17:52:33566
Dominic Mazzoni215e80b2017-11-29 20:05:27567def CopyMissingFileFromCurrentSource(src_glob, dst):
568 """Work around missing files in archives.
569 This happens when archives of Chrome don't contain all of the files
570 needed to build it. In many cases we can work around this using
571 files from the current checkout. The source is in the form of a glob
572 so that it can try to look for possible sources of the file in
573 multiple locations, but we just arbitrarily try the first match.
574
575 Silently fail if this doesn't work because we don't yet have clear
576 markers for builds that require certain files or a way to test
577 whether or not launching Chrome succeeded.
578 """
579 if not os.path.exists(dst):
580 matches = glob.glob(src_glob)
581 if matches:
582 shutil.copy2(matches[0], dst)
583
584
[email protected]4df583c2014-07-31 17:11:55585def RunRevision(context, revision, zip_file, profile, num_runs, command, args):
[email protected]afe30662011-07-30 01:05:52586 """Given a zipped revision, unzip it and run the test."""
Raul Tambre57e09d62019-09-22 17:18:52587 print('Trying revision %s...' % str(revision))
[email protected]3ff00b72011-07-20 21:34:47588
[email protected]afe30662011-07-30 01:05:52589 # Create a temp directory and unzip the revision into it.
[email protected]7ad66a72009-09-04 17:52:33590 cwd = os.getcwd()
591 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
[email protected]4df583c2014-07-31 17:11:55592 UnzipFilenameToDir(zip_file, tempdir)
dmazzoni76e907d2015-01-22 08:14:49593
Dominic Mazzoni215e80b2017-11-29 20:05:27594 # Hack: Some Chrome OS archives are missing some files; try to copy them
595 # from the local directory.
Dominic Mazzonie84e40b2018-10-08 06:44:45596 if context.platform == 'chromeos' and revision < 591483:
Dominic Mazzoni215e80b2017-11-29 20:05:27597 CopyMissingFileFromCurrentSource('third_party/icu/common/icudtl.dat',
598 '%s/chrome-linux/icudtl.dat' % tempdir)
599 CopyMissingFileFromCurrentSource('*out*/*/libminigbm.so',
600 '%s/chrome-linux/libminigbm.so' % tempdir)
dmazzoni76e907d2015-01-22 08:14:49601
[email protected]7ad66a72009-09-04 17:52:33602 os.chdir(tempdir)
[email protected]67e0bc62009-09-03 22:06:09603
[email protected]5e93cf162012-01-28 02:16:56604 # Run the build as many times as specified.
[email protected]4646a752013-07-19 22:14:34605 testargs = ['--user-data-dir=%s' % profile] + args
[email protected]d0149c5c2012-05-29 21:12:11606 # The sandbox must be run as root on Official Chrome, so bypass it.
Jason Kersey97bb027a2016-05-11 20:10:43607 if (context.flash_path and context.platform.startswith('linux')):
[email protected]d0149c5c2012-05-29 21:12:11608 testargs.append('--no-sandbox')
[email protected]fc3702e2013-11-09 04:23:00609 if context.flash_path:
610 testargs.append('--ppapi-flash-path=%s' % context.flash_path)
611 # We have to pass a large enough Flash version, which currently needs not
612 # be correct. Instead of requiring the user of the script to figure out and
613 # pass the correct version we just spoof it.
614 testargs.append('--ppapi-flash-version=99.9.999.999')
[email protected]d0149c5c2012-05-29 21:12:11615
[email protected]4646a752013-07-19 22:14:34616 runcommand = []
[email protected]61ea90a2013-09-26 10:17:34617 for token in shlex.split(command):
[email protected]4df583c2014-07-31 17:11:55618 if token == '%a':
[email protected]4646a752013-07-19 22:14:34619 runcommand.extend(testargs)
620 else:
[email protected]4df583c2014-07-31 17:11:55621 runcommand.append(
[email protected]011886692014-08-01 21:00:21622 token.replace('%p', os.path.abspath(context.GetLaunchPath(revision))).
623 replace('%s', ' '.join(testargs)))
[email protected]fb61fc32019-04-18 19:47:20624 result = None
[email protected]7ad66a72009-09-04 17:52:33625 try:
[email protected]fb61fc32019-04-18 19:47:20626 for _ in range(num_runs):
627 subproc = subprocess.Popen(
628 runcommand,
629 bufsize=-1,
630 stdout=subprocess.PIPE,
631 stderr=subprocess.PIPE)
632 (stdout, stderr) = subproc.communicate()
633 result = (subproc.returncode, stdout, stderr)
634 if subproc.returncode:
635 break
636 return result
637 finally:
638 os.chdir(cwd)
639 try:
640 shutil.rmtree(tempdir, True)
641 except Exception:
642 pass
[email protected]79f14742010-03-10 01:01:57643
[email protected]cb155a82011-11-29 17:25:34644
Jason Kersey97bb027a2016-05-11 20:10:43645# The arguments status, stdout and stderr are unused.
[email protected]4df583c2014-07-31 17:11:55646# They are present here because this function is passed to Bisect which then
647# calls it with 5 arguments.
648# pylint: disable=W0613
Jason Kersey97bb027a2016-05-11 20:10:43649def AskIsGoodBuild(rev, exit_status, stdout, stderr):
[email protected]4df583c2014-07-31 17:11:55650 """Asks the user whether build |rev| is good or bad."""
Fergal Daly0dd19532019-04-04 07:45:33651 if exit_status:
Raul Tambre57e09d62019-09-22 17:18:52652 print('Chrome exit_status: %d. Use s to see output' % exit_status)
[email protected]79f14742010-03-10 01:01:57653 # Loop until we get a response that we can parse.
[email protected]67e0bc62009-09-03 22:06:09654 while True:
[email protected]4df583c2014-07-31 17:11:55655 response = raw_input('Revision %s is '
wangxianzhud8c4c562015-12-15 23:39:51656 '[(g)ood/(b)ad/(r)etry/(u)nknown/(s)tdout/(q)uit]: ' %
[email protected]53bb6342012-06-01 04:11:00657 str(rev))
wangxianzhud8c4c562015-12-15 23:39:51658 if response in ('g', 'b', 'r', 'u'):
[email protected]53bb6342012-06-01 04:11:00659 return response
wangxianzhud8c4c562015-12-15 23:39:51660 if response == 'q':
[email protected]afe30662011-07-30 01:05:52661 raise SystemExit()
wangxianzhud8c4c562015-12-15 23:39:51662 if response == 's':
Raul Tambre57e09d62019-09-22 17:18:52663 print(stdout)
664 print(stderr)
[email protected]67e0bc62009-09-03 22:06:09665
[email protected]cb155a82011-11-29 17:25:34666
Jason Kersey97bb027a2016-05-11 20:10:43667def IsGoodASANBuild(rev, exit_status, stdout, stderr):
[email protected]011886692014-08-01 21:00:21668 """Determine if an ASAN build |rev| is good or bad
669
670 Will examine stderr looking for the error message emitted by ASAN. If not
671 found then will fallback to asking the user."""
672 if stderr:
673 bad_count = 0
674 for line in stderr.splitlines():
Raul Tambre57e09d62019-09-22 17:18:52675 print(line)
[email protected]011886692014-08-01 21:00:21676 if line.find('ERROR: AddressSanitizer:') != -1:
677 bad_count += 1
678 if bad_count > 0:
Raul Tambre57e09d62019-09-22 17:18:52679 print('Revision %d determined to be bad.' % rev)
[email protected]011886692014-08-01 21:00:21680 return 'b'
Jason Kersey97bb027a2016-05-11 20:10:43681 return AskIsGoodBuild(rev, exit_status, stdout, stderr)
skobes21b5cdfb2016-03-21 23:13:02682
683
Jason Kersey97bb027a2016-05-11 20:10:43684def DidCommandSucceed(rev, exit_status, stdout, stderr):
skobes21b5cdfb2016-03-21 23:13:02685 if exit_status:
Raul Tambre57e09d62019-09-22 17:18:52686 print('Bad revision: %s' % rev)
skobes21b5cdfb2016-03-21 23:13:02687 return 'b'
688 else:
Raul Tambre57e09d62019-09-22 17:18:52689 print('Good revision: %s' % rev)
skobes21b5cdfb2016-03-21 23:13:02690 return 'g'
691
[email protected]011886692014-08-01 21:00:21692
[email protected]53bb6342012-06-01 04:11:00693class DownloadJob(object):
694 """DownloadJob represents a task to download a given Chromium revision."""
[email protected]4df583c2014-07-31 17:11:55695
696 def __init__(self, context, name, rev, zip_file):
[email protected]53bb6342012-06-01 04:11:00697 super(DownloadJob, self).__init__()
698 # Store off the input parameters.
699 self.context = context
700 self.name = name
701 self.rev = rev
[email protected]4df583c2014-07-31 17:11:55702 self.zip_file = zip_file
[email protected]53bb6342012-06-01 04:11:00703 self.quit_event = threading.Event()
704 self.progress_event = threading.Event()
[email protected]4df583c2014-07-31 17:11:55705 self.thread = None
[email protected]53bb6342012-06-01 04:11:00706
707 def Start(self):
708 """Starts the download."""
709 fetchargs = (self.context,
710 self.rev,
[email protected]4df583c2014-07-31 17:11:55711 self.zip_file,
[email protected]53bb6342012-06-01 04:11:00712 self.quit_event,
713 self.progress_event)
714 self.thread = threading.Thread(target=FetchRevision,
715 name=self.name,
716 args=fetchargs)
717 self.thread.start()
718
719 def Stop(self):
720 """Stops the download which must have been started previously."""
[email protected]4df583c2014-07-31 17:11:55721 assert self.thread, 'DownloadJob must be started before Stop is called.'
[email protected]53bb6342012-06-01 04:11:00722 self.quit_event.set()
723 self.thread.join()
[email protected]4df583c2014-07-31 17:11:55724 os.unlink(self.zip_file)
[email protected]53bb6342012-06-01 04:11:00725
726 def WaitFor(self):
727 """Prints a message and waits for the download to complete. The download
728 must have been started previously."""
[email protected]4df583c2014-07-31 17:11:55729 assert self.thread, 'DownloadJob must be started before WaitFor is called.'
Raul Tambre57e09d62019-09-22 17:18:52730 print('Downloading revision %s...' % str(self.rev))
[email protected]53bb6342012-06-01 04:11:00731 self.progress_event.set() # Display progress of download.
rob8a4543f2016-01-20 00:43:59732 try:
733 while self.thread.isAlive():
734 # The parameter to join is needed to keep the main thread responsive to
735 # signals. Without it, the program will not respond to interruptions.
736 self.thread.join(1)
737 except (KeyboardInterrupt, SystemExit):
738 self.Stop()
739 raise
[email protected]53bb6342012-06-01 04:11:00740
741
skobes21b5cdfb2016-03-21 23:13:02742def VerifyEndpoint(fetch, context, rev, profile, num_runs, command, try_args,
743 evaluate, expected_answer):
744 fetch.WaitFor()
745 try:
Roman Sorokin760f06cd2019-12-24 08:35:41746 answer = 'r'
747 # This is intended to allow evaluate() to return 'r' to retry RunRevision.
748 while answer == 'r':
749 (exit_status, stdout, stderr) = RunRevision(
750 context, rev, fetch.zip_file, profile, num_runs, command, try_args)
751 answer = evaluate(rev, exit_status, stdout, stderr);
skobes21b5cdfb2016-03-21 23:13:02752 except Exception, e:
Raul Tambre57e09d62019-09-22 17:18:52753 print(e, file=sys.stderr)
Lei Zhang2fa76302018-11-09 20:16:31754 raise SystemExit
Roman Sorokin760f06cd2019-12-24 08:35:41755 if (answer != expected_answer):
Raul Tambre57e09d62019-09-22 17:18:52756 print('Unexpected result at a range boundary! Your range is not correct.')
skobes21b5cdfb2016-03-21 23:13:02757 raise SystemExit
758
759
[email protected]2e0f2672014-08-13 20:32:58760def Bisect(context,
[email protected]5e93cf162012-01-28 02:16:56761 num_runs=1,
[email protected]4df583c2014-07-31 17:11:55762 command='%p %a',
[email protected]60ac66e32011-07-18 16:08:25763 try_args=(),
[email protected]afe30662011-07-30 01:05:52764 profile=None,
skobes21b5cdfb2016-03-21 23:13:02765 evaluate=AskIsGoodBuild,
766 verify_range=False):
[email protected]afe30662011-07-30 01:05:52767 """Given known good and known bad revisions, run a binary search on all
768 archived revisions to determine the last known good revision.
[email protected]60ac66e32011-07-18 16:08:25769
[email protected]2e0f2672014-08-13 20:32:58770 @param context PathContext object initialized with user provided parameters.
[email protected]5e93cf162012-01-28 02:16:56771 @param num_runs Number of times to run each build for asking good/bad.
[email protected]afe30662011-07-30 01:05:52772 @param try_args A tuple of arguments to pass to the test application.
773 @param profile The name of the user profile to run with.
[email protected]53bb6342012-06-01 04:11:00774 @param evaluate A function which returns 'g' if the argument build is good,
775 'b' if it's bad or 'u' if unknown.
skobes21b5cdfb2016-03-21 23:13:02776 @param verify_range If true, tests the first and last revisions in the range
777 before proceeding with the bisect.
[email protected]afe30662011-07-30 01:05:52778
779 Threading is used to fetch Chromium revisions in the background, speeding up
780 the user's experience. For example, suppose the bounds of the search are
781 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
782 whether revision 50 is good or bad, the next revision to check will be either
783 25 or 75. So, while revision 50 is being checked, the script will download
784 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
785 known:
786
787 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
788 is run on rev 75.
789
790 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
791 is run on rev 25.
[email protected]60ac66e32011-07-18 16:08:25792 """
793
[email protected]afe30662011-07-30 01:05:52794 if not profile:
795 profile = 'profile'
796
[email protected]2e0f2672014-08-13 20:32:58797 good_rev = context.good_revision
798 bad_rev = context.bad_revision
[email protected]afe30662011-07-30 01:05:52799 cwd = os.getcwd()
800
Raul Tambre57e09d62019-09-22 17:18:52801 print('Downloading list of known revisions...', end=' ')
Jason Kersey97bb027a2016-05-11 20:10:43802 if not context.use_local_cache:
Raul Tambre57e09d62019-09-22 17:18:52803 print('(use --use-local-cache to cache and re-use the list of revisions)')
[email protected]28a3c122014-08-09 11:04:51804 else:
Raul Tambre57e09d62019-09-22 17:18:52805 print()
[email protected]d0149c5c2012-05-29 21:12:11806 _GetDownloadPath = lambda rev: os.path.join(cwd,
807 '%s-%s' % (str(rev), context.archive_name))
Jason Kersey97bb027a2016-05-11 20:10:43808 revlist = context.GetRevList()
[email protected]afe30662011-07-30 01:05:52809
810 # Get a list of revisions to bisect across.
811 if len(revlist) < 2: # Don't have enough builds to bisect.
812 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
813 raise RuntimeError(msg)
814
815 # Figure out our bookends and first pivot point; fetch the pivot revision.
[email protected]eadd95d2012-11-02 22:42:09816 minrev = 0
817 maxrev = len(revlist) - 1
818 pivot = maxrev / 2
[email protected]afe30662011-07-30 01:05:52819 rev = revlist[pivot]
skobes21b5cdfb2016-03-21 23:13:02820 fetch = DownloadJob(context, 'initial_fetch', rev, _GetDownloadPath(rev))
[email protected]eadd95d2012-11-02 22:42:09821 fetch.Start()
skobes21b5cdfb2016-03-21 23:13:02822
823 if verify_range:
824 minrev_fetch = DownloadJob(
825 context, 'minrev_fetch', revlist[minrev],
826 _GetDownloadPath(revlist[minrev]))
827 maxrev_fetch = DownloadJob(
828 context, 'maxrev_fetch', revlist[maxrev],
829 _GetDownloadPath(revlist[maxrev]))
830 minrev_fetch.Start()
831 maxrev_fetch.Start()
832 try:
833 VerifyEndpoint(minrev_fetch, context, revlist[minrev], profile, num_runs,
834 command, try_args, evaluate, 'b' if bad_rev < good_rev else 'g')
835 VerifyEndpoint(maxrev_fetch, context, revlist[maxrev], profile, num_runs,
836 command, try_args, evaluate, 'g' if bad_rev < good_rev else 'b')
837 except (KeyboardInterrupt, SystemExit):
Raul Tambre57e09d62019-09-22 17:18:52838 print('Cleaning up...')
skobes21b5cdfb2016-03-21 23:13:02839 fetch.Stop()
840 sys.exit(0)
841 finally:
842 minrev_fetch.Stop()
843 maxrev_fetch.Stop()
844
[email protected]eadd95d2012-11-02 22:42:09845 fetch.WaitFor()
[email protected]60ac66e32011-07-18 16:08:25846
847 # Binary search time!
Bruce Dawson62257412020-01-17 17:39:53848 prefetch_revisions = True
[email protected]4df583c2014-07-31 17:11:55849 while fetch and fetch.zip_file and maxrev - minrev > 1:
[email protected]eadd95d2012-11-02 22:42:09850 if bad_rev < good_rev:
[email protected]4df583c2014-07-31 17:11:55851 min_str, max_str = 'bad', 'good'
[email protected]eadd95d2012-11-02 22:42:09852 else:
[email protected]4df583c2014-07-31 17:11:55853 min_str, max_str = 'good', 'bad'
Raul Tambre57e09d62019-09-22 17:18:52854 print(
855 'Bisecting range [%s (%s), %s (%s)], '
856 'roughly %d steps left.' % (revlist[minrev], min_str, revlist[maxrev],
857 max_str, int(maxrev - minrev).bit_length()))
[email protected]eadd95d2012-11-02 22:42:09858
[email protected]afe30662011-07-30 01:05:52859 # Pre-fetch next two possible pivots
860 # - down_pivot is the next revision to check if the current revision turns
861 # out to be bad.
862 # - up_pivot is the next revision to check if the current revision turns
863 # out to be good.
[email protected]eadd95d2012-11-02 22:42:09864 down_pivot = int((pivot - minrev) / 2) + minrev
Bruce Dawson62257412020-01-17 17:39:53865 if prefetch_revisions:
866 down_fetch = None
867 if down_pivot != pivot and down_pivot != minrev:
868 down_rev = revlist[down_pivot]
869 down_fetch = DownloadJob(context, 'down_fetch', down_rev,
870 _GetDownloadPath(down_rev))
871 down_fetch.Start()
[email protected]60ac66e32011-07-18 16:08:25872
[email protected]eadd95d2012-11-02 22:42:09873 up_pivot = int((maxrev - pivot) / 2) + pivot
Bruce Dawson62257412020-01-17 17:39:53874 if prefetch_revisions:
875 up_fetch = None
876 if up_pivot != pivot and up_pivot != maxrev:
877 up_rev = revlist[up_pivot]
878 up_fetch = DownloadJob(context, 'up_fetch', up_rev,
879 _GetDownloadPath(up_rev))
880 up_fetch.Start()
[email protected]60ac66e32011-07-18 16:08:25881
[email protected]afe30662011-07-30 01:05:52882 # Run test on the pivot revision.
skobes21b5cdfb2016-03-21 23:13:02883 exit_status = None
[email protected]e29c08c2012-09-17 20:50:50884 stdout = None
885 stderr = None
886 try:
skobes21b5cdfb2016-03-21 23:13:02887 (exit_status, stdout, stderr) = RunRevision(
888 context, rev, fetch.zip_file, profile, num_runs, command, try_args)
[email protected]e29c08c2012-09-17 20:50:50889 except Exception, e:
Raul Tambre57e09d62019-09-22 17:18:52890 print(e, file=sys.stderr)
[email protected]60ac66e32011-07-18 16:08:25891
[email protected]53bb6342012-06-01 04:11:00892 # Call the evaluate function to see if the current revision is good or bad.
[email protected]afe30662011-07-30 01:05:52893 # On that basis, kill one of the background downloads and complete the
894 # other, as described in the comments above.
895 try:
Jason Kersey97bb027a2016-05-11 20:10:43896 answer = evaluate(rev, exit_status, stdout, stderr)
Bruce Dawson62257412020-01-17 17:39:53897 prefetch_revisions = True
[email protected]4df583c2014-07-31 17:11:55898 if ((answer == 'g' and good_rev < bad_rev)
899 or (answer == 'b' and bad_rev < good_rev)):
[email protected]1d4a06242013-08-20 22:53:12900 fetch.Stop()
[email protected]eadd95d2012-11-02 22:42:09901 minrev = pivot
[email protected]53bb6342012-06-01 04:11:00902 if down_fetch:
903 down_fetch.Stop() # Kill the download of the older revision.
[email protected]1d4a06242013-08-20 22:53:12904 fetch = None
[email protected]53bb6342012-06-01 04:11:00905 if up_fetch:
906 up_fetch.WaitFor()
[email protected]afe30662011-07-30 01:05:52907 pivot = up_pivot
[email protected]eadd95d2012-11-02 22:42:09908 fetch = up_fetch
[email protected]4df583c2014-07-31 17:11:55909 elif ((answer == 'b' and good_rev < bad_rev)
910 or (answer == 'g' and bad_rev < good_rev)):
[email protected]1d4a06242013-08-20 22:53:12911 fetch.Stop()
[email protected]eadd95d2012-11-02 22:42:09912 maxrev = pivot
[email protected]53bb6342012-06-01 04:11:00913 if up_fetch:
914 up_fetch.Stop() # Kill the download of the newer revision.
[email protected]1d4a06242013-08-20 22:53:12915 fetch = None
[email protected]53bb6342012-06-01 04:11:00916 if down_fetch:
917 down_fetch.WaitFor()
[email protected]afe30662011-07-30 01:05:52918 pivot = down_pivot
[email protected]eadd95d2012-11-02 22:42:09919 fetch = down_fetch
[email protected]1d4a06242013-08-20 22:53:12920 elif answer == 'r':
Bruce Dawson62257412020-01-17 17:39:53921 # Don't redundantly prefetch.
922 prefetch_revisions = False
[email protected]53bb6342012-06-01 04:11:00923 elif answer == 'u':
924 # Nuke the revision from the revlist and choose a new pivot.
[email protected]1d4a06242013-08-20 22:53:12925 fetch.Stop()
[email protected]53bb6342012-06-01 04:11:00926 revlist.pop(pivot)
[email protected]eadd95d2012-11-02 22:42:09927 maxrev -= 1 # Assumes maxrev >= pivot.
[email protected]53bb6342012-06-01 04:11:00928
[email protected]eadd95d2012-11-02 22:42:09929 if maxrev - minrev > 1:
[email protected]53bb6342012-06-01 04:11:00930 # Alternate between using down_pivot or up_pivot for the new pivot
931 # point, without affecting the range. Do this instead of setting the
932 # pivot to the midpoint of the new range because adjacent revisions
933 # are likely affected by the same issue that caused the (u)nknown
934 # response.
935 if up_fetch and down_fetch:
936 fetch = [up_fetch, down_fetch][len(revlist) % 2]
937 elif up_fetch:
938 fetch = up_fetch
939 else:
940 fetch = down_fetch
941 fetch.WaitFor()
942 if fetch == up_fetch:
943 pivot = up_pivot - 1 # Subtracts 1 because revlist was resized.
944 else:
945 pivot = down_pivot
[email protected]53bb6342012-06-01 04:11:00946
947 if down_fetch and fetch != down_fetch:
948 down_fetch.Stop()
949 if up_fetch and fetch != up_fetch:
950 up_fetch.Stop()
951 else:
[email protected]4df583c2014-07-31 17:11:55952 assert False, 'Unexpected return value from evaluate(): ' + answer
skobes21b5cdfb2016-03-21 23:13:02953 except (KeyboardInterrupt, SystemExit):
Raul Tambre57e09d62019-09-22 17:18:52954 print('Cleaning up...')
skobes21b5cdfb2016-03-21 23:13:02955 for f in [_GetDownloadPath(rev),
956 _GetDownloadPath(revlist[down_pivot]),
[email protected]5e93cf162012-01-28 02:16:56957 _GetDownloadPath(revlist[up_pivot])]:
[email protected]afe30662011-07-30 01:05:52958 try:
959 os.unlink(f)
960 except OSError:
961 pass
962 sys.exit(0)
963
964 rev = revlist[pivot]
965
[email protected]2e0f2672014-08-13 20:32:58966 return (revlist[minrev], revlist[maxrev], context)
[email protected]60ac66e32011-07-18 16:08:25967
968
pshenoycd6bd682014-09-10 20:50:22969def GetBlinkDEPSRevisionForChromiumRevision(self, rev):
[email protected]4c6fec6b2013-09-17 17:44:08970 """Returns the blink revision that was in REVISIONS file at
[email protected]b2fe7f22011-10-25 22:58:31971 chromium revision |rev|."""
pshenoycd6bd682014-09-10 20:50:22972
973 def _GetBlinkRev(url, blink_re):
974 m = blink_re.search(url.read())
975 url.close()
976 if m:
fmalitaa898d222016-07-12 22:29:03977 return m.group(1)
pshenoycd6bd682014-09-10 20:50:22978
Di Mu08c59682016-07-11 23:05:07979 url = urllib.urlopen(DEPS_FILE % GetGitHashFromSVNRevision(rev))
pshenoycd6bd682014-09-10 20:50:22980 if url.getcode() == 200:
Di Mu08c59682016-07-11 23:05:07981 blink_re = re.compile(r'webkit_revision\D*\d+;\D*\d+;(\w+)')
982 blink_git_sha = _GetBlinkRev(url, blink_re)
983 return self.GetSVNRevisionFromGitHash(blink_git_sha, 'blink')
pshenoycd6bd682014-09-10 20:50:22984 raise Exception('Could not get Blink revision for Chromium rev %d' % rev)
[email protected]37ed3172013-09-24 23:49:30985
986
[email protected]2e0f2672014-08-13 20:32:58987def GetBlinkRevisionForChromiumRevision(context, rev):
[email protected]37ed3172013-09-24 23:49:30988 """Returns the blink revision that was in REVISIONS file at
989 chromium revision |rev|."""
[email protected]3e7c85322014-06-27 20:27:36990 def _IsRevisionNumber(revision):
991 if isinstance(revision, int):
992 return True
993 else:
994 return revision.isdigit()
[email protected]2e0f2672014-08-13 20:32:58995 if str(rev) in context.githash_svn_dict:
996 rev = context.githash_svn_dict[str(rev)]
997 file_url = '%s/%s%s/REVISIONS' % (context.base_url,
998 context._listing_platform_dir, rev)
[email protected]4c6fec6b2013-09-17 17:44:08999 url = urllib.urlopen(file_url)
[email protected]2e0f2672014-08-13 20:32:581000 if url.getcode() == 200:
1001 try:
1002 data = json.loads(url.read())
1003 except ValueError:
Raul Tambre57e09d62019-09-22 17:18:521004 print('ValueError for JSON URL: %s' % file_url)
[email protected]2e0f2672014-08-13 20:32:581005 raise ValueError
1006 else:
1007 raise ValueError
[email protected]b2fe7f22011-10-25 22:58:311008 url.close()
[email protected]4c6fec6b2013-09-17 17:44:081009 if 'webkit_revision' in data:
[email protected]3e7c85322014-06-27 20:27:361010 blink_rev = data['webkit_revision']
1011 if not _IsRevisionNumber(blink_rev):
[email protected]2e0f2672014-08-13 20:32:581012 blink_rev = int(context.GetSVNRevisionFromGitHash(blink_rev, 'blink'))
[email protected]3e7c85322014-06-27 20:27:361013 return blink_rev
[email protected]b2fe7f22011-10-25 22:58:311014 else:
[email protected]ff50d1c2013-04-17 18:49:361015 raise Exception('Could not get blink revision for cr rev %d' % rev)
[email protected]b2fe7f22011-10-25 22:58:311016
[email protected]4df583c2014-07-31 17:11:551017
[email protected]37ed3172013-09-24 23:49:301018def FixChromiumRevForBlink(revisions_final, revisions, self, rev):
1019 """Returns the chromium revision that has the correct blink revision
1020 for blink bisect, DEPS and REVISIONS file might not match since
1021 blink snapshots point to tip of tree blink.
1022 Note: The revisions_final variable might get modified to include
1023 additional revisions."""
pshenoycd6bd682014-09-10 20:50:221024 blink_deps_rev = GetBlinkDEPSRevisionForChromiumRevision(self, rev)
[email protected]37ed3172013-09-24 23:49:301025
1026 while (GetBlinkRevisionForChromiumRevision(self, rev) > blink_deps_rev):
1027 idx = revisions.index(rev)
1028 if idx > 0:
1029 rev = revisions[idx-1]
1030 if rev not in revisions_final:
1031 revisions_final.insert(0, rev)
1032
1033 revisions_final.sort()
1034 return rev
[email protected]b2fe7f22011-10-25 22:58:311035
[email protected]4df583c2014-07-31 17:11:551036
[email protected]5980b752014-07-02 00:34:401037def GetChromiumRevision(context, url):
[email protected]801fb652012-07-20 20:13:501038 """Returns the chromium revision read from given URL."""
1039 try:
1040 # Location of the latest build revision number
[email protected]5980b752014-07-02 00:34:401041 latest_revision = urllib.urlopen(url).read()
1042 if latest_revision.isdigit():
1043 return int(latest_revision)
1044 return context.GetSVNRevisionFromGitHash(latest_revision)
[email protected]4df583c2014-07-31 17:11:551045 except Exception:
Raul Tambre57e09d62019-09-22 17:18:521046 print('Could not determine latest revision. This could be bad...')
[email protected]801fb652012-07-20 20:13:501047 return 999999999
1048
Bruce Dawsoncd63ea22020-10-26 16:37:411049
1050def GetRevision(revision_text):
1051 """Translates from a text description of a revision to an integral revision
1052 number. Currently supported formats are a number (i.e.; '782793') or a
1053 milestone specifier (i.e.; 'M85') or a full version string
1054 (i.e. '85.0.4183.121')."""
1055
1056 # Check if we already have a revision number, such as when -g or -b is
1057 # omitted.
1058 if type(revision_text) == type(0):
1059 return revision_text
1060
1061 # Translate from stable milestone name to the latest version number released
1062 # for that milestone, i.e.; 'M85' to '85.0.4183.121'.
1063 if revision_text[:1].upper() == 'M':
1064 milestone = revision_text[1:]
1065 response = urllib.urlopen(VERSION_HISTORY_URL)
1066 version_history = json.loads(response.read())
1067 version_matcher = re.compile(
1068 '.*versions/(\d*)\.(\d*)\.(\d*)\.(\d*)/releases.*')
1069 for version in version_history['releases']:
1070 match = version_matcher.match(version['name'])
1071 # There will be multiple versions of each milestone, but we just grab the
1072 # first one that we see which will be the most recent version. If you need
1073 # more granularity then specify a full version number or revision number.
1074 if match and match.groups()[0] == milestone:
1075 revision_text = '.'.join(match.groups())
1076 break
1077 if revision_text[:1].upper() == 'M':
1078 raise Exception('No stable release matching %s found.' % revision_text)
1079
1080 # Translate from version number to commit position, also known as revision
1081 # number.
1082 if len(revision_text.split('.')) == 4:
1083 response = urllib.urlopen(OMAHA_REVISIONS_URL % revision_text)
1084 revision_details = json.loads(response.read())
1085 revision_text = revision_details['chromium_base_position']
1086
1087 # Translate from text commit position to integer commit position.
1088 return int(revision_text)
1089
1090
pshenoycd6bd682014-09-10 20:50:221091def GetGitHashFromSVNRevision(svn_revision):
1092 crrev_url = CRREV_URL + str(svn_revision)
1093 url = urllib.urlopen(crrev_url)
1094 if url.getcode() == 200:
1095 data = json.loads(url.read())
1096 if 'git_sha' in data:
1097 return data['git_sha']
1098
pshenoy9ce271f2014-09-02 22:14:051099def PrintChangeLog(min_chromium_rev, max_chromium_rev):
1100 """Prints the changelog URL."""
1101
Raul Tambre57e09d62019-09-22 17:18:521102 print(' ' + CHANGELOG_URL % (GetGitHashFromSVNRevision(min_chromium_rev),
1103 GetGitHashFromSVNRevision(max_chromium_rev)))
1104
pshenoy9ce271f2014-09-02 22:14:051105
elawrence446bcc32017-04-14 17:18:511106def error_internal_option(option, opt, value, parser):
[email protected]fb61fc32019-04-18 19:47:201107 raise optparse.OptionValueError(
1108 'The -o and -r options are only\navailable in the internal version of '
1109 'this script. Google\nemployees should visit http://go/bisect-builds '
1110 'for\nconfiguration instructions.')
[email protected]801fb652012-07-20 20:13:501111
[email protected]67e0bc62009-09-03 22:06:091112def main():
[email protected]2c1d2732009-10-29 19:52:171113 usage = ('%prog [options] [-- chromium-options]\n'
[email protected]887c9182013-02-12 20:30:311114 'Perform binary search on the snapshot builds to find a minimal\n'
1115 'range of revisions where a behavior change happened. The\n'
1116 'behaviors are described as "good" and "bad".\n'
1117 'It is NOT assumed that the behavior of the later revision is\n'
[email protected]09c58da2013-01-07 21:30:171118 'the bad one.\n'
[email protected]178aab72010-10-08 17:21:381119 '\n'
[email protected]887c9182013-02-12 20:30:311120 'Revision numbers should use\n'
[email protected]887c9182013-02-12 20:30:311121 ' SVN revisions (e.g. 123456) for chromium builds, from trunk.\n'
1122 ' Use base_trunk_revision from http://omahaproxy.appspot.com/\n'
1123 ' for earlier revs.\n'
1124 ' Chrome\'s about: build number and omahaproxy branch_revision\n'
1125 ' are incorrect, they are from branches.\n'
1126 '\n'
Bruce Dawsone3573052020-06-29 23:14:351127 'Use "-- <args-to-pass-to-chromium>" to pass arbitrary extra \n'
1128 'arguments to the test binaries.\n'
1129 'E.g., add "-- --no-first-run" to bypass the first run prompts.')
[email protected]7ad66a72009-09-04 17:52:331130 parser = optparse.OptionParser(usage=usage)
[email protected]1a45d222009-09-19 01:58:571131 # Strangely, the default help output doesn't include the choice list.
mikecasea8cd284c2014-12-02 21:30:581132 choices = ['mac', 'mac64', 'win', 'win64', 'linux', 'linux64', 'linux-arm',
dmazzoni76e907d2015-01-22 08:14:491133 'chromeos']
[email protected]7ad66a72009-09-04 17:52:331134 parser.add_option('-a', '--archive',
[email protected]4df583c2014-07-31 17:11:551135 choices=choices,
1136 help='The buildbot archive to bisect [%s].' %
1137 '|'.join(choices))
Bruce Dawsoncd63ea22020-10-26 16:37:411138 parser.add_option('-b',
1139 '--bad',
[email protected]4df583c2014-07-31 17:11:551140 type='str',
1141 help='A bad revision to start bisection. '
Bruce Dawsoncd63ea22020-10-26 16:37:411142 'May be earlier or later than the good revision. '
1143 'Default is HEAD. Can be a revision number, milestone '
1144 'name (eg. M85, matches the most recent stable release of '
1145 'that milestone) or version number (eg. 85.0.4183.121)')
[email protected]4df583c2014-07-31 17:11:551146 parser.add_option('-f', '--flash_path',
1147 type='str',
1148 help='Absolute path to a recent Adobe Pepper Flash '
1149 'binary to be used in this bisection (e.g. '
1150 'on Windows C:\...\pepflashplayer.dll and on Linux '
1151 '/opt/google/chrome/PepperFlash/'
1152 'libpepflashplayer.so).')
Bruce Dawsoncd63ea22020-10-26 16:37:411153 parser.add_option('-g',
1154 '--good',
[email protected]4df583c2014-07-31 17:11:551155 type='str',
1156 help='A good revision to start bisection. ' +
Bruce Dawsoncd63ea22020-10-26 16:37:411157 'May be earlier or later than the bad revision. ' +
1158 'Default is 0. Can be a revision number, milestone '
1159 'name (eg. M85, matches the most recent stable release of '
1160 'that milestone) or version number (eg. 85.0.4183.121)')
[email protected]4df583c2014-07-31 17:11:551161 parser.add_option('-p', '--profile', '--user-data-dir',
1162 type='str',
1163 default='profile',
1164 help='Profile to use; this will not reset every run. '
1165 'Defaults to a clean profile.')
1166 parser.add_option('-t', '--times',
1167 type='int',
1168 default=1,
1169 help='Number of times to run each build before asking '
1170 'if it\'s good or bad. Temporary profiles are reused.')
Bruce Dawsone3573052020-06-29 23:14:351171 parser.add_option('-c',
1172 '--command',
[email protected]4df583c2014-07-31 17:11:551173 type='str',
1174 default='%p %a',
1175 help='Command to execute. %p and %a refer to Chrome '
Bruce Dawsone3573052020-06-29 23:14:351176 'executable and specified extra arguments respectively. '
1177 'Use %s to specify all extra arguments as one string. '
1178 'Defaults to "%p %a". Note that any extra paths specified '
1179 'should be absolute. If you just need to append an '
1180 'argument to the Chrome command line use "-- '
1181 '<args-to-pass-to-chromium>" instead.')
[email protected]4df583c2014-07-31 17:11:551182 parser.add_option('-l', '--blink',
1183 action='store_true',
1184 help='Use Blink bisect instead of Chromium. ')
1185 parser.add_option('', '--not-interactive',
1186 action='store_true',
1187 default=False,
1188 help='Use command exit code to tell good/bad revision.')
[email protected]011886692014-08-01 21:00:211189 parser.add_option('--asan',
1190 dest='asan',
1191 action='store_true',
1192 default=False,
1193 help='Allow the script to bisect ASAN builds')
rob724c9062015-01-22 00:26:421194 parser.add_option('--use-local-cache',
1195 dest='use_local_cache',
[email protected]6a7a5d62014-07-09 04:45:501196 action='store_true',
1197 default=False,
rob724c9062015-01-22 00:26:421198 help='Use a local file in the current directory to cache '
1199 'a list of known revisions to speed up the '
1200 'initialization of this script.')
skobes21b5cdfb2016-03-21 23:13:021201 parser.add_option('--verify-range',
1202 dest='verify_range',
1203 action='store_true',
1204 default=False,
1205 help='Test the first and last revisions in the range ' +
1206 'before proceeding with the bisect.')
elawrence446bcc32017-04-14 17:18:511207 parser.add_option("-r", action="callback", callback=error_internal_option)
1208 parser.add_option("-o", action="callback", callback=error_internal_option)
[email protected]b3b20512013-08-26 18:51:041209
[email protected]7ad66a72009-09-04 17:52:331210 (opts, args) = parser.parse_args()
1211
1212 if opts.archive is None:
Raul Tambre57e09d62019-09-22 17:18:521213 print('Error: missing required parameter: --archive')
1214 print()
[email protected]7ad66a72009-09-04 17:52:331215 parser.print_help()
1216 return 1
1217
[email protected]011886692014-08-01 21:00:211218 if opts.asan:
1219 supported_platforms = ['linux', 'mac', 'win']
1220 if opts.archive not in supported_platforms:
Raul Tambre57e09d62019-09-22 17:18:521221 print('Error: ASAN bisecting only supported on these platforms: [%s].' %
1222 ('|'.join(supported_platforms)))
[email protected]011886692014-08-01 21:00:211223 return 1
[email protected]011886692014-08-01 21:00:211224
1225 if opts.asan:
1226 base_url = ASAN_BASE_URL
1227 elif opts.blink:
[email protected]4c6fec6b2013-09-17 17:44:081228 base_url = WEBKIT_BASE_URL
1229 else:
1230 base_url = CHROMIUM_BASE_URL
1231
[email protected]183706d92011-06-10 13:06:221232 # Create the context. Initialize 0 for the revisions as they are set below.
[email protected]2e0f2672014-08-13 20:32:581233 context = PathContext(base_url, opts.archive, opts.good, opts.bad,
Jason Kersey97bb027a2016-05-11 20:10:431234 opts.asan, opts.use_local_cache,
vitalybuka4d1e1e412015-07-06 17:21:061235 opts.flash_path)
mikecasea8cd284c2014-12-02 21:30:581236
[email protected]67e0bc62009-09-03 22:06:091237 # Pick a starting point, try to get HEAD for this.
[email protected]2e0f2672014-08-13 20:32:581238 if not opts.bad:
1239 context.bad_revision = '999.0.0.0'
1240 context.bad_revision = GetChromiumRevision(
1241 context, context.GetLastChangeURL())
[email protected]67e0bc62009-09-03 22:06:091242
1243 # Find out when we were good.
[email protected]2e0f2672014-08-13 20:32:581244 if not opts.good:
Jason Kersey97bb027a2016-05-11 20:10:431245 context.good_revision = 0
[email protected]801fb652012-07-20 20:13:501246
[email protected]fc3702e2013-11-09 04:23:001247 if opts.flash_path:
[email protected]2e0f2672014-08-13 20:32:581248 msg = 'Could not find Flash binary at %s' % opts.flash_path
1249 assert os.path.exists(opts.flash_path), msg
[email protected]fc3702e2013-11-09 04:23:001250
Bruce Dawsoncd63ea22020-10-26 16:37:411251 context.good_revision = GetRevision(context.good_revision)
1252 context.bad_revision = GetRevision(context.bad_revision)
[email protected]801fb652012-07-20 20:13:501253
[email protected]5e93cf162012-01-28 02:16:561254 if opts.times < 1:
1255 print('Number of times to run (%d) must be greater than or equal to 1.' %
1256 opts.times)
1257 parser.print_help()
1258 return 1
1259
skobes21b5cdfb2016-03-21 23:13:021260 if opts.not_interactive:
1261 evaluator = DidCommandSucceed
1262 elif opts.asan:
[email protected]011886692014-08-01 21:00:211263 evaluator = IsGoodASANBuild
1264 else:
1265 evaluator = AskIsGoodBuild
1266
[email protected]2e0f2672014-08-13 20:32:581267 # Save these revision numbers to compare when showing the changelog URL
1268 # after the bisect.
1269 good_rev = context.good_revision
1270 bad_rev = context.bad_revision
1271
Bruce Dawsoncd63ea22020-10-26 16:37:411272 print('Scanning from %d to %d (%d revisions).' %
1273 (good_rev, bad_rev, abs(good_rev - bad_rev)))
1274
[email protected]2e0f2672014-08-13 20:32:581275 (min_chromium_rev, max_chromium_rev, context) = Bisect(
1276 context, opts.times, opts.command, args, opts.profile,
skobes21b5cdfb2016-03-21 23:13:021277 evaluator, opts.verify_range)
[email protected]67e0bc62009-09-03 22:06:091278
[email protected]ff50d1c2013-04-17 18:49:361279 # Get corresponding blink revisions.
[email protected]b2fe7f22011-10-25 22:58:311280 try:
[email protected]4c6fec6b2013-09-17 17:44:081281 min_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1282 min_chromium_rev)
1283 max_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1284 max_chromium_rev)
[email protected]4df583c2014-07-31 17:11:551285 except Exception:
[email protected]b2fe7f22011-10-25 22:58:311286 # Silently ignore the failure.
[email protected]ff50d1c2013-04-17 18:49:361287 min_blink_rev, max_blink_rev = 0, 0
[email protected]b2fe7f22011-10-25 22:58:311288
[email protected]3bdaa4752013-09-30 20:13:361289 if opts.blink:
1290 # We're done. Let the user know the results in an official manner.
1291 if good_rev > bad_rev:
Raul Tambre57e09d62019-09-22 17:18:521292 print(DONE_MESSAGE_GOOD_MAX % (str(min_blink_rev), str(max_blink_rev)))
[email protected]3bdaa4752013-09-30 20:13:361293 else:
Raul Tambre57e09d62019-09-22 17:18:521294 print(DONE_MESSAGE_GOOD_MIN % (str(min_blink_rev), str(max_blink_rev)))
[email protected]eadd95d2012-11-02 22:42:091295
Raul Tambre57e09d62019-09-22 17:18:521296 print('BLINK CHANGELOG URL:')
1297 print(' ' + BLINK_CHANGELOG_URL % (max_blink_rev, min_blink_rev))
[email protected]3bdaa4752013-09-30 20:13:361298
[email protected]d0149c5c2012-05-29 21:12:111299 else:
[email protected]3bdaa4752013-09-30 20:13:361300 # We're done. Let the user know the results in an official manner.
1301 if good_rev > bad_rev:
Raul Tambre57e09d62019-09-22 17:18:521302 print(DONE_MESSAGE_GOOD_MAX % (str(min_chromium_rev),
1303 str(max_chromium_rev)))
[email protected]3bdaa4752013-09-30 20:13:361304 else:
Raul Tambre57e09d62019-09-22 17:18:521305 print(DONE_MESSAGE_GOOD_MIN % (str(min_chromium_rev),
1306 str(max_chromium_rev)))
[email protected]3bdaa4752013-09-30 20:13:361307 if min_blink_rev != max_blink_rev:
[email protected]4df583c2014-07-31 17:11:551308 print ('NOTE: There is a Blink roll in the range, '
1309 'you might also want to do a Blink bisect.')
[email protected]3bdaa4752013-09-30 20:13:361310
Raul Tambre57e09d62019-09-22 17:18:521311 print('CHANGELOG URL:')
Jason Kersey97bb027a2016-05-11 20:10:431312 PrintChangeLog(min_chromium_rev, max_chromium_rev)
[email protected]cb155a82011-11-29 17:25:341313
[email protected]4df583c2014-07-31 17:11:551314
[email protected]67e0bc62009-09-03 22:06:091315if __name__ == '__main__':
[email protected]7ad66a72009-09-04 17:52:331316 sys.exit(main())