diff options
Diffstat (limited to 'llvm_tools/update_chromeos_llvm_hash_unittest.py')
-rwxr-xr-x | llvm_tools/update_chromeos_llvm_hash_unittest.py | 996 |
1 files changed, 996 insertions, 0 deletions
diff --git a/llvm_tools/update_chromeos_llvm_hash_unittest.py b/llvm_tools/update_chromeos_llvm_hash_unittest.py new file mode 100755 index 00000000..84717ac7 --- /dev/null +++ b/llvm_tools/update_chromeos_llvm_hash_unittest.py @@ -0,0 +1,996 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2019 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unit tests for updating LLVM hashes.""" + +from __future__ import print_function + +from collections import namedtuple +import os +import subprocess +import unittest +import unittest.mock as mock + +from failure_modes import FailureModes +from test_helpers import CreateTemporaryJsonFile +import llvm_patch_management +import update_chromeos_llvm_hash + +# These are unittests; protected access is OK to a point. +# pylint: disable=protected-access + + +class UpdateLLVMHashTest(unittest.TestCase): + """Test class for updating LLVM hashes of packages.""" + + @mock.patch.object(update_chromeos_llvm_hash, 'ChrootRunCommand') + def testSucceedsToGetChrootPathForPackage(self, mock_chroot_command): + package_chroot_path = '/chroot/path/to/package.ebuild' + + # Emulate ChrootRunCommandWOutput behavior when a chroot path is found for + # a valid package. + mock_chroot_command.return_value = package_chroot_path + + chroot_path = '/test/chroot/path' + package_list = ['new-test/package'] + + self.assertEqual( + update_chromeos_llvm_hash.GetChrootBuildPaths( + chroot_path, package_list), [package_chroot_path]) + + mock_chroot_command.assert_called_once() + + def testFailedToConvertChrootPathWithInvalidPrefixToSymlinkPath(self): + chroot_path = '/path/to/chroot' + chroot_file_path = '/src/package.ebuild' + + # Verify the exception is raised when a symlink does not have the prefix + # '/mnt/host/source/'. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash._ConvertChrootPathsToSymLinkPaths( + chroot_path, [chroot_file_path]) + + self.assertEqual( + str(err.exception), 'Invalid prefix for the chroot path: ' + '%s' % chroot_file_path) + + def testSucceedsToConvertChrootPathToSymlinkPath(self): + chroot_path = '/path/to/chroot' + chroot_file_paths = ['/mnt/host/source/src/package.ebuild'] + + expected_symlink_path = '/path/to/chroot/src/package.ebuild' + + self.assertEqual( + update_chromeos_llvm_hash._ConvertChrootPathsToSymLinkPaths( + chroot_path, chroot_file_paths), [expected_symlink_path]) + + # Simulate 'os.path.islink' when a path is not a symbolic link. + @mock.patch.object(os.path, 'islink', return_value=False) + def testFailedToGetEbuildPathFromInvalidSymlink(self, mock_islink): + symlink_path = '/symlink/path/src/to/package-r1.ebuild' + + # Verify the exception is raised when the argument is not a symbolic link. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.GetEbuildPathsFromSymLinkPaths([symlink_path]) + + self.assertEqual( + str(err.exception), 'Invalid symlink provided: %s' % symlink_path) + + mock_islink.assert_called_once_with(symlink_path) + + # Simulate 'os.path.islink' when a path is a symbolic link. + @mock.patch.object(os.path, 'islink', return_value=True) + @mock.patch.object(os.path, 'realpath') + def testSucceedsToGetEbuildPathFromValidSymlink(self, mock_realpath, + mock_islink): + + symlink_path = '/path/to/chroot/src/package-r1.ebuild' + + abs_path_to_package = '/abs/path/to/src/package.ebuild' + + # Simulate 'os.path.realpath' when a valid path is passed in. + mock_realpath.return_value = abs_path_to_package + + expected_resolved_paths = {symlink_path: abs_path_to_package} + + self.assertEqual( + update_chromeos_llvm_hash.GetEbuildPathsFromSymLinkPaths( + [symlink_path]), expected_resolved_paths) + + mock_realpath.assert_called_once_with(symlink_path) + + mock_islink.assert_called_once_with(symlink_path) + + # Simulate behavior of 'os.path.isfile()' when the ebuild path to a package + # does not exist. + @mock.patch.object(os.path, 'isfile', return_value=False) + def testFailedToUpdateLLVMHashForInvalidEbuildPath(self, mock_isfile): + ebuild_path = '/some/path/to/package.ebuild' + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current + git_hash = 'a123testhash1' + svn_version = 1000 + + # Verify the exception is raised when the ebuild path does not exist. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UpdateEbuildLLVMHash(ebuild_path, llvm_variant, + git_hash, svn_version) + + self.assertEqual( + str(err.exception), 'Invalid ebuild path provided: %s' % ebuild_path) + + mock_isfile.assert_called_once() + + # Simulate 'os.path.isfile' behavior on a valid ebuild path. + @mock.patch.object(os.path, 'isfile', return_value=True) + def testFailedToUpdateLLVMHash(self, mock_isfile): + # Create a temporary file to simulate an ebuild file of a package. + with CreateTemporaryJsonFile() as ebuild_file: + with open(ebuild_file, 'w') as f: + f.write('\n'.join([ + 'First line in the ebuild', 'Second line in the ebuild', + 'Last line in the ebuild' + ])) + + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current + git_hash = 'a123testhash1' + svn_version = 1000 + + # Verify the exception is raised when the ebuild file does not have + # 'LLVM_HASH'. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UpdateEbuildLLVMHash( + ebuild_file, llvm_variant, git_hash, svn_version) + + self.assertEqual( + str(err.exception), ('Failed to update %s.', 'LLVM_HASH')) + + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next + + self.assertEqual(mock_isfile.call_count, 2) + + # Simulate 'os.path.isfile' behavior on a valid ebuild path. + @mock.patch.object(os.path, 'isfile', return_value=True) + def testFailedToUpdateLLVMNextHash(self, mock_isfile): + # Create a temporary file to simulate an ebuild file of a package. + with CreateTemporaryJsonFile() as ebuild_file: + with open(ebuild_file, 'w') as f: + f.write('\n'.join([ + 'First line in the ebuild', 'Second line in the ebuild', + 'Last line in the ebuild' + ])) + + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next + git_hash = 'a123testhash1' + svn_version = 1000 + + # Verify the exception is raised when the ebuild file does not have + # 'LLVM_NEXT_HASH'. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UpdateEbuildLLVMHash( + ebuild_file, llvm_variant, git_hash, svn_version) + + self.assertEqual( + str(err.exception), ('Failed to update %s.', 'LLVM_NEXT_HASH')) + + self.assertEqual(mock_isfile.call_count, 2) + + # Simulate 'os.path.isfile' behavior on a valid ebuild path. + @mock.patch.object(os.path, 'isfile', return_value=True) + # Simulate 'ExecCommandAndCaptureOutput()' when successfully staged the + # ebuild file for commit. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyStageTheEbuildForCommitForLLVMHashUpdate( + self, mock_stage_commit_command, mock_isfile): + + # Create a temporary file to simulate an ebuild file of a package. + with CreateTemporaryJsonFile() as ebuild_file: + # Updates LLVM_HASH to 'git_hash' and revision to + # 'svn_version'. + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.current + git_hash = 'a123testhash1' + svn_version = 1000 + + with open(ebuild_file, 'w') as f: + f.write('\n'.join([ + 'First line in the ebuild', 'Second line in the ebuild', + 'LLVM_HASH=\"a12b34c56d78e90\" # r500', 'Last line in the ebuild' + ])) + + update_chromeos_llvm_hash.UpdateEbuildLLVMHash(ebuild_file, llvm_variant, + git_hash, svn_version) + + expected_file_contents = [ + 'First line in the ebuild\n', 'Second line in the ebuild\n', + 'LLVM_HASH=\"a123testhash1\" # r1000\n', 'Last line in the ebuild' + ] + + # Verify the new file contents of the ebuild file match the expected file + # contents. + with open(ebuild_file) as new_file: + file_contents_as_a_list = [cur_line for cur_line in new_file] + self.assertListEqual(file_contents_as_a_list, expected_file_contents) + + self.assertEqual(mock_isfile.call_count, 2) + + mock_stage_commit_command.assert_called_once() + + # Simulate 'os.path.isfile' behavior on a valid ebuild path. + @mock.patch.object(os.path, 'isfile', return_value=True) + # Simulate 'ExecCommandAndCaptureOutput()' when successfully staged the + # ebuild file for commit. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyStageTheEbuildForCommitForLLVMNextHashUpdate( + self, mock_stage_commit_command, mock_isfile): + + # Create a temporary file to simulate an ebuild file of a package. + with CreateTemporaryJsonFile() as ebuild_file: + # Updates LLVM_NEXT_HASH to 'git_hash' and revision to + # 'svn_version'. + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next + git_hash = 'a123testhash1' + svn_version = 1000 + + with open(ebuild_file, 'w') as f: + f.write('\n'.join([ + 'First line in the ebuild', 'Second line in the ebuild', + 'LLVM_NEXT_HASH=\"a12b34c56d78e90\" # r500', + 'Last line in the ebuild' + ])) + + update_chromeos_llvm_hash.UpdateEbuildLLVMHash(ebuild_file, llvm_variant, + git_hash, svn_version) + + expected_file_contents = [ + 'First line in the ebuild\n', 'Second line in the ebuild\n', + 'LLVM_NEXT_HASH=\"a123testhash1\" # r1000\n', + 'Last line in the ebuild' + ] + + # Verify the new file contents of the ebuild file match the expected file + # contents. + with open(ebuild_file) as new_file: + file_contents_as_a_list = [cur_line for cur_line in new_file] + self.assertListEqual(file_contents_as_a_list, expected_file_contents) + + self.assertEqual(mock_isfile.call_count, 2) + + mock_stage_commit_command.assert_called_once() + + # Simulate behavior of 'os.path.islink()' when the argument passed in is not a + # symbolic link. + @mock.patch.object(os.path, 'islink', return_value=False) + def testFailedToUprevEbuildForInvalidSymlink(self, mock_islink): + symlink_to_uprev = '/symlink/to/package.ebuild' + + # Verify the exception is raised when a symbolic link is not passed in. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UprevEbuild(symlink_to_uprev) + + self.assertEqual( + str(err.exception), 'Invalid symlink provided: %s' % symlink_to_uprev) + + mock_islink.assert_called_once() + + # Simulate 'os.path.islink' when a symbolic link is passed in. + @mock.patch.object(os.path, 'islink', return_value=True) + def testFailedToUprevEbuild(self, mock_islink): + symlink_to_uprev = '/symlink/to/package.ebuild' + + # Verify the exception is raised when the symlink does not have a revision + # number. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UprevEbuild(symlink_to_uprev) + + self.assertEqual(str(err.exception), 'Failed to uprev the ebuild.') + + mock_islink.assert_called_once_with(symlink_to_uprev) + + # Simulate 'os.path.islink' when a valid symbolic link is passed in. + @mock.patch.object(os.path, 'islink', return_value=True) + # Simulate 'os.path.dirname' when returning a path to the directory of a + # valid symbolic link. + @mock.patch.object(os.path, 'dirname', return_value='/symlink/to') + # Simulate 'RunCommandWOutput' when successfully added the upreved symlink + # for commit. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyUprevEbuild(self, mock_command_output, mock_dirname, + mock_islink): + + symlink_to_uprev = '/symlink/to/package-r1.ebuild' + + update_chromeos_llvm_hash.UprevEbuild(symlink_to_uprev) + + mock_islink.assert_called_once_with(symlink_to_uprev) + + mock_dirname.assert_called_once_with(symlink_to_uprev) + + mock_command_output.assert_called_once() + + # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a + # directory. + @mock.patch.object(os.path, 'isdir', return_value=False) + def testFailedToCreateRepoForInvalidDirectoryPath(self, mock_isdir): + path_to_repo = '/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash1' + + # Verify the exception is raised when provided an invalid directory path. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash._CreateRepo(path_to_repo, branch) + + self.assertEqual( + str(err.exception), + 'Invalid directory path provided: %s' % path_to_repo) + + mock_isdir.assert_called_once() + + # Simulate 'os.path.isdir' when a valid repo path is provided. + @mock.patch.object(os.path, 'isdir', return_value=True) + # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully reset + # changes and created a repo. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyCreatedRepo(self, mock_command_output, mock_isdir): + path_to_repo = '/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash1' + + update_chromeos_llvm_hash._CreateRepo(path_to_repo, branch) + + mock_isdir.assert_called_once_with(path_to_repo) + + self.assertEqual(mock_command_output.call_count, 2) + + # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a + # directory. + @mock.patch.object(os.path, 'isdir', return_value=False) + def testFailedToDeleteRepoForInvalidDirectoryPath(self, mock_isdir): + path_to_repo = '/some/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash2' + + # Verify the exception is raised on an invalid repo path. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash._DeleteRepo(path_to_repo, branch) + + self.assertEqual( + str(err.exception), + 'Invalid directory path provided: %s' % path_to_repo) + + mock_isdir.assert_called_once() + + # Simulate 'os.path.isdir' on valid directory path. + @mock.patch.object(os.path, 'isdir', return_value=True) + # Simulate 'ExecCommandAndCaptureOutput()' when successfully checkout to + # cros/master, reset changes, and deleted the repo. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyDeletedRepo(self, mock_command_output, mock_isdir): + path_to_repo = '/some/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash2' + + update_chromeos_llvm_hash._DeleteRepo(path_to_repo, branch) + + mock_isdir.assert_called_once_with(path_to_repo) + + self.assertEqual(mock_command_output.call_count, 3) + + def testFailedToFindChangeListURL(self): + repo_upload_contents = 'remote: https://some_url' + + # Verify the exception is raised when failed to find the Gerrit URL when + # parsing the 'repo upload' contents. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.GetGerritRepoUploadContents( + repo_upload_contents) + + self.assertEqual(str(err.exception), 'Failed to find change list URL.') + + def testSuccessfullyGetGerritRepoUploadContents(self): + repo_upload_contents = ('remote: https://chromium-review.googlesource.com' + '/c/chromiumos/overlays/chromiumos-overlay/+/' + '193147 Some commit header') + + change_list = update_chromeos_llvm_hash.GetGerritRepoUploadContents( + repo_upload_contents) + + self.assertEqual( + change_list.url, + 'https://chromium-review.googlesource.com/c/chromiumos/overlays/' + 'chromiumos-overlay/+/193147') + + self.assertEqual(change_list.cl_number, 193147) + + # Simulate behavior of 'os.path.isdir()' when the path to the repo is not a + # directory. + @mock.patch.object(os.path, 'isdir', return_value=False) + def testFailedToUploadChangesForInvalidPathDirectory(self, mock_isdir): + path_to_repo = '/some/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash3' + commit_messages = ['-m Test message'] + + # Verify exception is raised when on an invalid repo path. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UploadChanges(path_to_repo, branch, + commit_messages) + + self.assertEqual( + str(err.exception), + 'Invalid directory path provided: %s' % path_to_repo) + + mock_isdir.assert_called_once() + + # Simulate 'os.path.isdir' on a valid repo path. + @mock.patch.object(os.path, 'isdir', return_value=True) + # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully + # committed the changes. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + @mock.patch.object(subprocess, 'Popen') + def testFailedToUploadChangesForReview(self, mock_repo_upload, + mock_command_output, mock_isdir): + + # Simulate the behavior of 'subprocess.Popen()' when uploading the changes + # for review + # + # `Popen.communicate()` returns a tuple of `stdout` and `stderr`. + mock_repo_upload.return_value.communicate.return_value = ( + None, 'Branch does not exist.') + + # Exit code of 1 means failed to upload changes for review. + mock_repo_upload.return_value.returncode = 1 + + path_to_repo = '/some/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash3' + commit_messages = ['-m Test message'] + + # Verify exception is raised when failed to upload the changes for review. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UploadChanges(path_to_repo, branch, + commit_messages) + + self.assertEqual(str(err.exception), 'Failed to upload changes for review') + + mock_isdir.assert_called_once_with(path_to_repo) + + mock_command_output.assert_called_once() + + mock_repo_upload.assert_called_once() + + # Simulate 'os.path.isdir' when a valid repo path is passed in. + @mock.patch.object(os.path, 'isdir', return_value=True) + # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully + # committed the changes. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + @mock.patch.object(subprocess, 'Popen') + def testSuccessfullyUploadedChangesForReview(self, mock_repo_upload, + mock_command_output, mock_isdir): + + # A test CL generated by `repo upload`. + repo_upload_contents = ('remote: https://chromium-review.googlesource.' + 'com/c/chromiumos/overlays/chromiumos-overlay/' + '+/193147 Fix stdout') + + # Simulate the behavior of 'subprocess.Popen()' when uploading the changes + # for review + # + # `Popen.communicate()` returns a tuple of `stdout` and `stderr`. + mock_repo_upload.return_value.communicate.return_value = ( + repo_upload_contents, None) + + # Exit code of 0 means successfully uploaded changes for review. + mock_repo_upload.return_value.returncode = 0 + + path_to_repo = '/some/path/to/repo' + branch = 'update-LLVM_NEXT_HASH-a123testhash3' + commit_messages = ['-m Test message'] + + change_list = update_chromeos_llvm_hash.UploadChanges( + path_to_repo, branch, commit_messages) + + self.assertEqual( + change_list.url, + 'https://chromium-review.googlesource.com/c/chromiumos/overlays/' + 'chromiumos-overlay/+/193147') + + self.assertEqual(change_list.cl_number, 193147) + + mock_isdir.assert_called_once_with(path_to_repo) + + mock_command_output.assert_called_once() + + mock_repo_upload.assert_called_once() + + @mock.patch.object(update_chromeos_llvm_hash, 'GetChrootBuildPaths') + @mock.patch.object(update_chromeos_llvm_hash, + '_ConvertChrootPathsToSymLinkPaths') + def testExceptionRaisedWhenCreatingPathDictionaryFromPackages( + self, mock_chroot_paths_to_symlinks, mock_get_chroot_paths): + + chroot_path = '/some/path/to/chroot' + + package_name = 'test-pckg/package' + package_chroot_path = '/some/chroot/path/to/package-r1.ebuild' + + # Test function to simulate '_ConvertChrootPathsToSymLinkPaths' when a + # symlink does not start with the prefix '/mnt/host/source'. + def BadPrefixChrootPath(_chroot_path, _chroot_file_paths): + raise ValueError('Invalid prefix for the chroot path: ' + '%s' % package_chroot_path) + + # Simulate 'GetChrootBuildPaths' when valid packages are passed in. + # + # Returns a list of chroot paths. + mock_get_chroot_paths.return_value = [package_chroot_path] + + # Use test function to simulate '_ConvertChrootPathsToSymLinkPaths' + # behavior. + mock_chroot_paths_to_symlinks.side_effect = BadPrefixChrootPath + + # Verify exception is raised when for an invalid prefix in the symlink. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.CreatePathDictionaryFromPackages( + chroot_path, [package_name]) + + self.assertEqual( + str(err.exception), 'Invalid prefix for the chroot path: ' + '%s' % package_chroot_path) + + mock_get_chroot_paths.assert_called_once_with(chroot_path, [package_name]) + + mock_chroot_paths_to_symlinks.assert_called_once_with( + chroot_path, [package_chroot_path]) + + @mock.patch.object(update_chromeos_llvm_hash, 'GetChrootBuildPaths') + @mock.patch.object(update_chromeos_llvm_hash, + '_ConvertChrootPathsToSymLinkPaths') + @mock.patch.object(update_chromeos_llvm_hash, + 'GetEbuildPathsFromSymLinkPaths') + def testSuccessfullyCreatedPathDictionaryFromPackages( + self, mock_ebuild_paths_from_symlink_paths, mock_chroot_paths_to_symlinks, + mock_get_chroot_paths): + + package_chroot_path = '/mnt/host/source/src/path/to/package-r1.ebuild' + + # Simulate 'GetChrootBuildPaths' when returning a chroot path for a valid + # package. + # + # Returns a list of chroot paths. + mock_get_chroot_paths.return_value = [package_chroot_path] + + package_symlink_path = '/some/path/to/chroot/src/path/to/package-r1.ebuild' + + # Simulate '_ConvertChrootPathsToSymLinkPaths' when returning a symlink to + # a chroot path that points to a package. + # + # Returns a list of symlink file paths. + mock_chroot_paths_to_symlinks.return_value = [package_symlink_path] + + chroot_package_path = '/some/path/to/chroot/src/path/to/package.ebuild' + + # Simulate 'GetEbuildPathsFromSymlinkPaths' when returning a dictionary of + # a symlink that points to an ebuild. + # + # Returns a dictionary of a symlink and ebuild file path pair + # where the key is the absolute path to the symlink of the ebuild file + # and the value is the absolute path to the ebuild file of the package. + mock_ebuild_paths_from_symlink_paths.return_value = { + package_symlink_path: chroot_package_path + } + + chroot_path = '/some/path/to/chroot' + package_name = 'test-pckg/package' + + self.assertEqual( + update_chromeos_llvm_hash.CreatePathDictionaryFromPackages( + chroot_path, [package_name]), + {package_symlink_path: chroot_package_path}) + + mock_get_chroot_paths.assert_called_once_with(chroot_path, [package_name]) + + mock_chroot_paths_to_symlinks.assert_called_once_with( + chroot_path, [package_chroot_path]) + + mock_ebuild_paths_from_symlink_paths.assert_called_once_with( + [package_symlink_path]) + + # Simulate behavior of 'ExecCommandAndCaptureOutput()' when successfully + # removed patches. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyRemovedPatchesFromFilesDir(self, mock_run_cmd): + patches_to_remove_list = [ + '/abs/path/to/filesdir/cherry/fix_output.patch', + '/abs/path/to/filesdir/display_results.patch' + ] + + update_chromeos_llvm_hash.RemovePatchesFromFilesDir(patches_to_remove_list) + + self.assertEqual(mock_run_cmd.call_count, 2) + + # Simulate behavior of 'os.path.isfile()' when the absolute path to the patch + # metadata file does not exist. + @mock.patch.object(os.path, 'isfile', return_value=False) + def testInvalidPatchMetadataFileStagedForCommit(self, mock_isfile): + patch_metadata_path = '/abs/path/to/filesdir/PATCHES' + + # Verify the exception is raised when the absolute path to the patch + # metadata file does not exist or is not a file. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.StagePatchMetadataFileForCommit( + patch_metadata_path) + + self.assertEqual( + str(err.exception), 'Invalid patch metadata file provided: ' + '%s' % patch_metadata_path) + + mock_isfile.assert_called_once() + + # Simulate the behavior of 'os.path.isfile()' when the absolute path to the + # patch metadata file exists. + @mock.patch.object(os.path, 'isfile', return_value=True) + # Simulate the behavior of 'ExecCommandAndCaptureOutput()' when successfully + # staged the patch metadata file for commit. + @mock.patch.object( + update_chromeos_llvm_hash, + 'ExecCommandAndCaptureOutput', + return_value=None) + def testSuccessfullyStagedPatchMetadataFileForCommit(self, mock_run_cmd, + _mock_isfile): + + patch_metadata_path = '/abs/path/to/filesdir/PATCHES.json' + + update_chromeos_llvm_hash.StagePatchMetadataFileForCommit( + patch_metadata_path) + + mock_run_cmd.assert_called_once() + + def testNoPatchResultsForCommit(self): + package_1_patch_info_dict = { + 'applied_patches': ['display_results.patch'], + 'failed_patches': ['fixes_output.patch'], + 'non_applicable_patches': [], + 'disabled_patches': [], + 'removed_patches': [], + 'modified_metadata': None + } + + package_2_patch_info_dict = { + 'applied_patches': ['redirects_stdout.patch', 'fix_display.patch'], + 'failed_patches': [], + 'non_applicable_patches': [], + 'disabled_patches': [], + 'removed_patches': [], + 'modified_metadata': None + } + + test_package_info_dict = { + 'test-packages/package1': package_1_patch_info_dict, + 'test-packages/package2': package_2_patch_info_dict + } + + test_commit_message = ['-m %s' % 'Updated packages'] + + self.assertListEqual( + update_chromeos_llvm_hash.StagePackagesPatchResultsForCommit( + test_package_info_dict, test_commit_message), test_commit_message) + + @mock.patch.object(update_chromeos_llvm_hash, + 'StagePatchMetadataFileForCommit') + @mock.patch.object(update_chromeos_llvm_hash, 'RemovePatchesFromFilesDir') + def testAddedPatchResultsForCommit(self, mock_remove_patches, + mock_stage_patches_for_commit): + + package_1_patch_info_dict = { + 'applied_patches': [], + 'failed_patches': [], + 'non_applicable_patches': [], + 'disabled_patches': ['fixes_output.patch'], + 'removed_patches': [], + 'modified_metadata': '/abs/path/to/filesdir/PATCHES.json' + } + + package_2_patch_info_dict = { + 'applied_patches': ['fix_display.patch'], + 'failed_patches': [], + 'non_applicable_patches': [], + 'disabled_patches': [], + 'removed_patches': ['/abs/path/to/filesdir/redirect_stdout.patch'], + 'modified_metadata': '/abs/path/to/filesdir/PATCHES.json' + } + + test_package_info_dict = { + 'test-packages/package1': package_1_patch_info_dict, + 'test-packages/package2': package_2_patch_info_dict + } + + test_commit_message = ['-m %s' % 'Updated packages'] + + expected_commit_messages = [ + '-m %s' % 'Updated packages', + '-m %s' % 'For the package test-packages/package1:', + '-m %s' % 'The patch metadata file PATCHES.json was modified', + '-m %s' % 'The following patches were disabled:', + '-m %s' % 'fixes_output.patch', + '-m %s' % 'For the package test-packages/package2:', + '-m %s' % 'The patch metadata file PATCHES.json was modified', + '-m %s' % 'The following patches were removed:', + '-m %s' % 'redirect_stdout.patch' + ] + + self.assertListEqual( + update_chromeos_llvm_hash.StagePackagesPatchResultsForCommit( + test_package_info_dict, test_commit_message), + expected_commit_messages) + + path_to_removed_patch = '/abs/path/to/filesdir/redirect_stdout.patch' + + mock_remove_patches.assert_called_once_with([path_to_removed_patch]) + + self.assertEqual(mock_stage_patches_for_commit.call_count, 2) + + @mock.patch.object(update_chromeos_llvm_hash, + 'CreatePathDictionaryFromPackages') + @mock.patch.object(update_chromeos_llvm_hash, '_CreateRepo') + @mock.patch.object(update_chromeos_llvm_hash, 'UpdateEbuildLLVMHash') + @mock.patch.object(update_chromeos_llvm_hash, 'UprevEbuild') + @mock.patch.object(update_chromeos_llvm_hash, 'UploadChanges') + @mock.patch.object(update_chromeos_llvm_hash, '_DeleteRepo') + def testExceptionRaisedWhenUpdatingPackages( + self, mock_delete_repo, mock_upload_changes, mock_uprev_ebuild, + mock_update_llvm_next, mock_create_repo, mock_create_path_dict): + + abs_path_to_package = '/some/path/to/chroot/src/path/to/package.ebuild' + + symlink_path_to_package = \ + '/some/path/to/chroot/src/path/to/package-r1.ebuild' + + path_to_package_dir = '/some/path/to/chroot/src/path/to' + + # Test function to simulate '_CreateRepo' when successfully created the + # repo on a valid repo path. + def SuccessfullyCreateRepoForChanges(_repo_path, branch): + self.assertEqual(branch, 'update-LLVM_NEXT_HASH-a123testhash4') + return + + # Test function to simulate 'UpdateEbuildLLVMHash' when successfully + # updated the ebuild's 'LLVM_NEXT_HASH'. + def SuccessfullyUpdatedLLVMHash(ebuild_path, _, git_hash, svn_version): + self.assertEqual(ebuild_path, abs_path_to_package) + self.assertEqual(git_hash, 'a123testhash4') + self.assertEqual(svn_version, 1000) + return + + # Test function to simulate 'UprevEbuild' when the symlink to the ebuild + # does not have a revision number. + def FailedToUprevEbuild(_symlink_path): + # Raises a 'ValueError' exception because the symlink did not have have a + # revision number. + raise ValueError('Failed to uprev the ebuild.') + + # Test function to fail on 'UploadChanges' if the function gets called + # when an exception is raised. + def ShouldNotExecuteUploadChanges(_repo_path, _git_hash, _commit_messages): + # Test function should not be called (i.e. execution should resume in the + # 'finally' block) because 'UprevEbuild()' raised an + # exception. + assert False, 'Failed to go to "finally" block ' \ + 'after the exception was raised.' + + test_package_path_dict = {symlink_path_to_package: abs_path_to_package} + + # Simulate behavior of 'CreatePathDictionaryFromPackages()' when + # successfully created a dictionary where the key is the absolute path to + # the symlink of the package and value is the absolute path to the ebuild of + # the package. + mock_create_path_dict.return_value = test_package_path_dict + + # Use test function to simulate behavior. + mock_create_repo.side_effect = SuccessfullyCreateRepoForChanges + mock_update_llvm_next.side_effect = SuccessfullyUpdatedLLVMHash + mock_uprev_ebuild.side_effect = FailedToUprevEbuild + mock_upload_changes.side_effect = ShouldNotExecuteUploadChanges + + packages_to_update = ['test-packages/package1'] + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next + git_hash = 'a123testhash4' + svn_version = 1000 + chroot_path = '/some/path/to/chroot' + patch_metadata_file = 'PATCHES.json' + git_hash_source = 'google3' + branch = 'update-LLVM_NEXT_HASH-a123testhash4' + + # Verify exception is raised when an exception is thrown within + # the 'try' block by UprevEbuild function. + with self.assertRaises(ValueError) as err: + update_chromeos_llvm_hash.UpdatePackages( + packages_to_update, llvm_variant, git_hash, svn_version, chroot_path, + patch_metadata_file, FailureModes.FAIL, git_hash_source) + + self.assertEqual(str(err.exception), 'Failed to uprev the ebuild.') + + mock_create_path_dict.assert_called_once_with(chroot_path, + packages_to_update) + + mock_create_repo.assert_called_once_with(path_to_package_dir, branch) + + mock_update_llvm_next.assert_called_once_with( + abs_path_to_package, llvm_variant, git_hash, svn_version) + + mock_uprev_ebuild.assert_called_once_with(symlink_path_to_package) + + mock_upload_changes.assert_not_called() + + mock_delete_repo.assert_called_once_with(path_to_package_dir, branch) + + @mock.patch.object(update_chromeos_llvm_hash, + 'CreatePathDictionaryFromPackages') + @mock.patch.object(update_chromeos_llvm_hash, '_CreateRepo') + @mock.patch.object(update_chromeos_llvm_hash, 'UpdateEbuildLLVMHash') + @mock.patch.object(update_chromeos_llvm_hash, 'UprevEbuild') + @mock.patch.object(update_chromeos_llvm_hash, 'UploadChanges') + @mock.patch.object(update_chromeos_llvm_hash, '_DeleteRepo') + @mock.patch.object(llvm_patch_management, 'UpdatePackagesPatchMetadataFile') + @mock.patch.object(update_chromeos_llvm_hash, + 'StagePatchMetadataFileForCommit') + def testSuccessfullyUpdatedPackages( + self, mock_stage_patch_file, mock_update_package_metadata_file, + mock_delete_repo, mock_upload_changes, mock_uprev_ebuild, + mock_update_llvm_next, mock_create_repo, mock_create_path_dict): + + abs_path_to_package = '/some/path/to/chroot/src/path/to/package.ebuild' + + symlink_path_to_package = \ + '/some/path/to/chroot/src/path/to/package-r1.ebuild' + + path_to_package_dir = '/some/path/to/chroot/src/path/to' + + # Test function to simulate '_CreateRepo' when successfully created the repo + # for the changes to be made to the ebuild files. + def SuccessfullyCreateRepoForChanges(_repo_path, branch): + self.assertEqual(branch, 'update-LLVM_NEXT_HASH-a123testhash5') + return + + # Test function to simulate 'UploadChanges' after a successfull update of + # 'LLVM_NEXT_HASH" of the ebuild file. + def SuccessfullyUpdatedLLVMHash(ebuild_path, _, git_hash, svn_version): + self.assertEqual(ebuild_path, + '/some/path/to/chroot/src/path/to/package.ebuild') + self.assertEqual(git_hash, 'a123testhash5') + self.assertEqual(svn_version, 1000) + return + + # Test function to simulate 'UprevEbuild' when successfully incremented + # the revision number by 1. + def SuccessfullyUprevedEbuild(symlink_path): + self.assertEqual(symlink_path, + '/some/path/to/chroot/src/path/to/package-r1.ebuild') + + return + + # Test function to simulate 'UpdatePackagesPatchMetadataFile()' when the + # patch results contains a disabled patch in 'disable_patches' mode. + def RetrievedPatchResults(chroot_path, svn_version, patch_metadata_file, + packages, mode): + + self.assertEqual(chroot_path, '/some/path/to/chroot') + self.assertEqual(svn_version, 1000) + self.assertEqual(patch_metadata_file, 'PATCHES.json') + self.assertListEqual(packages, ['path/to']) + self.assertEqual(mode, FailureModes.DISABLE_PATCHES) + + PatchInfo = namedtuple('PatchInfo', [ + 'applied_patches', 'failed_patches', 'non_applicable_patches', + 'disabled_patches', 'removed_patches', 'modified_metadata' + ]) + + package_patch_info = PatchInfo( + applied_patches=['fix_display.patch'], + failed_patches=['fix_stdout.patch'], + non_applicable_patches=[], + disabled_patches=['fix_stdout.patch'], + removed_patches=[], + modified_metadata='/abs/path/to/filesdir/%s' % patch_metadata_file) + + package_info_dict = {'path/to': package_patch_info._asdict()} + + # Returns a dictionary where the key is the package and the value is a + # dictionary that contains information about the package's patch results + # produced by the patch manager. + return package_info_dict + + # Test function to simulate 'UploadChanges()' when successfully created a + # commit for the changes made to the packages and their patches and + # retrieved the change list of the commit. + def SuccessfullyUploadedChanges(_repo_path, _branch, _commit_messages): + commit_url = 'https://some_name/path/to/commit/+/12345' + + return update_chromeos_llvm_hash.CommitContents( + url=commit_url, cl_number=12345) + + test_package_path_dict = {symlink_path_to_package: abs_path_to_package} + + # Simulate behavior of 'CreatePathDictionaryFromPackages()' when + # successfully created a dictionary where the key is the absolute path to + # the symlink of the package and value is the absolute path to the ebuild of + # the package. + mock_create_path_dict.return_value = test_package_path_dict + + # Use test function to simulate behavior. + mock_create_repo.side_effect = SuccessfullyCreateRepoForChanges + mock_update_llvm_next.side_effect = SuccessfullyUpdatedLLVMHash + mock_uprev_ebuild.side_effect = SuccessfullyUprevedEbuild + mock_update_package_metadata_file.side_effect = RetrievedPatchResults + mock_upload_changes.side_effect = SuccessfullyUploadedChanges + + packages_to_update = ['test-packages/package1'] + llvm_variant = update_chromeos_llvm_hash.LLVMVariant.next + git_hash = 'a123testhash5' + svn_version = 1000 + chroot_path = '/some/path/to/chroot' + patch_metadata_file = 'PATCHES.json' + git_hash_source = 'tot' + branch = 'update-LLVM_NEXT_HASH-a123testhash5' + + change_list = update_chromeos_llvm_hash.UpdatePackages( + packages_to_update, llvm_variant, git_hash, svn_version, chroot_path, + patch_metadata_file, FailureModes.DISABLE_PATCHES, git_hash_source) + + self.assertEqual(change_list.url, + 'https://some_name/path/to/commit/+/12345') + + self.assertEqual(change_list.cl_number, 12345) + + mock_create_path_dict.assert_called_once_with(chroot_path, + packages_to_update) + + mock_create_repo.assert_called_once_with(path_to_package_dir, branch) + + mock_update_llvm_next.assert_called_once_with( + abs_path_to_package, llvm_variant, git_hash, svn_version) + + mock_uprev_ebuild.assert_called_once_with(symlink_path_to_package) + + expected_commit_messages = [ + '-m %s' % 'llvm-next/tot: upgrade to a123testhash5 (r1000)', + '-m %s' % 'The following packages have been updated:', + '-m %s' % 'path/to', + '-m %s' % 'For the package path/to:', + '-m %s' % 'The patch metadata file PATCHES.json was modified', + '-m %s' % 'The following patches were disabled:', + '-m %s' % 'fix_stdout.patch' + ] + + mock_update_package_metadata_file.assert_called_once() + + mock_stage_patch_file.assert_called_once_with( + '/abs/path/to/filesdir/PATCHES.json') + + mock_upload_changes.assert_called_once_with(path_to_package_dir, branch, + expected_commit_messages) + + mock_delete_repo.assert_called_once_with(path_to_package_dir, branch) + + +if __name__ == '__main__': + unittest.main() |