
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}
1784 lines
75 KiB
Python
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()
|