1#!/usr/bin/env python3 2# Copyright 2019 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"""Unit tests for updating LLVM hashes.""" 7 8import os 9from pathlib import Path 10import subprocess 11import sys 12import tempfile 13from typing import Optional, Union 14import unittest 15from unittest import mock 16 17import chroot 18import failure_modes 19import get_llvm_hash 20import patch_utils 21import test_helpers 22import update_chromeos_llvm_hash 23 24 25# These are unittests; protected access is OK to a point. 26# pylint: disable=protected-access 27 28 29class UpdateLLVMHashTest(unittest.TestCase): 30 """Test class for updating LLVM hashes of packages.""" 31 32 @staticmethod 33 def _make_patch_entry( 34 relpath: Union[str, Path], workdir: Optional[Path] = None 35 ) -> patch_utils.PatchEntry: 36 if workdir is None: 37 workdir = Path("llvm_tools/update_chromeos_llvm_hash_unittest.py") 38 return patch_utils.PatchEntry( 39 workdir=workdir, 40 rel_patch_path=str(relpath), 41 metadata={}, 42 platforms=["chromiumos"], 43 version_range={"from": None, "until": None}, 44 verify_workdir=False, 45 ) 46 47 @mock.patch.object(os.path, "realpath") 48 def testDefaultCrosRootFromCrOSCheckout(self, mock_llvm_tools): 49 llvm_tools_path = ( 50 "/path/to/cros/src/third_party/toolchain-utils/llvm_tools" 51 ) 52 mock_llvm_tools.return_value = llvm_tools_path 53 self.assertEqual( 54 update_chromeos_llvm_hash.defaultCrosRoot(), Path("/path/to/cros") 55 ) 56 57 @mock.patch.object(os.path, "realpath") 58 def testDefaultCrosRootFromOutsideCrOSCheckout(self, mock_llvm_tools): 59 mock_llvm_tools.return_value = "~/toolchain-utils/llvm_tools" 60 self.assertEqual( 61 update_chromeos_llvm_hash.defaultCrosRoot(), 62 Path.home() / "chromiumos", 63 ) 64 65 # Simulate behavior of 'os.path.isfile()' when the ebuild path to a package 66 # does not exist. 67 @mock.patch.object(os.path, "isfile", return_value=False) 68 def testFailedToUpdateLLVMHashForInvalidEbuildPath(self, mock_isfile): 69 ebuild_path = Path("/some/path/to/package.ebuild") 70 llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current 71 git_hash = "a123testhash1" 72 svn_version = 1000 73 74 # Verify the exception is raised when the ebuild path does not exist. 75 with self.assertRaises(ValueError) as err: 76 update_chromeos_llvm_hash.UpdateEbuildLLVMHash( 77 ebuild_path, llvm_variant, git_hash, svn_version 78 ) 79 80 self.assertEqual( 81 str(err.exception), 82 "Invalid ebuild path provided: %s" % ebuild_path, 83 ) 84 85 mock_isfile.assert_called_once() 86 87 # Simulate 'os.path.isfile' behavior on a valid ebuild path. 88 @mock.patch.object(os.path, "isfile", return_value=True) 89 def testFailedToUpdateLLVMHash(self, mock_isfile): 90 # Create a temporary file to simulate an ebuild file of a package. 91 with test_helpers.CreateTemporaryJsonFile() as ebuild_file: 92 with open(ebuild_file, "w", encoding="utf-8") as f: 93 f.write( 94 "\n".join( 95 [ 96 "First line in the ebuild", 97 "Second line in the ebuild", 98 "Last line in the ebuild", 99 ] 100 ) 101 ) 102 103 llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current 104 git_hash = "a123testhash1" 105 svn_version = 1000 106 107 # Verify the exception is raised when the ebuild file does not have 108 # 'LLVM_HASH'. 109 with self.assertRaises(ValueError) as err: 110 update_chromeos_llvm_hash.UpdateEbuildLLVMHash( 111 Path(ebuild_file), llvm_variant, git_hash, svn_version 112 ) 113 114 self.assertEqual(str(err.exception), "Failed to update LLVM_HASH") 115 116 llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next 117 118 self.assertEqual(mock_isfile.call_count, 2) 119 120 # Simulate 'os.path.isfile' behavior on a valid ebuild path. 121 @mock.patch.object(os.path, "isfile", return_value=True) 122 def testFailedToUpdateLLVMNextHash(self, mock_isfile): 123 # Create a temporary file to simulate an ebuild file of a package. 124 with test_helpers.CreateTemporaryJsonFile() as ebuild_file: 125 with open(ebuild_file, "w", encoding="utf-8") as f: 126 f.write( 127 "\n".join( 128 [ 129 "First line in the ebuild", 130 "Second line in the ebuild", 131 "Last line in the ebuild", 132 ] 133 ) 134 ) 135 136 llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next 137 git_hash = "a123testhash1" 138 svn_version = 1000 139 140 # Verify the exception is raised when the ebuild file does not have 141 # 'LLVM_NEXT_HASH'. 142 with self.assertRaises(ValueError) as err: 143 update_chromeos_llvm_hash.UpdateEbuildLLVMHash( 144 Path(ebuild_file), llvm_variant, git_hash, svn_version 145 ) 146 147 self.assertEqual( 148 str(err.exception), "Failed to update LLVM_NEXT_HASH" 149 ) 150 151 self.assertEqual(mock_isfile.call_count, 2) 152 153 @mock.patch.object(os.path, "isfile", return_value=True) 154 @mock.patch.object(subprocess, "check_output", return_value=None) 155 def testSuccessfullyStageTheEbuildForCommitForLLVMHashUpdate( 156 self, mock_stage_commit_command, mock_isfile 157 ): 158 # Create a temporary file to simulate an ebuild file of a package. 159 with test_helpers.CreateTemporaryJsonFile() as ebuild_file: 160 # Updates LLVM_HASH to 'git_hash' and revision to 161 # 'svn_version'. 162 llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current 163 git_hash = "a123testhash1" 164 svn_version = 1000 165 166 with open(ebuild_file, "w", encoding="utf-8") as f: 167 f.write( 168 "\n".join( 169 [ 170 "First line in the ebuild", 171 "Second line in the ebuild", 172 'LLVM_HASH="a12b34c56d78e90" # r500', 173 "Last line in the ebuild", 174 ] 175 ) 176 ) 177 178 update_chromeos_llvm_hash.UpdateEbuildLLVMHash( 179 Path(ebuild_file), llvm_variant, git_hash, svn_version 180 ) 181 182 expected_file_contents = [ 183 "First line in the ebuild\n", 184 "Second line in the ebuild\n", 185 'LLVM_HASH="a123testhash1" # r1000\n', 186 "Last line in the ebuild", 187 ] 188 189 # Verify the new file contents of the ebuild file match the expected 190 # file contents. 191 with open(ebuild_file, encoding="utf-8") as new_file: 192 self.assertListEqual( 193 new_file.readlines(), expected_file_contents 194 ) 195 196 self.assertEqual(mock_isfile.call_count, 2) 197 198 mock_stage_commit_command.assert_called_once() 199 200 @mock.patch.object(os.path, "isfile", return_value=True) 201 @mock.patch.object(subprocess, "check_output", return_value=None) 202 def testSuccessfullyStageTheEbuildForCommitForLLVMNextHashUpdate( 203 self, mock_stage_commit_command, mock_isfile 204 ): 205 # Create a temporary file to simulate an ebuild file of a package. 206 with test_helpers.CreateTemporaryJsonFile() as ebuild_file: 207 # Updates LLVM_NEXT_HASH to 'git_hash' and revision to 208 # 'svn_version'. 209 llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next 210 git_hash = "a123testhash1" 211 svn_version = 1000 212 213 with open(ebuild_file, "w", encoding="utf-8") as f: 214 f.write( 215 "\n".join( 216 [ 217 "First line in the ebuild", 218 "Second line in the ebuild", 219 'LLVM_NEXT_HASH="a12b34c56d78e90" # r500', 220 "Last line in the ebuild", 221 ] 222 ) 223 ) 224 225 update_chromeos_llvm_hash.UpdateEbuildLLVMHash( 226 Path(ebuild_file), llvm_variant, git_hash, svn_version 227 ) 228 229 expected_file_contents = [ 230 "First line in the ebuild\n", 231 "Second line in the ebuild\n", 232 'LLVM_NEXT_HASH="a123testhash1" # r1000\n', 233 "Last line in the ebuild", 234 ] 235 236 # Verify the new file contents of the ebuild file match the expected 237 # file contents. 238 with open(ebuild_file, encoding="utf-8") as new_file: 239 self.assertListEqual( 240 new_file.readlines(), expected_file_contents 241 ) 242 243 self.assertEqual(mock_isfile.call_count, 2) 244 245 mock_stage_commit_command.assert_called_once() 246 247 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 248 @mock.patch.object(os.path, "islink", return_value=False) 249 def testFailedToUprevEbuildToVersionForInvalidSymlink( 250 self, mock_islink, mock_llvm_version 251 ): 252 symlink_path = "/path/to/chromeos/package/package.ebuild" 253 svn_version = 1000 254 git_hash = "badf00d" 255 mock_llvm_version.return_value = "1234" 256 257 # Verify the exception is raised when a invalid symbolic link is 258 # passed in. 259 with self.assertRaises(ValueError) as err: 260 update_chromeos_llvm_hash.UprevEbuildToVersion( 261 symlink_path, svn_version, git_hash 262 ) 263 264 self.assertEqual( 265 str(err.exception), "Invalid symlink provided: %s" % symlink_path 266 ) 267 268 mock_islink.assert_called_once() 269 mock_llvm_version.assert_not_called() 270 271 @mock.patch.object(os.path, "islink", return_value=False) 272 def testFailedToUprevEbuildSymlinkForInvalidSymlink(self, mock_islink): 273 symlink_path = "/path/to/chromeos/package/package.ebuild" 274 275 # Verify the exception is raised when a invalid symbolic link is 276 # passed in. 277 with self.assertRaises(ValueError) as err: 278 update_chromeos_llvm_hash.UprevEbuildSymlink(symlink_path) 279 280 self.assertEqual( 281 str(err.exception), "Invalid symlink provided: %s" % symlink_path 282 ) 283 284 mock_islink.assert_called_once() 285 286 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 287 # Simulate 'os.path.islink' when a symbolic link is passed in. 288 @mock.patch.object(os.path, "islink", return_value=True) 289 # Simulate 'os.path.realpath' when a symbolic link is passed in. 290 @mock.patch.object(os.path, "realpath", return_value=True) 291 def testFailedToUprevEbuildToVersion( 292 self, mock_realpath, mock_islink, mock_llvm_version 293 ): 294 symlink_path = "/path/to/chromeos/llvm/llvm_pre123_p.ebuild" 295 mock_realpath.return_value = "/abs/path/to/llvm/llvm_pre123_p.ebuild" 296 git_hash = "badf00d" 297 mock_llvm_version.return_value = "1234" 298 svn_version = 1000 299 300 # Verify the exception is raised when the symlink does not match the 301 # expected pattern 302 with self.assertRaises(ValueError) as err: 303 update_chromeos_llvm_hash.UprevEbuildToVersion( 304 symlink_path, svn_version, git_hash 305 ) 306 307 self.assertEqual(str(err.exception), "Failed to uprev the ebuild.") 308 309 mock_llvm_version.assert_called_once_with(git_hash) 310 mock_islink.assert_called_once_with(symlink_path) 311 312 # Simulate 'os.path.islink' when a symbolic link is passed in. 313 @mock.patch.object(os.path, "islink", return_value=True) 314 def testFailedToUprevEbuildSymlink(self, mock_islink): 315 symlink_path = "/path/to/chromeos/llvm/llvm_pre123_p.ebuild" 316 317 # Verify the exception is raised when the symlink does not match the 318 # expected pattern 319 with self.assertRaises(ValueError) as err: 320 update_chromeos_llvm_hash.UprevEbuildSymlink(symlink_path) 321 322 self.assertEqual(str(err.exception), "Failed to uprev the symlink.") 323 324 mock_islink.assert_called_once_with(symlink_path) 325 326 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 327 @mock.patch.object(os.path, "islink", return_value=True) 328 @mock.patch.object(os.path, "realpath") 329 @mock.patch.object(subprocess, "check_output", return_value=None) 330 def testSuccessfullyUprevEbuildToVersionLLVM( 331 self, 332 mock_command_output, 333 mock_realpath, 334 mock_islink, 335 mock_llvm_version, 336 ): 337 symlink = "/path/to/llvm/llvm-12.0_pre3_p2-r10.ebuild" 338 ebuild = "/abs/path/to/llvm/llvm-12.0_pre3_p2.ebuild" 339 mock_realpath.return_value = ebuild 340 git_hash = "badf00d" 341 mock_llvm_version.return_value = "1234" 342 svn_version = 1000 343 344 update_chromeos_llvm_hash.UprevEbuildToVersion( 345 symlink, svn_version, git_hash 346 ) 347 348 mock_llvm_version.assert_called_once_with(git_hash) 349 350 mock_islink.assert_called() 351 352 mock_realpath.assert_called_once_with(symlink) 353 354 mock_command_output.assert_called() 355 356 # Verify commands 357 symlink_dir = os.path.dirname(symlink) 358 new_ebuild = "/abs/path/to/llvm/llvm-1234.0_pre1000.ebuild" 359 new_symlink = new_ebuild[: -len(".ebuild")] + "-r1.ebuild" 360 361 expected_cmd = ["git", "-C", symlink_dir, "mv", ebuild, new_ebuild] 362 self.assertEqual( 363 mock_command_output.call_args_list[0], mock.call(expected_cmd) 364 ) 365 366 expected_cmd = ["ln", "-s", "-r", new_ebuild, new_symlink] 367 self.assertEqual( 368 mock_command_output.call_args_list[1], mock.call(expected_cmd) 369 ) 370 371 expected_cmd = ["git", "-C", symlink_dir, "add", new_symlink] 372 self.assertEqual( 373 mock_command_output.call_args_list[2], mock.call(expected_cmd) 374 ) 375 376 expected_cmd = ["git", "-C", symlink_dir, "rm", symlink] 377 self.assertEqual( 378 mock_command_output.call_args_list[3], mock.call(expected_cmd) 379 ) 380 381 @mock.patch.object( 382 chroot, 383 "GetChrootEbuildPaths", 384 return_value=["/chroot/path/test.ebuild"], 385 ) 386 @mock.patch.object(subprocess, "check_output", return_value="") 387 def testManifestUpdate(self, mock_subprocess, mock_ebuild_paths): 388 manifest_packages = ["sys-devel/llvm"] 389 chromeos_path = "/path/to/chromeos" 390 update_chromeos_llvm_hash.UpdatePortageManifests( 391 manifest_packages, Path(chromeos_path) 392 ) 393 394 args = mock_subprocess.call_args_list[0] 395 manifest_cmd = ( 396 [ 397 "cros_sdk", 398 "--chroot=chroot", 399 "--out-dir=out", 400 "--", 401 "ebuild", 402 "/chroot/path/test.ebuild", 403 "manifest", 404 ], 405 ) 406 self.assertEqual(args[0], manifest_cmd) 407 408 args = mock_subprocess.call_args_list[1] 409 git_add_cmd = ( 410 [ 411 "cros_sdk", 412 "--chroot=chroot", 413 "--out-dir=out", 414 "--", 415 "git", 416 "-C", 417 "/chroot/path", 418 "add", 419 "Manifest", 420 ], 421 ) 422 self.assertEqual(args[0], git_add_cmd) 423 mock_ebuild_paths.assert_called_once() 424 425 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 426 @mock.patch.object(os.path, "islink", return_value=True) 427 @mock.patch.object(os.path, "realpath") 428 @mock.patch.object(subprocess, "check_output", return_value=None) 429 def testSuccessfullyUprevEbuildToVersionNonLLVM( 430 self, mock_command_output, mock_realpath, mock_islink, mock_llvm_version 431 ): 432 symlink = ( 433 "/abs/path/to/compiler-rt/compiler-rt-12.0_pre314159265-r4.ebuild" 434 ) 435 ebuild = "/abs/path/to/compiler-rt/compiler-rt-12.0_pre314159265.ebuild" 436 mock_realpath.return_value = ebuild 437 mock_llvm_version.return_value = "1234" 438 svn_version = 1000 439 git_hash = "5678" 440 441 update_chromeos_llvm_hash.UprevEbuildToVersion( 442 symlink, svn_version, git_hash 443 ) 444 445 mock_islink.assert_called() 446 447 mock_realpath.assert_called_once_with(symlink) 448 449 mock_llvm_version.assert_called_once_with(git_hash) 450 451 mock_command_output.assert_called() 452 453 # Verify commands 454 symlink_dir = os.path.dirname(symlink) 455 new_ebuild = ( 456 "/abs/path/to/compiler-rt/compiler-rt-1234.0_pre1000.ebuild" 457 ) 458 new_symlink = new_ebuild[: -len(".ebuild")] + "-r1.ebuild" 459 460 expected_cmd = ["git", "-C", symlink_dir, "mv", ebuild, new_ebuild] 461 self.assertEqual( 462 mock_command_output.call_args_list[0], mock.call(expected_cmd) 463 ) 464 465 expected_cmd = ["ln", "-s", "-r", new_ebuild, new_symlink] 466 self.assertEqual( 467 mock_command_output.call_args_list[1], mock.call(expected_cmd) 468 ) 469 470 expected_cmd = ["git", "-C", symlink_dir, "add", new_symlink] 471 self.assertEqual( 472 mock_command_output.call_args_list[2], mock.call(expected_cmd) 473 ) 474 475 expected_cmd = ["git", "-C", symlink_dir, "rm", symlink] 476 self.assertEqual( 477 mock_command_output.call_args_list[3], mock.call(expected_cmd) 478 ) 479 480 @mock.patch.object(os.path, "islink", return_value=True) 481 @mock.patch.object(subprocess, "check_output", return_value=None) 482 def testSuccessfullyUprevEbuildSymlink( 483 self, mock_command_output, mock_islink 484 ): 485 symlink_to_uprev = "/symlink/to/package-r1.ebuild" 486 487 update_chromeos_llvm_hash.UprevEbuildSymlink(symlink_to_uprev) 488 489 mock_islink.assert_called_once_with(symlink_to_uprev) 490 491 mock_command_output.assert_called_once() 492 493 @mock.patch.object(subprocess, "check_output", return_value=None) 494 def testSuccessfullyRemovedPatchesFromFilesDir(self, mock_run_cmd): 495 patches_to_remove_list = [ 496 "/abs/path/to/filesdir/cherry/fix_output.patch", 497 "/abs/path/to/filesdir/display_results.patch", 498 ] 499 500 update_chromeos_llvm_hash.RemovePatchesFromFilesDir( 501 patches_to_remove_list 502 ) 503 504 self.assertEqual(mock_run_cmd.call_count, 2) 505 506 @mock.patch.object(os.path, "isfile", return_value=False) 507 def testInvalidPatchMetadataFileStagedForCommit(self, mock_isfile): 508 patch_metadata_path = "/abs/path/to/filesdir/PATCHES" 509 510 # Verify the exception is raised when the absolute path to the patch 511 # metadata file does not exist or is not a file. 512 with self.assertRaises(ValueError) as err: 513 update_chromeos_llvm_hash.StagePatchMetadataFileForCommit( 514 patch_metadata_path 515 ) 516 517 self.assertEqual( 518 str(err.exception), 519 "Invalid patch metadata file provided: " "%s" % patch_metadata_path, 520 ) 521 522 mock_isfile.assert_called_once() 523 524 @mock.patch.object(os.path, "isfile", return_value=True) 525 @mock.patch.object(subprocess, "check_output", return_value=None) 526 def testSuccessfullyStagedPatchMetadataFileForCommit(self, mock_run_cmd, _): 527 patch_metadata_path = "/abs/path/to/filesdir/PATCHES.json" 528 529 update_chromeos_llvm_hash.StagePatchMetadataFileForCommit( 530 patch_metadata_path 531 ) 532 533 mock_run_cmd.assert_called_once() 534 535 def testNoPatchResultsForCommit(self): 536 package_1_patch_info = patch_utils.PatchInfo( 537 applied_patches=[self._make_patch_entry("display_results.patch")], 538 failed_patches=[self._make_patch_entry("fixes_output.patch")], 539 non_applicable_patches=[], 540 disabled_patches=[], 541 removed_patches=[], 542 modified_metadata=None, 543 ) 544 545 package_2_patch_info = patch_utils.PatchInfo( 546 applied_patches=[ 547 self._make_patch_entry("redirects_stdout.patch"), 548 self._make_patch_entry("fix_display.patch"), 549 ], 550 failed_patches=[], 551 non_applicable_patches=[], 552 disabled_patches=[], 553 removed_patches=[], 554 modified_metadata=None, 555 ) 556 557 test_package_info_dict = { 558 "test-packages/package1": package_1_patch_info, 559 "test-packages/package2": package_2_patch_info, 560 } 561 562 test_commit_message = ["Updated packages"] 563 564 self.assertListEqual( 565 update_chromeos_llvm_hash.StagePackagesPatchResultsForCommit( 566 test_package_info_dict, test_commit_message 567 ), 568 test_commit_message, 569 ) 570 571 @mock.patch.object( 572 update_chromeos_llvm_hash, "StagePatchMetadataFileForCommit" 573 ) 574 @mock.patch.object(update_chromeos_llvm_hash, "RemovePatchesFromFilesDir") 575 def testAddedPatchResultsForCommit( 576 self, mock_remove_patches, mock_stage_patches_for_commit 577 ): 578 package_1_patch_info = patch_utils.PatchInfo( 579 applied_patches=[], 580 failed_patches=[], 581 non_applicable_patches=[], 582 disabled_patches=["fixes_output.patch"], 583 removed_patches=[], 584 modified_metadata="/abs/path/to/filesdir/PATCHES.json", 585 ) 586 587 package_2_patch_info = patch_utils.PatchInfo( 588 applied_patches=[self._make_patch_entry("fix_display.patch")], 589 failed_patches=[], 590 non_applicable_patches=[], 591 disabled_patches=[], 592 removed_patches=["/abs/path/to/filesdir/redirect_stdout.patch"], 593 modified_metadata="/abs/path/to/filesdir/PATCHES.json", 594 ) 595 596 test_package_info_dict = { 597 "test-packages/package1": package_1_patch_info, 598 "test-packages/package2": package_2_patch_info, 599 } 600 601 test_commit_message = ["Updated packages"] 602 603 expected_commit_messages = [ 604 "Updated packages", 605 "\nFor the package test-packages/package1:", 606 "The patch metadata file PATCHES.json was modified", 607 "The following patches were disabled:", 608 "fixes_output.patch", 609 "\nFor the package test-packages/package2:", 610 "The patch metadata file PATCHES.json was modified", 611 "The following patches were removed:", 612 "redirect_stdout.patch", 613 ] 614 615 self.assertListEqual( 616 update_chromeos_llvm_hash.StagePackagesPatchResultsForCommit( 617 test_package_info_dict, test_commit_message 618 ), 619 expected_commit_messages, 620 ) 621 622 path_to_removed_patch = "/abs/path/to/filesdir/redirect_stdout.patch" 623 624 mock_remove_patches.assert_called_once_with([path_to_removed_patch]) 625 626 self.assertEqual(mock_stage_patches_for_commit.call_count, 2) 627 628 def setup_mock_src_tree(self, src_tree: Path): 629 package_dir = ( 630 src_tree / "src/third_party/chromiumos-overlay/sys-devel/llvm" 631 ) 632 package_dir.mkdir(parents=True) 633 ebuild_path = package_dir / "llvm-00.00_pre0_p0.ebuild" 634 with ebuild_path.open("w", encoding="utf-8") as f: 635 f.writelines( 636 [ 637 'LLVM_HASH="abcdef123456" # r123456', 638 'LLVM_NEXT_HASH="987654321fedcba" # r99453', 639 ] 640 ) 641 symlink_path = package_dir / "llvm-00.00_pre0_p0-r1234.ebuild" 642 symlink_path.symlink_to(ebuild_path) 643 return package_dir, ebuild_path, symlink_path 644 645 def testPortagePackageConstruction(self): 646 with tempfile.TemporaryDirectory( 647 "update_chromeos_llvm_hash.tmp" 648 ) as workdir_str: 649 src_tree = Path(workdir_str) 650 package_dir, ebuild_path, symlink_path = self.setup_mock_src_tree( 651 src_tree 652 ) 653 654 # Test that we're upreving if there's a symlink. 655 def mock_find_package_ebuild(_, package_name): 656 self.assertEqual( 657 package_name, 658 f"{package_dir.parent.name}/{package_dir.name}", 659 ) 660 return symlink_path 661 662 with mock.patch( 663 "update_chromeos_llvm_hash.PortagePackage.find_package_ebuild", 664 mock_find_package_ebuild, 665 ): 666 pkg = update_chromeos_llvm_hash.PortagePackage( 667 src_tree, "sys-devel/llvm" 668 ) 669 self.assertEqual(pkg.uprev_target, symlink_path.absolute()) 670 self.assertEqual(pkg.ebuild_path, ebuild_path.absolute()) 671 self.assertEqual(pkg.live_ebuild(), None) 672 673 # Make sure if the live ebuild is there, we find it. 674 live_ebuild_path = package_dir / "llvm-9999.ebuild" 675 live_ebuild_path.touch() 676 677 pkg = update_chromeos_llvm_hash.PortagePackage( 678 src_tree, "sys-devel/llvm" 679 ) 680 self.assertEqual(pkg.live_ebuild(), live_ebuild_path) 681 682 @mock.patch("subprocess.run") 683 @mock.patch("subprocess.check_output") 684 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 685 def testUpdatePackages( 686 self, mock_llvm_major_version, _mock_check_output, _mock_run 687 ): 688 mock_llvm_major_version.return_value = "17" 689 with tempfile.TemporaryDirectory( 690 "update_chromeos_llvm_hash.tmp" 691 ) as workdir_str: 692 src_tree = Path(workdir_str) 693 _package_dir, _ebuild_path, symlink_path = self.setup_mock_src_tree( 694 src_tree 695 ) 696 697 def mock_find_package_ebuild(*_): 698 return symlink_path 699 700 with mock.patch( 701 "update_chromeos_llvm_hash.PortagePackage.find_package_ebuild", 702 mock_find_package_ebuild, 703 ): 704 pkg = update_chromeos_llvm_hash.PortagePackage( 705 src_tree, "sys-devel/llvm" 706 ) 707 pkg.update( 708 update_chromeos_llvm_hash.LLVMVariant.current, 709 "beef3333", 710 3333, 711 ) 712 713 @mock.patch.object(chroot, "VerifyChromeOSRoot") 714 @mock.patch.object(chroot, "VerifyOutsideChroot") 715 @mock.patch.object(get_llvm_hash, "GetLLVMHashAndVersionFromSVNOption") 716 @mock.patch.object(update_chromeos_llvm_hash, "UpdatePackages") 717 def testMainDefaults( 718 self, 719 mock_update_packages, 720 mock_gethash, 721 mock_outside_chroot, 722 mock_chromeos_root, 723 ): 724 git_hash = "1234abcd" 725 svn_version = 5678 726 mock_gethash.return_value = (git_hash, svn_version) 727 argv = [ 728 "./update_chromeos_llvm_hash_unittest.py", 729 "--no_repo_manifest", 730 "--llvm_version", 731 "google3", 732 ] 733 734 with mock.patch.object(sys, "argv", argv) as mock.argv: 735 update_chromeos_llvm_hash.main() 736 737 expected_packages = set(update_chromeos_llvm_hash.DEFAULT_PACKAGES) 738 expected_manifest_packages = set( 739 update_chromeos_llvm_hash.DEFAULT_MANIFEST_PACKAGES, 740 ) 741 expected_llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current 742 expected_chroot = update_chromeos_llvm_hash.defaultCrosRoot() 743 mock_update_packages.assert_called_once_with( 744 packages=expected_packages, 745 manifest_packages=expected_manifest_packages, 746 llvm_variant=expected_llvm_variant, 747 git_hash=git_hash, 748 svn_version=svn_version, 749 chroot_opts=update_chromeos_llvm_hash.ChrootOpts(expected_chroot), 750 mode=failure_modes.FailureModes.FAIL, 751 git_hash_source="google3", 752 extra_commit_msg_lines=None, 753 delete_branch=True, 754 upload_changes=True, 755 ) 756 mock_outside_chroot.assert_called() 757 mock_chromeos_root.assert_called() 758 759 @mock.patch.object(chroot, "VerifyChromeOSRoot") 760 @mock.patch.object(chroot, "VerifyOutsideChroot") 761 @mock.patch.object(get_llvm_hash, "GetLLVMHashAndVersionFromSVNOption") 762 @mock.patch.object(update_chromeos_llvm_hash, "UpdatePackages") 763 def testMainLlvmNext( 764 self, 765 mock_update_packages, 766 mock_gethash, 767 mock_outside_chroot, 768 mock_chromeos_root, 769 ): 770 git_hash = "1234abcd" 771 svn_version = 5678 772 mock_gethash.return_value = (git_hash, svn_version) 773 argv = [ 774 "./update_chromeos_llvm_hash_unittest.py", 775 "--llvm_version", 776 "google3", 777 "--is_llvm_next", 778 ] 779 780 with mock.patch.object(sys, "argv", argv) as mock.argv: 781 update_chromeos_llvm_hash.main() 782 783 expected_packages = set(update_chromeos_llvm_hash.DEFAULT_PACKAGES) 784 expected_llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next 785 expected_chroot = update_chromeos_llvm_hash.defaultCrosRoot() 786 # llvm-next upgrade does not update manifest by default. 787 mock_update_packages.assert_called_once_with( 788 packages=expected_packages, 789 manifest_packages=set(), 790 llvm_variant=expected_llvm_variant, 791 git_hash=git_hash, 792 svn_version=svn_version, 793 chroot_opts=update_chromeos_llvm_hash.ChrootOpts(expected_chroot), 794 mode=failure_modes.FailureModes.FAIL, 795 git_hash_source="google3", 796 extra_commit_msg_lines=None, 797 delete_branch=True, 798 upload_changes=True, 799 ) 800 mock_outside_chroot.assert_called() 801 mock_chromeos_root.assert_called() 802 803 @mock.patch.object(chroot, "VerifyChromeOSRoot") 804 @mock.patch.object(chroot, "VerifyOutsideChroot") 805 @mock.patch.object(get_llvm_hash, "GetLLVMHashAndVersionFromSVNOption") 806 @mock.patch.object(update_chromeos_llvm_hash, "UpdatePackages") 807 def testMainAllArgs( 808 self, 809 mock_update_packages, 810 mock_gethash, 811 mock_outside_chroot, 812 mock_chromeos_root, 813 ): 814 packages_to_update = "test-packages/package1,test-libs/lib1" 815 manifest_packages = "test-libs/lib1,test-libs/lib2" 816 failure_mode = failure_modes.FailureModes.DISABLE_PATCHES 817 chromeos_path = Path("/some/path/to/chromeos") 818 llvm_ver = 435698 819 git_hash = "1234abcd" 820 svn_version = 5678 821 mock_gethash.return_value = (git_hash, svn_version) 822 823 argv = [ 824 "./update_chromeos_llvm_hash_unittest.py", 825 "--llvm_version", 826 str(llvm_ver), 827 "--is_llvm_next", 828 "--chromeos_path", 829 str(chromeos_path), 830 "--update_packages", 831 packages_to_update, 832 "--manifest_packages", 833 manifest_packages, 834 "--failure_mode", 835 failure_mode.value, 836 "--patch_metadata_file", 837 "META.json", 838 "--no_repo_manifest", 839 ] 840 841 with mock.patch.object(sys, "argv", argv) as mock.argv: 842 update_chromeos_llvm_hash.main() 843 844 expected_packages = {"test-packages/package1", "test-libs/lib1"} 845 expected_manifest_packages = {"test-libs/lib1", "test-libs/lib2"} 846 expected_llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next 847 mock_update_packages.assert_called_once_with( 848 packages=expected_packages, 849 manifest_packages=expected_manifest_packages, 850 llvm_variant=expected_llvm_variant, 851 git_hash=git_hash, 852 svn_version=svn_version, 853 chroot_opts=update_chromeos_llvm_hash.ChrootOpts(chromeos_path), 854 mode=failure_mode, 855 git_hash_source=llvm_ver, 856 extra_commit_msg_lines=None, 857 delete_branch=True, 858 upload_changes=True, 859 ) 860 mock_outside_chroot.assert_called() 861 mock_chromeos_root.assert_called() 862 863 @mock.patch.object(subprocess, "check_output", return_value=None) 864 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 865 def testEnsurePackageMaskContainsExisting( 866 self, mock_llvm_version, mock_git_add 867 ): 868 chromeos_path = "absolute/path/to/chromeos" 869 git_hash = "badf00d" 870 mock_llvm_version.return_value = "1234" 871 with mock.patch( 872 "update_chromeos_llvm_hash.open", 873 mock.mock_open(read_data="\n=sys-devel/llvm-1234.0_pre*\n"), 874 create=True, 875 ) as mock_file: 876 update_chromeos_llvm_hash.EnsurePackageMaskContains( 877 chromeos_path, git_hash 878 ) 879 handle = mock_file() 880 handle.write.assert_not_called() 881 mock_llvm_version.assert_called_once_with(git_hash) 882 883 overlay_dir = ( 884 "absolute/path/to/chromeos/src/third_party/chromiumos-overlay" 885 ) 886 mask_path = overlay_dir + "/profiles/targets/chromeos/package.mask" 887 mock_git_add.assert_called_once_with( 888 ["git", "-C", overlay_dir, "add", mask_path] 889 ) 890 891 @mock.patch.object(subprocess, "check_output", return_value=None) 892 @mock.patch.object(get_llvm_hash, "GetLLVMMajorVersion") 893 def testEnsurePackageMaskContainsNotExisting( 894 self, mock_llvm_version, mock_git_add 895 ): 896 chromeos_path = "absolute/path/to/chromeos" 897 git_hash = "badf00d" 898 mock_llvm_version.return_value = "1234" 899 with mock.patch( 900 "update_chromeos_llvm_hash.open", 901 mock.mock_open(read_data="nothing relevant"), 902 create=True, 903 ) as mock_file: 904 update_chromeos_llvm_hash.EnsurePackageMaskContains( 905 chromeos_path, git_hash 906 ) 907 handle = mock_file() 908 handle.write.assert_called_once_with( 909 "=sys-devel/llvm-1234.0_pre*\n" 910 ) 911 mock_llvm_version.assert_called_once_with(git_hash) 912 913 overlay_dir = ( 914 "absolute/path/to/chromeos/src/third_party/chromiumos-overlay" 915 ) 916 mask_path = overlay_dir + "/profiles/targets/chromeos/package.mask" 917 mock_git_add.assert_called_once_with( 918 ["git", "-C", overlay_dir, "add", mask_path] 919 ) 920 921 922if __name__ == "__main__": 923 unittest.main() 924