0
Files
src/tools/bisect_test.py
Ben Joyce 3c940d31b0 Update biset_test presubmit tests
Test fails on python 3.11 as an extra call being added, probably
something to do with the temp directory in the test.

Expected: [call('some-file.apks'), call('file2.apk')]
  Actual: [call('some-file.apks'), call('/b/s/w/ir/x/t/tmpg25k88ff'), call('file2.apk')]

Bug: 40942322
Change-Id: I0ec0364bcabf49dcb5b7f4caf01fee28897de373
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6258897
Reviewed-by: Kuan Huang <kuanhuang@chromium.org>
Reviewed-by: Peter Wen <wnwen@chromium.org>
Commit-Queue: Benjamin Joyce (Ben) <bjoyce@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1419437}
2025-02-12 11:31:52 -08:00

1784 lines
75 KiB
Python

# 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()