• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2020 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Tests for rust_uprev.py"""
8
9import os
10import shutil
11import subprocess
12import tempfile
13import unittest
14from pathlib import Path
15from unittest import mock
16
17from llvm_tools import git
18
19import rust_uprev
20from rust_uprev import RustVersion
21
22
23def _fail_command(cmd, *_args, **_kwargs):
24  err = subprocess.CalledProcessError(returncode=1, cmd=cmd)
25  err.stderr = b'mock failure'
26  raise err
27
28
29class FetchDistfileTest(unittest.TestCase):
30  """Tests rust_uprev.fetch_distfile_from_mirror()"""
31
32  @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
33  @mock.patch.object(subprocess, 'call', side_effect=_fail_command)
34  def test_fetch_difstfile_fail(self, *_args) -> None:
35    with self.assertRaises(subprocess.CalledProcessError):
36      rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
37
38  @mock.patch.object(rust_uprev,
39                     'get_command_output_unchecked',
40                     return_value='AccessDeniedException: Access denied.')
41  @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
42  @mock.patch.object(subprocess, 'call', return_value=0)
43  def test_fetch_distfile_acl_access_denied(self, *_args) -> None:
44    rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
45
46  @mock.patch.object(
47      rust_uprev,
48      'get_command_output_unchecked',
49      return_value='[ { "entity": "allUsers", "role": "READER" } ]')
50  @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
51  @mock.patch.object(subprocess, 'call', return_value=0)
52  def test_fetch_distfile_acl_ok(self, *_args) -> None:
53    rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
54
55  @mock.patch.object(
56      rust_uprev,
57      'get_command_output_unchecked',
58      return_value='[ { "entity": "___fake@google.com", "role": "OWNER" } ]')
59  @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
60  @mock.patch.object(subprocess, 'call', return_value=0)
61  def test_fetch_distfile_acl_wrong(self, *_args) -> None:
62    with self.assertRaisesRegex(Exception, 'allUsers.*READER'):
63      with self.assertLogs(level='ERROR') as log:
64        rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
65        self.assertIn(
66            '[ { "entity": "___fake@google.com", "role": "OWNER" } ]',
67            '\n'.join(log.output))
68
69
70class FindEbuildPathTest(unittest.TestCase):
71  """Tests for rust_uprev.find_ebuild_path()"""
72
73  def test_exact_version(self):
74    with tempfile.TemporaryDirectory() as tmpdir:
75      ebuild = Path(tmpdir, 'test-1.3.4.ebuild')
76      ebuild.touch()
77      Path(tmpdir, 'test-1.2.3.ebuild').touch()
78      result = rust_uprev.find_ebuild_path(tmpdir, 'test',
79                                           rust_uprev.RustVersion(1, 3, 4))
80      self.assertEqual(result, ebuild)
81
82  def test_no_version(self):
83    with tempfile.TemporaryDirectory() as tmpdir:
84      ebuild = Path(tmpdir, 'test-1.2.3.ebuild')
85      ebuild.touch()
86      result = rust_uprev.find_ebuild_path(tmpdir, 'test')
87      self.assertEqual(result, ebuild)
88
89  def test_patch_version(self):
90    with tempfile.TemporaryDirectory() as tmpdir:
91      ebuild = Path(tmpdir, 'test-1.3.4-r3.ebuild')
92      ebuild.touch()
93      Path(tmpdir, 'test-1.2.3.ebuild').touch()
94      result = rust_uprev.find_ebuild_path(tmpdir, 'test',
95                                           rust_uprev.RustVersion(1, 3, 4))
96      self.assertEqual(result, ebuild)
97
98
99class RustVersionTest(unittest.TestCase):
100  """Tests for RustVersion class"""
101
102  def test_str(self):
103    obj = rust_uprev.RustVersion(major=1, minor=2, patch=3)
104    self.assertEqual(str(obj), '1.2.3')
105
106  def test_parse_version_only(self):
107    expected = rust_uprev.RustVersion(major=1, minor=2, patch=3)
108    actual = rust_uprev.RustVersion.parse('1.2.3')
109    self.assertEqual(expected, actual)
110
111  def test_parse_ebuild_name(self):
112    expected = rust_uprev.RustVersion(major=1, minor=2, patch=3)
113    actual = rust_uprev.RustVersion.parse_from_ebuild('rust-1.2.3.ebuild')
114    self.assertEqual(expected, actual)
115
116    actual = rust_uprev.RustVersion.parse_from_ebuild('rust-1.2.3-r1.ebuild')
117    self.assertEqual(expected, actual)
118
119  def test_parse_fail(self):
120    with self.assertRaises(AssertionError) as context:
121      rust_uprev.RustVersion.parse('invalid-rust-1.2.3')
122    self.assertEqual("failed to parse 'invalid-rust-1.2.3'",
123                     str(context.exception))
124
125
126class PrepareUprevTest(unittest.TestCase):
127  """Tests for prepare_uprev step in rust_uprev"""
128
129  def setUp(self):
130    self.bootstrap_version = rust_uprev.RustVersion(1, 1, 0)
131    self.version_old = rust_uprev.RustVersion(1, 2, 3)
132    self.version_new = rust_uprev.RustVersion(1, 3, 5)
133
134  @mock.patch.object(rust_uprev,
135                     'find_ebuild_for_rust_version',
136                     return_value='/path/to/ebuild')
137  @mock.patch.object(rust_uprev, 'find_ebuild_path')
138  @mock.patch.object(rust_uprev, 'get_command_output')
139  def test_success_with_template(self, mock_command, mock_find_ebuild,
140                                 _ebuild_for_version):
141    bootstrap_ebuild_path = Path(
142        '/path/to/rust-bootstrap/',
143        f'rust-bootstrap-{self.bootstrap_version}.ebuild')
144    mock_find_ebuild.return_value = bootstrap_ebuild_path
145    expected = (self.version_old, '/path/to/ebuild', self.bootstrap_version)
146    actual = rust_uprev.prepare_uprev(rust_version=self.version_new,
147                                      template=self.version_old)
148    self.assertEqual(expected, actual)
149    mock_command.assert_not_called()
150
151  @mock.patch.object(rust_uprev,
152                     'find_ebuild_for_rust_version',
153                     return_value='/path/to/ebuild')
154  @mock.patch.object(rust_uprev,
155                     'get_rust_bootstrap_version',
156                     return_value=RustVersion(0, 41, 12))
157  @mock.patch.object(rust_uprev, 'get_command_output')
158  def test_return_none_with_template_larger_than_input(self, mock_command,
159                                                       *_args):
160    ret = rust_uprev.prepare_uprev(rust_version=self.version_old,
161                                   template=self.version_new)
162    self.assertIsNone(ret)
163    mock_command.assert_not_called()
164
165  @mock.patch.object(rust_uprev, 'find_ebuild_path')
166  @mock.patch.object(os.path, 'exists')
167  @mock.patch.object(rust_uprev, 'get_command_output')
168  def test_success_without_template(self, mock_command, mock_exists,
169                                    mock_find_ebuild):
170    rust_ebuild_path = f'/path/to/rust/rust-{self.version_old}-r3.ebuild'
171    mock_command.return_value = rust_ebuild_path
172    bootstrap_ebuild_path = Path(
173        '/path/to/rust-bootstrap',
174        f'rust-bootstrap-{self.bootstrap_version}.ebuild')
175    mock_find_ebuild.return_value = bootstrap_ebuild_path
176    expected = (self.version_old, rust_ebuild_path, self.bootstrap_version)
177    actual = rust_uprev.prepare_uprev(rust_version=self.version_new,
178                                      template=None)
179    self.assertEqual(expected, actual)
180    mock_command.assert_called_once_with(['equery', 'w', 'rust'])
181    mock_exists.assert_not_called()
182
183  @mock.patch.object(rust_uprev,
184                     'get_rust_bootstrap_version',
185                     return_value=RustVersion(0, 41, 12))
186  @mock.patch.object(os.path, 'exists')
187  @mock.patch.object(rust_uprev, 'get_command_output')
188  def test_return_none_with_ebuild_larger_than_input(self, mock_command,
189                                                     mock_exists, *_args):
190    mock_command.return_value = f'/path/to/rust/rust-{self.version_new}.ebuild'
191    ret = rust_uprev.prepare_uprev(rust_version=self.version_old,
192                                   template=None)
193    self.assertIsNone(ret)
194    mock_exists.assert_not_called()
195
196  def test_prepare_uprev_from_json(self):
197    ebuild_path = '/path/to/the/ebuild'
198    json_result = (list(self.version_new), ebuild_path,
199                   list(self.bootstrap_version))
200    expected = (self.version_new, ebuild_path, self.bootstrap_version)
201    actual = rust_uprev.prepare_uprev_from_json(json_result)
202    self.assertEqual(expected, actual)
203
204
205class UpdateEbuildTest(unittest.TestCase):
206  """Tests for update_ebuild step in rust_uprev"""
207  ebuild_file_before = """
208BOOTSTRAP_VERSION="1.2.0"
209    """
210  ebuild_file_after = """
211BOOTSTRAP_VERSION="1.3.6"
212    """
213
214  def test_success(self):
215    mock_open = mock.mock_open(read_data=self.ebuild_file_before)
216    # ebuild_file and new bootstrap version are deliberately different
217    ebuild_file = '/path/to/rust/rust-1.3.5.ebuild'
218    with mock.patch('builtins.open', mock_open):
219      rust_uprev.update_ebuild(ebuild_file,
220                               rust_uprev.RustVersion.parse('1.3.6'))
221    mock_open.return_value.__enter__().write.assert_called_once_with(
222        self.ebuild_file_after)
223
224  def test_fail_when_ebuild_misses_a_variable(self):
225    mock_open = mock.mock_open(read_data='')
226    ebuild_file = '/path/to/rust/rust-1.3.5.ebuild'
227    with mock.patch('builtins.open', mock_open):
228      with self.assertRaises(RuntimeError) as context:
229        rust_uprev.update_ebuild(ebuild_file,
230                                 rust_uprev.RustVersion.parse('1.2.0'))
231    self.assertEqual('BOOTSTRAP_VERSION not found in rust ebuild',
232                     str(context.exception))
233
234
235class UpdateManifestTest(unittest.TestCase):
236  """Tests for update_manifest step in rust_uprev"""
237
238  # pylint: disable=protected-access
239  def _run_test_flip_mirror(self, before, after, add, expect_write):
240    mock_open = mock.mock_open(read_data=f'RESTRICT="{before}"')
241    with mock.patch('builtins.open', mock_open):
242      rust_uprev.flip_mirror_in_ebuild('', add=add)
243    if expect_write:
244      mock_open.return_value.__enter__().write.assert_called_once_with(
245          f'RESTRICT="{after}"')
246
247  def test_add_mirror_in_ebuild(self):
248    self._run_test_flip_mirror(before='variable1 variable2',
249                               after='variable1 variable2 mirror',
250                               add=True,
251                               expect_write=True)
252
253  def test_remove_mirror_in_ebuild(self):
254    self._run_test_flip_mirror(before='variable1 variable2 mirror',
255                               after='variable1 variable2',
256                               add=False,
257                               expect_write=True)
258
259  def test_add_mirror_when_exists(self):
260    self._run_test_flip_mirror(before='variable1 variable2 mirror',
261                               after='variable1 variable2 mirror',
262                               add=True,
263                               expect_write=False)
264
265  def test_remove_mirror_when_not_exists(self):
266    self._run_test_flip_mirror(before='variable1 variable2',
267                               after='variable1 variable2',
268                               add=False,
269                               expect_write=False)
270
271  @mock.patch.object(rust_uprev, 'flip_mirror_in_ebuild')
272  @mock.patch.object(rust_uprev, 'ebuild_actions')
273  def test_update_manifest(self, mock_run, mock_flip):
274    ebuild_file = Path('/path/to/rust/rust-1.1.1.ebuild')
275    rust_uprev.update_manifest(ebuild_file)
276    mock_run.assert_called_once_with('rust', ['manifest'])
277    mock_flip.assert_has_calls(
278        [mock.call(ebuild_file, add=True),
279         mock.call(ebuild_file, add=False)])
280
281
282class UpdateBootstrapEbuildTest(unittest.TestCase):
283  """Tests for rust_uprev.update_bootstrap_ebuild()"""
284
285  def test_update_bootstrap_ebuild(self):
286    # The update should do two things:
287    # 1. Create a copy of rust-bootstrap's ebuild with the new version number.
288    # 2. Add the old PV to RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE.
289    with tempfile.TemporaryDirectory() as tmpdir_str, \
290         mock.patch.object(rust_uprev, 'find_ebuild_path') as mock_find_ebuild:
291      tmpdir = Path(tmpdir_str)
292      bootstrapdir = Path.joinpath(tmpdir, 'rust-bootstrap')
293      bootstrapdir.mkdir()
294      old_ebuild = bootstrapdir.joinpath('rust-bootstrap-1.45.2.ebuild')
295      old_ebuild.write_text(encoding='utf-8',
296                            data="""
297some text
298RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=(
299\t1.43.1
300\t1.44.1
301)
302some more text
303""")
304      mock_find_ebuild.return_value = old_ebuild
305      rust_uprev.update_bootstrap_ebuild(rust_uprev.RustVersion(1, 46, 0))
306      new_ebuild = bootstrapdir.joinpath('rust-bootstrap-1.46.0.ebuild')
307      self.assertTrue(new_ebuild.exists())
308      text = new_ebuild.read_text()
309      self.assertEqual(
310          text, """
311some text
312RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=(
313\t1.43.1
314\t1.44.1
315\t1.45.2
316)
317some more text
318""")
319
320
321class UpdateRustPackagesTests(unittest.TestCase):
322  """Tests for update_rust_packages step."""
323
324  def setUp(self):
325    self.old_version = rust_uprev.RustVersion(1, 1, 0)
326    self.current_version = rust_uprev.RustVersion(1, 2, 3)
327    self.new_version = rust_uprev.RustVersion(1, 3, 5)
328    self.ebuild_file = os.path.join(rust_uprev.RUST_PATH,
329                                    'rust-{self.new_version}.ebuild')
330
331  def test_add_new_rust_packages(self):
332    package_before = (f'dev-lang/rust-{self.old_version}\n'
333                      f'dev-lang/rust-{self.current_version}')
334    package_after = (f'dev-lang/rust-{self.old_version}\n'
335                     f'dev-lang/rust-{self.current_version}\n'
336                     f'dev-lang/rust-{self.new_version}')
337    mock_open = mock.mock_open(read_data=package_before)
338    with mock.patch('builtins.open', mock_open):
339      rust_uprev.update_rust_packages(self.new_version, add=True)
340    mock_open.return_value.__enter__().write.assert_called_once_with(
341        package_after)
342
343  def test_remove_old_rust_packages(self):
344    package_before = (f'dev-lang/rust-{self.old_version}\n'
345                      f'dev-lang/rust-{self.current_version}\n'
346                      f'dev-lang/rust-{self.new_version}')
347    package_after = (f'dev-lang/rust-{self.current_version}\n'
348                     f'dev-lang/rust-{self.new_version}')
349    mock_open = mock.mock_open(read_data=package_before)
350    with mock.patch('builtins.open', mock_open):
351      rust_uprev.update_rust_packages(self.old_version, add=False)
352    mock_open.return_value.__enter__().write.assert_called_once_with(
353        package_after)
354
355
356class RustUprevOtherStagesTests(unittest.TestCase):
357  """Tests for other steps in rust_uprev"""
358
359  def setUp(self):
360    self.old_version = rust_uprev.RustVersion(1, 1, 0)
361    self.current_version = rust_uprev.RustVersion(1, 2, 3)
362    self.new_version = rust_uprev.RustVersion(1, 3, 5)
363    self.ebuild_file = os.path.join(rust_uprev.RUST_PATH,
364                                    'rust-{self.new_version}.ebuild')
365
366  @mock.patch.object(shutil, 'copyfile')
367  @mock.patch.object(os, 'listdir')
368  @mock.patch.object(subprocess, 'check_call')
369  def test_copy_patches(self, mock_call, mock_ls, mock_copy):
370    mock_ls.return_value = [
371        f'rust-{self.old_version}-patch-1.patch',
372        f'rust-{self.old_version}-patch-2-old.patch',
373        f'rust-{self.current_version}-patch-1.patch',
374        f'rust-{self.current_version}-patch-2-new.patch'
375    ]
376    rust_uprev.copy_patches(rust_uprev.RUST_PATH, self.current_version,
377                            self.new_version)
378    mock_copy.assert_has_calls([
379        mock.call(
380            os.path.join(rust_uprev.RUST_PATH, 'files',
381                         f'rust-{self.current_version}-patch-1.patch'),
382            os.path.join(rust_uprev.RUST_PATH, 'files',
383                         f'rust-{self.new_version}-patch-1.patch'),
384        ),
385        mock.call(
386            os.path.join(rust_uprev.RUST_PATH, 'files',
387                         f'rust-{self.current_version}-patch-2-new.patch'),
388            os.path.join(rust_uprev.RUST_PATH, 'files',
389                         f'rust-{self.new_version}-patch-2-new.patch'))
390    ])
391    mock_call.assert_called_once_with(
392        ['git', 'add', f'rust-{self.new_version}-*.patch'],
393        cwd=rust_uprev.RUST_PATH.joinpath('files'))
394
395  @mock.patch.object(shutil, 'copyfile')
396  @mock.patch.object(subprocess, 'check_call')
397  def test_create_ebuild(self, mock_call, mock_copy):
398    template_ebuild = f'/path/to/rust-{self.current_version}-r2.ebuild'
399    rust_uprev.create_ebuild(template_ebuild, self.new_version)
400    mock_copy.assert_called_once_with(
401        template_ebuild,
402        rust_uprev.RUST_PATH.joinpath(f'rust-{self.new_version}.ebuild'))
403    mock_call.assert_called_once_with(
404        ['git', 'add', f'rust-{self.new_version}.ebuild'],
405        cwd=rust_uprev.RUST_PATH)
406
407  @mock.patch.object(rust_uprev, 'find_ebuild_for_package')
408  @mock.patch.object(subprocess, 'check_call')
409  def test_remove_rust_bootstrap_version(self, mock_call, *_args):
410    bootstrap_path = os.path.join(rust_uprev.RUST_PATH, '..', 'rust-bootstrap')
411    rust_uprev.remove_rust_bootstrap_version(self.old_version, lambda *x: ())
412    mock_call.has_calls([
413        [
414            'git', 'rm',
415            os.path.join(bootstrap_path, 'files',
416                         f'rust-bootstrap-{self.old_version}-*.patch')
417        ],
418        [
419            'git', 'rm',
420            os.path.join(bootstrap_path,
421                         f'rust-bootstrap-{self.old_version}.ebuild')
422        ],
423    ])
424
425  @mock.patch.object(rust_uprev, 'find_ebuild_path')
426  @mock.patch.object(subprocess, 'check_call')
427  def test_remove_virtual_rust(self, mock_call, mock_find_ebuild):
428    ebuild_path = Path(
429        f'/some/dir/virtual/rust/rust-{self.old_version}.ebuild')
430    mock_find_ebuild.return_value = Path(ebuild_path)
431    rust_uprev.remove_virtual_rust(self.old_version)
432    mock_call.assert_called_once_with(
433        ['git', 'rm', str(ebuild_path.name)], cwd=ebuild_path.parent)
434
435  @mock.patch.object(rust_uprev, 'find_ebuild_path')
436  @mock.patch.object(shutil, 'copyfile')
437  @mock.patch.object(subprocess, 'check_call')
438  def test_update_virtual_rust(self, mock_call, mock_copy, mock_find_ebuild):
439    ebuild_path = Path(
440        f'/some/dir/virtual/rust/rust-{self.current_version}.ebuild')
441    mock_find_ebuild.return_value = Path(ebuild_path)
442    rust_uprev.update_virtual_rust(self.current_version, self.new_version)
443    mock_call.assert_called_once_with(
444        ['git', 'add', f'rust-{self.new_version}.ebuild'],
445        cwd=ebuild_path.parent)
446    mock_copy.assert_called_once_with(
447        ebuild_path.parent.joinpath(f'rust-{self.current_version}.ebuild'),
448        ebuild_path.parent.joinpath(f'rust-{self.new_version}.ebuild'))
449
450  @mock.patch.object(os, 'listdir')
451  def test_find_oldest_rust_version_in_chroot_pass(self, mock_ls):
452    oldest_version_name = f'rust-{self.old_version}.ebuild'
453    mock_ls.return_value = [
454        oldest_version_name, f'rust-{self.current_version}.ebuild',
455        f'rust-{self.new_version}.ebuild'
456    ]
457    actual = rust_uprev.find_oldest_rust_version_in_chroot()
458    expected = (self.old_version,
459                os.path.join(rust_uprev.RUST_PATH, oldest_version_name))
460    self.assertEqual(expected, actual)
461
462  @mock.patch.object(os, 'listdir')
463  def test_find_oldest_rust_version_in_chroot_fail_with_only_one_ebuild(
464      self, mock_ls):
465    mock_ls.return_value = [f'rust-{self.new_version}.ebuild']
466    with self.assertRaises(RuntimeError) as context:
467      rust_uprev.find_oldest_rust_version_in_chroot()
468    self.assertEqual('Expect to find more than one Rust versions',
469                     str(context.exception))
470
471  @mock.patch.object(rust_uprev, 'get_command_output')
472  @mock.patch.object(git, 'CreateBranch')
473  def test_create_new_repo(self, mock_branch, mock_output):
474    mock_output.return_value = ''
475    rust_uprev.create_new_repo(self.new_version)
476    mock_branch.assert_called_once_with(rust_uprev.RUST_PATH,
477                                        f'rust-to-{self.new_version}')
478
479  @mock.patch.object(rust_uprev, 'get_command_output')
480  @mock.patch.object(subprocess, 'check_call')
481  def test_build_cross_compiler(self, mock_call, mock_output):
482    mock_output.return_value = f'rust-{self.new_version}.ebuild'
483    cros_targets = [
484        'x86_64-cros-linux-gnu',
485        'armv7a-cros-linux-gnueabihf',
486        'aarch64-cros-linux-gnu',
487    ]
488    all_triples = ['x86_64-pc-linux-gnu'] + cros_targets
489    rust_ebuild = 'RUSTC_TARGET_TRIPLES=(' + '\n\t'.join(all_triples) + ')'
490    mock_open = mock.mock_open(read_data=rust_ebuild)
491    with mock.patch('builtins.open', mock_open):
492      rust_uprev.build_cross_compiler()
493
494    mock_call.assert_called_once_with(
495        ['sudo', 'emerge', '-j', '-G'] +
496        [f'cross-{x}/gcc' for x in cros_targets + ['arm-none-eabi']])
497
498
499if __name__ == '__main__':
500  unittest.main()
501