1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 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"""Unit tests for updating the LLVM next hash.""" 8 9from __future__ import print_function 10 11from collections import namedtuple 12import os 13import subprocess 14import unittest 15import unittest.mock as mock 16 17from failure_modes import FailureModes 18from test_helpers import CreateTemporaryJsonFile 19import llvm_patch_management 20import update_chromeos_llvm_next_hash 21 22# These are unittests; protected access is OK to a point. 23# pylint: disable=protected-access 24 25 26class UpdateLLVMNextHashTest(unittest.TestCase): 27 """Test class for updating 'LLVM_NEXT_HASH' of packages.""" 28 29 @mock.patch.object(update_chromeos_llvm_next_hash, 'ChrootRunCommand') 30 def testSucceedsToGetChrootPathForPackage(self, mock_chroot_command): 31 package_chroot_path = '/chroot/path/to/package.ebuild' 32 33 # Emulate ChrootRunCommandWOutput behavior when a chroot path is found for 34 # a valid package. 35 mock_chroot_command.return_value = package_chroot_path 36 37 chroot_path = '/test/chroot/path' 38 package_list = ['new-test/package'] 39 40 self.assertEqual( 41 update_chromeos_llvm_next_hash.GetChrootBuildPaths( 42 chroot_path, package_list), [package_chroot_path]) 43 44 mock_chroot_command.assert_called_once() 45 46 def testFailedToConvertChrootPathWithInvalidPrefixToSymlinkPath(self): 47 chroot_path = '/path/to/chroot' 48 chroot_file_path = '/src/package.ebuild' 49 50 # Verify the exception is raised when a symlink does not have the prefix 51 # '/mnt/host/source/'. 52 with self.assertRaises(ValueError) as err: 53 update_chromeos_llvm_next_hash._ConvertChrootPathsToSymLinkPaths( 54 chroot_path, [chroot_file_path]) 55 56 self.assertEqual( 57 str(err.exception), 'Invalid prefix for the chroot path: ' 58 '%s' % chroot_file_path) 59 60 def testSucceedsToConvertChrootPathToSymlinkPath(self): 61 chroot_path = '/path/to/chroot' 62 chroot_file_paths = ['/mnt/host/source/src/package.ebuild'] 63 64 expected_symlink_path = '/path/to/chroot/src/package.ebuild' 65 66 self.assertEqual( 67 update_chromeos_llvm_next_hash._ConvertChrootPathsToSymLinkPaths( 68 chroot_path, chroot_file_paths), [expected_symlink_path]) 69 70 # Simulate 'os.path.islink' when a path is not a symbolic link. 71 @mock.patch.object(os.path, 'islink', return_value=False) 72 def testFailedToGetEbuildPathFromInvalidSymlink(self, mock_islink): 73 symlink_path = '/symlink/path/src/to/package-r1.ebuild' 74 75 # Verify the exception is raised when the argument is not a symbolic link. 76 with self.assertRaises(ValueError) as err: 77 update_chromeos_llvm_next_hash.GetEbuildPathsFromSymLinkPaths( 78 [symlink_path]) 79 80 self.assertEqual( 81 str(err.exception), 'Invalid symlink provided: %s' % symlink_path) 82 83 mock_islink.assert_called_once_with(symlink_path) 84 85 # Simulate 'os.path.islink' when a path is a symbolic link. 86 @mock.patch.object(os.path, 'islink', return_value=True) 87 @mock.patch.object(os.path, 'realpath') 88 def testSucceedsToGetEbuildPathFromValidSymlink(self, mock_realpath, 89 mock_islink): 90 91 symlink_path = '/path/to/chroot/src/package-r1.ebuild' 92 93 abs_path_to_package = '/abs/path/to/src/package.ebuild' 94 95 # Simulate 'os.path.realpath' when a valid path is passed in. 96 mock_realpath.return_value = abs_path_to_package 97 98 expected_resolved_paths = {symlink_path: abs_path_to_package} 99 100 self.assertEqual( 101 update_chromeos_llvm_next_hash.GetEbuildPathsFromSymLinkPaths( 102 [symlink_path]), expected_resolved_paths) 103 104 mock_realpath.assert_called_once_with(symlink_path) 105 106 mock_islink.assert_called_once_with(symlink_path) 107 108 # Simulate behavior of 'os.path.isfile()' when the ebuild path to a package 109 # does not exist. 110 @mock.patch.object(os.path, 'isfile', return_value=False) 111 def testFailedToUpdateLLVMNextHashForInvalidEbuildPath(self, mock_isfile): 112 ebuild_path = '/some/path/to/package.ebuild' 113 114 llvm_hash = 'a123testhash1' 115 llvm_revision = 1000 116 117 # Verify the exception is raised when the ebuild path does not exist. 118 with self.assertRaises(ValueError) as err: 119 update_chromeos_llvm_next_hash.UpdateBuildLLVMNextHash( 120 ebuild_path, llvm_hash, llvm_revision) 121 122 self.assertEqual( 123 str(err.exception), 'Invalid ebuild path provided: %s' % ebuild_path) 124 125 mock_isfile.assert_called_once() 126 127 # Simulate 'os.path.isfile' behavior on a valid ebuild path. 128 @mock.patch.object(os.path, 'isfile', return_value=True) 129 def testFailedToUpdateLLVMNextHash(self, mock_isfile): 130 # Create a temporary file to simulate an ebuild file of a package. 131 with CreateTemporaryJsonFile() as ebuild_file: 132 with open(ebuild_file, 'w') as f: 133 f.write('\n'.join([ 134 'First line in the ebuild', 'Second line in the ebuild', 135 'Last line in the ebuild' 136 ])) 137 138 llvm_hash = 'a123testhash1' 139 llvm_revision = 1000 140 141 # Verify the exception is raised when the ebuild file does not have 142 # 'LLVM_NEXT_HASH'. 143 with self.assertRaises(ValueError) as err: 144 update_chromeos_llvm_next_hash.UpdateBuildLLVMNextHash( 145 ebuild_file, llvm_hash, llvm_revision) 146 147 self.assertEqual(str(err.exception), 'Failed to update the LLVM hash.') 148 149 self.assertEqual(mock_isfile.call_count, 2) 150 151 # Simulate 'os.path.isfile' behavior on a valid ebuild path. 152 @mock.patch.object(os.path, 'isfile', return_value=True) 153 # Simulate 'ExecCommandAndCaptureOutput()' when successfully staged the 154 # ebuild file for commit. 155 @mock.patch.object( 156 update_chromeos_llvm_next_hash, 157 'ExecCommandAndCaptureOutput', 158 return_value=None) 159 def testSuccessfullyStageTheEbuildForCommitForLLVMNextHashUpdate( 160 self, mock_stage_commit_command, mock_isfile): 161 162 # Create a temporary file to simulate an ebuild file of a package. 163 with CreateTemporaryJsonFile() as ebuild_file: 164 with open(ebuild_file, 'w') as f: 165 f.write('\n'.join([ 166 'First line in the ebuild', 'Second line in the ebuild', 167 'LLVM_NEXT_HASH=\"a12b34c56d78e90\" # r500', 168 'Last line in the ebuild' 169 ])) 170 171 # Updates the ebuild's git hash to 'llvm_hash' and revision to 172 # 'llvm_revision'. 173 llvm_hash = 'a123testhash1' 174 llvm_revision = 1000 175 176 update_chromeos_llvm_next_hash.UpdateBuildLLVMNextHash( 177 ebuild_file, llvm_hash, llvm_revision) 178 179 expected_file_contents = [ 180 'First line in the ebuild\n', 'Second line in the ebuild\n', 181 'LLVM_NEXT_HASH=\"a123testhash1\" # r1000\n', 182 'Last line in the ebuild' 183 ] 184 185 # Verify the new file contents of the ebuild file match the expected file 186 # contents. 187 with open(ebuild_file) as new_file: 188 file_contents_as_a_list = [cur_line for cur_line in new_file] 189 self.assertListEqual(file_contents_as_a_list, expected_file_contents) 190 191 self.assertEqual(mock_isfile.call_count, 2) 192 193 mock_stage_commit_command.assert_called_once() 194 195 # Simulate behavior of 'os.path.islink()' when the argument passed in is not a 196 # symbolic link. 197 @mock.patch.object(os.path, 'islink', return_value=False) 198 def testFailedToUprevEbuildForInvalidSymlink(self, mock_islink): 199 symlink_to_uprev = '/symlink/to/package.ebuild' 200 201 # Verify the exception is raised when a symbolic link is not passed in. 202 with self.assertRaises(ValueError) as err: 203 update_chromeos_llvm_next_hash.UprevEbuild(symlink_to_uprev) 204 205 self.assertEqual( 206 str(err.exception), 'Invalid symlink provided: %s' % symlink_to_uprev) 207 208 mock_islink.assert_called_once() 209 210 # Simulate 'os.path.islink' when a symbolic link is passed in. 211 @mock.patch.object(os.path, 'islink', return_value=True) 212 def testFailedToUprevEbuild(self, mock_islink): 213 symlink_to_uprev = '/symlink/to/package.ebuild' 214 215 # Verify the exception is raised when the symlink does not have a revision 216 # number. 217 with self.assertRaises(ValueError) as err: 218 update_chromeos_llvm_next_hash.UprevEbuild(symlink_to_uprev) 219 220 self.assertEqual(str(err.exception), 'Failed to uprev the ebuild.') 221 222 mock_islink.assert_called_once_with(symlink_to_uprev) 223 224 # Simulate 'os.path.islink' when a valid symbolic link is passed in. 225 @mock.patch.object(os.path, 'islink', return_value=True) 226 # Simulate 'os.path.dirname' when returning a path to the directory of a 227 # valid symbolic link. 228 @mock.patch.object(os.path, 'dirname', return_value='/symlink/to') 229 # Simulate 'RunCommandWOutput' when successfully added the upreved symlink 230 # for commit. 231 @mock.patch.object( 232 update_chromeos_llvm_next_hash, 233 'ExecCommandAndCaptureOutput', 234 return_value=None) 235 def testSuccessfullyUprevEbuild(self, mock_command_output, mock_dirname, 236 mock_islink): 237 238 symlink_to_uprev = '/symlink/to/package-r1.ebuild' 239 240 update_chromeos_llvm_next_hash.UprevEbuild(symlink_to_uprev) 241 242 mock_islink.assert_called_once_with(symlink_to_uprev) 243 244 mock_dirname.assert_called_once_with(symlink_to_uprev) 245 246 mock_command_output.assert_called_once() 247 248 # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a 249 # directory. 250 @mock.patch.object(os.path, 'isdir', return_value=False) 251 def testFailedToCreateRepoForInvalidDirectoryPath(self, mock_isdir): 252 path_to_repo = '/path/to/repo' 253 254 # The name to use for the repo name. 255 llvm_hash = 'a123testhash1' 256 257 # Verify the exception is raised when provided an invalid directory path. 258 with self.assertRaises(ValueError) as err: 259 update_chromeos_llvm_next_hash._CreateRepo(path_to_repo, llvm_hash) 260 261 self.assertEqual( 262 str(err.exception), 263 'Invalid directory path provided: %s' % path_to_repo) 264 265 mock_isdir.assert_called_once() 266 267 # Simulate 'os.path.isdir' when a valid repo path is provided. 268 @mock.patch.object(os.path, 'isdir', return_value=True) 269 # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully reset 270 # changes and created a repo. 271 @mock.patch.object( 272 update_chromeos_llvm_next_hash, 273 'ExecCommandAndCaptureOutput', 274 return_value=None) 275 def testSuccessfullyCreatedRepo(self, mock_command_output, mock_isdir): 276 path_to_repo = '/path/to/repo' 277 278 # The name to use for the repo name. 279 llvm_hash = 'a123testhash1' 280 281 update_chromeos_llvm_next_hash._CreateRepo(path_to_repo, llvm_hash) 282 283 mock_isdir.assert_called_once_with(path_to_repo) 284 285 self.assertEqual(mock_command_output.call_count, 2) 286 287 # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a 288 # directory. 289 @mock.patch.object(os.path, 'isdir', return_value=False) 290 def testFailedToDeleteRepoForInvalidDirectoryPath(self, mock_isdir): 291 path_to_repo = '/some/path/to/repo' 292 293 # The name to use for the repo name. 294 llvm_hash = 'a123testhash2' 295 296 # Verify the exception is raised on an invalid repo path. 297 with self.assertRaises(ValueError) as err: 298 update_chromeos_llvm_next_hash._DeleteRepo(path_to_repo, llvm_hash) 299 300 self.assertEqual( 301 str(err.exception), 302 'Invalid directory path provided: %s' % path_to_repo) 303 304 mock_isdir.assert_called_once() 305 306 # Simulate 'os.path.isdir' on valid directory path. 307 @mock.patch.object(os.path, 'isdir', return_value=True) 308 # Simulate 'ExecCommandAndCaptureOutput()' when successfully checkout to 309 # cros/master, reset changes, and deleted the repo. 310 @mock.patch.object( 311 update_chromeos_llvm_next_hash, 312 'ExecCommandAndCaptureOutput', 313 return_value=None) 314 def testSuccessfullyDeletedRepo(self, mock_command_output, mock_isdir): 315 path_to_repo = '/some/path/to/repo' 316 317 # The name of the repo to be deleted. 318 llvm_hash = 'a123testhash2' 319 320 update_chromeos_llvm_next_hash._DeleteRepo(path_to_repo, llvm_hash) 321 322 mock_isdir.assert_called_once_with(path_to_repo) 323 324 self.assertEqual(mock_command_output.call_count, 3) 325 326 def testFailedToFindChangeListURL(self): 327 repo_upload_contents = 'remote: https://some_url' 328 329 # Verify the exception is raised when failed to find the Gerrit URL when 330 # parsing the 'repo upload' contents. 331 with self.assertRaises(ValueError) as err: 332 update_chromeos_llvm_next_hash.GetGerritRepoUploadContents( 333 repo_upload_contents) 334 335 self.assertEqual(str(err.exception), 'Failed to find change list URL.') 336 337 def testSuccessfullyGetGerritRepoUploadContents(self): 338 repo_upload_contents = ('remote: https://chromium-review.googlesource.com' 339 '/c/chromiumos/overlays/chromiumos-overlay/+/' 340 '193147 Some commit header') 341 342 change_list = update_chromeos_llvm_next_hash.GetGerritRepoUploadContents( 343 repo_upload_contents) 344 345 self.assertEqual( 346 change_list.url, 347 'https://chromium-review.googlesource.com/c/chromiumos/overlays/' 348 'chromiumos-overlay/+/193147') 349 350 self.assertEqual(change_list.cl_number, 193147) 351 352 # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a 353 # directory. 354 @mock.patch.object(os.path, 'isdir', return_value=False) 355 def testFailedToUploadChangesForInvalidPathDirectory(self, mock_isdir): 356 path_to_repo = '/some/path/to/repo' 357 358 # The name of repo to upload for review. 359 llvm_hash = 'a123testhash3' 360 361 # Commit messages to add to the CL. 362 commit_messages = ['-m Test message'] 363 364 # Verify exception is raised when on an invalid repo path. 365 with self.assertRaises(ValueError) as err: 366 update_chromeos_llvm_next_hash.UploadChanges(path_to_repo, llvm_hash, 367 commit_messages) 368 369 self.assertEqual( 370 str(err.exception), 371 'Invalid directory path provided: %s' % path_to_repo) 372 373 mock_isdir.assert_called_once() 374 375 # Simulate 'os.path.isdir' on a valid repo path. 376 @mock.patch.object(os.path, 'isdir', return_value=True) 377 # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully 378 # committed the changes. 379 @mock.patch.object( 380 update_chromeos_llvm_next_hash, 381 'ExecCommandAndCaptureOutput', 382 return_value=None) 383 @mock.patch.object(subprocess, 'Popen') 384 def testFailedToUploadChangesForReview(self, mock_repo_upload, 385 mock_command_output, mock_isdir): 386 387 # Simulate the behavior of 'subprocess.Popen()' when uploading the changes 388 # for review 389 # 390 # `Popen.communicate()` returns a tuple of `stdout` and `stderr`. 391 mock_repo_upload.return_value.communicate.return_value = ( 392 None, 'Branch does not exist.') 393 394 # Exit code of 1 means failed to upload changes for review. 395 mock_repo_upload.return_value.returncode = 1 396 397 path_to_repo = '/some/path/to/repo' 398 399 # The name of repo to upload for review. 400 llvm_hash = 'a123testhash3' 401 402 # Commit messages to add to the CL. 403 commit_messages = ['-m Test message'] 404 405 # Verify exception is raised when failed to upload the changes for review. 406 with self.assertRaises(ValueError) as err: 407 update_chromeos_llvm_next_hash.UploadChanges(path_to_repo, llvm_hash, 408 commit_messages) 409 410 self.assertEqual(str(err.exception), 'Failed to upload changes for review') 411 412 mock_isdir.assert_called_once_with(path_to_repo) 413 414 mock_command_output.assert_called_once() 415 416 mock_repo_upload.assert_called_once() 417 418 # Simulate 'os.path.isdir' when a valid repo path is passed in. 419 @mock.patch.object(os.path, 'isdir', return_value=True) 420 # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully 421 # committed the changes. 422 @mock.patch.object( 423 update_chromeos_llvm_next_hash, 424 'ExecCommandAndCaptureOutput', 425 return_value=None) 426 @mock.patch.object(subprocess, 'Popen') 427 def testSuccessfullyUploadedChangesForReview(self, mock_repo_upload, 428 mock_command_output, mock_isdir): 429 430 # A test CL generated by `repo upload`. 431 repo_upload_contents = ('remote: https://chromium-review.googlesource.' 432 'com/c/chromiumos/overlays/chromiumos-overlay/' 433 '+/193147 Fix stdout') 434 435 # Simulate the behavior of 'subprocess.Popen()' when uploading the changes 436 # for review 437 # 438 # `Popen.communicate()` returns a tuple of `stdout` and `stderr`. 439 mock_repo_upload.return_value.communicate.return_value = ( 440 repo_upload_contents, None) 441 442 # Exit code of 0 means successfully uploaded changes for review. 443 mock_repo_upload.return_value.returncode = 0 444 445 path_to_repo = '/some/path/to/repo' 446 447 # The name of the hash to upload for review. 448 llvm_hash = 'a123testhash3' 449 450 # Commit messages to add to the CL. 451 commit_messages = ['-m Test message'] 452 453 change_list = update_chromeos_llvm_next_hash.UploadChanges( 454 path_to_repo, llvm_hash, commit_messages) 455 456 self.assertEqual( 457 change_list.url, 458 'https://chromium-review.googlesource.com/c/chromiumos/overlays/' 459 'chromiumos-overlay/+/193147') 460 461 self.assertEqual(change_list.cl_number, 193147) 462 463 mock_isdir.assert_called_once_with(path_to_repo) 464 465 mock_command_output.assert_called_once() 466 467 mock_repo_upload.assert_called_once() 468 469 @mock.patch.object(update_chromeos_llvm_next_hash, 'GetChrootBuildPaths') 470 @mock.patch.object(update_chromeos_llvm_next_hash, 471 '_ConvertChrootPathsToSymLinkPaths') 472 def testExceptionRaisedWhenCreatingPathDictionaryFromPackages( 473 self, mock_chroot_paths_to_symlinks, mock_get_chroot_paths): 474 475 chroot_path = '/some/path/to/chroot' 476 477 package_name = 'test-pckg/package' 478 package_chroot_path = '/some/chroot/path/to/package-r1.ebuild' 479 480 # Test function to simulate '_ConvertChrootPathsToSymLinkPaths' when a 481 # symlink does not start with the prefix '/mnt/host/source'. 482 def BadPrefixChrootPath(_chroot_path, _chroot_file_paths): 483 raise ValueError('Invalid prefix for the chroot path: ' 484 '%s' % package_chroot_path) 485 486 # Simulate 'GetChrootBuildPaths' when valid packages are passed in. 487 # 488 # Returns a list of chroot paths. 489 mock_get_chroot_paths.return_value = [package_chroot_path] 490 491 # Use test function to simulate '_ConvertChrootPathsToSymLinkPaths' 492 # behavior. 493 mock_chroot_paths_to_symlinks.side_effect = BadPrefixChrootPath 494 495 # Verify exception is raised when for an invalid prefix in the symlink. 496 with self.assertRaises(ValueError) as err: 497 update_chromeos_llvm_next_hash.CreatePathDictionaryFromPackages( 498 chroot_path, [package_name]) 499 500 self.assertEqual( 501 str(err.exception), 'Invalid prefix for the chroot path: ' 502 '%s' % package_chroot_path) 503 504 mock_get_chroot_paths.assert_called_once_with(chroot_path, [package_name]) 505 506 mock_chroot_paths_to_symlinks.assert_called_once_with( 507 chroot_path, [package_chroot_path]) 508 509 @mock.patch.object(update_chromeos_llvm_next_hash, 'GetChrootBuildPaths') 510 @mock.patch.object(update_chromeos_llvm_next_hash, 511 '_ConvertChrootPathsToSymLinkPaths') 512 @mock.patch.object(update_chromeos_llvm_next_hash, 513 'GetEbuildPathsFromSymLinkPaths') 514 def testSuccessfullyCreatedPathDictionaryFromPackages( 515 self, mock_ebuild_paths_from_symlink_paths, mock_chroot_paths_to_symlinks, 516 mock_get_chroot_paths): 517 518 package_chroot_path = '/mnt/host/source/src/path/to/package-r1.ebuild' 519 520 # Simulate 'GetChrootBuildPaths' when returning a chroot path for a valid 521 # package. 522 # 523 # Returns a list of chroot paths. 524 mock_get_chroot_paths.return_value = [package_chroot_path] 525 526 package_symlink_path = '/some/path/to/chroot/src/path/to/package-r1.ebuild' 527 528 # Simulate '_ConvertChrootPathsToSymLinkPaths' when returning a symlink to 529 # a chroot path that points to a package. 530 # 531 # Returns a list of symlink file paths. 532 mock_chroot_paths_to_symlinks.return_value = [package_symlink_path] 533 534 chroot_package_path = '/some/path/to/chroot/src/path/to/package.ebuild' 535 536 # Simulate 'GetEbuildPathsFromSymlinkPaths' when returning a dictionary of 537 # a symlink that points to an ebuild. 538 # 539 # Returns a dictionary of a symlink and ebuild file path pair 540 # where the key is the absolute path to the symlink of the ebuild file 541 # and the value is the absolute path to the ebuild file of the package. 542 mock_ebuild_paths_from_symlink_paths.return_value = { 543 package_symlink_path: chroot_package_path 544 } 545 546 chroot_path = '/some/path/to/chroot' 547 package_name = 'test-pckg/package' 548 549 self.assertEqual( 550 update_chromeos_llvm_next_hash.CreatePathDictionaryFromPackages( 551 chroot_path, [package_name]), 552 {package_symlink_path: chroot_package_path}) 553 554 mock_get_chroot_paths.assert_called_once_with(chroot_path, [package_name]) 555 556 mock_chroot_paths_to_symlinks.assert_called_once_with( 557 chroot_path, [package_chroot_path]) 558 559 mock_ebuild_paths_from_symlink_paths.assert_called_once_with( 560 [package_symlink_path]) 561 562 # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully 563 # removed patches. 564 @mock.patch.object( 565 update_chromeos_llvm_next_hash, 566 'ExecCommandAndCaptureOutput', 567 return_value=None) 568 def testSuccessfullyRemovedPatchesFromFilesDir(self, mock_run_cmd): 569 patches_to_remove_list = [ 570 '/abs/path/to/filesdir/cherry/fix_output.patch', 571 '/abs/path/to/filesdir/display_results.patch' 572 ] 573 574 update_chromeos_llvm_next_hash.RemovePatchesFromFilesDir( 575 patches_to_remove_list) 576 577 self.assertEqual(mock_run_cmd.call_count, 2) 578 579 # Simulate behavior of 'os.path.isfile()' when the absolute path to the patch 580 # metadata file does not exist. 581 @mock.patch.object(os.path, 'isfile', return_value=False) 582 def testInvalidPatchMetadataFileStagedForCommit(self, mock_isfile): 583 patch_metadata_path = '/abs/path/to/filesdir/PATCHES' 584 585 # Verify the exception is raised when the absolute path to the patch 586 # metadata file does not exist or is not a file. 587 with self.assertRaises(ValueError) as err: 588 update_chromeos_llvm_next_hash.StagePatchMetadataFileForCommit( 589 patch_metadata_path) 590 591 self.assertEqual( 592 str(err.exception), 'Invalid patch metadata file provided: ' 593 '%s' % patch_metadata_path) 594 595 mock_isfile.assert_called_once() 596 597 # Simulate the behavior of 'os.path.isfile()' when the absolute path to the 598 # patch metadata file exists. 599 @mock.patch.object(os.path, 'isfile', return_value=True) 600 # Simulate the behavior of 'ExecCommandAndCaptureOutput()' when successfully 601 # staged the patch metadata file for commit. 602 @mock.patch.object( 603 update_chromeos_llvm_next_hash, 604 'ExecCommandAndCaptureOutput', 605 return_value=None) 606 def testSuccessfullyStagedPatchMetadataFileForCommit(self, mock_run_cmd, 607 _mock_isfile): 608 609 patch_metadata_path = '/abs/path/to/filesdir/PATCHES.json' 610 611 update_chromeos_llvm_next_hash.StagePatchMetadataFileForCommit( 612 patch_metadata_path) 613 614 mock_run_cmd.assert_called_once() 615 616 def testNoPatchResultsForCommit(self): 617 package_1_patch_info_dict = { 618 'applied_patches': ['display_results.patch'], 619 'failed_patches': ['fixes_output.patch'], 620 'non_applicable_patches': [], 621 'disabled_patches': [], 622 'removed_patches': [], 623 'modified_metadata': None 624 } 625 626 package_2_patch_info_dict = { 627 'applied_patches': ['redirects_stdout.patch', 'fix_display.patch'], 628 'failed_patches': [], 629 'non_applicable_patches': [], 630 'disabled_patches': [], 631 'removed_patches': [], 632 'modified_metadata': None 633 } 634 635 test_package_info_dict = { 636 'test-packages/package1': package_1_patch_info_dict, 637 'test-packages/package2': package_2_patch_info_dict 638 } 639 640 test_commit_message = ['-m %s' % 'Updated packages'] 641 642 self.assertListEqual( 643 update_chromeos_llvm_next_hash.StagePackagesPatchResultsForCommit( 644 test_package_info_dict, test_commit_message), test_commit_message) 645 646 @mock.patch.object(update_chromeos_llvm_next_hash, 647 'StagePatchMetadataFileForCommit') 648 @mock.patch.object(update_chromeos_llvm_next_hash, 649 'RemovePatchesFromFilesDir') 650 def testAddedPatchResultsForCommit(self, mock_remove_patches, 651 mock_stage_patches_for_commit): 652 653 package_1_patch_info_dict = { 654 'applied_patches': [], 655 'failed_patches': [], 656 'non_applicable_patches': [], 657 'disabled_patches': ['fixes_output.patch'], 658 'removed_patches': [], 659 'modified_metadata': '/abs/path/to/filesdir/PATCHES.json' 660 } 661 662 package_2_patch_info_dict = { 663 'applied_patches': ['fix_display.patch'], 664 'failed_patches': [], 665 'non_applicable_patches': [], 666 'disabled_patches': [], 667 'removed_patches': ['/abs/path/to/filesdir/redirect_stdout.patch'], 668 'modified_metadata': '/abs/path/to/filesdir/PATCHES.json' 669 } 670 671 test_package_info_dict = { 672 'test-packages/package1': package_1_patch_info_dict, 673 'test-packages/package2': package_2_patch_info_dict 674 } 675 676 test_commit_message = ['-m %s' % 'Updated packages'] 677 678 expected_commit_messages = [ 679 '-m %s' % 'Updated packages', 680 '-m %s' % 'For the package test-packages/package1:', 681 '-m %s' % 'The patch metadata file PATCHES.json was modified', 682 '-m %s' % 'The following patches were disabled:', 683 '-m %s' % 'fixes_output.patch', 684 '-m %s' % 'For the package test-packages/package2:', 685 '-m %s' % 'The patch metadata file PATCHES.json was modified', 686 '-m %s' % 'The following patches were removed:', 687 '-m %s' % 'redirect_stdout.patch' 688 ] 689 690 self.assertListEqual( 691 update_chromeos_llvm_next_hash.StagePackagesPatchResultsForCommit( 692 test_package_info_dict, test_commit_message), 693 expected_commit_messages) 694 695 path_to_removed_patch = '/abs/path/to/filesdir/redirect_stdout.patch' 696 697 mock_remove_patches.assert_called_once_with([path_to_removed_patch]) 698 699 self.assertEqual(mock_stage_patches_for_commit.call_count, 2) 700 701 @mock.patch.object(update_chromeos_llvm_next_hash, 702 'CreatePathDictionaryFromPackages') 703 @mock.patch.object(update_chromeos_llvm_next_hash, '_CreateRepo') 704 @mock.patch.object(update_chromeos_llvm_next_hash, 'UpdateBuildLLVMNextHash') 705 @mock.patch.object(update_chromeos_llvm_next_hash, 'UprevEbuild') 706 @mock.patch.object(update_chromeos_llvm_next_hash, 'UploadChanges') 707 @mock.patch.object(update_chromeos_llvm_next_hash, '_DeleteRepo') 708 def testExceptionRaisedWhenUpdatingPackages( 709 self, mock_delete_repo, mock_upload_changes, mock_uprev_ebuild, 710 mock_update_llvm_next, mock_create_repo, mock_create_path_dict): 711 712 abs_path_to_package = '/some/path/to/chroot/src/path/to/package.ebuild' 713 714 symlink_path_to_package = \ 715 '/some/path/to/chroot/src/path/to/package-r1.ebuild' 716 717 path_to_package_dir = '/some/path/to/chroot/src/path/to' 718 719 # Test function to simulate '_CreateRepo' when successfully created the 720 # repo on a valid repo path. 721 def SuccessfullyCreateRepoForChanges(_repo_path, llvm_hash): 722 self.assertEqual(llvm_hash, 'a123testhash4') 723 return 724 725 # Test function to simulate 'UpdateBuildLLVMNextHash' when successfully 726 # updated the ebuild's 'LLVM_NEXT_HASH'. 727 def SuccessfullyUpdatedLLVMNextHash(ebuild_path, llvm_hash, llvm_version): 728 self.assertEqual(ebuild_path, abs_path_to_package) 729 self.assertEqual(llvm_hash, 'a123testhash4') 730 self.assertEqual(llvm_version, 1000) 731 return 732 733 # Test function to simulate 'UprevEbuild' when the symlink to the ebuild 734 # does not have a revision number. 735 def FailedToUprevEbuild(_symlink_path): 736 # Raises a 'ValueError' exception because the symlink did not have have a 737 # revision number. 738 raise ValueError('Failed to uprev the ebuild.') 739 740 # Test function to fail on 'UploadChanges' if the function gets called 741 # when an exception is raised. 742 def ShouldNotExecuteUploadChanges(_repo_path, _llvm_hash, _commit_messages): 743 # Test function should not be called (i.e. execution should resume in the 744 # 'finally' block) because 'UprevEbuild()' raised an 745 # exception. 746 assert False, 'Failed to go to "finally" block ' \ 747 'after the exception was raised.' 748 749 test_package_path_dict = {symlink_path_to_package: abs_path_to_package} 750 751 # Simulate behavior of 'CreatePathDictionaryFromPackages()' when 752 # successfully created a dictionary where the key is the absolute path to 753 # the symlink of the package and value is the absolute path to the ebuild of 754 # the package. 755 mock_create_path_dict.return_value = test_package_path_dict 756 757 # Use test function to simulate behavior. 758 mock_create_repo.side_effect = SuccessfullyCreateRepoForChanges 759 mock_update_llvm_next.side_effect = SuccessfullyUpdatedLLVMNextHash 760 mock_uprev_ebuild.side_effect = FailedToUprevEbuild 761 mock_upload_changes.side_effect = ShouldNotExecuteUploadChanges 762 763 packages_to_update = ['test-packages/package1'] 764 patch_metadata_file = 'PATCHES.json' 765 llvm_hash = 'a123testhash4' 766 llvm_version = 1000 767 chroot_path = '/some/path/to/chroot' 768 svn_option = 'google3' 769 770 # Verify exception is raised when an exception is thrown within 771 # the 'try' block by UprevEbuild function. 772 with self.assertRaises(ValueError) as err: 773 update_chromeos_llvm_next_hash.UpdatePackages( 774 packages_to_update, llvm_hash, llvm_version, chroot_path, 775 patch_metadata_file, FailureModes.FAIL, svn_option) 776 777 self.assertEqual(str(err.exception), 'Failed to uprev the ebuild.') 778 779 mock_create_path_dict.assert_called_once_with(chroot_path, 780 packages_to_update) 781 782 mock_create_repo.assert_called_once_with(path_to_package_dir, llvm_hash) 783 784 mock_update_llvm_next.assert_called_once_with(abs_path_to_package, 785 llvm_hash, llvm_version) 786 787 mock_uprev_ebuild.assert_called_once_with(symlink_path_to_package) 788 789 mock_upload_changes.assert_not_called() 790 791 mock_delete_repo.assert_called_once_with(path_to_package_dir, llvm_hash) 792 793 @mock.patch.object(update_chromeos_llvm_next_hash, 794 'CreatePathDictionaryFromPackages') 795 @mock.patch.object(update_chromeos_llvm_next_hash, '_CreateRepo') 796 @mock.patch.object(update_chromeos_llvm_next_hash, 'UpdateBuildLLVMNextHash') 797 @mock.patch.object(update_chromeos_llvm_next_hash, 'UprevEbuild') 798 @mock.patch.object(update_chromeos_llvm_next_hash, 'UploadChanges') 799 @mock.patch.object(update_chromeos_llvm_next_hash, '_DeleteRepo') 800 @mock.patch.object(llvm_patch_management, 'UpdatePackagesPatchMetadataFile') 801 @mock.patch.object(update_chromeos_llvm_next_hash, 802 'StagePatchMetadataFileForCommit') 803 def testSuccessfullyUpdatedPackages( 804 self, mock_stage_patch_file, mock_update_package_metadata_file, 805 mock_delete_repo, mock_upload_changes, mock_uprev_ebuild, 806 mock_update_llvm_next, mock_create_repo, mock_create_path_dict): 807 808 abs_path_to_package = '/some/path/to/chroot/src/path/to/package.ebuild' 809 810 symlink_path_to_package = \ 811 '/some/path/to/chroot/src/path/to/package-r1.ebuild' 812 813 path_to_package_dir = '/some/path/to/chroot/src/path/to' 814 815 # Test function to simulate '_CreateRepo' when successfully created the repo 816 # for the changes to be made to the ebuild files. 817 def SuccessfullyCreateRepoForChanges(_repo_path, llvm_hash): 818 self.assertEqual(llvm_hash, 'a123testhash5') 819 return 820 821 # Test function to simulate 'UploadChanges' after a successfull update of 822 # 'LLVM_NEXT_HASH" of the ebuild file. 823 def SuccessfullyUpdatedLLVMNextHash(ebuild_path, llvm_hash, llvm_version): 824 self.assertEqual(ebuild_path, 825 '/some/path/to/chroot/src/path/to/package.ebuild') 826 self.assertEqual(llvm_hash, 'a123testhash5') 827 self.assertEqual(llvm_version, 1000) 828 return 829 830 # Test function to simulate 'UprevEbuild' when successfully incremented 831 # the revision number by 1. 832 def SuccessfullyUprevedEbuild(symlink_path): 833 self.assertEqual(symlink_path, 834 '/some/path/to/chroot/src/path/to/package-r1.ebuild') 835 836 return 837 838 # Test function to simulate 'UpdatePackagesPatchMetadataFile()' when the 839 # patch results contains a disabled patch in 'disable_patches' mode. 840 def RetrievedPatchResults(chroot_path, llvm_version, patch_metadata_file, 841 packages, mode): 842 843 self.assertEqual(chroot_path, '/some/path/to/chroot') 844 self.assertEqual(llvm_version, 1000) 845 self.assertEqual(patch_metadata_file, 'PATCHES.json') 846 self.assertListEqual(packages, ['path/to']) 847 self.assertEqual(mode, FailureModes.DISABLE_PATCHES) 848 849 PatchInfo = namedtuple('PatchInfo', [ 850 'applied_patches', 'failed_patches', 'non_applicable_patches', 851 'disabled_patches', 'removed_patches', 'modified_metadata' 852 ]) 853 854 package_patch_info = PatchInfo( 855 applied_patches=['fix_display.patch'], 856 failed_patches=['fix_stdout.patch'], 857 non_applicable_patches=[], 858 disabled_patches=['fix_stdout.patch'], 859 removed_patches=[], 860 modified_metadata='/abs/path/to/filesdir/%s' % patch_metadata_file) 861 862 package_info_dict = {'path/to': package_patch_info._asdict()} 863 864 # Returns a dictionary where the key is the package and the value is a 865 # dictionary that contains information about the package's patch results 866 # produced by the patch manager. 867 return package_info_dict 868 869 # Test function to simulate 'UploadChanges()' when successfully created a 870 # commit for the changes made to the packages and their patches and 871 # retrieved the change list of the commit. 872 def SuccessfullyUploadedChanges(_repo_path, _llvm_hash, _commit_messages): 873 commit_url = 'https://some_name/path/to/commit/+/12345' 874 875 return update_chromeos_llvm_next_hash.CommitContents( 876 url=commit_url, cl_number=12345) 877 878 test_package_path_dict = {symlink_path_to_package: abs_path_to_package} 879 880 # Simulate behavior of 'CreatePathDictionaryFromPackages()' when 881 # successfully created a dictionary where the key is the absolute path to 882 # the symlink of the package and value is the absolute path to the ebuild of 883 # the package. 884 mock_create_path_dict.return_value = test_package_path_dict 885 886 # Use test function to simulate behavior. 887 mock_create_repo.side_effect = SuccessfullyCreateRepoForChanges 888 mock_update_llvm_next.side_effect = SuccessfullyUpdatedLLVMNextHash 889 mock_uprev_ebuild.side_effect = SuccessfullyUprevedEbuild 890 mock_update_package_metadata_file.side_effect = RetrievedPatchResults 891 mock_upload_changes.side_effect = SuccessfullyUploadedChanges 892 893 packages_to_update = ['test-packages/package1'] 894 patch_metadata_file = 'PATCHES.json' 895 llvm_hash = 'a123testhash5' 896 llvm_version = 1000 897 chroot_path = '/some/path/to/chroot' 898 svn_option = 'tot' 899 900 change_list = update_chromeos_llvm_next_hash.UpdatePackages( 901 packages_to_update, llvm_hash, llvm_version, chroot_path, 902 patch_metadata_file, FailureModes.DISABLE_PATCHES, svn_option) 903 904 self.assertEqual(change_list.url, 905 'https://some_name/path/to/commit/+/12345') 906 907 self.assertEqual(change_list.cl_number, 12345) 908 909 mock_create_path_dict.assert_called_once_with(chroot_path, 910 packages_to_update) 911 912 mock_create_repo.assert_called_once_with(path_to_package_dir, llvm_hash) 913 914 mock_update_llvm_next.assert_called_once_with(abs_path_to_package, 915 llvm_hash, llvm_version) 916 917 mock_uprev_ebuild.assert_called_once_with(symlink_path_to_package) 918 919 expected_commit_messages = [ 920 '-m %s' % 'llvm-next/tot: upgrade to a123testhash5 (r1000)', 921 '-m %s' % 'The following packages have been updated:', 922 '-m %s' % 'path/to', 923 '-m %s' % 'For the package path/to:', 924 '-m %s' % 'The patch metadata file PATCHES.json was modified', 925 '-m %s' % 'The following patches were disabled:', 926 '-m %s' % 'fix_stdout.patch' 927 ] 928 929 mock_update_package_metadata_file.assert_called_once() 930 931 mock_stage_patch_file.assert_called_once_with( 932 '/abs/path/to/filesdir/PATCHES.json') 933 934 mock_upload_changes.assert_called_once_with(path_to_package_dir, llvm_hash, 935 expected_commit_messages) 936 937 mock_delete_repo.assert_called_once_with(path_to_package_dir, llvm_hash) 938 939 940if __name__ == '__main__': 941 unittest.main() 942