blob: 864ec6333601b197498293c19f06ce1cac8a1eb5 [file] [log] [blame] [edit]
# 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', 'system_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, 'system_webview')
with self.assertRaises(bisect_builds.BisectException):
build._launch_revision('temp-dir', None, ['args'])
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')
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')
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('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)
if __name__ == '__main__':
unittest.main()