1#!/usr/bin/env vpython3 2# Copyright 2022 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""File for testing compatible_utils.py.""" 6 7import io 8import os 9import stat 10import tempfile 11import unittest 12import unittest.mock as mock 13 14import compatible_utils 15 16 17@unittest.skipIf(os.name == 'nt', 'Fuchsia tests not supported on Windows') 18class CompatibleUtilsTest(unittest.TestCase): 19 """Test compatible_utils.py methods.""" 20 21 def test_running_unattended_returns_true_if_headless_set(self) -> None: 22 """Test |running_unattended| returns True if CHROME_HEADLESS is set.""" 23 with mock.patch('os.environ', {'SWARMING_SERVER': 0}): 24 self.assertTrue(compatible_utils.running_unattended()) 25 26 with mock.patch('os.environ', {'FOO_HEADLESS': 0}): 27 self.assertFalse(compatible_utils.running_unattended()) 28 29 def test_get_host_arch(self) -> None: 30 """Test |get_host_arch| gets the host architecture and throws 31 exceptions on errors.""" 32 supported_arches = ['x86_64', 'AMD64', 'aarch64'] 33 with mock.patch('platform.machine', side_effect=supported_arches): 34 self.assertEqual(compatible_utils.get_host_arch(), 'x64') 35 self.assertEqual(compatible_utils.get_host_arch(), 'x64') 36 self.assertEqual(compatible_utils.get_host_arch(), 'arm64') 37 38 with mock.patch('platform.machine', return_value=['fake-arch']), \ 39 self.assertRaises(NotImplementedError): 40 compatible_utils.get_host_arch() 41 42 def test_add_exec_to_file(self) -> None: 43 """Test |add_exec_to_file| adds executable bit to file.""" 44 with tempfile.NamedTemporaryFile() as f: 45 original_stat = os.stat(f.name).st_mode 46 self.assertFalse(original_stat & stat.S_IXUSR) 47 48 compatible_utils.add_exec_to_file(f.name) 49 50 new_stat = os.stat(f.name).st_mode 51 self.assertTrue(new_stat & stat.S_IXUSR) 52 53 # pylint: disable=no-self-use 54 def test_pave_adds_exec_to_binary_files(self) -> None: 55 """Test |pave| calls |add_exec_to_file| on necessary files.""" 56 with mock.patch('os.path.exists', return_value=True), \ 57 mock.patch('compatible_utils.add_exec_to_file') as mock_exec, \ 58 mock.patch('platform.machine', return_value='x86_64'), \ 59 mock.patch('subprocess.run'): 60 compatible_utils.pave('some/path/to/dir', 'some-target') 61 62 mock_exec.assert_has_calls([ 63 mock.call('some/path/to/dir/pave.sh'), 64 mock.call('some/path/to/dir/host_x64/bootserver') 65 ], 66 any_order=True) 67 68 def test_pave_adds_exec_to_binary_files_if_pb_set_not_found(self) -> None: 69 """Test |pave| calls |add_exec_to_file| on necessary files. 70 71 Checks if current product-bundle files exist. If not, defaults to 72 prebuilt-images set. 73 """ 74 with mock.patch('os.path.exists', return_value=False), \ 75 mock.patch('compatible_utils.add_exec_to_file') as mock_exec, \ 76 mock.patch('platform.machine', return_value='x86_64'), \ 77 mock.patch('subprocess.run'): 78 compatible_utils.pave('some/path/to/dir', 'some-target') 79 80 mock_exec.assert_has_calls([ 81 mock.call('some/path/to/dir/pave.sh'), 82 mock.call('some/path/to/dir/bootserver.exe.linux-x64') 83 ], 84 any_order=True) 85 86 def test_pave_adds_target_id_if_given(self) -> None: 87 """Test |pave| adds target-id to the arguments.""" 88 with mock.patch('os.path.exists', return_value=False), \ 89 mock.patch('compatible_utils.add_exec_to_file'), \ 90 mock.patch('platform.machine', return_value='x86_64'), \ 91 mock.patch('compatible_utils.get_ssh_keys', 92 return_value='authorized-keys-file'), \ 93 mock.patch('subprocess.run') as mock_subproc: 94 mock_subproc.reset_mock() 95 compatible_utils.pave('some/path/to/dir', 'some-target') 96 97 mock_subproc.assert_called_once_with([ 98 'some/path/to/dir/pave.sh', '--authorized-keys', 99 'authorized-keys-file', '-1', '-n', 'some-target' 100 ], 101 check=True, 102 text=True, 103 timeout=300) 104 105 # pylint: disable=no-self-use 106 107 def test_parse_host_port_splits_address_and_strips_brackets(self) -> None: 108 """Test |parse_host_port| splits ipv4 and ipv6 addresses correctly.""" 109 self.assertEqual(compatible_utils.parse_host_port('hostname:55'), 110 ('hostname', 55)) 111 self.assertEqual(compatible_utils.parse_host_port('192.168.42.40:443'), 112 ('192.168.42.40', 443)) 113 self.assertEqual( 114 compatible_utils.parse_host_port('[2001:db8::1]:8080'), 115 ('2001:db8::1', 8080)) 116 117 def test_map_filter_filter_file_throws_value_error_if_wrong_path(self 118 ) -> None: 119 """Test |map_filter_file| throws ValueError if path is missing 120 FILTER_DIR.""" 121 with self.assertRaises(ValueError): 122 compatible_utils.map_filter_file_to_package_file('foo') 123 124 with self.assertRaises(ValueError): 125 compatible_utils.map_filter_file_to_package_file('some/other/path') 126 127 with self.assertRaises(ValueError): 128 compatible_utils.map_filter_file_to_package_file('filters/file') 129 130 # No error. 131 compatible_utils.map_filter_file_to_package_file( 132 'testing/buildbot/filters/some.filter') 133 134 def test_map_filter_filter_replaces_filter_dir_with_pkg_path(self) -> None: 135 """Test |map_filter_file| throws ValueError if path is missing 136 FILTER_DIR.""" 137 self.assertEqual( 138 '/pkg/testing/buildbot/filters/some.filter', 139 compatible_utils.map_filter_file_to_package_file( 140 'foo/testing/buildbot/filters/some.filter')) 141 142 def test_get_sdk_hash_fallsback_to_args_file_if_buildargs_dne(self 143 ) -> None: 144 """Test |get_sdk_hash| checks if buildargs.gn exists. 145 146 If it does not, fallsback to args.gn. This should raise an exception 147 as it does not exist. 148 """ 149 with mock.patch('os.path.exists', return_value=False) as mock_exists, \ 150 self.assertRaises(compatible_utils.VersionNotFoundError): 151 compatible_utils.get_sdk_hash('some/image/dir') 152 mock_exists.assert_has_calls([ 153 mock.call('some/image/dir/buildargs.gn'), 154 mock.call('some/image/dir/args.gn') 155 ]) 156 157 def test_get_sdk_hash_parse_contents_of_args_file(self) -> None: 158 """Test |get_sdk_hash| parses buildargs contents correctly.""" 159 build_args_test_contents = """ 160build_info_board = "chromebook-x64" 161build_info_product = "workstation_eng" 162build_info_version = "10.20221114.2.1" 163universe_package_labels += [] 164""" 165 with mock.patch('os.path.exists', return_value=True), \ 166 mock.patch('builtins.open', 167 return_value=io.StringIO(build_args_test_contents)): 168 self.assertEqual(compatible_utils.get_sdk_hash('some/dir'), 169 ('workstation_eng', '10.20221114.2.1')) 170 171 def test_get_sdk_hash_raises_error_if_keys_missing(self) -> None: 172 """Test |get_sdk_hash| raises VersionNotFoundError if missing keys""" 173 build_args_test_contents = """ 174import("//boards/chromebook-x64.gni") 175import("//products/workstation_eng.gni") 176cxx_rbe_enable = true 177host_labels += [ "//bundles/infra/build" ] 178universe_package_labels += [] 179""" 180 with mock.patch('os.path.exists', return_value=True), \ 181 mock.patch( 182 'builtins.open', 183 return_value=io.StringIO(build_args_test_contents)), \ 184 self.assertRaises(compatible_utils.VersionNotFoundError): 185 compatible_utils.get_sdk_hash('some/dir') 186 187 def test_get_sdk_hash_raises_error_if_contents_empty(self) -> None: 188 """Test |get_sdk_hash| raises VersionNotFoundError if no contents.""" 189 with mock.patch('os.path.exists', return_value=True), \ 190 mock.patch('builtins.open', return_value=io.StringIO("")), \ 191 self.assertRaises(compatible_utils.VersionNotFoundError): 192 compatible_utils.get_sdk_hash('some/dir') 193 194 def trim_noop_prefixes(self, path): 195 """Helper function to trim no-op path name prefixes that are 196 introduced by os.path.realpath on some platforms. These break 197 the unit tests, but have no actual effect on behavior.""" 198 # These must all end in the path separator character for the 199 # string length computation to be correct on all platforms. 200 noop_prefixes = ['/private/'] 201 for prefix in noop_prefixes: 202 if path.startswith(prefix): 203 return path[len(prefix) - 1:] 204 return path 205 206 def test_install_symbols(self): 207 208 """Test |install_symbols|.""" 209 210 with tempfile.TemporaryDirectory() as fuchsia_out_dir: 211 build_id = 'test_build_id' 212 symbol_file = os.path.join(fuchsia_out_dir, '.build-id', 213 build_id[:2], build_id[2:] + '.debug') 214 id_path = os.path.join(fuchsia_out_dir, 'ids.txt') 215 try: 216 binary_relpath = 'path/to/binary' 217 with open(id_path, 'w') as f: 218 f.write(f'{build_id} {binary_relpath}') 219 compatible_utils.install_symbols([id_path], fuchsia_out_dir) 220 self.assertTrue(os.path.islink(symbol_file)) 221 self.assertEqual( 222 self.trim_noop_prefixes(os.path.realpath(symbol_file)), 223 os.path.join(fuchsia_out_dir, binary_relpath)) 224 225 new_binary_relpath = 'path/to/new/binary' 226 with open(id_path, 'w') as f: 227 f.write(f'{build_id} {new_binary_relpath}') 228 compatible_utils.install_symbols([id_path], fuchsia_out_dir) 229 self.assertTrue(os.path.islink(symbol_file)) 230 self.assertEqual( 231 self.trim_noop_prefixes(os.path.realpath(symbol_file)), 232 os.path.join(fuchsia_out_dir, new_binary_relpath)) 233 finally: 234 os.remove(id_path) 235 236 237if __name__ == '__main__': 238 unittest.main() 239