1#!/usr/bin/env python3 2# Copyright 2020 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Tests for rust_uprev.py""" 7 8import os 9from pathlib import Path 10import shutil 11import subprocess 12import tempfile 13import unittest 14from unittest import mock 15 16from llvm_tools import git 17 18 19# rust_uprev sets SOURCE_ROOT to the output of `repo --show-toplevel`. 20# The mock below makes us not actually run repo but use a fake value 21# instead. 22with mock.patch("subprocess.check_output", return_value="/fake/chromiumos"): 23 import rust_uprev 24 25 26def _fail_command(cmd, *_args, **_kwargs): 27 err = subprocess.CalledProcessError(returncode=1, cmd=cmd) 28 err.stderr = b"mock failure" 29 raise err 30 31 32def start_mock(obj, *args, **kwargs): 33 """Creates a patcher, starts it, and registers a cleanup to stop it. 34 35 Args: 36 obj: 37 the object to attach the cleanup to 38 *args: 39 passed to mock.patch() 40 **kwargs: 41 passsed to mock.patch() 42 """ 43 patcher = mock.patch(*args, **kwargs) 44 val = patcher.start() 45 obj.addCleanup(patcher.stop) 46 return val 47 48 49class FetchDistfileTest(unittest.TestCase): 50 """Tests rust_uprev.fetch_distfile_from_mirror()""" 51 52 @mock.patch.object( 53 rust_uprev, "get_distdir", return_value=Path("/fake/distfiles") 54 ) 55 @mock.patch.object(subprocess, "call", side_effect=_fail_command) 56 def test_fetch_difstfile_fail(self, *_args) -> None: 57 with self.assertRaises(subprocess.CalledProcessError): 58 rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz") 59 60 @mock.patch.object( 61 rust_uprev, 62 "get_command_output_unchecked", 63 return_value="AccessDeniedException: Access denied.", 64 ) 65 @mock.patch.object( 66 rust_uprev, "get_distdir", return_value=Path("/fake/distfiles") 67 ) 68 @mock.patch.object(subprocess, "call", return_value=0) 69 def test_fetch_distfile_acl_access_denied(self, *_args) -> None: 70 rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz") 71 72 @mock.patch.object( 73 rust_uprev, 74 "get_command_output_unchecked", 75 return_value='[ { "entity": "allUsers", "role": "READER" } ]', 76 ) 77 @mock.patch.object( 78 rust_uprev, "get_distdir", return_value=Path("/fake/distfiles") 79 ) 80 @mock.patch.object(subprocess, "call", return_value=0) 81 def test_fetch_distfile_acl_ok(self, *_args) -> None: 82 rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz") 83 84 @mock.patch.object( 85 rust_uprev, 86 "get_command_output_unchecked", 87 return_value='[ { "entity": "[email protected]", "role": "OWNER" } ]', 88 ) 89 @mock.patch.object( 90 rust_uprev, "get_distdir", return_value=Path("/fake/distfiles") 91 ) 92 @mock.patch.object(subprocess, "call", return_value=0) 93 def test_fetch_distfile_acl_wrong(self, *_args) -> None: 94 with self.assertRaisesRegex(Exception, "allUsers.*READER"): 95 with self.assertLogs(level="ERROR") as log: 96 rust_uprev.fetch_distfile_from_mirror("test_distfile.tar.gz") 97 self.assertIn( 98 '[ { "entity": "[email protected]", "role": "OWNER" } ]', 99 "\n".join(log.output), 100 ) 101 102 103class FetchRustSrcFromUpstreamTest(unittest.TestCase): 104 """Tests for rust_uprev.fetch_rust_src_from_upstream.""" 105 106 def setUp(self) -> None: 107 self._mock_get_distdir = start_mock( 108 self, 109 "rust_uprev.get_distdir", 110 return_value=Path("/fake/distfiles"), 111 ) 112 113 self._mock_gpg = start_mock( 114 self, 115 "subprocess.run", 116 side_effect=self.fake_gpg, 117 ) 118 119 self._mock_urlretrieve = start_mock( 120 self, 121 "urllib.request.urlretrieve", 122 side_effect=self.fake_urlretrieve, 123 ) 124 125 self._mock_rust_signing_key = start_mock( 126 self, 127 "rust_uprev.RUST_SIGNING_KEY", 128 "1234567", 129 ) 130 131 @staticmethod 132 def fake_urlretrieve(src: str, dest: Path) -> None: 133 pass 134 135 @staticmethod 136 def fake_gpg(cmd, **_kwargs): 137 val = mock.Mock() 138 val.returncode = 0 139 val.stdout = "" 140 if "--verify" in cmd: 141 val.stdout = "GOODSIG 1234567" 142 return val 143 144 def test_success(self): 145 with mock.patch("rust_uprev.GPG", "gnupg"): 146 rust_uprev.fetch_rust_src_from_upstream( 147 "fakehttps://rustc-1.60.3-src.tar.gz", 148 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 149 ) 150 self._mock_urlretrieve.has_calls( 151 [ 152 ( 153 "fakehttps://rustc-1.60.3-src.tar.gz", 154 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 155 ), 156 ( 157 "fakehttps://rustc-1.60.3-src.tar.gz.asc", 158 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz.asc"), 159 ), 160 ] 161 ) 162 self._mock_gpg.has_calls( 163 [ 164 (["gnupg", "--refresh-keys", "1234567"], {"check": True}), 165 ] 166 ) 167 168 def test_no_signature_file(self): 169 def _urlretrieve(src, dest): 170 if src.endswith(".asc"): 171 raise Exception("404 not found") 172 return self.fake_urlretrieve(src, dest) 173 174 self._mock_urlretrieve.side_effect = _urlretrieve 175 176 with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx: 177 rust_uprev.fetch_rust_src_from_upstream( 178 "fakehttps://rustc-1.60.3-src.tar.gz", 179 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 180 ) 181 self.assertIn("error fetching signature file", ctx.exception.message) 182 183 def test_key_expired(self): 184 def _gpg_verify(cmd, *args, **kwargs): 185 val = self.fake_gpg(cmd, *args, **kwargs) 186 if "--verify" in cmd: 187 val.stdout = "EXPKEYSIG 1234567" 188 return val 189 190 self._mock_gpg.side_effect = _gpg_verify 191 192 with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx: 193 rust_uprev.fetch_rust_src_from_upstream( 194 "fakehttps://rustc-1.60.3-src.tar.gz", 195 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 196 ) 197 self.assertIn("key has expired", ctx.exception.message) 198 199 def test_key_revoked(self): 200 def _gpg_verify(cmd, *args, **kwargs): 201 val = self.fake_gpg(cmd, *args, **kwargs) 202 if "--verify" in cmd: 203 val.stdout = "REVKEYSIG 1234567" 204 return val 205 206 self._mock_gpg.side_effect = _gpg_verify 207 208 with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx: 209 rust_uprev.fetch_rust_src_from_upstream( 210 "fakehttps://rustc-1.60.3-src.tar.gz", 211 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 212 ) 213 self.assertIn("key has been revoked", ctx.exception.message) 214 215 def test_signature_expired(self): 216 def _gpg_verify(cmd, *args, **kwargs): 217 val = self.fake_gpg(cmd, *args, **kwargs) 218 if "--verify" in cmd: 219 val.stdout = "EXPSIG 1234567" 220 return val 221 222 self._mock_gpg.side_effect = _gpg_verify 223 224 with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx: 225 rust_uprev.fetch_rust_src_from_upstream( 226 "fakehttps://rustc-1.60.3-src.tar.gz", 227 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 228 ) 229 self.assertIn("signature has expired", ctx.exception.message) 230 231 def test_wrong_key(self): 232 def _gpg_verify(cmd, *args, **kwargs): 233 val = self.fake_gpg(cmd, *args, **kwargs) 234 if "--verify" in cmd: 235 val.stdout = "GOODSIG 0000000" 236 return val 237 238 self._mock_gpg.side_effect = _gpg_verify 239 240 with self.assertRaises(rust_uprev.SignatureVerificationError) as ctx: 241 rust_uprev.fetch_rust_src_from_upstream( 242 "fakehttps://rustc-1.60.3-src.tar.gz", 243 Path("/fake/distfiles/rustc-1.60.3-src.tar.gz"), 244 ) 245 self.assertIn("1234567 not found", ctx.exception.message) 246 247 248class FindEbuildPathTest(unittest.TestCase): 249 """Tests for rust_uprev.find_ebuild_path()""" 250 251 def test_exact_version(self): 252 with tempfile.TemporaryDirectory() as t: 253 tmpdir = Path(t) 254 ebuild = tmpdir / "test-1.3.4.ebuild" 255 ebuild.touch() 256 (tmpdir / "test-1.2.3.ebuild").touch() 257 result = rust_uprev.find_ebuild_path( 258 tmpdir, "test", rust_uprev.RustVersion(1, 3, 4) 259 ) 260 self.assertEqual(result, ebuild) 261 262 def test_no_version(self): 263 with tempfile.TemporaryDirectory() as t: 264 tmpdir = Path(t) 265 ebuild = tmpdir / "test-1.2.3.ebuild" 266 ebuild.touch() 267 result = rust_uprev.find_ebuild_path(tmpdir, "test") 268 self.assertEqual(result, ebuild) 269 270 def test_patch_version(self): 271 with tempfile.TemporaryDirectory() as t: 272 tmpdir = Path(t) 273 ebuild = tmpdir / "test-1.3.4-r3.ebuild" 274 ebuild.touch() 275 (tmpdir / "test-1.2.3.ebuild").touch() 276 result = rust_uprev.find_ebuild_path( 277 tmpdir, "test", rust_uprev.RustVersion(1, 3, 4) 278 ) 279 self.assertEqual(result, ebuild) 280 281 def test_multiple_versions(self): 282 with tempfile.TemporaryDirectory() as t: 283 tmpdir = Path(t) 284 (tmpdir / "test-1.3.4-r3.ebuild").touch() 285 (tmpdir / "test-1.3.5.ebuild").touch() 286 with self.assertRaises(AssertionError): 287 rust_uprev.find_ebuild_path(tmpdir, "test") 288 289 def test_selected_version(self): 290 with tempfile.TemporaryDirectory() as t: 291 tmpdir = Path(t) 292 ebuild = tmpdir / "test-1.3.4-r3.ebuild" 293 ebuild.touch() 294 (tmpdir / "test-1.3.5.ebuild").touch() 295 result = rust_uprev.find_ebuild_path( 296 tmpdir, "test", rust_uprev.RustVersion(1, 3, 4) 297 ) 298 self.assertEqual(result, ebuild) 299 300 def test_symlink(self): 301 # Symlinks to ebuilds in the same directory are allowed, and the return 302 # value is the regular file. 303 with tempfile.TemporaryDirectory() as t: 304 tmpdir = Path(t) 305 ebuild = tmpdir / "test-1.3.4.ebuild" 306 ebuild.touch() 307 (tmpdir / "test-1.3.4-r1.ebuild").symlink_to("test-1.3.4.ebuild") 308 result = rust_uprev.find_ebuild_path(tmpdir, "test") 309 self.assertEqual(result, ebuild) 310 311 312class FindRustVersionsTest(unittest.TestCase): 313 """Tests for rust_uprev.find_rust_versions.""" 314 315 def test_with_symlinks(self): 316 with tempfile.TemporaryDirectory() as t: 317 tmpdir = Path(t) 318 rust_1_49_1_ebuild = tmpdir / "rust-1.49.1.ebuild" 319 rust_1_50_0_ebuild = tmpdir / "rust-1.50.0.ebuild" 320 rust_1_50_0_r1_ebuild = tmpdir / "rust-1.50.0-r1.ebuild" 321 rust_1_49_1_ebuild.touch() 322 rust_1_50_0_ebuild.touch() 323 rust_1_50_0_r1_ebuild.symlink_to(rust_1_50_0_ebuild) 324 with mock.patch("rust_uprev.RUST_PATH", tmpdir): 325 actual = rust_uprev.find_rust_versions() 326 expected = [ 327 (rust_uprev.RustVersion(1, 49, 1), rust_1_49_1_ebuild), 328 (rust_uprev.RustVersion(1, 50, 0), rust_1_50_0_ebuild), 329 ] 330 self.assertEqual(actual, expected) 331 332 333class MirrorHasFileTest(unittest.TestCase): 334 """Tests for rust_uprev.mirror_has_file.""" 335 336 @mock.patch.object(subprocess, "run") 337 def test_no(self, mock_run): 338 mock_run.return_value = mock.Mock( 339 returncode=1, 340 stdout="CommandException: One or more URLs matched no objects.", 341 ) 342 self.assertFalse(rust_uprev.mirror_has_file("rustc-1.69.0-src.tar.gz")) 343 344 @mock.patch.object(subprocess, "run") 345 def test_yes(self, mock_run): 346 mock_run.return_value = mock.Mock( 347 returncode=0, 348 # pylint: disable=line-too-long 349 stdout="gs://chromeos-localmirror/distfiles/rustc-1.69.0-src.tar.gz", 350 ) 351 self.assertTrue(rust_uprev.mirror_has_file("rustc-1.69.0-src.tar.gz")) 352 353 354class MirrorRustSourceTest(unittest.TestCase): 355 """Tests for rust_uprev.mirror_rust_source.""" 356 357 def setUp(self) -> None: 358 start_mock(self, "rust_uprev.GSUTIL", "gsutil") 359 start_mock(self, "rust_uprev.MIRROR_PATH", "fakegs://fakemirror/") 360 start_mock( 361 self, "rust_uprev.get_distdir", return_value=Path("/fake/distfiles") 362 ) 363 self.mock_mirror_has_file = start_mock( 364 self, 365 "rust_uprev.mirror_has_file", 366 ) 367 self.mock_fetch_rust_src_from_upstream = start_mock( 368 self, 369 "rust_uprev.fetch_rust_src_from_upstream", 370 ) 371 self.mock_subprocess_run = start_mock( 372 self, 373 "subprocess.run", 374 ) 375 376 def test_already_present(self): 377 self.mock_mirror_has_file.return_value = True 378 rust_uprev.mirror_rust_source( 379 rust_uprev.RustVersion.parse("1.67.3"), 380 ) 381 self.mock_fetch_rust_src_from_upstream.assert_not_called() 382 self.mock_subprocess_run.assert_not_called() 383 384 def test_fetch_and_upload(self): 385 self.mock_mirror_has_file.return_value = False 386 rust_uprev.mirror_rust_source( 387 rust_uprev.RustVersion.parse("1.67.3"), 388 ) 389 self.mock_fetch_rust_src_from_upstream.called_once() 390 self.mock_subprocess_run.has_calls( 391 [ 392 ( 393 [ 394 "gsutil", 395 "cp", 396 "-a", 397 "public-read", 398 "/fake/distdir/rustc-1.67.3-src.tar.gz", 399 "fakegs://fakemirror/rustc-1.67.3-src.tar.gz", 400 ] 401 ), 402 ] 403 ) 404 405 406class RemoveEbuildVersionTest(unittest.TestCase): 407 """Tests for rust_uprev.remove_ebuild_version()""" 408 409 @mock.patch.object(subprocess, "check_call") 410 def test_single(self, check_call): 411 with tempfile.TemporaryDirectory() as tmpdir: 412 ebuild_dir = Path(tmpdir, "test-ebuilds") 413 ebuild_dir.mkdir() 414 ebuild = Path(ebuild_dir, "test-3.1.4.ebuild") 415 ebuild.touch() 416 Path(ebuild_dir, "unrelated-1.0.0.ebuild").touch() 417 rust_uprev.remove_ebuild_version( 418 ebuild_dir, "test", rust_uprev.RustVersion(3, 1, 4) 419 ) 420 check_call.assert_called_once_with( 421 ["git", "rm", "test-3.1.4.ebuild"], cwd=ebuild_dir 422 ) 423 424 @mock.patch.object(subprocess, "check_call") 425 def test_symlink(self, check_call): 426 with tempfile.TemporaryDirectory() as tmpdir: 427 ebuild_dir = Path(tmpdir, "test-ebuilds") 428 ebuild_dir.mkdir() 429 ebuild = Path(ebuild_dir, "test-3.1.4.ebuild") 430 ebuild.touch() 431 symlink = Path(ebuild_dir, "test-3.1.4-r5.ebuild") 432 symlink.symlink_to(ebuild.name) 433 Path(ebuild_dir, "unrelated-1.0.0.ebuild").touch() 434 rust_uprev.remove_ebuild_version( 435 ebuild_dir, "test", rust_uprev.RustVersion(3, 1, 4) 436 ) 437 check_call.assert_has_calls( 438 [ 439 mock.call( 440 ["git", "rm", "test-3.1.4.ebuild"], cwd=ebuild_dir 441 ), 442 mock.call( 443 ["git", "rm", "test-3.1.4-r5.ebuild"], cwd=ebuild_dir 444 ), 445 ], 446 any_order=True, 447 ) 448 449 450class RustVersionTest(unittest.TestCase): 451 """Tests for RustVersion class""" 452 453 def test_str(self): 454 obj = rust_uprev.RustVersion(major=1, minor=2, patch=3) 455 self.assertEqual(str(obj), "1.2.3") 456 457 def test_parse_version_only(self): 458 expected = rust_uprev.RustVersion(major=1, minor=2, patch=3) 459 actual = rust_uprev.RustVersion.parse("1.2.3") 460 self.assertEqual(expected, actual) 461 462 def test_parse_ebuild_name(self): 463 expected = rust_uprev.RustVersion(major=1, minor=2, patch=3) 464 actual = rust_uprev.RustVersion.parse_from_ebuild("rust-1.2.3.ebuild") 465 self.assertEqual(expected, actual) 466 467 actual = rust_uprev.RustVersion.parse_from_ebuild( 468 "rust-1.2.3-r1.ebuild" 469 ) 470 self.assertEqual(expected, actual) 471 472 def test_parse_fail(self): 473 with self.assertRaises(AssertionError) as context: 474 rust_uprev.RustVersion.parse("invalid-rust-1.2.3") 475 self.assertEqual( 476 "failed to parse 'invalid-rust-1.2.3'", str(context.exception) 477 ) 478 479 480class PrepareUprevTest(unittest.TestCase): 481 """Tests for prepare_uprev step in rust_uprev""" 482 483 def setUp(self): 484 self.version_old = rust_uprev.RustVersion(1, 2, 3) 485 self.version_new = rust_uprev.RustVersion(1, 3, 5) 486 487 @mock.patch.object( 488 rust_uprev, 489 "find_ebuild_for_rust_version", 490 return_value=Path("/path/to/ebuild"), 491 ) 492 @mock.patch.object(rust_uprev, "get_command_output") 493 def test_success_with_template(self, mock_command, _ebuild_for_version): 494 expected = rust_uprev.PreparedUprev(self.version_old) 495 actual = rust_uprev.prepare_uprev( 496 rust_version=self.version_new, template=self.version_old 497 ) 498 self.assertEqual(expected, actual) 499 mock_command.assert_not_called() 500 501 @mock.patch.object( 502 rust_uprev, 503 "find_ebuild_for_rust_version", 504 return_value="/path/to/ebuild", 505 ) 506 @mock.patch.object(rust_uprev, "get_command_output") 507 def test_return_none_with_template_larger_than_input( 508 self, mock_command, *_args 509 ): 510 ret = rust_uprev.prepare_uprev( 511 rust_version=self.version_old, template=self.version_new 512 ) 513 self.assertIsNone(ret) 514 mock_command.assert_not_called() 515 516 def test_prepare_uprev_from_json(self): 517 json_result = (list(self.version_new),) 518 expected = rust_uprev.PreparedUprev( 519 self.version_new, 520 ) 521 actual = rust_uprev.prepare_uprev_from_json(json_result) 522 self.assertEqual(expected, actual) 523 524 525class ToggleProfileData(unittest.TestCase): 526 """Tests functionality to include or exclude profile data from SRC_URI.""" 527 528 ebuild_with_profdata = """ 529# Some text here. 530INCLUDE_PROFDATA_IN_SRC_URI=yes 531some code here 532""" 533 534 ebuild_without_profdata = """ 535# Some text here. 536INCLUDE_PROFDATA_IN_SRC_URI= 537some code here 538""" 539 540 ebuild_unexpected_content = """ 541# Does not contain OMIT_PROFDATA_FROM_SRC_URI assignment 542""" 543 544 def setUp(self): 545 self.mock_read_text = start_mock(self, "pathlib.Path.read_text") 546 547 def test_turn_off_profdata(self): 548 # Test that a file with profdata on is rewritten to a file with 549 # profdata off. 550 self.mock_read_text.return_value = self.ebuild_with_profdata 551 ebuild_file = "/path/to/eclass/cros-rustc.eclass" 552 with mock.patch("pathlib.Path.write_text") as mock_write_text: 553 rust_uprev.set_include_profdata_src(ebuild_file, include=False) 554 mock_write_text.assert_called_once_with( 555 self.ebuild_without_profdata, encoding="utf-8" 556 ) 557 558 def test_turn_on_profdata(self): 559 # Test that a file with profdata off is rewritten to a file with 560 # profdata on. 561 self.mock_read_text.return_value = self.ebuild_without_profdata 562 ebuild_file = "/path/to/eclass/cros-rustc.eclass" 563 with mock.patch("pathlib.Path.write_text") as mock_write_text: 564 rust_uprev.set_include_profdata_src(ebuild_file, include=True) 565 mock_write_text.assert_called_once_with( 566 self.ebuild_with_profdata, encoding="utf-8" 567 ) 568 569 def test_turn_on_profdata_fails_if_no_assignment(self): 570 # Test that if the string the code expects to find is not found, 571 # this causes an exception and the file is not overwritten. 572 self.mock_read_text.return_value = self.ebuild_unexpected_content 573 ebuild_file = "/path/to/eclass/cros-rustc.eclass" 574 with mock.patch("pathlib.Path.write_text") as mock_write_text: 575 with self.assertRaises(Exception): 576 rust_uprev.set_include_profdata_src(ebuild_file, include=False) 577 mock_write_text.assert_not_called() 578 579 580class UpdateBootstrapVersionTest(unittest.TestCase): 581 """Tests for update_bootstrap_version step in rust_uprev""" 582 583 ebuild_file_before = """ 584BOOTSTRAP_VERSION="1.2.0" 585 """ 586 ebuild_file_after = """ 587BOOTSTRAP_VERSION="1.3.6" 588 """ 589 590 def setUp(self): 591 self.mock_read_text = start_mock(self, "pathlib.Path.read_text") 592 593 def test_success(self): 594 self.mock_read_text.return_value = self.ebuild_file_before 595 # ebuild_file and new bootstrap version are deliberately different 596 ebuild_file = "/path/to/rust/cros-rustc.eclass" 597 with mock.patch("pathlib.Path.write_text") as mock_write_text: 598 rust_uprev.update_bootstrap_version( 599 ebuild_file, rust_uprev.RustVersion.parse("1.3.6") 600 ) 601 mock_write_text.assert_called_once_with( 602 self.ebuild_file_after, encoding="utf-8" 603 ) 604 605 def test_fail_when_ebuild_misses_a_variable(self): 606 self.mock_read_text.return_value = "" 607 ebuild_file = "/path/to/rust/rust-1.3.5.ebuild" 608 with self.assertRaises(RuntimeError) as context: 609 rust_uprev.update_bootstrap_version( 610 ebuild_file, rust_uprev.RustVersion.parse("1.2.0") 611 ) 612 self.assertEqual( 613 "BOOTSTRAP_VERSION not found in /path/to/rust/rust-1.3.5.ebuild", 614 str(context.exception), 615 ) 616 617 618class UpdateRustPackagesTests(unittest.TestCase): 619 """Tests for update_rust_packages step.""" 620 621 def setUp(self): 622 self.old_version = rust_uprev.RustVersion(1, 1, 0) 623 self.current_version = rust_uprev.RustVersion(1, 2, 3) 624 self.new_version = rust_uprev.RustVersion(1, 3, 5) 625 self.ebuild_file = os.path.join( 626 rust_uprev.RUST_PATH, "rust-{self.new_version}.ebuild" 627 ) 628 629 def test_add_new_rust_packages(self): 630 package_before = ( 631 f"dev-lang/rust-{self.old_version}\n" 632 f"dev-lang/rust-{self.current_version}" 633 ) 634 package_after = ( 635 f"dev-lang/rust-{self.old_version}\n" 636 f"dev-lang/rust-{self.current_version}\n" 637 f"dev-lang/rust-{self.new_version}" 638 ) 639 mock_open = mock.mock_open(read_data=package_before) 640 with mock.patch("builtins.open", mock_open): 641 rust_uprev.update_rust_packages( 642 "dev-lang/rust", self.new_version, add=True 643 ) 644 mock_open.return_value.__enter__().write.assert_called_once_with( 645 package_after 646 ) 647 648 def test_remove_old_rust_packages(self): 649 package_before = ( 650 f"dev-lang/rust-{self.old_version}\n" 651 f"dev-lang/rust-{self.current_version}\n" 652 f"dev-lang/rust-{self.new_version}" 653 ) 654 package_after = ( 655 f"dev-lang/rust-{self.current_version}\n" 656 f"dev-lang/rust-{self.new_version}" 657 ) 658 mock_open = mock.mock_open(read_data=package_before) 659 with mock.patch("builtins.open", mock_open): 660 rust_uprev.update_rust_packages( 661 "dev-lang/rust", self.old_version, add=False 662 ) 663 mock_open.return_value.__enter__().write.assert_called_once_with( 664 package_after 665 ) 666 667 668class RustUprevOtherStagesTests(unittest.TestCase): 669 """Tests for other steps in rust_uprev""" 670 671 def setUp(self): 672 self.old_version = rust_uprev.RustVersion(1, 1, 0) 673 self.current_version = rust_uprev.RustVersion(1, 2, 3) 674 self.new_version = rust_uprev.RustVersion(1, 3, 5) 675 self.ebuild_file = os.path.join( 676 rust_uprev.RUST_PATH, "rust-{self.new_version}.ebuild" 677 ) 678 679 @mock.patch.object(shutil, "copyfile") 680 @mock.patch.object(subprocess, "check_call") 681 def test_create_rust_ebuild(self, mock_call, mock_copy): 682 template_ebuild = ( 683 rust_uprev.EBUILD_PREFIX 684 / f"dev-lang/rust/rust-{self.current_version}.ebuild" 685 ) 686 new_ebuild = ( 687 rust_uprev.EBUILD_PREFIX 688 / f"dev-lang/rust/rust-{self.new_version}.ebuild" 689 ) 690 rust_uprev.create_ebuild( 691 "dev-lang", "rust", self.current_version, self.new_version 692 ) 693 mock_copy.assert_called_once_with( 694 template_ebuild, 695 new_ebuild, 696 ) 697 mock_call.assert_called_once_with( 698 ["git", "add", f"rust-{self.new_version}.ebuild"], 699 cwd=new_ebuild.parent, 700 ) 701 702 @mock.patch.object(shutil, "copyfile") 703 @mock.patch.object(subprocess, "check_call") 704 def test_create_rust_host_ebuild(self, mock_call, mock_copy): 705 template_ebuild = ( 706 rust_uprev.EBUILD_PREFIX 707 / f"dev-lang/rust-host/rust-host-{self.current_version}.ebuild" 708 ) 709 new_ebuild = ( 710 rust_uprev.EBUILD_PREFIX 711 / f"dev-lang/rust-host/rust-host-{self.new_version}.ebuild" 712 ) 713 rust_uprev.create_ebuild( 714 "dev-lang", "rust-host", self.current_version, self.new_version 715 ) 716 mock_copy.assert_called_once_with( 717 template_ebuild, 718 new_ebuild, 719 ) 720 mock_call.assert_called_once_with( 721 ["git", "add", f"rust-host-{self.new_version}.ebuild"], 722 cwd=new_ebuild.parent, 723 ) 724 725 @mock.patch.object(subprocess, "check_call") 726 def test_remove_virtual_rust(self, mock_call): 727 with tempfile.TemporaryDirectory() as tmpdir: 728 ebuild_path = Path( 729 tmpdir, f"virtual/rust/rust-{self.old_version}.ebuild" 730 ) 731 os.makedirs(ebuild_path.parent) 732 ebuild_path.touch() 733 with mock.patch("rust_uprev.EBUILD_PREFIX", Path(tmpdir)): 734 rust_uprev.remove_virtual_rust(self.old_version) 735 mock_call.assert_called_once_with( 736 ["git", "rm", str(ebuild_path.name)], cwd=ebuild_path.parent 737 ) 738 739 @mock.patch.object(subprocess, "check_call") 740 def test_remove_virtual_rust_with_symlink(self, mock_call): 741 with tempfile.TemporaryDirectory() as tmpdir: 742 ebuild_path = Path( 743 tmpdir, f"virtual/rust/rust-{self.old_version}.ebuild" 744 ) 745 symlink_path = Path( 746 tmpdir, f"virtual/rust/rust-{self.old_version}-r14.ebuild" 747 ) 748 os.makedirs(ebuild_path.parent) 749 ebuild_path.touch() 750 symlink_path.symlink_to(ebuild_path.name) 751 with mock.patch("rust_uprev.EBUILD_PREFIX", Path(tmpdir)): 752 rust_uprev.remove_virtual_rust(self.old_version) 753 mock_call.assert_has_calls( 754 [ 755 mock.call( 756 ["git", "rm", ebuild_path.name], 757 cwd=ebuild_path.parent, 758 ), 759 mock.call( 760 ["git", "rm", symlink_path.name], 761 cwd=ebuild_path.parent, 762 ), 763 ], 764 any_order=True, 765 ) 766 767 @mock.patch.object(rust_uprev, "find_ebuild_path") 768 @mock.patch.object(shutil, "copyfile") 769 @mock.patch.object(subprocess, "check_call") 770 def test_update_virtual_rust(self, mock_call, mock_copy, mock_find_ebuild): 771 ebuild_path = Path( 772 f"/some/dir/virtual/rust/rust-{self.current_version}.ebuild" 773 ) 774 mock_find_ebuild.return_value = Path(ebuild_path) 775 rust_uprev.update_virtual_rust(self.current_version, self.new_version) 776 mock_call.assert_called_once_with( 777 ["git", "add", f"rust-{self.new_version}.ebuild"], 778 cwd=ebuild_path.parent, 779 ) 780 mock_copy.assert_called_once_with( 781 ebuild_path.parent.joinpath(f"rust-{self.current_version}.ebuild"), 782 ebuild_path.parent.joinpath(f"rust-{self.new_version}.ebuild"), 783 ) 784 785 @mock.patch("rust_uprev.find_rust_versions") 786 def test_find_oldest_rust_version_pass(self, rust_versions): 787 oldest_version_name = f"rust-{self.old_version}.ebuild" 788 rust_versions.return_value = [ 789 (self.old_version, oldest_version_name), 790 (self.current_version, f"rust-{self.current_version}.ebuild"), 791 (self.new_version, f"rust-{self.new_version}.ebuild"), 792 ] 793 actual = rust_uprev.find_oldest_rust_version() 794 expected = self.old_version 795 self.assertEqual(expected, actual) 796 797 @mock.patch("rust_uprev.find_rust_versions") 798 def test_find_oldest_rust_version_fail_with_only_one_ebuild( 799 self, rust_versions 800 ): 801 rust_versions.return_value = [ 802 (self.new_version, f"rust-{self.new_version}.ebuild"), 803 ] 804 with self.assertRaises(RuntimeError) as context: 805 rust_uprev.find_oldest_rust_version() 806 self.assertEqual( 807 "Expect to find more than one Rust versions", str(context.exception) 808 ) 809 810 @mock.patch.object(rust_uprev, "get_command_output") 811 @mock.patch.object(git, "CreateBranch") 812 def test_create_new_repo(self, mock_branch, mock_output): 813 mock_output.return_value = "" 814 rust_uprev.create_new_repo(self.new_version) 815 mock_branch.assert_called_once_with( 816 rust_uprev.EBUILD_PREFIX, f"rust-to-{self.new_version}" 817 ) 818 819 @mock.patch.object(rust_uprev, "run_in_chroot") 820 def test_build_cross_compiler(self, mock_run_in_chroot): 821 cros_targets = [ 822 "x86_64-cros-linux-gnu", 823 "armv7a-cros-linux-gnueabihf", 824 "aarch64-cros-linux-gnu", 825 ] 826 all_triples = ["x86_64-pc-linux-gnu"] + cros_targets 827 rust_ebuild = "RUSTC_TARGET_TRIPLES=(" + "\n\t".join(all_triples) + ")" 828 with mock.patch("rust_uprev.find_ebuild_path") as mock_find_ebuild_path: 829 mock_path = mock.Mock() 830 mock_path.read_text.return_value = rust_ebuild 831 mock_find_ebuild_path.return_value = mock_path 832 rust_uprev.build_cross_compiler(rust_uprev.RustVersion(7, 3, 31)) 833 834 mock_run_in_chroot.assert_called_once_with( 835 ["sudo", "emerge", "-j", "-G"] 836 + [f"cross-{x}/gcc" for x in cros_targets + ["arm-none-eabi"]] 837 ) 838 839 840if __name__ == "__main__": 841 unittest.main() 842