llvm_tool: cherry-pick multiple patches at once

This change will allow cherrypick_cl.py to take in multiple SHAs at once
and create local patches accordingly. All these SHAs will be applied to
the same starting SHA. The package a patch applies to will be inferred
automatically, so users no longer have to specify the package name. If a
patch changes files in more than one package, it will be split into
smaller patches by package and applied accordingly. The patch
information will be added to PATCHES.json of each affected package.

BUG=chromium:1057428

TEST=local tests.

Change-Id: I8c4d93716b7682b42c8202e8b939ca2175775fdf
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/2175675
Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
Tested-by: Jian Cai <jiancai@google.com>
diff --git a/llvm_tools/cherrypick_cl.py b/llvm_tools/cherrypick_cl.py
index 0641c09..992b04c 100755
--- a/llvm_tools/cherrypick_cl.py
+++ b/llvm_tools/cherrypick_cl.py
@@ -25,7 +25,8 @@
 
 def add_cherrypick(patches_json_path: str, patches_dir: str,
                    relative_patches_dir: str, start_version: git_llvm_rev.Rev,
-                   llvm_dir: str, rev: git_llvm_rev.Rev, sha: str):
+                   llvm_dir: str, rev: git_llvm_rev.Rev, sha: str,
+                   package: str):
   with open(patches_json_path, encoding='utf-8') as f:
     patches_json = json.load(f)
 
@@ -41,7 +42,14 @@
           'Similarly-named patch already exists in PATCHES.json: %r', rel_path)
 
   with open(os.path.join(patches_dir, file_name), 'wb') as f:
-    subprocess.check_call(['git', 'show', sha], stdout=f, cwd=llvm_dir)
+    cmd = ['git', 'show', sha]
+    # Only apply the part of the patch that belongs to this package, expect
+    # LLVM. This is because some packages are built with LLVM ebuild on X86 but
+    # not on the other architectures. e.g. compiler-rt. Therefore always apply
+    # the entire patch to LLVM ebuild as a workaround.
+    if package != 'llvm':
+      cmd.append(package_to_project(package))
+    subprocess.check_call(cmd, stdout=f, cwd=llvm_dir)
 
   commit_subject = subprocess.check_output(
       ['git', 'log', '-n1', '--format=%s', sha], cwd=llvm_dir, encoding='utf-8')
@@ -99,6 +107,54 @@
   ).strip()
 
 
+# Get the package name of an upstream project
+def project_to_package(project: str) -> str:
+  if project == 'libunwind':
+    return 'llvm-libunwind'
+  return project
+
+
+# Get the upstream project name of a package
+def package_to_project(package: str) -> str:
+  if package == 'llvm-libunwind':
+    return 'libunwind'
+  return package
+
+
+def cherry_pick(chroot_path: str, llvm_dir: str, start_rev: str, sha: str,
+                rev: str):
+  paths = subprocess.check_output(
+      ['git', 'show', '--name-only', '--format=', sha],
+      cwd=llvm_dir,
+      encoding='utf-8').splitlines()
+  # Some LLVM projects are built by LLVM ebuild on X86, so always apply the
+  # patch to LLVM ebuild
+  packages = {'llvm'}
+  # Detect if there are more packages to apply the patch to
+  for path in paths:
+    package = project_to_package(path.split('/')[0])
+    if package in ('compiler-rt', 'libcxx', 'libcxxabi', 'llvm-libunwind'):
+      packages.add(package)
+  packages = list(sorted(packages))
+
+  for package in packages:
+    # Use full package name to prevent equery from getting confused
+    symlink = chroot.GetChrootEbuildPaths(
+        chroot_path,
+        ['sys-devel/llvm' if package == 'llvm' else 'sys-libs/' + package])[0]
+    symlink = chroot.ConvertChrootPathsToAbsolutePaths(chroot_path,
+                                                       [symlink])[0]
+    symlink_dir = os.path.dirname(symlink)
+
+    patches_json_path = os.path.join(symlink_dir, 'files/PATCHES.json')
+    relative_patches_dir = 'cherry' if package == 'llvm' else ''
+    patches_dir = os.path.join(symlink_dir, 'files', relative_patches_dir)
+
+    logging.info('Cherrypicking %s (%s) into %s', rev, sha, package)
+    add_cherrypick(patches_json_path, patches_dir, relative_patches_dir,
+                   start_rev, llvm_dir, rev, sha, package)
+
+
 def main():
   chroot.VerifyOutsideChroot()
   logging.basicConfig(
@@ -111,47 +167,40 @@
       '--chroot_path',
       default=os.path.join(os.path.expanduser('~'), 'chromiumos'),
       help='the path to the chroot (default: %(default)s)')
-  parser.add_argument('--package', help='target package to apply the patch to.')
   parser.add_argument(
       '--start_sha',
       default='llvm-next',
       help='LLVM SHA that the patch should start applying at. You can specify '
       '"llvm" or "llvm-next", as well. Defaults to %(default)s.')
   parser.add_argument(
-      '--sha', help='LLVM git SHA. Either this or --sha must be specified.')
+      '--sha',
+      required=True,
+      action='append',
+      help='The LLVM git SHA to cherry-pick.')
   args = parser.parse_args()
 
-  symlink = chroot.GetChrootEbuildPaths(args.chroot_path, [args.package])[0]
-
-  symlink = chroot.ConvertChrootPathsToAbsolutePaths(args.chroot_path,
-                                                     [symlink])[0]
-  symlink_dir = os.path.dirname(symlink)
-
-  patches_json_path = os.path.join(symlink_dir, 'files/PATCHES.json')
-  relative_patches_dir = ''
-  if 'llvm' in args.package:
-    relative_patches_dir = 'cherry'
-  patches_dir = os.path.join(symlink_dir, 'files', relative_patches_dir)
-
   llvm_config = git_llvm_rev.LLVMConfig(
       remote='origin', dir=get_llvm_hash.GetAndUpdateLLVMProjectInLLVMTools())
 
+  llvm_symlink = chroot.ConvertChrootPathsToAbsolutePaths(
+      args.chroot_path,
+      chroot.GetChrootEbuildPaths(args.chroot_path, ['sys-devel/llvm']))[0]
   start_sha = args.start_sha
   if start_sha == 'llvm':
-    start_sha = parse_ebuild_for_assignment(symlink_dir, 'LLVM_HASH')
-    logging.info('Autodetected llvm hash == %s', start_sha)
+    start_sha = parse_ebuild_for_assignment(
+        os.path.dirname(llvm_symlink), 'LLVM_HASH')
   elif start_sha == 'llvm-next':
-    start_sha = parse_ebuild_for_assignment(symlink_dir, 'LLVM_NEXT_HASH')
-    logging.info('Autodetected llvm-next hash == %s', start_sha)
+    start_sha = parse_ebuild_for_assignment(
+        os.path.dirname(llvm_symlink), 'LLVM_NEXT_HASH')
+  logging.info('Base llvm hash == %s', start_sha)
 
   start_sha = resolve_llvm_ref(llvm_config.dir, start_sha)
   start_rev = git_llvm_rev.translate_sha_to_rev(llvm_config, start_sha)
-  sha = resolve_llvm_ref(llvm_config.dir, args.sha)
-  rev = git_llvm_rev.translate_sha_to_rev(llvm_config, sha)
 
-  logging.info('Will cherrypick %s (%s), with start == %s', rev, sha, start_sha)
-  add_cherrypick(patches_json_path, patches_dir, relative_patches_dir,
-                 start_rev, llvm_config.dir, rev, sha)
+  for sha in args.sha:
+    sha = resolve_llvm_ref(llvm_config.dir, sha)
+    rev = git_llvm_rev.translate_sha_to_rev(llvm_config, sha)
+    cherry_pick(args.chroot_path, llvm_config.dir, start_rev, sha, rev)
   logging.info('Complete.')
 
 
diff --git a/llvm_tools/git.py b/llvm_tools/git.py
index 778d969..f38d5e7 100755
--- a/llvm_tools/git.py
+++ b/llvm_tools/git.py
@@ -46,10 +46,9 @@
   if not os.path.isdir(repo):
     raise ValueError('Invalid directory path provided: %s' % repo)
 
-  subprocess.check_output(['git', '-C', repo, 'reset', 'HEAD', '--hard'],
-                          encoding='utf-8')
+  subprocess.check_output(['git', '-C', repo, 'reset', 'HEAD', '--hard'])
 
-  subprocess.check_output(['repo', 'start', branch], cwd=repo, encoding='utf-8')
+  subprocess.check_output(['repo', 'start', branch], cwd=repo)
 
 
 def DeleteBranch(repo, branch):
@@ -66,14 +65,11 @@
   if not os.path.isdir(repo):
     raise ValueError('Invalid directory path provided: %s' % repo)
 
-  subprocess.check_output(['git', '-C', repo, 'checkout', 'cros/master'],
-                          encoding='utf-8')
+  subprocess.check_output(['git', '-C', repo, 'checkout', 'cros/master'])
 
-  subprocess.check_output(['git', '-C', repo, 'reset', 'HEAD', '--hard'],
-                          encoding='utf-8')
+  subprocess.check_output(['git', '-C', repo, 'reset', 'HEAD', '--hard'])
 
-  subprocess.check_output(['git', '-C', repo, 'branch', '-D', branch],
-                          encoding='utf-8')
+  subprocess.check_output(['git', '-C', repo, 'branch', '-D', branch])
 
 
 def UploadChanges(repo, branch, commit_messages):
@@ -102,28 +98,17 @@
     f.write('\n'.join(commit_messages))
     f.flush()
 
-    subprocess.check_output(['git', 'commit', '-F', f.name],
-                            cwd=repo,
-                            encoding='utf-8')
+    subprocess.check_output(['git', 'commit', '-F', f.name], cwd=repo)
 
   # Upload the changes for review.
-  # Pylint currently doesn't lint things in py3 mode, and py2 didn't allow
-  # users to specify `encoding`s for Popen. Hence, pylint is "wrong" here.
-  # pylint: disable=unexpected-keyword-arg
-  # The CL URL is sent to 'stderr', so need to redirect 'stderr' to 'stdout'.
-  upload_changes_obj = subprocess.Popen(
+  out = subprocess.check_output(
       ['repo', 'upload', '--yes', '--ne', '--no-verify',
        '--br=%s' % branch],
-      cwd=repo,
-      stdout=subprocess.PIPE,
       stderr=subprocess.STDOUT,
+      cwd=repo,
       encoding='utf-8')
 
-  out, _ = upload_changes_obj.communicate()
-
-  if upload_changes_obj.returncode:  # Failed to upload changes.
-    print(out)
-    raise ValueError('Failed to upload changes for review')
+  print(out)
 
   found_url = re.search(
       r'https://chromium-review.googlesource.com/c/'
diff --git a/llvm_tools/git_unittest.py b/llvm_tools/git_unittest.py
index 3931627..4792771 100755
--- a/llvm_tools/git_unittest.py
+++ b/llvm_tools/git_unittest.py
@@ -10,6 +10,7 @@
 
 import os
 import subprocess
+import tempfile
 import unittest
 import unittest.mock as mock
 
@@ -92,81 +93,52 @@
     mock_isdir.assert_called_once()
 
   @mock.patch.object(os.path, 'isdir', return_value=True)
-  @mock.patch.object(subprocess, 'check_output', 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 = 'invalid-branch-name'
-    commit_messages = ['Test message']
-
-    # Verify exception is raised when failed to upload the changes for review.
-    with self.assertRaises(ValueError) as err:
-      git.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_command_output_args = mock_command_output.call_args_list[0][0][0]
-    expected_mock_command_output_prefix = ['git', 'commit', '-F']
-    self.assertEqual(
-        mock_command_output_args[:len(expected_mock_command_output_prefix)],
-        expected_mock_command_output_prefix)
-
-    mock_repo_upload.assert_called_once()
-
-  @mock.patch.object(os.path, 'isdir', return_value=True)
-  @mock.patch.object(subprocess, 'check_output', 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
+  @mock.patch.object(subprocess, 'check_output')
+  @mock.patch.object(tempfile, 'NamedTemporaryFile')
+  def testSuccessfullyUploadedChangesForReview(self, mock_tempfile,
+                                               mock_commands, mock_isdir):
 
     path_to_repo = '/some/path/to/repo'
     branch = 'branch-name'
     commit_messages = ['Test message']
+    mock_tempfile.return_value.__enter__.return_value.name = 'tmp'
 
+    # A test CL generated by `repo upload`.
+    mock_commands.side_effect = [
+        None,
+        ('remote: https://chromium-review.googlesource.'
+         'com/c/chromiumos/overlays/chromiumos-overlay/'
+         '+/193147 Fix stdout')
+    ]
     change_list = git.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()
+    expected_command = [
+        'git', 'commit', '-F',
+        mock_tempfile.return_value.__enter__.return_value.name
+    ]
+    self.assertEqual(mock_commands.call_args_list[0],
+                     mock.call(expected_command, cwd=path_to_repo))
 
-    mock_repo_upload.assert_called_once()
+    expected_cmd = [
+        'repo', 'upload', '--yes', '--ne', '--no-verify',
+        '--br=%s' % branch
+    ]
+    self.assertEqual(
+        mock_commands.call_args_list[1],
+        mock.call(
+            expected_cmd,
+            stderr=subprocess.STDOUT,
+            cwd=path_to_repo,
+            encoding='utf-8'))
+
+    self.assertEqual(
+        change_list.url,
+        'https://chromium-review.googlesource.com/c/chromiumos/overlays/'
+        'chromiumos-overlay/+/193147')
 
 
 if __name__ == '__main__':
diff --git a/llvm_tools/update_chromeos_llvm_hash.py b/llvm_tools/update_chromeos_llvm_hash.py
index f8cb04e..e28fe69 100755
--- a/llvm_tools/update_chromeos_llvm_hash.py
+++ b/llvm_tools/update_chromeos_llvm_hash.py
@@ -19,9 +19,9 @@
 import argparse
 import os
 import re
+import subprocess
 
 from failure_modes import FailureModes
-from subprocess_helpers import ExecCommandAndCaptureOutput
 import chroot
 import get_llvm_hash
 import git
@@ -206,9 +206,7 @@
   parent_dir = os.path.dirname(ebuild_path)
 
   # Stage the changes.
-  stage_changes_cmd = ['git', '-C', parent_dir, 'add', ebuild_path]
-
-  ExecCommandAndCaptureOutput(stage_changes_cmd, verbose=verbose)
+  subprocess.check_output(['git', '-C', parent_dir, 'add', ebuild_path])
 
 
 def ReplaceLLVMHash(ebuild_lines, llvm_variant, git_hash, svn_version):
@@ -262,12 +260,10 @@
   if not is_changed:
     raise ValueError('Failed to uprev the symlink.')
 
-  symlink_dirname = os.path.dirname(symlink)
-
   # rename the symlink
-  cmd = ['git', '-C', symlink_dirname, 'mv', symlink, new_symlink]
-
-  ExecCommandAndCaptureOutput(cmd, verbose=verbose)
+  subprocess.check_output(
+      ['git', '-C',
+       os.path.dirname(symlink), 'mv', symlink, new_symlink])
 
 
 def UprevEbuildToVersion(symlink, svn_version):
@@ -311,23 +307,19 @@
   symlink_dir = os.path.dirname(symlink)
 
   # Rename the ebuild
-  cmd = ['git', '-C', symlink_dir, 'mv', ebuild, new_ebuild]
-  ExecCommandAndCaptureOutput(cmd, verbose=verbose)
+  subprocess.check_output(['git', '-C', symlink_dir, 'mv', ebuild, new_ebuild])
 
   # Create a symlink of the renamed ebuild
   new_symlink = new_ebuild[:-len('.ebuild')] + '-r1.ebuild'
-  cmd = ['ln', '-s', '-r', new_ebuild, new_symlink]
-  ExecCommandAndCaptureOutput(cmd, verbose=verbose)
+  subprocess.check_output(['ln', '-s', '-r', new_ebuild, new_symlink])
 
   if not os.path.islink(new_symlink):
     raise ValueError('Invalid symlink name: %s' % new_ebuild[:-len('.ebuild')])
 
-  cmd = ['git', '-C', symlink_dir, 'add', new_symlink]
-  ExecCommandAndCaptureOutput(cmd, verbose=verbose)
+  subprocess.check_output(['git', '-C', symlink_dir, 'add', new_symlink])
 
   # Remove the old symlink
-  cmd = ['git', '-C', symlink_dir, 'rm', symlink]
-  ExecCommandAndCaptureOutput(cmd, verbose=verbose)
+  subprocess.check_output(['git', '-C', symlink_dir, 'rm', symlink])
 
 
 def CreatePathDictionaryFromPackages(chroot_path, update_packages):
@@ -355,23 +347,19 @@
   return GetEbuildPathsFromSymLinkPaths(symlink_file_paths)
 
 
-def RemovePatchesFromFilesDir(patches_to_remove):
+def RemovePatchesFromFilesDir(patches):
   """Removes the patches from $FILESDIR of a package.
 
   Args:
-    patches_to_remove: A list where each entry is the absolute path to a patch.
+    patches: A list of absolute pathes of patches to remove
 
   Raises:
     ValueError: Failed to remove a patch in $FILESDIR.
   """
 
-  for cur_patch in patches_to_remove:
-    remove_patch_cmd = [
-        'git', '-C',
-        os.path.dirname(cur_patch), 'rm', '-f', cur_patch
-    ]
-
-    ExecCommandAndCaptureOutput(remove_patch_cmd, verbose=verbose)
+  for patch in patches:
+    subprocess.check_output(
+        ['git', '-C', os.path.dirname(patch), 'rm', '-f', patch])
 
 
 def StagePatchMetadataFileForCommit(patch_metadata_file_path):
@@ -390,12 +378,10 @@
         'Invalid patch metadata file provided: %s' % patch_metadata_file_path)
 
   # Cmd to stage the patch metadata file for commit.
-  stage_patch_file = [
+  subprocess.check_output([
       'git', '-C',
       os.path.dirname(patch_metadata_file_path), 'add', patch_metadata_file_path
-  ]
-
-  ExecCommandAndCaptureOutput(stage_patch_file, verbose=verbose)
+  ])
 
 
 def StagePackagesPatchResultsForCommit(package_info_dict, commit_messages):
diff --git a/llvm_tools/update_chromeos_llvm_hash_unittest.py b/llvm_tools/update_chromeos_llvm_hash_unittest.py
index 5f86c2b..205feb0 100755
--- a/llvm_tools/update_chromeos_llvm_hash_unittest.py
+++ b/llvm_tools/update_chromeos_llvm_hash_unittest.py
@@ -12,6 +12,7 @@
 from datetime import datetime
 import os
 import re
+import subprocess
 import unittest
 import unittest.mock as mock
 
@@ -102,14 +103,8 @@
 
     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)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyStageTheEbuildForCommitForLLVMHashUpdate(
       self, mock_stage_commit_command, mock_isfile):
 
@@ -145,14 +140,8 @@
 
     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)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyStageTheEbuildForCommitForLLVMNextHashUpdate(
       self, mock_stage_commit_command, mock_isfile):
 
@@ -251,10 +240,7 @@
 
   @mock.patch.object(os.path, 'islink', return_value=True)
   @mock.patch.object(os.path, 'realpath')
-  @mock.patch.object(
-      update_chromeos_llvm_hash,
-      'ExecCommandAndCaptureOutput',
-      return_value=None)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyUprevEbuildToVersionLLVM(self, mock_command_output,
                                                mock_realpath, mock_islink):
     symlink = '/path/to/llvm/llvm_pre3_p2-r10.ebuild'
@@ -282,26 +268,23 @@
 
     expected_cmd = ['git', '-C', symlink_dir, 'mv', ebuild, new_ebuild]
     self.assertEqual(mock_command_output.call_args_list[0],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
     expected_cmd = ['ln', '-s', '-r', new_ebuild, new_symlink]
     self.assertEqual(mock_command_output.call_args_list[1],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
     expected_cmd = ['git', '-C', symlink_dir, 'add', new_symlink]
     self.assertEqual(mock_command_output.call_args_list[2],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
     expected_cmd = ['git', '-C', symlink_dir, 'rm', symlink]
     self.assertEqual(mock_command_output.call_args_list[3],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
   @mock.patch.object(os.path, 'islink', return_value=True)
   @mock.patch.object(os.path, 'realpath')
-  @mock.patch.object(
-      update_chromeos_llvm_hash,
-      'ExecCommandAndCaptureOutput',
-      return_value=None)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyUprevEbuildToVersionNonLLVM(self, mock_command_output,
                                                   mock_realpath, mock_islink):
     symlink = '/path/to/compiler-rt/compiler-rt_pre3_p2-r10.ebuild'
@@ -325,28 +308,22 @@
 
     expected_cmd = ['git', '-C', symlink_dir, 'mv', ebuild, new_ebuild]
     self.assertEqual(mock_command_output.call_args_list[0],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
     expected_cmd = ['ln', '-s', '-r', new_ebuild, new_symlink]
     self.assertEqual(mock_command_output.call_args_list[1],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
     expected_cmd = ['git', '-C', symlink_dir, 'add', new_symlink]
     self.assertEqual(mock_command_output.call_args_list[2],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
     expected_cmd = ['git', '-C', symlink_dir, 'rm', symlink]
     self.assertEqual(mock_command_output.call_args_list[3],
-                     mock.call(expected_cmd, verbose=False))
+                     mock.call(expected_cmd))
 
-  # Simulate 'os.path.islink' when a valid symbolic link is passed in.
   @mock.patch.object(os.path, 'islink', return_value=True)
-  # Simulate 'RunCommandWOutput' when successfully added the upreved symlink
-  # for commit.
-  @mock.patch.object(
-      update_chromeos_llvm_hash,
-      'ExecCommandAndCaptureOutput',
-      return_value=None)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyUprevEbuildSymlink(self, mock_command_output,
                                          mock_islink):
     symlink_to_uprev = '/symlink/to/package-r1.ebuild'
@@ -452,12 +429,7 @@
     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)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyRemovedPatchesFromFilesDir(self, mock_run_cmd):
     patches_to_remove_list = [
         '/abs/path/to/filesdir/cherry/fix_output.patch',
@@ -468,8 +440,6 @@
 
     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'
@@ -486,15 +456,8 @@
 
     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)
+  @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testSuccessfullyStagedPatchMetadataFileForCommit(self, mock_run_cmd,
                                                        _mock_isfile):
 
diff --git a/llvm_tools/update_packages_and_run_tests_unittest.py b/llvm_tools/update_packages_and_run_tests_unittest.py
index a4e9242..d852893 100755
--- a/llvm_tools/update_packages_and_run_tests_unittest.py
+++ b/llvm_tools/update_packages_and_run_tests_unittest.py
@@ -331,7 +331,6 @@
 
     self.assertIn('is not a valid llvm trybot', str(context.exception))
 
-  # Mock ExecCommandAndCaptureOutput for the gerrit command execution.
   @mock.patch.object(subprocess, 'check_output', return_value=None)
   def testStartCQDryRunNoDeps(self, mock_exec_cmd):
     chroot_path = '/abs/path/to/chroot'