blob: 0cd3d846b3050bfd2b6fb6e430a41279df9224c1 [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import base64
import functools
import io
import json
import os
import re
import subprocess
import sys
import tempfile
import unittest
import urllib.request
from unittest.mock import (ANY, Mock, MagicMock, mock_open, patch, call,
PropertyMock)
bisect_builds = __import__('bisect-builds')
if 'NO_MOCK_SERVER' not in os.environ:
maybe_patch = patch
else:
# SetupEnvironment for gsutil to connect to real server.
options = bisect_builds.ParseCommandLine(['-a', 'linux64', '-g', '1'])
bisect_builds.SetupEnvironment(options)
bisect_builds.SetupAndroidEnvironment()
# Mock object that always wraps for the spec.
# This will pass the call through and ignore the return_value and side_effect.
class WrappedMock(MagicMock):
def __init__(self,
spec=None,
return_value=None,
side_effect=None,
*args,
**kwargs):
wraps = kwargs.pop('wraps', spec)
super().__init__(spec, *args, **kwargs, wraps=wraps)
maybe_patch = functools.partial(patch, spec=True, new_callable=WrappedMock)
maybe_patch.object = functools.partial(patch.object,
spec=True,
new_callable=WrappedMock)
class BisectTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
# Patch the name pattern for pkgutil to accept "bisect-builds" as module
# name.
if sys.version_info[:2] > (3, 8):
dotted_words = r'(?!\d)([\w-]+)(\.(?!\d)(\w+))*'
name_pattern = re.compile(
f'^(?P<pkg>{dotted_words})'
f'(?P<cln>:(?P<obj>{dotted_words})?)?$', re.UNICODE)
cls.name_pattern_patcher = patch('pkgutil._NAME_PATTERN', name_pattern)
cls.name_pattern_patcher.start()
# patch cache filename to prevent pollute working dir.
fd, cls.tmp_cache_file = tempfile.mkstemp(suffix='.json')
os.close(fd)
cls.cache_filename_patcher = patch(
'bisect-builds.ArchiveBuild._rev_list_cache_filename',
new=PropertyMock(return_value=cls.tmp_cache_file))
cls.cache_filename_patcher.start()
@classmethod
def tearDownClass(cls):
if sys.version_info[:2] > (3, 8):
cls.name_pattern_patcher.stop()
cls.cache_filename_patcher.stop()
os.unlink(cls.tmp_cache_file)
class BisectTest(BisectTestCase):
max_rev = 10000
def setUp(self):
self.patchers = []
self.patchers.append(patch('bisect-builds.DownloadJob._fetch'))
self.patchers.append(
patch('bisect-builds.ArchiveBuild.run_revision',
return_value=(0, '', '')))
self.patchers.append(
patch('bisect-builds.SnapshotBuild._get_rev_list',
return_value=range(self.max_rev)))
for each in self.patchers:
each.start()
def tearDown(self):
for each in self.patchers:
each.stop()
def bisect(self, good_rev, bad_rev, evaluate, num_runs=1):
options = bisect_builds.ParseCommandLine([
'-a', 'linux64', '-g',
str(good_rev), '-b',
str(bad_rev), '--times',
str(num_runs), '--no-local-cache'
])
archive_build = bisect_builds.create_archive_build(options)
(minrev, maxrev) = bisect_builds.Bisect(archive_build=archive_build,
evaluate=evaluate,
try_args=options.args)
return (minrev, maxrev)
@patch('builtins.print')
def testBisectConsistentAnswer(self, mock_print):
def get_steps():
steps = []
for call in mock_print.call_args_list:
if call.args and call.args[0].startswith('You have'):
steps.append(int(re.search(r'(\d+) steps', call.args[0])[1]))
return steps
self.assertEqual(self.bisect(1000, 100, lambda *args: 'g'), (100, 101))
self.assertSequenceEqual(get_steps(), range(10, 1, -1))
mock_print.reset_mock()
self.assertEqual(self.bisect(100, 1000, lambda *args: 'b'), (100, 101))
self.assertSequenceEqual(get_steps(), range(10, 0, -1))
mock_print.reset_mock()
self.assertEqual(self.bisect(2000, 200, lambda *args: 'b'), (1999, 2000))
self.assertSequenceEqual(get_steps(), range(11, 0, -1))
mock_print.reset_mock()
self.assertEqual(self.bisect(200, 2000, lambda *args: 'g'), (1999, 2000))
self.assertSequenceEqual(get_steps(), range(11, 1, -1))
@patch('bisect-builds.ArchiveBuild.run_revision', return_value=(0, '', ''))
def test_bisect_should_retry(self, mock_run_revision):
evaluator = Mock(side_effect='rgrgrbr')
self.assertEqual(self.bisect(9, 1, evaluator), (2, 3))
tested_revisions = [c.args[0] for c in evaluator.call_args_list]
self.assertEqual(tested_revisions, [5, 5, 3, 3, 2, 2])
self.assertEqual(mock_run_revision.call_count, 6)
evaluator = Mock(side_effect='rgrrrgrbr')
self.assertEqual(self.bisect(1, 10, evaluator), (8, 9))
tested_revisions = [c.args[0] for c in evaluator.call_args_list]
self.assertEqual(tested_revisions, [6, 6, 8, 8, 8, 8, 9, 9])
def test_bisect_should_unknown(self):
evaluator = Mock(side_effect='uuuggggg')
self.assertEqual(self.bisect(9, 1, evaluator), (1, 2))
tested_revisions = [c.args[0] for c in evaluator.call_args_list]
self.assertEqual(tested_revisions, [5, 3, 6, 7, 2])
evaluator = Mock(side_effect='uuugggggg')
self.assertEqual(self.bisect(1, 9, evaluator), (8, 9))
tested_revisions = [c.args[0] for c in evaluator.call_args_list]
self.assertEqual(tested_revisions, [5, 7, 4, 3, 8])
def test_bisect_should_quit(self):
evaluator = Mock(side_effect=SystemExit())
with self.assertRaises(SystemExit):
self.assertEqual(self.bisect(9, 1, evaluator), (None, None))
def test_edge_cases(self):
with self.assertRaises(bisect_builds.BisectException):
self.assertEqual(self.bisect(1, 1, Mock()), (1, 1))
self.assertEqual(self.bisect(2, 1, Mock()), (1, 2))
self.assertEqual(self.bisect(1, 2, Mock()), (1, 2))
class DownloadJobTest(BisectTestCase):
@patch('bisect-builds.gsutil_download')
def test_fetch_gsutil(self, mock_gsutil_download):
fetch = bisect_builds.DownloadJob('gs://some-file.zip', 123)
fetch.start()
fetch.wait_for()
mock_gsutil_download.assert_called_once()
@patch('urllib.request.urlretrieve')
def test_fetch_http(self, mock_urlretrieve):
fetch = bisect_builds.DownloadJob('http://some-file.zip', 123)
fetch.start()
fetch.wait_for()
mock_urlretrieve.assert_called_once()
@patch('tempfile.mkstemp', return_value=(321, 'some-file.zip'))
@patch('urllib.request.urlretrieve')
@patch('os.close')
@patch('os.unlink')
def test_should_del(self, mock_unlink, mock_close, mock_urlretrieve,
mock_mkstemp):
fetch = bisect_builds.DownloadJob('http://some-file.zip', 123)
fetch.start().wait_for()
fetch.stop()
mock_mkstemp.assert_called_once()
mock_close.assert_called_once()
mock_urlretrieve.assert_called_once()
mock_unlink.assert_called_with('some-file.zip')
@patch('urllib.request.urlretrieve')
def test_stop_wait_for_should_be_able_to_reenter(self, mock_urlretrieve):
fetch = bisect_builds.DownloadJob('http://some-file.zip', 123)
fetch.start()
fetch.wait_for()
fetch.wait_for()
fetch.stop()
fetch.stop()
@patch('tempfile.mkstemp',
side_effect=[(321, 'some-file.apks'), (123, 'file2.apk')])
@patch('bisect-builds.gsutil_download')
@patch('os.close')
@patch('os.unlink')
def test_should_support_multiple_files(self, mock_unlink, mock_close,
mock_gsutil, mock_mkstemp):
urls = {
'trichrome':
('gs://chrome-unsigned/android-B0urB0N/129.0.6626.0/high-arm_64/'
'TrichromeChromeGoogle6432Stable.apks'),
'trichrome_library':
('gs://chrome-unsigned/android-B0urB0N/129.0.6626.0/high-arm_64/'
'TrichromeLibraryGoogle6432Stable.apk'),
}
fetch = bisect_builds.DownloadJob(urls, 123)
result = fetch.start().wait_for()
fetch.stop()
self.assertDictEqual(result, {
'trichrome': 'some-file.apks',
'trichrome_library': 'file2.apk',
})
self.assertEqual(mock_mkstemp.call_count, 2)
self.assertEqual(mock_close.call_count, 2)
mock_unlink.assert_has_calls(
[call('some-file.apks'), call('file2.apk')], any_order=True)
self.assertEqual(mock_gsutil.call_count, 2)
@patch(
"urllib.request.urlopen",
side_effect=urllib.request.HTTPError('url', 404, 'Not Found', None, None),
)
@patch('subprocess.Popen', spec=subprocess.Popen)
@patch('bisect-builds.GSUTILS_PATH', new='/some/path')
def test_download_failure_should_raised(self, mock_Popen, mock_urlopen):
fetch = bisect_builds.DownloadJob('http://some-file.zip', 123)
with self.assertRaises(urllib.request.HTTPError):
fetch.start().wait_for()
mock_Popen.return_value.communicate.return_value = (b'', b'status=403')
mock_Popen.return_value.returncode = 1
fetch = bisect_builds.DownloadJob('gs://some-file.zip', 123)
with self.assertRaises(bisect_builds.BisectException):
fetch.start().wait_for()
class ArchiveBuildTest(BisectTestCase):
def setUp(self):
self.patcher = patch.multiple(
bisect_builds.ArchiveBuild,
__abstractmethods__=set(),
build_type='release',
_get_rev_list=Mock(return_value=list(map(str, range(10)))),
_rev_list_cache_key='abc')
self.patcher.start()
def tearDown(self):
self.patcher.stop()
def create_build(self, *args):
args = ['-a', 'linux64', '-g', '0', '-b', '9', *args]
options = bisect_builds.ParseCommandLine(args)
return bisect_builds.ArchiveBuild(options)
def test_cache_should_not_work_if_not_enabled(self):
build = self.create_build('--no-local-cache')
self.assertFalse(build.use_local_cache)
with patch('builtins.open') as m:
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
bisect_builds.ArchiveBuild._get_rev_list.assert_called_once()
m.assert_not_called()
def test_cache_should_save_and_load(self):
build = self.create_build()
self.assertTrue(build.use_local_cache)
# Load the non-existent cache and write to it.
cached_data = []
# The cache file would be opened 3 times:
# 1. read by _load_rev_list_cache
# 2. read by _save_rev_list_cache for existing cache
# 3. write by _save_rev_list_cache
write_mock = MagicMock()
write_mock.__enter__().write.side_effect = lambda d: cached_data.append(d)
with patch('builtins.open',
side_effect=[FileNotFoundError, FileNotFoundError, write_mock]):
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
bisect_builds.ArchiveBuild._get_rev_list.assert_called_once()
cached_json = json.loads(''.join(cached_data))
self.assertDictEqual(cached_json, {'abc': [str(x) for x in range(10)]})
# Load cache with cached data.
build = self.create_build('--use-local-cache')
bisect_builds.ArchiveBuild._get_rev_list.reset_mock()
with patch('builtins.open', mock_open(read_data=''.join(cached_data))):
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
bisect_builds.ArchiveBuild._get_rev_list.assert_not_called()
@patch.object(bisect_builds.ArchiveBuild, '_load_rev_list_cache')
@patch.object(bisect_builds.ArchiveBuild, '_save_rev_list_cache')
@patch.object(bisect_builds.ArchiveBuild,
'_get_rev_list',
return_value=[str(x) for x in range(10)])
def test_should_request_partial_rev_list(self, mock_get_rev_list,
mock_save_rev_list_cache,
mock_load_rev_list_cache):
build = self.create_build('--no-local-cache')
# missing latest
mock_load_rev_list_cache.return_value = [str(x) for x in range(5)]
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
mock_get_rev_list.assert_called_with('4', '9')
# missing old and latest
mock_load_rev_list_cache.return_value = [str(x) for x in range(1, 5)]
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
mock_get_rev_list.assert_called_with('0', '9')
# missing old
mock_load_rev_list_cache.return_value = [str(x) for x in range(3, 10)]
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
mock_get_rev_list.assert_called_with('0', '3')
# no intersect
mock_load_rev_list_cache.return_value = ['c', 'd', 'e']
self.assertEqual(build.get_rev_list(), [str(x) for x in range(10)])
mock_save_rev_list_cache.assert_called_with([str(x) for x in range(10)] +
['c', 'd', 'e'])
mock_get_rev_list.assert_called_with('0', 'c')
@patch.object(bisect_builds.ArchiveBuild, '_get_rev_list', return_value=[])
def test_should_raise_error_when_no_rev_list(self, mock_get_rev_list):
build = self.create_build('--no-local-cache')
with self.assertRaises(bisect_builds.BisectException):
build.get_rev_list()
mock_get_rev_list.assert_any_call('0', '9')
mock_get_rev_list.assert_any_call()
@unittest.skipIf('NO_MOCK_SERVER' not in os.environ,
'The test is to ensure NO_MOCK_SERVER working correctly')
@maybe_patch('bisect-builds.GetRevisionFromVersion', return_value=123)
def test_no_mock(self, mock_GetRevisionFromVersion):
self.assertEqual(bisect_builds.GetRevisionFromVersion('127.0.6533.74'),
1313161)
mock_GetRevisionFromVersion.assert_called()
@patch('bisect-builds.ArchiveBuild._install_revision')
@patch('bisect-builds.ArchiveBuild._launch_revision',
return_value=(1, '', ''))
def test_run_revision_should_return_early(self, mock_launch_revision,
mock_install_revision):
build = self.create_build()
build.run_revision('', '', [])
mock_launch_revision.assert_called_once()
@patch('bisect-builds.ArchiveBuild._install_revision')
@patch('bisect-builds.ArchiveBuild._launch_revision',
return_value=(0, '', ''))
def test_run_revision_should_do_all_runs(self, mock_launch_revision,
mock_install_revision):
build = self.create_build('--time', '10')
build.run_revision('', '', [])
self.assertEqual(mock_launch_revision.call_count, 10)
@patch('bisect-builds.UnzipFilenameToDir')
@patch('glob.glob', return_value=['temp-dir/linux64/chrome'])
@patch('os.path.abspath', return_value='/tmp/temp-dir/linux64/chrome')
def test_install_revision_should_unzip_and_search_executable(
self, mock_abspath, mock_glob, mock_UnzipFilenameToDir):
build = self.create_build()
self.assertEqual(build._install_revision('some-file.zip', 'temp-dir'),
{'chrome': '/tmp/temp-dir/linux64/chrome'})
mock_UnzipFilenameToDir.assert_called_once_with('some-file.zip', 'temp-dir')
mock_glob.assert_called_once_with('temp-dir/*/chrome')
mock_abspath.assert_called_once_with('temp-dir/linux64/chrome')
@patch('bisect-builds.UnzipFilenameToDir')
@patch('glob.glob',
side_effect=[['temp-dir/chrome-linux64/chrome'],
['temp-dir/chromedriver_linux64/chromedriver']])
@patch('os.path.abspath',
side_effect=[
'/tmp/temp-dir/chrome-linux64/chrome',
'/tmp/temp-dir/chromedriver_linux64/chromedriver'
])
def test_install_chromedriver(self, mock_abspath, mock_glob,
mock_UnzipFilenameToDir):
build = self.create_build('--chromedriver')
self.assertEqual(
build._install_revision(
{
'chrome': 'some-file.zip',
'chromedriver': 'some-other-file.zip',
}, 'temp-dir'),
{
'chrome': '/tmp/temp-dir/chrome-linux64/chrome',
'chromedriver': '/tmp/temp-dir/chromedriver_linux64/chromedriver',
})
mock_UnzipFilenameToDir.assert_has_calls([
call('some-file.zip', 'temp-dir'),
call('some-other-file.zip', 'temp-dir'),
])
mock_glob.assert_has_calls([
call('temp-dir/*/chrome'),
call('temp-dir/*/chromedriver'),
])
mock_abspath.assert_has_calls([
call('temp-dir/chrome-linux64/chrome'),
call('temp-dir/chromedriver_linux64/chromedriver')
])
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_launch_revision_should_run_command(self, mock_Popen):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
build = self.create_build()
build._launch_revision('temp-dir', {'chrome': 'temp-dir/linux64/chrome'},
[])
mock_Popen.assert_called_once_with(
'temp-dir/linux64/chrome --user-data-dir=temp-dir/profile',
cwd=None,
shell=True,
bufsize=-1,
stdout=ANY,
stderr=ANY)
@unittest.skipIf(sys.platform.startswith('win'), 'This test is not for win')
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_launch_revision_should_run_command_for_mac(self, mock_Popen):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
build = self.create_build()
build._launch_revision(
'temp-dir', {
'chrome':
'temp-dir/full-build-mac/'
'Google Chrome.app/Contents/MacOS/Google Chrome'
}, [])
mock_Popen.assert_called_once_with(
"'temp-dir/full-build-mac/"
"Google Chrome.app/Contents/MacOS/Google Chrome'"
' --user-data-dir=temp-dir/profile',
cwd=None,
shell=True,
bufsize=-1,
stdout=ANY,
stderr=ANY)
@unittest.skipUnless(sys.platform.startswith('win'),
'This test is for win only')
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_launch_revision_should_run_command_for_win(self, mock_Popen):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
build = self.create_build()
build._launch_revision(
'C:\\temp-dir', {
'chrome': 'C:\\temp-dir\\full-build-win\\chrome.exe'
}, [])
mock_Popen.assert_called_once_with(
"C:\\temp-dir\\full-build-win\\chrome.exe "
'--user-data-dir=C:\\temp-dir/profile',
cwd=None,
shell=True,
bufsize=-1,
stdout=ANY,
stderr=ANY)
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_command_replacement(self, mock_Popen):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
build = self.create_build(
'--chromedriver', '-c',
'CHROMEDRIVER=%d BROWSER_EXECUTABLE_PATH=%p pytest %a')
build._launch_revision('/tmp', {
'chrome': '/tmp/chrome',
'chromedriver': '/tmp/chromedriver'
}, ['--args', '--args2="word 1"', 'word 2'])
if sys.platform.startswith('win'):
mock_Popen.assert_called_once_with(
'CHROMEDRIVER=/tmp/chromedriver BROWSER_EXECUTABLE_PATH=/tmp/chrome '
'pytest --user-data-dir=/tmp/profile --args "--args2=\\"word 1\\"" '
'"word 2"',
cwd=None,
shell=True,
bufsize=-1,
stdout=ANY,
stderr=ANY)
else:
mock_Popen.assert_called_once_with(
'CHROMEDRIVER=/tmp/chromedriver BROWSER_EXECUTABLE_PATH=/tmp/chrome '
'pytest --user-data-dir=/tmp/profile --args \'--args2="word 1"\' '
'\'word 2\'',
cwd=None,
shell=True,
bufsize=-1,
stdout=ANY,
stderr=ANY)
class ReleaseBuildTest(BisectTestCase):
def test_should_look_up_path_context(self):
options = bisect_builds.ParseCommandLine(
['-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.88'])
self.assertEqual(options.archive, 'linux64')
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ReleaseBuild)
self.assertEqual(build.binary_name, 'chrome')
self.assertEqual(build.listing_platform_dir, 'linux64/')
self.assertEqual(build.archive_name, 'chrome-linux64.zip')
self.assertEqual(build.archive_extract_dir, 'chrome-linux64')
@maybe_patch(
'bisect-builds.GsutilList',
return_value=[
'gs://chrome-unsigned/desktop-5c0tCh/%s/linux64/chrome-linux64.zip' %
x for x in ['127.0.6533.74', '127.0.6533.75', '127.0.6533.76']
])
def test_get_rev_list(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.76',
'--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ReleaseBuild)
self.assertEqual(build.get_rev_list(),
['127.0.6533.74', '127.0.6533.75', '127.0.6533.76'])
mock_GsutilList.assert_any_call('gs://chrome-unsigned/desktop-5c0tCh')
mock_GsutilList.assert_any_call(*[
'gs://chrome-unsigned/desktop-5c0tCh/%s/linux64/chrome-linux64.zip' % x
for x in ['127.0.6533.74', '127.0.6533.75', '127.0.6533.76']
],
ignore_fail=True)
self.assertEqual(mock_GsutilList.call_count, 2)
@patch('bisect-builds.GsutilList',
return_value=['127.0.6533.74', '127.0.6533.75', '127.0.6533.76'])
def test_should_save_and_load_cache(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.77',
'--use-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ReleaseBuild)
# Load the non-existent cache and write to it.
cached_data = []
write_mock = MagicMock()
write_mock.__enter__().write.side_effect = lambda d: cached_data.append(d)
with patch('builtins.open',
side_effect=[FileNotFoundError, FileNotFoundError, write_mock]):
self.assertEqual(build.get_rev_list(),
['127.0.6533.74', '127.0.6533.75', '127.0.6533.76'])
mock_GsutilList.assert_called()
cached_json = json.loads(''.join(cached_data))
self.assertDictEqual(
cached_json, {
build._rev_list_cache_key:
['127.0.6533.74', '127.0.6533.75', '127.0.6533.76']
})
# Load cache with cached data and merge with new data
mock_GsutilList.return_value = ['127.0.6533.76', '127.0.6533.77']
build = bisect_builds.create_archive_build(options)
with patch('builtins.open', mock_open(read_data=''.join(cached_data))):
self.assertEqual(
build.get_rev_list(),
['127.0.6533.74', '127.0.6533.75', '127.0.6533.76', '127.0.6533.77'])
print(mock_GsutilList.call_args)
mock_GsutilList.assert_any_call(
'gs://chrome-unsigned/desktop-5c0tCh'
'/127.0.6533.76/linux64/chrome-linux64.zip',
'gs://chrome-unsigned/desktop-5c0tCh'
'/127.0.6533.77/linux64/chrome-linux64.zip',
ignore_fail=True)
def test_get_download_url(self):
options = bisect_builds.ParseCommandLine(
['-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.77'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ReleaseBuild)
download_urls = build.get_download_url('127.0.6533.74')
self.assertEqual(
download_urls, 'gs://chrome-unsigned/desktop-5c0tCh'
'/127.0.6533.74/linux64/chrome-linux64.zip')
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.77',
'--chromedriver'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ReleaseBuild)
download_urls = build.get_download_url('127.0.6533.74')
self.assertEqual(
download_urls, {
'chrome':
'gs://chrome-unsigned/desktop-5c0tCh'
'/127.0.6533.74/linux64/chrome-linux64.zip',
'chromedriver':
'gs://chrome-unsigned/desktop-5c0tCh'
'/127.0.6533.74/linux64/chromedriver_linux64.zip',
})
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, 'stdout', ''))
def test_run_revision_with_real_zipfile(self, mock_run):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.77',
'--chromedriver', '-c', 'driver=%d prog=%p'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ReleaseBuild)
download_job = build.get_download_job('127.0.6533.74')
zip_file = download_job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(zip_file, tempdir, [])
self.assertRegex(mock_run.call_args.args[0],
r'driver=.+/chromedriver prog=.+/chrome')
class ArchiveBuildWithCommitPositionTest(BisectTestCase):
def setUp(self):
patch.multiple(bisect_builds.ArchiveBuildWithCommitPosition,
__abstractmethods__=set(),
build_type='release').start()
@maybe_patch('bisect-builds.GetRevisionFromVersion', return_value=1313161)
@maybe_patch('bisect-builds.GetChromiumRevision', return_value=999999999)
def test_should_convert_revision_as_commit_position(
self, mock_GetChromiumRevision, mock_GetRevisionFromVersion):
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '127.0.6533.74'])
build = bisect_builds.ArchiveBuildWithCommitPosition(options)
self.assertEqual(build.good_revision, 1313161)
self.assertEqual(build.bad_revision, 999999999)
mock_GetRevisionFromVersion.assert_called_once_with('127.0.6533.74')
mock_GetChromiumRevision.assert_called()
class OfficialBuildTest(BisectTestCase):
def test_should_lookup_path_context(self):
options = bisect_builds.ParseCommandLine(
['-o', '-a', 'linux64', '-g', '0', '-b', '10'])
self.assertEqual(options.archive, 'linux64')
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.OfficialBuild)
self.assertEqual(build.binary_name, 'chrome')
self.assertEqual(build.listing_platform_dir, 'linux-builder-perf/')
self.assertEqual(build.archive_name, 'chrome-perf-linux.zip')
self.assertEqual(build.archive_extract_dir, 'full-build-linux')
@maybe_patch('bisect-builds.GsutilList',
return_value=[
'full-build-linux_%d.zip' % x
for x in range(1313161, 1313164)
])
def test_get_rev_list(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'linux64', '-g', '1313161', '-b', '1313163',
'--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.OfficialBuild)
self.assertEqual(build.get_rev_list(), list(range(1313161, 1313164)))
mock_GsutilList.assert_called_once_with(
'gs://chrome-test-builds/official-by-commit/linux-builder-perf/')
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, 'stdout', ''))
def test_run_revision_with_real_zipfile(self, mock_run):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'linux64', '-g', '1313161', '-b', '1313163',
'--chromedriver', '-c', 'driver=%d prog=%p'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.OfficialBuild)
download_job = build.get_download_job(1334339)
zip_file = download_job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(zip_file, tempdir, [])
self.assertRegex(mock_run.call_args.args[0],
r'driver=.+/chromedriver prog=.+/chrome')
class SnapshotBuildTest(BisectTestCase):
def test_should_lookup_path_context(self):
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '0', '-b', '10'])
self.assertEqual(options.archive, 'linux64')
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
self.assertEqual(build.binary_name, 'chrome')
self.assertEqual(build.listing_platform_dir, 'Linux_x64/')
self.assertEqual(build.archive_name, 'chrome-linux.zip')
self.assertEqual(build.archive_extract_dir, 'chrome-linux')
CommonDataXMLContent = '''<?xml version='1.0' encoding='UTF-8'?>
<ListBucketResult xmlns='http://doc.s3.amazonaws.com/2006-03-01'>
<Name>chromium-browser-snapshots</Name>
<Prefix>Linux_x64/</Prefix>
<Marker></Marker>
<NextMarker></NextMarker>
<Delimiter>/</Delimiter>
<IsTruncated>true</IsTruncated>
<CommonPrefixes>
<Prefix>Linux_x64/1313161/</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>Linux_x64/1313163/</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>Linux_x64/1313185/</Prefix>
</CommonPrefixes>
</ListBucketResult>
'''
@maybe_patch('urllib.request.urlopen',
return_value=io.BytesIO(CommonDataXMLContent.encode('utf8')))
@patch('bisect-builds.GetChromiumRevision', return_value=1313185)
def test_get_rev_list(self, mock_GetChromiumRevision, mock_urlopen):
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '1313161', '-b', '1313185', '--no-local-cache'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
rev_list = build.get_rev_list()
mock_urlopen.assert_any_call(
'http://commondatastorage.googleapis.com/chromium-browser-snapshots/'
'?delimiter=/&prefix=Linux_x64/&marker=Linux_x64/1313161')
self.assertEqual(mock_urlopen.call_count, 1)
self.assertEqual(rev_list, [1313161, 1313163, 1313185])
@patch('bisect-builds.SnapshotBuild._fetch_and_parse',
return_value=([int(s)
for s in sorted([str(x) for x in range(1, 11)])], None))
def test_get_rev_list_should_start_from_a_marker(self, mock_fetch_and_parse):
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '0', '-b', '9', '--no-local-cache'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
rev_list = build._get_rev_list(0, 9)
self.assertEqual(rev_list, list(range(1, 10)))
mock_fetch_and_parse.assert_called_once_with(
'http://commondatastorage.googleapis.com/chromium-browser-snapshots/'
'?delimiter=/&prefix=Linux_x64/&marker=Linux_x64/0')
mock_fetch_and_parse.reset_mock()
rev_list = build._get_rev_list(1, 9)
self.assertEqual(rev_list, list(range(1, 10)))
mock_fetch_and_parse.assert_called_once_with(
'http://commondatastorage.googleapis.com/chromium-browser-snapshots/'
'?delimiter=/&prefix=Linux_x64/&marker=Linux_x64/1')
@patch('bisect-builds.SnapshotBuild._fetch_and_parse',
return_value=([int(s)
for s in sorted([str(x) for x in range(1, 11)])], None))
def test_get_rev_list_should_scan_all_pages(self, mock_fetch_and_parse):
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '3', '-b', '11', '--no-local-cache'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
rev_list = build._get_rev_list(0, 11)
self.assertEqual(sorted(rev_list), list(range(1, 11)))
mock_fetch_and_parse.assert_called_once_with(
'http://commondatastorage.googleapis.com/chromium-browser-snapshots/'
'?delimiter=/&prefix=Linux_x64/')
def test_get_download_url(self):
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '3', '-b', '11'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
download_urls = build.get_download_url(123)
self.assertEqual(
download_urls,
'http://commondatastorage.googleapis.com/chromium-browser-snapshots'
'/Linux_x64/123/chrome-linux.zip',
)
options = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '3', '-b', '11', '--chromedriver'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
download_urls = build.get_download_url(123)
self.assertDictEqual(
download_urls, {
'chrome':
'http://commondatastorage.googleapis.com/chromium-browser-snapshots'
'/Linux_x64/123/chrome-linux.zip',
'chromedriver':
'http://commondatastorage.googleapis.com/chromium-browser-snapshots'
'/Linux_x64/123/chromedriver_linux64.zip'
})
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, 'stdout', ''))
def test_run_revision_with_real_zipfile(self, mock_run):
options = bisect_builds.ParseCommandLine([
'-a', 'linux64', '-g', '1313161', '-b', '1313185', '--chromedriver',
'-c', 'driver=%d prog=%p'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
download_job = build.get_download_job(1313161)
zip_file = download_job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(zip_file, tempdir, [])
self.assertRegex(mock_run.call_args.args[0],
r'driver=.+/chromedriver prog=.+/chrome')
@patch('bisect-builds.GetChromiumRevision', return_value=1313185)
def test_get_bad_revision(self, mock_GetChromiumRevision):
options = bisect_builds.ParseCommandLine(['-a', 'linux64', '-g', '1313161'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
mock_GetChromiumRevision.assert_called_once_with(
'http://commondatastorage.googleapis.com/chromium-browser-snapshots'
'/Linux_x64/LAST_CHANGE')
self.assertEqual(build.bad_revision, 1313185)
class ChromeForTestingBuild(BisectTestCase):
def test_should_lookup_path_context(self):
options = bisect_builds.ParseCommandLine(
['-cft', '-a', 'linux64', '-g', '0', '-b', '10'])
self.assertEqual(options.archive, 'linux64')
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ChromeForTestingBuild)
self.assertEqual(build.binary_name, 'chrome')
self.assertEqual(build.listing_platform_dir, 'linux64/')
self.assertEqual(build.archive_name, 'chrome-linux64.zip')
self.assertEqual(build.chromedriver_binary_name, 'chromedriver')
self.assertEqual(build.chromedriver_archive_name,
'chromedriver-linux64.zip')
CommonDataXMLContent = '''<?xml version='1.0' encoding='UTF-8'?>
<ListBucketResult xmlns="http://doc.s3.amazonaws.com/2006-03-01">
<Name>chrome-for-testing-per-commit-public</Name>
<Prefix>linux64/</Prefix>
<Marker/>
<Delimiter>/</Delimiter>
<IsTruncated>false</IsTruncated>
<Contents>
<Key>linux64/LAST_CHANGE</Key>
<Generation>1733959087133532</Generation>
<MetaGeneration>1</MetaGeneration>
<LastModified>2024-12-11T23:18:07.235Z</LastModified>
<ETag>"dd60cb93e225ab33d7254beca56b507a"</ETag>
<Size>7</Size>
</Contents>
<CommonPrefixes>
<Prefix>linux64/r1390729/</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>linux64/r1390746/</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>linux64/r1390757/</Prefix>
</CommonPrefixes>
</ListBucketResult>
'''
@maybe_patch('urllib.request.urlopen',
return_value=io.BytesIO(CommonDataXMLContent.encode('utf8')))
@patch('bisect-builds.GetChromiumRevision', return_value=1390757)
def test_get_rev_list(self, mock_GetChromiumRevision, mock_urlopen):
options = bisect_builds.ParseCommandLine([
'-cft', '-a', 'linux64', '-g', '1390729', '-b', '1390757',
'--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ChromeForTestingBuild)
rev_list = build.get_rev_list()
mock_urlopen.assert_any_call(
'https://storage.googleapis.com/chrome-for-testing-per-commit-public/'
'?delimiter=/&prefix=linux64/&marker=linux64/r1390729')
self.assertEqual(mock_urlopen.call_count, 1)
self.assertEqual(rev_list, [1390729, 1390746, 1390757])
def test_get_marker(self):
options = bisect_builds.ParseCommandLine(
['-cft', '-a', 'linux64', '-g', '1', '-b', '3'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ChromeForTestingBuild)
self.assertEqual('linux64/r1390729',
build._get_marker_for_revision(1390729))
def test_get_download_url(self):
options = bisect_builds.ParseCommandLine(
['-cft', '-a', 'linux64', '-g', '3', '-b', '11'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ChromeForTestingBuild)
download_urls = build.get_download_url(123)
self.assertEqual(
download_urls,
'https://storage.googleapis.com/chrome-for-testing-per-commit-public'
'/linux64/r123/chrome-linux64.zip',
)
options = bisect_builds.ParseCommandLine(
['-cft', '-a', 'linux64', '-g', '3', '-b', '11', '--chromedriver'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ChromeForTestingBuild)
download_urls = build.get_download_url(123)
download_urls = build.get_download_url(123)
self.assertEqual(
download_urls, {
'chrome':
'https://storage.googleapis.com/chrome-for-testing-per-commit-public'
'/linux64/r123/chrome-linux64.zip',
'chromedriver':
'https://storage.googleapis.com/chrome-for-testing-per-commit-public'
'/linux64/r123/chromedriver-linux64.zip',
})
@patch('bisect-builds.GetChromiumRevision', return_value=1390757)
def test_get_bad_revision(self, mock_GetChromiumRevision):
options = bisect_builds.ParseCommandLine(
['-cft', '-a', 'linux64', '-g', '3'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ChromeForTestingBuild)
mock_GetChromiumRevision.assert_called_once_with(
'https://storage.googleapis.com/chrome-for-testing-per-commit-public'
'/linux64/LAST_CHANGE')
self.assertEqual(build.bad_revision, 1390757)
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, 'stdout', ''))
def test_run_revision_with_real_zipfile(self, mock_run):
options = bisect_builds.ParseCommandLine([
'--cft', '-a', 'linux64', '-g', '1', '--chromedriver', '-c',
'driver=%d prog=%p'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.SnapshotBuild)
download_job = build.get_download_job(build.bad_revision)
zip_file = download_job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(zip_file, tempdir, [])
self.assertRegex(mock_run.call_args.args[0],
r'driver=.+/chromedriver prog=.+/chrome')
class ASANBuildTest(BisectTestCase):
CommonDataXMLContent = '''<?xml version='1.0' encoding='UTF-8'?>
<ListBucketResult xmlns='http://doc.s3.amazonaws.com/2006-03-01'>
<Name>chromium-browser-asan</Name>
<Prefix>mac-release/asan-mac-release</Prefix>
<Marker></Marker>
<NextMarker></NextMarker>
<Delimiter>.zip</Delimiter>
<IsTruncated>true</IsTruncated>
<CommonPrefixes>
<Prefix>mac-release/asan-mac-release-1313186.zip</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>mac-release/asan-mac-release-1313195.zip</Prefix>
</CommonPrefixes>
<CommonPrefixes>
<Prefix>mac-release/asan-mac-release-1313210.zip</Prefix>
</CommonPrefixes>
</ListBucketResult>
'''
@maybe_patch('urllib.request.urlopen',
return_value=io.BytesIO(CommonDataXMLContent.encode('utf8')))
def test_get_rev_list(self, mock_urlopen):
options = bisect_builds.ParseCommandLine([
'--asan', '-a', 'mac', '-g', '1313161', '-b', '1313210',
'--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.ASANBuild)
rev_list = build.get_rev_list()
# print(mock_urlopen.call_args_list)
mock_urlopen.assert_any_call(
'http://commondatastorage.googleapis.com/chromium-browser-asan/'
'?delimiter=.zip&prefix=mac-release/asan-mac-release'
'&marker=mac-release/asan-mac-release-1313161.zip')
self.assertEqual(mock_urlopen.call_count, 1)
self.assertEqual(rev_list, [1313186, 1313195, 1313210])
class AndroidBuildTest(BisectTestCase):
def setUp(self):
# patch for devil_imports
self.patchers = []
flag_changer_patcher = maybe_patch('bisect-builds.flag_changer',
create=True)
self.patchers.append(flag_changer_patcher)
self.mock_flag_changer = flag_changer_patcher.start()
chrome_patcher = maybe_patch('bisect-builds.chrome', create=True)
self.patchers.append(chrome_patcher)
self.mock_chrome = chrome_patcher.start()
version_codes_patcher = maybe_patch('bisect-builds.version_codes',
create=True)
self.patchers.append(version_codes_patcher)
self.mock_version_codes = version_codes_patcher.start()
self.mock_version_codes.LOLLIPOP = 21
self.mock_version_codes.NOUGAT = 24
self.mock_version_codes.PIE = 28
self.mock_version_codes.Q = 29
initial_android_device_patcher = patch(
'bisect-builds.InitializeAndroidDevice')
self.patchers.append(initial_android_device_patcher)
self.mock_initial_android_device = initial_android_device_patcher.start()
self.device = self.mock_initial_android_device.return_value
self.set_sdk_level(bisect_builds.version_codes.Q)
def set_sdk_level(self, level):
self.device.build_version_sdk = level
def tearDown(self):
for patcher in self.patchers:
patcher.stop()
class AndroidReleaseBuildTest(AndroidBuildTest):
def setUp(self):
super().setUp()
self.set_sdk_level(bisect_builds.version_codes.PIE)
@maybe_patch(
'bisect-builds.GsutilList',
return_value=[
'gs://chrome-signed/android-B0urB0N/%s/arm_64/MonochromeStable.apk' %
x for x in ['127.0.6533.76', '127.0.6533.78', '127.0.6533.79']
])
def test_get_android_rev_list(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64', '--apk', 'chrome_stable', '-g',
'127.0.6533.76', '-b', '127.0.6533.79', '--signed', '--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidReleaseBuild)
self.assertEqual(build.get_rev_list(),
['127.0.6533.76', '127.0.6533.78', '127.0.6533.79'])
mock_GsutilList.assert_any_call('gs://chrome-signed/android-B0urB0N')
mock_GsutilList.assert_any_call(*[
'gs://chrome-signed/android-B0urB0N/%s/arm_64/MonochromeStable.apk' % x
for x in ['127.0.6533.76', '127.0.6533.78', '127.0.6533.79']
],
ignore_fail=True)
self.assertEqual(mock_GsutilList.call_count, 2)
@patch('bisect-builds.InstallOnAndroid')
def test_install_revision(self, mock_InstallOnAndroid):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64', '-g', '127.0.6533.76', '-b',
'127.0.6533.79', '--apk', 'chrome'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidReleaseBuild)
build._install_revision('chrome.apk', 'temp-dir')
mock_InstallOnAndroid.assert_called_once_with(self.device, 'chrome.apk')
@patch('bisect-builds.LaunchOnAndroid')
def test_launch_revision(self, mock_LaunchOnAndroid):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64', '-g', '127.0.6533.76', '-b',
'127.0.6533.79', '--apk', 'chrome'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidReleaseBuild)
build._launch_revision('temp-dir', None)
mock_LaunchOnAndroid.assert_called_once_with(self.device, 'chrome')
@patch('bisect-builds.LaunchOnAndroid')
def test_webview_launch_revision(self, mock_LaunchOnAndroid):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64', '-g', '127.0.6533.76', '-b',
'127.0.6533.79', '--apk', 'webview'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidReleaseBuild)
build._launch_revision('temp-dir', None)
mock_LaunchOnAndroid.assert_called_once_with(self.device, 'webview')
with self.assertRaises(bisect_builds.BisectException):
build._launch_revision('temp-dir', None, ['args'])
def test_get_apk_filename_webview(self):
"""Test _get_apk_filename returns AndroidWebview.apk for webview APK."""
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64', '-g', '127.0.6533.76', '-b',
'127.0.6533.79', '--apk', 'webview'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidReleaseBuild)
self.assertEqual(build._get_apk_filename(), 'AndroidWebview.apk')
def test_get_apk_filename_chrome(self):
"""Test _get_apk_filename returns correct filename for chrome APK."""
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64', '-g', '127.0.6533.76', '-b',
'127.0.6533.79', '--apk', 'chrome'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidReleaseBuild)
self.assertEqual(build._get_apk_filename(), 'Monochrome.apk')
class AndroidSnapshotBuildTest(AndroidBuildTest):
def setUp(self):
super().setUp()
self.set_sdk_level(bisect_builds.version_codes.PIE)
@patch('bisect-builds.UnzipFilenameToDir')
@patch('bisect-builds.InstallOnAndroid')
@patch('glob.glob', return_value=['Monochrome.apk'])
def test_install_revision(self, mock_glob, mock_InstallOnAndroid, mock_unzip):
options = bisect_builds.ParseCommandLine([
'-a', 'android-arm64', '-g', '1313161', '-b', '1313210', '--apk',
'chrome'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidSnapshotBuild)
build._install_revision('chrome.zip', 'temp-dir')
mock_glob.assert_called_once_with('temp-dir/*/apks/Monochrome.apk')
mock_InstallOnAndroid.assert_called_once_with(self.device, 'Monochrome.apk')
@patch('bisect-builds.UnzipFilenameToDir')
@patch('sys.stdout', new_callable=io.StringIO)
@patch('glob.glob',
side_effect=[[],
[
"temp-dir/full-build-linux/apks/MonochromeBeta.apk",
"temp-dir/full-build-linux/apks/ChromePublic.apk"
]])
def test_install_revision_with_show_available_apks(self, mock_glob,
mock_stdout, mock_unzip):
options = bisect_builds.ParseCommandLine([
'-a', 'android-arm64', '-g', '1313161', '-b', '1313210', '--apk',
'chrome'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidSnapshotBuild)
with self.assertRaises(bisect_builds.BisectException):
build._install_revision('chrome.zip', 'temp-dir')
self.assertIn("The list of available --apk:", mock_stdout.getvalue())
self.assertIn("chrome_beta", mock_stdout.getvalue())
self.assertIn("chromium", mock_stdout.getvalue())
@patch('bisect-builds.UnzipFilenameToDir')
@patch('sys.stdout', new_callable=io.StringIO)
@patch('glob.glob',
side_effect=[[], ["temp-dir/full-build-linux/apks/unknown.apks"]])
def test_install_revision_with_show_unknown_apks(self, mock_glob, mock_stdout,
mock_unzip):
options = bisect_builds.ParseCommandLine([
'-a', 'android-arm64', '-g', '1313161', '-b', '1313210', '--apk',
'chrome'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidSnapshotBuild)
with self.assertRaises(bisect_builds.BisectException):
build._install_revision('chrome.zip', 'temp-dir')
self.assertIn("No supported apk found. But found following",
mock_stdout.getvalue())
self.assertIn("unknown.apks", mock_stdout.getvalue())
class AndroidTrichromeReleaseBuildTest(AndroidBuildTest):
def setUp(self):
super().setUp()
self.set_sdk_level(bisect_builds.version_codes.Q)
@maybe_patch(
'bisect-builds.GsutilList',
side_effect=[[
'gs://chrome-unsigned/android-B0urB0N/%s/' % x for x in [
'129.0.6626.0', '129.0.6626.1', '129.0.6627.0', '129.0.6627.1',
'129.0.6628.0', '129.0.6628.1'
]
],
[('gs://chrome-unsigned/android-B0urB0N/%s/'
'high-arm_64/TrichromeChromeGoogle6432Stable.apks') % x
for x in ['129.0.6626.0', '129.0.6627.0', '129.0.6628.0']]])
def test_get_rev_list(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64-high', '--apk', 'chrome_stable', '-g',
'129.0.6626.0', '-b', '129.0.6628.0', '--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeReleaseBuild)
self.assertEqual(build.get_rev_list(),
['129.0.6626.0', '129.0.6627.0', '129.0.6628.0'])
print(mock_GsutilList.call_args_list)
mock_GsutilList.assert_any_call('gs://chrome-unsigned/android-B0urB0N')
mock_GsutilList.assert_any_call(*[
('gs://chrome-unsigned/android-B0urB0N/%s/'
'high-arm_64/TrichromeChromeGoogle6432Stable.apks') % x for x in [
'129.0.6626.0', '129.0.6626.1', '129.0.6627.0', '129.0.6627.1',
'129.0.6628.0'
]
],
ignore_fail=True)
self.assertEqual(mock_GsutilList.call_count, 2)
def test_should_raise_exception_for_PIE(self):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64-high', '--apk', 'chrome_stable', '-g',
'129.0.6626.0', '-b', '129.0.6667.0'
])
self.set_sdk_level(bisect_builds.version_codes.PIE)
with self.assertRaises(bisect_builds.BisectException):
bisect_builds.create_archive_build(options)
def test_get_download_url(self):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64-high', '--apk', 'chrome_stable', '-g',
'129.0.6626.0', '-b', '129.0.6628.0'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeReleaseBuild)
download_urls = build.get_download_url('129.0.6626.0')
self.maxDiff = 1000
self.assertDictEqual(
download_urls, {
'trichrome':
('gs://chrome-unsigned/android-B0urB0N/129.0.6626.0/high-arm_64/'
'TrichromeChromeGoogle6432Stable.apks'),
'trichrome_library':
('gs://chrome-unsigned/android-B0urB0N/129.0.6626.0/high-arm_64/'
'TrichromeLibraryGoogle6432Stable.apk'),
})
@patch('bisect-builds.InstallOnAndroid')
def test_install_revision(self, mock_InstallOnAndroid):
downloads = {
'trichrome': 'some-file.apks',
'trichrome_library': 'file2.apk',
}
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64-high', '--apk', 'chrome_stable', '-g',
'129.0.6626.0', '-b', '129.0.6628.0'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeReleaseBuild)
build._install_revision(downloads, 'tmp-dir')
mock_InstallOnAndroid.assert_any_call(self.device, 'some-file.apks')
mock_InstallOnAndroid.assert_any_call(self.device, 'file2.apk')
class AndroidTrichromeOfficialBuildTest(AndroidBuildTest):
@maybe_patch('bisect-builds.GsutilList',
return_value=[
'full-build-linux_%d.zip' % x
for x in [1334339, 1334342, 1334344, 1334345, 1334356]
])
def test_get_rev_list(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'chrome', '-g', '1334338',
'-b', '1334380', '--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeOfficialBuild)
self.assertEqual(build.get_rev_list(),
[1334339, 1334342, 1334344, 1334345, 1334356])
mock_GsutilList.assert_called_once_with(
'gs://chrome-test-builds/official-by-commit/'
'android_arm64_high_end-builder-perf/')
def test_get_download_url(self):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'chrome', '-g', '1334338',
'-b', '1334380'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeOfficialBuild)
self.assertEqual(
build.get_download_url(1334338),
'gs://chrome-test-builds/official-by-commit'
'/android_arm64_high_end-builder-perf/full-build-linux_1334338.zip')
@patch('glob.glob',
side_effect=[[
'temp-dir/full-build-linux/apks/TrichromeChromeGoogle6432.apks'
], ['temp-dir/full-build-linux/apks/TrichromeLibraryGoogle6432.apk']])
@patch('bisect-builds.UnzipFilenameToDir')
@patch('bisect-builds.InstallOnAndroid')
def test_install_revision(self, mock_InstallOnAndroid,
mock_UnzipFilenameToDir, mock_glob):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'chrome', '-g', '1334338',
'-b', '1334380'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeOfficialBuild)
build._install_revision('download.zip', 'tmp-dir')
mock_UnzipFilenameToDir.assert_called_once_with('download.zip', 'tmp-dir')
mock_InstallOnAndroid.assert_any_call(
self.device,
'temp-dir/full-build-linux/apks/TrichromeLibraryGoogle6432.apk')
mock_InstallOnAndroid.assert_any_call(
self.device,
'temp-dir/full-build-linux/apks/TrichromeChromeGoogle6432.apks')
@patch('sys.stdout', new_callable=io.StringIO)
@patch('glob.glob',
side_effect=[[],
['temp-dir/TrichromeChromeGoogle6432Canary.minimal.apks']
])
@patch('bisect-builds.UnzipFilenameToDir')
def test_install_revision_with_show_available_apks(self,
mock_UnzipFilenameToDir,
mock_glob, mock_stdout):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'chrome', '-g', '1334338',
'-b', '1334380'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeOfficialBuild)
with self.assertRaises(bisect_builds.BisectException):
build._install_revision('download.zip', 'tmp-dir')
self.assertIn("The list of available --apk:", mock_stdout.getvalue())
self.assertIn("chrome_canary", mock_stdout.getvalue())
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.InstallOnAndroid')
@patch('bisect-builds.LaunchOnAndroid')
def test_run_revision_with_real_zipfile(self, mock_LaunchOnAndroid,
mock_InstallOnAndroid):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'chrome', '-g', '1334338',
'-b', '1334380'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeOfficialBuild)
download_job = build.get_download_job(1334339)
zip_file = download_job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(zip_file, tempdir, [])
print(mock_InstallOnAndroid.call_args_list)
self.assertRegex(mock_InstallOnAndroid.mock_calls[0].args[1],
'full-build-linux/apks/TrichromeLibraryGoogle6432.apk$')
self.assertRegex(
mock_InstallOnAndroid.mock_calls[1].args[1],
'full-build-linux/apks/TrichromeChromeGoogle6432.minimal.apks$')
mock_LaunchOnAndroid.assert_called_once_with(self.device, 'chrome')
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.InstallOnAndroid')
@patch('bisect-builds.LaunchOnAndroid')
def test_run_revision_with_webview_apk_with_unsupported_versions(
self, mock_LaunchOnAndroid, mock_InstallOnAndroid):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'webview', '-g', '100000',
'-b', '100010'
])
with self.assertRaises(bisect_builds.BisectException):
_ = bisect_builds.create_archive_build(options)
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.InstallOnAndroid')
@patch('bisect-builds.LaunchOnAndroid')
def test_webview_launch_revision(self, mock_LaunchOnAndroid,
mock_InstallOnAndroid):
options = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '--apk', 'webview', '-g', '1350000',
'-b', '1350010'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidTrichromeOfficialBuild)
download_job = build.get_download_job(1334339)
zip_file = download_job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(zip_file, tempdir, [])
print(mock_InstallOnAndroid.call_args_list)
self.assertRegex(mock_InstallOnAndroid.mock_calls[0].args[1],
'full-build-linux/apks/TrichromeLibraryGoogle6432.apk$')
self.assertRegex(
mock_InstallOnAndroid.mock_calls[1].args[1],
'full-build-linux/apks/TrichromeWebViewGoogle6432.minimal.apks$')
mock_LaunchOnAndroid.assert_called_once_with(self.device, 'webview')
@patch('bisect-builds.UnzipFilenameToDir')
@patch('bisect-builds.InstallOnAndroid')
@patch('sys.stdout', new_callable=io.StringIO)
@patch('glob.glob',
side_effect=[
[],
['temp-dir/full-build-linux/apks/SystemWebView.apk'],
])
def test_install_revision_webview_swap_apk_filename(self, mock_glob,
mock_stdout,
mock_InstallOnAndroid,
mock_unzip):
options = bisect_builds.ParseCommandLine([
'o', '-a', 'android-arm64', '-g', '1313161', '-b', '1313210', '--apk',
'webview'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.AndroidSnapshotBuild)
self.assertEqual(build.binary_name, 'SystemWebViewGoogle.apk')
build._install_revision('webview.zip', 'temp-dir')
self.assertEqual(build.binary_name, 'SystemWebView.apk')
self.assertEqual(mock_glob.call_count, 2)
mock_glob.assert_any_call('temp-dir/*/apks/SystemWebViewGoogle.apk')
mock_glob.assert_any_call('temp-dir/*/apks/SystemWebView.apk')
mock_InstallOnAndroid.assert_called_once_with(
self.device, 'temp-dir/full-build-linux/apks/SystemWebView.apk')
self.assertIn('Retrying with SystemWebView.apk', mock_stdout.getvalue())
class LinuxReleaseBuildTest(BisectTestCase):
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_launch_revision_should_has_no_sandbox(self, mock_Popen):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
options = bisect_builds.ParseCommandLine(
['-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.88'])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.LinuxReleaseBuild)
build._launch_revision('temp-dir', {'chrome': 'temp-dir/linux64/chrome'},
[])
mock_Popen.assert_called_once_with(
'temp-dir/linux64/chrome --user-data-dir=temp-dir/profile --no-sandbox',
cwd=None,
shell=True,
bufsize=-1,
stdout=ANY,
stderr=ANY)
class IOSReleaseBuildTest(BisectTestCase):
@maybe_patch(
'bisect-builds.GsutilList',
side_effect=[[
'gs://chrome-unsigned/ios-G1N/127.0.6533.76/',
'gs://chrome-unsigned/ios-G1N/127.0.6533.77/',
'gs://chrome-unsigned/ios-G1N/127.0.6533.78/'
],
[
'gs://chrome-unsigned/ios-G1N'
'/127.0.6533.76/iphoneos17.5/ios/10863/canary.ipa',
'gs://chrome-unsigned/ios-G1N'
'/127.0.6533.77/iphoneos17.5/ios/10866/canary.ipa',
'gs://chrome-unsigned/ios-G1N'
'/127.0.6533.78/iphoneos17.5/ios/10868/canary.ipa'
]])
def test_list_rev(self, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios', '--ipa=canary.ipa', '--device-id', '321', '-g',
'127.0.6533.74', '-b', '127.0.6533.78', '--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSReleaseBuild)
self.assertEqual(build.get_rev_list(),
['127.0.6533.76', '127.0.6533.77', '127.0.6533.78'])
mock_GsutilList.assert_any_call('gs://chrome-unsigned/ios-G1N')
mock_GsutilList.assert_any_call(*[
'gs://chrome-unsigned/ios-G1N/%s/*/ios/*/canary.ipa' % x
for x in ['127.0.6533.76', '127.0.6533.77', '127.0.6533.78']
],
ignore_fail=True)
@patch('bisect-builds.UnzipFilenameToDir')
@patch('glob.glob', return_value=['Payload/canary.app/Info.plist'])
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_install_revision(self, mock_Popen, mock_glob,
mock_UnzipFilenameToDir):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios', '--ipa=canary.ipa', '--device-id', '321', '-g',
'127.0.6533.74', '-b', '127.0.6533.78'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSReleaseBuild)
build._install_revision('canary.ipa', 'tempdir')
mock_glob.assert_called_once_with('tempdir/Payload/*/Info.plist')
mock_Popen.assert_has_calls([
call([
'xcrun', 'devicectl', 'device', 'install', 'app', '--device', '321',
'canary.ipa'
],
cwd=None,
shell=False,
bufsize=-1,
stdout=-1,
stderr=-1),
call([
'plutil', '-extract', 'CFBundleIdentifier', 'raw',
'Payload/canary.app/Info.plist'
],
cwd=None,
shell=False,
bufsize=-1,
stdout=-1,
stderr=-1)
],
any_order=True)
@patch('subprocess.Popen', spec=subprocess.Popen)
def test_launch_revision(self, mock_Popen):
mock_Popen.return_value.communicate.return_value = ('', '')
mock_Popen.return_value.returncode = 0
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios', '--ipa=canary.ipa', '--device-id', '321', '-g',
'127.0.6533.74', '-b', '127.0.6533.78', '--', 'args1', 'args2'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSReleaseBuild)
build._launch_revision('tempdir', 'com.google.chrome.ios', options.args)
mock_Popen.assert_any_call([
'xcrun', 'devicectl', 'device', 'process', 'launch', '--device', '321',
'com.google.chrome.ios', 'args1', 'args2'
],
cwd=None,
shell=False,
bufsize=-1,
stdout=-1,
stderr=-1)
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, 'stdout', ''))
def test_run_revision(self, mock_run):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios', '--ipa=canary.ipa', '--device-id', '321', '-g',
'127.0.6533.74', '-b', '127.0.6533.78'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSReleaseBuild)
job = build.get_download_job('127.0.6533.76')
ipa = job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(ipa, tempdir, options.args)
mock_run.assert_has_calls([
call([
'xcrun', 'devicectl', 'device', 'install', 'app', '--device', '321',
ANY
]),
call(['plutil', '-extract', 'CFBundleIdentifier', 'raw', ANY]),
call([
'xcrun', 'devicectl', 'device', 'process', 'launch', '--device',
'321', 'stdout'
])
])
class IOSSimulatorReleaseBuildTest(BisectTestCase):
@maybe_patch(
'bisect-builds.GsutilList',
side_effect=[
[
'gs://bling-archive/128.0.6534.0/',
'gs://bling-archive/128.0.6534.1/',
'gs://bling-archive/128.0.6535.0/',
'gs://bling-archive/128.0.6535.1/',
'gs://bling-archive/128.0.6536.0/',
],
[
'gs://bling-archive/128.0.6534.0/20240612011643/Chromium.tar.gz',
'gs://bling-archive/128.0.6536.0/20240613011356/Chromium.tar.gz',
]
])
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, '', ''))
def test_list_rev(self, mock_run, mock_GsutilList):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios-simulator', '--device-id', '321', '-g', '128.0.6534.0',
'-b', '128.0.6536.0', '--no-local-cache'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSSimulatorReleaseBuild)
self.assertEqual(build.get_rev_list(), ['128.0.6534.0', '128.0.6536.0'])
mock_run.assert_called_once_with(['xcrun', 'simctl', 'boot', '321'])
mock_GsutilList.assert_any_call('gs://bling-archive')
mock_GsutilList.assert_any_call(*[
'gs://bling-archive/%s/*/Chromium.tar.gz' % x for x in [
'128.0.6534.0', '128.0.6534.1', '128.0.6535.0', '128.0.6535.1',
'128.0.6536.0'
]
],
ignore_fail=True)
@patch('bisect-builds.UnzipFilenameToDir')
@patch('glob.glob', return_value=['Info.plist'])
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, '', ''))
def test_install_revision(self, mock_run, mock_glob, mock_UnzipFilenameToDir):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios-simulator', '--device-id', '321', '-g', '128.0.6534.0',
'-b', '128.0.6539.0'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSSimulatorReleaseBuild)
build._install_revision('Chromium.tar.gz', 'tempdir')
mock_UnzipFilenameToDir.assert_called_once_with('Chromium.tar.gz',
'tempdir')
self.assertEqual(mock_glob.call_count, 2)
mock_run.assert_has_calls([
call(['xcrun', 'simctl', 'boot', '321']),
call(['xcrun', 'simctl', 'install', '321', ANY]),
call(['plutil', '-extract', 'CFBundleIdentifier', 'raw', 'Info.plist']),
])
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, '', ''))
def test_launch_revision(self, mock_run):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios-simulator', '--device-id', '321', '-g', '128.0.6534.0',
'-b', '128.0.6539.0'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSSimulatorReleaseBuild)
build._launch_revision('tempdir', 'com.google.chrome.ios.dev',
['args1', 'args2'])
mock_run.assert_any_call([
'xcrun', 'simctl', 'launch', '321', 'com.google.chrome.ios.dev',
'args1', 'args2'
])
@unittest.skipUnless('NO_MOCK_SERVER' in os.environ,
'The test only valid when NO_MOCK_SERVER')
@patch('bisect-builds.ArchiveBuild._run', return_value=(0, 'stdout', ''))
def test_run_revision(self, mock_run):
options = bisect_builds.ParseCommandLine([
'-r', '-a', 'ios-simulator', '--device-id', '321', '-g', '128.0.6534.0',
'-b', '128.0.6539.0'
])
build = bisect_builds.create_archive_build(options)
self.assertIsInstance(build, bisect_builds.IOSSimulatorReleaseBuild)
job = build.get_download_job('128.0.6534.0')
download = job.start().wait_for()
with tempfile.TemporaryDirectory(prefix='bisect_tmp') as tempdir:
build.run_revision(download, tempdir, options.args)
mock_run.assert_has_calls([
call(['xcrun', 'simctl', 'boot', '321']),
call(['xcrun', 'simctl', 'install', '321', ANY]),
call(['plutil', '-extract', 'CFBundleIdentifier', 'raw', ANY]),
call(['xcrun', 'simctl', 'launch', '321', 'stdout'])
])
class MaybeSwitchBuildTypeTest(BisectTestCase):
def test_generate_new_command_without_cache(self):
command_line = [
'-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.88',
'--no-local-cache'
]
options = bisect_builds.ParseCommandLine(command_line)
with patch('sys.argv', ['bisect-builds.py', *command_line]):
new_cmd = bisect_builds.MaybeSwitchBuildType(
options, bisect_builds.ChromiumVersion('127.0.6533.74'),
bisect_builds.ChromiumVersion('127.0.6533.88'))
self.assertEqual(new_cmd[1:], [
'-o', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.88',
'--verify-range', '--no-local-cache'
])
def test_android_signed_with_args(self):
command_line = [
'-r', '--archive=android-arm64-high', '--good=127.0.6533.74', '-b',
'127.0.6533.88', '--apk=chrome', '--signed', '--no-local-cache', '--',
'args1', '--args2'
]
options = bisect_builds.ParseCommandLine(command_line)
with patch('sys.argv', ['bisect-builds.py', *command_line]):
new_cmd = bisect_builds.MaybeSwitchBuildType(options, '127.0.6533.74',
'127.0.6533.88')
self.assertEqual(new_cmd[1:], [
'-o', '-a', 'android-arm64-high', '-g', '127.0.6533.74', '-b',
'127.0.6533.88', '--verify-range', '--apk=chrome', '--no-local-cache',
'--', 'args1', '--args2'
])
def test_no_official_build(self):
command_line = [
'-r', '-a', 'ios', '--ipa=canary.ipa', '--device-id', '321', '-g',
'127.0.6533.74', '-b', '127.0.6533.78', '--no-local-cache'
]
options = bisect_builds.ParseCommandLine(command_line)
with patch('sys.argv', ['bisect-builds.py', *command_line]):
new_cmd = bisect_builds.MaybeSwitchBuildType(options, '127.0.6533.74',
'127.0.6533.88')
self.assertEqual(new_cmd, None)
@patch('bisect-builds.ArchiveBuild.get_rev_list', return_value=list(range(3)))
def test_generate_suggestion_with_cache(self, mock_get_rev_list):
command_line = [
'-r', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.88',
'--use-local-cache'
]
options = bisect_builds.ParseCommandLine(command_line)
with patch('sys.argv', ['bisect-builds.py', *command_line]):
new_cmd = bisect_builds.MaybeSwitchBuildType(options, '127.0.6533.74',
'127.0.6533.88')
self.assertEqual(new_cmd[1:], [
'-o', '-a', 'linux64', '-g', '127.0.6533.74', '-b', '127.0.6533.88',
'--verify-range', '--use-local-cache'
])
mock_get_rev_list.assert_called()
class MethodTest(BisectTestCase):
@patch('sys.stderr', new_callable=io.StringIO)
def test_ParseCommandLine(self, mock_stderr):
opts = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '1', 'args1', 'args2 3', '-b', '2'])
self.assertEqual(opts.build_type, 'snapshot')
self.assertEqual(opts.args, ['args1', 'args2 3'])
opts = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '1', 'args1', 'args2 3'])
self.assertEqual(opts.args, ['args1', 'args2 3'])
opts = bisect_builds.ParseCommandLine(
['-a', 'linux64', '-g', '1', '--', 'args1', 'args2 3', '-b', '2'])
self.assertEqual(opts.args, ['args1', 'args2 3', '-b', '2'])
with self.assertRaises(SystemExit):
bisect_builds.ParseCommandLine(['-a', 'mac64', '-o', '-g', '1'])
self.assertRegexpMatches(
mock_stderr.getvalue(), r'To bisect for mac64, please choose from '
r'release(-r), snapshot(-s)')
@patch('bisect-builds._DetectArchive', return_value='linux64')
def test_ParseCommandLine_DetectArchive(self, mock_detect_archive):
opts = bisect_builds.ParseCommandLine(['-o', '-g', '1'])
self.assertEqual(opts.archive, 'linux64')
def test_ParseCommandLine_default_apk(self):
opts = bisect_builds.ParseCommandLine(
['-o', '-a', 'android-arm', '-g', '1'])
self.assertEqual(opts.apk, 'chrome')
opts = bisect_builds.ParseCommandLine(['-a', 'android-arm64', '-g', '1'])
self.assertEqual(opts.apk, 'chromium')
def test_ParseCommandLine_default_ipa(self):
opts = bisect_builds.ParseCommandLine(
['-r', '-a', 'ios', '-g', '127.0.6533.74', '-b', '127.0.6533.88'])
self.assertEqual(opts.ipa, 'canary')
def test_ParseCommandLine_DetectArchive_with_apk(self):
opts = bisect_builds.ParseCommandLine(['-o', '--apk', 'chrome', '-g', '1'])
self.assertEqual(opts.archive, 'android-arm64-high')
def test_ParseCommandLine_DetectArchive_with_apk_Snapshot(self):
opts = bisect_builds.ParseCommandLine(['-s', '--apk', 'chrome', '-g', '1'])
self.assertEqual(opts.archive, 'android-arm64')
def test_ParseCommandLine_DetectArchive_with_ipa(self):
opts = bisect_builds.ParseCommandLine(
['-r', '--ipa', 'stable', '-g', '127.0.6533.74', '-b', '127.0.6533.88'])
self.assertEqual(opts.archive, 'ios-simulator')
@patch('sys.stderr', new_callable=io.StringIO)
def test_ParseCommandLine_apk_error(self, mock_stderr):
with self.assertRaises(SystemExit):
bisect_builds.ParseCommandLine(
['-a', 'linux64', '--apk', 'chrome', '-g', '1'])
self.assertIn('--apk is only supported', mock_stderr.getvalue())
@patch('sys.stderr', new_callable=io.StringIO)
def test_ParseCommandLine_ipa_error(self, mock_stderr):
with self.assertRaises(SystemExit):
bisect_builds.ParseCommandLine(
['-a', 'linux64', '--ipa', 'stable', '-g', '1'])
self.assertIn('--ipa is only supported', mock_stderr.getvalue())
@patch('sys.stderr', new_callable=io.StringIO)
def test_ParseCommandLine_signed_error(self, mock_stderr):
with self.assertRaises(SystemExit):
bisect_builds.ParseCommandLine(['-a', 'linux64', '--signed', '-g', '1'])
self.assertIn('--signed is only supported', mock_stderr.getvalue())
@patch('sys.stderr', new_callable=io.StringIO)
def test_ParseCommandLine_webview_incompatibility_error(self, mock_stderr):
with self.assertRaises(SystemExit):
_ = bisect_builds.ParseCommandLine([
'-r', '-a', 'android-arm64-high', '-g', '127.0.6533.76', '-b',
'127.0.6533.79', '--apk', 'webview'
])
self.assertIn(
'Bisecting WebView for android-arm64-high, please choose official '
'builds (-o)', mock_stderr.getvalue())
opts = bisect_builds.ParseCommandLine([
'-o', '-a', 'android-arm64-high', '-g', '1334017', '-b', '1335078',
'--apk', 'webview'
])
self.assertEqual(opts.apk, 'webview')
self.assertEqual(opts.archive, 'android-arm64-high')
self.assertEqual(opts.build_type, 'official')
@patch('urllib.request.urlopen')
@patch('builtins.open')
@patch('sys.stdout', new_callable=io.StringIO)
def test_update_script(self, mock_stdout, mock_open, mock_urlopen):
mock_urlopen.return_value = io.BytesIO(
base64.b64encode('content'.encode('utf-8')))
with self.assertRaises(SystemExit):
bisect_builds.ParseCommandLine(['--update-script'])
mock_urlopen.assert_called_once_with(
'https://chromium.googlesource.com/chromium/src/+/HEAD/'
'tools/bisect-builds.py?format=TEXT')
mock_open.assert_called_once()
mock_open.return_value.__enter__().write.assert_called_once_with('content')
self.assertEqual(mock_stdout.getvalue(), 'Update successful!\n')
@patch("urllib.request.urlopen",
side_effect=[
urllib.request.HTTPError('url', 404, 'Not Found', None, None),
urllib.request.HTTPError('url', 404, 'Not Found', None, None),
io.BytesIO(b"NOT_A_JSON"),
io.BytesIO(b'{"chromium_main_branch_position": 123}'),
])
def test_GetRevisionFromVersion(self, mock_urlopen):
self.assertEqual(123,
bisect_builds.GetRevisionFromVersion('127.0.6533.134'))
mock_urlopen.assert_has_calls([
call('https://chromiumdash.appspot.com/fetch_version'
'?version=127.0.6533.134'),
call('https://chromiumdash.appspot.com/fetch_version'
'?version=127.0.6533.0'),
])
@maybe_patch("urllib.request.urlopen",
side_effect=[
io.BytesIO(b'{"chromium_main_branch_position": null}'),
io.BytesIO(b'{"message": "DEP\\n"}'),
io.BytesIO(b')]}\'\n{"message": "Cr-Branched-From: '
b'3d60439cfb36485e76a1c5bb7f513d3721b20da1-'
b'refs/heads/master@{#870763}\\n"}'),
])
def test_GetRevisionFromSourceTag(self, mock_urlopen):
self.assertEqual(870763,
bisect_builds.GetRevisionFromVersion('91.0.4472.38'))
mock_urlopen.assert_has_calls([
call('https://chromiumdash.appspot.com/fetch_version'
'?version=91.0.4472.38'),
call('https://chromium.googlesource.com/chromium/src/'
'+/refs/tags/91.0.4472.38?format=JSON'),
call('https://chromium.googlesource.com/chromium/src/'
'+/refs/tags/91.0.4472.38^?format=JSON'),
])
def test_join_args(self):
test_data = ['a', 'b c', 'C:\\a b\\c', '/a b/c', '"a"', "'a'"]
quoted_command = bisect_builds.join_args(
[sys.executable, '-c', 'import sys, json; print(json.dumps(sys.argv))']
+ test_data)
subproc = subprocess.Popen(
quoted_command, shell=True, stdout=subprocess.PIPE)
stdout, _ = subproc.communicate()
dumped_argv = json.loads(stdout.decode('utf-8'))
self.assertListEqual(dumped_argv, ['-c'] + test_data)
class ChromiumVersionTest(BisectTestCase):
def test_cmpare_version_numbers(self):
v127_0_6533_74 = bisect_builds.ChromiumVersion('127.0.6533.74')
v127_0_6533_75 = bisect_builds.ChromiumVersion('127.0.6533.75')
v127_0_6533_75_with_space = bisect_builds.ChromiumVersion('127.0.6533.75 ')
v127 = bisect_builds.ChromiumVersion('127')
self.assertLess(v127_0_6533_74, v127_0_6533_75)
self.assertLessEqual(v127_0_6533_74, v127_0_6533_75)
self.assertGreater(v127_0_6533_75, v127_0_6533_74)
self.assertGreaterEqual(v127_0_6533_75, v127_0_6533_74)
self.assertEqual(v127_0_6533_75, v127_0_6533_75_with_space)
self.assertLess(v127, v127_0_6533_74)
class GetEarliestBuildVersionFromRevisionTest(BisectTestCase):
@patch('bisect-builds.FetchJsonFromURL')
def test_get_build_versions_from_revision(self, mock_fetch_json):
mock_fetch_json.return_value = {'commits': [{'earliest': '144.0.7502.0'}]}
version = bisect_builds.GetEarliestBuildVersionFromRevision(1537356)
self.assertIsNotNone(version)
self.assertEqual(str(version), '144.0.7502.0')
@patch('bisect-builds.FetchJsonFromURL')
def test_get_build_versions_from_revision_multiple_commits(
self, mock_fetch_json):
mock_fetch_json.return_value = {
'commits': [{
'earliest': '144.0.7559.3'
}, {
'earliest': '144.0.7502.0'
}]
}
with self.assertRaises(bisect_builds.BisectException):
bisect_builds.GetEarliestBuildVersionFromRevision(1537356)
@patch('bisect-builds.FetchJsonFromURL')
def test_get_build_versions_from_revision_no_commits(self, mock_fetch_json):
mock_fetch_json.return_value = {'commits': []}
with self.assertRaises(bisect_builds.BisectException):
bisect_builds.GetEarliestBuildVersionFromRevision(1537356)
@patch('bisect-builds.FetchJsonFromURL')
def test_get_build_versions_from_revision_no_earliest(self, mock_fetch_json):
mock_fetch_json.return_value = {'commits': [{}]}
with self.assertRaises(bisect_builds.BisectException):
bisect_builds.GetEarliestBuildVersionFromRevision(1537356)
@patch('bisect-builds.FetchJsonFromURL')
def test_get_build_versions_from_revision_non_integer_revision(
self, mock_fetch_json):
version = bisect_builds.GetEarliestBuildVersionFromRevision('not-a-number')
self.assertIsNone(version)
mock_fetch_json.assert_not_called()
if __name__ == '__main__':
unittest.main()