• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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